⚠ Reconciled at canonical schema v1.0.0. This narrative was authored against a bespoke minimal schema (Agent D, PR #1) that is now superseded. Any claim below that a construct is “UNREPRESENTABLE in schema v1.0.0” or “pending-v1.1” refers to that bespoke schema, not the canonical one — the canonical schema expresses these constructs. The executable truth for this domain is the canonical scene fixture
conformance/case-studies/auction-family/auction-family.json, which validates + audits clean vianode conformance/runner/run.mjs. See case-studies/README.md for the reconciled status table and schema/BACKLOG-v1.1.md for the real findings.
Case Study: Auction-Family Commerce
Adversarial test corpus — executable. This is the auction-family domain in the Commerce Model Formal Sufficiency Test. Two JSON fixtures live under
conformance/case-studies/auction-family/and validate against schema v1.0.0 with zero errors. Run it:node conformance/audit.mjs conformance/case-studies/auction-family.
Fixtures:
english-auction.json— ascending-bid auction, two bidders, loser cancelleddutch-auction.json— descending-price, first-accept wins
The domain and the hard cases it stresses
Auction commerce is market-making commerce: the price is not set before the transaction begins — it is determined by competitive bidding. The hard problem for the model is representing a commitment whose counterparty and final price are both unknown at the moment the bidder acts.
The spec resolves this with three interlocking pieces:
| Piece | What it models |
|---|---|
ValueState::under_auction |
The item cannot be reserved or sold while the auction runs — the auction process controls allocation |
CommitmentState::tendered |
A bidder’s open offer, carrying the bid amount, the closing time, and (when outbid) a superseded_by pointer to the winner |
AuctionProcess auxiliary record |
Coordinates all tendered commitments; declares the winner; holds the mechanism parameters |
What this domain stresses:
-
The losing bidder must be cancelled forward, never deleted. State Monotonicity (I-2) means a
tendered→cancelledtransition is the only legal resolution for a losing bid. The commitment is preserved as an immutable record. -
superseded_byis set before cancellation. When Bidder B outbids Bidder A, A’s tendered state gainssuperseded_by: "C-BID-B"in the source of the cancellation transition. This lets the model trace the exact chain of bids that led to the outcome. -
The winning value path is deterministic:
under_auction → reserved (physical_stock) → transferred. The value never becomesavailableagain mid-auction;under_auctionis a lock. Thereservedstate carries the winningcommitment_idas the basis, making the chain I-1 verifiable. -
Payment precedes physical delivery (partially_fulfilled bridge). The winner’s commitment follows the same
accepted → partially_fulfilled → fulfilledpath as a standard order. There is no auction-specific shortcut in the lifecycle. -
AuctionMechanism is a closed set of four. The schema defines only
english,dutch,sealed_bid, andvickrey. This case study coversenglishanddutch.sealed_bidandvickreydiffer only in mechanism parameters; the commitment/value lifecycle is identical.
Fixture 1 — English Auction (english-auction.json)
This fixture mirrors the spec’s worked example in full.
Scenario: A vintage oil painting (circa 1960, artist Hassan El Glaoui) is listed by its owner through a Casablanca auction house. Reserve price: 8,000 MAD. Minimum increment: 500 MAD. Auction runs for six days.
Parties:
| id | role |
|---|---|
party_seller_hassan |
painting owner, seller |
party_bidder_a |
first bidder — loses |
party_bidder_b |
second bidder — wins |
party_auction_house |
intermediary, coordinates close |
Step-by-step flow
1. Seller lists painting
Value(val_painting_casablanca).state: available → under_auction {
auction_process_id: "AUC-ENG-001"
current_high_commitment: null
closes_at: "2026-06-11T18:00:00+00:00"
}
2. Bidder A bids 10,000 MAD
Commitment(C-BID-A): draft → tendered {
offer_amount: "10000", offer_currency: "MAD"
closes_at: "2026-06-11T18:00:00+00:00"
superseded_by: null
}
AuctionProcess(AUC-ENG-001).current_high_commitment: "C-BID-A"
3. Bidder B outbids at 12,000 MAD
Commitment(C-BID-B): draft → tendered { offer_amount: "12000", superseded_by: null }
Commitment(C-BID-A).tendered.superseded_by: "C-BID-B" ← set before cancellation
AuctionProcess(AUC-ENG-001).current_high_commitment: "C-BID-B"
4. Auction closes (2026-06-11T18:00:00+00:00)
Commitment(C-BID-B): tendered → accepted
Commitment(C-BID-A): tendered → cancelled { by: "party_auction_house", reason: "Outbid" }
Value(val_painting_casablanca).state: under_auction → reserved {
commitment_id: "C-BID-B", basis: "physical_stock"
}
AuctionProcess(AUC-ENG-001).state: closed {
winning_commitment: "C-BID-B"
winning_price_str: "12000", winning_currency: "MAD"
reason: "normal_close"
}
5. Fulfillment (same as a standard order, two-step)
Fulfillment(F-PAY-AUCTION-1): planned → in_progress → completed (bank transfer 12,000 MAD)
Commitment(C-BID-B): accepted → partially_fulfilled → fulfilled
Fulfillment(F-DEL-AUCTION-1): planned → in_progress → completed (in-person handover at auction house)
Value(val_painting_casablanca).state: reserved → transferred { to: "party_bidder_b" }
Lifecycle as a transition table
Intent INT-BID-A: active → abandoned
Intent INT-BID-B: active → converted("C-BID-B")
Commitment C-BID-A (loser): draft → tendered → cancelled
Commitment C-BID-B (winner): draft → tendered → accepted → partially_fulfilled → fulfilled
Fulfillment F-PAY-AUCTION-1: planned → in_progress → completed
Fulfillment F-DEL-AUCTION-1: planned → in_progress → completed
Fixture 2 — Dutch Auction (dutch-auction.json)
Scenario: Zust x Casablanca releases a limited-edition sneaker (50 units) via a Dutch auction on the Agora platform. Starting price: 3,000 MAD, decrementing by 100 MAD every 30 minutes. The first buyer to accept the current displayed price wins their unit immediately — the auction for that unit closes the instant the first-accept fires.
Parties:
| id | role |
|---|---|
party_brand_zust |
seller and fulfiller |
party_buyer_youssef |
buyer — accepts at 1,500 MAD |
party_platform_agora |
Agora commerce OS, orchestrates the dutch clock |
Mechanism parameters
"dutch": {
"start_price_str": "3000",
"start_currency": "MAD",
"decrement_str": "100",
"interval_seconds": 1800
}
Price at accept: 3,000 − (100 × 15 intervals) = 1,500 MAD at T+7.5 hours.
Step-by-step flow
1. Unit listed under Dutch auction
Value(val_sneaker_limited).state: under_auction {
auction_process_id: "AUC-DUTCH-001"
current_high_commitment: null (Dutch: no bids until first-accept)
closes_at: "2026-06-13T18:00:00+00:00"
}
2. Youssef accepts at 1,500 MAD (T+7.5h after open)
Commitment(C-DUTCH-Y): draft → tendered {
offer_amount: "1500", offer_currency: "MAD"
superseded_by: null
}
Platform immediately applies first-accept rule:
Commitment(C-DUTCH-Y): tendered → accepted (same timestamp)
AuctionProcess(AUC-DUTCH-001).state: closed {
winning_commitment: "C-DUTCH-Y"
winning_price_str: "1500"
reason: "normal_close"
}
3. Fulfillment
Fulfillment(F-PAY-DUTCH-1): planned → in_progress → completed (card, 1,500 MAD)
Commitment(C-DUTCH-Y): accepted → partially_fulfilled → fulfilled
Fulfillment(F-DEL-DUTCH-1): planned → in_progress → completed (Aramex, 7 days)
Value(val_sneaker_limited).state: transferred { to: "party_buyer_youssef" }
Dutch vs. English: model-level difference
In a Dutch auction the tendered and accepted transitions fire at the same
timestamp — the first-accept is simultaneously the bid and the acceptance.
The superseded_by field stays null because there is only ever one
tendered commitment per unit: the winner. There are no losers to cancel.
The value still traverses under_auction → reserved → transferred — the
same path as the English auction — because the item is locked during the
clock run and only released to the buyer on commitment acceptance.
Invariants exercised
| Invariant | How the auction domain exercises it |
|---|---|
| I-1 Value Conservation | value.under_auction.auction_process_id resolves to a real AuctionProcess. AuctionProcess.tendered_commitments lists only commitment ids that exist in the fixture. AuctionProcess.closed.winning_commitment resolves to a real commitment. value.reserved.commitment_id resolves to the winner. |
| I-2 State Monotonicity | Losing bid: draft → tendered → cancelled (legal). Winning bid: draft → tendered → accepted → partially_fulfilled → fulfilled (legal). No backward transitions. The superseded_by pointer is set in the source state of the cancellation transition, not as a separate mutation. |
| I-3 Capacity Verification | Both the winning bidder and the seller have verified_at timestamps before the commitment reaches accepted. |
| I-4 Temporal Integrity | Every history entry’s at timestamp is ≥ the previous entry’s at. The English auction demonstrates multi-day non-decreasing timestamps; the Dutch auction demonstrates same-second tendered→accepted. |
| I-5 Identity Permanence | All ids across parties, values, intents, commitments, fulfillments, and auction processes are unique within each fixture. |
Extensions relied upon
| Extension | What it adds |
|---|---|
| AuctionProcess | The coordination record that holds mechanism parameters, the list of tendered commitments, and the closed state with the winning commitment reference. |
| Tendered commitment state | Allows a commitment to express an open offer whose counterparty acceptance is contingent on a mechanism (highest bid at close, first accept). Carries offer_amount, offer_currency, closes_at, superseded_by. |
| UnderAuction value state | Prevents a value from being reserved or sold while an auction is active. Carries auction_process_id, current_high_commitment, current_high_offer_amount/currency, closes_at. |
| English auction mechanism | reserve_price_str, reserve_currency, min_increment_str — ascending bids, highest bid at close wins. |
| Dutch auction mechanism | start_price_str, start_currency, decrement_str, interval_seconds — descending clock, first-accept wins immediately. |
What the model cannot represent (scope note)
The schema’s AuctionMechanism is a closed set of four variants:
english, dutch, sealed_bid, vickrey. The spec v0.3 prose
describes a ScoredSelection variant for government procurement (where
bids are evaluated on technical + price criteria, not price alone). That
variant is not in schema v1.0.0 and is not exercised here — it belongs to
a separate government-procurement domain case study.
Run it
node conformance/audit.mjs conformance/case-studies/auction-family
# ✓ conformance/case-studies/auction-family/dutch-auction.json
# ✓ conformance/case-studies/auction-family/english-auction.json
# auditCommerce: 2 passed, 0 failed, 0 warnings, 2 fixtures