Skip to content

QE-07: an order held by price exposure answers 409 with its blockers - #263

Open
futurebuildai wants to merge 8 commits into
refactor/v1from
refactor/qe-exposure-409
Open

futurebuildai wants to merge 8 commits into
refactor/v1from
refactor/qe-exposure-409

Conversation

@futurebuildai

Copy link
Copy Markdown
Contributor

QE-07: confirming or fulfilling an order held by quote price exposure now answers 409 with its blockers (it was 500), and the desk says so in plain words.

Row (audit QE-07 settlement 1, slice QE-07-a first backend bullet): a dealer who confirms an order whose source quote has an unresolved price exposure needing acknowledgement gets the gate's blockers, not a server error.

What was not true and is now:

  • HandleTransition and HandleFulfil passed ErrUnresolvedExposure to httpx.WriteError (500). Both now map it through exposureBlockers to the same 409 HandleExposureGate answers. The delivery handler already mapped it.
  • OrderDetail shows a panel on that 409 naming the source quote (linked), the indexes and exposure dollars when readable, and the two ways clear: the customer acknowledges on the quote, or an owner overrides.

Tests:

  • Go (internal/order/qe07_exposure_test.go): TestQE07_ConfirmBlockedByExposureAnswers409, TestQE07_FulfilBlockedByExposureAnswers409, TestQE07_ConfirmAfterAcknowledgeSucceeds, TestQE07_ConfirmAfterOverrideSucceeds. Each mapping removed in turn shows red (confirm three tests, fulfil one), restored green.
  • vitest (OrderDetail.test.ts): blockers from a confirm 409 and from a fulfil 409.
  • Playwright: web/e2e/order-exposure-gate.spec.ts.
  • Migration: none. No OpenAPI change (409 blocker shape already in the contract).

Money checklist: 1 n/a (no new check and write pair, the gate only maps an existing refusal); 2 n/a; 3 n/a (no new lock); 4 n/a (no new money write, no transaction added); 5 n/a; 6 done (server enforces, screen only reports); 7 n/a; 8 done (gate read at confirm and fulfil); 9 n/a; 10 n/a; 11 n/a; 12 n/a; 13 n/a; 14 n/a; 15 n/a (gate runs under the existing order wall); 16 done (test asserts the refused confirm leaves the draft on revision 1); 17 n/a; 18 done (apicontract and characterization green, no contract change); 19 not yet run by me (see lead report); 20 done (desk vitest clean).

Model: Sonnet 5.5 finished it; the first commits were written by GLM-5.3.

🤖 Generated with Claude Code

futurebuildai and others added 8 commits October 30, 2026 08:17
Confirming or fulfilling an order whose source quote carries an unresolved
lumber-index exposure is refused by the service with the pricing exposure
error, which the two handlers pass to the error writer unmapped, so the desk
sees 500 internal_error. Four wire tests state the contract: the 409 with
the exposure blockers (the blocker code and the quote) on confirm and on
fulfil, and the confirm that goes through after the customer acknowledges
the price change on the quote or an owner overrides the gate.

Signed-off-by: colton <colton@futurebuild.ai>
… fulfil

HandleTransition and HandleFulfil pass the pre-ship gate's pricing exposure
error through exposureBlockers onto the same 409 conflict envelope the
exposure-gate route answers, instead of the error writer's 500
internal_error, so the dealer is told which quote holds the order and what
clears it. The delivery assign route already mapped the error; the order
module's two write routes now agree with it.

Signed-off-by: colton <colton@futurebuild.ai>
Confirm Order and Fulfil Order catch the pre-ship gate's 409 and render the
blockers inline: which quote holds the order (linked by its number), the
indexes that moved and the exposure dollars, and the two ways clear, the
customer acknowledging the price change on the quote or an owner overriding
the gate. A refusal is no longer a generic error toast. The figures and the
number are read best effort after the refusal, so the panel still speaks
when those reads fail.

Signed-off-by: colton <colton@futurebuild.ai>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
…mmodity

The seed catalog holds more than one page of products, so the framing
stud was not always in the first 200.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
…anned

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: colton <colton@futurebuild.ai>
…ate can arm

Migration 081 backfills is_commodity on the lumber subtree, but migrations
run before the seed, on an empty products table, so every seeded lumber
product kept the flag false and a quote send snapshotted no price
escalator. seedQECommodities (core/internal/app/seed/qe.go) runs the same
backfill from Run after every product insert, covering the base seed and
the opt in catalogue import.

The characterization goldens re-recorded through the harness's -update:
the quote exposure answer now names the framing composite in indexes, and
the quote.exposure.ack_required event data carries index_code, both with
state and dollars unchanged.

Signed-off-by: colton <colton@futurebuild.ai>
…wers the same blockers

The spec now finds the framing stud by a SKU the seed actually makes
(LUM-248-PREM, or DL_SPF_2X4_L08_S under the catalogue import), quotes a
quantity of ten that stays under the seeded stock, and sends the convert
with If-Match carrying the quote's revision as the quote last answered it,
asserting 201. Releasing a held order can meet the same exposure refusal
as confirm and fulfil, so handleRelease shows the blocker panel instead of
a generic toast, with a unit test beside it.

Signed-off-by: colton <colton@futurebuild.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant