Annual price roll (SY rollover) risk 5 · criticalScheduled cyclereflexion on

workflow_type: annual_price_roll · owner: — · contract v2

July 1 cycle: recompute every customer × SKU price for next SY. Cloudflare Workflow durable execution via AnnualRollWorkflow class. Bulk HITL review per customer; on approval, NS bulk update + customer notification + pricing_history snapshot.

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

Workflow flow
01 / Trigger 02 / Context + preconditions 03 / HITL gate (Mike approves) 04 / Fan-out cascade 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: cron cron: 0 8 1 7 * tz: America/New_York invoker: Mike (single-admin) risk_level: 5i Manual Mike invokes risk 5 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) all_customers: SELECT id, companyname FROM customers WHERE isinactive != 'T' all_active_items: SELECT id, itemid FROM items WHERE isinactive != 'T' prior_sy_pricing: SELECT customer_id, item_code, case_price FROM pricing_master WHERE school_year… customer_programs_active: SELECT customer_id, program_type, value, value_type FROM customer_programs WHER…i Load context 4 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] prior_sy_pricing_exists: COUNT(prior_sy_pricing) > 0 [warn] cost_data_fresh: MAX(assembly_cost_rollup.computed_at WHERE status='active') > now - 7 days [block] kill_switch: NOT kill:high_risk_opsi Preconditions 3 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_annual_price_roll entity_ref: workflow:annual_price_roll:run_<run_id> approver: mike (single-admin) risk_gate: >= 3 (this workflow = 5) approval window: typical <= 60 min envelope: proposed_actions row staged by runneri Mike approves stage_proposed_action risk ≥ 3 gate Spawn a Cloudflare Workflow durable-execution class (e.g., AnnualRollWorkflow) for long-running multi-step work. STUB today. FAN-OUT: pillar_4_workflow_lau… kind: workflow_class_invoke (not in contract — diagram-only annotation)i pillar_4_workflow_lau… workflow_class_invoke 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 #customer_notification -> email draft kind: hitl_email_draft tool: propose_email_to_customer status: STUB (src/lib/workflow_runner.ts)i customer_notification hitl_email_draft STUB Fire an HTTP request to another endpoint (often a self-fetch to /api/). STUB today - runner doesn't yet make the call. FAN-OUT #render_quote_pdfs -> HTTP POST /api/quote/pdf kind: http_call status: REAL (src/lib/workflow_runner.ts)i render_quote_pdfs http_call 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: annual_price_roll 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: new_sy_pricing_present window: 60m sql_check: SELECT COUNT(*) FROM pricing_master WHERE school_year=? AND created_at > ? scheduler: verify cron @ :08/:38 (R563) result row: workflow_verify_resultsi new_sy_pricing_present ≤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: history_snapshot_pres… scheduler: verify cron @ :08/:38 (R563)i history_snapshot_pres… ≤60m Legend User UI Agent logic Policy Tool action Context / trace

1 · Trigger FIRES WHEN…

kind
cron
schedule (cron)
0 8 1 7 *
timezone
America/New_York
manual override
allowed

2 · Inputs required

namerequiredtype / hint
next_school_yearrequired
markup_rules_jsonrequired
customer_cohort_sizeoptional

3 · Context loaded D1 queries run before fan-out

name
all_customers
tables
customers
SELECT
  id, companyname
  FROM customers
  WHERE isinactive != 'T'
name
all_active_items
tables
items
SELECT
  id, itemid
  FROM items
  WHERE isinactive != 'T'
name
prior_sy_pricing
tables
pricing_master
SELECT
  customer_id, item_code, case_price
  FROM pricing_master
  WHERE school_year=?
name
customer_programs_active
tables
customer_programs
SELECT
  customer_id, program_type, value, value_type
  FROM customer_programs
  WHERE status='active'
  AND deleted_at IS NULL

4 · Preconditions checked before any fan-out

checkruleseverity
prior_sy_pricing_existsCOUNT(prior_sy_pricing) > 0block
cost_data_freshMAX(assembly_cost_rollup.computed_at WHERE status='active') > now - 7 dayswarn
kill_switchNOT kill:high_risk_opsblock

5 · HITL gate

Risk level 5 ≥ 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_annual_price_roll (proposal envelope)
entity_ref
workflow:annual_price_roll:run_<run_id>
approver
mike (single-admin)

6 · Fan-out targets 3 total · 0 real · 3 stub

#1pillar_4_workflow_launch workflow_class_invoke STUB

workflow class
AnnualRollWorkflow
binding
ANNUAL_ROLL_WORKFLOW
initial_input
{next_sy:?, markup_rules:?}
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.

#2customer_notification hitl_email_draft STUB

tool
propose_email_to_customer
one_per
customer_cohort
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.

#3render_quote_pdfs http_call STUB

method
POST
path
/api/quote/pdf
stub — not yet implemented in src/lib/workflow_runner.ts (kind http_call 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=annual_price_roll, run_id, narrative)runner automatic (reflexion_enabled=1)
log_runINSERT workflow_runsdeclared in contract
reflexionINSERT reflexion_log with tags=annual_roll,sy:?declared in contract
workflow_healthUPDATE annual_roll_workflow_runs statusdeclared in contract

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

name
new_sy_pricing_present
when_to_run
60 min after run
SELECT
  COUNT(*)
  FROM pricing_master
  WHERE school_year=?
  AND created_at > ?
name
history_snapshot_present
when_to_run
15 min after run
SELECT
  COUNT(*)
  FROM pricing_history
  WHERE created_at > ?

9 · Retry policy

max_attempts
1
backoff
none
alert_on_final_failure
true
workflow_class_retry
native_cloudflare_workflow_retry

10 · What changes when this workflow runs aggregated side effects

systemtable / resourceactionstatussource
D1workflow_run_logINSERT (run summary)REALrunner automatic
D1reflexion_logINSERT (tags=annual_price_roll)REALrunner automatic
Eventworkflow.completed (or workflow.failed)fireREALrunner automatic
D1workflow_verify_resultsINSERT pending × 2REALrunner verify staging
D1proposed_actionsINSERT (HITL gate envelope)REALrunner HITL gate
Cloudflare WorkflowAnnualRollWorkflowinvoke instanceSTUBfan-out #1 (pillar_4_workflow_launch)
D1proposed_actionsINSERT (email draft via propose_email_to_customer)STUBfan-out #2 (customer_notification)
Worker HTTPPOST /api/quote/pdfinvokeSTUBfan-out #3 (render_quote_pdfs)