The cascade that protects margin
A vendor cost update is rarely a single-row change. A 5% butter increase from Bongards moves the landed cost on every assembly that uses butter, which moves the margin band on every active bid line containing those assemblies, which may breach the 22% gross margin floor on customer-facing quotes. This workflow encodes that cascade so Mike sees the second- and third-order impact before he says yes.
The contract lives in workflow_definitions WHERE workflow_type='vendor_cost_review'. Trigger is typically event (a new vendor invoice or RESTlet poll detects a price change), risk level 3, expected duration 20 minutes including Mike's review of the impact report.
The thing this workflow gets right that a manual spreadsheet doesn't: it walks the BOM graph. If Bongards Salted Butter is an ingredient in Right Start Breakfast Burrito and that's a component of the SY2026 NYC DOE bid, the workflow shows you that chain in one view.
Trigger conditions
- Vendor sends a new price list (typically quarterly for ingredient vendors).
- CME trailing-week price moves the Bongards butter peg outside the band.
- USDA entitlement allocation changes the net cost on a barrel cheddar line.
- Mike negotiates a new contract price on a phone call.
- Reconciliation cron detects a posted bill price differs from the master cost.
Bongards raises butter price 5%
Bongards Creameries (vendor #387) emails a new price list: salted butter (item #BTRSLT-1) moves from $2.80/lb to $2.94/lb (+5.0%). The cost ingestion page parses the email attachment; a proposed action lands in Mike's queue.
The workflow runs the impact simulation: 14 assemblies use BTRSLT-1, the landed cost on Right Start Breakfast Burrito moves from $3.42 to $3.51 (+2.6%), the SY2026 NYC DOE bid line for that assembly drops from 22.0% to 19.4% gross margin — below the 20% floor. The workflow surfaces this with a red band on that bid line.
Mike has two choices: accept the cost and re-price the bid (triggers bid price update workflow), or push back on Bongards. He approves the cost change but routes the SY2026 line to a re-pricing follow-up. Cascade total: vendor_costs updated, 14 assemblies recomputed, 31 open bid lines flagged, 4 active customer quotes flagged for review.
The four beats
-
01
Detect the change (auto)
vendor_last_cost_changecompares the incoming cost tovendor_costs.current_cost. If the delta exceeds the per-item tolerance (default 0.5%), a row is written toproposed_actionswithaction_type='vendor_cost_update'. -
02
Simulate the impact (auto)
simulate_cost_increasewalks the BOM graph frombom_components, recomputes each affectedassembly_cost_rollupin memory (no writes), and joins tobid_lines+quote_linesto identify margin breaches. A structured impact report is attached to the proposed action. -
03
Propose + approve (HITL)
Mike sees the impact report in
/proposed-actions.html: cost delta, list of affected assemblies, list of margin-breach bid lines, recommended follow-ups. He approves or rejects. R560 atomic claim applies — no double-approve race. -
04
Apply + recompute (auto)
propose_bulk_cost_basiswrites the new cost tovendor_costs, appends tovendor_cost_history, runsrecompute_assembly_cost_rollupon each affected assembly, and emits onevendor.cost_changedevent plus anassembly.cost_recomputedevent per assembly. Bid-line and quote-line flags are written tomargin_breach_queuefor follow-up workflows.
What's different after the workflow runs
vendor_costsreflects the new price;vendor_cost_historyhas the audit row.- Every dependent row in
assembly_cost_rollupshows the new landed cost. - Margin-breach bid lines and quotes are surfaced for Mike's next-day queue.
- Downstream subscribers (bid price update, customer quote) receive the event and can re-trigger.
- If Mike opts to push the cost back to NS, the vendor record update sibling workflow handles that.
What can go wrong and how to recover
Rare but possible if an assembly accidentally references itself. recompute_assembly_cost_rollup uses a recursive CTE with depth limit 6. Beyond that it aborts with BOM_CYCLE_DETECTED. Manual fix: untangle in bom_components.
If Mike approves the cost but doesn't follow up on the margin-breach queue, customers may see stale (below-floor) prices. The breach queue has a 7-day SLA; daily digest highlights overdue items.
If two proposed actions land on the same vendor_item, the second one's impact simulation may use stale cost from the first. The PushMutexDO Durable Object serializes vendor updates per vendor to prevent this.
Adjacent workflows + diagrams
Code paths + invariants
| Concern | Where |
|---|---|
| Workflow contract | workflow_definitions WHERE workflow_type='vendor_cost_review' |
| Change detection | src/chat_tools/impls.ts vendor_last_cost_change |
| Impact simulation | src/chat_tools/impls.ts simulate_cost_increase |
| Bulk apply | src/chat_tools/impls.ts propose_bulk_cost_basis |
| Assembly recompute | recompute_assembly_cost_rollup (recursive CTE depth 6) |
| Concurrency | src/durable_objects.ts PushMutexDO |
| Event emission | events table — vendor.cost_changed, assembly.cost_recomputed |
| HITL invariant | ADR-031 — vendor cost change never auto-applies |