Wiki · Workflow companion

Draft quote (legacy v2 contract)

The legacy v2 contract that anchored the canonical bid_price_update pattern. Now superseded by customer_quote (R550) — but the contract row remains as the historical anchor and is what every other v2 workflow was modeled against.

Mixed · Some steps live, others stubbed
What this is

Draft quote (legacy v2 contract)

Before R549, the platform had a thin draft_quote row in workflow_definitions with the v1 schema. R549's migration 118 used draft_quote as the row to upgrade with the canonical worked example, then R550's migration 119 added customer_quote as the renamed-forward replacement.

The draft_quote row still exists for historical traceability — it carries the bid_price_update contract values (trigger spec, fan-out, verify checks) so the "first v2 contract" history is preserved. New work should target customer_quote.

Risk level 3 (medium). Mostly a documentation row at this point.

When to use it

Trigger conditions

Heuristic

If you find yourself dispatching to draft_quote, dispatch to customer_quote instead. The runner doesn't prevent it, but the v2 fan-out is more complete on the renamed-forward row.

Step-by-step what happens

The 7 beats

  1. 01

    NS itempricing update (legacy)

    The legacy fan-out target queued itempricing writes to NS_PUSH_QUEUE. Same semantics as bid_price_update.

    Writes NS pricing record
    Time ~3–8s
    Kind ns_push
    Status stub
  2. 02

    D1 pricing_master + pricing_history

    D1 pricing_master is updated and pricing_history captures the prior value and change metadata.

    Writes pricing_master, pricing_history
    Time ~80ms
    Kind d1_write
    Status stub
  3. 03

    bid_lines update (conditional)

    If bid_id is present, the matching bid_lines row gets the new price + updated_at.

    Writes bid_lines
    Time ~50ms
    Kind d1_write
    Status stub
    Note conditional · bid_id present
  4. 04

    Regenerate bid PDF artifact

    POST /api/bids/:bid_id/regenerate rebuilds the bid response PDF.

    Writes R2 bucket bids/
    Time ~6–10s
    Kind http_call
    Status stub
    Note conditional · bid_id + artifact_r2_key
  5. 05

    Invalidate hub UI cache

    Keys hub:nycdoe:bid_{bid_id} and hub:nycdoe:summary are deleted.

    Writes HUB_CACHE KV
    Time ~40ms
    Kind kv_invalidate
    Status real
    Note conditional · bid in active_hub_bids
  6. 06

    Spec sheet review flag

    If abs(price_change_pct) > 5, a row inserts into proposed_actions with action_type=spec_review_needed.

    Writes proposed_actions
    Time ~30ms
    Kind flag
    Status stub
    Note conditional · delta > 5%
  7. 07

    Customer notification draft

    If the customer has an open quote and the change is a price increase, draft an email via propose_email_to_customer.

    Writes outbound_email_log
    Time ~80ms
    Kind hitl_email_draft
    Status stub
    Note conditional · open quote + price increase
Outcomes

What's different after the workflow runs

Status
Legacy
use customer_quote
Contract version
2
v2 schema
Anchor
R549 migration
historical
Replacement
customer_quote
active
Failure modes

What can go wrong and how to recover

Wrong workflow_type chosen

If a chat tool emits action_type=draft_quote, the runner accepts it and runs the legacy contract. Functionally identical to bid_price_update for the price path, but customer_quote is preferred for explicit quote drafting.

Confused with customer_quote

These rows have overlapping intent. Always check workflow_type in workflow_runs to know which one ran.

Related

Adjacent workflows + diagrams

For developers

Code paths + invariants

ConcernWhere
Workflow contractworkflow_definitions WHERE workflow_type='draft_quote'
StatusLegacy — use customer_quote
Origin migration118_workflow_contracts_v2.sql (R549)
Replacementworkflow_type='customer_quote' (R550)
Contract version2
Risk level3
Expected duration~5 min
Triggerhitl_approval · proposed_actions approved with action_type=bid_price_update
// Prefer customer_quote in new code await runner.invoke('customer_quote', params); // ✓ canonical // not: // await runner.invoke('draft_quote', params); // legacy