The Pillar 4 build — cost, capacity, consumption
The NS assembly build flow is the operational sibling of the work order lifecycle. Where the WO lifecycle tracks what NS records, the assembly build flow tracks what the platform decides before NS records anything — BOM resolution, cost rollup against current vendor costs, capacity check, and HITL authorization when the cost surface is about to move significantly.
It runs every time a build request hits the system — from an operator, a WO, or a sales-driven backorder. The first six steps happen before NS posts a build; the last four are the consumption + creation + refresh tail.
Diagram: ns-assembly-build-flow.html. One STUB: labor capacity check — there is no formal capacity calendar table yet, so the check is currently a hand-wave that returns true unless inventory is short.
Trigger conditions
- Operator manually requests N units of an assembly via NS UI.
- Open work order requires components staged for a release.
- Sales order requires assembled stock not on the shelf.
- Pillar 4 annual roll triggers a rebuild of cost rollups for all assemblies.
- Vendor cost moved — flow runs in re-cost mode (no actual build) to refresh
assembly_cost_rollup.
Authorization fires if rollup cost > $5K OR drift_pct_vs_prior > 8%. Either condition stages a row in proposed_actions — risk_level 3, action_type='assembly_build_review'.
Build 200 cases of Melt Mates — cheese cost moved
Wednesday. NYC DOE bid B5875 requires 200 cases of MM-CHEESE-MELT-22 by Friday. SO posts; allocation can't satisfy from FG; build request opens. BOM resolves: cheddar (Bongards), bread (PUB house), butter, seasoning. assembly_cost_rollup recomputes: cheese ran +9.1% WoW (CME trailing-week formula caught the spike). Total cost moves from $14.42 to $15.71 — 8.9% drift, over threshold.
HITL fires. Mike sees the card at 11:04 — with the Bongards CME table, the 35% moisture adjustment, and the drift_pct sparkline embedded. He approves at 11:07 (knows it's real; Bongards moved this week). Capacity check: STUB — returns ok unless inventory short. Components debit; the floor builds 200 cases; NS posts the build at 16:30. Assembly created; cost recalc anchors the new $15.71 number on the cost surface. Spec sheet didn't need refresh — pack and allergens unchanged.
The ten beats
-
01
Build request — manual, WO, or SO-driven
An operator, an open work order, or a customer order requiring assembled stock fires a build request. The source is recorded for downstream tracing.
-
02
BOM resolution
Pull
assembly_bomrows for theassembly_id. Validate everyline_keyexists initemsmaster, has a current cost, and has on-hand availability. Missing or zero-cost lines abort the request with an actionable error. -
03
Cost rollup — Pillar 4 logic
Refresh
assembly_cost_rollupby summing raw component cost, packaging, labor estimate, overhead allocation, and freight per pillar 4 cost-surface logic. Bongards, Cardinal, and Ace Endico items pick up their special formulas (CME trailing week, USDA drawdown, etc.). -
04
Capacity check — STUB
Confirm labor hours available and components in inventory. Components check is real (reads
inventory_balance). STUB: labor capacity is not formally tracked — there's nocapacity_calendartable, so the check returns true unless a hard inventory short forces a queue. This is acceptable today because we run a single shift, but it won't scale beyond two production lines. -
05
HITL authorization — $5K or 8% gate
If
rollup_cost > $5,000ORdrift_pct_vs_prior > 0.08, a row is staged inproposed_actionswithaction_type='assembly_build_review', risk_level 3. Mike approves at/proposed-actions.html. Below threshold, the build proceeds without HITL. -
06
Component consumption — debit inventory
Debit
inventory_balanceper BOM line for the build qty. NS posts aworkorderissue(orinventoryadjustmentfor non-WO builds). D1 mirrors negative-qty rows intransaction_lines. -
07
Build operation — the physical build
The floor produces the assembly. System-side this is the NS
buildtransaction record that links consumption (step 6) to creation (step 8). The transaction id is the durable cross-reference. -
08
Assembly created — credit inventory
Credit
inventory_balancefor the assembly item at the production location. The assembly is now allocatable for the triggering SO or for general sale. -
09
Spec sheet refresh — if claims changed
If claims, allergens, or pack format changed in the BOM since the last spec render, regenerate via the spec-sheet-pipeline workflow. Otherwise skip. Driven by a content hash on the BOM spec-relevant fields.
-
10
Cost recalc — final actuals
assembly_cost_rollupre-runs with the final actuals from steps 6 & 8.drift_pct_vs_priorsurfaces on the item entity page and the admin-dashboard cost-surface tile. The new value becomes the baseline for the next build.
What's different after the build
- FG inventory reflects the new build; components decremented.
- Cost rollup carries the new component costs forward.
- Spec sheet regenerated if BOM claims drifted.
- Drift signal feeds the cost-surface tile and any vendor cost watchers.
What can go wrong
With no capacity calendar, two parallel build requests can claim the same labor hours. Today we mitigate by running a single shift with one production scheduler (Susan). Detection: WOs that miss promised dates. Long-term fix: capacity_calendar table + per-line shift inventory.
Resolution aborts. The build request returns an error naming the missing line_key. Recovery: create the item in NS, wait for hot-tier mirror, retry.
If vendor_cost_update workflow hasn't run since the last vendor invoice, rollup carries old number. Detection: drift_pct_vs_prior unexpectedly low when invoices show a change. Recovery: trigger vendor_cost_update for the affected vendor.
Mike doesn't approve the build_review in time; production blocks. The decide endpoint emits hitl.deferred; the operator can fall back to manual NS build but loses the cost rollup audit trail.
Adjacent flows + diagrams
Code paths + invariants
| Concern | Where |
|---|---|
| BOM source | assembly_bom (D1 mirror) |
| Cost rollup | assembly_cost_rollup (Pillar 4) |
| HITL workflow | workflow_type='assembly_build_review' |
| Drift threshold | guardrails.assembly_drift_threshold (default 0.08) |
| Cost threshold | guardrails.assembly_cost_threshold (default $5,000) |
| Spec sheet regen | spec_review_queue, content_hash trigger |
| Drift surface | /item/<code> (Pages Function), admin-dashboard |
| STUB — capacity | no capacity_calendar table; check returns true |
Dated trail · spot stale claims
Dated trail of when this doc was last touched, what changed, and what to look at if it feels stale.
| Date | Round | Change | Touched by |
|---|---|---|---|
2026-05-26 | R586 | Added CHANGELOG · SCHEMA · RUNBOOK · BACKLOG sections — wiki became best-in-class operating documentation. | Mike + Claude |
2026-05-25 | R584/R585 | Wiki originally shipped — 8-section structure (hero / what / when / steps / outcomes / failure-modes / related / for-developers). | Mike + Claude |
workflow_definitions WHERE workflow_type='ns_assembly_build_flow' before acting on these claims.The machine-readable spec
Canonical fields, table names, endpoint signatures. What code should match, what tests should assert. workflow_type · ns_assembly_build_flow · risk_level · N/A (read-model).
Inputs (required + optional)
| Field | Type | Description |
|---|---|---|
build_id | integer | NS assembly build internal ID. Required. |
D1 tables written
| Table | Operation | Trigger |
|---|---|---|
assemblybuild | UPSERT (sync) | tier=warm |
assemblybuild_lines | DELETE + INSERT | delete_by_parent |
inventory_snapshots | INSERT | Post-build inventory delta |
Endpoints called
| Method | Path | Purpose |
|---|---|---|
GET | /api/assemblybuild/:id | Live lookup |
POST | /api/sync/run?table=assemblybuild | Force sync |
Events fired
| event_type | When | Subscribers |
|---|---|---|
ns.assemblybuild.synced | Post-sync | audit |
It broke at 2am — what now
Different from "how do I use this." This is the page Mike pulls up when something is wrong: logs to check, recovery steps, who to escalate to.
Scenario · Build completed in NS but inventory didn't reflect in D1
Sync lag or assemblybuild_lines missed.
- Force sync:
POST /api/sync/run?tier=warm&table=assemblybuild - Then sync inventory:
POST /api/sync/run?tier=warm&table=inventoryitem - Verify: Compare D1 quantityonhand to NS via ns_suiteql.
Scenario · Build shows committed in D1 but cancelled in NS
Reverse direction — NS cancellation didn't propagate.
- Force sync: Re-pull assemblybuild row; should show cancelled status.
- Reconcile inventory: Cancelled builds should release inventory; verify.
Logs to check
workflow_run_log· top-level run auditworkflow_step_log· per-step traceworkflow_verify_results· post-window verify outcomescron_locks· stuck cron lock detectionevents· workflow.completed / workflow.failed event trailreflexion_log· per-run narrative (if reflexion_enabled)npx wrangler tail· live Worker logs
Kill switch · emergency stop
If this workflow is misbehaving in a high-impact way (creating bad proposed_actions in volume, pushing wrong things to NS), flip a kill switch:
kill:ns_writes· stops every NS push platform-widekill:proposed_apply· stops HITL approvals from executing fan-outkill:high_risk_ops· stops risk_level >= 4 fan-out
See kill-switches-state-machine.html for the full state machine + recovery procedure.
Escalation
Primary: Mike Levine (single-admin) · mikelevine@globalfoodsolutions.co. For prolonged outage during business hours, notify warehouse lead + accounting lead so they can defer dependent work.
What's not done · what's uncertain
What's not done, what's uncertain, what we punted. Captured so it survives context switches and doesn't die in someone's head.
-
STUB
Real-time build event from NS
Today polling.
-
OPEN
Variance tracking
Built 100 but inventory shows 98. Today no variance audit.
-
DEFER
Build → SO linkage when build was for a specific SO
Today inferred. Direct FK would help.