Item record review + push-back risk 3 · mediumOtherreflexion on

workflow_type: item_record_review · owner: — · contract v2

Query an item record, review its current state across all surfaces (NS field values, spec sheet, pricing, vendor costs, assembly memberships, recent transactions), propose targeted field updates, and after HITL approval push the changes back to NS plus invalidate every downstream cache + re-derive dependent aggregates. The data-cleanup workhorse for the items catalog.

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: data_quality.item_flagged 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_item: SELECT id, itemid, displayname, salesdescription, custitem_brand, custitem_pack… current_spec: SELECT spec_id, brand, item_name, pack_size, claims_json, nutritionals_json, al… pricing_master_row: SELECT customer_name, case_price, cost_basis, school_year, status, last_updated… vendor_costs: SELECT vendor_name, vendor_item_code, unit_cost, effective_start, effective_end… assembly_memberships: SELECT a.id, a.item_code AS assembly_code, a.description FROM assemblies a JOIN… open_bid_lines: SELECT bid_id, customer_name, proposed_price FROM bid_lines WHERE UPPER(item_co… recent_transactions: SELECT COUNT(*) AS so_count_30d FROM so_lines WHERE UPPER(item)=UPPER(?1) AND t…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] item_exists: current_item.id present [warn] item_active: current_item.isinactive = 0 [block] proposed_changes_non_empty: proposed_changes present [block] rationale_present: rationale present [warn] not_in_active_bid: open_bid_lines is nulli 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_item_record_review entity_ref: workflow:item_record_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 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 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 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 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 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: item_record_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: ns_field_actually_upd… scheduler: verify cron @ :08/:38 (R563)i ns_field_actually_upd… ≤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: pricing_master_still_… scheduler: verify cron @ :08/:38 (R563)i pricing_master_still_… ≤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: spec_sheet_present_if… scheduler: verify cron @ :08/:38 (R563)i spec_sheet_present_if… ≤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: data_quality.item_flagged 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_item: SELECT id, itemid, displayname, salesdescription, custitem_brand, custitem_pack… current_spec: SELECT spec_id, brand, item_name, pack_size, claims_json, nutritionals_json, al… pricing_master_row: SELECT customer_name, case_price, cost_basis, school_year, status, last_updated… vendor_costs: SELECT vendor_name, vendor_item_code, unit_cost, effective_start, effective_end… assembly_memberships: SELECT a.id, a.item_code AS assembly_code, a.description FROM assemblies a JOIN… open_bid_lines: SELECT bid_id, customer_name, proposed_price FROM bid_lines WHERE UPPER(item_co… recent_transactions: SELECT COUNT(*) AS so_count_30d FROM so_lines WHERE UPPER(item)=UPPER(?1) AND t…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] item_exists: current_item.id present [warn] item_active: current_item.isinactive = 0 [block] proposed_changes_non_empty: proposed_changes present [block] rationale_present: rationale present [warn] not_in_active_bid: open_bid_lines is nulli 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_item_record_review entity_ref: workflow:item_record_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 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 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: item_record_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: ns_field_actually_upd… scheduler: verify cron @ :08/:38 (R563)i ns_field_actually_upd… ≤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: pricing_master_still_… scheduler: verify cron @ :08/:38 (R563)i pricing_master_still_… ≤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: spec_sheet_present_if… scheduler: verify cron @ :08/:38 (R563)i spec_sheet_present_if… ≤60m Legend User UI Agent logic Policy Tool action Context / trace

1 · Trigger FIRES WHEN…

kind
event pattern
data_quality.item_flagged
kind
"manual_or_event"
cron
null
description
"Manually invoked from /item/<code> review page OR fired by data-quality watchdog when an item field drift is detected."

2 · Inputs required

No declared inputs.

3 · Context loaded D1 queries run before fan-out

name
current_item
tables
items
binds
["item_code"]
SELECT
  id, itemid, displayname, salesdescription, custitem_brand, custitem_pack_size, custitem_case_count, custitem_buyamerican, custitem_kosher, custitem_allergens, custitem_country_of_origin, custitem_storage_temp, reorderpoint, taxschedule, isinactive, lastmodifieddate
  FROM items
  WHERE UPPER(itemid) = UPPER(?1)
  LIMIT 1
name
current_spec
tables
spec_items
binds
["item_code"]
SELECT
  spec_id, brand, item_name, pack_size, claims_json, nutritionals_json, allergens_json, spec_status, spec_version, last_updated
  FROM spec_items
  WHERE UPPER(item_code) = UPPER(?1)
  ORDER BY spec_version DESC
  LIMIT 1
name
pricing_master_row
tables
pricing_master
binds
["item_code"]
SELECT
  customer_name, case_price, cost_basis, school_year, status, last_updated
  FROM pricing_master
  WHERE UPPER(item_code) = UPPER(?1)
  AND status='active'
name
vendor_costs
tables
vendor_costs · vendor_items
binds
["item_code"]
SELECT
  vendor_name, vendor_item_code, unit_cost, effective_start, effective_end, status
  FROM vendor_costs vc
  JOIN vendor_items vi ON vi.vendor_item_id = vc.vendor_item_id
  WHERE UPPER(vi.item_code) = UPPER(?1)
  AND vc.status='active'
name
assembly_memberships
tables
assemblies · assembly_bom
binds
["item_code"]
SELECT
  a.id, a.item_code AS assembly_code, a.description
  FROM assemblies a
  JOIN assembly_bom bom ON bom.assembly_id = a.id
  WHERE UPPER(bom.line_key) = UPPER(?1)
  AND a.deleted_at IS NULL
  AND bom.deleted_at IS NULL
name
open_bid_lines
tables
bid_lines · bids
binds
["item_code"]
SELECT
  bid_id, customer_name, proposed_price
  FROM bid_lines
  WHERE UPPER(item_code) = UPPER(?1)
  AND bid_id IN (SELECT id
  FROM bids
  WHERE status IN ('active','submitted'))
name
recent_transactions
tables
so_lines
binds
["item_code"]
SELECT
  COUNT(*) AS so_count_30d
  FROM so_lines
  WHERE UPPER(item)=UPPER(?1)
  AND trandate >= date('now','-30 days')

4 · Preconditions checked before any fan-out

checkruleseverity
item_existscurrent_item.id presentblock
item_activecurrent_item.isinactive = 0warn
proposed_changes_non_emptyproposed_changes presentblock
rationale_presentrationale presentblock
not_in_active_bidopen_bid_lines is nullwarn

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_item_record_review (proposal envelope)
entity_ref
workflow:item_record_review:run_<run_id>
approver
mike (single-admin)

6 · Fan-out targets 7 total · 3 real · 4 stub

#1step_1 stage_proposed_action REAL

action_type
item_field_update
entity_type
item
entity_ref
{{item_code}}
step_id
01_stage_ns_field_update
description
Stage NS-bound field update for HITL review. Drainer flips status to applied only after confirmed NS write.

#2step_2 kv_invalidate REAL

keys
item:{{item_code}}
item_full:{{item_code}}
ns:item:{{item_code}}
step_id
02_invalidate_item_cache
description
Bust the live item caches so /item/<code> page renders fresh on next request.

#3step_3 kv_invalidate REAL

keys
pricing:{{item_code}}:*
step_id
03_invalidate_pricing_cache
description
Bust pricing caches that key off item_code (pattern delete via list scan).

#4step_4 chat_tool STUB

tool
regenerate_spec_sheet
side_effects
(unknown)
args
{"item_code":"{{item_code}}","reason":"{{rationale}}"}
step_id
04_regenerate_spec_sheet
description
If proposed_changes touched any spec-relevant field (claims, pack_size, allergens), regenerate the PDF spec sheet via the existing generator. Conditional on field set.
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.

#5step_5 d1_write STUB

table
assembly_cost_rollup
step_id
05_recompute_assembly_cost_rollups
operation
recompute
scope
for each assembly in context.assembly_memberships
description
If this item is in any assembly BOM, recompute the affected assembly cost rollups so margin math stays current.
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.

#6step_6 flag STUB

step_id
06_flag_dependent_pricing
action
mark_for_pricing_review
scope
pricing_master rows in context.pricing_master_row
description
If item-level changes could affect customer pricing (e.g. pack_size change ripples to case_price), flag the impacted pricing_master rows for next pricing review cycle.
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.

#7step_7 hitl_email_draft STUB

tool
propose_email_to_customer
step_id
07_notify_sales_if_active_bid
conditional
context.open_bid_lines not null
description
If the item is in any open bid, draft a notification email to the bid owner so the customer can be looped in before NS push happens.
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.

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=item_record_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":"item","entity_id":"{{item_code}}","tags":"item_review,data_cleanup","observation":"{{rationale}}","outcome_from_status":true,"description":"Records that this item was reviewed and what changed — future runs see the history."}declared in contract
fire_event{"name":"fire_event","kind":"event","event_type":"item.review_completed","entity_type":"item","entity_id":"{{item_code}}","always":true}declared in contract

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

name
ns_field_actually_updated
expected
non_null
SELECT
  lastmodifieddate
  FROM items
  WHERE UPPER(itemid)=UPPER(?1)
  AND lastmodifieddate > ?2
name
pricing_master_still_valid
expected
0
SELECT
  COUNT(*) AS broken
  FROM pricing_master
  WHERE UPPER(item_code)=UPPER(?1)
  AND status='active'
  AND case_price IS NULL
name
spec_sheet_present_if_changed
expected
non_null
SELECT
  spec_id
  FROM spec_items
  WHERE UPPER(item_code)=UPPER(?1)
  ORDER BY spec_version DESC
  LIMIT 1

9 · Retry policy

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

10 · What changes when this workflow runs aggregated side effects

systemtable / resourceactionstatussource
D1proposed_actionsINSERT (action_type=item_field_update, entity_type=item)REALfan-out #1 ()
KV (CACHE)item:{{item_code}}invalidateREALfan-out #2 ()
KV (CACHE)item_full:{{item_code}}invalidateREALfan-out #2 ()
KV (CACHE)ns:item:{{item_code}}invalidateREALfan-out #2 ()
KV (CACHE)pricing:{{item_code}}:*invalidateREALfan-out #3 ()
D1workflow_run_logINSERT (run summary)REALrunner automatic
D1reflexion_logINSERT (tags=item_record_review)REALrunner automatic
Eventworkflow.completed (or workflow.failed)fireREALrunner automatic
D1workflow_verify_resultsINSERT pending × 3REALrunner verify staging
D1proposed_actionsINSERT (HITL gate envelope)REALrunner HITL gate
Chat toolregenerate_spec_sheetinvoke (side_effects=unknown)STUBfan-out #4 ()
D1assembly_cost_rollupwriteSTUBfan-out #5 ()
D1flags / status fieldset =STUBfan-out #6 ()
D1proposed_actionsINSERT (email draft via propose_email_to_customer)STUBfan-out #7 ()