Order Confirmation - System Design

The design for IMP's order confirmation flow, built on the greenfield pipeline with self-hosted Temporal. Written so an engineer who has not sat in the IMP calls can follow every step and question every choice. The system we run today is described in the appendix, as the thing this replaces and has to coexist with for a while.

1. The problem

IMP buys printed tin-can sheets from one supplier, RIC in Turkey, and sells the finished cans to its customers. The sheets carry each customer's artwork. So before IMP can order sheets for an order, the customer has to look at the artwork for every line and say "yes, print this."

Today one person does this by hand. For each line they find the current design file, send the set to the customer, chase for a reply, file whatever comes back, work out how many sheets each line needs, and write the supplier purchase order. About two hours per order. It repeats on every order, because customers change artwork between orders.

A wrong design on a print run costs about $10,000. Sari (the decision maker at IMP) has had cases where his team and the customer both signed off on the wrong version. He cannot tell two versions apart by eye. A nutrition panel shifted a few millimetres, a dash missing from a name.

His one hard requirement: when a customer sends him a new design, he forwards it to us and he needs to be certain that this version, and no older one, goes out for confirmation. He does not want to compare files or look at version lists. Ask him only when we are not sure.

IMP has no ERP. WhatsApp and a spreadsheet are the record. That matters for who owns which fact (section 6.2).

2. Requirements

Functional:

Non-functional:

Non-goals for now: inventory, warehouse, tracking after the PO, and any customer-facing UI. The customer only ever sees messages.

3. Decisions taken

  1. Build on the greenfield pipeline, with self-hosted Temporal as the runtime. Control flow is code; everything a tenant varies is policy data. No operator-editable graph builder.
  2. Design the full desk from the demo, and phase the build. The data model covers all ten steps on day one so later phases add code, not migrations.
  3. Channel-agnostic core. Customers on email first. Sari forwards by email or upload until the production WhatsApp number exists (an operator task, ARD-728); the same handlers pick up WhatsApp when it does. Customer WhatsApp needs the customer's opt-in and Meta-approved templates, so it is added per customer later.
  4. A forwarded file replaces the current version when independent signals agree, and the mode is a per-customer policy: auto when signals agree (IMP's ask), require approval on every version, or ask only when the artwork visibly changed.
  5. The delivery is the unit of reading. A WhatsApp text, a photo of an order, a forwarded PDF and a reply are all deliveries; each file inside, body included, is a document under it.
  6. Headless and conversational are one path. A step that is confident materializes; a step that is not becomes a question on the person's channel, and the reply is the decision. Conversations are Temporal workflows.
  7. Specifications, versions and asks are Aradus-owned for every tenant. Orders, customers and products are owned per tenant through objects.authority; for IMP that is aradus, since there is no ERP.

4. Concepts, in plain words

Specification. A customer-approved specification for one product, such as "Al-Ain Foods, Caramel 500g" artwork; IMP calls this a brand. One specification has many versions over time.

Specification version. One design file, frozen. A new file is a new version. Versions are not edited after creation.

Current version. The version we would print today. Exactly one per specification. Changing it is a decision with a record.

Pin. A line on an order points at one specific version of a specification, not at the specification. The pack we sent, the yes we received and the sheets on the PO all cite that same version. That pointer is the pin, and it is how we can say with certainty what the customer approved.

Supersede. When a specification's current version changes, every open line pinned to the old version is marked superseded, re-pinned to the new version, and only those lines go back to the customer.

Candidate. A forwarded file is saved right away as a candidate version, before anyone decides which specification it belongs to, so a file cannot be lost while we work out what it is.

Signal. One independent piece of evidence about which specification a candidate belongs to. We use three, and a candidate is promoted without asking only when they agree and the customer's policy allows it.

Delivery and dispatch. A delivery is anything that arrives: a message, a file, a reply. A dispatch is anything we send: a pack, a reminder, a question, a receipt, a PO. Both are rows; together they are the transcript.

Proposal and decision. The machine proposes, with a confidence and the evidence it used. A policy or a person decides. Only the decision becomes a fact. Every proposal and decision is kept.

Observation. A statement a source made about an existing object: the customer said line 3 is confirmed, the supplier said the lead time slipped a week. Observations are appended, and a per-attribute reducer computes the current value from them.

Ask and ask item. An ask is one set of questions put to a person or a counterparty, with a policy that says how to chase and when to give up. An ask item is one thing asked about, immutable once written; its current answer is the latest decision recorded against it.

Policy. A per-tenant, per-customer rule read by the workflow at each step: thresholds, contacts, intervals, modes. The customer rulebook is the operator's view of the policies for that customer.

Conversation workflow. One long-lived Temporal workflow per (tenant, channel, contact). Every message that person sends goes to it first; it decides whether the message answers an open question, instructs a change, or asks something.

Durable timer. A timer that survives restarts and deploys. "Remind in two days" fires in two days whatever happened to the service in between.

5. The core

5.1 Two state machines

An order confirmation moves through received → matching → with_customer → confirmed → po_drafted → po_sent, with paused reachable from any state. Nothing runs until the gate opens: by an operator's Start, or on its own when every line clears the customer's order-entry threshold (F1). The gate is a policies row of type autonomy on the order, so the same workflow serves a customer who sends photographs and one who sends clean PDFs, and moving a customer from the button to auto-start changes a row, not code.

A line confirmation is where the work happens:

stateDiagram-v2
    [*] --> unmatched
    unmatched --> matched: specification found, version pinned
    matched --> sent: pack goes out
    sent --> sent: reminder
    sent --> confirmed: customer says yes
    sent --> deferred: customer rule allows deferral
    matched --> superseded: specification got a new version
    sent --> superseded: specification got a new version
    confirmed --> superseded: specification got a new version
    superseded --> matched: re-pinned to the new version, re-send queued
    confirmed --> [*]
    deferred --> [*]

An order is confirmed when every line is confirmed, or deferred under a customer rule that permits it. Supersession of a line on an order in po_drafted blocks the PO until the line is re-confirmed. On an order in po_sent it raises an operator alert, because the print may be running.

In the greenfield these states are derived: a line's state is the latest decision on its ask item, and the order's state is a function of its lines. The workflow holds the waiting; Postgres holds the facts.

5.2 The certainty rule

A forwarded file lands as a candidate. Three signals then vote on which specification it belongs to:

  1. Origin. Who did the forward come from? A forwarded email keeps the original sender in the quoted headers, which maps to a customer contact and narrows the candidates to that customer's specifications. The WhatsApp Business API exposes a Forwarded flag and withholds the original sender, so on WhatsApp this signal abstains unless Sari's caption mentions the customer.
  2. Content. Text read out of the artwork: specification name, product, size, variant. Matched against the specification library, with a confidence. The confidence is the calibrated one from the greenfield spine (a fitted_models mapping over the proposal's features, see the greenfield page, section 6 "Confidence and calibration") rather than the model's own estimate; on day one it reads the platform mapping, and IMP gets its own once its decisions are enough to fit one.
  3. Geometry. The artwork's trim box against the customer's expected can geometry. A wrap-around label is a rectangle whose width is the can's circumference plus the seam where it overlaps itself, so the page size in the file says which can the design is for, with no model involved. It is deterministic, costs nothing, and is independent of the identity read in a way a second model call would not be.

This replaced a perceptual hash, which measurement rejected. Run over IMP's forty-five library files the hash put two different products six apart on a 256-bit hash while putting one label a hundred and twenty-eight away from a sheet of itself. Neither is a tuning problem: IMP's customers reuse one template across products and countries, so two designs can differ only in a product banner and a country line, while the same design is laid out as a single label on one file and a ten-up imposition sheet on another. A metric measuring overall visual similarity answers both of those correctly and is useless for deciding which specification a file belongs to. Local feature matching fared no better on the same pairs.

Geometry is what separates the cases nothing else can. Every residual ask in that measurement was a pair of designs differing only in which can they wrap, on artwork printing no size at all, where about a hundred and ninety millimetres of trim geometry separates what no text can.

The agreement rule is deterministic code: every signal that voted points to the same specification, at least two voted, and content confidence is above the customer's threshold. What happens when it holds is policy, per customer:

Whatever the mode, a promotion runs the supersession cascade in the same transaction, and a candidate that is rejected stays in history as rejected.

5.3 The invariant

No line in state confirmed may pin a version that is not its specification's current version. The same transaction that moves the current pointer supersedes the lines, so the invariant cannot be broken between two writes.

5.4 The model calls

Three, all activities returning structured results with confidence: match a line's text to a specification; read identity text out of an artwork image; interpret a reply per line as confirmed, rejected, deferred or unclear, quoting the words that support each verdict. A fourth phrases a question in the customer's language when a step needs to ask. The content hash and the geometry check are plain code. Any failed or low-confidence call becomes a question for a person (N2).

6. The design

6.1 How it sits on the greenfield spine

The greenfield pipeline already has deliveries, artifacts, born-typed documents (typed rows in their own right, not objects rows), runs, proposals, decisions, policies, materializations, facts, evidence_bindings for what a document created or supports, and order_lines as the order line kind's typed extension. Order confirmation adds one business aggregate (specifications and their versions), one fact table (asks and ask items), and three Temporal workflows. Everything else is the spine doing what it does for invoices.

Concept in this flow What it is in the greenfield
A forwarded design file, a reply, a photo of an order A delivery with delivery_artifacts; each artifact becomes a born-typed document (artwork, ask_reply, sales_order)
"Which specification is this file?" A refers_to proposal, decided by policy or by Sari
A new specification version A specification_versions row materialized from the decided proposal; the specification's current pointer moves in the same transaction
A line's pin and its answer A line_specification_pins row ties the order line to the offered specification_versions row; an ask_items row, item_ref_id -> line_specification_pins, carries the preview sent; its verdict is the latest proposal_decisions row on the ask item, which moves the pin to confirmed or rejected
"Line 3 confirmed" in a customer's reply An assertion proposal with a text-span source_ref, decided with actor_type = counterparty, materialized as an observation
The pack, a reminder, a question, a receipt, the PO dispatches
The customer rulebook policies rows scoped to the customer partner
Line text to specification mapping saved from an answer An alias in the identity spine
The supplier PO An obligation payable to RIC, its obligation_revisions row in posting_state = draft (snapshot), posted by a decision
Reminders, waits, escalations Durable timers and signals in the order workflow
Sari's messages The conversation workflow keyed on his number

6.2 Data model

New tables, all with organization_id and row-level security like the rest of the spine:

specifications
  id PK, organization_id, customer_partner_id -> objects, product_id -> objects,
  kind (artwork|label|formula|packaging|quality_standard), name, current_version_id -> specification_versions NULL
  UNIQUE (organization_id, customer_partner_id, product_id, kind, name)

specification_versions                -- append-only; one row per design file ever received
  id PK, organization_id, specification_id -> specifications, version_no,
  artifact_id -> artifacts,            -- the file, content-addressed
  status (candidate|current|superseded|rejected),
  created_from_proposal_id -> pipeline_proposals UNIQUE,   -- the decided refers_to proposal; a retry cannot create a duplicate
  received_via_delivery_id -> deliveries, promoted_by_decision_id NULL, created_at
  UNIQUE (specification_id, version_no)
  -- specifications.current_version_id is the single authority for "current"; a deferred trigger asserts the version it
  -- points to has status current and no sibling version of the same specification does

line_specification_pins                -- which version an order line is confirmed against; no kind of its own
  id PK, organization_id, order_line_id -> order_lines, specification_version_id -> specification_versions,
  status (matched|sent|confirmed|rejected|superseded), ask_item_id -> ask_items NULL, decided_by_decision_id NULL, superseded_by_id -> line_specification_pins NULL
  UNIQUE (order_line_id) WHERE status <> 'superseded'
  -- composite: the pinned version's specification must carry this line's product_id and this order's buyer as its customer,
  -- enforced by a constraint trigger since no single FK can express that join

asks                                   -- every question this flow puts to a person, whoever they are
  id PK, organization_id,
  kind (artwork|artwork_identity|line_match|order_start),
  audience (user|counterparty),
  assignee_user_id NULL, counterparty_partner_id -> objects NULL, contact_role NULL,
  subject_type (order|order_line|artifact), subject_id,
  policy_id -> policies NULL,           -- reminder_interval, escalate_after_reminders, what counts as an answer; NULL where nothing is chased
  state (open|complete|abandoned), created_at

ask_items                              -- one per thing being asked about; immutable once written
  id PK, ask_id, organization_id,
  item_ref_type (line_specification_pin|order_line|specification_version), item_ref_id,
  asked jsonb,                         -- the preview sent, or the candidates offered
  asked_schema_version, dispatch_id -> dispatches NULL   -- the message that carried it
  UNIQUE (ask_id, item_ref_type, item_ref_id)

Four questions arise in this flow and they take three audiences between them, which is why the audience and the subject are columns rather than fixed values. The confirmation pack is artwork to a counterparty about an order, one item per line. An artwork file whose signals disagree is artwork_identity to Sari about an artifact, before any order exists, one item per candidate specification. A line that matches nothing is line_match to Sari about an order line. An order whose entry confidence sits below the customer's threshold is order_start to Sari about the order. Only the first two carry a chase policy; the others wait without reminding.

The pin is its own row, line_specification_pins, rather than a field inside asked: it is what lets a confirmed line be found without reading every ask, and what a superseded_by_id chain points through when a forwarded replacement re-pins it. The ask item cites the pin, since the pin is what the PO conversion and the three-way match read back, rather than the order line or the ask itself.

Policies for this flow, as rows in policies with counterparty_id = the customer partner:

Authority for IMP: the order, partner and product kinds default to objects.authority = aradus through the authority policy. Specifications, versions and asks are Aradus-owned for every tenant and need no such policy row.

The supplier PO is an obligation (payable, debtor IMP, creditor RIC) with one obligation_revisions row whose obligation_lines carry sheet quantities and the working, rounded to MOQ. The conversion itself reads bills_of_materials and bom_components for the recipe (one can is one sheet, one cover and one bottom, with yield and scrap columns), and units and product_packagings for the unit the customer ordered in versus the unit the recipe is written in. Approval is a decision on its materialization; posting and sending follow.

6.3 Architecture

flowchart TB
    SW[Sari on WhatsApp] --> IN
    CE[Customer on email] --> IN
    RIC[RIC on email] --> IN
    UI[Rulebook and review queue<br/>read models over decisions and dispatches] --> W1
    IN[Edge adapters<br/>existing Twilio, Outlook and Resend handlers<br/>write deliveries and artifacts, emit delivery.arrived] --> CW[ConversationWorkflow<br/>one per tenant, channel, contact<br/>SignalWithStart on every inbound]
    CW -- answers an open question --> W1[ConfirmationWorkflow<br/>one per order, by ask kind and policy]
    CW -- a forwarded design file --> W2[ArtworkReplacementWorkflow<br/>one per file, keyed on its hash]
    W2 -. line_superseded signal .-> W1
    W1 --> AC[Activities<br/>idempotent on business keys]
    W2 --> AC
    CW --> AC
    AC -- model calls --> GW[Python gateway<br/>classify, read artwork, interpret reply, phrase question]
    AC -- every fact --> PG[(Postgres, the only business truth<br/>deliveries, artifacts, documents, runs<br/>proposals, decisions, materializations, facts<br/>specifications, specification_versions, asks, ask_items, line_specification_pins, objects (order, obligation kinds)<br/>dispatches, policies, aliases, objects.authority)]
    AC -- send dispatch --> OUT[Sari, customers, RIC]
    TP[Temporal, self-hosted on the cluster<br/>execution history only] -.- W1

Reading the boxes:

6.4 Data flow

sequenceDiagram
    autonumber
    participant S as Sari (WhatsApp)
    participant C as Customer (email)
    participant CW as ConversationWorkflow
    participant W as Confirmation and Artwork workflows
    participant G as Python gateway
    participant P as Postgres
    Note over S,P: A forwarded design file
    S->>CW: forward arrives: INSERT deliveries, delivery_artifacts, artifacts, then SignalWithStart conversation-{tenant}-whatsapp-{sari}
    CW->>G: interpret: an answer, an instruction, a question, or a file to file?
    G-->>CW: a design file with no open question, caption "Al-Ain"
    CW->>W: SignalWithStart artwork-{artifact hash}
    W->>G: classify
    G-->>W: document_type artwork
    W->>P: INSERT proposal_decisions (policy), documents born typed, run
    W->>G: read artwork identity
    W->>W: trim geometry against the customer's can formats, origin from caption
    W->>P: INSERT pipeline_proposals: the signals and refers_to (specification, confidence)
    alt policy auto_when_signals_agree and the rule holds
        W->>P: INSERT proposal_decisions, actor policy
    else policy says ask, or the signals disagree
        W->>P: INSERT dispatches (question, asks_proposal_ids)
        W->>S: one-tap question via the channel sender
        S-->>CW: reply arrives as a delivery
        CW->>W: signal decision_recorded (routed because the dispatch asked this proposal)
        W->>P: INSERT proposal_decisions, actor user
    end
    W->>P: INSERT specification_versions status current, UPDATE specifications.current_version_id, UPDATE prior version superseded, INSERT materializations (one transaction)
    W->>W: SignalWithStart line_superseded to every order-confirmation-{order} pinning the old version
    W->>S: receipt dispatch: replaced v3 with v4
    Note over C,P: The order workflow reacts
    W->>P: INSERT asks (new round), INSERT ask_items pinned to the new version for each affected line
    W->>P: INSERT dispatches (pack, affected lines only)
    W->>C: pack with the changed design, previews and one link per line
    loop durable timer at the customer's reminder_interval
        W->>C: reminder dispatch, escalation contact added after the policy's count
    end
    C-->>W: reply arrives as a delivery, routed by the dispatch it answers
    W->>G: classify ask_reply, interpret per line
    W->>P: INSERT pipeline_proposals (assertion per line, text-span source_ref)
    W->>P: INSERT proposal_decisions (policy for clear verdicts with actor counterparty, operator for unclear), INSERT facts
    Note over S,P: Every line confirmed
    W->>P: INSERT obligations (payable to RIC, draft), revision and lines with sheets, working, MOQ, INSERT materializations
    W->>S: PO approval question as a dispatch
    S-->>CW: approve
    CW->>W: signal po_decision
    W->>P: INSERT proposal_decisions, UPDATE obligations posting_state posted
    W->>P: INSERT dispatches (PO to RIC from IMP's mailbox)

Walking it once. Sari's forward is a delivery, and it goes to his conversation workflow first, which sees a design file with no question pending and hands it to the artwork workflow, keyed on the file's hash so a duplicate forward joins the existing run. The signals become proposals plus a refers_to, which is the linkage. Policy decides, or asks Sari; his answer comes back through the same conversation workflow, which knows which dispatch asked which proposal and routes it as a decision. The promotion, the supersession of the old version and the materialization record are one transaction. The artwork workflow then signals every order workflow that pinned the old version.

The order workflow re-pins and re-sends only the affected lines. The customer's reply is a delivery too, routed to the order workflow by the dispatch it answers, read per line as assertions with the quoted words as evidence, and decided with the customer as actor. When every line is confirmed the workflow converts the BOM into a draft obligation, asks Sari to approve it, and sends the PO.

6.5 The three workflows

ConversationWorkflow, id conversation-{tenant}-{channel}-{contact}. Started by SignalWithStart on the first message from that contact; lives indefinitely.

ConfirmationWorkflow, id order-confirmation-{order_id}, parametrised by the ask's kind and policy; artwork is the first kind built.

  1. Wait for start.
  2. match_lines activity: proposals, policy, returns the lines needing a person. Wait on decision_recorded until none remain.
  3. One dispatch per recipient. Per dispatch: durable timer at reminder_interval, send_reminder activity (template or free-form chosen from the conversation's last inbound time), escalation contact after the policy's count. Signals: reply_processed, remind_now, pause, resume, line_superseded.
  4. On line_superseded: match_lines for those lines, re-send dispatch, continue.
  5. When every line is confirmed or permissibly deferred: convert_bom writes the draft obligation; wait on po_decision; send_po; wait for the receipt delivery or a timeout that alerts.
  6. ContinueAsNew after each pack cycle.

ArtworkReplacementWorkflow, id artwork-{artifact_hash}.

  1. Activities for the three signal proposals and refers_to.
  2. Read the customer's replacement_mode; apply the agreement rule; decide or ask (question dispatch, wait on decision_recorded with a reminder timer).
  3. materialize_version: version row, current pointer, prior version superseded, materialization record, specification.version_replaced event, then SignalWithStart line_superseded on every order workflow pinning the old version (a Postgres query). An order already at po_sent gets a short alert workflow rather than nothing.
  4. send_receipt.

Rules for all three: model calls only in activities; workflows carry ids rather than customer data; every activity idempotent on a business key (dispatch_id, created_from_proposal_id, the materialization digest); worker versioning decided before the first live run, because a deploy that changes a running workflow's logic without it fails on replay.

6.6 Deep dive: why replacement is designed this way

The naive design asks Sari which specification a file belongs to on each forward. He said no. The other naive design trusts one model call. That fails N2 the day the model is confidently wrong about a file that differs by one dash.

The signals fail differently, which is why there are several. Origin fails when the channel hides the sender. Content fails when two variants share their text, or when the artwork prints no size. Geometry fails when two specifications share a can. Requiring agreement covers one signal's blind spot with another's vote, and abstaining is allowed so a missing signal does not force a guess.

Two of them are not independent, and the rule must not pretend otherwise. Reading the identity text and comparing two images are the same model looking at the same picture, so their agreement carries far less evidence than two unrelated signals agreeing. The content hash and the geometry check are the independent ones; the pairwise comparison adjudicates between candidates rather than casting a third vote.

Auto-apply ships disabled, whatever the signals say. IMP's library holds forty-five specifications and forty-five distinct customer-product-brand keys, so it contains no instance of the event this workflow exists to handle: one specification at two versions. There is nothing to set a threshold against, and the measurement had to synthesise its own positive cases for exactly that reason. Until a tenant has accumulated enough resolved replacement decisions to backtest, every replacement opens an ask and a person assents once. Those resolved asks are where the labels come from, and the gate to enable the unattended path is a backtest over held-out pairs reported per pair type rather than pooled. Pooling is what hid the perceptual hash's failure in the first place.

The candidate step matters as much as the rule. Saving first means a wrong decision is reversed by promoting a different candidate. Nothing is lost while we ask.

And the mode is policy because IMP's answer is one customer's answer. A customer with a compliance team wants to approve every version. A customer with fifty specifications and a designer who re-sends the current file to "make sure" wants the similarity ceiling. Same workflow, one policy row.

On WhatsApp the origin signal is usually absent, so content and visual have to agree on their own, and Sari will be asked more often than he would like. A short caption habit ("Al-Ain") fixes it. So does a WhatsApp group per customer, which the rulebook can record as that customer's channel.

6.7 Failure modes

What breaks What the system does
Model call fails, times out, or returns low confidence The activity retries on transient errors; a permanent failure or low confidence becomes a review task. The line stays where it was.
Worker restarts mid-chase The workflow resumes from history. Timers fire on schedule. Nothing is re-sent, because dispatch activities are idempotent on dispatch_id.
Reply arrives after the request expired The conversation workflow routes it as a late answer; the order workflow records it and raises a review task rather than confirming.
Reply from an unknown number or address A conversation workflow is created for it; with no open questions and no resolvable object, the message becomes a review task.
Two forwards of the same file Same content hash, same workflow id: the second SignalWithStart joins the first run.
Near-identical file to the current version Above the similarity ceiling the question becomes "new version or the same file?" regardless of mode.
New version lands while the PO is drafted The order workflow blocks po_decision until the affected lines are re-confirmed.
New version lands after the PO was sent Alert workflow. Nothing automatic can help.
Sari has several questions open at once Each dispatch carries asks_proposal_ids; his reply is matched to the question it answers, and an ambiguous reply is asked about.
A deploy changes workflow code Worker versioning keeps old runs on old code; new runs take the new code. Without it, replay fails loudly rather than diverging silently.
Temporal is unavailable No new signals are processed and no timers fire until it returns; deliveries keep landing in Postgres through the edge adapters, so nothing is lost, only delayed.

6.8 Testing

Temporal's time-skipping test environment runs a three-week chase in milliseconds: every reminder, escalation and supersession path is a unit test. Replay tests over recorded histories catch a non-deterministic change before deploy. A golden set of Sari's production specification files, including near-identical pairs, runs through the three signals and each policy mode with expected outcomes. Replayed production replies, including phone relays and marked-up screenshots, run through the interpreter. A property test asserts the invariant in 5.3 after any sequence of events, and a database constraint enforces it.

6.9 Phasing

7. What this means for the existing system

The greenfield is not a second building. Four things move out of the current system so both can share them, in this order, each with a parallel run before the old path is switched off:

  1. Shared dispatch. The existing SendMessage and message_deliveries become the dispatches service with an idempotency key. Tenant-visible change: none.
  2. Shared intake. The Twilio and inbox handlers write a delivery and its artifacts in the same transaction as the old rows, and announce arrivals. Old readers are untouched until each is moved and compared. A failure to write the new row cannot fail the old one.
  3. Greenfield core plus this flow. IMP goes live. No other tenant is on it. Orders themselves still come from the existing pipeline at this step: a customer's purchase order forwarded to IMP's inbound address is classified and extracted by the existing process stage (verified on next on 10 September: a three-line PO became a linked sales order in about forty seconds, with one field review), and written as an objects row of kind order from this step on, the same write path every other tenant's orders use. The ConfirmationWorkflow wakes on that row through the intake seam and reads its lines from there.
  4. The Command Center reads greenfield decisions alongside the old engine's version reviews. One queue for the operator.
  5. Specification library readers rewritten (partners UI, order-line validation, doc-gen asset manager) against specifications and specification_versions; the old brandings and order_brandings tables are dropped.

For ITCC, whose invoice versioning, tracking and document pipeline all read the inbox tables directly: nothing changes until step 2, and step 2 only adds a write. Their readers move one at a time, each with the old row still present as the fallback. What they gain once step 2 is done is the thing they asked for: email bodies read as facts rather than only attachments.

8. Open questions

Appendix A. What exists today

The current system (next branch) has more of this than it looks, and none of it is the runtime this design uses.