Annual Price Roll — CF Workflow

Pillar 4 of PRICING_OPERATING_MODEL · 11-step durable workflow · July 1 cutover · src/annual_roll_workflow.ts

01 / Snapshot & Derive 02 / Outliers & Packets 03 / Email + HITL Approval 04 / Lock + Push to NS 05 / Verify + Complete POST /api/pricing/annual-roll/start admin · X-Edit-Token · run_id + SY 01 snapshot_current_sy freeze price_history for outgoing SY 02 derive_next_sy compute new prices · CME + index inputs 03 apply_programs discount programs · rebates · ladders 04 flag_outliers drift > threshold % (default 15) 05 generate_packets per-customer + per-vendor preview packets 06 email_admin_summary Mike gets roll summary · links to packets 07 await_admin_approval step.waitForEvent · indefinite hold decision approved or rejected · X-Edit-Token 08 lock_history price_history rows immutable post-cutover 09 push_to_netsuite enqueue ns_pending_pushes · NS_PUSH_QUEUE step.sleep 30m ADR-019 · let NS mirror-back settle 10 verify_mirror_back compare D1 ↔ NS price match · log diffs 11 mark_cutover_complete annual_roll_runs.status=complete rejected path annual_roll_runs.status=rejected · run ends approved rejected Legend User UI Agent logic Policy Tool action Context / trace

Why a Workflow class

  • • step.do() = durable retry · resumes after Worker restart
  • • step.sleep(30m) covers NS mirror-back lag (ADR-019)
  • • step.waitForEvent gates step 7 on admin decision
  • • Each step row-mirrors into annual_roll_runs (migration 035)
  • • Tight retry policy: D1 3× linear · AI 2× exp · Queue 5× exp

Steps 1-5: derive

  • • Snapshot outgoing SY to price_history (immutable later)
  • • Derive next-SY prices from CME + index inputs
  • • Apply customer programs / rebates / ladders
  • • Flag outliers above drift_threshold_pct (default 15%)
  • • Generate per-customer + per-vendor preview packets

Steps 6-7: HITL gate

  • • Email Mike (ADMIN_EMAIL) with roll summary + packet links
  • • step.waitForEvent('admin_decision') · no timeout
  • • Decision payload: {decision, notes, decided_by}
  • • Standing invariant: HITL on every NS write

Steps 8-11: push + verify

  • • Lock price_history rows for outgoing SY
  • • Enqueue NS_PUSH_QUEUE · OAuth1 TBA writes
  • • 30-min sleep, then compare D1 ↔ NS mirror
  • • Mark run complete OR record drift in annual_roll_runs
  • • Cutover effective_from = cutover_date (default 2026-07-01)