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.
—assembly.cost_drift_detected"manual_or_event"null"Manually invoked from /assembly/<code> review page OR fired by weekly assembly rollup cron when cost_drift_pct exceeds threshold (R533)."current_assemblyassemblies["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
current_bomassembly_bom["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
current_cost_rollupassembly_cost_rollup["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
customer_programs_usingassembly_programs · customer_programs["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'
active_pricing_rowspricing_master · assemblies["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'
spec_sheetspec_items · assemblies["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
recent_buildstransactions · assemblies["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')| check | rule | severity |
|---|---|---|
assembly_exists | current_assembly.id present | block |
either_line_or_header_changes | line_changes present OR header_changes present | block |
rationale_present | rationale present | block |
no_active_customer_program_locked | customer_programs_using is null | warn |
cost_rollup_fresh | current_cost_rollup.last_computed >= date('now','-7 days') | warn |
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).
workflow_assembly_build_review (proposal envelope)workflow:assembly_build_review:run_<run_id>step_1 stage_proposed_action REALassembly_header_updateassembly{{assembly_id}}01_stage_assembly_header_updateheader_changes presentStage NS-bound assembly header update for HITL review.step_2 stage_proposed_action REALassembly_bom_updateassembly_bom{{assembly_id}}02_stage_bom_line_updatesline_changes presentStage 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.step_3 kv_invalidate REALassembly:{{assembly_code}}assembly_full:{{assembly_id}}bom:{{assembly_id}}03_invalidate_assembly_cacheBust live assembly + BOM caches.step_4 d1_write STUBassembly_cost_rollup04_recompute_cost_rolluprecomputeline_changes presentRecompute raw + packaging + labor + overhead + freight from the new BOM. Persist drift_pct_vs_prior.src/lib/workflow_runner.ts (kind d1_write hits the placeholder branch at line ~340 and emits step status 'stub'). Documented intent only.step_5 kv_invalidate REAL05_invalidate_pricing_cache_for_pricing_rowsEach (customer, school_year) keyed cache that touched this assembly gets busted.step_6 chat_tool STUBregenerate_spec_sheet{"item_code":"{{current_assembly.item_code}}","reason":"{{rationale}}"}06_regenerate_spec_sheetheader_changes touches pack_size/case_count OR line_changes affect yieldSpec sheet pulls from assembly header; regenerate if user-facing fields changed.src/lib/workflow_runner.ts (kind chat_tool hits the placeholder branch at line ~340 and emits step status 'stub'). Documented intent only.step_7 flag STUB07_flag_dependent_bidsmark_bids_for_repricingbid_lines where item_code = current_assembly.item_code AND bid status in (active,submitted)If this assembly is in any open bid line, flag the bid for re-pricing review.src/lib/workflow_runner.ts (kind flag hits the placeholder branch at line ~340 and emits step status 'stub'). Documented intent only.step_8 hitl_email_draft STUBpropose_email_to_customer08_notify_program_ownerscustomer_programs_using not nullDraft notification email per customer whose program includes this assembly so they hear it from us first.src/lib/workflow_runner.ts (kind hitl_email_draft hits the placeholder branch at line ~340 and emits step status 'stub'). Documented intent only.step_9 workflow_class_invoke STUBusda_drawdown_commit09_trigger_usda_drawdown_checkany BOM line references a USDA commodity itemIf a USDA commodity line changed quantity, trigger the drawdown commit workflow so allocation stays aligned.src/lib/workflow_runner.ts (kind workflow_class_invoke hits the placeholder branch at line ~340 and emits step status 'stub'). Documented intent only.| id | action | source |
|---|---|---|
runner_log_run | INSERT into workflow_run_log (run_id, workflow_type, status, started_at, completed_at, summary_json) | runner automatic |
runner_reflexion | INSERT 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 |
workflow_verify_results (pending — verify cron not yet wired)assembly_header_actually_updatednon_nullSELECT lastmodifieddate FROM assemblies WHERE id=?1 AND lastmodifieddate > ?2
bom_line_count_changed_as_expectedmatches_expected_post_changeSELECT COUNT(*) AS line_count FROM assembly_bom WHERE assembly_id=?1 AND deleted_at IS NULL
cost_rollup_recomputedlast_computed > run_started_atSELECT last_computed, drift_pct_vs_prior FROM assembly_cost_rollup WHERE assembly_id=?1
no_orphaned_pricing0SELECT 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
2[300,1800]["ns_push_transient","cost_rollup_compute_failed"]| system | table / resource | action | status | source |
|---|---|---|---|---|
| D1 | proposed_actions | INSERT (action_type=assembly_header_update, entity_type=assembly) | REAL | fan-out #1 () |
| D1 | proposed_actions | INSERT (action_type=assembly_bom_update, entity_type=assembly_bom) | REAL | fan-out #2 () |
| KV (CACHE) | assembly:{{assembly_code}} | invalidate | REAL | fan-out #3 () |
| KV (CACHE) | assembly_full:{{assembly_id}} | invalidate | REAL | fan-out #3 () |
| KV (CACHE) | bom:{{assembly_id}} | invalidate | REAL | fan-out #3 () |
| KV (CACHE) | (no keys specified) | invalidate | REAL | fan-out #5 () |
| D1 | workflow_run_log | INSERT (run summary) | REAL | runner automatic |
| D1 | reflexion_log | INSERT (tags=assembly_build_review) | REAL | runner automatic |
| Event | workflow.completed (or workflow.failed) | fire | REAL | runner automatic |
| D1 | workflow_verify_results | INSERT pending × 4 | REAL | runner verify staging |
| D1 | proposed_actions | INSERT (HITL gate envelope) | REAL | runner HITL gate |
| D1 | assembly_cost_rollup | write | STUB | fan-out #4 () |
| Chat tool | regenerate_spec_sheet | invoke (side_effects=unknown) | STUB | fan-out #6 () |
| D1 | flags / status field | set = | STUB | fan-out #7 () |
| D1 | proposed_actions | INSERT (email draft via propose_email_to_customer) | STUB | fan-out #8 () |
| Cloudflare Workflow | usda_drawdown_commit | invoke instance | STUB | fan-out #9 () |