Pilot transaction state machine v0.1¶
Status: Experimental working specification. Mixed. Sections 1, 3, 5, 6, 7, 8, 9, 10, and 11 are normative. Sections 2 and 4 are informative and state no requirement on an implementation.
Date: 25 July 2026
Revised: 30 July 2026, under A202-0014: the agreement.direct transition and rules version 1.3, the bilateral transaction record in section 8.1, the operated reading of offer.selected corrected in section 6.2, and the required tests for the direct formation path. Previous revision 28 July 2026, under A202-0010: the termination.agreed and agreement.amended transitions, and the error-code table completed as the registry, with the codes other documents and the manifest were already using. 26 July 2026 added the invitation events; 25 July 2026 followed conformance review.
1. Rule¶
Only signed, authorized events move transaction state. Agent messages, model outputs, adapter callbacks, and database updates do not move state by themselves.
2. Two levels of state¶
The pilot has two state machines, not one.
| Level | Scope | Stream |
|---|---|---|
| Transaction aggregate | The whole commercial request, across all counterparties | transaction |
| Negotiation session | One bilateral relationship with one counterparty | session |
They are separated for confidentiality, not for tidiness. A single shared sequence counter across concurrent bilateral sessions leaks rival activity: a supplier that observes the counter advance, or that receives a sequence conflict, learns that a competitor acted and when. Section 8 defines the concurrency rules that follow from this.
3. Aggregate states¶
| State | Meaning |
|---|---|
draft |
Request exists privately and is not discoverable |
published |
Bounded request is visible to the configured audience |
qualifying |
Candidate counterparties are presenting required evidence |
negotiating |
At least one private negotiation session is active |
agreement_pending |
One offer is selected and awaits authority, approval, or signatures |
committed |
Both parties signed the same agreement hash |
in_performance |
At least one obligation is active |
acceptance_pending |
Required performance was declared and awaits acceptance |
settlement_pending |
Acceptance conditions are met and a settlement instruction may be routed |
completed |
All required obligations and settlement conditions for the pilot are complete |
exception_open |
A performance, evidence, adapter, or settlement exception needs resolution |
cancelled |
A pre-commit transaction was cancelled |
expired |
A pre-commit deadline elapsed |
terminated |
A committed transaction ended through an authorized termination path |
cancelled, expired, completed, and terminated are terminal in v0.1.
4. State diagram¶
stateDiagram-v2
[*] --> draft
draft --> published: request.published
draft --> agreement_pending: agreement.direct
published --> qualifying: qualification.started
qualifying --> negotiating: negotiation.opened
negotiating --> agreement_pending: offer.selected
agreement_pending --> negotiating: approval.rejected
agreement_pending --> negotiating: acceptance.withdrawn
agreement_pending --> committed: agreement.committed
committed --> in_performance: obligation.activated
in_performance --> acceptance_pending: performance.declared
acceptance_pending --> in_performance: acceptance.rejected
acceptance_pending --> settlement_pending: acceptance.granted
settlement_pending --> completed: transaction.completed
draft --> cancelled: transaction.cancelled
published --> cancelled: transaction.cancelled
qualifying --> cancelled: transaction.cancelled
negotiating --> cancelled: transaction.cancelled
agreement_pending --> cancelled: transaction.cancelled
draft --> expired: deadline.elapsed
published --> expired: deadline.elapsed
qualifying --> expired: deadline.elapsed
negotiating --> expired: deadline.elapsed
agreement_pending --> expired: deadline.elapsed
committed --> exception_open: exception.opened
in_performance --> exception_open: exception.opened
acceptance_pending --> exception_open: exception.opened
settlement_pending --> exception_open: exception.opened
exception_open --> in_performance: remediation.accepted
exception_open --> settlement_pending: exception.resolved
exception_open --> terminated: transaction.terminated
committed --> terminated: termination.agreed
in_performance --> terminated: termination.agreed
acceptance_pending --> terminated: termination.agreed
settlement_pending --> terminated: termination.agreed
exception_open --> terminated: termination.agreed
5. Aggregate transition table¶
| Current | Event | Guard | Next | Required side effect |
|---|---|---|---|---|
draft |
request.published |
Publisher mandate permits disclosure; request schema valid | published |
Add directory index |
draft |
agreement.direct |
No session stream exists on the transaction; the referenced offer is current and unexpired; an Acceptance signs the exact offer hash; the offer carries the offeror's signature and the acceptance the offeree's |
agreement_pending |
Record the party-minted session identifier on the transaction record |
published or qualifying |
invitation.issued |
Inviting mandate permits invitation.issue; disclosure constraints pass; rate limit and suppression pass; grant names exactly this transaction |
unchanged | Deliver claim secret on the invited channel |
published or qualifying |
invitation.claimed |
Claim secret matches; invitation live; channel proof verified; claimant's own principal issued a root mandate bounded to this transaction | unchanged | Register the candidate |
published or qualifying |
invitation.declined |
Declining party controls the channel | unchanged | Suppress the channel for this inviting organization |
published or qualifying |
invitation.expired |
Authoritative clock passed expires_at |
unchanged | Delete the contact record after the retention window |
published or qualifying |
invitation.revoked |
Inviting mandate permits invitation.revoke |
unchanged | Invalidate the claim secret |
published |
qualification.started |
At least one candidate and qualification profile exist | qualifying |
Open qualification window |
qualifying |
negotiation.opened |
Candidate passed required evidence checks | negotiating |
Create isolated session and session stream |
negotiating |
offer.selected |
Referenced offer is current and accepted in its session; selection authority valid | agreement_pending |
Freeze single-award selection version |
agreement_pending |
approval.requested |
Approval rule matched | agreement_pending |
Notify exact approver |
agreement_pending |
approval.rejected |
Approval binds selected offer hash | negotiating |
Release selection freeze |
agreement_pending |
acceptance.withdrawn |
Withdrawal authorized and before commitment | negotiating |
Release selection freeze |
agreement_pending |
agreement.committed |
Approvals complete; agreement names the acceptance; both parties sign the same hash | committed |
Emit commitments and export-ready event; close competing sessions |
committed |
obligation.activated |
Obligation derives from agreement | in_performance |
Start due-condition monitoring |
in_performance |
performance.declared |
Evidence manifest present | acceptance_pending |
Notify accepting party |
acceptance_pending |
acceptance.rejected |
Rejection reason and evidence present | in_performance |
Activate rework obligation if agreed |
acceptance_pending |
acceptance.granted |
Acceptance authority valid | settlement_pending |
Permit settlement instruction |
settlement_pending |
settlement.instructed |
Payment mandate and conditions valid | settlement_pending |
Invoke sandbox payment adapter |
settlement_pending |
transaction.completed |
All required obligations accepted; required receipts present | completed |
Seal audit bundle |
| eligible pre-commit | transaction.cancelled |
Canceller has authority; no committed agreement | cancelled |
Close sessions and directory listing |
| eligible pre-commit | deadline.elapsed |
Authoritative clock exceeds deadline | expired |
Close sessions and directory listing |
| eligible committed | exception.opened |
Exception type, scope, and evidence supplied | exception_open |
Pause affected obligation |
exception_open |
remediation.accepted |
Parties accept remediation hash | in_performance |
Activate remediation obligation |
exception_open |
exception.resolved |
Resolution conditions met | settlement_pending |
Resume eligible settlement |
exception_open |
transaction.terminated |
Termination authority and evidence valid | terminated |
Seal termination audit bundle |
eligible committed or exception_open |
termination.agreed |
Both parties signed the same termination record hash; the record names the disposition of every open obligation | terminated |
Release named obligations; seal termination audit bundle |
committed or in_performance |
agreement.amended |
Superseding agreement version formed per canonical model section 10.1: fresh offer, fresh acceptance, both signatures | unchanged | Record the superseding version; activate obligations the amendment adds |
Eligible pre-commit states are draft, published, qualifying, negotiating, and agreement_pending. Eligible committed states are committed, in_performance, acceptance_pending, and settlement_pending.
5.1 Invitation changes no aggregate state¶
The five invitation events are self-loops. An invitation is a fact about the market around a transaction, not a step in the transaction, and it grants participation rather than authority. The full object model, claim flow, custody rules, and abuse controls are in counterparty-invitation-v0.1.md.
invitation.claimed is how a candidate comes to exist when the request audience is invited rather than public. The published to qualifying guard, "at least one candidate and qualification profile exist", is satisfied by a claimed invitation exactly as it is satisfied by a directory response.
Invitation is denied from draft, because an unpublished request has no audience and inviting a party to it would disclose it outside the state machine. It is also denied from negotiating onward, because a late entrant joins a market that is already moving and this revision does not resolve the fairness and timing-disclosure questions that raises. Both refusals return A202-STATE-TRANSITION-DENIED.
Invitation events are readable by the inviting party and the operator. An invited party reads only its own invitation and its own acceptance, and never reads the transaction stream. This is why invitation did not get a third stream kind: a per-invitation stream would need sequencing, and any counter shared across invitations on one transaction is a covert channel of exactly the kind section 8 exists to prevent.
5.2 Consensual termination is not an exception¶
transaction.terminated ends a committed transaction through the exception path: something went wrong, the exception record says what, and termination is its resolution. termination.agreed ends one because both parties chose to end it. Before this event existed, two parties who agreed to walk away had to manufacture an exception in order to reach terminated, which put a false fault record on transactions that had none. The guard requires both signatures over one termination record hash for the same reason an agreement requires both over one agreement hash, and the record MUST name the disposition of every obligation that is not yet terminal, so that no obligation is left stranded in pending, due, asserted, rejected, or disputed on a transaction that no longer exists. The named obligations move to released under obligation-v0.1.md section 6.
5.3 Direct formation needs no venue¶
agreement.direct exists because the only route into agreement_pending ran through publication, qualification, and a negotiation room, and each of those three steps requires something neither party is. A request is published to a directory, candidates are qualified against a profile, and a session and its stream are created and ordered on negotiation.opened. Two organisations that already know each other, have each other's mandates, and have agreed terms had no way to record an agreement at all, and an implementation graded on the bilateral surface could not reach any state past draft without an operator. That is the defect this transition closes, and it is adopted through A202-0014.
Four things hold on the direct path, and each is a guard rather than a description.
- No session stream exists on the transaction. The direct path is not a way around an open negotiation. Where a room is open, offers are contending in it, and a party that entered directly would be selecting itself out of a contest the other participants are still in. A record carrying both an
agreement.directevent and a session stream on the same transaction is refused at replay withA202-EVIDENCE-TRANSITION-ILLEGAL, and live withA202-STATE-TRANSITION-DENIED. - The offer is current and unexpired, on the ordinary rules of section 7. Nothing about the entry path relaxes what makes an offer acceptable.
- The acceptance signs the exact offer hash, so that the two parties have agreed to the same bytes rather than to the same description of them.
- Both parties have signed. The offer carries the offeror's signature and the acceptance the offeree's, which is the same pair of acts section 10 of the canonical model already requires. The transition reaches
agreement_pending, nevercommitted:agreement.committedis still a separate event under its own guard, because approval, authority, and the dual signature over the agreement bytes are not established by the fact that an offer was accepted.
Skipping publication, qualification, and negotiation removes no check that bore on the agreement. Publication makes a request discoverable, qualification decides who may bid, and a negotiation room isolates concurrent counterparties from each other. Where there is one counterparty, already found, already known, and not concurrent with anyone, all three are answering questions nobody asked.
The session identifier does not disappear on this path. An Offer carries session_id and the schema keeps it REQUIRED, because a bilateral exchange is a session: it is one relationship with one counterparty, and every offer in it belongs to that relationship. What disappears is the ordering service. The offeror mints the ses_ identifier on its first offer and the counterparty adopts it, exactly as it adopts the offer's other bytes, and there is no session stream, no sequence counter, and nothing for a third party to order. This is stated in section 9.2 of canonical-commercial-model-v0.1.md.
Rules version 1.3 registers the transition. Versions 1.0, 1.1, and 1.2 are immutable, so a record made under any of them replays against the set that was in force when it appended and agreement.direct is illegal there. This is the same treatment termination.agreed received when 1.2 registered it, and for the same reason: editing a rules version in place would change the answer to a question that was already asked.
6. Negotiation session states¶
| State | Meaning |
|---|---|
opened |
Session created; no offer exchanged |
active |
At least one offer exchanged |
paused_for_approval |
An action is held pending a named approver |
accepted |
An Acceptance was signed over a current offer hash |
rejected |
Closed without acceptance |
withdrawn |
The offeror withdrew before acceptance |
expired |
The session deadline elapsed |
closed |
Terminal, after aggregate commitment or cancellation |
6.1 Session transition table¶
| Current | Event | Guard | Next |
|---|---|---|---|
opened |
offer.submitted |
Offer complete, authorized, current, signed; terms validate against the transaction profile | active |
active |
offer.submitted |
supersedes_offer_id references the immediately preceding offer in this session |
active |
active |
clarification.sent |
Disclosure constraints satisfied | active |
active |
approval.requested |
Approval rule matched | paused_for_approval |
paused_for_approval |
approval.granted |
Approval binds the exact action hash | active |
paused_for_approval |
approval.rejected |
Approval binds the exact action hash | active |
active |
offer.accepted |
Offer current, unexpired, signed; acceptor authorized; Acceptance signs the exact offer hash |
accepted |
active |
offer.withdrawn |
Withdrawal before any acceptance | withdrawn |
active |
deadline.elapsed |
Session deadline passed | expired |
accepted |
session.closed |
Aggregate reached committed or cancelled |
closed |
| any non-terminal | session.closed |
Aggregate left negotiating or agreement_pending |
closed |
offer.accepted is the event that was missing from the previous revision. Without it the Acceptance object had no transition, the mandate action offer.accept was unreachable, and the happy-path test could not pass.
6.2 Relationship to the aggregate¶
An Acceptance moves the session to accepted. It does not commit the transaction.
Where the transaction was published and negotiated, the buyer then appends offer.selected to the transaction stream, referencing the accepted offer. Commitment follows. This keeps the acceptance private to the session until the moment the aggregate records a winner.
Competing sessions are paused when offer.selected is appended and close as rejected with a neutral reason code after agreement.committed. The reason code MUST NOT disclose the winning price, the winning counterparty, or the number of competing sessions.
Both paragraphs above describe the operated selection, and only it. Competing-session closure is machinery for a venue that opened several rooms on one request, and pausing and closing rivals is an act of the party that ordered those rooms. On the direct path of section 5.3 there are no competing sessions, no rooms to pause, and no ordering service to pause them: selection is moot, because the transaction reaches agreement_pending through agreement.direct and never passes through negotiating at all. A bilateral implementation is not assessed on offer.selected, and an implementation that never emits one has skipped nothing it needed.
7. Offer rules¶
- A counteroffer creates a new immutable offer.
supersedes_offer_idMUST reference the immediately preceding offer in that bilateral session.- An offer accepted after
valid_untilis invalid. - An offer whose
valid_untilis not later than itscreated_atis invalid. - Withdrawal is valid only before acceptance.
- Acceptance is not selection.
- Selection is not agreement.
- Approval is not agreement.
- Payment is not agreement.
- An adapter acknowledgment is not agreement.
8. Concurrency and isolation¶
Optimistic concurrency applies per stream, never across streams.
- Every action supplies
expected_sequencefor the stream it targets. - A bilateral action targets its session stream. Its
expected_sequenceis the session sequence. - An aggregate action targets the transaction stream.
- Append succeeds only when
expected_sequenceequals that stream's current sequence. - A mismatch returns
409 A202-SEQUENCE-CONFLICTwith the current sequence of that stream only.
Consequences, all of them required:
- A supplier never observes a sequence number influenced by a rival's activity.
- A supplier never receives a conflict caused by a rival's activity.
- Concurrent negotiation with three suppliers does not degrade into repeated conflict and retry.
- A denied action consumes no sequence number in any shared stream. It is recorded in the actor's private stream.
- Single-award integrity is preserved at the aggregate level: two
offer.selectedevents cannot both append, because they contend on the transaction stream where contention is intended.
The isolation requirement is testable. Required test: run three concurrent sessions, then assert that each supplier's observed sequence series is identical to the series it would have observed had it been the only participant.
8.1 The bilateral transaction record¶
Every rule above presupposes an ordering service: expected_sequence, the append that succeeds only against the current sequence, the 409 that carries a stream's sequence, and consequence 5, single-award integrity through contention on the transaction stream. None of that exists where there is no operator, and the specification said nothing about what stands in its place. This is stated here rather than left to be inferred.
Where no operator is present, the transaction record is the hash-chained event sequence each party holds and countersigns. It is dual held: each party retains its own copy, and each event is signed by the party that appended it and countersigned by the other before either treats it as part of the record. Ordering is by predecessor reference, not by a counter. Each event names the content_hash of the event it follows, and a record whose links do not form one chain from the first disclosed event is refused with A202-EVIDENCE-CHAIN-GAP. This is exactly the mechanism evidence bundles already replay at step 4 of evidence-verification-v0.1.md section 4, so nothing new is required of a verifier.
Two consequences follow.
- The shared-sequence rules apply where an operated stream exists, and only there. Consequence 4 above, that a denied action consumes no sequence number in any shared stream, and consequence 5, single-award integrity under contention, are properties of a service that assigns sequence numbers. Bilaterally a denied action is simply never countersigned into the record, and there is no contention to resolve because there is one counterparty.
- A fork is a disagreement, not a conflict. Two operators cannot both append at one sequence number, and the ordering service is what makes that true. Two parties can each hold a chain that diverges after a common ancestor, and neither holds an authority that resolves it. The divergence is visible, because both chains are signed and hash linked back to the ancestor, and it is a dispute under determination-v0.1.md rather than a sequence conflict. An implementation MUST NOT resolve a fork by preferring its own chain, by timestamp, or by length.
9. Replay¶
Replay:
- verifies the event-chain hashes within each stream;
- verifies each actor signature;
- resolves the mandate and policy decision referenced by each event;
- applies session events in sequence order, then aggregate events in sequence order;
- recomputes object and agreement hashes;
- produces final aggregate and session states;
- produces one Merkle root or ordered-event root per stream, plus a combined root for the audit bundle.
Replay MUST fail on a missing sequence, hash mismatch, invalid signature, unauthorized transition, or missing referenced policy decision.
Cross-stream ordering uses the recorded received_at from kernel_annotations for presentation only. It is never used to authorize a transition, because clock order between streams is not a security boundary.
10. Error codes¶
This table, together with the additions declared in auction-event-semantics-v0.1.md section 8.1 and conformance-role-scopes-v0.1.md section 8, is the complete refusal-code registry. A code that appears in a fixture manifest, a runner, or a report and resolves in none of the three tables is an unregistered code, and an implementation MUST NOT invent one.
| Code | Meaning |
|---|---|
A202-STATE-TRANSITION-DENIED |
Event is not allowed from current state, or a determination's state_result names a state no machine registers |
A202-SEQUENCE-CONFLICT |
The targeted stream changed since proposal |
A202-STREAM-MISMATCH |
Action targeted a stream it is not a party to |
A202-OFFER-STALE |
Offer is not current |
A202-OFFER-EXPIRED |
Offer validity elapsed, or expiry precedes creation |
A202-APPROVAL-REQUIRED |
Valid action is held for approval |
A202-APPROVAL-HASH-MISMATCH |
Approval references different bytes |
A202-AGREEMENT-HASH-MISMATCH |
Parties did not sign identical agreement bytes, an agreement's terms_hash is not the hash of its own terms, or a carried offer or acceptance hash does not match the referenced object |
A202-AGREEMENT-AMENDMENT-UNACCEPTED |
A later agreement version names the acceptance or accepted offer of the version it supersedes, instead of a fresh offer-and-acceptance cycle |
A202-MANDATE-INACTIVE |
Mandate expired, suspended, or revoked |
A202-MANDATE-INTERVAL-INVALID |
A mandate's valid_from is not strictly earlier than its valid_until |
A202-MANDATE-UNBOUNDED |
A mandate carries no constraints |
A202-MANDATE-SCOPE-TOO-BROAD |
A mandate's scope is bounded by neither transaction nor category |
A202-MANDATE-SUBJECT-AMBIGUOUS |
A mandate names both an agent and a delegated principal, or neither |
A202-MANDATE-DELEGATION-INCOHERENT |
delegation.allowed and delegation.maximum_depth contradict each other |
A202-MANDATE-DELEGATION-WIDENING |
A child mandate is wider than its parent on any axis |
A202-MANDATE-CONSTRAINT-UNKNOWN |
A constraint names an unregistered type or operator |
A202-MANDATE-STATUS-INSECURE |
A mandate's status endpoint is not HTTPS |
A202-ENDPOINT-INSECURE |
A declared endpoint other than a mandate status endpoint uses a transport other than HTTPS |
A202-MANDATE-STATUS-UNRESOLVED |
A mandate's status endpoint could not be resolved within the cache window |
A202-ANNOTATION-FORGED |
kernel_annotations were included in hashed or signed bytes, or written by other than the control plane |
A202-DISCLOSURE-POLICY-VIOLATION |
An event, reason code, or free-text field carries rival identity, price, count, timing, or other aggregate state its reader may not hold |
A202-TERMS-INVALID |
A term violates the money, percentage, quantity, or calendar representation rules of the canonical model section 7 |
A202-HASH-FORMAT-INVALID |
A declared hash is not 64 lowercase hexadecimal characters |
A202-POLICY-DENIED |
Deterministic constraint failed |
A202-DISCLOSURE-DENIED |
Action or record would disclose private strategy |
A202-EVIDENCE-UNVERIFIED |
Required evidence is absent or failed |
A202-PROFILE-UNKNOWN |
Transaction profile does not resolve |
A202-PROFILE-TERMS-INVALID |
Terms failed the named profile schema |
A202-INVITATION-UNCLAIMABLE |
Claim refused. Returned uniformly for unknown, expired, revoked, consumed, and wrong-channel secrets |
A202-INVITATION-EXPIRED |
Invitation validity elapsed, or expiry precedes issue |
A202-INVITATION-SCOPE-EXCEEDED |
A grant, or an invited party's root mandate, reaches beyond the invitation's transaction |
A202-INVITATION-HASH-MISMATCH |
Acceptance references different invitation bytes |
A202-INVITATION-CLAIM-UNSIGNED |
Acceptance lacks the claimant's attestation or the operator's issuance signature |
A202-INVITATION-SECRET-DISCLOSED |
A claim secret appeared in a shared object |
A202-ASSURANCE-UNSUPPORTED |
Declared assurance level exceeds the evidence presented |
A202-CUSTODY-APPROVAL-REQUIRED |
An operator-custodied key acted without an approval bound to the action hash |
A202-OBLIGATION-CONDITION-UNKNOWN |
A due condition names an unregistered type, or a registered type with fields belonging to a different one |
A202-OBLIGATION-CONDITION-CYCLIC |
A set of discharge conditions forms a cycle, so no obligation in it can become due |
A202-OBLIGATION-SUBJECT-UNREFERENCED |
An obligation subject does not resolve against the referenced agreement, its terms hash differs, or a named party is not a party to the agreement |
A202-OBLIGATION-ASSERTION-UNEVIDENCED |
An assertion of performance carries no evidence reference |
A202-OBLIGATION-RESPONSE-UNAUTHORIZED |
A response to an assertion was signed by a party other than the obligee |
A202-OBLIGATION-RESPONSE-HASH-MISMATCH |
A response references different assertion bytes |
A202-OBLIGATION-REMAINDER-MISSING |
A partial acceptance names no remainder obligation |
A202-OBLIGATION-TERMS-MUTATED |
An act would alter an obligation's subject, due condition, quantity, unit code, or consideration |
A202-OBLIGATION-REJECTION-REASON-UNKNOWN |
A rejection carries a reason code outside the closed list |
A202-DISPUTE-OUT-OF-WINDOW |
A dispute or appeal was raised outside the window resolved through the rules in force, or the window could not be resolved |
A202-DISPUTE-GROUNDS-UNKNOWN |
A dispute states a ground outside the closed list, or states none |
A202-DISPUTE-SUBJECT-UNREFERENCED |
A dispute or determination names no resolvable subject, names one by identifier alone, or names a subject hash that differs from the dispute it determines |
A202-DETERMINATION-EFFECT-OVERCLAIM |
A determination claims an effect greater than the referenced rules granted for this question class and these parties |
A202-DETERMINATION-SUPERSESSION-UNREASONED |
A superseding determination states no reason for superseding |
A202-DETERMINATION-SUPERSESSION-FORKED |
A determination supersedes one already superseded, or an appeal targets a superseded determination |
A202-DETERMINATION-NOT-FOLLOWING |
A determination's stated outcome does not follow from its referenced rules and inputs |
A202-APPEAL-GROUNDS-UNKNOWN |
An appeal states a ground outside the closed list, including disagreement with the rule itself |
A202-EVIDENCE-HASH-MISMATCH |
A recomputed hash differs from the declared one |
A202-EVIDENCE-SIGNATURE-INVALID |
A signature does not verify over the canonical bytes, was issued for a different purpose, or a required signature is absent |
A202-EVIDENCE-CHAIN-GAP |
A version chain or a disclosed stream has a gap, or a chain forks |
A202-EVIDENCE-TRANSITION-ILLEGAL |
A replayed transition was not legal, its guard did not hold, or its actor was not authorized |
A202-EVIDENCE-TYPE-UNKNOWN |
An evidence reference names a type that does not resolve in the registered list |
A202-EVIDENCE-REPORT-INVALID |
A verification report states no scope, collapses a gap into a pass or a failure, or reduces the per-check results to a boolean |
A202-EVIDENCE-DISCLOSURE-INCOMPLETE |
A disclosed subset cannot be verified without an object that was not disclosed to that verifier |
A202-SETTLEMENT-RAIL-UNKNOWN |
A settlement rail identifier does not resolve in the registered rail set |
A202-SETTLEMENT-TRIGGER-ABSENT |
A settlement trigger is absent, does not resolve, or does not satisfy the named condition |
A202-SETTLEMENT-IDEMPOTENCY-CONFLICT |
A recorded instruction identifier and idempotency key pair was presented with different content |
A202-SETTLEMENT-RECEIPT-UNMATCHED |
A receipt references no issued instruction identifier and idempotency key pair |
A202-SETTLEMENT-CUSTODY-REFUSED |
An instruction would place funds in the custody of the commercial layer |
A202-EXTENSION-UNSUPPORTED |
Returned uniformly for an absent carrier extension declaration, an unparseable version declaration, an incompatible version, and an unretrievable capability surface |
11. Required transition tests¶
- Happy path from
drafttocompleted, includingoffer.acceptedandoffer.selectedas distinct events. - Cancellation from every eligible pre-commit state, including
draft. - Expiry from every eligible pre-commit state.
- Illegal direct transition from
publishedtocommitted. - Performance event before commitment.
- Settlement instruction before acceptance.
- Two concurrent offer selections on the transaction stream.
- Acceptance of a stale offer.
- Acceptance of an offer whose expiry precedes its creation.
- Approval over a changed offer.
- Withdrawal of an acceptance before commitment.
- Exception, remediation, and resumed performance.
- Termination from exception.
- Replay after adapter failure.
- Isolation: three concurrent sessions produce sequence series indistinguishable from solo participation.
- Isolation: a losing supplier's close reason code contains no price, counterparty, or session-count information.
- A denied action leaves every shared stream sequence unchanged.
- Onboarding by invitation from
publishedthrough to a committed agreement, by an organization with no prior A202 presence. - Invitation refused from
draftand fromnegotiating. - Isolation: an invited party cannot determine, from response content, error codes, or timing, how many other invitations were issued on the same transaction.
- An operator-custodied key attempts a commercial act with no bound approval and is refused.
- Consensual termination from each eligible committed state, with every open obligation named and released.
termination.agreedwith one signature, or with an open obligation the record does not name, is refused.- Amendment under section 10.1 of the canonical model: a superseding version through a fresh offer and acceptance succeeds; a re-versioned agreement naming the prior acceptance is refused.
termination.agreedreplayed against a rules version that never permitted it is illegal at replay.- Direct formation:
agreement.directcarries a transaction fromdrafttoagreement_pendingunder rules version 1.3, with a party-minted session identifier, a current offer, an acceptance over the exact offer hash, and both parties' signatures, and the transaction proceeds tocompletedwith no operator-authored, operator-ordered, or operator-annotated object anywhere in the record. - Direct formation refused:
agreement.directon a transaction that already carries a session stream;agreement.directreplayed against a rules version that never permitted it; and a record replayingdraftstraight tocommitted, which no rules version permits.
Tests 1, 2, and 3 traverse the operated entry path. Test 1 begins at request.published, and tests 2 and 3 require cancellation and expiry from published, qualifying, and negotiating, which exist only where an operator publishes, qualifies, and opens a room. Tests 26 and 27 are their bilateral counterparts, and the scope each test belongs to is recorded in conformance-role-scopes-v0.1.md sections 4.2 and 5.2.