Assembly build + BOM review risk 3 · mediumOtherreflexion on

workflow_type: assembly_build_review · owner: — · contract v2

Query an assembly build, review its current BOM + cost rollup + customer-program usage + spec deviations, propose BOM line changes or assembly-header field updates, and after HITL approval push back to NS plus recompute every downstream cost rollup, regenerate spec sheets for impacted assemblies, and invalidate caches. The assembly catalog cleanup workhorse.

0 · Visual flow archify workflow graph (6 lanes: trigger → context → HITL → fan-out → post → verify)

Part 1 of 2
01 / Trigger 02 / Context + preconditions 03 / HITL gate (Mike approves) 04 / Fan-out (Part 1 of 2) 05 / Post actions (log_run + reflexion + events) 06 / Verify (SQL / R2 / HTTP checks) How this workflow gets kicked off. Could be a chat-tool invocation, a cron tick, an inbound event (e.g. price.changed), or Mike clicking 'execute' from an admin page. TRIGGER kind: ? glob: assembly.cost_drift_detected invoker: Mike (single-admin) risk_level: 3i Trigger manual_or_event risk 3 Before deciding anything, pull related data from D1 (the local mirror of NetSuite). Each query loads a slice of the entity's current state so the AI and Mike can review before any writes happen. LOAD CONTEXT (D1 queries before fan-out) current_assembly: SELECT id, item_code, description, custitem_brand, custitem_pack_size, custitem… current_bom: SELECT bom_line_id, line_key, line_description, quantity, unit, source_vendor, … current_cost_rollup: SELECT raw_cost, packaging_cost, labor_cost, overhead_cost, freight_cost, fob_d… customer_programs_using: SELECT ap.program_id, cp.customer_name, cp.program_name, cp.contract_price, cp.… active_pricing_rows: SELECT customer_name, case_price, cost_basis, school_year FROM pricing_master W… spec_sheet: SELECT spec_id, spec_status, spec_version, claims_json, last_updated FROM spec_… recent_builds: SELECT COUNT(*) AS build_count_90d FROM transactions WHERE type='Build' AND ent…i Load context 7 D1 queries Safety checks that must pass before any writes. 'block' severity halts the run; 'warn' surfaces a warning but continues. Without these, a bad input could cascade into NetSuite. PRECONDITIONS (checked before fan-out) [block] assembly_exists: current_assembly.id present [block] either_line_or_header_changes: line_changes present OR header_changes present [block] rationale_present: rationale present [warn] no_active_customer_program_locked: customer_programs_using is null [warn] cost_rollup_fresh: current_cost_rollup.last_computed >= date('now','-7 days')i Preconditions 5 checks The HITL (Human-In-The-Loop) gate. The workflow stages a proposed_action and waits for Mike to approve in /proposed-actions.html. Only fires when risk_level >= 3. This is the invariant: no NS write happens without Mike's go-ahead. HITL GATE (Mike approves before fan-out) action_type: workflow_assembly_build_review entity_ref: workflow:assembly_build_review:run_<run_id> approver: mike (single-admin) risk_gate: >= 3 (this workflow = 3) approval window: typical <= 60 min envelope: proposed_actions row staged by runneri Mike approves stage_proposed_action risk ≥ 3 gate Queue a write for Mike's HITL approval - staged in the proposed_actions table with status='pending'. Doesn't touch NetSuite directly. Real implementation. FAN-OUT: stage_proposed_action kind: stage_proposed_action (not in contract — diagram-only annotation)i stage_proposed_action stage_proposed_action REAL Queue a write for Mike's HITL approval - staged in the proposed_actions table with status='pending'. Doesn't touch NetSuite directly. Real implementation. FAN-OUT: stage_proposed_action kind: stage_proposed_action (not in contract — diagram-only annotation)i stage_proposed_action stage_proposed_action REAL Wipe one or more KV cache keys so the next read pulls fresh data instead of stale cache. Real implementation - runs synchronously. FAN-OUT: kv_invalidate kind: kv_invalidate (not in contract — diagram-only annotation)i kv_invalidate kv_invalidate REAL Write to the local D1 mirror (the read-side cache of NetSuite). Used for derived data and platform state. STUB today - per-tool d1_write logic lives in chat_tools/impls.ts. FAN-OUT: d1_write kind: d1_write (not in contract — diagram-only annotation)i d1_write d1_write STUB Wipe one or more KV cache keys so the next read pulls fresh data instead of stale cache. Real implementation - runs synchronously. FAN-OUT: kv_invalidate kind: kv_invalidate (not in contract — diagram-only annotation)i kv_invalidate kv_invalidate REAL Invoke an existing chat tool (one of 50+ registered in tool_registry) as part of the cascade. STUB today - runner doesn't yet dispatch. FAN-OUT: chat_tool kind: chat_tool (not in contract — diagram-only annotation)i chat_tool chat_tool STUB Always-runs at the end of every workflow execution: writes a row to workflow_run_log with status, duration, step counts, and errors. Real implementation. POST ACTION: log_run -> D1: workflow_run_log fields: run_id, workflow_type, status, started_at, completed_at, summary_json source: runner automatic (always)i log_run workflow_run_log REAL Writes an entry to reflexion_log so the AI 'remembers' what happened. Only fires if the contract has reflexion_enabled=1. Future workflows can search this log for prior context. Real implementation. POST ACTION: reflexion -> D1: reflexion_log tags: assembly_build_review reflexion_enabled: True fields: run_id, narrative, tags source: runner automatic (when reflexion_enabled=1)i reflexion reflexion_log REAL Fires a workflow.completed / workflow.partial / workflow.failed event into the event ledger so downstream subscriptions can react. Uses an idempotency_key so producer retries collapse. Real implementation (R564). POST ACTION: event -> Event ledger (recordEvent) types: workflow.completed | workflow.partial | workflow.failed idempotency_key per run_id source: runner automatici event workflow.completed REAL A post-execution sanity check. The runner stages this with status='pending'; the verify-scheduler cron (every :08 and :38 hourly) wakes up after the configured window (e.g. +24h) and executes the sql_check, then flips status to pass/fail/timeout. Real implementation (R564). VERIFY: assembly_header_actua… scheduler: verify cron @ :08/:38 (R563)i assembly_header_actua… ≤60m A post-execution sanity check. The runner stages this with status='pending'; the verify-scheduler cron (every :08 and :38 hourly) wakes up after the configured window (e.g. +24h) and executes the sql_check, then flips status to pass/fail/timeout. Real implementation (R564). VERIFY: bom_line_count_change… scheduler: verify cron @ :08/:38 (R563)i bom_line_count_change… ≤60m A post-execution sanity check. The runner stages this with status='pending'; the verify-scheduler cron (every :08 and :38 hourly) wakes up after the configured window (e.g. +24h) and executes the sql_check, then flips status to pass/fail/timeout. Real implementation (R564). VERIFY: cost_rollup_recomputed scheduler: verify cron @ :08/:38 (R563) result row: workflow_verify_resultsi cost_rollup_recomputed ≤60m A post-execution sanity check. The runner stages this with status='pending'; the verify-scheduler cron (every :08 and :38 hourly) wakes up after the configured window (e.g. +24h) and executes the sql_check, then flips status to pass/fail/timeout. Real implementation (R564). VERIFY: no_orphaned_pricing scheduler: verify cron @ :08/:38 (R563) result row: workflow_verify_resultsi no_orphaned_pricing ≤60m Legend User UI Agent logic Policy Tool action Context / trace
Part 2 of 2
01 / Trigger 02 / Context + preconditions 03 / HITL gate (Mike approves) 04 / Fan-out (Part 2 of 2) 05 / Post actions (log_run + reflexion + events) 06 / Verify (SQL / R2 / HTTP checks) How this workflow gets kicked off. Could be a chat-tool invocation, a cron tick, an inbound event (e.g. price.changed), or Mike clicking 'execute' from an admin page. TRIGGER kind: ? glob: assembly.cost_drift_detected invoker: Mike (single-admin) risk_level: 3i Trigger manual_or_event risk 3 Before deciding anything, pull related data from D1 (the local mirror of NetSuite). Each query loads a slice of the entity's current state so the AI and Mike can review before any writes happen. LOAD CONTEXT (D1 queries before fan-out) current_assembly: SELECT id, item_code, description, custitem_brand, custitem_pack_size, custitem… current_bom: SELECT bom_line_id, line_key, line_description, quantity, unit, source_vendor, … current_cost_rollup: SELECT raw_cost, packaging_cost, labor_cost, overhead_cost, freight_cost, fob_d… customer_programs_using: SELECT ap.program_id, cp.customer_name, cp.program_name, cp.contract_price, cp.… active_pricing_rows: SELECT customer_name, case_price, cost_basis, school_year FROM pricing_master W… spec_sheet: SELECT spec_id, spec_status, spec_version, claims_json, last_updated FROM spec_… recent_builds: SELECT COUNT(*) AS build_count_90d FROM transactions WHERE type='Build' AND ent…i Load context 7 D1 queries Safety checks that must pass before any writes. 'block' severity halts the run; 'warn' surfaces a warning but continues. Without these, a bad input could cascade into NetSuite. PRECONDITIONS (checked before fan-out) [block] assembly_exists: current_assembly.id present [block] either_line_or_header_changes: line_changes present OR header_changes present [block] rationale_present: rationale present [warn] no_active_customer_program_locked: customer_programs_using is null [warn] cost_rollup_fresh: current_cost_rollup.last_computed >= date('now','-7 days')i Preconditions 5 checks The HITL (Human-In-The-Loop) gate. The workflow stages a proposed_action and waits for Mike to approve in /proposed-actions.html. Only fires when risk_level >= 3. This is the invariant: no NS write happens without Mike's go-ahead. HITL GATE (Mike approves before fan-out) action_type: workflow_assembly_build_review entity_ref: workflow:assembly_build_review:run_<run_id> approver: mike (single-admin) risk_gate: >= 3 (this workflow = 3) approval window: typical <= 60 min envelope: proposed_actions row staged by runneri Mike approves stage_proposed_action risk ≥ 3 gate Set a follow-up flag on a row (e.g., 'needs_review') so a downstream cron or human picks it up later. STUB today. FAN-OUT: flag kind: flag (not in contract — diagram-only annotation)i flag flag STUB Draft an email and stage it as a proposed_action for Mike's review. The email is NOT sent - only drafted + queued. STUB today. FAN-OUT: hitl_email_draft kind: hitl_email_draft (not in contract — diagram-only annotation)i hitl_email_draft hitl_email_draft STUB Spawn a Cloudflare Workflow durable-execution class (e.g., AnnualRollWorkflow) for long-running multi-step work. STUB today. FAN-OUT: workflow_class_invoke kind: workflow_class_invoke (not in contract — diagram-only annotation)i workflow_class_invoke workflow_class_invoke STUB Always-runs at the end of every workflow execution: writes a row to workflow_run_log with status, duration, step counts, and errors. Real implementation. POST ACTION: log_run -> D1: workflow_run_log fields: run_id, workflow_type, status, started_at, completed_at, summary_json source: runner automatic (always)i log_run workflow_run_log REAL Writes an entry to reflexion_log so the AI 'remembers' what happened. Only fires if the contract has reflexion_enabled=1. Future workflows can search this log for prior context. Real implementation. POST ACTION: reflexion -> D1: reflexion_log tags: assembly_build_review reflexion_enabled: True fields: run_id, narrative, tags source: runner automatic (when reflexion_enabled=1)i reflexion reflexion_log REAL Fires a workflow.completed / workflow.partial / workflow.failed event into the event ledger so downstream subscriptions can react. Uses an idempotency_key so producer retries collapse. Real implementation (R564). POST ACTION: event -> Event ledger (recordEvent) types: workflow.completed | workflow.partial | workflow.failed idempotency_key per run_id source: runner automatici event workflow.completed REAL A post-execution sanity check. The runner stages this with status='pending'; the verify-scheduler cron (every :08 and :38 hourly) wakes up after the configured window (e.g. +24h) and executes the sql_check, then flips status to pass/fail/timeout. Real implementation (R564). VERIFY: assembly_header_actua… scheduler: verify cron @ :08/:38 (R563)i assembly_header_actua… ≤60m A post-execution sanity check. The runner stages this with status='pending'; the verify-scheduler cron (every :08 and :38 hourly) wakes up after the configured window (e.g. +24h) and executes the sql_check, then flips status to pass/fail/timeout. Real implementation (R564). VERIFY: bom_line_count_change… scheduler: verify cron @ :08/:38 (R563)i bom_line_count_change… ≤60m A post-execution sanity check. The runner stages this with status='pending'; the verify-scheduler cron (every :08 and :38 hourly) wakes up after the configured window (e.g. +24h) and executes the sql_check, then flips status to pass/fail/timeout. Real implementation (R564). VERIFY: cost_rollup_recomputed scheduler: verify cron @ :08/:38 (R563) result row: workflow_verify_resultsi cost_rollup_recomputed ≤60m A post-execution sanity check. The runner stages this with status='pending'; the verify-scheduler cron (every :08 and :38 hourly) wakes up after the configured window (e.g. +24h) and executes the sql_check, then flips status to pass/fail/timeout. Real implementation (R564). VERIFY: no_orphaned_pricing scheduler: verify cron @ :08/:38 (R563) result row: workflow_verify_resultsi no_orphaned_pricing ≤60m Legend User UI Agent logic Policy Tool action Context / trace

1 · Trigger FIRES WHEN…

kind
event pattern
assembly.cost_drift_detected
kind
"manual_or_event"
cron
null
description
"Manually invoked from /assembly/<code> review page OR fired by weekly assembly rollup cron when cost_drift_pct exceeds threshold (R533)."

2 · Inputs required

No declared inputs.

3 · Context loaded D1 queries run before fan-out

name
current_assembly
tables
assemblies
binds
["assembly_id","assembly_code"]
SELECT
  id, item_code, description, custitem_brand, custitem_pack_size, custitem_case_count, custitem_yield, lastmodifieddate
  FROM assemblies
  WHERE (id = ?1
  OR UPPER(item_code) = UPPER(?2))
  AND deleted_at IS NULL
  LIMIT 1
name
current_bom
tables
assembly_bom
binds
["assembly_id"]
SELECT
  bom_line_id, line_key, line_description, quantity, unit, source_vendor, unit_cost, line_total, deleted_at
  FROM assembly_bom
  WHERE assembly_id = ?1
  AND deleted_at IS NULL
  ORDER BY bom_line_id
name
current_cost_rollup
tables
assembly_cost_rollup
binds
["assembly_id"]
SELECT
  raw_cost, packaging_cost, labor_cost, overhead_cost, freight_cost, fob_dock_cost, last_computed, drift_pct_vs_prior
  FROM assembly_cost_rollup
  WHERE assembly_id = ?1
name
customer_programs_using
tables
assembly_programs · customer_programs
binds
["assembly_id"]
SELECT
  ap.program_id, cp.customer_name, cp.program_name, cp.contract_price, cp.status
  FROM assembly_programs ap
  JOIN customer_programs cp ON cp.program_id = ap.program_id
  WHERE ap.assembly_id = ?1
  AND cp.status = 'active'
name
active_pricing_rows
tables
pricing_master · assemblies
binds
["assembly_id"]
SELECT
  customer_name, case_price, cost_basis, school_year
  FROM pricing_master
  WHERE UPPER(item_code) = (SELECT UPPER(item_code)
  FROM assemblies
  WHERE id = ?1)
  AND status='active'
name
spec_sheet
tables
spec_items · assemblies
binds
["assembly_id"]
SELECT
  spec_id, spec_status, spec_version, claims_json, last_updated
  FROM spec_items
  WHERE UPPER(item_code) = (SELECT UPPER(item_code)
  FROM assemblies
  WHERE id = ?1)
  ORDER BY spec_version DESC
  LIMIT 1
name
recent_builds
tables
transactions · assemblies
binds
["assembly_id"]
SELECT
  COUNT(*) AS build_count_90d
  FROM transactions
  WHERE type='Build'
  AND entity_name LIKE '%'||(SELECT item_code
  FROM assemblies
  WHERE id=?1)||'%'
  AND trandate >= date('now','-90 days')

4 · Preconditions checked before any fan-out

checkruleseverity
assembly_existscurrent_assembly.id presentblock
either_line_or_header_changesline_changes present OR header_changes presentblock
rationale_presentrationale presentblock
no_active_customer_program_lockedcustomer_programs_using is nullwarn
cost_rollup_freshcurrent_cost_rollup.last_computed >= date('now','-7 days')warn

5 · HITL gate

Risk level 3 ≥ 3 — runner stages a proposed_actions row before fan-out runs. Mike must approve in proposed-actions.html before any side-effect step executes (real or stub).

action_type
workflow_assembly_build_review (proposal envelope)
entity_ref
workflow:assembly_build_review:run_<run_id>
approver
mike (single-admin)

6 · Fan-out targets 9 total · 4 real · 5 stub

#1step_1 stage_proposed_action REAL

action_type
assembly_header_update
entity_type
assembly
entity_ref
{{assembly_id}}
step_id
01_stage_assembly_header_update
conditional
header_changes present
description
Stage NS-bound assembly header update for HITL review.

#2step_2 stage_proposed_action REAL

action_type
assembly_bom_update
entity_type
assembly_bom
entity_ref
{{assembly_id}}
step_id
02_stage_bom_line_updates
conditional
line_changes present
description
Stage NS-bound BOM line add/update/remove batch for HITL review. Each line change creates one proposed_action row OR they bundle, per cascade policy.

#3step_3 kv_invalidate REAL

keys
assembly:{{assembly_code}}
assembly_full:{{assembly_id}}
bom:{{assembly_id}}
step_id
03_invalidate_assembly_cache
description
Bust live assembly + BOM caches.

#4step_4 d1_write STUB

table
assembly_cost_rollup
step_id
04_recompute_cost_rollup
operation
recompute
conditional
line_changes present
description
Recompute raw + packaging + labor + overhead + freight from the new BOM. Persist drift_pct_vs_prior.
stub — not yet implemented in src/lib/workflow_runner.ts (kind d1_write hits the placeholder branch at line ~340 and emits step status 'stub'). Documented intent only.

#5step_5 kv_invalidate REAL

keys
(none)
step_id
05_invalidate_pricing_cache_for_pricing_rows
description
Each (customer, school_year) keyed cache that touched this assembly gets busted.

#6step_6 chat_tool STUB

tool
regenerate_spec_sheet
side_effects
(unknown)
args
{"item_code":"{{current_assembly.item_code}}","reason":"{{rationale}}"}
step_id
06_regenerate_spec_sheet
conditional
header_changes touches pack_size/case_count OR line_changes affect yield
description
Spec sheet pulls from assembly header; regenerate if user-facing fields changed.
stub — not yet implemented in src/lib/workflow_runner.ts (kind chat_tool hits the placeholder branch at line ~340 and emits step status 'stub'). Documented intent only.

#7step_7 flag STUB

step_id
07_flag_dependent_bids
action
mark_bids_for_repricing
scope
bid_lines where item_code = current_assembly.item_code AND bid status in (active,submitted)
description
If this assembly is in any open bid line, flag the bid for re-pricing review.
stub — not yet implemented in src/lib/workflow_runner.ts (kind flag hits the placeholder branch at line ~340 and emits step status 'stub'). Documented intent only.

#8step_8 hitl_email_draft STUB

tool
propose_email_to_customer
step_id
08_notify_program_owners
conditional
customer_programs_using not null
description
Draft notification email per customer whose program includes this assembly so they hear it from us first.
stub — not yet implemented in src/lib/workflow_runner.ts (kind hitl_email_draft hits the placeholder branch at line ~340 and emits step status 'stub'). Documented intent only.

#9step_9 workflow_class_invoke STUB

workflow class
usda_drawdown_commit
step_id
09_trigger_usda_drawdown_check
conditional
any BOM line references a USDA commodity item
description
If a USDA commodity line changed quantity, trigger the drawdown commit workflow so allocation stays aligned.
stub — not yet implemented in src/lib/workflow_runner.ts (kind workflow_class_invoke hits the placeholder branch at line ~340 and emits step status 'stub'). Documented intent only.

7 · Post actions declared + runner-automatic

idactionsource
runner_log_runINSERT into workflow_run_log (run_id, workflow_type, status, started_at, completed_at, summary_json)runner automatic
runner_reflexionINSERT into reflexion_log (tags=assembly_build_review, run_id, narrative)runner automatic (reflexion_enabled=1)
log_run{"name":"log_run","kind":"d1_write","table":"workflow_run_log","always":true}declared in contract
reflexion{"name":"reflexion","kind":"reflexion_log","entity_type":"assembly","entity_id":"{{assembly_id}}","tags":"assembly_review,bom_change,data_cleanup","observation":"{{rationale}}","outcome_from_status":true,"description":"Records the review + outcome. Future runs see prior BOM evolution."}declared in contract
fire_event{"name":"fire_event","kind":"event","event_type":"assembly.review_completed","entity_type":"assembly","entity_id":"{{assembly_id}}","always":true}declared in contract
fire_cost_drift_event{"name":"fire_cost_drift_event","kind":"event","event_type":"assembly.cost_recomputed","entity_type":"assembly","entity_id":"{{assembly_id}}","conditional":"step 04 executed"}declared in contract

8 · Verify checks written to workflow_verify_results (pending — verify cron not yet wired)

name
assembly_header_actually_updated
expected
non_null
SELECT
  lastmodifieddate
  FROM assemblies
  WHERE id=?1
  AND lastmodifieddate > ?2
name
bom_line_count_changed_as_expected
expected
matches_expected_post_change
SELECT
  COUNT(*) AS line_count
  FROM assembly_bom
  WHERE assembly_id=?1
  AND deleted_at IS NULL
name
cost_rollup_recomputed
expected
last_computed > run_started_at
SELECT
  last_computed, drift_pct_vs_prior
  FROM assembly_cost_rollup
  WHERE assembly_id=?1
name
no_orphaned_pricing
expected
0
SELECT
  COUNT(*) AS orphans
  FROM pricing_master pm
  WHERE UPPER(pm.item_code) = (SELECT UPPER(item_code)
  FROM assemblies
  WHERE id=?1)
  AND pm.status='active'
  AND pm.cost_basis IS NULL

9 · Retry policy

max_attempts
2
backoff_seconds
[300,1800]
retry_on
["ns_push_transient","cost_rollup_compute_failed"]

10 · What changes when this workflow runs aggregated side effects

systemtable / resourceactionstatussource
D1proposed_actionsINSERT (action_type=assembly_header_update, entity_type=assembly)REALfan-out #1 ()
D1proposed_actionsINSERT (action_type=assembly_bom_update, entity_type=assembly_bom)REALfan-out #2 ()
KV (CACHE)assembly:{{assembly_code}}invalidateREALfan-out #3 ()
KV (CACHE)assembly_full:{{assembly_id}}invalidateREALfan-out #3 ()
KV (CACHE)bom:{{assembly_id}}invalidateREALfan-out #3 ()
KV (CACHE)(no keys specified)invalidateREALfan-out #5 ()
D1workflow_run_logINSERT (run summary)REALrunner automatic
D1reflexion_logINSERT (tags=assembly_build_review)REALrunner automatic
Eventworkflow.completed (or workflow.failed)fireREALrunner automatic
D1workflow_verify_resultsINSERT pending × 4REALrunner verify staging
D1proposed_actionsINSERT (HITL gate envelope)REALrunner HITL gate
D1assembly_cost_rollupwriteSTUBfan-out #4 ()
Chat toolregenerate_spec_sheetinvoke (side_effects=unknown)STUBfan-out #6 ()
D1flags / status fieldset =STUBfan-out #7 ()
D1proposed_actionsINSERT (email draft via propose_email_to_customer)STUBfan-out #8 ()
Cloudflare Workflowusda_drawdown_commitinvoke instanceSTUBfan-out #9 ()