# A202, the Verifiable Agreement Protocol for Agent-Led Commerce
This file is the concatenated markdown of the A202 site and specification set, for language models. The curated map is https://a202.org/llms.txt and the source of truth is https://github.com/a202-protocol/a202.
---
Source: https://a202.org/index.md
---
title: A202
description: "A202, the Verifiable Agreement Protocol for Agent-Led Commerce: an open, carrier-neutral specification of commercial authority, negotiation state, and verifiable conformance for transactions between independent organisations and the software agents acting for them."
hide:
- navigation
- toc
---
v0.1 working documents
# A202
The Verifiable Agreement Protocol for Agent-Led Commerce
An open, carrier-neutral specification of commercial authority, negotiation
state, and verifiable conformance for transactions between independent
organisations, including transactions conducted on their behalf by software
agents.
[Read the introduction](introduction.md){ .md-button .md-button--primary }
[The specification](schemas/canonical-commercial-model-v0.1.md){ .md-button }
[The charter](CHARTER.md){ .md-button }
## Five questions every transaction must answer
Two organisations that have never transacted before need a shared answer to a
small number of questions. Existing agent, payment, and identity
specifications answer parts of the first and none of the rest. A202 specifies
the missing layer.
- :material-account-key:{ .lg .middle } **Authority**
---
Who is this counterparty, and who inside it authorised this act? What
exactly was that party permitted to commit to, and was the act inside
that permission?
[Commercial mandate](authority/commercial-mandate-v0.1.md)
- :material-state-machine:{ .lg .middle } **State**
---
What state is this transaction in, and what changed it? Only a signed,
authorised event moves state. A message, a model output, and a database
write do not.
[Transaction state machine](negotiation/pilot-transaction-state-machine-v0.1.md)
- :material-eye-off:{ .lg .middle } **Disclosure**
---
What did the other side learn, and what did it not? Disclosure is a
declared policy with a default of revealing nothing across
counterparties, and every disclosure is a recorded event.
[Canonical commercial model](schemas/canonical-commercial-model-v0.1.md)
- :material-history:{ .lg .middle } **Record**
---
If this is disputed a year from now, what can be reconstructed, by whom,
from what? Every claim is checkable by replaying signed records, with no
privileged access to anyone's infrastructure.
[Evidence verification](evidence/evidence-verification-v0.1.md)
## Three layers
The specification set is organised in three layers that work together, and
each layer is checkable on its own.
```mermaid
flowchart TB
subgraph objects ["Commercial objects"]
direction LR
M[Mandate] ~~~ O[Offer] ~~~ AG[Agreement] ~~~ OB[Obligation] ~~~ EV[Evidence] ~~~ D[Determination]
end
subgraph state ["Transaction state"]
direction LR
TA[Transaction aggregate] ~~~ NS[Negotiation sessions] ~~~ SE[Signed events] ~~~ DR[Disclosure rules]
end
subgraph verify ["Verification and conformance"]
direction LR
CB[Canonical bytes] ~~~ SG[Signatures] ~~~ RP[Replay] ~~~ CS[Conformance suite]
end
objects --> state --> verify
```
Typed objects carry commercial meaning: who may act, what was offered, what
was agreed, what is owed, what was proven, what was decided. Two state
machines govern how those objects move a transaction, one for the aggregate
and one for each bilateral session inside it. Underneath both, every object
canonicalises to exact bytes, every commitment is a signature over those
bytes, and an executable suite of 148 fixtures decides whether an
implementation agrees with the specification.
## Meaning, not transport
A202 is carrier-neutral by construction: it defines what objects mean, not
how they travel. Objects defined here may be carried, wrapped, or referenced
by another protocol without changing what they mean.
- **[A2A carrier binding](bindings/a2a-binding-v0.1.md)**: how A202
objects travel between agents over the Agent2Agent protocol, with a
declared extension and closed failure modes.
- **[A202 MCP server](reference/a202_mcp/README.md)**: a reference server
giving an agent, over the Model Context Protocol, the seven capabilities
two organisations need in order to buy and sell from each other directly.
[How A202 composes with A2A and MCP](carriers.md){ .md-button }
## Where to go next
- :material-book-open-variant:{ .lg .middle } **Specification**
---
The canonical commercial model, the object families, and the state
machines, each marking its own normative sections.
[Introduction](introduction.md)
- :material-check-decagram:{ .lg .middle } **Conformance**
---
An executable fixture set and a normative runner. Schema validity is
necessary and not sufficient, and grades are earned per role scope.
[Conformance grades](conformance/conformance-grades-v0.1.md)
- :material-source-branch:{ .lg .middle } **Proposals**
---
Every normative change lands under a numbered proposal, so the record
of why the specification says what it says is public.
[The proposal process](proposals/README.md)
- :material-scale-balance:{ .lg .middle } **Governance**
---
How the project is run, what the sponsor does and does not control, and
when the governance is reviewed.
[Governance](GOVERNANCE.md)
---
**Status: pre-release.** No release has been made. The contents are `v0.1`
working documents under the release policy in [RELEASES.md](RELEASES.md),
and the specification is licensed under the
[Apache License, Version 2.0](LICENSE).
---
Source: https://a202.org/what-is-a202.md
---
description: "What A202 is, what the name means, what the protocol covers and deliberately does not, and how it relates to AP2, ACP, A2A, and MCP. A direct answer to each question, with links to the normative documents."
---
# What is A202?
**Status:** Informative in full. This page answers the questions a reader
or a software agent asks first, in plain terms. Every rule it mentions is
normative in the specification document it links to, and where this page
and a specification document disagree, the document is the definition.
## What is A202?
A202, the Verifiable Agreement Protocol for Agent-Led Commerce, is an
open, carrier-neutral specification of commercial authority, negotiation
state, and verifiable conformance for transactions between independent
organisations, including transactions conducted on their behalf by
software agents. It defines typed objects for delegated commercial
authority, a state machine for the transaction and for each bilateral
session inside it, rules for what may be disclosed to whom, and an
executable conformance suite that turns each of those rules into a check
an implementation either passes or fails.
In one sentence: **A202 specifies how two organisations that have never
transacted before — or their agents — reach commitments that either side
can later prove, from the record alone.**
## What does the name mean?
The name is **A202**, spoken "A two-oh-two". The long form, *A202, the
Verifiable Agreement Protocol for Agent-Led Commerce*, is a descriptor and
not an expansion: the letters do not stand for it. The `202` is HTTP 202
Accepted, the status an accepted submission returns under
[A202-0017](proposals/A202-0017-submission-success-status.md), because
acceptance is the primitive the rest of the specification is built on.
## What problem does it solve?
When software agents transact for organisations, the questions that
matter are not how messages travel but what they commit anyone to. A202
gives both sides a shared, checkable answer to five questions no message
transport answers: **who authorised this act** ([commercial
mandate](authority/commercial-mandate-v0.1.md)), **what state the
transaction is in and what changed it** ([transaction state
machine](negotiation/pilot-transaction-state-machine-v0.1.md)),
**what was agreed and what is owed**
([obligation](agreement/obligation-v0.1.md)), **what each side was
allowed to learn** ([canonical commercial
model](schemas/canonical-commercial-model-v0.1.md)), and **what can be
proven a year later, by whom, from what**
([evidence verification](evidence/evidence-verification-v0.1.md)).
## What is A202 not?
- **Not a payment protocol.** A202 stops at the [settlement
handoff](fulfillment/settlement-handoff-v0.1.md): an authorised
instruction to a payment rail. It moves no money.
- **Not a transport.** A202 is carrier-neutral: it defines what objects
mean, not how they travel. Objects may be carried by
[A2A](carriers.md), exposed over [MCP](carriers.md), or referenced by
another protocol without changing what they mean.
- **Not an identity scheme.** Parties bring their own keys; A202 defines
what a signature over canonical bytes commits the signer to.
- **Not an agent framework.** A202 constrains what agents may commit
their principals to, whatever software the agents are built with.
The full statement of scope and non-goals is the [charter](CHARTER.md).
## How does A202 relate to AP2, ACP, A2A, and MCP?
AP2 (the Agent Payments Protocol) and ACP (the Agentic Commerce Protocol)
answer payment-shaped questions: proving a purchaser authorised a payment,
and executing checkout. A202 specifies the layer those protocols assume:
how the agreement being paid for was formed, under whose authority, with
what negotiation history and disclosure record, and how any of it is
verified. A2A and MCP are carriers A202 objects travel over — see [A202
with A2A and MCP](carriers.md) and the [comparison page](comparison.md)
for the field-by-field picture.
## Who is behind A202, and under what terms?
A202 was created by A. A. Musse and is sponsored by Plural Worlds, under
a published [governance document](GOVERNANCE.md) that states what the
sponsor does and does not control. The whole repository — specification
text, schemas, fixtures, and reference implementation — is licensed under
the [Apache License, Version 2.0](LICENSE), which carries an express
patent grant from each contributor. **A202**™ is a trademark of Plural
Worlds; permitted use is stated in [TRADEMARK.md](TRADEMARK.md).
## What is its status?
**Pre-release.** The contents are `v0.1` working documents under the
release policy in [RELEASES.md](RELEASES.md); no release has been
tagged. The conformance suite currently holds 148 executable fixtures,
and every normative change lands under a numbered, public
[proposal](proposals/README.md).
## Where does an agent start?
- Machine-readable map of this site: [llms.txt](https://a202.org/llms.txt),
full text at [llms-full.txt](https://a202.org/llms-full.txt).
- Schemas resolve under `https://schemas.a202.org`.
- Reference implementation and [MCP server](reference/a202_mcp/README.md):
`pip`-installable from the [repository](https://github.com/a202-protocol/a202);
the npm package is
[`a202-protocol`](https://www.npmjs.com/package/a202-protocol).
- A human starts with the [introduction](introduction.md).
---
Source: https://a202.org/introduction.md
---
description: "An orientation to the A202 protocol: the three layers — commercial objects, transaction state, and verification — and a reading order through the specification documents."
---
# Introduction
**Status:** Informative in full. This page orients a reader; it states no
requirement. Every rule it mentions is normative in the specification
document it links to, and where this page and a specification document
disagree, the document is the definition.
## What A202 specifies
A202, the Verifiable Agreement Protocol for Agent-Led Commerce, specifies
how two independent organisations, or the software agents acting for them,
reach commitments that either side can later prove: who had authority to
act, what was offered and agreed, what is owed, what was disclosed to whom,
and how any of it is verified from the record alone.
The full statement of purpose, scope, non-goals, and design principles is
the [charter](CHARTER.md). The adversaries the specification assumes and
the properties it defends are the [threat model](THREAT-MODEL.md).
## The three layers
```mermaid
flowchart TB
subgraph objects ["Commercial objects: what things mean"]
direction LR
M[Mandate] ~~~ O[Offer] ~~~ AG[Agreement] ~~~ OB[Obligation] ~~~ EV[Evidence] ~~~ D[Determination]
end
subgraph state ["Transaction state: what may happen next"]
direction LR
TA[Transaction aggregate] ~~~ NS[Negotiation sessions] ~~~ SE[Signed events] ~~~ DR[Disclosure rules]
end
subgraph verify ["Verification: how anyone checks"]
direction LR
CB[Canonical bytes] ~~~ SG[Signatures] ~~~ RP[Replay] ~~~ CS[Conformance suite]
end
objects --> state --> verify
```
**Commercial objects.** Typed objects carry commercial meaning on a common
envelope: delegated authority as a [commercial
mandate](authority/commercial-mandate-v0.1.md), the terms of a deal as an
offer and an [agreement](schemas/canonical-commercial-model-v0.1.md),
what is owed as an [obligation](agreement/obligation-v0.1.md), what was
proven as [evidence](evidence/evidence-verification-v0.1.md), and what
was decided as a [determination](disputes/determination-v0.1.md). Domain
vocabulary enters through [transaction
profiles](schemas/transaction-profile-extension-model-v0.1.md) without
changing the kernel.
**Transaction state.** Two state machines govern movement: one for the
transaction aggregate and one for each bilateral session inside it, with
guarded transitions, per-stream concurrency, and replay rules, in the
[transaction state
machine](negotiation/pilot-transaction-state-machine-v0.1.md). Event
semantics and disclosure rules for competitive bidding are in [auction event
semantics](negotiation/auction-event-semantics-v0.1.md). Parties with no
prior presence enter one named transaction through a [counterparty
invitation](discovery/counterparty-invitation-v0.1.md).
**Verification and conformance.** Every object canonicalises to exact bytes
and every commitment is a signature over them, per the [canonical commercial
model](schemas/canonical-commercial-model-v0.1.md). The [conformance
suite](conformance/conformance-grades-v0.1.md) turns the rules into 148
executable fixtures, and [role
scopes](conformance/conformance-role-scopes-v0.1.md) name the surface a
grade covers. Settlement is handed off to payment rails as an authorised
instruction, per the [settlement
handoff](fulfillment/settlement-handoff-v0.1.md).
## How to read the documents
Every document carries a status header stating which of its sections are
**normative** and which are **informative**. The normative keywords `MUST`,
`MUST NOT`, `SHOULD`, and `MAY` are used in the RFC 2119 sense, and only
inside sections marked normative.
Schema validity is necessary and not sufficient: an implementation that
passes every schema and violates an invariant of the canonical model is not
conformant. That gap is why the conformance suite exists, and running it is
two commands, described in the [schemas overview](schemas/v0.1/README.md).
## A reading order
1. The [charter](CHARTER.md), for what this is and what it deliberately
is not.
2. The [canonical commercial model](schemas/canonical-commercial-model-v0.1.md),
for the object model, the envelope, and the invariants schema validation
cannot express.
3. The [transaction state machine](negotiation/pilot-transaction-state-machine-v0.1.md),
for what moves state and what does not.
4. The [conformance manifest](conformance/manifest-v0.1.json), for the
fixtures that decide whether an implementation agrees with either of the
above.
Every normative change that produced the current text landed under a
numbered proposal, and the [proposals](proposals/README.md) are published
so the record of why the specification says what it says is public.
---
Source: https://a202.org/carriers.md
---
description: "How the A202 protocol composes with A2A and MCP: A202 defines commercial meaning, A2A carries it between agents, and MCP gives a model the capabilities — three protocols that compose rather than compete."
---
# A202 with A2A and MCP
**Status:** Informative in full. This page describes how A202 composes with
two protocols it is designed to travel over, as a matter of technical
relationship. It states no requirement; the normative rules are in the
documents it links to.
## Three protocols, three jobs
A202 defines commercial meaning and deliberately defines no transport. The
Agent2Agent protocol (A2A) defines how two agents talk to each other. The
Model Context Protocol (MCP) defines how a model reaches tools and context. The three compose rather than compete: an agent can hold
A202 capabilities through MCP, speak to a counterparty's agent over A2A, and
what makes the resulting commitments verifiable is A202.
```mermaid
flowchart LR
subgraph buyer ["Buyer organisation"]
direction TB
MB[Model or agent runtime] -- "MCP: tools" --> SB[A202 MCP server]
end
subgraph seller ["Seller organisation"]
direction TB
MS[Model or agent runtime] -- "MCP: tools" --> SS[A202 MCP server]
end
SB <-- "A2A carrier: A202 objects as signed parts" --> SS
```
The division of labour matters because each protocol fails differently. A
carrier failure is a delivery problem; an authority failure is a commercial
problem. A202 keeps the second class out of the first: no carrier
acknowledgement, and no transport status code, is ever acceptance of an
offer. Acceptance is a signed object, and an accepted submission returns
HTTP 202, a rule the specification makes literal in
[A202-0017](proposals/A202-0017-submission-success-status.md).
## Over A2A: the carrier binding
The [A2A carrier binding](bindings/a2a-binding-v0.1.md) states how A202
objects travel between agents over A2A:
- A202 objects ride as parts of A2A messages, unchanged. The carrier
envelope carries delivery metadata; commercial meaning stays inside the
signed object, and the binding forbids the two from mixing.
- Support is declared through an extension declaration, and every failure
mode of the declaration check returns the single refusal
`A202-EXTENSION-UNSUPPORTED`, so a counterparty that cannot verify never
half-participates.
- The binding is one of two defined in
[A202-0001](proposals/A202-0001-carrier-bindings.md); the other is
plain HTTPS, for a party with no agent stack at all.
## Over MCP: the reference server
The reference implementation includes an [A202 MCP
server](reference/a202_mcp/README.md) that exposes, as MCP tools, the
seven capabilities two organisations need in order to buy and sell from each
other directly: issue a mandate, check what an agent may do under it,
approve an act that needs a person, form an agreement, exchange obligations,
verify a record, and read the transaction record back.
The server enforces the specification's rules at the tool boundary: an act
outside the mandate's constraints is refused, an approval binds to the exact
action hash it approved, and verification replays the signed record rather
than trusting the caller's summary.
## What this page is not
This page describes composition with carriers the repository defines
bindings or reference code for. It is not a survey of adjacent protocols,
and it makes no claim about any specification not linked here.
---
Source: https://a202.org/comparison.md
---
description: "A202 compared with AP2 (Agent Payments Protocol), ACP (Agentic Commerce Protocol), and UCP (Universal Commerce Protocol): what each protocol answers, what each assumes, and how they compose. Field-by-field, as of August 2026."
---
# A202 compared with AP2, ACP, and UCP
**Status:** Informative in full. This page describes other projects as
accurately as we can state them from their published documents, **as of
August 2026**; each moves on its own schedule, and where this page and a
project's own documents disagree, its documents are the definition. Where
this page and an A202 specification document disagree, the A202 document
is the definition.
## Four protocols, four questions
The agentic-commerce specifications now in circulation answer different
questions, and for the most part they are not competing answers to the
same one.
**[AP2](https://ap2-protocol.org/)**, the Agent Payments Protocol,
answers: *can this agent prove its principal authorised this payment?*
Originated by Google in September 2025 and contributed to the [FIDO
Alliance](https://fidoalliance.org/fido-alliance-to-develop-standards-for-trusted-ai-agent-interactions/)
in spring 2026, its v0.2 defines two SD-JWT mandate types — a Checkout
Mandate binding what the agent was authorised to buy, and a Payment
Mandate binding the payment authorisation — each usable in open
(pre-authorised, human-not-present) or closed form. Its specification
states its non-goals plainly: the commerce protocol itself, dispute
resolution procedures, delegation details, and negotiation are out of
scope.
**[ACP](https://github.com/agentic-commerce-protocol/agentic-commerce-protocol)**,
the Agentic Commerce Protocol, answers: *how does an agent execute a
checkout against a merchant?* Maintained by OpenAI and Stripe (latest
stable release 2026-04-17), it defines a merchant-hosted Checkout API, a
narrowly scoped delegated payment token, and a product feed
specification. It is in production behind ChatGPT Instant Checkout. Trust
rides on the platform and the scoped token; the protocol defines no
user-signed authority artifact a counterparty can hold.
**[UCP](https://ucp.dev/)**, the Universal Commerce Protocol, answers:
*how do agent surfaces and merchants integrate once instead of N×N
times?* Announced by Google with Shopify, Etsy, Target, Walmart and
others in January 2026, it specifies catalog search, cart, checkout,
identity linking, and order management, with REST, JSON-RPC, A2A, and MCP
bindings, and takes AP2 as an optional mandate extension. It ships its
own [conformance tooling](https://github.com/Universal-Commerce-Protocol/conformance)
for those capability APIs.
**A202** answers the question the other three assume an answer to: *how
was the agreement being paid for formed, under whose commercial
authority, through what negotiation, with what disclosed to whom — and
how does anyone verify any of that later, from the record alone?* A202
is deliberately not a payment protocol: it ends at a [settlement
handoff](fulfillment/settlement-handoff-v0.1.md), an authorised
instruction to exactly the rails the other three serve.
## Field by field
| Concern | A202 | AP2 v0.2 | ACP (2026-04-17) | UCP |
|---|---|---|---|---|
| Delegated commercial authority | [Commercial mandate](authority/commercial-mandate-v0.1.md): typed, signed, constraint-scoped, covering what may be *negotiated and agreed*, not only paid | Payment-scoped: Checkout and Payment mandates (SD-JWT) | None; scoped payment token | Via the AP2 extension, payment-scoped |
| Negotiation | Offer state, [auction event semantics](negotiation/auction-event-semantics-v0.1.md), per-session disclosure rules | Out of scope (stated) | None; fixed prices | None; merchant-declared discounts |
| Transaction state | Two state machines, [aggregate and per bilateral session](negotiation/pilot-transaction-state-machine-v0.1.md); only signed, authorised events move state | Delegated to carrier | Merchant-hosted session status | Merchant-hosted checkout lifecycle |
| Agreement and obligation objects | [Agreement](schemas/canonical-commercial-model-v0.1.md) and [obligation](agreement/obligation-v0.1.md) as first-class signed objects | Hash-bound checkout contents inside mandates | Order confirmation | Order object, webhook lifecycle |
| Disclosure rules | Declared policy, default reveal-nothing across counterparties, every disclosure a recorded event | SD-JWT selective disclosure at credential level | None | None |
| Evidence and disputes | [Evidence verification](evidence/evidence-verification-v0.1.md) by replay; [determination](disputes/determination-v0.1.md) objects | Non-repudiable mandates; dispute procedures and retention out of scope (stated) | Deferred to card rails / merchant of record | Not specified |
| Payment execution | Out of scope by design: [settlement handoff](fulfillment/settlement-handoff-v0.1.md) to rails | The core subject | The core subject (delegate payment) | Payment handler architecture |
| Catalog and checkout surface | Out of scope | Out of scope (stated) | Product feed, Checkout API | Catalog, cart, checkout capabilities |
| Conformance | [Executable suite](conformance/conformance-grades-v0.1.md), 148 fixtures, graded per [role scope](conformance/conformance-role-scopes-v0.1.md); schema validity necessary but not sufficient | None published | Examples and sample requests | Conformance repo for capability APIs |
| Transport stance | Carrier-neutral; [A2A binding](bindings/a2a-binding-v0.1.md), [MCP server](reference/a202_mcp/README.md) | Transport-agnostic; deployed as a UCP extension | HTTPS APIs | REST, JSON-RPC, A2A, MCP bindings |
| Governance | Public [governance](GOVERNANCE.md), numbered public [proposals](proposals/README.md), sponsor limits stated | FIDO Alliance working groups | OpenAI + Stripe maintainers, SEP process | Google-led multi-vendor open source |
| Licence | Apache 2.0 | Apache 2.0 | Apache 2.0 | Apache 2.0 |
## How they compose
Nothing above is an either/or. A transaction can be discovered and
carried on UCP or ACP surfaces, authorised for payment under AP2
mandates, and still need what A202 specifies: the mandate that authorised
the *negotiation*, the signed events that moved the *agreement* to
formed, the disclosure record of the bargaining, and the evidence a
dispute is decided from. Concretely:
- A202's [settlement handoff](fulfillment/settlement-handoff-v0.1.md)
is an authorised instruction shaped to hand a formed agreement to a
payment layer — an AP2 mandate exchange or an ACP delegated payment is
a natural recipient.
- A202 objects are carrier-neutral, and UCP's A2A and MCP bindings are
carriers like any other: an A202 agreement can be referenced from a
UCP checkout without changing what either means.
- Where AP2 scopes out dispute procedures and retention, A202's
[evidence](evidence/evidence-verification-v0.1.md) and
[determination](disputes/determination-v0.1.md) documents specify
exactly that layer.
## What this page does not compare
[A2A and MCP](carriers.md) are carriers, not commerce protocols; the
composition is described on its own page. The "Vibe Commerce Protocol"
appearing in the ACWorld paper
([arXiv:2608.02441](https://arxiv.org/abs/2608.02441), August 2026) is
the action-validation mechanism of a benchmark environment for evaluating
shopping agents, not an interoperability standard, so it does not fit the
table above.
Corrections to this page are welcome through the ordinary
[contribution process](CONTRIBUTING.md); inaccuracy about another
project is a defect.
---
Source: https://a202.org/CHARTER.md
# Charter
**Status:** Informative in full. This document defines the project's purpose, scope, and non-goals. It states no requirement on an implementation. Normative requirements are carried only by the specification documents in `schemas/`, `authority/`, `discovery/`, `negotiation/`, and `conformance/`, each of which marks its own normative sections.
**Version:** 0.1
## 1. Purpose
A202, the Verifiable Agreement Protocol for Agent-Led Commerce, is an open, carrier-neutral specification of commercial authority, negotiation state, and verifiable conformance for transactions between independent organisations, including transactions conducted on their behalf by software agents.
Two organisations that have never transacted before need to agree on more than a message format. They need a shared answer to a small number of questions with direct commercial consequence:
- Who is this counterparty, and who inside it authorised this act?
- What exactly was that party permitted to commit to, and was the act inside that permission?
- What state is this transaction in, and what changed it?
- What did the other side learn, and what did it not?
- If this is disputed a year from now, what can be reconstructed, by whom, from what?
Existing agent, payment, and identity specifications answer parts of the first and none of the rest. Existing document standards describe the artefacts of a completed trade rather than the authority and state that produced it. A202 specifies the missing layer: typed objects for authority, typed state for the transaction, explicit rules for what may be disclosed to whom, and an executable conformance suite that turns each of those into a check an implementation either passes or fails.
The specification is carrier-neutral by construction. It defines meaning, not transport. Objects defined here may be carried, wrapped, or referenced by another protocol without changing what they mean.
## 2. Scope
The specification covers the following object families. Each is present in this repository today.
| Family | Location | What it specifies |
|---|---|---|
| Canonical commercial model | `schemas/` | The common object envelope, canonicalisation and signature rules, the object inventory, money and quantity representation, the transaction profile boundary, event stream rules, and the invariants that schema validation cannot express |
| Transaction profiles | `schemas/` | How a commercial domain adds vocabulary, evidence requirements, and execution mappings without changing the kernel |
| Mandates and authority | `authority/` | Delegated commercial authority, its constraint vocabulary, delegation narrowing, approval binding, suspension and revocation, and the authority rules for a party onboarded by invitation |
| Invitations and onboarding | `discovery/` | How an organisation with no prior presence and possibly no agent infrastructure enters one named transaction, acquires its own authority, and is bounded to that transaction |
| Transaction states | `negotiation/` | The aggregate and session state machines, guarded transitions, per-stream concurrency, replay, and error codes |
| Auction event semantics | `negotiation/` | Event semantics and disclosure rules for competitive bidding, the authority requirement on every bid, award as a transition distinct from acceptance, rule freezing, and the isolation property with its verification approach |
| Conformance | `conformance/` | The executable fixture set, the manifest, the normative runner, and the conformance grade dimensions, bands, and object shape |
## 3. Non-goals
The following are outside the specification. They are named because each is adjacent enough that a reader would otherwise reasonably expect to find it here.
1. **Agent profiles and listings.** How an agent or organisation describes itself for discovery, and how supply and demand are advertised, are not specified here.
2. **Marketplace and matching.** Bringing a buyer and a seller together, ranking candidates, and recommending counterparties are not specified here. A202 begins once the parties are identified.
3. **Settlement orchestration.** The specification defines a settlement instruction as an authorised object and an adapter receipt as evidence of an external system's response. Moving money, routing payments, and reconciling ledgers belong to payment rails and are not specified here.
4. **Reputation.** No score, rating, or history-derived trust signal is defined. Assurance is reported from evidence, and conformance grades are reported from assessment. Neither is reputation, and neither is inferred from transaction outcomes.
5. **Turn-taking and impasse.** Whose turn it is, how many responses an open offer may receive, and when a negotiation is declared deadlocked are not specified in v0.1. The state machine constrains which transitions are legal, not the conversational discipline over them.
6. **Operator implementation mechanism.** How any particular operator implements isolation, evaluates policy, ranks or scores bids, sequences streams, handles award contention, normalises timing and error responses, generates adversarial test cases, or runs an appeal is outside this specification and is not published here. What is published is the property such an implementation must exhibit, the interface a counterparty implements against, and the procedure by which the property is independently verified.
A non-goal is a statement about this specification's scope. It is not a claim that the excluded capability is unimportant, and it does not prevent a later version from taking one of these up through the proposal process.
## 4. Design principles
These six principles explain why the specification is shaped as it is. Where a requirement in a specification document looks arbitrary, it usually follows from one of them.
**Fail closed.** An unrecognised value, an unresolvable reference, an unreachable status endpoint, or an unregistered constraint type produces a refusal, never a permissive default. Unavailability is not permission. Where a rule is enforced in two layers, each layer fails closed on its own, so that widening one without the other cannot let an act through.
**Explicit typed state.** Commercial meaning is carried by typed objects and named states, never by prose in a free-text field and never by the absence of an object. Only a signed, authorised event moves state. A message, a model output, an adapter callback, and a database write do not.
**Assurance is reported, never inferred.** An identity assurance level, a conformance grade, and a level of authority are each held because they were established and recorded, not because time passed or because previous transactions succeeded. Absence of an assessment reads as unassessed everywhere it is consumed, never as a pass.
**Disclosure minimalism.** A party learns what it needs in order to act and to verify, and nothing more. Disclosure is a declared policy with a default of revealing nothing across counterparties, every disclosure is a recorded event, and a refusal must not itself become a disclosure. Structures are allowlists rather than denylists, because a denylist refuses only the leaks somebody anticipated.
**Deterministic verification.** Every claim the specification makes is checkable by replaying signed records and recomputing hashes, by an authorised party with no privileged access to the operator. Replay of the same valid inputs produces the same result. No model output may serve as deterministic authority.
**Carrier neutrality.** The specification defines commercial meaning independently of any transport, agent framework, identity provider, payment rail, or enterprise system. The kernel contains no field that is meaningful in only one commercial domain, and that property is tested by a fixture rather than asserted.
## 5. Conformance language
The specification documents use `MUST`, `MUST NOT`, `REQUIRED`, `SHOULD`, `SHOULD NOT`, and `MAY` in the sense of RFC 2119 and RFC 8174, and only inside sections marked normative.
Schema validity is necessary and not sufficient. An implementation that passes every schema and violates an invariant listed in the canonical model is not conformant. The conformance suite exists because that gap is real and is otherwise invisible.
---
Source: https://a202.org/THREAT-MODEL.md
# Threat model
**Status:** Informative in full. This document states no requirement of its own. Every rule it describes is normative in the specification document it cites, and where this document and a specification document disagree, the document is the definition and this summary is wrong.
**Date:** 31 July 2026
## 1. What this document is
[SECURITY.md](SECURITY.md) declares five classes of defect in scope for a vulnerability report. This document is the analysis behind that list: the adversaries the specification assumes, the properties it defends against them, where each defence is written down, and what is out of scope.
It is a threat model of the specification, not of any implementation. An implementation has an attack surface of its own, such as key storage, parsing, transport, and deployment, and owes its users its own threat model. What belongs here is the layer above: the ways the specification as written could permit, or fail to prevent, an outcome no conformant party agreed to. A defect at that layer is exploitable against every implementation at once, which is why it is the layer this repository accepts reports for.
## 2. What is defended
Four properties. Each is a claim a counterparty must be able to rely on without trusting the party asserting it, and each is stated here together with the mechanism-independent form in which the specification carries it.
1. **Authority.** An act takes commercial effect only under a valid, unexpired, unrevoked chain of mandates, and only inside the scope that chain grants. The chain rules are in [authority/commercial-mandate-v0.1.md](authority/commercial-mandate-v0.1.md).
2. **Disclosure control.** A party learns what its declared disclosure policy entitles it to learn, and nothing more: not through content, and not through sequence numbers, reason codes, refusal shapes, or response timing. The default across counterparties is that nothing is revealed. The rules are in the canonical model's event stream and private strategy sections and, for competitive events, in [negotiation/auction-event-semantics-v0.1.md](negotiation/auction-event-semantics-v0.1.md) section 6, which states isolation as a property with a verification approach.
3. **Commitment integrity.** A party is bound only to bytes it signed. Signatures are computed over canonical bytes, checked against a declared purpose, and an agreement exists only when both parties have signed the same content. Formation rules are in the canonical model; the signing rules are in [schemas/canonical-commercial-model-v0.1.md](schemas/canonical-commercial-model-v0.1.md) section 4.
4. **Replayability.** A record replays to the state it claims and to no other, by an authorised party with no privileged access to anyone's infrastructure. The replay rules are in the state machine and the canonical model's event stream section; the verification procedure over evidence is [evidence/evidence-verification-v0.1.md](evidence/evidence-verification-v0.1.md) section 4.
These properties are not features beside the specification. They are the reason it exists, and the [charter's](CHARTER.md) design principles (fail closed, explicit typed state, assurance reported never inferred, disclosure minimalism, deterministic verification) are the general form of the defences enumerated below.
## 3. Adversaries assumed
Ordered by how much of the design each one stresses. Every adversary below is assumed to have read every published document, including this one.
| Adversary | Capability assumed | Primary target |
|---|---|---|
| **Strategic counterparty** | Fully conformant, patient, unlimited transactions, analyses everything it is sent and everything it is refused | Disclosure control |
| **Forging counterparty** | Authors and signs arbitrary objects with keys it legitimately holds | Commitment integrity |
| **Over-reached agent** | Holds a legitimate agent key; is compromised, mis-instructed, or induced to act beyond what its principal intended | Authority |
| **Hostile evidence author** | Supplies evidence, free-text notes, and profile terms crafted to influence a human approver or a downstream agent | Authority and commitment integrity |
| **Venue or operator** | Runs the venue: sees every object, orders every event, custodies keys for onboarded parties | All four |
| **Malicious implementer** | Ships an implementation that passes the published conformance suite and behaves differently on inputs the suite does not contain | The conformance claim itself |
| **Specification supply-chain adversary** | Tampers with a schema, a fixture, or a release artefact in transit or at its host | All four, against every implementation at once |
| **Network observer** | Observes traffic volume and timing on the carrier; cannot break the carrier's transport security | Disclosure control. This adversary is *not* defended; see section 7 |
The operator appears in this list deliberately. The specification's verification principle is that every claim is checkable by replaying signed records and recomputing hashes with no privileged access to the operator. A venue that asks to be trusted instead of checked is not implementing this specification.
## 4. Threats and where the specification answers them
Grouped by the property under attack. The right-hand column names the refusal code or verification path a reader can follow into the fixtures; the [conformance manifest](conformance/manifest-v0.1.json) is the single source for what the suite contains.
### 4.1 Against authority
| Threat | The specification's answer | Refused with / checked by |
|---|---|---|
| Acting under an expired, suspended, or revoked mandate | Every link in the chain must be verifiable; missing, expired, suspended, revoked, or scope-incompatible links fail closed | `A202-MANDATE-INACTIVE` |
| Delegation that widens authority | A child mandate can only narrow its parent. Narrowing is monotonic and checked, not assumed | `A202-MANDATE-DELEGATION-WIDENING` |
| Reusing a human approval | An approval is bound to the hash of one action in one transaction. Changing one byte of the action invalidates it; it cannot be reused across actions or transactions | Mandate specification section 8 and its fixtures |
| An operator signing on behalf of a party it custodies keys for | A custodied subject's act requires an approval bound to the principal, enforced whether or not the mandate carries the rule. A forged operator signature alone moves nothing | `A202-CUSTODY-APPROVAL-REQUIRED` |
| A constraint the evaluator cannot evaluate | An unevaluable constraint denies. Unavailability is not permission | Fail-closed principle, mandate specification |
| An invited party acting beyond the transaction it was invited into | Invitation-derived authority is bounded to the named transaction | `A202-INVITATION-SCOPE-EXCEEDED` |
| Injection through free text into an approver or an agent | Commercial meaning is never carried by prose. Free-text fields are bounded, non-load-bearing data; the evidence locator hint is explicitly never load-bearing; typed fields cannot invoke anything | Explicit-typed-state principle; evidence specification section 3.2 |
### 4.2 Against disclosure control
| Threat | The specification's answer | Refused with / checked by |
|---|---|---|
| Inferring a rival's existence, activity, or terms in a sealed event | Isolation stated as a property covering content, sequence numbers, reason codes, and response timing, with a verification approach a losing bidder can run over its own replayable export | Auction event semantics section 6 |
| A refusal that is itself a disclosure | Disclosure is a declared policy defaulting to nothing across counterparties; a refusal must not reveal what the policy withholds. Concretely: under a sealed policy, a late bid is refused with the ordinary state-transition refusal, because the award-specific code would disclose that an award has happened | `A202-DISCLOSURE-POLICY-VIOLATION`; auction event semantics section 4 |
| Undeclared or asymmetric disclosure by a venue | Any disclosure an event makes must be declared before rules freeze, applied symmetrically, and recorded as an event | Auction event semantics sections 4 and 5 |
| Reservation values or strategy leaking into shared objects | The private strategy boundary: strategy never enters a shared object, and structures are allowlists, so a field nobody anticipated cannot leak by default | Canonical model section 13 |
### 4.3 Against commitment integrity
| Threat | The specification's answer | Refused with / checked by |
|---|---|---|
| Binding a party to bytes it never signed | Signatures are over RFC 8785 canonical bytes with the declared purpose checked at verification. An agreement requires both parties' signatures over the same content | Canonical model sections 4 and 10 |
| Amount drift through number representation | Money is a base-10 string with an ISO 4217 currency, never a float, so canonicalisation cannot change a price | Canonical model section 7 |
| Mutating terms between offer and acceptance | Responses carry the content hash of what they respond to; a mutated document no longer matches | `A202-OBLIGATION-TERMS-MUTATED`, `A202-OBLIGATION-RESPONSE-HASH-MISMATCH` |
| Reusing an old acceptance to form a new agreement or amendment | Acceptance is bound to what it accepted; an amendment that reuses a prior acceptance is a negative fixture in the published suite | Agreement formation rules, canonical model section 10 |
| A determination claiming more effect than the rules granted | The effect is read from the referenced rule set, never from the determiner's claim, and both layers of the check fail closed on their own. Where the rules resolve to nothing, the effect is advisory, never inferred upward | `A202-DETERMINATION-EFFECT-OVERCLAIM` |
### 4.4 Against replayability
| Threat | The specification's answer | Refused with / checked by |
|---|---|---|
| Rewriting history | Objects are never edited; a new version points at its predecessor and both stay verifiable. Replay recomputes every hash and signature rather than trusting a declared one | Canonical model; evidence verification steps 1 to 3 |
| Splicing or reordering an event stream | Per-stream sequence continuity, checked | `A202-SEQUENCE-CONFLICT`, `A202-STREAM-MISMATCH` |
| Smuggling state change through an event type replay does not check | The transaction event stream is an allowlist; an event outside it is not part of the record | Canonical model section 11 |
| A gap in an evidence chain presented as verified | The verification output is three-valued per check (verified, failed, not checkable) and has no overall boolean, so a gap cannot be rounded up to a pass | `A202-EVIDENCE-CHAIN-GAP`; evidence verification section 5 |
| Hiding tampering behind selective disclosure | Selective disclosure states what was withheld; the report carries the gap as scope, never as silence | Evidence verification section 6 |
### 4.5 At the carrier and settlement boundaries
| Threat | The specification's answer | Refused with / checked by |
|---|---|---|
| Capability downgrade: a counterparty pretending the extension is absent to fall back to unverifiable behaviour | Capability negotiation fails closed. Every failure mode (undeclared, malformed, version-mismatched) produces the same single refusal | `A202-EXTENSION-UNSUPPORTED`, [bindings/a2a-binding-v0.1.md](bindings/a2a-binding-v0.1.md) |
| Settlement executed without commercial authority | The specification's entire payments surface is a handoff: a settlement instruction is an authorised, signed object, and the rail's response returns as a typed receipt in evidence. Moving the money is the rail's job and the rail's risk model | [fulfillment/settlement-handoff-v0.1.md](fulfillment/settlement-handoff-v0.1.md) |
| Tampered schemas or fixtures at their host | A release carries a digest for every schema file, and the manifest names every fixture with its expected classification. A schema whose digest differs is not that release. Implementations declare read and write versions, so a downgrade is visible | [RELEASES.md](RELEASES.md) sections 3 and 4 |
## 5. Verifying these claims
Nothing in section 4 asks to be believed. The checks are runnable:
1. **Run the conformance suite** from the repository root. The suite is deliberately weighted toward negative fixtures, behaviour under hostile and malformed input, and the [manifest](conformance/manifest-v0.1.json) is the single source for its contents and counts. The runner asserts not only that a negative fixture is refused but that it is refused for the reason code the manifest declares, wherever the normative layer raises codes. Every negative fixture is minimal: remove the single offending element and the document validates cleanly, so each fixture tests exactly one rule.
2. **Read the grade definitions** in [conformance/conformance-grades-v0.1.md](conformance/conformance-grades-v0.1.md). Passing the published set is the floor, not the claim. Band 2 requires passing a held-out case set exercising the same invariants; band 3 requires an assessor constructing inputs outside any set. The bands exist because the malicious implementer of section 3 is assumed: an implementation can be written to the published fixtures, which is precisely why the published fixtures are not the whole assessment. Publication is one way: a fixture that enters the published set never returns to a held-out set.
3. **Read the reference implementation** in [reference/README.md](reference/README.md). It is informative, one checkable reading of the wire rules, and its test sweep routes the manifest's evidence-bundle fixtures through the verification procedure directly, independently of the runner, stating a reason for every bundle it skips, so the two readings check each other rather than sharing one path.
## 6. The mechanism boundary
The charter's non-goal 6 draws a line this document keeps: how an operator implements isolation (sequencing, contention handling, timing and error normalisation) is not published here. What is published is the property such an implementation must exhibit, the interface a counterparty implements against, and the procedure by which the property is independently verified.
The trade-off is that an unpublished mechanism receives less external review than a published one. The specification compensates by not asking the reviewer to trust the mechanism. Isolation is defined over what a participant can observe and replay, and the verification procedure runs against a participant's own export. A mechanism defect that violates the property is detectable at the boundary where the property is stated, and reportable under [SECURITY.md](SECURITY.md), without the mechanism ever being disclosed.
## 7. What is not defended
Each of the following is a stated scope decision.
- **Collusion agreed outside the system.** Two bidders who agree their prices by telephone produce a record indistinguishable from two who did not. The auction event semantics state this in section 7, and no A202 claim should ever be read as detecting it.
- **Traffic analysis by a network observer.** Disclosure control is defined against participants. An observer correlating traffic volume and timing on the carrier is outside the specification's reach; transport confidentiality belongs to the carrier.
- **Key compromise, beyond its consequences.** The specification defines suspension, revocation, and the fail-closed evaluation of a dead chain. It does not defend the key itself; custody, rotation, and storage are implementation concerns.
- **Identity truth.** A202 consumes external identity and governance evidence and records the assurance level established from it; it does not prove a legal entity exists or that an issuer holds legal power. Assurance is reported, never inferred, and absence of assessment reads as unassessed, but the quality of the external assertion is the assertion issuer's to defend.
- **Settlement execution.** Once the handoff instruction leaves, the movement of money is governed by the rail.
- **Legal enforceability.** The record is designed to be reconstructable and attributable. Whether a given jurisdiction enforces what it records is a question for counsel, not for a schema.
- **Implementation defects.** An implementation that mishandles a rule the specification states correctly is defended against only insofar as the conformance suite catches it. Its remaining defects belong to its own reporting channel.
## 8. Review status
This threat model describes a pre-release specification. As of its date, no release has been tagged, no external security review of the specification has taken place, and no independent implementation exists. The conformance suite tests the specification's own reading of itself: the reference implementation and the runner were built against the same documents by the same project, so their agreement is a consistency check rather than independent confirmation.
External review is welcome. A reader who finds a way through any property in section 2 should report it privately per [SECURITY.md](SECURITY.md).
---
Source: https://a202.org/schemas/canonical-commercial-model-v0.1.md
# Canonical commercial model v0.1
**Status:** Experimental working specification. Mixed. Sections 2, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, and 15 are **normative**. Sections 1, 5, 6, and 16 are **informative** and state no requirement on an implementation.
**Date:** 25 July 2026
**Revised:** 30 July 2026, under [A202-0016](../proposals/A202-0016-casing-short-form-and-amendment-corrections.md): the `Agreement` row of section 5.4 states the amendment rule section 10.1 already carries, in place of a deferral to a future specification. Also revised 30 July 2026, under [A202-0014](../proposals/A202-0014-bilateral-formation-and-scope-repair.md): the `PolicyDecision` owner named as the acting party's own evaluator, the bilateral discharge of the kernel role and the party-minted session identifier stated in section 9, the denied-decision invariant split into its privacy half and its shared-sequence half, and the restated fixture counts in section 15 replaced by the manifest they were already declared to defer to. Previously revised 28 July 2026, under [A202-0009](../proposals/A202-0009-enforcement-fidelity.md) and [A202-0010](../proposals/A202-0010-model-completion.md): signature protected members, the mandate's standalone status made explicit, the full payload-deferral list, agreement amendment, hash-recomputation invariants, and the corrected fixture-count statement. Further revised the same day under [A202-0012](../proposals/A202-0012-party-family-payloads.md), which defines the organization, agent, and principal payloads, and [A202-0013](../proposals/A202-0013-transaction-event-allowlist.md), which types the transaction stream and registers the `req_` prefix. Previous revision 26 July 2026 added the counterparty invitation objects; 25 July 2026 followed conformance review and horizontal-scope formalization.
**Scope:** Synthetic pilot transactions only
## 1. Purpose
This model gives A202 one protocol-independent and market-independent meaning for commercial identity, authority, discovery, negotiation, agreement, performance, settlement instructions, and audit.
External protocols may carry, wrap, or reference these objects. They do not define A202 transaction state.
### 1.1 Strategic scope
This document is the experimental core-primitives specification for horizontal commercial negotiation, meaning commercial coordination that is not specific to any one commercial domain.
Market-independent means that the kernel represents commercial coordination without embedding procurement, freight, cloud, energy, insurance, or another domain's vocabulary. It does not mean that one generic workflow is sufficient for every market. Domain meaning belongs in transaction profiles governed by the [transaction profile extension model](transaction-profile-extension-model-v0.1.md).
The reusable primitive map is:
| Commercial requirement | Kernel representation | Privacy or authority boundary |
|---|---|---|
| Parties and organizational identity | `Organization`, `Agent`, `Principal`, external identity assertions, key records | Public or selectively disclosed claims do not create commercial authority |
| Entry for a party that is not yet registered | `CounterpartyInvitation`, `InvitationAcceptance` | An invitation grants participation in one named transaction. It never grants authority, and the operator never issues the invited party's mandate |
| Delegated authority | `CommercialMandate`, `Delegation`, `Approval`, `RevocationRecord` | Scope is disclosed only as required for verification |
| Objectives, constraints, and approval thresholds | Mandate constraints and participant policy inputs | Objectives, reservation values, internal thresholds, and strategy remain participant-private |
| Typed proposals and counterproposals | `ActionEnvelope`, `Offer`, profile terms, supersession link | Only transmitted proposals enter a bilateral session |
| Evidence and representations | `Evidence`, qualification requirements, evidence manifest | Evidence has an explicit audience, issuer, scope, and verification result |
| Conditional commitments | `Agreement`, `Commitment`, `Obligation` | A condition becomes shared only when it is part of a disclosed proposal or agreement |
| Policy evaluation | `PolicyDecision` bound to one action hash | Underlying private rules need not be disclosed |
| Escalation to humans | `Approval` bound to one action hash | Approval discloses the decision and scope required by the workflow, not the private threshold |
| Acceptance, rejection, withdrawal, expiry, and revocation | Typed objects and authorized events in the state machines | Only an authorized event changes canonical state |
| Settlement or execution instructions | `SettlementInstruction`, adapter job, `AdapterReceipt` | Connectivity and receipts do not create authority or prove commercial completion |
| Auditable state transitions | Per-session and aggregate events, evidence manifest, audit bundle, replay | Each party sees only the streams and evidence it is authorized to hold |
### 1.2 Storage and source-of-truth boundary
The kernel defines meaning and visibility. It does not require every data class to be stored in one shared database.
- Participant-private strategy and policy remain in participant security domains.
- An operator may hold the canonical ordering and policy service for shared transaction and bilateral-session state.
- Authorized parties must be able to retain and independently verify signed records relevant to them.
- External execution systems remain authoritative for the facts they produce.
A canonical operating source is not an exclusive surviving copy. An operator may hold the authoritative ordering of shared state while every authorized party independently retains verifiable records of the parts that concern it, so that no party depends on the operator's continued existence to prove what happened.
## 2. Conformance language
The terms `MUST`, `MUST NOT`, `REQUIRED`, `SHOULD`, `SHOULD NOT`, and `MAY` are normative within this experimental specification.
A A202 v0.1 implementation conforms when it:
1. validates shared objects against `v0.1/commercial-kernel.schema.json`;
2. validates mandates against `v0.1/commercial-mandate.schema.json`;
3. resolves and validates transaction profiles under `v0.1/profiles/`;
4. enforces every invariant in section 12, which schema validation cannot express;
5. enforces the state transitions in `../negotiation/pilot-transaction-state-machine-v0.1.md`, and the onboarding rules in `../discovery/counterparty-invitation-v0.1.md`;
6. produces canonical hashes using the serialization profile in section 4;
7. passes every fixture in `../conformance/manifest-v0.1.json`.
Schema validity is necessary and not sufficient. An implementation that passes the schemas and fails section 12 is not conformant.
## 3. Common envelope
Every shared object MUST include:
| Field | Type | Rule |
|---|---|---|
| `spec_version` | string | Exact value `a202-commercial/0.1` |
| `id` | string | Type-prefixed immutable identifier |
| `object_type` | string | Registered A202 object type |
| `version` | integer | Starts at 1 and increases by 1 |
| `created_at` | RFC 3339 timestamp | UTC with `Z` |
| `created_by` | `ActorRef` | Agent, organization, and mandate |
| `transaction_id` | string or null | Required for transaction-bound objects |
| `previous_version_id` | string or null | Required after version 1 |
| `content_hash` | string | Lowercase hexadecimal SHA-256 over canonical content |
| `signatures` | array | Detached or embedded signatures over `content_hash` |
| `payload` | object | Object-specific fields validated for `object_type` |
Every shared object MAY additionally carry:
| Field | Type | Rule |
|---|---|---|
| `kernel_annotations` | object or absent | Control-plane metadata attached **after** signing. Excluded from `content_hash` and from every signature. Only the control plane may write it. An agent-authored `action_envelope` MUST NOT carry it. |
Type prefixes:
| Object | Prefix |
|---|---|
| Organization | `org_` |
| Agent | `agt_` |
| Principal | `prn_` |
| Mandate | `mnd_` |
| Capability | `cap_` |
| Intent | `int_` |
| Counterparty invitation | `inv_` |
| Invitation acceptance | `ina_` |
| Transaction | `txn_` |
| Negotiation session | `ses_` |
| Action envelope | `act_` |
| Offer | `off_` |
| Acceptance | `acc_` |
| Agreement | `agr_` |
| Commitment | `cmt_` |
| Obligation | `obl_` |
| Obligation response | `obr_` |
| Performance event | `prf_` |
| Exception | `exc_` |
| Dispute | `dsp_` |
| Determination | `det_` |
| Evidence | `evd_` |
| Event | `evt_` |
| Policy decision | `pol_` |
| Approval | `apr_` |
| Settlement instruction | `stl_` |
| Adapter receipt | `adp_` |
| Clarification | `clr_` |
| Key record | `key_` |
| Revocation record | `rev_` |
| Commercial request | `req_` |
Identifiers are opaque. No legal name, email, employee number, or secret may be encoded in an identifier.
### 3.1 The mandate is not an envelope object
A `CommercialMandate` is a standalone signed document under `v0.1/commercial-mandate.schema.json`, with `spec_version` `a202-mandate/0.1` and an issuer `proof` rather than the envelope's `signatures` array. It is not wrapped in the common envelope, `commercial_mandate` is not a member of the kernel's `object_type` enum, and an implementation MUST NOT accept a mandate presented in envelope form. Envelope objects reference a mandate by its `mnd_` identifier, which is why the prefix stays registered in the table above.
The exception exists because the mandate is the root of the authority chain that envelope objects are verified against. An envelope object names its authorising mandate in `created_by`; a mandate wrapped in that same envelope would name a mandate in its own `created_by`, and the chain would either recurse or terminate in an unverifiable self-reference. A standalone document signed by the issuing principal terminates the chain instead.
## 4. Canonicalization and signatures
1. JSON objects MUST be serialized using JSON Canonicalization Scheme, RFC 8785.
2. `content_hash`, `signatures`, and `kernel_annotations` MUST be omitted from the bytes being hashed.
3. The hash algorithm for v0.1 is SHA-256, encoded as 64 lowercase hexadecimal characters. Multibase is not accepted in v0.1.
4. A signature MUST identify the key, algorithm, signature value, signed time, and purpose.
5. The signature value MUST be computed over the object's canonical bytes from rule 2, followed by a single `.` byte, followed by the RFC 8785 serialization of an object holding the signature entry's own `algorithm`, `key_id`, `purpose`, and `signed_at` members. Those four members are thereby protected: rewriting any of them after signing invalidates the signature. An unprotected entry would be relabelable, so that a signature issued for one purpose could be presented as a signature for another, and a signed time could be moved to before a key's revocation, and neither edit would be detectable from the bytes.
6. Verification MUST resolve the key status at the signed time and at verification time. Key status resolves against the key's `KeyRecord` version chain: the version whose validity interval covered the signed time governs historical validity, and the latest version states current status.
7. An expired or revoked key does not erase a signature that was valid when created. Verification output MUST report both historical validity and current key status.
## 5. Object inventory
### 5.1 Party and identity
| Object | Purpose | Owner | Shared? | Mutable fields |
|---|---|---|---|---|
| `Organization` | Commercial participant and legal-entity reference | Organization | Public profile | Status through new version |
| `Agent` | Software actor bound to an organization and operator | Organization | Public profile | Endpoints, status, keys through new version |
| `Principal` | Human or organizational authority source | Organization | Restricted | Status and role through new version |
| `ExternalIdentityAssertion` | Evidence received from GLEIF, vLEI, or another issuer | Source holder | Scoped | Verification result only through new version |
| `KeyRecord` | Public verification material and lifecycle status | Key controller | Scoped | Status through new version |
### 5.2 Authority
| Object | Purpose | Owner | Shared? | Mutable fields |
|---|---|---|---|---|
| `CommercialMandate` | Delegated commercial authority and constraints | Issuing organization | Selectively disclosed | None; supersede or revoke |
| `Delegation` | Parent-child authority link | Delegator | Selectively disclosed | None |
| `Approval` | Human or deterministic approval of one exact action hash | Approver | Transaction parties as required | None |
| `PolicyDecision` | Deterministic allow, deny, or approval-required result | The acting party's own policy evaluator. A control plane is the operated deployment of that role, never a separate one | Governed by its `visibility` field | None |
| `RevocationRecord` | Suspension or revocation of agent, key, or mandate | Authorized controller | Verification surface | None |
### 5.3 Market
| Object | Purpose | Owner | Shared? | Mutable fields |
|---|---|---|---|---|
| `Capability` | What an organization can supply, where, and under which evidence | Supplier | Public or invited | Availability through new version |
| `Intent` | Bounded demand or supply signal | Publishing organization | Public, invited, or private | Status through state event |
| `CounterpartyInvitation` | Single-use, expiring grant of participation in one named transaction, issued at a party that may not be registered | Inviting organization | Inviting party, operator, and the one invited party | None; revoke or let expire |
| `InvitationAcceptance` | Record that a claim completed: channel proved, party registered, own root mandate issued | Control plane, attested by the claimant | Inviting party, operator, and the claimant | Assurance level through new version |
| `QualificationRequirement` | Evidence and rules required to participate | Buyer or market profile | Shared | None after publication |
| `Evidence` | Claim, artifact reference, issuer, scope, and verification | Presenting party | Scoped | Verification status through new version |
### 5.4 Transaction
| Object | Purpose | Owner | Shared? | Mutable fields |
|---|---|---|---|---|
| `CommercialRequest` | Typed request with requirements, terms, and qualification | Buyer | Shared with eligible parties | Clarified through linked objects |
| `Clarification` | Question and disclosed answer | Requester and respondent | Transaction scoped | None |
| `ActionEnvelope` | The agent-signed unit of intent that policy is evaluated against | Proposing agent | Not shared with the counterparty | None |
| `Offer` | Complete or partial proposed terms | Offering party | Session scoped | None; counter with a new offer |
| `Acceptance` | Signature over one current offer hash | Accepting party | Session scoped | None |
| `Agreement` | Canonical accepted terms and party signatures | Both parties | Transaction parties | None; amend by a superseding version reached through a fresh offer and acceptance, under section 10.1 |
| `TransactionEvent` | Signed fact that drives state | Authorized actor or system | According to event class and stream | None |
### 5.5 Performance and audit
| Object | Purpose | Owner | Shared? | Mutable fields |
|---|---|---|---|---|
| `Commitment` | Party promise derived from agreement | Obligated party | Shared | None |
| `Obligation` | Measurable duty, due condition, and acceptance rule | Obligated party | Shared | Status derived from events |
| `PerformanceEvent` | Delivery, milestone, inspection, or service result | Performing or verifying party | Shared | None |
| `Exception` | Claimed failure, variance, or remediation path | Raising party | Shared | Status derived from events |
| `SettlementInstruction` | Requested payment route and condition | Authorized party | Restricted | Status derived from receipts |
| `AdapterReceipt` | External system request and result | Connector plane | Restricted or shared | Attempt status through new receipt |
| `EvidenceManifest` | Hash-addressed inventory of transaction evidence | Control plane | Transaction parties | None |
| `AuditBundle` | Signed manifest, event root, and replay metadata | Control plane | Authorized auditors | None |
### 5.6 Payload shapes: defined and deferred, restated 28 July 2026
Not every member of the `object_type` enum has a payload definition in the kernel schema. The consequence is the same for every type that lacks one, and it is stated here rather than left to be discovered: schema validation of such a payload is not claimable in v0.1. An object of such a type validates against the common envelope and nothing else, so an implementation that reports it as schema-valid is reporting that the envelope validated.
An earlier revision of this section named three deferred types. That list was under-inclusive, and this revision states the full division.
**Payload-defined types.** `action_envelope`, `counterparty_invitation`, `invitation_acceptance`, `offer`, `acceptance`, `agreement`, `transaction_event`, `policy_decision`, `obligation`, `performance_event`, `obligation_response`, `dispute`, `determination`, `settlement_instruction`, `adapter_receipt`; added by A202-0010 because normative rules elsewhere lean on their contents, `approval`, `commitment`, `evidence`, `revocation_record`, and `key_record`; and added by A202-0012 as the three an implementer constructs first, `organization`, `agent`, and `principal`.
**Payload-deferred types.** `external_identity_assertion`, `capability`, `intent`, `qualification_requirement`, `commercial_request`, `clarification`, `delegation`, `exception`, `evidence_manifest`, and `audit_bundle`. Each is named in the inventory with a purpose, an owner, and a sharing rule, and each validates as envelope-only until a proposal lands its shape. `exception`, `evidence_manifest`, and `audit_bundle` sit here as recorded on 27 July 2026; the rest are exercised by the pilot through references rather than through their contents.
The `exc_` prefix is registered in section 3 ahead of its payload proposal, for a reason that does not wait. The generic identifier pattern admits any three-letter prefix, so two implementations can mint different prefixes for the same object type and both validate. Identifiers are opaque and long lived, and every reference written under one form stays written, so a prefix that diverges before publication diverges permanently. Registering the prefix costs nothing and closes that. The same reasoning registered `clr_`, `key_`, and `rev_` when their reference patterns tightened. `evidence_manifest` and `audit_bundle` carry no registered prefix here, because neither appears as a reference inside another object's payload in v0.1.
## 6. Relationship model
```mermaid
erDiagram
ORGANIZATION ||--o{ AGENT : operates
ORGANIZATION ||--o{ PRINCIPAL : authorizes
ORGANIZATION ||--o{ CAPABILITY : publishes
ORGANIZATION ||--o{ COUNTERPARTY_INVITATION : issues
COUNTERPARTY_INVITATION ||--o| INVITATION_ACCEPTANCE : claimed_by
INVITATION_ACCEPTANCE ||--|| ORGANIZATION : registers
INVITATION_ACCEPTANCE ||--|| COMMERCIAL_MANDATE : names_root
PRINCIPAL ||--o{ COMMERCIAL_MANDATE : issues
COMMERCIAL_MANDATE ||--o{ COMMERCIAL_MANDATE : delegates
AGENT }o--o{ COMMERCIAL_MANDATE : uses
INTENT ||--o| COMMERCIAL_REQUEST : opens
COMMERCIAL_REQUEST ||--o{ QUALIFICATION_REQUIREMENT : requires
ORGANIZATION ||--o{ EVIDENCE : presents
COMMERCIAL_REQUEST ||--o{ NEGOTIATION_SESSION : opens
AGENT ||--o{ ACTION_ENVELOPE : signs
ACTION_ENVELOPE ||--|| POLICY_DECISION : evaluated_by
POLICY_DECISION ||--o| OFFER : mints
NEGOTIATION_SESSION ||--o{ OFFER : carries
OFFER ||--o| ACCEPTANCE : accepted_by
ACCEPTANCE ||--|| AGREEMENT : creates
AGREEMENT ||--o{ COMMITMENT : contains
COMMITMENT ||--o{ OBLIGATION : decomposes
OBLIGATION ||--o{ PERFORMANCE_EVENT : evidenced_by
TRANSACTION_EVENT }o--|| COMMERCIAL_MANDATE : authorized_by
TRANSACTION_EVENT }o--|| POLICY_DECISION : guarded_by
TRANSACTION_EVENT ||--o{ ADAPTER_RECEIPT : exported_by
AUDIT_BUNDLE ||--|| EVIDENCE_MANIFEST : inventories
```
## 7. Money, quantity, and commercial terms
Money MUST use:
```json
{
"currency": "EUR",
"amount": "3200.00"
}
```
Rules:
- `currency` MUST be an ISO 4217 alphabetic code for fiat values.
- `amount` MUST be a base-10 string, never a binary floating-point number.
- `amount` MUST be non-negative. A credit, refund, or downward adjustment is expressed as a directed adjustment object with its own type, not as a negative price. This prevents a sign error from silently inverting consideration.
- Percentages MUST be decimal strings in the closed interval `"0"` to `"100"`. A value above 100 is invalid.
- Quantities MUST use `unit_code` from UN/ECE Recommendation 20. This is the declared controlled vocabulary. `unit_name` is an optional human label and MUST NOT be used for matching or validation.
- Dates MUST state a time zone or a business-calendar reference. A duration expressed in business days without a named calendar is not a term.
- A term omitted from an offer is not accepted by implication unless a transaction profile defines a default and both parties reference the same profile version.
## 8. Transaction profiles and market neutrality
The kernel is market-neutral. This is a testable property, not a stated intention.
1. `terms` has exactly three parts: `profile`, `core`, and `profile_terms`.
2. `core` is identical for every transaction type: description, quantity, unit code, and total.
3. `profile_terms` is **opaque to the kernel**. The kernel validates only that a profile identifier resolves in the profile registry; the profile's own schema validates the terms.
4. The kernel schema MUST NOT contain a field, enum member, or constant that is meaningful in only one transaction profile.
5. An unresolvable profile fails closed with `A202-PROFILE-UNKNOWN`.
**Neutrality test.** `../conformance/fixtures/v0.1/valid-offer-alternate-profile.json` uses a transaction profile with entirely different commercial terms. It MUST validate against the unchanged kernel schema. If adding a transaction profile ever requires a kernel change, the kernel is not canonical and the market-neutrality property claimed in this section has failed.
The calibration-service profile is a synthetic pilot fixture. Its presence in `v0.1/profiles/` does not select a market and is not a statement that any commercial domain is in scope.
## 9. Action, policy, and offer ordering
An offer does not contain the policy decision that authorized it. That would be circular: the decision is computed over the offer, so embedding the decision identifier inside the signed offer would change the hash the decision was taken against.
The order is:
1. The agent constructs a candidate object and wraps it in a signed `ActionEnvelope`.
2. `action_hash` is the canonical hash of the `ActionEnvelope`.
3. The policy evaluator returns a `PolicyDecision` bound to that `action_hash`.
4. On `allow`, the kernel mints the commercial object and attaches `kernel_annotations` containing `policy_decision_id`, `session_id`, `session_sequence`, and `received_at`.
5. Because annotations are excluded from the hashed bytes, the agent's signature remains valid.
An offer payload MUST contain:
- `offeror` and `offeree`;
- `session_id`;
- `supersedes_offer_id` when it is a counteroffer;
- `valid_until`;
- a complete `terms` object for every term required by the transaction profile;
- evidence references.
An offer MUST NOT contain private objectives, scoring weights, reservation values, hidden prompts, or untransmitted alternatives.
### 9.1 Who discharges the kernel role
Step 3 and step 4 above name a policy evaluator and a kernel. Neither names a third organisation, and this section states which party performs each act so that the ordering is executable with no operator present.
1. **Each party evaluates its own proposed actions.** The evaluator in step 3 is the acting party's own. It returns a `PolicyDecision` bound to the `action_hash` of that party's own `ActionEnvelope`, and the decision is signed by that party under its own key. A party never evaluates a counterparty's proposed action and never issues a decision about one, in either scope. Where an operator is present it deploys this role as a control plane and the decision it issues is a decision about the actions of the party it acts for; the role is the same role, deployed once rather than twice.
2. **The kernel in step 4 is a role, not a party.** Where no operator is present, each party's own runtime discharges it over its own proposed actions: it mints the commercial object on `allow`, applies the object rules of this document to it, and signs it. Nothing in step 4 requires a party other than the two, other than the annotations, which are the next rule.
3. **`kernel_annotations` are control-plane only and are absent bilaterally.** The annotation members in step 4 record what an ordering service observed: `policy_decision_id`, `session_id`, `session_sequence`, and `received_at`. Where there is no ordering service, there is nothing to record and the member is absent. An object emitted bilaterally MUST NOT carry `kernel_annotations`, and a party that received one has received an object that passed through a control plane. This changes nothing about the existing rule in section 3 that only the control plane may write the member; it states what follows from that rule where no control plane exists.
4. **A decision the verifier was not given is a stated gap.** Because each party issues its own decisions, a verifier holding one party's record will not hold the other party's decisions. Step 5 item 4 of [evidence-verification-v0.1.md](../evidence/evidence-verification-v0.1.md) section 4 requires the referenced `PolicyDecision` to resolve; where it was not disclosed, the check is reported as `not_checkable` under step 7 and never as verified.
### 9.2 The session identifier bilaterally
`session_id` is REQUIRED on an offer payload and stays REQUIRED. A bilateral exchange is a session in the sense the model uses the word: one relationship with one counterparty, carrying the offers, counteroffers, and acceptance between them.
What differs bilaterally is who establishes it. The parties establish it themselves: the offeror mints the `ses_` identifier on its first offer, and the counterparty adopts it as it adopts the offer's other bytes, referencing the same value on its acceptance and on any counteroffer. There is no session stream, no session sequence, and no ordering service, so nothing has to be created by a third party before an offer can be made. An identifier minted by one party and adopted by the other is sufficient for every use the model makes of `session_id`: it groups the objects of one relationship and it is opaque, which is all section 3 requires of an identifier.
Where an operator is present it creates the session and its stream on `negotiation.opened`, and the identifier is the operator's. The field is the same field and the schema is unchanged; only the minting party differs.
## 10. Agreement formation
An agreement is formed in the pilot only when:
1. the offer is current and unexpired;
2. the offeror signature is valid;
3. the offeree signs an `Acceptance` over the exact offer hash;
4. both sides pass authority and policy checks;
5. any required approvals reference the same offer hash;
6. the kernel creates an `Agreement` that names the `Acceptance` and whose terms hash equals the accepted offer terms hash;
7. both parties sign the agreement hash;
8. an `agreement.committed` event is appended.
Any hash mismatch fails with `A202-AGREEMENT-HASH-MISMATCH`. `terms_hash` and `accepted_offer_hash` are recomputed by any party relying on the agreement, never trusted from the object: an agreement whose signed summary and signed content disagree fails with the same code.
### 10.1 Amendment
An amendment replaces a committed agreement's terms by consent, through the same mechanism that formed them. It is adopted through [A202-0010](../proposals/A202-0010-model-completion.md).
1. An amendment is a new agreement version: `version` increments by one, `previous_version_id` names the superseded version, and the superseded version remains in the record unchanged.
2. The amended terms MUST be reached through a fresh offer and a fresh `Acceptance` over that offer's exact hash, under the parties' mandates and policy checks, exactly as section 10 formed the original. A later agreement version that names the same `acceptance_id` or the same `accepted_offer_id` as the version it supersedes is a unilateral restatement, not an amendment, and fails with `A202-AGREEMENT-AMENDMENT-UNACCEPTED`.
3. Both parties MUST sign the amended version. One signature is not an amendment.
4. `agreement.amended` is appended to the transaction stream. It changes no aggregate state: a transaction in `committed` stays in `committed`, and one in `in_performance` stays in `in_performance`.
5. An obligation continues to reference the `terms_hash` of the agreement version it derived from, and its version chain is what resolves that reference. A duty the amendment adds or changes is a new obligation under the new version. A duty the amendment removes is discharged by the obligee's `waive` response against the existing obligation, so that the release is a signed act of the party it releases, not a side effect of the amendment.
## 11. Event streams and aggregate rules
Events belong to a named stream. There are two stream kinds:
| Stream kind | Contains | Readable by |
|---|---|---|
| `transaction` | Publication, qualification, selection, commitment, performance, settlement, exception | Transaction parties |
| `session` | Everything inside one bilateral negotiation: offers, counteroffers, clarifications, acceptance | The two parties to that session only |
Bilateral negotiation events MUST NOT be appended to the transaction stream while competing sessions are open. A shared monotonic counter across concurrent sessions is a covert channel: a supplier that observes the counter advance learns that a rival acted, and when. Sequence numbers are therefore per stream.
Other rules:
- Events are append-only and ordered by sequence within their own stream.
- Each event includes the previous event hash **within that stream**.
- Derived state is a materialized view, not the source of truth.
- A failed side effect creates a new event or adapter receipt. It never edits the original event.
- Replaying the same valid event streams MUST produce the same aggregate state.
- An event signed by an unauthorized actor MUST be rejected before append.
- A `deny` decision MUST NOT consume a sequence number in any shared stream. It is recorded in the actor's private stream with `visibility: private_to_actor`.
## 12. Invariants that schema validation cannot express
An implementation MUST enforce each of the following. `../conformance/run-conformance.py` checks them, and each has a negative fixture.
| Invariant | Reason code |
|---|---|
| A mandate's `valid_from` is strictly earlier than its `valid_until` | `A202-MANDATE-INTERVAL-INVALID` |
| An offer's `valid_until` is strictly later than its `created_at` | `A202-OFFER-EXPIRED` |
| The transaction profile named in `terms.profile` resolves in the registry | `A202-PROFILE-UNKNOWN` |
| `terms.profile_terms` validates against that profile's schema | `A202-PROFILE-TERMS-INVALID` |
| A `deny` decision is private to the actor and is disclosed to no counterparty | `A202-DISCLOSURE-DENIED` |
| A `deny` decision consumes no sequence number in a shared stream | `A202-DISCLOSURE-DENIED` |
| A child mandate is equal to or narrower than its parent on every axis | `A202-MANDATE-DELEGATION-WIDENING` |
| An unregistered constraint type or operator fails closed at evaluation as well as at validation | `A202-MANDATE-CONSTRAINT-UNKNOWN` |
| `kernel_annotations` are excluded from the bytes covered by `content_hash` and every signature | `A202-ANNOTATION-FORGED` |
| An invitation's `expires_at` is strictly later than its `created_at` | `A202-INVITATION-EXPIRED` |
| An invitation's `granted_scope.transaction_ids` contains exactly its own `transaction_id` | `A202-INVITATION-SCOPE-EXCEEDED` |
| An invitation acceptance carries both a claimant `invitation_claim` signature and an operator `object_issuance` signature | `A202-INVITATION-CLAIM-UNSIGNED` |
| An assurance level above `self_asserted` is supported by at least one evidence reference | `A202-ASSURANCE-UNSUPPORTED` |
| An operator-custodied key carries a bound principal approval | `A202-CUSTODY-APPROVAL-REQUIRED` |
| A session-stream event discloses no rival identity, price, count, or timing | `A202-DISCLOSURE-POLICY-VIOLATION` |
| An event's `data` carries no rival-disclosing member on any stream kind, at any depth. The transaction stream is covered because the winning counterparty reads it after commitment | `A202-DISCLOSURE-POLICY-VIOLATION` |
| A policy decision returns no reason code that discloses aggregate state to the actor | `A202-DISCLOSURE-POLICY-VIOLATION` |
| An agreement's `terms_hash` equals the recomputed hash of its own `terms`, and its `accepted_offer_hash` equals the accepted offer's `content_hash` | `A202-AGREEMENT-HASH-MISMATCH` |
| A later agreement version names a fresh acceptance and a fresh accepted offer, per section 10.1 | `A202-AGREEMENT-AMENDMENT-UNACCEPTED` |
| A determination's `state_result` names the determined subject and a registered state, and appears only under a `binding` effect | `A202-STATE-TRANSITION-DENIED`, `A202-DETERMINATION-EFFECT-OVERCLAIM` |
Adding a constraint type to the schema enum without an evaluator implementation MUST NOT cause the action to be allowed. Both layers fail closed independently. The delegation-narrowing invariant is exercised statically by the `mandate_chain` fixtures, which carry a parent and child pair so that every axis of narrowing is checkable without runtime state.
The denied-decision invariant is stated as two rows rather than one because its halves hold in different places. Privacy toward the counterparty holds wherever a decision exists, and either party checks it on any decision it receives. The shared-sequence half is a property of a stream an ordering service assigns numbers on, and where no such service exists there is no shared sequence for a decision to consume: a denied action is simply never countersigned into the record described in section 8.1 of [pilot-transaction-state-machine-v0.1.md](../negotiation/pilot-transaction-state-machine-v0.1.md). One row carrying both meant the partition in [conformance-role-scopes-v0.1.md](../conformance/conformance-role-scopes-v0.1.md) had to split a row against itself, which is what these two rows remove. Both refuse with the same code, because a relying party's correct response to either is the same refusal.
### 12.1 Event data is an allowlist on every stream
Every event carries a closed `data` shape. There are three, and between them they cover the whole transaction and session lifecycle.
| Event | Shape | Permitted content |
|---|---|---|
| Moves the session to `rejected`, `withdrawn`, `expired`, or `closed` | `sessionCloseData` | One `close_reason` from a closed enum |
| Every other session event | `sessionEventData` | Pattern-checked object references only: `offer_id`, `supersedes_offer_id`, `acceptance_id`, `clarification_id`, `approval_id` |
| Every transaction-stream event | `transactionEventData` | Pattern-checked object references and bound hashes only, covering the aggregate lifecycle. No member is required: most aggregate transitions are fully described by `from_state`, `event_type`, and `to_state` |
Two rules make this work:
1. **Allowlist, not denylist.** A denylist refuses only the leaks someone anticipated. A free-text field named `note` carrying "three others bid" defeats any keyword filter and is refused here by construction.
2. **References, never content.** An event names the signed object it concerns; it never restates the object's terms. The referenced object enforces its own visibility and private-field validation separately. Every reference pattern is bound to its registered prefix, which constrains what family an identifier claims; it does not make the identifier's characters incapable of carrying a value, because no pattern can. Full opacity is enforced at resolution: an identifier on a session stream that resolves to no object is refused, and an identifier is never rendered as content. The pattern is the static layer, resolution is the enforcing one, and both fail closed on their own.
The evaluator denylist remains as an independent second layer, and applies to every stream kind at any nesting depth. Both layers must fail closed on their own: widening either allowlist without updating the evaluator, or the reverse, must not let a leak through.
**Why the transaction stream is typed too.** An earlier revision left transaction-stream `data` an open object, reasoning that suppliers act on session streams and never read the transaction stream, so its audience is the buyer, the operator, and scoped auditors. That reasoning was incomplete in one case that matters: after `agreement.committed` the winning counterparty is a party to the transaction and reads the stream, so an award or commitment event carrying a losing rival's price, identity, or count discloses across counterparties on exactly the boundary the session design exists to protect. Typing the aggregate lifecycle closes it, and the cost is low because the aggregate transitions are few and reference-shaped. This is [A202-0013](../proposals/A202-0013-transaction-event-allowlist.md).
## 13. Private strategy boundary
The following data classes MUST remain outside the shared kernel:
- maximum willingness to pay unless deliberately disclosed;
- minimum acceptable price;
- scoring weights;
- negotiation tactics;
- supplier or buyer rankings not yet disclosed;
- model prompts, reasoning traces, and hidden memory;
- draft offers not transmitted;
- confidential information that the mandate prohibits disclosing;
- the existence, count, and timing of a party's rejected proposals.
The kernel may store a hash of a private record for later integrity proof. It MUST NOT require the record's contents.
## 14. Versioning
- v0.1 objects use `spec_version: a202-commercial/0.1`.
- Unknown object types or required fields fail validation.
- Additive optional fields require a registered extension namespace.
- A breaking semantic change requires a new specification version.
- Adapters MUST declare the kernel version and external specification version used for each receipt.
- A transaction profile versions independently of the kernel. A new profile is not a kernel change.
## 15. Conformance fixtures
The executable set is `../conformance/manifest-v0.1.json`, run by `../conformance/run-conformance.py`. The manifest is the single source for what the set contains and how large it is; a count restated in prose goes stale the first time the set grows, and three of this repository's own documents proved that, including the sentence that used to stand here and that had gone stale by two proposals. No count is restated here. The runner asserts that each negative fixture is refused for its declared reason code where the normative layer raises codes at all. The set is deliberately weighted toward the refuse direction, because a specification is tested by what it refuses.
A conformant implementation MUST be verified against coverage of each of the following:
1. Valid organization, agent, and mandate chain.
2. Expired, revoked, and suspended mandate rejection.
3. Unknown constraint type and operator rejection.
4. Offer with missing required term rejection.
5. Offer with binary floating-point money rejection.
6. Counteroffer linked to a stale offer rejection.
7. Acceptance hash mismatch rejection.
8. Agreement party-signature mismatch rejection.
9. Duplicate event with identical idempotency key returns original result.
10. Duplicate event with different content returns conflict.
11. Event-chain tampering fails replay.
12. Private field in a shared object fails data-classification validation.
13. Adapter retry does not duplicate an external order.
14. Market-neutrality probe: a second transaction profile validates without a kernel change.
15. Cross-session isolation: a session event is not visible in the transaction stream.
16. Onboarding by invitation: an organization with no prior A202 presence claims an invitation, issues its own root mandate, and reaches an agreement.
17. An operator-custodied key cannot move state without an approval bound to the exact action hash.
18. Invitation isolation: an invited party cannot determine how many other invitations were issued on the same transaction.
19. Delegation narrowing: a child mandate that outlives, out-scopes, out-acts, or out-spends its parent is refused on each axis independently.
20. Amendment: a superseding agreement version is reached only through a fresh offer and acceptance, and a re-versioned agreement nobody re-accepted is refused.
21. Consensual termination: `termination.agreed` ends a committed transaction under the rules version that permits it, is illegal under the versions that never did, and releases open obligations rather than stranding them.
22. Direct formation: two parties reach an agreement through `agreement.direct` with no operator-authored, operator-ordered, or operator-annotated object anywhere in the record, the same transition is illegal under the rules versions that never carried it, and it is refused on a transaction that already carries a session stream.
Items 1 to 3, 6 to 11, and 16 to 18 require runtime state and cannot be expressed as static documents. They are verified against a running implementation rather than against a fixture file. Items 19 to 22 are exercised statically: 19 by the `mandate_chain` fixtures, 20 to 22 by bundle fixtures. The full invitation test list is in [counterparty-invitation-v0.1.md](../discovery/counterparty-invitation-v0.1.md) section 12.
## 16. Open questions
- Whether the public specification should use JSON-LD contexts or plain JSON Schema plus explicit mappings.
- Which signature suites should be mandatory beyond the pilot.
- How selective disclosure should be implemented for mandates and evidence.
- Whether agreement objects should map directly to an existing contract standard.
- Whether the profile registry should be centrally curated, self-asserted with conformance testing, or both.
The question of which object families belong to this specification and which behaviours belong to an implementation that operates a venue is settled and is stated in the scope and non-goals sections of [CHARTER.md](../CHARTER.md), not reopened here.
---
Source: https://a202.org/schemas/transaction-profile-extension-model-v0.1.md
# Transaction profile extension model v0.1
**Status:** Experimental working specification. Mixed. Sections 2, 3, 4, 5, 6, 7, and 8 are **normative**. Sections 1, 9, 10, and 11 are **informative** and state no requirement on an implementation.
**Date:** 25 July 2026
**Scope:** Synthetic and non-binding validation only
**Relates to:** [Canonical commercial model v0.1](canonical-commercial-model-v0.1.md)
## 1. Purpose
This specification defines how a commercial domain adds vocabulary, rules, evidence requirements, and execution mappings without changing the common A202 commercial kernel.
A transaction profile describes one bounded commercial interaction. It is not a market launch, an industry ontology, an ERP connector, or evidence that organizations want the product.
## 2. Layer model
| Layer | Contains | Must not contain |
|---|---|---|
| Commercial kernel | Identity references, authority references, common proposal envelope, acceptance, agreement, obligations, event ordering, audit | Domain-specific term names, preferred rails, market-specific workflow assumptions |
| Transaction profile | Typed commercial terms, controlled vocabularies, evidence requirements, domain policy hooks, obligation templates | Private strategy, participant-specific preferences, transport credentials |
| Participant policy | Limits, approvals, disclosure rules, risk tolerances, counterparty rules | Shared defaults presented as bilateral agreement |
| Protocol adapter | Mapping to and from external interaction protocols | Independent commercial authority or state transitions |
| Execution adapter | Mapping to ERP, payment, logistics, provisioning, or other systems | Authority inferred from connectivity or a successful API call |
| Operated A202 service | Verification, policy execution, canonical ordering, isolation, adjudication, replay | Undisclosed participant reasoning or silent modification of signed terms |
## 3. Profile package
A complete profile package MUST contain:
1. **Manifest**
- stable profile identifier;
- semantic version;
- status and owner;
- referenced kernel versions;
- human-readable name and bounded transaction purpose;
- compatibility and supersession information.
2. **Terms schema**
- machine-validatable `profile_terms`;
- required and optional fields;
- controlled units, currencies, time zones, and vocabularies;
- no unrestricted field used to hide a material commercial term.
3. **Semantic definitions**
- commercial meaning of every term;
- default behavior, if any;
- which omissions are invalid;
- examples and counterexamples.
4. **Evidence rules**
- claims requiring evidence;
- acceptable issuer or verifier classes;
- freshness, scope, status, and audience requirements;
- explicit statement that verifiability does not establish truth by itself.
5. **Policy hooks**
- profile facts made available to deterministic participant policy;
- typed failure reasons;
- actions that require approval;
- no model output used as deterministic authority.
6. **Obligation and acceptance mapping**
- how accepted terms create commitments and measurable obligations;
- required performance events;
- acceptance, exception, remediation, and termination conditions.
7. **Execution mapping**
- external documents, systems, or rails that may receive authorized instructions;
- expected semantic loss for each mapping;
- idempotency and receipt rules;
- fields that cannot be exported without review.
8. **Threat addendum**
- domain-specific disclosure, manipulation, collusion, fraud, safety, and market-integrity risks;
- consequence limits and mandatory human review.
9. **Conformance fixtures**
- at least one valid offer;
- missing and malformed material terms;
- expired or stale terms where relevant;
- invalid evidence;
- private-field leakage;
- unauthorized and approval-required actions;
- execution mapping loss.
The v0.1 profile schemas in `v0.1/profiles/` are incomplete probes. They contain terms schemas but not every package artifact required for a production candidate.
## 4. Kernel invariants
A transaction profile MUST NOT:
- add a domain-specific field, enum, or constant to the kernel schema;
- redefine organization, agent, principal, mandate, offer, acceptance, agreement, commitment, obligation, evidence, policy decision, or audit semantics;
- permit a proposal to bypass the signed `ActionEnvelope`;
- create a new authoritative transition without an explicit kernel-version decision;
- infer delegated authority from identity, system connectivity, payment ability, or possession of data;
- place private objectives, reservation values, rankings, prompts, or reasoning traces in shared terms;
- require one agent framework, ERP, identity provider, payment rail, or settlement method;
- treat an adapter receipt as acceptance, agreement, performance, or settlement unless an authorized typed event establishes that meaning;
- use unstructured prose as the only representation of a material term that controls price, scope, liability, performance, acceptance, or termination.
If a proposed profile cannot conform to these rules, one of three conclusions is required:
1. the proposed interaction is not supported by the current kernel;
2. the proposed term is not truly domain-specific and a versioned kernel change should be considered;
3. the horizontal-kernel hypothesis has failed or needs narrowing.
Silent kernel expansion is not permitted.
## 5. Domain vocabulary
A profile may define:
- product, service, route, location, capacity, inventory, or license descriptors;
- domain-specific time windows;
- quality, availability, service-level, inspection, or acceptance measures;
- pricing components and adjustment formulas;
- liability, remedy, credit, or cancellation terms;
- evidence types and qualification requirements;
- obligation templates;
- mapping rules to established domain standards.
Controlled vocabularies SHOULD reuse an appropriate existing standard when one can express the required meaning. A profile author MUST record reuse, profile, extension, or creation decisions and any semantic loss.
## 6. Domain policy
Profile schemas express terms. They do not decide whether a party should accept them.
Participant policy evaluates typed profile facts against participant-controlled rules. Examples include:
- require approval above a private monetary threshold;
- deny a collection window outside an authorized date range;
- require a named evidence class for a regulated service;
- require human approval for an uncapped liability term;
- prohibit disclosure of a private reservation value.
A shared `PolicyDecision` MAY disclose `allow`, `deny`, or `require_approval` only to the audience required by the workflow. The underlying private threshold or rule MUST remain private unless deliberately disclosed.
## 7. Profile registration
Registration means that an identifier resolves to a versioned schema and metadata. It does not certify commercial quality, legal sufficiency, safety, market adoption, or suitability for production.
A profile is eligible for the experimental registry when:
- its package is complete for the claimed maturity;
- the unchanged kernel validates its common envelope;
- all material terms are typed;
- evidence and policy hooks are explicit;
- private and shared fields are classified;
- conformance fixtures pass;
- expected execution-mapping loss is documented;
- the threat addendum identifies consequence limits;
- an owner and reversal condition are named.
The governance choice between central curation, federated publication, and self-assertion with conformance testing remains open.
## 8. Versioning
- A profile versions independently of the kernel.
- Adding an optional term is a profile change, not automatically a compatible change.
- Changing a term's meaning, default, unit, evidence rule, or obligation mapping is a breaking profile change.
- An agreement MUST retain the exact profile identifier and version used.
- An active negotiation MUST NOT change profile version without an authorized proposal accepted by every affected party.
- A superseded profile remains resolvable for verification and replay.
## 9. Example domain boundaries
| Domain probe | Profile-specific terms | Common kernel behavior |
|---|---|---|
| Calibration service | Completion calendar, certificate requirement, acceptance result, included rework | Authority, proposal, evidence reference, acceptance, obligations, audit |
| Freight spot capacity | Lane, collection window, equipment, liability regime | Authority, proposal, conditional commitment, performance evidence, exception |
| Cloud capacity reservation | Region, resource class, capacity period, service level, usage commitment, service credits | Authority, proposal, agreement, recurring obligation, acceptance, settlement instruction |
These examples are architecture probes. They do not select a commercial market or establish market demand.
## 10. Open questions
- Can established domain document standards serve directly as `profile_terms`, or is a A202 mapping required?
- How should profiles express amendments, options, partial acceptance, and multi-party commitments?
- Which domains require state beyond the v0.1 bilateral session and single-award aggregate?
- How should profile conflicts be resolved when each party proposes a different standard or version?
- Which entity governs names, versions, deprecations, and conformance claims?
Which profile elements belong to this specification, and which behaviours belong to an implementation that operates a venue, is settled and is stated in the scope and non-goals sections of [CHARTER.md](../CHARTER.md). It is not an open question of this document.
## 11. Reversal conditions
Revisit this model if:
- domain profiles routinely require kernel changes;
- material commercial meaning cannot be expressed without domain-specific state machines;
- established standards are more interoperable when used without a A202 profile;
- profile packages create more integration work than direct bilateral mappings;
- participants reject A202 identifiers or profile governance;
- conformance passes syntactically while materially different interpretations remain possible.
---
Source: https://a202.org/authority/commercial-mandate-v0.1.md
# Commercial mandate v0.1
**Status:** Experimental working specification. Mixed. Sections 2, 3, 4, 5, 6, 7, 8, 9, 11, and 12 are **normative**. Sections 1 and 10 are **informative** and state no requirement on an implementation.
**Date:** 25 July 2026
**Revised:** 26 July 2026. Added section 12, which bounds the root mandate of a party onboarded by invitation and forbids the operator from ever issuing it. Previous revision 25 July 2026 after conformance review.
**Security boundary:** Synthetic pilot authority only
## 1. Purpose
A commercial mandate states which organization an agent represents, which commercial actions it may take, under which constraints, for which transaction or market scope, and when human approval is required.
The mandate does not prove that a legal entity exists or that the issuer has legal power under applicable law. External identity and governance evidence supports those questions. A202 evaluates what the presented evidence and mandate mean within a pilot transaction.
## 2. Authority chain
```text
external organization evidence
-> A202 Organization
-> authorized Principal
-> root CommercialMandate
-> optional delegated CommercialMandate
-> Agent key
-> signed ActionEnvelope
-> transaction action
```
Every link MUST be verifiable. Missing, expired, suspended, revoked, or scope-incompatible links fail closed.
## 3. Required fields
The normative machine schema is `../schemas/v0.1/commercial-mandate.schema.json`.
| Field | Meaning |
|---|---|
| `id` | Immutable mandate identifier |
| `issuer` | Principal and organization issuing authority |
| `subject` | Exactly one of: an agent, or a delegated principal |
| `represented_organization_id` | Organization represented in actions |
| `parent_mandate_id` | Delegation source, or explicit null |
| `valid_from` and `valid_until` | Closed authority interval |
| `status_endpoint` | Current suspension and revocation source. HTTPS only. |
| `actions` | Explicit allowed action names |
| `scope` | Transaction, capability category, counterparty, and geography limits |
| `constraints` | Deterministically evaluated commercial and disclosure rules. At least one. |
| `approval_rules` | Conditions requiring named human or role approval |
| `delegation` | Whether and how authority may be delegated |
| `evidence_refs` | Identity, role, policy, or authorization evidence |
| `proof` | Issuer signature over canonical content |
Actions are deny by default. An omitted action is not allowed.
### 3.1 Scope must bound authority
`scope` MUST contain `transaction_ids`, `categories`, or both. `counterparty_organization_ids` and `geographies` narrow an existing boundary; they cannot establish one.
A mandate scoped only to a country is not transaction-scoped. It authorizes an agent to act on any transaction of any value in that country, which is the control point A202 claims to solve rather than an example of it. This is rejected at validation.
### 3.2 A mandate with no constraints is rejected
An empty `constraints` array confers unbounded authority within the allowed actions. Deliberate absence of a limit MUST be expressed as an explicit permissive constraint so that it appears in the audit record and in any delegation-narrowing check.
### 3.3 Status endpoint transport
`status_endpoint` MUST use HTTPS. Cached status is the only channel by which a revocation reaches a relying party, and the pilot caps that cache at 60 seconds. Status served over plain HTTP is forgeable by a network attacker, which would make revocation unenforceable.
## 4. Constraint vocabulary
Each constraint contains `id`, `type`, `path`, `operator`, `value`, and `on_failure`.
Registered v0.1 types:
| Type | Applies to |
|---|---|
| `commercial.decimal` | Money amounts, percentages, decimal quantities |
| `commercial.integer` | Counts and whole-number limits |
| `commercial.string` | Currency codes, unit codes, enumerated terms |
| `commercial.boolean` | Binary terms |
| `commercial.timestamp` | Dates and deadlines |
| `commercial.set` | Membership over a declared set |
| `disclosure.path` | Presence or absence of a field in an outbound action |
| `evidence.reference` | Required evidence and its verification result |
| `counterparty.reference` | Counterparty organization identity |
| `geography.reference` | Site, delivery, or performance geography |
Registered v0.1 operators:
| Operator | Evaluation |
|---|---|
| `equals` | Proposed value equals required value |
| `not_equals` | Proposed value differs |
| `one_of` | Proposed value is in the allowed set |
| `none_of` | Proposed value is absent from the denied set |
| `minimum` | Numeric decimal is at or above the limit |
| `maximum` | Numeric decimal is at or below the limit |
| `matches` | String matches the registered regular expression |
| `present` | Required path exists |
| `absent` | Prohibited path does not exist |
| `before` | Timestamp is before the limit |
| `after` | Timestamp is after the limit |
| `evidence_verified` | Referenced evidence has a valid verification result |
Required failure modes:
- `deny`;
- `require_approval`.
Both registries are closed in v0.1. An unregistered type or operator is rejected at schema validation **and** evaluates to `deny` at runtime. Both layers are required and are tested independently: adding a type to the schema enum without an evaluator implementation MUST NOT cause an action to be allowed.
## 5. Pilot mandate example
```json
{
"spec_version": "a202-mandate/0.1",
"id": "mnd_northstar_buyer_01",
"issuer": {
"organization_id": "org_northstar",
"principal_id": "prn_northstar_procurement_director",
"key_id": "key_northstar_principal_01"
},
"subject": {
"agent_id": "agt_northstar_buyer_01",
"key_id": "key_northstar_agent_01"
},
"represented_organization_id": "org_northstar",
"parent_mandate_id": null,
"valid_from": "2026-07-25T00:00:00Z",
"valid_until": "2026-08-25T00:00:00Z",
"status_endpoint": "https://sandbox.a202.invalid/v1/mandates/mnd_northstar_buyer_01/status",
"actions": [
"intent.publish",
"invitation.issue",
"invitation.revoke",
"qualification.request",
"clarification.send",
"offer.submit",
"offer.accept",
"agreement.sign",
"settlement.instruct"
],
"scope": {
"transaction_ids": [
"txn_calibration_demo_01"
],
"categories": [
"services.calibration"
],
"counterparty_organization_ids": [
"org_delta",
"org_meridian"
],
"geographies": [
"NL"
]
},
"constraints": [
{
"id": "c_total_price",
"type": "commercial.decimal",
"path": "$.proposed_terms.core.total.amount",
"operator": "maximum",
"value": "4000.00",
"on_failure": "deny"
},
{
"id": "c_currency",
"type": "commercial.string",
"path": "$.proposed_terms.core.total.currency",
"operator": "equals",
"value": "EUR",
"on_failure": "deny"
},
{
"id": "c_prepayment",
"type": "commercial.decimal",
"path": "$.proposed_terms.profile_terms.payment.prepayment_percent",
"operator": "maximum",
"value": "20",
"on_failure": "deny"
},
{
"id": "c_budget_disclosure",
"type": "disclosure.path",
"path": "$.message.private_budget_ceiling",
"operator": "absent",
"value": true,
"on_failure": "deny"
}
],
"approval_rules": [
{
"id": "a_new_counterparty",
"when": {
"path": "$.counterparty_organization_id",
"operator": "none_of",
"value": [
"org_delta",
"org_meridian"
]
},
"approver": {
"organization_id": "org_northstar",
"role": "procurement_director"
},
"expires_after_seconds": 3600
}
],
"delegation": {
"allowed": false,
"maximum_depth": 0
},
"evidence_refs": [
"evd_northstar_org_identity_01",
"evd_northstar_principal_role_01"
],
"proof": {
"key_id": "key_northstar_principal_01",
"algorithm": "ES256",
"created_at": "2026-07-25T00:00:00Z",
"purpose": "mandate_issuance",
"signature": "synthetic-placeholder-signature-value"
}
}
```
The example uses the reserved `.invalid` domain and a placeholder signature. It MUST NOT be treated as a usable credential.
Constraint paths address the canonical terms structure defined in `../schemas/canonical-commercial-model-v0.1.md` section 8. A constraint that targets a profile-specific term addresses it under `profile_terms`, so a mandate written for one transaction profile cannot silently bind a different one.
## 6. Evaluation algorithm
For each proposed action:
1. Parse and schema-validate the `ActionEnvelope` and the mandate.
2. Authenticate the subject key and agent status.
3. Verify the mandate signature.
4. Resolve the mandate status endpoint.
5. Verify `valid_from <= now < valid_until`.
6. Resolve every parent mandate and verify the chain.
7. Confirm that each child action and constraint is equal to or narrower than its parent.
8. Confirm that the action appears in `actions`.
9. Confirm transaction, category, counterparty, and geography scope.
10. Resolve the transaction profile and validate the proposed terms against it.
11. Evaluate every constraint in stable order.
12. Evaluate approval rules.
13. Evaluate the transaction-state guard for the target stream.
14. Return one `PolicyDecision` bound to the envelope's `action_hash`:
- `allow`;
- `deny`;
- `require_approval`.
15. Sign and persist the decision before any external transmission.
The evaluator MUST use decimal arithmetic for money and percentages. It MUST NOT call a language model.
### 6.1 Checks the schema cannot perform
These are enforced by the evaluator and covered by fixtures in `../conformance/`:
| Check | Reason code |
|---|---|
| `valid_from` strictly earlier than `valid_until` | `A202-MANDATE-INTERVAL-INVALID` |
| Child mandate narrower than parent on every axis | `A202-MANDATE-DELEGATION-WIDENING` |
| Unregistered constraint type or operator denies at runtime | `A202-MANDATE-CONSTRAINT-UNKNOWN` |
| Status endpoint reachable and fresh within the cache window | `A202-MANDATE-STATUS-UNRESOLVED` |
| Mandate still active at append time, not only at proposal time | `A202-MANDATE-INACTIVE` |
| An invited party's root mandate is bounded to the invitation's transaction | `A202-INVITATION-SCOPE-EXCEEDED` |
| An operator-custodied subject key carries an approval bound to the action hash | `A202-CUSTODY-APPROVAL-REQUIRED` |
If the status endpoint cannot be resolved, the decision is `deny`. Unavailability is not permission.
## 7. Delegation
A child mandate MUST:
- reference one parent mandate;
- have a shorter or equal validity interval;
- contain a subset of parent actions;
- contain equal or narrower scope;
- retain every parent `deny` constraint;
- tighten but never loosen a numeric limit;
- never increase delegation depth;
- use a subject permitted by the parent delegation rule.
Failure to prove monotonic narrowing returns `A202-MANDATE-DELEGATION-WIDENING`.
`delegation.allowed` and `delegation.maximum_depth` MUST be coherent: `allowed: false` requires depth `0`, and `allowed: true` requires depth of at least `1`. An incoherent pair is rejected rather than interpreted.
## 8. Approval
An approval MUST bind:
- exact action hash;
- transaction;
- approver identity and role;
- decision;
- created time;
- expiry time;
- any conditions;
- approver signature.
Changing one byte of the action invalidates the approval. An approval cannot be reused across actions or transactions.
## 9. Suspension and revocation
- `active`: New actions may be evaluated.
- `suspended`: New actions fail. The record can return to active through an authorized event.
- `revoked`: New actions fail permanently for this mandate version.
- `expired`: New actions fail because time is outside the validity interval.
Status changes MUST produce signed `RevocationRecord` events. Cached status responses MUST include an expiry no longer than 60 seconds in the pilot, and MUST be retrieved over HTTPS.
## 10. Interoperability notes
This section is an interoperability aid. It records how objects defined here line up with purchase-authorization constructs in an adjacent specification, so that an implementer holding both can see where a mapping is available and where it is lossy. It defines no requirement and asserts no equivalence.
### 10.1 Purchase authorization mandates
**Checked 25 July 2026:** AP2 v0.2 defines two mandate types, Checkout Mandate and Payment Mandate, both bound to purchase authorization. It does not define authority over disclosure, qualification, negotiation concessions, non-payment commercial terms, performance, or acceptance. A A202 commercial mandate therefore has no equivalent there and is not interchangeable with either type.
Where an implementation carries both, the nearest correspondence is:
| A202 | Nearest purchase-authorization construct |
|---|---|
| `CommercialMandate` | Open authorization source for commercial actions |
| Accepted `Agreement` hash | Checkout or purchase-detail binding input |
| `SettlementInstruction` | Payment mandate input |
| `PolicyDecision` | Deterministic constraint evaluation receipt |
| `AdapterReceipt` | Checkout or payment receipt reference |
The mapping is deliberately lossy, and each row names the nearest construct rather than an equivalent one. An implementation that relies on it must state the loss rather than let it pass silently.
## 11. Authority for a party onboarded by invitation
A party that enters through a [counterparty invitation](../discovery/counterparty-invitation-v0.1.md) has no principal, no mandate, and no key when the invitation arrives. This section states what it may hold once it claims.
### 11.1 The operator never issues it
The root mandate of an invited party MUST be issued by a principal the invited organization designated. The A202 operator MUST NOT appear anywhere in the chain, at any depth, as issuer or as delegator.
This is the load-bearing rule of the whole onboarding path. If the operator can complete a counterparty's authority chain, then "the kernel refuses the act because the mandate was revoked" becomes "the kernel refuses the act unless the operator prefers otherwise," and the control point A202 claims stops existing. The operator may author the onboarding record, because the claimant has no mandate with which to author anything, and it may custody a key. It may not be the source of authority.
### 11.2 Bounded to the invitation
An invited party's root mandate MUST satisfy all of:
- `scope.transaction_ids` contains exactly the invitation's transaction, and `scope.categories` is absent;
- `actions` is a subset of the invitation's `granted_scope.actions`;
- `valid_until` is at or before the invitation's `expires_at` plus the transaction's own deadline, whichever is earlier;
- `delegation.allowed` is `false` and `maximum_depth` is `0`.
Failure returns `A202-INVITATION-SCOPE-EXCEEDED`.
A party that has received one invitation has demonstrated nothing beyond that transaction. A category-scoped root mandate would turn a single invitation into standing market access, which is the same failure that section 3.1 rejects for geography-only scope, arriving by a different route.
Standing authority is reached the ordinary way: the organization registers properly, presents evidence, and its principal issues a wider mandate. Invitation is an entry, not a shortcut.
### 11.3 Operator key custody requires per-act approval
When the subject key is operator-custodied, every action under the mandate requires a human `Approval` from a named principal of the invited organization, bound to that exact action hash.
The mandate MUST therefore carry an approval rule matching every action:
```json
{
"id": "a_custodied_key_all_actions",
"when": {
"path": "$.action_type",
"operator": "matches",
"value": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
},
"approver": {
"organization_id": "org_helix",
"role": "managing_director"
},
"expires_after_seconds": 3600
}
```
The evaluator MUST enforce the requirement independently of whether the rule is present. A missing rule is not permission, and the reason code is `A202-CUSTODY-APPROVAL-REQUIRED`.
Section 8 already establishes that changing one byte of an action invalidates its approval, and that an approval cannot be reused across actions or transactions. Custody inherits both properties, which is why this reuses `Approval` rather than adding a trusted signing path. A forged operator signature alone does not move state, because the bound principal approval is missing.
### 11.4 Assurance is a constraint input, not a mandate property
The assurance level of a counterparty is recorded on its `InvitationAcceptance`, not on its mandate. A relying party sets a floor with an ordinary constraint, for example an `evidence.reference` constraint with `evidence_verified`, or a `commercial.string` constraint over the counterparty's recorded level.
Assurance is reported and never inferred. A mandate does not become stronger because the transactions under it succeeded.
## 12. Required negative tests
An implementation MUST refuse each of the following. The first eight are expressed as static fixtures under `../conformance/fixtures/v0.1/negative/`:
1. Mandate with no constraints.
2. Scope bounded only by geography.
3. Incoherent delegation pair.
4. Inverted validity interval.
5. Status endpoint over plain HTTP.
6. Unknown constraint type.
7. Unknown constraint operator.
8. Ambiguous subject naming both an agent and a principal.
Delegation narrowing is additionally expressed as static fixtures of kind `mandate_chain`, each carrying a parent and child pair: a valid chain narrowed on every axis, and one widening fixture per axis for the validity interval, the action set, the scope, and a numeric limit. Each widening fixture is refused with `A202-MANDATE-DELEGATION-WIDENING`. Tests 20 to 22 below therefore have static counterparts; their runtime forms remain required because a live implementation must also refuse the act, not only the document pair.
The remainder require runtime state and cannot be expressed as static documents. They are verified against a running implementation:
9. Invalid issuer signature.
10. Subject key mismatch.
11. Expired root mandate.
12. Suspended child mandate.
13. Revoked agent key.
14. Missing action.
15. Counterparty outside scope.
16. Category outside scope.
17. Amount over limit.
18. Prepayment over limit.
19. Prohibited disclosure path.
20. Child validity exceeds parent.
21. Child action absent from parent.
22. Child constraint loosens a limit.
23. Approval expired.
24. Approval action-hash mismatch.
25. Mandate revoked between proposal and append.
26. Status endpoint unreachable.
27. Invited party's root mandate scoped by category rather than by the invitation's transaction.
28. Invited party's root mandate permitting delegation.
29. Invited party's root mandate carrying an action absent from the invitation's granted scope.
30. Operator named as issuer or delegator anywhere in an invited party's chain.
31. Operator-custodied key acting with no approval bound to the action hash.
32. Operator-custodied key acting with an approval bound to a different action hash.
---
Source: https://a202.org/discovery/counterparty-invitation-v0.1.md
# Counterparty invitation v0.1
**Status:** Experimental working specification. Mixed. Sections 2, 4, 5, 6, 7, 8, 9, 10, and 12 are **normative**. Sections 1, 3, and 11 are **informative** and state no requirement on an implementation.
**Date:** 26 July 2026
**Scope:** Synthetic pilot onboarding only
## 1. Purpose
This specification defines how an organization that has never registered with A202, and may have no agent infrastructure at all, enters one named transaction because a counterparty invited it.
A202 v0.1 previously assumed both parties were already registered. Every entry point to the transaction state machine began after an organization, a principal, a root mandate, and an agent key existed. Nothing said how they came to exist for a party that did not seek them out.
The specification answers three questions and refuses a fourth.
1. How does an unregistered party receive and prove a right to participate?
2. How does that party acquire commercial authority without the operator manufacturing it?
3. How can the operator host a party that has no infrastructure, without becoming that party?
The refused question is whether an invitation can shortcut authority. It cannot. An invitation is a grant of participation. Authority is issued only by the invited party's own principal.
### 1.1 Relationship to the kernel
Invitation is onboarding, not commerce. It creates no offer, no agreement, no commitment, and no obligation. Its only effect on the transaction aggregate is that a candidate counterparty comes to exist where none existed.
It is market-neutral: no field here is meaningful in only one transaction profile. Adding these objects is a kernel change but not a transaction-profile change, so the kernel's market-neutrality property is unaffected. The neutrality probe in [canonical-commercial-model-v0.1.md](../schemas/canonical-commercial-model-v0.1.md) section 8 tests profiles, not object count.
## 2. Conformance language
`MUST`, `MUST NOT`, `REQUIRED`, `SHOULD`, `SHOULD NOT`, and `MAY` are normative within this experimental specification.
An implementation conforms to this specification when it:
1. validates `counterparty_invitation` and `invitation_acceptance` objects against `../schemas/v0.1/commercial-kernel.schema.json`;
2. enforces every invariant in section 9;
3. enforces the transitions in section 6 and in `../negotiation/pilot-transaction-state-machine-v0.1.md` section 5;
4. enforces the invited-party mandate rules in `../authority/commercial-mandate-v0.1.md` section 11;
5. passes the invitation fixtures in `../conformance/manifest-v0.1.json`.
## 3. The bootstrap problem
An unregistered party cannot sign an `ActionEnvelope`. The envelope's `created_by` is an `ActorRef` requiring an organization, an agent, and a mandate. The invited party has none of the three. This is not an oversight in the envelope design; it is the design working, and it is the reason invitation needs its own specification rather than a new action type.
Three resolutions were available.
| Resolution | Effect |
|---|---|
| Relax `ActorRef` so an unauthenticated actor may author objects | Creates a second, weaker authoring path into the kernel. Every later reader has to ask which path an object came through. Rejected. |
| Have the operator issue the invited party an organization, principal, and mandate | Completes the chain by forging it. Rejected: if the operator can complete a counterparty's authority chain, no relying party can distinguish an act the counterparty authorised from one the operator manufactured, and the mandate stops being evidence of anything. |
| Have the operator **author** the onboarding record while the claimant **attests** to it with its own key | Adopted. |
The adopted resolution is:
> An `InvitationAcceptance` is authored by the A202 control plane under the operator's own mandate, and carries the claimant's signature over the same bytes.
The operator's authorship is visible on the record and auditable. It is an honest statement of what happened: the operator registered a party, and that party attested to the registration. It is not a statement that the operator holds the party's authority, because the object grants none.
The claimant's root mandate that follows is issued by the claimant's own designated principal. The operator never appears in that chain.
## 4. Objects
### 4.1 CounterpartyInvitation
Identifier prefix `inv_`. Object type `counterparty_invitation`. Transaction-bound.
Authored by the inviting party under a mandate that includes the `invitation.issue` action. Signed with purpose `invitation_issuance`.
| Field | Type | Rule |
|---|---|---|
| `inviting_party` | `PartyRef` | The organization and agent issuing the invitation |
| `invited_channel` | object | Where the invitation is delivered. See 4.1.1 |
| `claim_secret_hash` | SHA-256 hex | Hash of the single-use claim secret. The secret itself MUST NOT appear in this object or in any shared object |
| `expires_at` | RFC 3339 | MUST be strictly later than `created_at` |
| `purpose_note` | string, 1 to 512 characters | Human-readable reason for the contact. Treated as data in every rendering |
| `disclosed_scope` | object | The bounded description of what the party is invited to. See 4.1.2 |
| `granted_scope` | object | What claiming permits. See 4.1.3 |
| `minimum_assurance` | enum | Assurance level the inviting party requires before negotiation opens |
| `endpoint_offer` | enum | `participant_operated` or `operator_hosted_available` |
#### 4.1.1 Invited channel
An invited channel is frequently a named individual's address, which is personal data. It is held in the operator's contact store and referenced here.
| Field | Rule |
|---|---|
| `channel_type` | `email_domain_scoped`, `https_endpoint`, or `existing_organization` |
| `channel_domain` | Registrable domain only. A local part, an `@`, or a full address in this field is a data-classification failure |
| `channel_ref` | Opaque pointer to the operator-held contact record |
| `channel_hash` | SHA-256 of the normalized channel value. Permits suppression and duplicate checks without storing the value |
The invitation MUST NOT carry the claim URL, any other URL, or any executable or renderable markup. The claim URL is transport, delivered on the channel, and is always an operator-controlled origin. An invitation object therefore cannot carry an attacker-chosen destination into an invited party's agent.
#### 4.1.2 Disclosed scope
Issuing an invitation is a disclosure act. The invited party learns that the inviting organization is in the market for something.
| Field | Rule |
|---|---|
| `category` | Capability category, using the same vocabulary as mandate scope |
| `summary` | 1 to 1024 characters. A bounded description, not the `CommercialRequest` |
| `response_deadline` | RFC 3339, at or before `expires_at` |
The full `CommercialRequest` is not disclosed by invitation. It becomes visible after qualification, under the existing access rules.
The inviting party's mandate governs this disclosure through `disclosure.path` constraints in the ordinary way. A summary that would disclose a prohibited field fails with `A202-DISCLOSURE-DENIED` before delivery.
#### 4.1.3 Granted scope
| Field | Rule |
|---|---|
| `transaction_ids` | MUST contain exactly one entry, equal to the invitation's own `transaction_id` |
| `actions` | The actions the invited party may later seek a mandate for. MUST be a subset of the actions the inviting party's mandate permits it to grant participation in |
An invitation that grants scope beyond its own transaction fails with `A202-INVITATION-SCOPE-EXCEEDED`. A party that has done nothing but receive one invitation has demonstrated nothing beyond that transaction, and a category-scoped grant would let one invitation become standing market access.
### 4.2 InvitationAcceptance
Identifier prefix `ina_`. Object type `invitation_acceptance`. Transaction-bound.
Authored by the A202 control plane under the operator mandate, per section 3. Requires at least two signatures: the claimant's key with purpose `invitation_claim`, and the operator's key with purpose `object_issuance`. An acceptance carrying only one of the two is not an onboarding record.
| Field | Type | Rule |
|---|---|---|
| `invitation_id` | `inv_` reference | The invitation being claimed |
| `invitation_hash` | SHA-256 hex | MUST equal the invitation's `content_hash`. Binds the claim to exact bytes, as an `Acceptance` binds an offer |
| `claimant` | object | `organization_id`, `agent_id`, `principal_id`, all newly registered or pre-existing and authorized |
| `channel_proof` | object | `method`, `verified_at`, and an optional evidence reference |
| `assurance` | enum | `self_asserted`, `credential_verified`, or `entity_bound` |
| `assurance_evidence_refs` | array | MUST be non-empty for any level above `self_asserted` |
| `key_custody` | enum | `self` or `operator_custodied` |
| `endpoint_mode` | enum | `participant_operated` or `operator_hosted` |
| `issuance_approval_id` | `apr_` or null | REQUIRED and non-null when `key_custody` is `operator_custodied` |
| `root_mandate_id` | `mnd_` reference | The mandate issued by the claimant's own principal |
Onboarding is complete when this object exists. Before it exists the party cannot act; after it exists the party can act only within its own root mandate.
## 5. Claim flow
```text
1. Inviting agent proposes invitation.issue in a signed ActionEnvelope.
2. Policy evaluator checks the inviting mandate, disclosure constraints,
rate limit, and suppression list. Deny is private to the inviter.
3. Kernel mints the CounterpartyInvitation and appends invitation.issued.
4. Operator generates the single-use claim secret, stores only its hash
on the object, and delivers the secret on the invited channel.
5. Invited party presents the secret at the operator claim endpoint.
6. Operator verifies the secret, the channel proof, and expiry, all with
uniform responses on every failure.
7. Invited party registers an Organization, designates a Principal, and
binds an Agent key, either self-held or operator-custodied.
8. The claimant's Principal issues the root CommercialMandate, bounded
to this transaction. Under operator custody, this requires a human
Approval bound to the mandate content hash.
9. Kernel mints the InvitationAcceptance, carrying the claimant's
signature and the operator's, and appends invitation.claimed.
10. The claimed party is now a qualification candidate. Nothing else has
changed. No offer, no session, no obligation.
```
Step 4 is the only point at which the claim secret exists outside the claimant's possession, and it is never written to a shared object. Step 6 is the enumeration boundary. Step 8 is the authority boundary.
### 5.1 Uniform failure
The claim endpoint MUST return an indistinguishable response for an unknown secret, an expired invitation, a revoked invitation, an already-consumed invitation, and a secret presented against the wrong channel. Response body, status code, and timing MUST NOT differ.
A distinguishable failure turns the claim endpoint into an oracle that tells an attacker which invitations exist and which are live. The response is `A202-INVITATION-UNCLAIMABLE` in every case, and the specific reason is recorded only in the operator record.
### 5.2 Claiming into an existing organization
An invitation whose `channel_type` is `existing_organization` targets a party A202 already knows. Claiming it MUST require authorization from that organization's existing principal, through the ordinary approval path.
Without this rule, an invitation would be a mechanism for attaching an attacker-controlled key to somebody else's registered organization. With it, an invitation to an existing party is a notification rather than an onboarding, which is the correct behaviour.
## 6. State and events
Invitation events are appended to the **transaction** stream.
| Current aggregate state | Event | Guard | Next |
|---|---|---|---|
| `published` | `invitation.issued` | Inviting mandate permits `invitation.issue`; disclosure constraints pass; rate limit and suppression pass; `expires_at` later than `created_at`; granted scope equals this transaction | `published` |
| `qualifying` | `invitation.issued` | As above | `qualifying` |
| `published` | `invitation.claimed` | Secret matches; invitation live; channel proof verified; root mandate issued and bounded to this transaction | `published` |
| `qualifying` | `invitation.claimed` | As above | `qualifying` |
| `published` or `qualifying` | `invitation.declined` | Declining party controls the channel | unchanged |
| `published` or `qualifying` | `invitation.expired` | Authoritative clock passed `expires_at` | unchanged |
| `published` or `qualifying` | `invitation.revoked` | Inviting mandate permits `invitation.revoke` | unchanged |
None of these transitions change aggregate state. An invitation is a fact about the market around a transaction, not a step in the transaction.
`invitation.claimed` is how a candidate comes to exist when the request audience is invited rather than public. The existing `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.
### 6.1 Invitation is denied outside the pre-qualification window
Issuing an invitation from `draft` fails with `A202-STATE-TRANSITION-DENIED`. A request that is not published has no audience, and inviting a party to an unpublished request would disclose it outside the state machine.
Issuing an invitation once the aggregate has reached `negotiating` also fails. A late entrant would join a market that is already moving, which raises fairness and timing-disclosure questions this specification does not resolve. This is a v0.1 bound, listed in section 11, not a permanent position.
### 6.2 Isolation
An invited party learns that it was invited, by whom, and to what. It MUST NOT be able to learn that anyone else was invited, how many were, or whether any of them responded.
- Invitation events are readable by the inviting party and the operator.
- An invited party reads only its own invitation and its own acceptance.
- The invited party never reads the transaction stream. Its invitation is delivered to it, and its later activity is confined to its own session stream.
- Claim response timing, error codes, and delivery latency MUST NOT vary with the number of invitations issued on the same transaction.
This follows the reasoning in [pilot-transaction-state-machine-v0.1.md](../negotiation/pilot-transaction-state-machine-v0.1.md) section 8. Invitation deliberately does not get its own 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 the per-session design exists to prevent.
## 7. Assurance
Assurance is reported. It is never inferred, and it is never raised by the passage of time or by successful transactions.
| Level | Established by | What it does not establish |
|---|---|---|
| `self_asserted` | Control of the invited channel | That the organization exists, that the claimant works there, or that anyone may bind it |
| `credential_verified` | A verified credential under the A202 VC profile | That the issuer is authoritative for the claim, or that a legal entity exists |
| `entity_bound` | Registry evidence such as GLEIF or vLEI, verified per the integration plan | Power to bind. Section 10 of the mandate specification already refuses this mapping |
An invited party enters at `self_asserted`. Raising the level requires presenting evidence, which produces a new acceptance version.
The level MUST be disclosed to the counterparty, and a mandate MAY set a floor with an `evidence.reference` or `commercial.string` constraint so that a buyer can refuse to negotiate below a level it chooses. `minimum_assurance` on the invitation is the inviting party's declared floor; the mandate constraint is what enforces it.
Declaring a level above `self_asserted` with no supporting evidence fails with `A202-ASSURANCE-UNSUPPORTED`.
## 8. Hosted endpoints and key custody
The operator MAY host an endpoint and custody a signing key for an invited party that has no infrastructure. Without this the invitation path reaches only parties that already run agent software, which is the smaller half of any real market.
Four rules bound it.
1. **An operator-custodied key MUST NOT sign a commercial act without a human `Approval`, issued by a named principal of the invited organization, bound to that exact action hash.** A hosted party operates in per-act approval mode until it presents a key it controls. The `Approval` object already binds one action hash and already invalidates on a one-byte change, so this reuses an enforced primitive rather than introducing a trusted path. The reason code is `A202-CUSTODY-APPROVAL-REQUIRED`.
2. **Custody and endpoint mode are disclosed to the counterparty.** A buyer negotiating with an operator-custodied supplier can see that it is doing so, and can set a mandate constraint refusing it.
3. **The operator runs no negotiation strategy for a hosted party.** The hosted endpoint renders proposals and collects human decisions. It does not evaluate, counter, concede, or optimize. The policy evaluator MUST NOT call a language model, which is already required by the mandate specification section 6, and the hosted endpoint inherits that prohibition.
4. **Operator signing without a bound approval is an incident, not an error.** It cannot occur through the write path, so its occurrence means the write path was bypassed. It opens an incident record rather than returning a reason code to a caller.
The conflict of interest is real: the operator runs the venue and would be holding one participant's key. Rules 1 and 3 are structural limits, not disclosures. Rule 2 is the disclosure. The residual risk, that the operator's key store is compromised and acts are forged, is bounded by rule 1: a forged signature alone does not move state, because the bound principal approval is missing.
## 9. Invariants that schema validation cannot express
| Invariant | Reason code |
|---|---|
| An invitation's `expires_at` is strictly later than its `created_at` | `A202-INVITATION-EXPIRED` |
| `granted_scope.transaction_ids` contains exactly the invitation's own `transaction_id` | `A202-INVITATION-SCOPE-EXCEEDED` |
| An acceptance's `invitation_hash` equals the referenced invitation's `content_hash` | `A202-INVITATION-HASH-MISMATCH` |
| An acceptance carries both a claimant `invitation_claim` signature and an operator `object_issuance` signature | `A202-INVITATION-CLAIM-UNSIGNED` |
| An assurance level above `self_asserted` is supported by at least one evidence reference | `A202-ASSURANCE-UNSUPPORTED` |
| `key_custody: operator_custodied` carries a non-null `issuance_approval_id` | `A202-CUSTODY-APPROVAL-REQUIRED` |
| The claim secret never appears in a shared object | `A202-INVITATION-SECRET-DISCLOSED` |
| A raw channel address never appears in a shared object | `A202-DISCLOSURE-DENIED` |
| An invited party's root mandate is scoped to the invitation's transaction only | `A202-INVITATION-SCOPE-EXCEEDED` |
| A single-use invitation is claimed at most once | `A202-INVITATION-UNCLAIMABLE` |
## 10. Abuse and anti-abuse
Invitation is an outbound messaging surface addressed at parties who did not ask to hear from it. An implementation MUST enforce every control below. They are mandatory rather than advisory, because each of them is the only thing standing between a legitimate onboarding path and a bulk-messaging channel with a signature on it.
| Control | Requirement |
|---|---|
| Mandate gate | `invitation.issue` is an explicit mandate action, deny by default like every other action |
| Rate limit | Per inviting organization, per transaction, and per invited domain. Exceeding it is a `deny`, private to the inviter |
| Suppression | A declined channel is suppressed permanently for that inviting organization. `channel_hash` makes this checkable without storing addresses |
| Permanent decline | An invited party can decline without claiming and without registering anything |
| Content bound | `purpose_note` and `summary` are length-bounded, carry no URL and no markup, and are marked as data wherever an agent renders them |
| Retention | An unclaimed invitation and its contact record are deleted after expiry plus a retention window set by privacy review |
The content bound narrows the prompt-injection surface. It does not close it. An invited party's agent still reads attacker-influenced free text, and the only structural defense here is that the text cannot carry a destination.
## 11. Open questions
- What channel proof is sufficient for `self_asserted`? A domain-scoped address is the current position. A shared mailbox at a large consumer provider is materially weaker and may need a lower sub-level.
- Should invitation be permitted once the aggregate reaches `negotiating`? Section 6.1 currently denies it. Permitting a late entrant needs a fairness rule and a timing-disclosure analysis first.
- Should the inviting party learn that an invitation was delivered, opened, or declined? Delivery and claim are needed. Open, and declined with a reason, may disclose more about the invitee than the invitation disclosed about the inviter.
- Does operator key custody create a controller or processor relationship, and in which jurisdiction? This is a legal question rather than a protocol question and is not answered here.
- Is per-act approval usable in practice, or does it make hosted participation too slow to adopt? This is a measurement that only deployment can settle.
## 12. Required tests
An implementation MUST be verified against each of the following. The first nine are static fixtures, in `../conformance/`:
1. Valid counterparty invitation.
2. Valid invitation acceptance.
3. Claim secret present in the shared object.
4. Expiry at or before issue.
5. Granted scope beyond the invitation's transaction.
6. Raw channel address in the shared object.
7. Assurance above `self_asserted` with no evidence.
8. Operator custody with no issuance approval.
9. Acceptance with a single signature.
The remainder require runtime state and cannot be expressed as static documents. They are verified against a running implementation:
10. End-to-end onboarding: an organization with no prior A202 presence receives an invitation, claims it, issues its own root mandate, negotiates, and reaches an agreement.
11. Invited party's root mandate scoped by category rather than transaction is refused.
12. Operator-custodied key attempts a commercial act with no bound approval and is refused.
13. Approval bound to a different action hash does not authorize the act.
14. Claim replayed after consumption is refused, with a response indistinguishable from an unknown secret.
15. Claim after expiry is refused, with the same indistinguishable response.
16. Claim against an existing organization without that organization's principal authorization is refused.
17. **Isolation:** an invited supplier cannot determine how many other invitations were issued on the same transaction, from response content, error codes, or timing.
18. Declined channel is suppressed and a second invitation from the same inviting organization is refused.
19. Rate limit exceeded produces a `deny` that is private to the inviter and consumes no shared sequence.
20. An invitation issued from `draft`, and one issued from `negotiating`, are both refused.
---
Source: https://a202.org/negotiation/pilot-transaction-state-machine-v0.1.md
# 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](../proposals/A202-0014-bilateral-formation-and-scope-repair.md): 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](../proposals/A202-0010-model-completion.md): 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
```mermaid
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](../discovery/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](../agreement/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](../proposals/A202-0014-bilateral-formation-and-scope-repair.md).
Four things hold on the direct path, and each is a guard rather than a description.
1. **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.direct` event and a session stream on the same transaction is refused at replay with `A202-EVIDENCE-TRANSITION-ILLEGAL`, and live with `A202-STATE-TRANSITION-DENIED`.
2. **The offer is current and unexpired**, on the ordinary rules of section 7. Nothing about the entry path relaxes what makes an offer acceptable.
3. **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.
4. **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`, never `committed`: `agreement.committed` is 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](../schemas/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_id` MUST reference the immediately preceding offer in that bilateral session.
- An offer accepted after `valid_until` is invalid.
- An offer whose `valid_until` is not later than its `created_at` is 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_sequence` for the stream it targets.
- A bilateral action targets its session stream. Its `expected_sequence` is the session sequence.
- An aggregate action targets the transaction stream.
- Append succeeds only when `expected_sequence` equals that stream's current sequence.
- A mismatch returns `409 A202-SEQUENCE-CONFLICT` with the current sequence **of that stream only**.
Consequences, all of them required:
1. A supplier never observes a sequence number influenced by a rival's activity.
2. A supplier never receives a conflict caused by a rival's activity.
3. Concurrent negotiation with three suppliers does not degrade into repeated conflict and retry.
4. A denied action consumes no sequence number in any shared stream. It is recorded in the actor's private stream.
5. Single-award integrity is preserved at the aggregate level: two `offer.selected` events 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](../evidence/evidence-verification-v0.1.md) section 4, so nothing new is required of a verifier.
Two consequences follow.
1. **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.
2. **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](../disputes/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:
1. verifies the event-chain hashes within each stream;
2. verifies each actor signature;
3. resolves the mandate and policy decision referenced by each event;
4. applies session events in sequence order, then aggregate events in sequence order;
5. recomputes object and agreement hashes;
6. produces final aggregate and session states;
7. 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](auction-event-semantics-v0.1.md) section 8.1 and [conformance-role-scopes-v0.1.md](../conformance/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
1. Happy path from `draft` to `completed`, including `offer.accepted` and `offer.selected` as distinct events.
2. Cancellation from every eligible pre-commit state, including `draft`.
3. Expiry from every eligible pre-commit state.
4. Illegal direct transition from `published` to `committed`.
5. Performance event before commitment.
6. Settlement instruction before acceptance.
7. Two concurrent offer selections on the transaction stream.
8. Acceptance of a stale offer.
9. Acceptance of an offer whose expiry precedes its creation.
10. Approval over a changed offer.
11. Withdrawal of an acceptance before commitment.
12. Exception, remediation, and resumed performance.
13. Termination from exception.
14. Replay after adapter failure.
15. **Isolation:** three concurrent sessions produce sequence series indistinguishable from solo participation.
16. **Isolation:** a losing supplier's close reason code contains no price, counterparty, or session-count information.
17. A denied action leaves every shared stream sequence unchanged.
18. Onboarding by invitation from `published` through to a committed agreement, by an organization with no prior A202 presence.
19. Invitation refused from `draft` and from `negotiating`.
20. **Isolation:** an invited party cannot determine, from response content, error codes, or timing, how many other invitations were issued on the same transaction.
21. An operator-custodied key attempts a commercial act with no bound approval and is refused.
22. Consensual termination from each eligible committed state, with every open obligation named and released.
23. `termination.agreed` with one signature, or with an open obligation the record does not name, is refused.
24. 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.
25. `termination.agreed` replayed against a rules version that never permitted it is illegal at replay.
26. Direct formation: `agreement.direct` carries a transaction from `draft` to `agreement_pending` under 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 to `completed` with no operator-authored, operator-ordered, or operator-annotated object anywhere in the record.
27. Direct formation refused: `agreement.direct` on a transaction that already carries a session stream; `agreement.direct` replayed against a rules version that never permitted it; and a record replaying `draft` straight to `committed`, 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](../conformance/conformance-role-scopes-v0.1.md) sections 4.2 and 5.2.
---
Source: https://a202.org/negotiation/auction-event-semantics-v0.1.md
# Auction event semantics v0.1
**Status:** Experimental working specification. Mixed. Sections 2, 3, 4, 5, 6, 7, and 8 are **normative**. Sections 1 and 9 are **informative** and state no requirement on an implementation.
**Date:** 26 July 2026
**Scope:** Synthetic and non-binding validation only
**Relates to:** [pilot transaction state machine v0.1](pilot-transaction-state-machine-v0.1.md), [transaction profile extension model v0.1](../schemas/transaction-profile-extension-model-v0.1.md), [commercial mandate v0.1](../authority/commercial-mandate-v0.1.md), [counterparty invitation v0.1](../discovery/counterparty-invitation-v0.1.md)
## 1. Purpose
A competitive bidding event is a transaction with several counterparties bidding against one demand, exactly one of which is awarded. The state machine already carries the states and streams such an event needs. This document states the semantics that are specific to it: what a bidder may be told, under what authority a bid may be appended, when an award is distinct from an acceptance, what may not change once the event has opened, and what a losing bidder can verify afterwards.
It specifies event semantics. It does not specify how an implementation ranks or scores bids, how it advances or extends an event in time, how it executes the event internally, or how it eliminates the side channels that would otherwise carry rival information. Those are implementation concerns. This document states the properties an implementation has to exhibit and the procedures by which a participant checks them.
Auction mechanism design is a mature field. Nothing here attempts novel mechanism design and nothing here should be read as doing so.
## 2. Bid authority
Every bid is an ordinary offer under an ordinary mandate. There is no separate bidding credential.
1. A bid MUST be carried in a signed `ActionEnvelope` under a `CommercialMandate` whose `actions` include `offer.submit`, and whose `scope` covers the transaction being bid into.
2. A bid whose mandate does not resolve, has expired, is suspended, is revoked, or does not cover the transaction MUST NOT append to any stream.
3. Bid limits are mandate constraints in the ordinary vocabulary. A maximum or minimum price, a permitted currency, and a prohibited disclosure path are expressed as `commercial.decimal`, `commercial.string`, and `disclosure.path` constraints, and are evaluated by the same path as any other action.
The consequence is that a bid which no principal authorised cannot exist. An operator or an event owner cannot inject a bid that is not backed by a mandate, because a bid with no valid mandate cannot append. This is the structural control against a bid that is placed only to move the outcome.
## 3. Award is a transition distinct from acceptance
Three things happen in sequence and MUST NOT be collapsed:
- **Acceptance is not selection.** An `Acceptance` signed over a current offer hash moves one bilateral session to `accepted`. It does not commit the transaction and it does not end the event.
- **Selection is not agreement.** `offer.selected` on the transaction stream is the award. It moves the aggregate to `agreement_pending`. It is not itself the agreement.
- **Agreement is agreement.** `agreement.committed` requires both parties to have signed the same agreement hash.
Where an implementation ranks bids, ranking MUST NOT award. Ranking produces an order; `offer.selected` produces a winner. The party holding selection authority retains it subject to its own mandate, and an award that does not follow the ranking MUST record a reason.
Exactly one award appends per award unit. Two `offer.selected` events contend on the transaction stream, and the losing attempt is refused. This is the one place where contention is intended, and it is what makes single-award integrity an invariant rather than an expectation.
## 4. Disclosure policy
Isolation is the default value of a declared disclosure policy, not the only behaviour an event may have. An event that reveals nothing across bidders and an event that reveals a bidder's own rank are both legitimate. What is not legitimate is disclosure that is undeclared, asymmetric, unrecorded, or inferred from a refusal.
| Policy | What a bidder observes |
|---|---|
| `sealed` | Nothing about rivals. The default |
| `participant_count` | The number of qualified bidders only |
| `rank_only` | Its own position, and neither prices nor identities |
| `best_value_anonymous` | The current best price, with no identity attached |
| `full_public` | All bids and all identities |
Every policy other than `sealed` MUST satisfy all five of the following.
1. **Declared before the event opens.** The policy is part of the signed event definition. It MUST NOT change while bidding is live.
2. **Symmetric.** Every bidder in a class receives the same disclosure on the same terms. Asymmetric visibility is the definition of an unfair event, and an implementation MUST NOT produce it.
3. **Bounded by the mandate.** A bidder's `disclosure.path` constraints continue to apply to its own outbound content. An event policy widens what a bidder is shown. It never widens what a bidder may send.
4. **Recorded.** Every disclosure is an event. What was revealed, to whom, at which sequence, and at what time MUST be replayable.
5. **Fail closed.** An unrecognised policy value MUST evaluate to `sealed`. This matches the closed-registry treatment of mandate constraint types and operators: an unknown value is a refusal, never a permission.
Property 4 is what makes a disclosure checkable rather than promised. A bidder that is told its rank can, after the event, replay the record and confirm that the rank it was shown is the rank the record carries.
## 5. Rule freezing
The event definition is signed and frozen when the event opens.
1. Disclosure policy, close time, and any published rule governing how bids are compared MUST be part of the signed event definition.
2. An attempt to modify any of them after the event has opened MUST be refused with `A202-SCORING-RULE-FROZEN` and MUST be recorded.
3. A change to a frozen rule requires a new event. It is not an amendment to a running one.
A rule that changes after bids are visible is the classic manipulation of a competitive event, and it is the one an outside party has the least chance of detecting after the fact. Freezing it at open and recording every refused attempt is what converts "the rules did not change" from an assurance into a check.
## 6. Isolation, stated as a property
**The property.** No participant in a sealed event can infer the existence, the activity, or the terms of another participant. This holds for the content a participant receives, for the sequence numbers it observes, for the reason codes it is returned, and for the timing of the responses it gets.
Three consequences follow, and an implementation MUST exhibit all three.
1. A bidder never observes a sequence number that was influenced by a rival's activity. Sequence numbers are per stream, and a bidder's session stream carries only its own activity. A shared counter across concurrent sessions would be a covert channel, because a bidder that watched it advance would learn that somebody else acted and when.
2. A bidder never receives a conflict, a refusal, or a delay that was caused by a rival's activity.
3. A refused act consumes no sequence number in any shared stream and is therefore invisible to every other participant.
**How a losing bidder verifies it.** The verification does not require access to the operator. A losing bidder holds a replayable export of its own session stream and of the transaction-stream events it is party to. It verifies the event-chain hashes within each stream, verifies each actor signature, resolves the mandate and policy decision referenced by each event, replays the events in sequence order, and recomputes the object and agreement hashes. It then checks that the sequence series it observed is the series it would have observed as the only participant, and that no close reason, refusal code, or event payload it received names a rival, a price, a count, or a time attributable to another party.
The property is verified by tests 1 to 5 in section 8. It is asserted here against the state machine's per-stream concurrency rules and the session event allowlist, and it is those tests that establish it for any particular implementation.
**What isolation does not cover.** Isolation is a property of the event record and the responses an implementation gives. It says nothing about what participants tell each other outside the system. See section 7.
## 7. Market integrity
An implementation can enforce mechanism integrity: sealed bids stay sealed, the declared rules do not change once the event has opened, every bid is authorised, and the whole event is replayable. Those are the properties this document specifies.
An implementation cannot detect collusion agreed outside the system. Two bidders who agree their prices by telephone produce a record that is indistinguishable from two bidders who did not. Any claim to detect that would be unsupportable, and this specification does not make it.
The controls that are enforceable are these:
| Threat | Control |
|---|---|
| A bid placed only to move the outcome, by a party with no intention or no authority to perform | Every bid carries a mandate. A bid with no valid mandate cannot append. Section 2 |
| A rule altered after bids are visible | The event definition is signed and frozen at open. Changes require a new event. Section 5 |
| A participant inferring another participant's existence, activity, or terms | Isolation, as a property with a verification procedure. Section 6 |
| A refusal that discloses what the refusal was protecting | Disclosure-bearing reason codes fail closed under a policy that does not permit them. Section 8 |
| A late entrant joining a market that has already moved | Invitation is refused once the aggregate reaches `negotiating`. See [counterparty-invitation-v0.1.md](../discovery/counterparty-invitation-v0.1.md) section 6.1 |
## 8. Error codes and required tests
### 8.1 Error codes
Additions to [pilot-transaction-state-machine-v0.1.md](pilot-transaction-state-machine-v0.1.md) section 10. All fail closed.
| Code | Meaning |
|---|---|
| `A202-AUCTION-CLOSED` | A bid arrived after the authoritative close for its event |
| `A202-AUCTION-FORMAT-UNSUPPORTED` | The requested event format is not defined in this specification version |
| `A202-BID-NO-IMPROVEMENT` | The event requires an improving bid and this bid does not improve on the bidder's own standing bid |
| `A202-LOT-UNKNOWN` | A bid names an award unit that does not resolve on this event. v0.1 defines no award-unit object, so any bid naming one fails with this code |
| `A202-LOT-ALREADY-AWARDED` | An award has already appended for this award unit |
| `A202-DISCLOSURE-POLICY-VIOLATION` | A requested view exceeds the event's declared disclosure policy |
| `A202-SCORING-RULE-FROZEN` | An attempt to modify a frozen event rule after the event opened |
| `A202-ROUND-NOT-OPEN` | A bid arrived outside the window it was submitted against |
`A202-BID-NO-IMPROVEMENT` MUST NOT be returned under `sealed` or `participant_count`. Under those policies the refusal itself would tell the bidder that a better standing bid exists, which is precisely what those policies withhold. This is a disclosure rule expressed as an error-code rule, and it is the case that shows why a code cannot be chosen independently of the policy in force.
`A202-LOT-ALREADY-AWARDED` is bound by the same rule. Returned to a bidder under `sealed` or `participant_count` it discloses that an award happened before the neutral close reason delivers that fact symmetrically to every loser at once. Under those policies a late bid is refused with the ordinary state-transition refusal, and the award-specific code is reserved for policies that already disclose the event's progress. Both codes fail closed the same way: the v0.1 kernel has no disclosure policy object, so a policy decision carrying either code is refused with `A202-DISCLOSURE-POLICY-VIOLATION`.
### 8.2 Required tests
These extend the required transition tests in [pilot-transaction-state-machine-v0.1.md](pilot-transaction-state-machine-v0.1.md) section 11. Each is a normative requirement on an implementation and each is the procedure by which a participant, rather than the operator, establishes the corresponding property.
1. **Sealed-bid isolation under award.** Five concurrent bidders. After the award, each losing bidder's full replayable export permits no inference of rival count, identity, price, or timing.
2. **Hard close.** A bid arriving one second after the authoritative close is refused with `A202-AUCTION-CLOSED`, consumes no sequence in any shared stream, and is invisible to every other bidder.
3. **Single award under contention.** Two selections on one award unit. Exactly one appends and the other is refused with `A202-LOT-ALREADY-AWARDED`.
4. **A refusal leaks nothing.** A bid refused for mandate, policy, or award-unit reasons leaves every shared stream sequence unchanged.
5. **Award-unit independence.** Award, cancellation, and expiry on one award unit produce no observable event, sequence change, or timing signal for a bidder participating only on another.
6. **Disclosure policy conformance.** Under `rank_only`, no bidder's export contains a rival price or a rival identity. Under `best_value_anonymous`, no bidder's export contains a rival identity.
7. **Frozen rules.** Any attempt to alter the disclosure policy, the close time, or a published comparison rule after open is refused and recorded.
## 9. Limitations
- The sealed-bid semantics stated here are asserted against the transaction state machine and the session event allowlist. Tests 1 to 4 in section 8.2 are what verify them for a given implementation, and an implementation that has not run them has not established the property.
- This specification version defines a single award unit per event. The `A202-LOT-UNKNOWN` and `A202-LOT-ALREADY-AWARDED` codes exist so that a bid naming an award unit is refused rather than silently accepted against the wrong one.
- Formats that require an event to advance in time under an authoritative timeline, rather than to close at an absolute deadline, are not defined in this version. An implementation MUST refuse to open an event in a format this specification does not define, rather than approximate it. An approximated event is one whose fairness cannot be proven, which removes the reason to run it under this specification at all.
- Regulated public procurement has format, notice, and record obligations that are not analysed here.
---
Source: https://a202.org/agreement/obligation-v0.1.md
# Obligation v0.1
**Status:** Experimental, adopted through proposal [A202-0002](../proposals/A202-0002-obligation.md). Mixed. Sections 2, 3, 4, 5, 6, and 7 are **normative**. Sections 1 and 8 are **informative** and state no requirement on an implementation.
**Date:** 27 July 2026
**Revised:** 28 July 2026, under [A202-0010](../proposals/A202-0010-model-completion.md): the `released` terminal state and the `obligation.released` transition, so that a terminated transaction cannot strand an open obligation. Also under [A202-0011](../proposals/A202-0011-registry-and-waiver-corrections.md): the waiver reaches `asserted` and `rejected`, which section 5.4 already described as available.
**Scope:** Synthetic pilot transactions only
**Depends on:** [canonical commercial model v0.1](../schemas/canonical-commercial-model-v0.1.md), [commercial mandate v0.1](../authority/commercial-mandate-v0.1.md), [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md), [evidence verification v0.1](../evidence/evidence-verification-v0.1.md), [determination and dispute v0.1](../disputes/determination-v0.1.md)
## 1. Purpose
A commitment produces obligations. A counterparty cannot discharge an obligation it cannot parse, and two organisations that agree on price and then disagree on what was owed have not agreed on anything durable.
This document defines four things and nothing else: what is owed, by whom, when it is due, and what counts as done.
Everything that happens around those four is outside this specification. Tracking an obligation, monitoring its due condition, reminding a party, escalating, and enforcing a failure are operator concerns. They are not published here and an implementation is not conformant or non-conformant by reference to them. What is published is the shape a counterparty parses, the transitions the shape may take, and the refusals that hold when the shape or the transition is wrong.
The relationship to the transaction aggregate is already fixed. `obligation.activated` moves the aggregate from `committed` to `in_performance`, and a dispute over an obligation is an exception that moves the aggregate to `exception_open`. This document specifies the object underneath those transitions rather than adding new aggregate states.
## 2. Conformance language
`MUST`, `MUST NOT`, `REQUIRED`, `SHOULD`, `SHOULD NOT`, and `MAY` are normative within this experimental specification.
An implementation conforms to this specification when it:
1. validates `obligation`, `performance_event`, and `obligation_response` objects against `../schemas/v0.1/commercial-kernel.schema.json`;
2. enforces the due-condition typing rules in section 4;
3. enforces the discharge and acceptance rules in section 5;
4. enforces the guarded transitions in section 6;
5. returns the refusal codes in section 7 for the failures they name;
6. passes the obligation fixtures in `../conformance/manifest-v0.1.json`.
Schema validity is necessary and not sufficient. The rules in sections 4, 5, and 6 are cross-object and cannot be expressed in JSON Schema alone.
## 3. The obligation object
An obligation is a shared kernel object. It carries the common envelope defined in [canonical-commercial-model-v0.1.md](../schemas/canonical-commercial-model-v0.1.md) section 3, uses the `obl_` identifier prefix, and is canonicalised, hashed, and signed under section 4 of that document. `transaction_id` is REQUIRED and non-null.
| Field | Type | Rule |
|---|---|---|
| `agreement_id` | `agr_` identifier | The agreement the obligation derives from. REQUIRED |
| `commitment_id` | `cmt_` identifier | The commitment this obligation decomposes. REQUIRED |
| `obligor` | `partyRef` | The party that owes. REQUIRED |
| `obligee` | `partyRef` | The party owed to. REQUIRED |
| `subject` | `subjectRef` | What is owed, by reference. See section 3.1. REQUIRED |
| `due_condition` | `dueCondition` | When it is due. See section 4. REQUIRED |
| `quantity` | `quantityString` | How much is owed, in the same representation the kernel uses for terms. REQUIRED |
| `unit_code` | UN/ECE Recommendation 20 common code | REQUIRED. `unit_name` MAY accompany it and MUST NOT be used for matching or validation |
| `consideration` | `money` or null | The consideration attached to this obligation, where the agreement attaches one. Null where it does not |
| `state` | enum | One of the states in section 6. REQUIRED |
`obligor` and `obligee` MUST both appear in the referenced agreement as `buyer` or `supplier`. An obligation naming a party that is not a party to the agreement is refused with `A202-OBLIGATION-SUBJECT-UNREFERENCED`.
Money uses the `money` type. Percentages, where a profile term expresses one, use `percentString`. An obligation MUST NOT introduce a parallel representation for either.
### 3.1 Subject is a reference, never a restatement
`subject` names what is owed by pointing at the agreement's terms. It does not copy them.
| Field | Type | Rule |
|---|---|---|
| `agreement_id` | `agr_` identifier | MUST equal the obligation's `agreement_id` |
| `terms_hash` | `sha256Hex` | MUST equal the referenced agreement's `terms_hash` |
| `profile` | registered profile identifier | MUST equal the agreement's `terms.profile` |
| `term_path` | path expression | Addresses the term under `$.terms.core` or `$.terms.profile_terms` |
`term_path` uses the same path addressing as a mandate constraint `path`, so a term that a mandate constrains and a term that an obligation owes are named the same way. A path that addresses a term outside the referenced profile, or that does not resolve against the referenced terms, is refused with `A202-OBLIGATION-SUBJECT-UNREFERENCED`.
Restating a term inside the obligation would create a second copy that can drift from the agreement, and the drift would be invisible because both copies would be validly signed. Reference plus terms hash makes drift a hash mismatch instead.
An obligation MUST NOT carry any of the private data classes listed in [canonical-commercial-model-v0.1.md](../schemas/canonical-commercial-model-v0.1.md) section 13.
## 4. Due condition
`due_condition` is typed. It is never free text, because a due date a counterparty has to read in order to compute is a due date the counterparty's agent cannot act on.
Three condition types are registered in v0.1, plus one composition.
| `type` | Fields | Meaning |
|---|---|---|
| `due_at_time` | `at`, RFC 3339 timestamp in UTC, or a date with a named business calendar reference | Due at a stated time |
| `due_on_event` | `act_ref`, or `evidence_type`, exactly one of the two | Due when the referenced act appends, or when evidence of the referenced registered type is presented and verifies |
| `due_on_discharge` | `obligation_id` | Due when the named obligation reaches `discharged`. This is how ordering between obligations is expressed |
| `all_of` | `conditions`, at least two entries | Due when every listed condition is met |
Rules:
1. Conditions compose with AND only. There is no OR and no NOT in v0.1. A disjunction is expressible as two obligations, and stating that explicitly is what makes the record replayable.
2. `all_of` MUST NOT nest more than one level deep. A nested conjunction is a flat conjunction written twice.
3. `due_on_event` MUST reference an act by content hash, or an evidence type from the registered evidence type list defined in [evidence-verification-v0.1.md](../evidence/evidence-verification-v0.1.md) section 3. An unregistered evidence type fails closed.
4. `due_on_discharge` MUST reference an obligation on the same `transaction_id`. A cycle among `due_on_discharge` references is refused: no obligation in the cycle can ever become due, so accepting the cycle would create an obligation set that is permanently unsatisfiable and silently so.
5. A `due_at_time` value expressed in business days MUST name a calendar, as required by [canonical-commercial-model-v0.1.md](../schemas/canonical-commercial-model-v0.1.md) section 7. A duration in business days with no named calendar is not a term.
An unknown `type`, or a registered `type` with fields belonging to a different one, fails closed with `A202-OBLIGATION-CONDITION-UNKNOWN`. The registry is closed in v0.1 and is enforced at validation and independently at evaluation, so that adding a member to the schema enum without an evaluator implementation cannot cause an obligation to become due.
## 5. Discharge and acceptance
Performance and acceptance are two acts by two parties. Collapsing them lets the obligor declare its own obligation discharged.
### 5.1 Assertion
The obligor asserts performance by appending a `performance_event` object that:
1. names the obligation in `obligation_id`;
2. carries at least one evidence reference in `evidence_refs`, in the shape defined in [evidence-verification-v0.1.md](../evidence/evidence-verification-v0.1.md) section 3;
3. states `asserted_quantity` and `unit_code`, where `unit_code` equals the obligation's;
4. is signed by the obligor.
An assertion with no evidence reference is refused with `A202-OBLIGATION-ASSERTION-UNEVIDENCED`. An assertion carries a claim about the world, and a claim with nothing behind it cannot be checked by the obligee now or by a third party later.
The assertion's `content_hash` is the assertion hash. It is the value the obligee's response binds to.
### 5.2 Response
The obligee responds by appending an `obligation_response` object, signed by the obligee, with `response_type` one of `accept`, `reject`, or `waive`.
| Field | Rule |
|---|---|
| `obligation_id` | REQUIRED. The obligation responded to |
| `response_type` | REQUIRED. `accept`, `reject`, or `waive` |
| `assertion_id` | REQUIRED for `accept` and `reject`. Absent for `waive` |
| `assertion_hash` | REQUIRED for `accept` and `reject`. MUST equal the referenced assertion's `content_hash` |
| `accepted_quantity` | REQUIRED for `accept`. A `quantityString` at or below the asserted quantity |
| `remainder_obligation_id` | REQUIRED for `accept` where `accepted_quantity` is below the obligation's `quantity`. Absent otherwise |
| `reason_code` | REQUIRED for `reject`. From the closed list in section 5.5 |
Four rules hold and an implementation MUST enforce each independently.
1. **Acceptance is a distinct signed act by the obligee.** A response signed by any party other than the obligee named on the obligation is refused with `A202-OBLIGATION-RESPONSE-UNAUTHORIZED`. This holds for `waive` as well as for `accept`.
2. **Acceptance binds an exact assertion.** `assertion_hash` MUST equal the `content_hash` of the assertion named in `assertion_id`. A mismatch is refused with `A202-OBLIGATION-RESPONSE-HASH-MISMATCH`. Changing one byte of the assertion invalidates the response, exactly as changing one byte of an action invalidates an approval under [commercial-mandate-v0.1.md](../authority/commercial-mandate-v0.1.md) section 8.
3. **Partial acceptance creates a new obligation.** Where `accepted_quantity` is below the obligation's `quantity`, the response MUST name a `remainder_obligation_id`, and that obligation MUST reference the same `agreement_id`, the same `subject`, and a `quantity` equal to the difference. A partial acceptance with no remainder named is refused with `A202-OBLIGATION-REMAINDER-MISSING`.
4. **No response mutates the obligation.** An obligation's `subject`, `due_condition`, `quantity`, `unit_code`, and `consideration` are fixed at issue. A response that would alter any of them is refused with `A202-OBLIGATION-TERMS-MUTATED`. Changed terms are a new obligation under an amended agreement, never a new version of this one.
### 5.3 Why partial acceptance is never a mutation
If accepting eight of ten units rewrote the obligation to say eight, the record would afterwards show an obligation that was fully performed. The two units that were not delivered would have left no trace, and the party that most needs to prove they were owed would be the party whose evidence had been edited away. Splitting into an accepted obligation and a remainder obligation keeps both facts, and both remain independently verifiable.
### 5.4 Rejection
A rejection carries a `reason_code` and leaves the obligation's terms untouched. It moves the obligation to `rejected` and opens the dispute path defined in [determination-v0.1.md](../disputes/determination-v0.1.md). It does not renegotiate the obligation, and it does not by itself determine anything: a rejection is one party's position, and a determination is what resolves a contested one.
The obligor MAY assert again against the same obligation. Each assertion is a new object with its own hash, and each response binds exactly one of them.
### 5.5 Rejection reason codes
The list is closed in v0.1. An unregistered value is refused with `A202-OBLIGATION-REJECTION-REASON-UNKNOWN`.
| `reason_code` | Meaning |
|---|---|
| `evidence_insufficient` | The evidence presented does not support the assertion |
| `evidence_unverified` | Required evidence is absent or failed verification |
| `quantity_short` | The asserted quantity is below what the obligation owes and no remainder was accepted |
| `subject_not_as_agreed` | What was performed does not match the referenced term |
| `due_condition_not_met` | The obligation was not due when the assertion was made |
| `assertion_unauthorized` | The assertion was not signed under a mandate covering the obligor's act |
Each value is stated from the obligee's own position. None of them discloses the identity, price, count, or timing of any other party's activity on the transaction, and an implementation MUST NOT extend the list with a value that would.
## 6. States and transitions
| State | Meaning |
|---|---|
| `pending` | The obligation exists and its due condition is not met |
| `due` | The due condition is met and no assertion is outstanding |
| `asserted` | The obligor asserted performance and the obligee has not responded |
| `accepted` | The obligee accepted an assertion |
| `rejected` | The obligee rejected an assertion |
| `disputed` | A dispute has been raised on the obligation |
| `discharged` | Nothing further is owed under this obligation |
| `waived` | The obligee released the obligor by signed act |
| `expired` | A deadline elapsed before the obligation was discharged or waived |
| `released` | The transaction ended through an authorized termination whose record named this obligation's disposition |
`discharged`, `waived`, `expired`, and `released` are terminal in v0.1.
`released` exists because a terminated transaction must not strand its obligations. It is distinct from `waived` because the release is carried by the termination record both parties signed, not by a unilateral obligee response, and distinct from `discharged` because nothing was performed. The record shows that the duty ended with the transaction, which is a different fact from the duty having been met.
### 6.1 Transition table
| Current | Event | Guard | Next |
|---|---|---|---|
| none | `obligation.activated` | Obligation derives from a committed agreement; `subject.terms_hash` equals the agreement's `terms_hash` | `pending` |
| `pending` | `obligation.due` | Every clause of `due_condition` evaluates true against a referenced act, evidence, discharge, or the authoritative clock | `due` |
| `pending`, `due`, `asserted`, or `rejected` | `obligation.waived` | Response signed by the obligee with `response_type` `waive` | `waived` |
| `pending` or `due` | `deadline.elapsed` | Authoritative clock passed the agreement or transaction deadline governing this obligation | `expired` |
| `due` | `performance.declared` | Assertion names this obligation, carries at least one evidence reference, and is signed by the obligor | `asserted` |
| `asserted` | `acceptance.granted` | Response signed by the obligee; `response_type` `accept`; `assertion_hash` matches; remainder named where the accepted quantity is short | `accepted` |
| `asserted` | `acceptance.rejected` | Response signed by the obligee; `response_type` `reject`; `assertion_hash` matches; `reason_code` registered | `rejected` |
| `asserted`, `accepted`, or `rejected` | `dispute.raised` | Dispute references this obligation's state by content hash and is raised inside the window in force | `disputed` |
| `rejected` | `performance.declared` | A new assertion, with its own hash, naming the same obligation | `asserted` |
| `accepted` | `obligation.discharged` | `accepted_quantity` equals `quantity`, or a remainder obligation exists and is named | `discharged` |
| `disputed` | `determination.recorded` | The determination's stated effect is `binding` on this question under the rules in force at the time | the state the determination states |
| `disputed` | `determination.recorded` | The determination's stated effect is `advisory` or `presumptive` | `disputed` |
| `pending`, `due`, `asserted`, `rejected`, or `disputed` | `obligation.released` | The transaction appended `termination.agreed` or `transaction.terminated`, and the termination record names this obligation's disposition | `released` |
Two guards carry most of the weight and are stated separately because they are easy to lose.
**A determination moves state only where the rules granted it that effect.** An advisory or presumptive determination is evidence and is recorded as evidence. It does not move the obligation, and an implementation that lets it move the obligation has given a determination an effect the parties did not agree to. This fails closed: where the effect is unstated or does not resolve, the obligation does not move.
**A rejection does not end the obligation.** `rejected` is not terminal. The obligation is still owed, and the path forward is a further assertion, a waiver, a determination, or expiry.
**A waiver is available wherever the obligation is still owed.** The obligee may release the obligor from `pending`, `due`, `asserted`, and `rejected`. Restricting the waiver to the states before an assertion would mean that an obligee who wished to release its counterparty after seeing a partial or unsatisfactory assertion had no way to say so: it would have to accept performance it did not receive, or reject and wait for a deadline it did not want to enforce. Both would put a false record on the transaction, and the second leaves the obligation owed until it expires, which is a worse outcome for the obligor the waiver was meant to benefit.
A waiver is deliberately not available from `disputed`. A dispute is a contested question already before a determiner, and a unilateral release while it is open would moot a determination the other party is entitled to receive. The obligee that wishes to release a disputed obligation withdraws its position through the dispute path, and the determination records what happened.
### 6.2 Relationship to the aggregate
The aggregate transitions are unchanged by this document.
| Obligation event | Aggregate effect under [pilot-transaction-state-machine-v0.1.md](../negotiation/pilot-transaction-state-machine-v0.1.md) |
|---|---|
| `obligation.activated` | `committed` to `in_performance` |
| `performance.declared` | `in_performance` to `acceptance_pending` |
| `acceptance.granted` | `acceptance_pending` to `settlement_pending` |
| `acceptance.rejected` | `acceptance_pending` to `in_performance` |
| `dispute.raised` on an obligation | `exception.opened`, reaching `exception_open` |
| `obligation.released` | None. The aggregate already moved to `terminated`; the release is the obligation-level consequence, not a further aggregate event |
An obligation reaching `disputed` is the object-level fact behind `exception.opened`. The aggregate carries one exception state; an obligation set may carry several disputed obligations at once, and the aggregate leaves `exception_open` only when the events for that transition append.
An illegal transition returns `A202-STATE-TRANSITION-DENIED`, the existing code, rather than a new one. There is one state machine vocabulary and this document joins it.
## 7. Refusal codes
All fail closed. These extend the table in [pilot-transaction-state-machine-v0.1.md](../negotiation/pilot-transaction-state-machine-v0.1.md) section 10.
| Code | Meaning |
|---|---|
| `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 `due_on_discharge` conditions forms a cycle, so no obligation in it can become due |
| `A202-OBLIGATION-SUBJECT-UNREFERENCED` | `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 carries no evidence reference |
| `A202-OBLIGATION-RESPONSE-UNAUTHORIZED` | A response 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 |
Codes reused unchanged from existing specifications, rather than duplicated here: `A202-STATE-TRANSITION-DENIED` for an illegal transition, `A202-SEQUENCE-CONFLICT` for a stream conflict, `A202-EVIDENCE-UNVERIFIED` for absent or failed evidence, `A202-MANDATE-INACTIVE` for an act under an inactive mandate, and `A202-AGREEMENT-HASH-MISMATCH` for an agreement hash that does not match.
## 8. Explicit non-goals
None of the following is specified here, and an implementation is neither conformant nor non-conformant by reference to any of them.
1. **Delivery logistics.** Routing, carriage, handover, custody, and the operational sequence by which performance actually happens.
2. **Quality standards.** What counts as acceptable quality for a given subject. That belongs to the transaction profile, to a referenced external standard, or to the agreement's own terms, and this document only carries the reference.
3. **Tracking.** Holding obligation state, watching due conditions, and computing what is outstanding across a portfolio.
4. **Reminders and notification.** Who is told what, when, and through which channel.
5. **Enforcement.** What happens when an obligation is not performed. This document defines the states a failure produces and the record it leaves. It does not define a remedy, and nothing here makes any obligation enforceable at law.
Each is named because a reader would otherwise reasonably expect to find it in a document about obligations. A non-goal is a statement about this specification's scope, not a claim that the excluded capability is unimportant.
---
Source: https://a202.org/evidence/evidence-verification-v0.1.md
# Evidence verification v0.1
**Status:** Experimental, adopted through proposal [A202-0004](../proposals/A202-0004-evidence-verification.md). Mixed. Sections 2, 3, 4, 5, 6, and 8 are **normative**. Sections 1 and 7 are **informative** and state no requirement on an implementation.
**Date:** 27 July 2026
**Revised:** 30 July 2026, under [A202-0016](../proposals/A202-0016-casing-short-form-and-amendment-corrections.md): section 3 admits the identifier-only short form that the schema, the fixture set, and the runner already carried, marks the field table by form, and states in a new section 3.3 the fail-closed constraints under which the short form resolves. Previously revised 28 July 2026, under [A202-0011](../proposals/A202-0011-registry-and-waiver-corrections.md): section 3.1 now enumerates the registered evidence types, which other documents already cited this section as defining. The closure rule is unchanged and moves to section 3.1.1.
**Scope:** Synthetic pilot transactions only
**Depends on:** [canonical commercial model v0.1](../schemas/canonical-commercial-model-v0.1.md), [commercial mandate v0.1](../authority/commercial-mandate-v0.1.md), [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md), [obligation v0.1](../agreement/obligation-v0.1.md), [determination and dispute v0.1](../disputes/determination-v0.1.md)
## 1. Purpose
Evidence has to outlive any single operator. A record that can only be checked by the party that produced it is not evidence about that party, and a commitment to portability that keeps the checking algorithm private is a commitment nobody can act on.
So verification is public. This document states, as numbered steps, exactly what a third party does to check a bundle of A202 records, using nothing but the bundle, the schemas, the rules versions the bundle references, and the keys it declares. No operator access is required at any step.
Bundle construction is not specified here. How records are gathered, ordered, packaged, held, exported, and disclosed is outside this specification. The asymmetry is deliberate: verification must be public or the record is not portable, and construction need not be for the record to be checkable.
The posture the procedure encodes is that a relying party verifies. It does not trust. Every step either recomputes something or resolves something, and any step that cannot be completed produces a stated gap in the report rather than an assumption in the relying party's favour.
## 2. Conformance language
`MUST`, `MUST NOT`, `REQUIRED`, `SHOULD`, `SHOULD NOT`, and `MAY` are normative within this experimental specification.
An implementation conforms when it:
1. emits evidence references in the shape defined in section 3;
2. produces records against which the procedure in section 4 can be executed in full by a party with no operator access;
3. produces verification reports in the shape defined in section 5;
4. supports selective disclosure under section 6;
5. returns the refusal codes in section 8 for the failures they name;
6. passes the verification fixtures in `../conformance/manifest-v0.1.json`.
A verifier conforms when it executes every step in section 4 in order, and reports what it could not check as not checkable rather than as either verified or failed.
## 3. Evidence reference
An evidence reference is the shape used across the protocol wherever one object points at evidence. Obligations, disputes, determinations, offers, invitations, and mandates all use this shape, so that a verifier resolves one thing rather than a different thing per object family.
A reference takes one of two forms. The **full form** is the object below. The **short form** is the `evidence_id` alone, carried as a bare identifier string, admitted only where section 3.3 admits it and constrained there.
| Field | Type | Rule |
|---|---|---|
| `evidence_id` | `evd_` identifier | REQUIRED in both forms. The `Evidence` object referenced. In the short form it is the whole reference |
| `content_hash` | `sha256Hex` | REQUIRED in the full form. The content hash of the referenced evidence, lowercase hexadecimal SHA-256. Multibase is not accepted in v0.1. Not carried by the short form, which is why section 3.3 constrains where the short form resolves |
| `evidence_type` | registered type identifier | REQUIRED in the full form. From the registered evidence type list. An unregistered value fails closed with `A202-EVIDENCE-TYPE-UNKNOWN`. Not carried by the short form; where the referenced `Evidence` object is co-present the type resolves from that object's own `evidence_type`, and where it is not the reference is not checkable |
| `locator_hint` | string or null | OPTIONAL in the full form. Where a copy may be found. Never load bearing. See section 3.2. Not carried by the short form |
| `signed_by` | `partyRef` | REQUIRED in the full form. The party whose signature covers the referenced evidence. Not carried by the short form; where the referenced `Evidence` object is co-present the signing party resolves from that object's own `signatures`, and where it is not the reference is not checkable |
The full form is the shape an implementation emits unless section 3.3 states otherwise. Every field marked REQUIRED in the full form is required whenever the full form is used: a reference carrying some of them and not others is not a short form, it is an incomplete full form, and it is refused.
### 3.1 The registered evidence types
This table is the registry. Other documents cite this section as the place the list is defined, so the list is written here rather than left to be read out of a schema enum. The registry is closed in v0.1.
| `evidence_type` | What it evidences |
|---|---|
| `attestation` | A statement by a party about a fact it asserts, signed by that party and standing on nothing further |
| `third_party_certificate` | A certificate issued by a party other than the two, such as an accreditation or a conformity certificate |
| `inspection_result` | The recorded outcome of an inspection carried out against a stated method |
| `delivery_confirmation` | Confirmation that a thing arrived, or that a service was rendered, at a stated place and time |
| `measurement_record` | Measured values produced by an instrument or procedure, with the conditions under which they were taken |
| `adapter_receipt` | An `AdapterReceipt` presented as evidence of what an external system reported. It evidences the report and never the authority behind it, per [settlement-handoff-v0.1.md](../fulfillment/settlement-handoff-v0.1.md) section 4.2 |
| `verification_report` | A report produced under section 5 of this document, referenced as evidence in a later dispute or determination |
| `signed_document` | A document signed by one or more parties, where the document itself is the evidence rather than a claim about it |
A type states what kind of thing the evidence is. It states nothing about whether the evidence is true, whether its issuer was authoritative, or whether it verifies: those are the verification result under section 4 and the reporting rules under section 5, and a type that resolves is not thereby a claim that verified.
Adding a member to this registry is a change to this table and requires a proposal under [proposals/README.md](../proposals/README.md). The kernel schema's `evidenceTypeId` enum carries the same eight values and MUST be changed in the same proposal; the two are one registry expressed twice, and a member present in one and absent from the other is a defect in whichever was not updated.
### 3.1.1 The type list is closed
`evidence_type` resolves in the registry in section 3.1. An unregistered type fails closed at validation and independently at verification, matching the treatment of mandate constraint types and transaction profiles: adding a member to a schema enum without a verifier that knows what it means MUST NOT cause the evidence to be treated as verified.
Where a type does not resolve, the reference is not verified and it is not failed either. It is reported as not checkable under section 5, and the relying party decides what to do with a gap it can see.
### 3.2 The locator hint is never load bearing
`locator_hint` may help a verifier find a copy of the evidence. It never establishes anything.
1. Verification MUST NOT depend on `locator_hint` resolving. A bundle whose hints are all stale is still fully verifiable from the bytes it contains.
2. A verifier MUST NOT treat evidence retrieved through a hint as verified because it was retrieved. It is verified because its content hash matches and its signature verifies, and by nothing else.
3. A hint MUST NOT carry a secret, a bearer token, a credential, or personal data. It is an ordinary field of a shared object and the private-data rules of [canonical-commercial-model-v0.1.md](../schemas/canonical-commercial-model-v0.1.md) section 13 apply to it unchanged.
Content addressing is what makes evidence portable. If the location mattered, then the party controlling the location would control the evidence, which is the dependency this whole document exists to remove.
### 3.3 The identifier-only short form
The short form is a bare `evd_` identifier where the full form's object would stand. It carries no content hash, so it establishes less than the full form does, and the rules below are what keep the difference visible rather than letting it pass as equivalent.
1. **It is admitted only where it is already written.** The short form is accepted on `evidence_refs` of an `Offer` and of a `CommercialMandate`, on `assurance_evidence_refs` of an `InvitationAcceptance`, on `identity_evidence_refs` of an `Organization`, and on `authority_evidence_refs` of a `Principal`. These are the members that existed before the reference shape did. Every family defined with the shape requires the full form: `evidence_refs` on a `PerformanceEvent` and on a `Dispute`, and `evidence_relied_on` on a `Determination`. A short form in one of those is refused at validation, on the rule of section 3 that a reference missing a REQUIRED full-form field is an incomplete reference and not a short one.
2. **It resolves only where its target is co-present.** A short-form reference is resolvable only where the referenced `Evidence` object is in the same bundle, or in the same transaction record, as the object carrying the reference. Resolved that way the hash chain is unbroken and nothing is taken on trust: the referencing object's own `content_hash` covers the identifier and is recomputed at step 1, and the referenced `Evidence` object's `content_hash` and `signatures` are checked at steps 1 and 2 like any other object in the set.
3. **An absent target is not checkable, never accepted.** A short-form reference whose target is not co-present MUST be reported as `not_checkable` under section 5, and named as an unresolved reference under section 4 step 7. It MUST NOT be reported as verified on the strength of the identifier, and it is not reported as failed either: the target may exist and simply not have been disclosed, which under section 6 is the normal case. The object carrying the reference is not thereby invalid. What is unestablished is the reference, and the report says so.
4. **A resolution does not travel.** Two sets can carry different `Evidence` objects under one identifier and each verify internally, because nothing in the short form binds bytes. A verifier MUST NOT carry a short-form resolution from one bundle into another, and MUST NOT treat a short-form reference as evidence that any particular bytes were referenced. Only the full form's `content_hash` makes a reference portable, which is why the full form exists.
5. **Nothing new takes the short form.** An object family defined after the reference shape takes the full form, and an implementation SHOULD emit the full form on the members of rule 1 as well. The short form is retained because closing it would invalidate objects that are valid today, which [A202-0004](../proposals/A202-0004-evidence-verification.md) section 4.1 recorded as a MAJOR change deliberately not taken there.
The reason for retaining a weaker form at all is stated rather than left to be inferred. An identifier alone can be pointed at different bytes later, and that is exactly the substitution the content hash removes. What the short form still supports is the check a verifier can actually run on a co-present target, and what it does not support is a claim about bytes nobody holds. Rules 3 and 4 are what keep the second from being read out of the first.
## 4. The verification procedure
A verifier executes the following steps in order, against the bundle it holds. Every step is executable by a third party with the bundle, the referenced schemas, the referenced rule set versions, and the declared keys, and with no access to any operator.
A step that fails produces a finding with the refusal code named in section 8 and the verifier continues to the following steps, so that the report states everything that is wrong rather than only the first thing.
### Step 1: canonicalise and check content hashes
Serialise every object under JSON Canonicalization Scheme, RFC 8785, omitting `content_hash`, `signatures`, and `kernel_annotations` from the bytes hashed, exactly as required by [canonical-commercial-model-v0.1.md](../schemas/canonical-commercial-model-v0.1.md) section 4. Recompute the SHA-256 of those bytes and compare it to the declared `content_hash`.
Check every hash the bundle asserts, not only object content hashes: `terms_hash` on an agreement, `accepted_offer_hash`, `offer_hash` on an acceptance, `assertion_hash` on an obligation response, `invitation_hash` on an acceptance, `subject_hash` on a dispute, `inputs_hash` on a determination, and the `content_hash` inside every evidence reference.
Any mismatch is `A202-EVIDENCE-HASH-MISMATCH`.
### Step 2: verify every signature
For each object, verify every entry in `signatures` against the declared key and the declared purpose.
1. Verify the signature value over the canonical bytes from step 1.
2. Check that `purpose` is the purpose appropriate to the object and the act. A signature valid over the bytes but issued for a different purpose does not count as a signature for this one.
3. Resolve key status at `signed_at` and at verification time, and report both. An expired or revoked key does not erase a signature that was valid when it was created.
4. Check signature count requirements: an agreement carries at least two, an invitation acceptance carries both a claimant `invitation_claim` signature and an operator `object_issuance` signature.
Any failure is `A202-EVIDENCE-SIGNATURE-INVALID`. A key whose status cannot be resolved at either point is reported as not checkable for that point, and the signature is not thereby verified.
### Step 3: check version chains
For every object that carries versions, follow `previous_version_id` from the earliest version to the latest.
1. `version` starts at 1 and increases by 1 with no gaps.
2. Version 1 carries a null `previous_version_id`, and every later version names one.
3. No two objects name the same `previous_version_id`. Two successors to one version is a fork, and a fork means two objects claim to be current.
The same rule applies to determination supersession chains under [determination-v0.1.md](../disputes/determination-v0.1.md) section 5: linear, no gaps, no forks.
Any gap or fork is `A202-EVIDENCE-CHAIN-GAP`.
### Step 4: check per-stream sequence continuity
For each stream disclosed to the verifier, check continuity within that stream and only within it.
1. `sequence` increases by 1 from the stream's first disclosed event.
2. `previous_event_hash` on each event equals the `content_hash` of the preceding event **in the same stream**.
3. The first disclosed event of a stream either carries a null `previous_event_hash`, meaning the stream is disclosed from its start, or names a predecessor the verifier does not hold, which is reported as a disclosed boundary rather than as a gap.
Sequence numbers are per stream. A verifier MUST NOT expect continuity across streams, and MUST NOT infer anything from the sequence numbers of one stream about the activity on another. Cross-stream ordering from `kernel_annotations.received_at` is presentation only and is never used to authorise anything.
A discontinuity inside a disclosed stream is `A202-EVIDENCE-CHAIN-GAP`. A stream that was not disclosed is not a gap; it is out of scope for this report and is stated as such under section 6.
### Step 5: replay guarded transitions
Replay the disclosed events in sequence order, applying session events then aggregate events, against the state machines in [pilot-transaction-state-machine-v0.1.md](../negotiation/pilot-transaction-state-machine-v0.1.md) and, for obligations, against the transition table in [obligation-v0.1.md](../agreement/obligation-v0.1.md) section 6.1.
For every transition, check all of:
1. the transition is legal from the `from_state` the record carries;
2. the guard stated for that transition held, against the rules version in force at the time the event appended, not against the current one;
3. the actor was authorised: the mandate referenced by the event resolves, covered the action, and was active at append time;
4. the referenced `PolicyDecision` resolves and is bound to the event's `action_hash`;
5. the recomputed `to_state` equals the recorded one.
An illegal or unguarded transition is `A202-EVIDENCE-TRANSITION-ILLEGAL`. This is the replay-time counterpart of `A202-STATE-TRANSITION-DENIED`, which is what a live implementation returns when it refuses the transition in the first place. Both exist because a transition can be refused at the time or discovered afterwards, and the two are different findings.
### Step 6: check every determination
For every determination in the bundle, check that its stated outcome follows from its referenced rules and inputs, under [determination-v0.1.md](../disputes/determination-v0.1.md) section 4.3.
1. `question.rules_ref` resolves to an exact rule set version, and it is the version in force at the time of the subject act.
2. Every entry in `outcome.rules_applied` resolves inside that version.
3. `outcome.inputs_hash` recomputes over the canonically ordered input set.
4. Every entry in `outcome.evidence_relied_on` resolves and verifies under steps 1 to 3 of this procedure.
5. The stated `finding` follows from the rules applied and the evidence relied on.
6. `effect` does not exceed what the referenced rules granted, under section 6 of that document.
A determination whose outcome does not follow is `A202-DETERMINATION-NOT-FOLLOWING`. One whose effect exceeds its grant is `A202-DETERMINATION-EFFECT-OVERCLAIM`.
Where the rule set version does not resolve, the determination is reported as not checkable, never as verified. An unresolvable rule set is the case where a verifier most wants to conclude something, and it is exactly the case where it may not.
### Step 7: report what could not be checked
State every gap explicitly.
1. **Undisclosed streams.** Name each stream the bundle references but does not contain.
2. **Unresolved references.** Name each evidence reference, rule set version, mandate, policy decision, or key status that did not resolve.
3. **Unreferenced evidence.** Name each evidence object present in the bundle that nothing in the bundle references. Unreferenced evidence is not a failure and is not support for anything either.
4. **Unregistered types.** Name each evidence type, condition type, constraint type, or profile that did not resolve in its registry.
Every one of these is reported as not checkable. None of them is reported as failed, and none of them is reported as verified.
The distinction is the point of the step. Reporting a gap as a failure makes a partial disclosure look like misconduct, and partial disclosure is the normal case. Reporting a gap as a pass makes an absence look like evidence, which is the failure mode that assurance reporting exists to prevent everywhere else in this specification set. Absence of a check reads as unchecked, exactly as absence of an assessment reads as unassessed.
## 5. Verification output
A verification report distinguishes three outcomes and never collapses them.
| Outcome | Meaning |
|---|---|
| `verified` | The check was executed and passed |
| `failed` | The check was executed and did not pass |
| `not_checkable` | The check could not be executed with what the verifier holds |
Rules:
1. A report MUST carry a per-check result, not only an overall one. The three outcomes MUST NOT be reduced to a boolean at any point in the report.
2. A report MUST state its scope: which streams, which objects, and which time range it covers. See section 6.
3. A report MUST name each refusal code from section 8 that was raised, together with the object it was raised against.
4. `not_checkable` MUST NOT be presented, coloured, summarised, or aggregated as either of the other two. A consumer that treats not checkable as verified has misconfigured its own bar, and the report MUST NOT make that reading easy.
5. A report is a statement by whoever produced it. It is not itself a determination, and it carries no effect under [determination-v0.1.md](../disputes/determination-v0.1.md) section 6. A report may of course be evidence in a dispute, referenced like any other evidence.
A report MUST NOT be reported as passing where any check returned `failed`. It MAY be reported as passing in scope where checks returned `verified` and `not_checkable`, provided the not checkable set is stated in the same report at the same prominence.
## 6. Selective disclosure
A bundle may disclose a subset of the streams and objects that exist. Verification of the subset MUST be possible without the remainder.
1. **The subset verifies on its own.** Every check in section 4 that can be executed against the disclosed subset MUST be executable without any undisclosed object. An implementation MUST NOT produce records whose verification requires an object it does not disclose to that verifier.
2. **The report states scope.** A report over a subset MUST state which streams and objects were in scope, and MUST list the undisclosed streams the bundle references. A report that does not state its scope is a report whose silence is indistinguishable from completeness.
3. **A boundary is not a gap.** A disclosed stream that begins mid stream, with a `previous_event_hash` naming an event the verifier does not hold, is a stated boundary. It is reported under section 4 step 7 and is not `A202-EVIDENCE-CHAIN-GAP`.
4. **Disclosure reveals nothing about what was withheld.** The number, size, sequence range, or timing of undisclosed streams MUST NOT be inferable from the disclosed subset beyond the fact that a named reference exists. This follows the same rule as the per-stream sequencing in the state machine: a counter that moves because of something a party may not see is a channel through which that party learns it anyway.
5. **Selective disclosure never weakens a check.** A check executed over a subset is executed in full or it is reported as not checkable. There is no reduced form of a check for a reduced bundle.
## 7. Human oversight evidence
This section is informative. It states no requirement on an implementation, and it is not a legal compliance assessment.
Human oversight of an agent acting commercially is evidenced by objects this specification set already defines. Nothing here adds an object; the point is to say which existing object carries which fact, so that a party building an oversight record knows what to export rather than inventing a parallel log.
### 7.1 Where oversight appears in the objects
- **The mandate carries the oversight design.** `approval_rules` state the conditions under which a named human or role must approve, and `actions`, `scope`, and `constraints` state the boundary inside which the agent may act at all. Both are set before the agent acts, by a principal, and both are part of the signed mandate.
- **The pause is a state.** A session reaching `paused_for_approval` is the recorded fact that an action was held for a named approver, and `approval.granted` or `approval.rejected` is the recorded fact of what that approver did.
- **The approval binds exact bytes.** An `Approval` binds one action hash, one transaction, an approver identity and role, a decision, a created and an expiry time, and the approver's signature. Changing one byte of the action invalidates it, and it cannot be reused across actions or transactions. Under operator key custody, every act requires such an approval from a named principal of the acting organisation.
- **The contest is recorded.** A determination records what was determined, on which rules, on which evidence, and by whom. An appeal produces a superseding determination and both remain in the record permanently.
### 7.2 Mapping to the oversight concerns of EU AI Act Article 14
Each row names an oversight concern and the object in this specification set that evidences it. The mapping is informative. It is not a legal compliance assessment, it is not advice, and it does not state that any implementation satisfies any obligation. Whether Article 14 applies to a given deployment, and what it requires of it, is a question for the deployer and its advisers.
| Oversight concern | Objects that evidence it | Where specified |
|---|---|---|
| Oversight is assigned to identified natural persons before the system is used | `approval_rules` on the `CommercialMandate`, naming the approver organisation and role; the `Principal` that issued the mandate | [commercial-mandate-v0.1.md](../authority/commercial-mandate-v0.1.md) sections 3 and 8 |
| Oversight measures are matched to the system's autonomy and to its context of use | Mandate `actions`, `scope`, and `constraints`, with monotonic narrowing along the delegation chain; per-act approval where the acting key is operator custodied | [commercial-mandate-v0.1.md](../authority/commercial-mandate-v0.1.md) sections 3.1, 4, 7, and 11.3 |
| The conduct of the system can be monitored while it operates | Per-stream signed event records with guarded transitions; one `PolicyDecision` bound to each `action_hash`, recording allow, deny, or approval required | [pilot-transaction-state-machine-v0.1.md](../negotiation/pilot-transaction-state-machine-v0.1.md) sections 5 and 8; [canonical-commercial-model-v0.1.md](../schemas/canonical-commercial-model-v0.1.md) section 9 |
| Intervention is possible and, when it happens, it is recorded | `paused_for_approval` with `approval.granted` and `approval.rejected`; the `Approval` object bound to one action hash; `RevocationRecord` for suspension and revocation; `determination` and appeal records | [pilot-transaction-state-machine-v0.1.md](../negotiation/pilot-transaction-state-machine-v0.1.md) section 6.1; [commercial-mandate-v0.1.md](../authority/commercial-mandate-v0.1.md) sections 8 and 9; [determination-v0.1.md](../disputes/determination-v0.1.md) sections 4 and 7 |
Two limits of the mapping are worth stating plainly. The objects evidence that an oversight design existed and that particular interventions happened. They do not evidence that the design was adequate, that the person named was competent to exercise it, or that the intervention was correct. And an object exists because a party created it: a record showing no interventions is consistent with an agent that needed none and with an oversight arrangement nobody used.
## 8. Refusal codes
All fail closed. These extend the table in [pilot-transaction-state-machine-v0.1.md](../negotiation/pilot-transaction-state-machine-v0.1.md) section 10.
| Code | Meaning |
|---|---|
| `A202-EVIDENCE-HASH-MISMATCH` | A recomputed hash differs from the declared one, at step 1 |
| `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, at step 2 |
| `A202-EVIDENCE-CHAIN-GAP` | A version chain or a disclosed stream has a gap, or a chain forks, at steps 3 and 4 |
| `A202-EVIDENCE-TRANSITION-ILLEGAL` | A replayed transition was not legal, its guard did not hold, or its actor was not authorised, at step 5 |
| `A202-EVIDENCE-TYPE-UNKNOWN` | An evidence reference names a type that does not resolve in the registered list, at step 1 and at step 7 |
| `A202-DETERMINATION-NOT-FOLLOWING` | A determination's stated outcome does not follow from its referenced rules and inputs, at step 6 |
| `A202-EVIDENCE-REPORT-INVALID` | A verification report states no scope, collapses `not_checkable` into `verified` or `failed`, or reduces the per-check results to a boolean, against section 5 |
| `A202-EVIDENCE-DISCLOSURE-INCOMPLETE` | A disclosed subset cannot be verified without an object that was not disclosed to that verifier, against section 6 rule 1 |
`A202-DETERMINATION-NOT-FOLLOWING` and `A202-DETERMINATION-EFFECT-OVERCLAIM` are defined in [determination-v0.1.md](../disputes/determination-v0.1.md) section 9 and are reused here unchanged rather than duplicated under a verification-specific name.
A refusal code in a verification report names a check that was executed and failed. A check that could not be executed produces a `not_checkable` result under section 5 and no refusal code.
---
Source: https://a202.org/disputes/determination-v0.1.md
# Determination and dispute v0.1
**Status:** Experimental, adopted through proposal [A202-0003](../proposals/A202-0003-determination.md). Mixed. Sections 2, 3, 4, 5, 6, 7, and 9 are **normative**. Sections 1 and 8 are **informative** and state no requirement on an implementation.
**Date:** 27 July 2026
**Scope:** Synthetic pilot transactions only
**Depends on:** [canonical commercial model v0.1](../schemas/canonical-commercial-model-v0.1.md), [commercial mandate v0.1](../authority/commercial-mandate-v0.1.md), [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md), [obligation v0.1](../agreement/obligation-v0.1.md), [evidence verification v0.1](../evidence/evidence-verification-v0.1.md), [conformance grades v0.1](../conformance/conformance-grades-v0.1.md)
## 1. Purpose
Two parties transacting through agents will eventually disagree about whether an act was permitted, whether an obligation was performed, or whether a refusal was correct. A specification that defines authority, state, and evidence, and then says nothing about how a disagreement is raised or what a ruling on it looks like, leaves the most consequential object in the system undefined.
This document defines that object and the family around it: how a dispute is raised, what a determination is, how a later determination replaces an earlier one, what effect a determination has, and how it is appealed.
It specifies shapes and interfaces. It does not specify how any operator staffs, schedules, prices, or processes a dispute, how a determiner reaches a conclusion, or what a determiner should conclude on any question. Those are outside this specification and are not published here. The right to raise a dispute and the right to appeal are part of the protocol. What happens inside the room where the question is answered is not.
Two properties follow from the rest of the specification set rather than from anything new here. A determination is replayable, because everything it relies on is hash addressed and signed. A determination never claims more effect than the parties agreed in advance, because the effect is read from the rules in force and not from the determiner.
## 2. Conformance language
`MUST`, `MUST NOT`, `REQUIRED`, `SHOULD`, `SHOULD NOT`, and `MAY` are normative within this experimental specification.
An implementation conforms when it:
1. validates `dispute` and `determination` objects against `../schemas/v0.1/commercial-kernel.schema.json`;
2. enforces the raising rules in section 3;
3. enforces the supersession rules in section 5, including the absence of any deletion operation;
4. enforces the effect rules in section 6;
5. enforces the appeal rules in section 7;
6. returns the refusal codes in section 9 for the failures they name;
7. passes the dispute and determination fixtures in `../conformance/manifest-v0.1.json`.
## 3. Part 1: raising
### 3.1 Who may raise
Any party to the transaction may raise a dispute. This includes raising one against the party operating the venue in which the transaction is conducted.
An implementation MUST NOT restrict the right to raise by reference to a party's role, its assurance level, its conformance grade, or whether it prevailed on any earlier question. A right to contest that is available only to some participants is not a right to contest.
The raising party MUST hold a mandate whose `actions` include `dispute.raise` and whose `scope` covers the transaction. A dispute is a commercial act and is authorised on the ordinary path, through a signed `ActionEnvelope` evaluated against the mandate. There is no separate disputing credential.
### 3.2 What may be raised against
A dispute MUST reference exactly one subject, by content hash, from this closed list.
| `subject_type` | What it references |
|---|---|
| `act` | A signed act, referenced by the `content_hash` of the object that carried it |
| `determination` | An earlier determination, referenced by its `content_hash`. This is the appeal path in section 7 |
| `obligation_state` | An obligation together with the state it stood in, referenced by the `content_hash` of the object that placed it there |
| `refusal` | A refusal, referenced by the `content_hash` of the `PolicyDecision` that carried it |
The reference is REQUIRED and is a content hash, never a bare identifier. A dispute that names no resolvable subject, or that names one by identifier alone, is refused with `A202-DISPUTE-SUBJECT-UNREFERENCED`. A dispute about "the delivery" is not a dispute anyone can determine; a dispute about the exact bytes of a named act is.
### 3.3 Within what window
The window is set by the rules in force on the transaction at the time the subject act occurred. This document references that window and does not restate it, because a window restated in two places is a window with two values.
The dispute object carries `rules_ref`, which names the rule set and version the window is read from. A dispute raised outside the window resolved through `rules_ref` is refused with `A202-DISPUTE-OUT-OF-WINDOW`. A `rules_ref` that does not resolve is refused with the same code, because a window that cannot be resolved cannot be shown to have been met, and unavailability is not permission.
### 3.4 What a dispute carries
| Field | Type | Rule |
|---|---|---|
| `raising_party` | `partyRef` | REQUIRED |
| `respondent_party` | `partyRef` | REQUIRED. The party the dispute is against, which MAY be the venue operator |
| `subject_type` | enum | REQUIRED. From the list in section 3.2 |
| `subject_hash` | `sha256Hex` | REQUIRED. The content hash of the referenced subject |
| `subject_id` | identifier | REQUIRED. The identifier of the referenced object, present for resolution and never load bearing on its own |
| `grounds` | array of enum, at least one | REQUIRED. From the list in section 3.5 |
| `description` | string, bounded, marked untrusted | OPTIONAL free text. See section 3.6 |
| `evidence_refs` | array of evidence references | REQUIRED, at least one. In the shape defined in [evidence-verification-v0.1.md](../evidence/evidence-verification-v0.1.md) section 3 |
| `rules_ref` | `rulesRef` | REQUIRED. The rule set and version in force, from which the window and the available effects are read |
### 3.5 Grounds
The list is closed in v0.1. An unregistered ground is refused with `A202-DISPUTE-GROUNDS-UNKNOWN`.
| Ground | Meaning |
|---|---|
| `authority_absent` | The act was taken without a mandate covering it |
| `authority_exceeded` | The act was outside the constraints of a mandate that otherwise covered it |
| `state_transition_invalid` | The transition was not legal from the state that stood at the time |
| `obligation_not_performed` | An obligation reached its due condition and was not performed |
| `obligation_wrongly_rejected` | An assertion of performance was rejected on grounds the record does not support |
| `evidence_unverified` | Evidence relied on does not verify |
| `disclosure_breach` | An act disclosed what the rules in force did not permit it to disclose |
| `refusal_incorrect` | A refusal was returned where the rules in force required the act to be permitted |
| `rule_misapplied` | The rules in force were applied incorrectly to the subject |
### 3.6 Free text is data
`description` is untrusted free text directed at a counterparty's agent. It is bounded in length, it is rendered as data, and it MUST NOT be interpreted as an instruction by any component that reads the dispute. Nothing in the protocol reads a meaning out of it: every element a determination relies on appears in `grounds`, `subject_hash`, `evidence_refs`, and `rules_ref`.
This is the same treatment the `purpose_note` field receives on an invitation, and for the same reason. A free-text field that is allowed to carry commercial meaning is a field through which anything can travel.
### 3.7 Raising suspends nothing
Raising a dispute does not by itself suspend, reverse, or pause the referenced act. The act stands until a determination with an effect sufficient to move it says otherwise.
If raising suspended the subject, raising would become a tactic: any party could halt a counterparty's act at no cost by contesting it. The remedy for an act that should not stand is a determination, not the act of complaining about one.
An implementation MAY record that a dispute is open against a subject, and that record is visible to the parties to the dispute. Recording it MUST NOT alter the subject's state.
## 4. Part 2: the determination object
A determination is a signed shared kernel object carrying the common envelope, the `det_` identifier prefix, and a non-null `transaction_id`.
| Field | Type | Rule |
|---|---|---|
| `dispute_id` | `dsp_` identifier | REQUIRED. The dispute determined |
| `question` | `questionRef` | REQUIRED. What was determined. See section 4.1 |
| `outcome` | `outcome` | REQUIRED. A reasoned finding. See section 4.2 |
| `determiner` | `partyRef` | REQUIRED. Who determined it |
| `authority_basis_ref` | reference | REQUIRED. The basis on which the determiner had authority to determine this question, resolvable to the rules in force |
| `effect` | enum | REQUIRED. `advisory`, `presumptive`, or `binding`. See section 6 |
| `supersedes` | `supersession` or null | REQUIRED and non-null where this determination replaces an earlier one. See section 5 |
| `appeal_route_ref` | reference | REQUIRED. The appeal route available against this determination, resolved from the rules in force |
| `determined_at` | RFC 3339 timestamp | REQUIRED. UTC |
| `effective_from` | RFC 3339 timestamp | REQUIRED. UTC. At or after `determined_at` |
### 4.1 The question
| Field | Rule |
|---|---|
| `subject_type`, `subject_hash`, `subject_id` | REQUIRED. Copied from the dispute, and MUST equal the dispute's values |
| `rules_ref` | REQUIRED. The rule set and version in force **at the time of the subject act**, not at the time of the determination |
A determination that names a different subject hash from its dispute is not a determination on that dispute and is refused with `A202-DISPUTE-SUBJECT-UNREFERENCED`.
`rules_ref` resolving to the version in force at the time of the subject act is what makes a determination stable. If a later rule version governed, then changing the rules would change the answer to a question that was already asked, and no record would be reliable for longer than the rules it was decided under.
### 4.2 The outcome is a reasoned finding
`outcome` is not an enumerated verdict. There is no three-value result, and an implementation MUST NOT reduce a determination to one.
| Field | Type | Rule |
|---|---|---|
| `finding` | string, bounded | REQUIRED. The finding on the question, stated in terms of the referenced rules |
| `rules_applied` | array, at least one | REQUIRED. Each entry names a rule inside `rules_ref` by its stable rule identifier |
| `evidence_relied_on` | array of evidence references, at least one | REQUIRED. Each entry is an evidence reference carried by the dispute, by the referenced act, or by the record the determiner replayed |
| `inputs_hash` | `sha256Hex` | REQUIRED. The canonical hash over the ordered set of inputs the determination was taken against |
| `state_result` | closed object or null | The determined subject's identifier and one registered state token, where the effect permits a state change. Null otherwise. A subject other than the determined one, an unregistered state, or a `state_result` under a non-binding effect is refused |
An enumerated verdict says which way a question went and nothing about why, which makes it unappealable in any meaningful sense: there is nothing to point at and say it was applied wrongly. A finding that names the rules applied and the evidence relied on can be checked, and a determination that cannot be checked is an assertion wearing a determination's shape.
`state_result` is the only field through which a determination touches state, and it is inert unless section 6 permits it.
### 4.3 A determination is replayable
A third party holding `rules_ref`, the inputs covered by `inputs_hash`, and the evidence named in `evidence_relied_on` MUST be able to check that the stated `finding` follows from them.
This is a property of the object, not a request for good faith. It requires all four of the following, and an implementation MUST satisfy each.
1. `rules_ref` resolves to an exact, hash-addressed rule set version.
2. `inputs_hash` covers a canonically ordered input set, serialised under JCS as required by [canonical-commercial-model-v0.1.md](../schemas/canonical-commercial-model-v0.1.md) section 4.
3. Every entry in `rules_applied` resolves inside the referenced rule set version.
4. Every entry in `evidence_relied_on` resolves and verifies under [evidence-verification-v0.1.md](../evidence/evidence-verification-v0.1.md) section 4.
A determination whose stated outcome does not follow from its referenced rules and inputs is reported by a verifier as `A202-DETERMINATION-NOT-FOLLOWING`. The check is the one at step 6 of the verification procedure, and it is the check that distinguishes a determination from an announcement.
No model output may serve as the deterministic authority for a finding. Where an implementation uses one, its output is an input recorded under `inputs_hash` like any other, and the finding still has to follow from the rules and the evidence.
## 5. Part 3: supersession
A determination is never edited and never removed. A later determination on the same question replaces an earlier one by reference, and both remain in evidence permanently.
`supersedes` carries:
| Field | Rule |
|---|---|
| `determination_id` | REQUIRED. The `det_` identifier of the determination replaced |
| `determination_hash` | REQUIRED. The `content_hash` of the determination replaced |
| `reason` | REQUIRED. One of `appeal_outcome`, `corrected_input`, `rule_misapplication` |
Rules:
1. A superseding determination MUST name the same `question.subject_hash` as the determination it supersedes. A determination on a different question is a separate determination, not a replacement.
2. A superseding determination MUST state `reason`. A replacement with no stated reason is refused with `A202-DETERMINATION-SUPERSESSION-UNREASONED`. A record that changes for reasons it does not give is a record whose changes cannot be audited.
3. A determination MUST NOT supersede one that has already been superseded. The chain is linear, and a fork means two determinations claim to be current on one question. This is refused with `A202-DETERMINATION-SUPERSESSION-FORKED`, and it is the same no-forks rule the version chain check enforces at step 3 of the verification procedure.
4. Both determinations remain in the evidence record, retrievable, and verifiable. The superseded one is marked as superseded by the existence of the later one's reference, not by a mutation of its own bytes.
5. **Deletion is not a defined operation.** There is no protocol operation that removes a determination, and an implementation MUST NOT provide one. A record that can be removed when it is unfavourable is a record nobody has a reason to read.
Section 5 rule 5 and the retention practices of any particular operator are different questions. This specification defines no deletion operation. It states nothing about how long any operator retains anything.
## 6. Part 4: binding effect
A determination's effect is exactly what the parties' rules in force agreed in advance. It is never more.
| `effect` | What it means |
|---|---|
| `advisory` | The determination is a finding. It moves no state and binds no party. Each party's own process decides what to do with it |
| `presumptive` | The determination is presumptive evidence of the finding on the question. It moves no state by itself. A party asserting a different account of the same facts carries the burden of displacing it |
| `binding` | The determination is binding on the parties on the stated question. Where `state_result` is present, the subject moves to the stated state |
Three rules hold.
1. **The effect is read from the rules, not from the determiner.** The determination carries an `effect` claim, and that claim MUST NOT exceed what the rule set resolved through `question.rules_ref` granted for this question class and these parties. A determination claiming `binding` where the rules granted `presumptive`, or claiming any effect where the rules granted none, is refused with `A202-DETERMINATION-EFFECT-OVERCLAIM`.
2. **The check is independent of the claim.** A relying party MUST evaluate the effect against the referenced rules rather than accept the carried value. Both layers fail closed on their own: an implementation that widened the carried enum without the rules granting it, and one that read the rules without checking the carried claim, must each refuse.
3. **Absence is not permission.** Where `question.rules_ref` does not resolve, or resolves to a rule set that states no effect for this question class, the effect is `advisory`. It is never inferred upward.
Where the effect is `binding` and `state_result` is present, the state change is subject to the guards of the state machine it targets. A determination cannot produce a transition that would be illegal for any other event. This is why the obligation transition table admits `determination.recorded` under a guard rather than as an unconditional move.
**Informative note on legal effect.** Nothing in this specification makes any determination enforceable at law. `binding` is a statement about what the parties' rules in force said, recorded in a form that can be verified afterwards. Whether an agreement to be bound is enforceable, in which jurisdiction, between which parties, and on which questions, is a matter of the law applicable to those parties and their contract. This specification defines an object and a check. It does not create an obligation to comply with one, and no material may describe it as doing so.
## 7. Part 5: appeal route
An appeal is a dispute whose `subject_type` is `determination`. It runs on the same path, with the additional rules in this section.
### 7.1 Grounds
An appeal MUST state at least one of the following. The list is closed.
| Ground | Meaning |
|---|---|
| `rule_misapplied` | The rules in force were applied incorrectly to the subject |
| `input_did_not_exercise_invariant` | An input the determination relied on does not exercise the invariant the finding attributes to it |
| `wrong_scope` | The determination reached beyond the question that was raised |
| `wrong_rule_version` | The determination applied a rule set version other than the one in force at the time of the subject act |
**Disagreement with the rule itself is not a ground of appeal.** A party that thinks the rule is wrong is asking for the rule to change, and that is a change proposal against this specification set under [proposals/README.md](../proposals/README.md), handled through the proposal process. An appeal on that basis is refused with `A202-APPEAL-GROUNDS-UNKNOWN`.
The distinction is load bearing. An appeal route that accepts "the rule produced an outcome I dislike" is a route through which every determination is relitigated, and a rule that can be set aside case by case is not a rule.
### 7.2 Preconditions
An appeal MUST satisfy all of the following, and each failure fails closed.
1. It references the determination by `content_hash`, not by identifier alone.
2. It is raised inside the appeal window resolved through the determination's `appeal_route_ref`. Outside it, `A202-DISPUTE-OUT-OF-WINDOW`.
3. It is raised by a party to the transaction, on the terms in section 3.1.
4. The referenced determination has not already been superseded. An appeal against a superseded determination is refused with `A202-DETERMINATION-SUPERSESSION-FORKED`, because determining it would fork the chain.
5. It states at least one ground from section 7.1.
### 7.3 Effect on status while an appeal is open
While an appeal is open, the prior determination stands. It is neither raised nor suspended by the act of appealing.
Both alternatives are worse. If appealing suspended the determination, appealing would be a way of setting aside any unfavourable outcome for the duration. If appealing raised its standing, appealing would be a way of strengthening what it contests. Either would make appealing a tactic rather than a remedy, which is exactly the failure the right to appeal exists to prevent.
Where the subject carries a status field, that status reflects that an appeal is open. A conformance grade's `status` becomes `under_appeal` under [conformance-grades-v0.1.md](../conformance/conformance-grades-v0.1.md) section 6, and the bands it carries are unchanged while it is there.
### 7.4 What an appeal produces
An appeal produces a new determination. That determination supersedes the appealed one under section 5, with `supersedes.reason` set to `appeal_outcome`, and both remain in the evidence record permanently.
An appeal that upholds the original still produces a determination. There is no null outcome, because "the appeal was heard and the original stands" is a finding that a third party has to be able to verify as much as any other.
### 7.5 What is not specified here
The right to raise an appeal is part of the protocol. How any operator staffs an appeal, what it charges for one, how it schedules or sequences one, who reviews it, and what internal procedure it follows are outside this specification and are not published here.
## 8. Certification note
A conformance grade determination is a determination in this family. The `determination_ref` field on the grade object defined in [conformance-grades-v0.1.md](../conformance/conformance-grades-v0.1.md) section 4 references a `determination` object of the shape in section 4 above, and the appeal in section 6 of that document is an appeal under section 7 above.
The consequence is that a grade is contestable in exactly the way any other determination is contestable, by the same parties, on the same grounds, through the same route, producing the same superseding record. Nothing about a grade makes it a different kind of statement.
The four grounds line up directly. Ground 4 of the grade appeal, "the specification version was wrong", is the `wrong_rule_version` ground in section 7.1, where the rule set in question is a version of this specification set. The other three carry the same names.
This section is informative. The normative rules are in sections 3 to 7 and in the conformance grades document, and neither restates the other.
## 9. Refusal codes
All fail closed. These extend the table in [pilot-transaction-state-machine-v0.1.md](../negotiation/pilot-transaction-state-machine-v0.1.md) section 10.
| Code | Meaning |
|---|---|
| `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 that has already been 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-DETERMINATION-NOT-FOLLOWING` is returned by a verifier executing step 6 of [evidence-verification-v0.1.md](../evidence/evidence-verification-v0.1.md) section 4, and by an implementation that refuses to record a determination it cannot itself replay. Both layers are required.
---
Source: https://a202.org/fulfillment/settlement-handoff-v0.1.md
# Settlement handoff v0.1
**Status:** Experimental, adopted through proposal [A202-0005](../proposals/A202-0005-settlement-handoff.md). Mixed. Sections 2, 3, 4, 5, 6, and 7 are **normative**. Sections 1, 8, 9, and 10 are **informative** and state no requirement on an implementation. Sections 8, 9, and 10 are annexes describing external payment rails; they are descriptive and impose no requirement on any external protocol.
**Date:** 27 July 2026
**Revised:** 30 July 2026, under [A202-0016](../proposals/A202-0016-casing-short-form-and-amendment-corrections.md): section 2.1 names the registered enum value `settlement_instruction` where it previously named the object kind, which no object could carry and pass schema validation.
**Scope:** Synthetic pilot settlement handoff only
**Relates to:** [canonical commercial model v0.1](../schemas/canonical-commercial-model-v0.1.md), [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md), [commercial mandate v0.1](../authority/commercial-mandate-v0.1.md), [CHARTER.md](../CHARTER.md)
## 1. Purpose
A202 does not execute payments. It states what has been agreed, what is owed, to whom, and when settlement is triggered. Everything after the trigger belongs to a settlement rail.
The line is drawn there because the two sides answer different questions and fail in different ways. A rail answers whether value moved: whether an account was debited, whether a transfer cleared, whether a card authorization held. A202 answers whether value was owed: whether a mandate permitted the commitment, whether the obligation was accepted by a party with authority to accept it, and whether the acceptance can be reconstructed a year later by someone who was not there. A rail cannot answer the second question, because it never saw the negotiation. A202 must not attempt the first, because it holds no funds and settles nothing.
This document specifies the handoff between them: one object that states what a rail is being asked to do, one rule for what may trigger it, one rule for what comes back, and one rule that stops a retry becoming a second payment. The interface is rail-neutral by construction. A rail appears in it only as an opaque registered identifier, and no field in the object is meaningful to one rail and meaningless to another.
The charter records settlement orchestration as a non-goal. This document is not a retreat from that. Moving money, routing payments, choosing a rail, and reconciling ledgers remain outside the specification. What is specified here is the boundary object, which has to exist precisely because the orchestration does not.
## 2. Settlement instruction
`SettlementInstruction` is listed in [canonical commercial model v0.1](../schemas/canonical-commercial-model-v0.1.md) section 5.5 as an authorized object whose status derives from receipts. This section states its shape.
### 2.1 Envelope
A `SettlementInstruction` is a shared object and MUST carry the common envelope defined in [canonical commercial model v0.1](../schemas/canonical-commercial-model-v0.1.md) section 3, with `object_type` set to `settlement_instruction`. Its identifier prefix is `stl_`, allocated by [A202-0005](../proposals/A202-0005-settlement-handoff.md), which carries the corresponding row into the kernel prefix table.
Two spellings of one thing appear in this document and they are not interchangeable. `SettlementInstruction` names the object kind, which is how the object inventory and the annexes below refer to it. `settlement_instruction` is the registered member of the kernel schema's `object_type` enum, which is the value an instance carries in its envelope. Where a rule states what an object carries, the enum value is what it states.
The envelope's `id` is the instruction identifier referred to throughout this document. The envelope's `transaction_id` is the transaction the instruction belongs to and MUST NOT be null.
### 2.2 Payload
The payload MUST carry every field below and MUST NOT carry any field not listed. The shape is closed for the same reason the session event shapes are closed: an open payload is a channel through which rail-specific detail, and eventually private commercial strategy, arrives inside a shared object.
| Field | Type | Rule |
|---|---|---|
| `obligation_id` | string | The obligation being settled. MUST resolve within this transaction |
| `agreement_id` | string | The agreement the obligation derives from. MUST resolve within this transaction |
| `amount` | money | The A202 money type defined in [canonical commercial model v0.1](../schemas/canonical-commercial-model-v0.1.md) section 7: an ISO 4217 alphabetic `currency` and a non-negative base-10 string `amount` |
| `payer_organization_id` | string | The organization that owes |
| `payee_organization_id` | string | The organization that is owed. MUST differ from `payer_organization_id` |
| `trigger` | object | The trigger reference defined in section 3.2 |
| `rail` | string | An opaque registered rail identifier, governed by section 2.3 |
| `idempotency_key` | string | Governed by section 5 |
Every reference is an identifier. The instruction names the obligation and the agreement; it does not restate their terms. A rail adapter that needs the terms resolves the named objects and verifies them, which is a different act from being told them by the instruction and a materially safer one.
`amount` is expressed in the A202 money type and inherits its rules unchanged, including that it is a base-10 string rather than a binary floating-point number and that it is non-negative. A refund, a credit, or a downward adjustment is a directed adjustment object with its own type. A settlement instruction MUST NOT express one as a negative amount, because a sign error in a settlement instruction pays the wrong party.
### 2.3 The rail identifier is opaque and registered
`rail` is a hint to the adapter layer about which settlement rail is expected. It is opaque to the commercial layer: no rule in this specification varies on its value, no invariant depends on it, and no schema field exists for one rail and not another.
An implementation MUST reject an instruction whose `rail` value does not resolve in its registered rail set, with `A202-SETTLEMENT-RAIL-UNKNOWN`. An unregistered rail fails closed. It MUST NOT be passed through to a default adapter, MUST NOT be treated as a free-text routing hint, and MUST NOT be ignored so that the instruction proceeds without a rail.
The reason is the reason given for unregistered constraint types and unresolvable transaction profiles: an unrecognised value that is permitted to pass is a value whose meaning was never checked, and here the unchecked value selects who receives money.
## 3. Trigger condition
### 3.1 What may trigger settlement
Settlement is triggered by an explicit commercial transition. In the normal case that transition is acceptance of the obligation, which is the `acceptance.granted` event that moves the transaction aggregate to `settlement_pending` in [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md) section 5.
A settlement MUST NOT be triggered by a carrier event. A delivered message, a completed request, a closed connection, a successful callback, and a task reaching a terminal state are all carrier facts and none of them is a commercial transition.
A settlement MUST NOT be triggered by time alone, unless the agreement's own terms stated a time-based condition in a form that parses deterministically. A term such as a named payment date under a named business calendar is such a condition. An implementation's scheduler firing is not, and neither is an operator's belief that enough time has passed.
The distinction is that a parseable condition in the agreement was agreed by both parties and can be re-evaluated identically by anyone replaying the record, whereas an elapsed timer exists only inside one party's infrastructure and cannot be re-evaluated by the other at all.
### 3.2 The trigger reference
The `trigger` object MUST carry both fields:
| Field | Type | Rule |
|---|---|---|
| `condition_ref` | string | The identifier of the accepted term or obligation condition that makes the amount due |
| `accepting_act_hash` | string | The `content_hash` of the act that satisfied the condition, in the lowercase hexadecimal SHA-256 form required by [canonical commercial model v0.1](../schemas/canonical-commercial-model-v0.1.md) section 4 |
`accepting_act_hash` binds the instruction to exact bytes. Changing one byte of the accepting act invalidates the binding, in the same way and for the same reason that an approval is bound to an exact action hash.
An implementation MUST refuse an instruction whose `trigger` is absent, whose `accepting_act_hash` does not resolve to a recorded act in this transaction, or whose named act does not satisfy the named condition. The refusal is `A202-SETTLEMENT-TRIGGER-ABSENT`.
A settlement instruction that reaches an adapter without a resolvable trigger is a payment nobody can later show was owed. Refusing it is cheaper than reconstructing it.
## 4. Adapter receipt
### 4.1 What comes back
Whatever executes settlement returns an `AdapterReceipt`, as listed in [canonical commercial model v0.1](../schemas/canonical-commercial-model-v0.1.md) section 5.5. The receipt MUST reference both the instruction identifier and the `idempotency_key` of the instruction it answers.
An implementation MUST refuse a receipt that references no instruction, or that references an instruction identifier and idempotency key pair that was never issued, with `A202-SETTLEMENT-RECEIPT-UNMATCHED`. An unmatched receipt is either a misrouted message or an attempt to record a settlement that was never instructed, and both are refused for the same reason: a receipt is an answer, and an answer to a question nobody asked is not evidence of anything.
A failed attempt produces a receipt recording the failure. It does not edit the instruction and does not delete the earlier receipt, because a settlement that failed twice and succeeded on the third attempt is a fact about the transaction that a reader is entitled to see.
### 4.2 A receipt is evidence, never authority
An `AdapterReceipt` is an evidence reference. It records what an external system said in response to a request.
An implementation MUST NOT infer authority from a receipt. Specifically:
1. it MUST NOT treat a successful receipt as evidence that the instruction was authorized;
2. it MUST NOT treat a successful receipt as evidence that an obligation was performed, or accepted, or discharged;
3. it MUST NOT treat connectivity to a rail, possession of rail credentials, or the ability to obtain a receipt from a rail as conferring any commercial authority whatsoever.
The third is the one that matters most in practice. An integration that can reach a payment rail is an integration that can move money, and it is tempting to read that capability as permission. It is not. Authority in A202 comes from the mandate chain and from nowhere else, and an adapter with excellent connectivity and no mandate may do nothing. Payment is not agreement, and an adapter acknowledgment is not agreement; both statements are already in the state machine's offer rules and this section adds no exception to them.
The corollary runs the other way too. A rail that refuses or reverses a payment has not cancelled an obligation. The obligation stands, and the shortfall is a performance or settlement exception, resolved through the exception path in the state machine by parties with authority to resolve it.
## 5. Idempotency
The same instruction identifier and `idempotency_key` pair MUST NOT produce a second settlement.
An implementation MUST record the pair on first submission and MUST, on any subsequent submission of the same pair, return the original result rather than performing the settlement again. A retry references the original; it does not create a new instruction.
A submission that presents an already-recorded pair with different instruction content MUST be refused with `A202-SETTLEMENT-IDEMPOTENCY-CONFLICT`. It MUST NOT be treated as a retry, and it MUST NOT overwrite the recorded instruction. Two different instructions carrying one key is an error in the sender, and resolving it by preferring either version silently would pay one of the two amounts with no record of the disagreement.
This mirrors the kernel's existing duplicate-event rules, where a duplicate with an identical idempotency key returns the original result and a duplicate with different content returns a conflict. The rules are the same here because the failure is the same: a retry after an ambiguous timeout is the ordinary case, and it is the case in which a system most easily pays twice.
## 6. Non-custodial posture
The commercial layer never holds funds.
1. An implementation of this specification MUST NOT receive, hold, pool, or disburse funds belonging to a transacting party as part of its commercial-layer function.
2. A `SettlementInstruction` MUST NOT name the operator, or any commercial-layer component, as `payee_organization_id` for the purpose of onward transmission to the true payee. The payee is the party that is owed.
3. An implementation MUST refuse an instruction that violates either rule, with `A202-SETTLEMENT-CUSTODY-REFUSED`.
Custody is a rail property. Where funds are held between the payer and the payee, they are held by the rail or by a party the rail defines, under that rail's own rules, and the commercial layer records only that an instruction was issued and what came back.
The posture is stated normatively rather than left as an implementation choice because it is load-bearing for everything above it. A layer that holds funds acquires an interest in the outcome of the disputes it also adjudicates, and every disclosure and authority rule in this specification is written on the assumption that no such interest exists.
## 7. Refusal codes
These codes belong to the same registry as the codes in [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md) section 10.
| Code | Meaning |
|---|---|
| `A202-SETTLEMENT-RAIL-UNKNOWN` | The `rail` identifier does not resolve in the registered rail set |
| `A202-SETTLEMENT-TRIGGER-ABSENT` | The 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 |
## 8. Annex A: AP2
**Informative.** This annex is descriptive. It records how the objects in this document line up with a purchase-authorization protocol so that an implementer holding both can see where a mapping is available and where it is lossy. It creates no requirement on AP2 and asserts no equivalence.
**Checked 27 July 2026** against the AP2 v0.2 specification documents.
AP2 defines two mandate types. A Checkout Mandate is described as providing the merchant with cryptographic proof that the shopping agent is authorized to purchase. A Payment Mandate is described as providing the credential provider, the payment network, and the merchant's payment processor with cryptographic proof. AP2 describes two modes, one in which the user is present and approves directly, and one in which the user is not present and approves a constrained mandate in advance that the agent later closes with its own key. AP2 states that it operates as a security feature within a commerce protocol, and that catalog interfaces, checkout update interfaces, and the specific interfaces between roles are outside its scope.
At the level verified, the correspondence is:
| A202 | Nearest AP2 construct |
|---|---|
| Accepted obligation, bound by `accepting_act_hash` | Input to the purchase content over which a Checkout Mandate is taken |
| `SettlementInstruction` | Input to the payment authorization a Payment Mandate covers |
| `AdapterReceipt` | Reference to the result reported by the payment side |
The mapping is stated at the level of the objects and not at the level of fields. No AP2 field name is restated here, because a binding written against field names would become false the first time either specification revised one, and an implementer who needs those names should read AP2 rather than a summary of it.
What AP2 covers that A202 deliberately does not restate: the presentation and handling of payment credentials, the roles and obligations of the credential provider, the network, and the payment processor, the proof each of those parties requires, and the dispute procedures attached to a payment. A202 states none of these and defers to AP2 or to any other rail on all of them.
The division of ownership is that AP2 owns authentication to itself, including whatever proves to a merchant or a processor that a purchase was authorized on the payment side. A202 owns the commercial state: what was negotiated, under what authority, what was accepted, and what is therefore owed.
## 9. Annex B: x402
**Informative.** This annex is descriptive. It creates no requirement on x402 and asserts no equivalence.
**Checked 27 July 2026** against the x402 HTTP transport specification, version 2.
x402 defines a payment interaction over HTTP for machine-payable endpoints. A resource server that requires payment answers a request with HTTP status `402 Payment Required` and a challenge carried in a base64-encoded header field, `PAYMENT-REQUIRED`, whose object carries `x402Version`, `error`, `resource`, and an `accepts` array. Each entry in `accepts` carries `scheme`, `network`, `amount`, `asset`, `payTo`, `maxTimeoutSeconds`, and `extra`. The client resubmits with a payment payload in a `PAYMENT-SIGNATURE` header field, and the server returns settlement detail in a `PAYMENT-RESPONSE` header field. Header field names have differed between x402 transport versions, so an implementation binds to a specific transport version rather than to the names alone; this annex accordingly states the mapping at the level of the flow.
The mapping to this document is short, and the important part of it is a negative:
- receiving a `402` challenge is **not** a A202 trigger. It is a rail's demand for payment at the point of resource access. Under section 3, the trigger is a commercial transition, and a challenge issued by a counterparty's server is not one;
- where a A202 obligation exists, the trigger governs. The accepted obligation is what makes the amount due, and the `402` exchange is how the corresponding `SettlementInstruction` is discharged for that rail;
- the settlement detail returned in the response is an evidence reference for an `AdapterReceipt` under section 4, and inherits section 4.2 in full: it evidences that a transfer was reported, never that a commitment was authorized.
x402 is designed for interactions in which the commercial relationship is thin, often a single priced request with no negotiation and no prior agreement. A202 has nothing to add to that case, and an endpoint operating in it is not obliged to produce A202 objects at all. The mapping is for the case where a negotiated obligation exists and the parties choose this rail to discharge it.
The division of ownership is that x402 and its facilitators own authentication to the rail and the movement of value on it. A202 owns the commercial state that says the value was owed.
## 10. Annex C: ACP
**Informative.** This annex is descriptive. It creates no requirement on ACP and asserts no equivalence.
**Checked 27 July 2026** against the Agentic Commerce Protocol specification, maintained by OpenAI and Stripe, whose specification versions are dated; the surfaces below were read at version `2026-04-17`.
ACP defines a delegated checkout model in which a buyer's agent completes a purchase with a business without becoming the merchant of record. It defines an Agentic Checkout API organized around a checkout session, with operations to create, update, retrieve, complete, and cancel one, and a Delegate Payment API through which a payment credential is delegated for that purchase. It also defines a product feed, cart and order surfaces, capability negotiation, and an extension mechanism.
At the level verified, the correspondence is:
| A202 | Nearest ACP construct |
|---|---|
| Accepted obligation, bound by `accepting_act_hash` | Upstream of the checkout session; the commercial fact the session is opened to discharge |
| `SettlementInstruction` | Input to completing the checkout session for the named rail |
| `AdapterReceipt` | Reference to the order or completion result the merchant side returns |
The checkout session is the rail's own state for a purchase and A202 does not restate it. In particular, a A202 transaction does not track session lifecycle, and a cancelled checkout session does not cancel an obligation; that would be a carrier or rail event determining commercial state, which section 3.1 and the state machine both refuse.
A delegated payment credential is authority over a payment instrument. It is not commercial authority, and section 4.2 applies to it directly: holding a delegated credential does not permit an agent to commit its organization to anything, and the rule that forbids reading it that way is the one already stated in section 4.2. This annex only points at it.
The division of ownership is that ACP owns authentication to itself, including how an agent proves to a business that it may complete a checkout and how a payment credential is delegated. A202 owns the commercial state: what was negotiated, under what authority, what was accepted, and what is owed.
---
Source: https://a202.org/bindings/a2a-binding-v0.1.md
# A2A carrier binding v0.1
**Status:** Experimental, adopted through proposal [A202-0001](../proposals/A202-0001-carrier-bindings.md); section 7.4 adopted through [A202-0017](../proposals/A202-0017-submission-success-status.md). Mixed. Sections 2, 3, 4, 5, 6, 7, and 8 are **normative**. Sections 1 and 9 are **informative** and state no requirement on an implementation.
**Date:** 27 July 2026
**Scope:** Synthetic pilot transport only
**Relates to:** [canonical commercial model v0.1](../schemas/canonical-commercial-model-v0.1.md), [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md), [commercial mandate v0.1](../authority/commercial-mandate-v0.1.md), [release policy](../RELEASES.md)
## 1. Purpose
A202 objects are carrier-independent. An offer means the same thing whether it arrives over an agent protocol, over a plain HTTPS request, or on a memory stick. The canonical model defines that meaning and defines the bytes that carry it. Nothing in the meaning depends on how the bytes moved.
This document states how those objects travel over the A2A protocol, and how they travel over plain HTTPS for a party that does not run A2A. It is a binding, not a second specification of commercial semantics. Where this document and the canonical model appear to say different things about an object, the canonical model is the object's definition and this document is wrong.
Session mechanics belong to the carrier. Turn order, request timeouts, cancellation, streaming, retries, push notification, and connection lifetime are carrier concerns, and A202 states no requirement on any of them. This is deliberate rather than an omission. A commercial specification that also specified conversational discipline would have to be reimplemented for every carrier it was ported to, and the two sets of rules would drift. The transaction and session state machines constrain which transitions are legal; they do not constrain the conversation that proposes them.
Two consequences follow, and both are load-bearing later in this document. A carrier event is not a commercial event: a cancelled request, a dropped stream, and a timed-out task change nothing about what has been agreed. And a carrier session is not a commercial correlation: what binds an act to a transaction is the transaction identifier inside the signed object, not the connection it arrived on.
**Checked 27 July 2026** against the A2A specification and its interface definition at version 1.0. Where a detail of A2A could not be verified from those sources, this binding states the requirement at the level of the declaration and leaves the carrier detail to the carrier.
## 2. Extension declaration
The A202 commercial extension is declared using A2A's own extension mechanism. A202 defines no parallel discovery surface for it.
### 2.1 Where the declaration sits
A party that carries A202 commercial objects over A2A MUST declare the A202 commercial extension in its A2A AgentCard, as one entry in the extension array carried by the card's capabilities object.
The entry MUST populate A2A's declared extension fields as follows:
| A2A extension field | Required value |
|---|---|
| `uri` | Exactly the extension URI in section 2.2 |
| `description` | A human-readable statement that the agent exchanges A202 commercial objects |
| `required` | `true` when the party requires the extension under section 3, `false` otherwise |
| `params` | The version declaration object in section 4.1 |
A party MUST NOT declare the extension in any other field of the card, and MUST NOT treat a skill name, a description string, or an output mode as a declaration of A202 support. A capability that is inferred is a capability that was guessed.
### 2.2 Extension URI
The v0.1 extension URI is:
```text
https://schemas.a202.org/a2a-ext/commercial/0.1
```
The URI is issued under `schemas.a202.org`, a host the project controls, and is the long-lived commitment for v0.1. It replaces an earlier placeholder on the reserved `.invalid` top-level domain, which was used while no host had been settled so that the placeholder could not resolve to a host anyone controls. An implementation that hard-coded the placeholder hard-coded a value that has now changed.
The URI carries the specification minor version because A2A requires a new extension URI when an extension makes a breaking change to its logic, data structures, or required parameters. A breaking change to this binding therefore produces a new URI rather than a new value inside the existing one, and an agent that supports only the new version simply does not declare the old URI. Version negotiation inside a single URI is additionally available through section 4 for changes that are not breaking.
### 2.3 Activation
A client that intends to exchange A202 commercial objects in a request MUST activate the extension by naming the URI in section 2.2 in A2A's extension activation service parameter, which A2A carries as a comma-separated list of extension URIs under the field name `A2A-Extensions`.
A server that accepts the activation echoes it under A2A's own rules. A client MUST treat an absent echo as non-activation and MUST apply section 3 to it. Silence is not acceptance.
## 3. Mandatory to understand
### 3.1 A party may require the extension
A party MAY require the A202 commercial extension, by declaring `required` as `true` in its own card entry. A party that requires the extension is stating that it will not conduct a commercial exchange with a counterparty that cannot parse, validate, and refuse A202 objects correctly.
### 3.2 Failure is closed and it happens first
When a party requires the extension, interaction with a counterparty that does not declare support MUST fail closed **before any A202 commercial object is exchanged**, and the refusal code is `A202-EXTENSION-UNSUPPORTED`.
Each of the following is a failure to declare support, and all four produce the same refusal:
1. the counterparty's card carries no entry with the URI in section 2.2;
2. the entry is present and its version declaration does not parse under section 4.1;
3. the version declaration parses and section 4.2 finds no compatible version;
4. the counterparty's card cannot be retrieved at all.
Condition 4 is included for the same reason that an unresolvable mandate status endpoint denies rather than allows: unavailability is not permission. A party that cannot be checked has not passed the check.
### 3.3 The check runs at capability negotiation, not at first commercial act
The fail-closed check applies to capability negotiation itself, not only to the exchange of mandates or of objects that carry money. The order is: resolve the counterparty declaration, evaluate section 4.2, then and only then transmit.
This ordering matters because the first A202 object a party sends is frequently not an offer. It is a counterparty invitation, a qualification request, or a mandate presented for verification, and each of those discloses something: that a transaction exists, that this party is participating in it, which categories it is buying in, or which principal issued its authority. A capability check that ran at first offer would have leaked all of that to a counterparty that was never able to participate.
A party MUST NOT downgrade to a bare carrier exchange when the check fails. There is no defined behaviour for a A202 object exchanged outside the extension, so a fallback path would be a path along which commercial objects move under no stated rules at all.
### 3.4 A declaration is not authority
Declaring the extension states what a counterparty can parse. It states nothing about what that counterparty may commit to, on whose behalf, or within what limits. Authority is established only by the mandate chain in [commercial mandate v0.1](../authority/commercial-mandate-v0.1.md), and an implementation MUST NOT treat extension support, extension activation, or a successful capability negotiation as an input to any authority decision.
## 4. Version declaration and mismatch
### 4.1 What travels in the declaration
The extension declaration's parameter object MUST carry exactly two fields:
| Field | Type | Meaning |
|---|---|---|
| `read_versions` | array of strings, at least one entry | The read version, as defined in [RELEASES.md](../RELEASES.md) section 4 |
| `write_version` | string | The write version, as defined in [RELEASES.md](../RELEASES.md) section 4 |
Each value is a specification set version of the form `MAJOR.MINOR`. The meaning of the two declarations, the rule that both are declared, and the rule that a party never writes a version it cannot read are stated in [RELEASES.md](../RELEASES.md) section 4 and are not restated here. This section states only how the two declarations travel over this carrier.
A declaration that omits either field, that carries `read_versions` as an empty array, or that carries a value that is not of the form `MAJOR.MINOR` does not parse, and section 3.2 condition 2 applies.
### 4.2 Mismatch fails closed
Before any A202 commercial object is exchanged, each party MUST evaluate the counterparty's declaration against its own:
1. the counterparty's `write_version` MUST appear in this party's `read_versions`;
2. this party's `write_version` MUST appear in the counterparty's `read_versions`.
If either check fails, the interaction fails closed with `A202-EXTENSION-UNSUPPORTED`. An implementation MUST NOT proceed on a partial match, MUST NOT select a nearest version, and MUST NOT fall back to an older version of the extension URI. A version an implementation did not declare is a version it did not commit to reading.
### 4.3 One code for four conditions
All four conditions in section 3.2 return `A202-EXTENSION-UNSUPPORTED`. They are not distinguished.
The reason is not disclosure; an AgentCard is published and a caller can read the declaration for itself. The reason is that the four conditions have exactly one correct outcome, and a caller handed four codes that must all be handled identically will eventually handle one of them differently. A single code keeps the fail-closed path single, so that widening one branch of it cannot quietly open another.
An implementation MAY log the distinguishing detail locally. It MUST NOT vary the code on the wire.
## 5. Object transport
### 5.1 Objects travel as message parts
A A202 shared object travels as a part of an A2A message. One part carries exactly one object. A message MAY carry several parts and therefore several objects.
### 5.2 Canonical bytes are the object
The bytes carried in the part MUST be byte-identical to the object's canonical form under [canonical commercial model v0.1](../schemas/canonical-commercial-model-v0.1.md) section 4, which is JSON Canonicalization Scheme serialization under RFC 8785.
The object MUST therefore be carried in a part whose content is opaque bytes, with the media type `application/a202-commercial+json`. A part that carries the object as structured data is re-serialized by the carrier and by every intermediate library that touches it, and a re-serialized object is a different sequence of bytes even when it is the same JSON document. Such a part MAY accompany the object as a parsed convenience view for a reader, and an implementation MUST NOT treat it as the object, MUST NOT hash it, and MUST NOT verify a signature against it.
A receiver MUST verify against the bytes it received. An implementation that parses, re-serializes, and then hashes has verified its own serializer rather than the sender's object.
### 5.3 Signatures cover the object, never the framing
Signatures cover the canonical bytes defined in section 5.2 and nothing else. No signature in this binding covers a message identifier, a task identifier, a context identifier, a part metadata field, a transport header, or any other carrier framing.
This follows from the canonical model rather than adding to it: `content_hash` is computed over the object's canonical content with `content_hash`, `signatures`, and `kernel_annotations` omitted, and carrier framing is not part of that content at all.
### 5.4 Carrier metadata stays outside the signed bytes
Carrier metadata MUST NOT appear inside the signed bytes. A sender MUST NOT copy a task identifier, a context identifier, a message identifier, a header value, or any other carrier-assigned value into the object before hashing it.
A receiver that finds carrier framing inside the signed bytes MUST refuse the object. No new refusal code is defined for this case, because none is needed: the kernel envelope and the payload shapes are closed, an unknown field fails schema validation, and the canonical model already requires that unknown fields fail validation. The refusal is a kernel validation refusal and is reported as such.
The rule exists because a signature over carrier framing would make the object mean something different depending on how it travelled. An agreement that verifies only when replayed alongside the task that carried it is an agreement that depends on the carrier's records surviving, which is the dependency the audit design exists to remove.
## 6. Task to transaction correlation
### 6.1 The relationship is many to one, and it is not a mapping
Many A2A tasks may serve one A202 transaction. A task may serve none. A single task may carry objects belonging to more than one transaction, though an implementation is not obliged to allow that.
The A202 transaction aggregate defined in [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md) is the commercial source of truth. There is no correspondence between the carrier's task lifecycle and the aggregate's states, and an implementation MUST NOT construct one.
### 6.2 Task state does not determine transaction state
Task state MUST NOT determine transaction state.
A task that reaches a failed, cancelled, or rejected state is a carrier event. It reports that a unit of carrier work did not complete. It does not cancel a transaction, does not withdraw an offer, does not release a selection freeze, and does not terminate an agreement. Any commercial consequence requires an explicit A202 transition: a signed, authorized event that satisfies the guard for that transition, exactly as required by [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md) section 1.
The concrete case this rule exists for is ordinary. A supplier's agent submits a signed offer; the offer is appended to the session stream; the carrier request that delivered it then times out and its task is recorded as failed. The offer stands. The supplier is bound by it until it expires or is withdrawn by an authorized withdrawal event. An implementation that swept the offer away with the failed task would have let a network condition retract a commercial commitment, and neither party signed anything to that effect.
The same holds in the other direction. A task that reaches a completed state completes nothing commercially. Acceptance is not selection, selection is not agreement, and a completed carrier task is further from agreement than any of them.
### 6.3 Correlation is carried by the transaction identifier
Correlation between a carrier exchange and a transaction is carried by `transaction_id` in the extension payload. No carrier identifier correlates a commercial act to a transaction.
A message that carries a transaction-bound A202 object MUST carry, in the extension payload, the same `transaction_id` as the object's envelope. Where the two disagree, the envelope is authoritative and the object MUST NOT be processed; the refusal is `A202-STREAM-MISMATCH`, defined in [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md) section 10, because the carrier exchange has directed an act at a stream the object does not belong to.
An implementation MAY align the carrier's own grouping of related tasks with a transaction as an operational convenience. Such alignment MUST NOT be relied on for correlation, MUST NOT be relied on for authorization, and MUST NOT be used to decide which stream an event targets.
## 7. Plain HTTPS binding
This section binds A202 objects to plain HTTPS for a party that does not run A2A. It is deliberately minimal. It defines a capability check, a request, and a refusal, and nothing else.
### 7.1 Endpoint and request
1. The endpoint MUST use HTTPS. Plain HTTP is refused, for the reason given for mandate status endpoints in [commercial mandate v0.1](../authority/commercial-mandate-v0.1.md) section 3.3.
2. An object is submitted by `POST`.
3. The request body MUST be the object's canonical bytes exactly as defined in section 5.2, with the media type `application/a202-commercial+json`. Every rule in section 5 applies unchanged, including the rule that carrier framing stays outside the signed bytes; under this binding, carrier framing means the request line, the headers, and any envelope a proxy adds.
### 7.2 Capability check
The capability check MUST complete, and MUST pass, before any A202 commercial object is transmitted. An implementation MUST support at least one of the two following forms and MAY support both.
**Preflight.** A `GET` on a declared capability path returns a document carrying the same three values as the A2A declaration: the extension URI from section 2.2, and the `read_versions` and `write_version` fields defined in section 4.1.
**Declared header.** The caller names the extension URI from section 2.2 in a request header field `A202-Extensions`, carried as a comma-separated list of extension URIs in the same form A2A uses for activation. A responder that accepts the activation echoes the header field with the URIs it activated. An absent echo is non-activation, and section 3 applies to it.
### 7.3 Refusal
Every condition in section 3.2 and every mismatch under section 4.2 fails closed here identically, with `A202-EXTENSION-UNSUPPORTED`. The refusal is carried with HTTP status `412`, because a stated precondition on the exchange was not met.
A responder MUST NOT accept a commercial object on a request that failed the capability check, and MUST NOT queue it for later evaluation.
### 7.4 The response to an accepted submission
Adopted through [A202-0017](../proposals/A202-0017-submission-success-status.md).
1. A submission under section 7.1 that the responder does not refuse in the response MUST be answered with HTTP status `202`.
2. A responder MUST NOT answer a submission with `200` or `201`. Either status asserts an outcome, completion in one case and creation in the other, and the outcome of a commercial act is never in the carrier response. An adapter acknowledgment is not agreement, as [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md) section 7 states; a determination arrives, if it arrives, as a signed object.
3. `202` states receipt for evaluation and nothing else. It does not state that the object validated, appended, or will be acted on. The refusal statuses stand unchanged: `412` under section 7.3, and `409` for a sequence mismatch under the state machine's section 8. A responder that answers `202` and evaluates afterwards conveys any later refusal the way every commercial outcome is conveyed, as a signed object; the status carries no promise that a refusal cannot follow.
No response body is defined for the `202`. Anything a responder puts there is carrier framing under section 5.2, is covered by no signature, and section 9 applies to it.
## 8. Refusal codes
This binding adds one code. It belongs to the same registry as the codes in [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md) section 10.
| Code | Meaning |
|---|---|
| `A202-EXTENSION-UNSUPPORTED` | Returned uniformly for an absent extension declaration, an unparseable version declaration, an incompatible version, and an unretrievable capability surface |
Codes this binding reuses without redefining are `A202-STREAM-MISMATCH`, used in section 6.3, and the kernel validation refusals used in section 5.4. Their definitions are in the documents that own them.
## 9. Security considerations
Replay resistance in A202 comes from the object envelope, not from the carrier. Three properties do the work, and all three are defined in the canonical model and the state machine rather than here: `content_hash` fixes the object's bytes, `previous_version_id` fixes an object's place in its own version chain, and the per-stream sequence fixes an event's place in its stream. A carrier that redelivers a message therefore redelivers bytes whose sequence has already been consumed, and the duplicate is recognised without reference to the connection it arrived on. This is why the binding needs no session token, no nonce, and no carrier-level replay window.
The binding adds no new signed material. Nothing in the extension declaration, the version declaration, the activation parameter, the correlation payload, or the HTTPS headers is covered by any A202 signature, and no security property in this specification depends on any of them being authentic. That is the intended shape: a hostile carrier that reorders, drops, duplicates, or rewrites framing can degrade availability, and it cannot manufacture a commercial act, because every act is a signed object evaluated against a mandate chain.
Two smaller observations follow from the same design. A capability declaration is a public statement about parsing, so treating it as evidence of anything else, including good standing, willingness to transact, or authority, imports a trust decision the declaration was never able to support. And the capability check is a check on the counterparty, not a mutual secret, so a party that fails it learns only that the exchange will not proceed, which is what it needed to know.
Retrieving a counterparty's capability surface is itself a disclosure: it tells the counterparty, or anyone observing, that this party is looking. Where that matters, the discovery and invitation rules in the specification govern who may be approached at all, and this binding neither widens nor narrows them.
---
Source: https://a202.org/conformance/conformance-grades-v0.1.md
# Conformance grades v0.1
**Status:** Experimental working specification. Mixed. Sections 2, 3, 4, 5, and 6 are **normative**. Sections 1 and 7 are **informative** and state no requirement on an implementation.
**Date:** 26 July 2026
**Revised:** 30 July 2026, under [A202-0015](../proposals/A202-0015-fixture-minimality-orphan-codes-and-grade-scope.md): section 4 states the structure of `scope`, of `dimensions`, and of `held_out_coverage` explicitly, so that the grade object can be fixtured and the role scope rules can be checked rather than asserted. The `scope` example that named "bidder" and "event operator", neither of which resolves in the role scope registry, is replaced by the registered identifiers. No dimension, band, constraint, or appeal rule changes.
**Depends on:** [canonical commercial model v0.1](../schemas/canonical-commercial-model-v0.1.md), [commercial mandate v0.1](../authority/commercial-mandate-v0.1.md), [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md), [auction event semantics v0.1](../negotiation/auction-event-semantics-v0.1.md), and the fixture set, manifest, and runner in this directory
**No external body recognises anything defined here.** A grade defined by this document is a statement by whichever party issued it, about one implementation, scoped to one specification version. It is not certification by a standards body, a regulator, an auditor, or an insurer, and no material may describe it as one.
## 1. What a grade measures
A grade measures whether an implementation **refuses correctly**, not whether it works.
An implementation that passes every positive fixture has demonstrated that it can transact on a good day. Loss in agent-mediated commerce comes from the other direction: authority that outlived its revocation, a disclosure that let one counterparty infer another, a commitment that was accepted when it should have been refused, an unparseable input that was guessed at rather than rejected.
The published suite in this directory is therefore weighted toward negative fixtures, and the manifest is the single source for how many there are: a count restated here went stale once already, and this sentence now defers to `manifest-v0.1.json` instead of repeating one. The negative-to-positive ratio has stayed near five to one as the set has grown, and the ratio is deliberate. The grade defined below is weighted the same way, toward behaviour under hostile and malformed input.
## 2. Dimensions
A grade is a vector, never a single letter. A scalar hides exactly the information a relying party needs in order to decide whether this implementation may hold commitment authority in its transactions.
| # | Dimension | What it covers | Principal loss it predicts |
|---|---|---|---|
| **A** | **Authority handling** | Mandate parse, delegation chain narrowing, per-hop expiry, revocation signalling, fail-closed behaviour when a status endpoint is unreachable | A commitment made without valid authority behind it |
| **B** | **Disclosure and isolation** | Rival non-inference, denied-action visibility, close-reason discipline, refusal codes that carry aggregate state, timing and error-code side channels | One counterparty inferring another's existence, activity, or terms |
| **C** | **Commitment integrity** | Acceptance versus commitment separation, dual signature, guarded state transitions, award subordination, replay of transitions | A record read as a binding agreement when it is not, or a commitment that cannot be reconstructed |
| **D** | **Evidence** | Portable export, hash scope and canonicalisation, deterministic replay by a third party | An event that cannot be proved after the fact |
| **E** | **Adversarial failure behaviour** | Response to malformed, hostile, unregistered, and unanticipated input. Whether the implementation refuses or guesses | Silent wrong results, which are the failure mode that tests written by the same party never catch |
A grade MUST report all five dimensions. A dimension that was not assessed is reported explicitly as unassessed, never omitted.
## 3. Bands
Each dimension is scored on a four-point band.
| Band | Meaning |
|---|---|
| **0** | Fails at least one published negative fixture in the dimension |
| **1** | Passes the published set for the dimension |
| **2** | Passes the published set, and also passes an assessor's held-out case set exercising the same invariants as the published set |
| **3** | Passes both, and refuses cleanly on inputs no fixture anticipated, with a correct reason code rather than a generic error |
Band 2 requires that the subject was measured on rules it was told about, using inputs it was not given. The invariants exercised are the invariants of this specification set. A band 2 result therefore says that the implementation enforces the rule rather than that it recognises the fixture.
Band 3 is not reachable by automated fixture execution alone. It requires an assessor to construct inputs outside any published or held-out set and to judge the quality of the refusal. It is the band that separates an implementation that was tested from one that was designed to fail closed.
## 4. The grade object
A grade is emitted as a signed, machine-readable object, versioned alongside the kernel schemas.
| Field | Purpose |
|---|---|
| `subject` | The assessed implementation and the organisation responsible for it |
| `spec_version` | The specification version assessed. A grade is meaningless without it |
| `level` | The assessment level at which the grade was established. A grade is held at the level it was assessed at and is never inferred at a higher one |
| `dimensions` | Band per dimension A to E, and for each band the fixture families it was established from. Always all five, with any unassessed dimension explicitly null rather than absent |
| `scope` | Transaction profile coverage, transport coverage, and exactly one role scope identifier. A grade earned against `a202-scope/bilateral/0.1` says nothing about behaviour against `a202-scope/operated/0.1` |
| `held_out_coverage` | Which dimensions a held-out case set exercised, the case count, and the fixture families the cases were drawn against. Coverage and count only, never case content |
| `issued_at`, `expires_at` | Expiry is 12 months or the next specification minor version, whichever is first |
| `status` | `active`, `expired`, `suspended`, `withdrawn`, `under_appeal` |
| `determination_ref` | Reference to the determination record, so that a grade is contestable in the same way a protocol determination is |
| `signature` | Issuance signature |
### 4.1 The structure of `scope`, `dimensions`, and `held_out_coverage`
Three of the fields above carry structure rather than a scalar, and until 30 July 2026 this section described them in prose alone. The prose was not enough to write a grade against: it named "roles covered" with an example that resolved in no registry, and it said a band was established from a scope without saying where a band records what established it. The three shapes below close that, and [conformance-grade.schema.json](../schemas/v0.1/conformance-grade.schema.json) carries them.
**`scope`** is an object with three members.
| Member | Contents |
|---|---|
| `role_scopes` | An array of role scope identifiers from section 3.1 of [conformance-role-scopes-v0.1.md](conformance-role-scopes-v0.1.md). A grade MUST name exactly one. An identifier that does not resolve there is refused with `A202-GRADE-SCOPE-UNKNOWN`; naming none or naming more than one is refused with `A202-GRADE-SCOPE-INVALID` |
| `profiles` | The transaction profile identifiers the assessment covered |
| `transports` | The carrier extension URIs the assessment covered |
The member is an array although exactly one identifier is permitted. The cardinality rule is a refusal carrying a reason code, not a shape: a grade naming none is the unscoped form that existed before the registry, a grade naming two is the form that claims one assessment as two, and both have to be expressible in order to be refused with the code section 3.2 of the role scope document names. A shape that could not express them could not fixture them either.
**`dimensions`** is an object with the five members `A` to `E`. Each is either `null`, meaning the dimension was not assessed, or an object carrying `band`, an integer 0 to 3 from section 3, and `established_from`, the fixture families the band was established from. A band that does not record what established it cannot be checked against the scope it is reported under, and section 6.3 of the role scope document requires exactly that check.
**`held_out_coverage`** is `null` where no held-out set was used, or an object carrying `dimensions`, the dimension letters the held-out cases exercised, `case_count`, and `families`, the fixture families the cases were drawn against. It reports coverage and count and never case content.
A **fixture family identifier** is a family's name in the Family column of sections 4.3 and 5.3 of [conformance-role-scopes-v0.1.md](conformance-role-scopes-v0.1.md), lowercased with spaces replaced by hyphens: `mandate`, `delegation-chain`, `evidence-and-verification`, `session-event`, and so on. A family outside the scope the grade names is the overclaim refused with `A202-GRADE-SCOPE-OVERCLAIM`.
Nothing else about the field set changes. Profile and transport coverage are carried as they were carried before, verbatim, and this section states no registry for either.
### 4.2 Design constraints
Four design constraints hold, and an implementation of this object MUST satisfy all four.
1. **No overall score.** The object carries no composite figure and no weighting. A consumer combines the vector under its own policy. A published weighting is immediately optimised against, which converts the grade from a measurement into a target.
2. **Absence is not a pass.** A null dimension reads as unassessed everywhere it is consumed. A relying party that treats null as satisfactory has misconfigured its own bar, and the object MUST NOT make that reading easy by omitting the field.
3. **Expiry is enforced, not advisory.** An expired grade fails a check exactly as a band 0 does. A consumer MUST evaluate `expires_at` before relying on `dimensions`.
4. **A grade is never raised by re-running.** A failed assessment is recorded. Re-assessment is available after a stated remediation interval, and the record shows the sequence rather than the best result. This is what stops grade shopping.
## 5. Published fixture policy
The published fixture set is the set a subject is measured against for band 1, and it is the set an implementer may use freely to test against before any assessment takes place.
1. The published set, the manifest, and the runner are open. Anyone may execute them, and a self-run result is a legitimate statement by the party that ran it. It is not a grade, because a published fixture can be hard-coded and a self-run result carries no independent evidence that it was not.
2. Publication is one way. A fixture that enters the published set stays published. It does not return to any held-out set afterwards, so a contributed fixture is a permanent addition to what every implementer can see.
3. Band 1 is defined entirely by the published set. An implementation that passes it has demonstrated exactly that, and the grade says so and no more.
4. Above band 1, a subject is measured using inputs it was not given. `held_out_coverage` reports which dimensions were exercised and how many cases were used. It MUST NOT disclose the cases themselves, because a disclosed case is a case the next subject can hard-code.
## 6. Appeal
A grade is a determination and is appealable on the same terms as any other determination under this specification set.
**Grounds.** An appeal MUST state at least one of:
1. the rule was misapplied;
2. the input did not exercise the stated invariant;
3. the scope was wrong;
4. the specification version was wrong.
Disagreement with the rule itself is not a ground of appeal. That is a change proposal against this specification, and it goes through the proposal process rather than through an appeal.
**Effect on status.** While an appeal is open, `status` becomes `under_appeal` and the prior grade stands. It is neither raised nor suspended. Both would let the act of appealing move the outcome, which would make appealing a tactic rather than a remedy.
**Determination.** An appeal determination is written, recorded alongside the original, and supersedes it. It never deletes it. A record that can be deleted when it is unfavourable is a record nobody has a reason to read.
## 7. Open questions
- Whether a subject may hold different grades for different scopes at the same time, and how a relying party evaluates a mixed holding.
- Whether an implementation assessed while running under a hosting provider inherits or shares that provider's grade, and how the boundary is stated when the provider custodies keys.
---
Source: https://a202.org/conformance/conformance-role-scopes-v0.1.md
# Conformance role scopes v0.1
**Status:** Experimental, adopted through proposal [A202-0006](../proposals/A202-0006-bilateral-conformance-role-scopes.md). Mixed. Sections 2, 3, 4, 5, 6, 7, and 8 are **normative**. Sections 1, 9, and 10 are **informative** and state no requirement on an implementation.
**Date:** 27 July 2026
**Revised:** 30 July 2026, under [A202-0015](../proposals/A202-0015-fixture-minimality-orphan-codes-and-grade-scope.md): the fixture plan of [A202-0006](../proposals/A202-0006-bilateral-conformance-role-scopes.md) section 5 implemented, so that the three refusal codes in section 8 are raised by an evaluator rather than named in a table. A conformance-grade family joins section 4.3 and section 5.3, family identifiers are stated for the grade object's coverage members, and item 6 of section 2 names the fixtures it requires. No scope identifier, capability, or section assignment changes. Also revised 30 July 2026, under [A202-0014](../proposals/A202-0014-bilateral-formation-and-scope-repair.md): the bilateral scope made true against the claim in section 4.4, following the specification review of 30 July 2026. The direct formation path enters the bilateral scope, the fixture families are split by authorship so that no bilateral family holds an operator-issued object, the partition is made total and disjoint over every fixture on disk, the invariants named in section 5.2 are cited by reason code rather than by nickname, and the appeal-route claim in section 7.1 is stated as the open question it is. The two scope identifiers mean what they meant. Previously revised 28 July 2026, under [A202-0009](../proposals/A202-0009-enforcement-fidelity.md): the fixture partition restated by family pattern so that it covers the whole manifest and stops going stale as the set grows.
**Scope:** Synthetic pilot transactions only
**Depends on:** [conformance grades v0.1](conformance-grades-v0.1.md), [canonical commercial model v0.1](../schemas/canonical-commercial-model-v0.1.md), [commercial mandate v0.1](../authority/commercial-mandate-v0.1.md), [obligation v0.1](../agreement/obligation-v0.1.md), [evidence verification v0.1](../evidence/evidence-verification-v0.1.md), [pilot transaction state machine v0.1](../negotiation/pilot-transaction-state-machine-v0.1.md), and the fixture set, [manifest](manifest-v0.1.json), and [runner](run-conformance.py) in this directory
**No external body recognises anything defined here.** A scope identifier defined in this document names a set of specification sections and fixtures. It is not an accreditation, a certification class, or a licence, and no material may describe it as one.
## 1. What this document is for
The specification set defines capabilities of two different kinds and does not currently say which is which.
Some capabilities are exercised by exactly two organisations. A principal issues a mandate and the counterparty verifies it, checks its status endpoint, and refuses an act outside it. Two parties form an agreement over one offer hash, decompose it into obligations, assert and respond to performance, and verify each other's records by executing the published verification procedure. Nothing in any of that requires a third party to be present, to hold a key, or to sign anything.
Other capabilities do not exist without an operator. An invitation acceptance is authored by a control plane under the operator's own mandate, because the claimant has no mandate with which to author anything. A negotiation session stream is created and ordered by something that is neither of the two parties. An award among rival sessions contends on a stream that no single participant holds. A determination issued by a venue is issued by the venue.
Both kinds are specified here, and a grade issued against the specification set today does not say which of the two it covers. This document names the two sets so that a grade can say, and so that two organisations can point at a bounded surface and adopt it between themselves.
The division is descriptive. It reclassifies nothing, changes no rule, and states no preference for either scope. A capability is in the operated scope because the specification says an operator authored, ordered, or issued something, and for no other reason.
## 2. Conformance language
`MUST`, `MUST NOT`, `REQUIRED`, `SHOULD`, `SHOULD NOT`, and `MAY` are normative within this experimental specification.
An implementation that issues or consumes a conformance grade conforms to this document when it:
1. names exactly one role scope identifier from section 3 in the `scope` field of every grade it issues;
2. refuses a grade whose role scope identifier does not resolve in the registry in section 3, under section 3.2;
3. reports, under section 6, every dimension that the named scope does not cover as unassessed rather than as a band;
4. treats the absence of a grade in a scope as unassessed rather than as a pass, under section 6.4;
5. returns the refusal codes in section 8 for the failures they name;
6. passes the conformance-grade family of [manifest-v0.1.json](manifest-v0.1.json), which is the row of that name in section 4.3 and in section 5.3: two grades in the allow direction, one per scope, and five refusals covering each of the three codes in section 8.
This document adds no object, no field, no state, no transition, and no guard. It defines a closed registry for a field that already exists.
## 3. The role scope registry
### 3.1 Registered identifiers
Two role scopes are registered in v0.1. The registry is closed in v0.1.
| Identifier | Name | What an assessment against it establishes |
|---|---|---|
| `a202-scope/bilateral/0.1` | Bilateral scope | The implementation issues, verifies, and refuses the objects that two organisations exchange directly, with no operator present and no operator-issued object in any chain it relies on |
| `a202-scope/operated/0.1` | Operated scope | The implementation issues, orders, verifies, and refuses the objects and streams whose authorship, ordering, or issuance the specification set assigns to an operator or a control plane |
A role scope identifier is a long-lived commitment. It is resolved by consumers of a grade, so an identifier registered here is never reused for a different meaning and never removed once a grade has been issued against it.
### 3.2 An unrecognised identifier fails closed
1. A grade's `scope` field MUST name exactly one role scope identifier from section 3.1, alongside whatever transaction profile and transport coverage that field also carries under [conformance-grades-v0.1.md](conformance-grades-v0.1.md) section 4. This document is the registry for the role part of that field and changes nothing else about it.
2. A consumer that reads a role scope identifier which does not resolve in section 3.1 MUST refuse the grade with `A202-GRADE-SCOPE-UNKNOWN`. It MUST NOT fall back to the other registered scope, to a nearest match, or to an unscoped reading.
3. A grade naming no role scope identifier, or naming more than one, MUST be refused with `A202-GRADE-SCOPE-INVALID`. A grade that covers both scopes is two grades.
4. Refusal is refusal of the grade, not a band 0. A grade that cannot be read tells a relying party nothing, and recording it as a failure would tell it something false.
This matches the treatment of every other closed registry in the set: an unregistered transaction profile, constraint type, constraint operator, evidence type, or due condition type fails closed rather than resolving to a default.
### 3.3 Adding a scope
Adding a role scope identifier is a change to this registry and requires a proposal under [proposals/README.md](../proposals/README.md). Under [RELEASES.md](../RELEASES.md) section 2 an addition that leaves both registered identifiers meaning what they mean today is a MINOR change. Changing what a registered identifier covers is a MAJOR change, because a grade already issued against it would afterwards claim something its assessment did not establish.
## 4. The bilateral scope
`a202-scope/bilateral/0.1`
### 4.1 Capabilities in this scope
1. **Mandate issuance and verification.** Issuing a root or delegated mandate, parsing a counterparty's mandate, verifying the issuer signature, verifying the chain, proving monotonic narrowing at every hop, evaluating the constraint vocabulary, and refusing an act outside `actions`, `scope`, or a constraint.
2. **Revocation status checking.** Resolving a mandate's status endpoint over HTTPS, respecting the cache bound, and denying when the endpoint does not resolve.
3. **Approval binding.** Binding a human approval to one exact action hash, and refusing a reused or altered one.
4. **Agreement formation between two parties.** An offer that is current and unexpired, carrying a session identifier the offeror minted and the counterparty adopted; an acceptance over the exact offer hash; entry to `agreement_pending` through `agreement.direct` with no publication, qualification, or negotiation room; an agreement whose terms hash equals the accepted offer's; and both parties' signatures over the same agreement bytes.
5. **Obligation exchange.** Deriving obligations from a committed agreement, typed due conditions, assertion by the obligor with at least one evidence reference, response by the obligee bound to the assertion hash, partial acceptance with a named remainder, rejection with a registered reason code, and the guarded transitions between those states.
6. **Evidence reference resolution and the seven-step verification procedure.** Emitting evidence references in the structured shape, and executing steps 1 to 7 of [evidence-verification-v0.1.md](../evidence/evidence-verification-v0.1.md) section 4 over records a counterparty produced, with the three-valued output and the selective disclosure rules.
7. **Settlement handoff.** Issuing a settlement instruction triggered by a commercial act hash, refusing an unregistered rail, refusing a custodial route, and treating an adapter receipt as evidence rather than as authority.
8. **Refusal behaviour on everything above.** Malformed, hostile, unregistered, and unanticipated input against every object family in this scope.
### 4.2 Specification sections in this scope
| Document | Sections in scope | What an implementation demonstrates |
|---|---|---|
| [canonical-commercial-model-v0.1.md](../schemas/canonical-commercial-model-v0.1.md) | 2 and 15 in their bilateral reading, 3, 4, 7, 8, 9 other than the `kernel_annotations` clause of step 4, 9.1, 9.2, 10, 10.1, 13, 14, and the invariants in 12 other than those named in section 5.2 of this document | Envelope, canonicalisation, signature rules, money and quantity representation, profile resolution, action and policy ordering with each party evaluating its own actions, agreement formation and amendment, and the private strategy boundary |
| [commercial-mandate-v0.1.md](../authority/commercial-mandate-v0.1.md) | 2, 3, 3.1, 3.2, 3.3, 4, 5, 6, 6.1, 7, 8, 9, and required negative tests 1 to 26 in section 12 | The whole authority chain other than the invitation-onboarded path |
| [obligation-v0.1.md](../agreement/obligation-v0.1.md) | 2, 3, 3.1, 4, 5, 5.1, 5.2, 5.4, 5.5, 6, 6.1, 6.2, and 7 | The complete obligation lifecycle. Every act in it is signed by the obligor or the obligee |
| [evidence-verification-v0.1.md](../evidence/evidence-verification-v0.1.md) | 2, 3, 3.1, 3.2, 4 steps 1 to 7, 5, 6, and 8 | The evidence reference shape and the verification procedure, subject to the two limits in section 4.4 |
| [pilot-transaction-state-machine-v0.1.md](../negotiation/pilot-transaction-state-machine-v0.1.md) | 1, 3 for `draft` and for the states from `agreement_pending` onward, 5 for the `agreement.direct` row and for the transitions from `agreement_pending` onward, 5.2, 5.3, 6, 6.1, 6.2 other than the operated selection stated in its last three paragraphs, 7, 8.1, 9, 10, and required tests 4 to 6, 8 to 14, 22 to 25, 26, and 27 in section 11 | Direct formation, the session state machine, the offer rules, the commitment, performance, amendment and termination transitions, the dual-held transaction record, and replay |
| [determination-v0.1.md](../disputes/determination-v0.1.md) | 2 in its bilateral reading, 3 other than a dispute whose respondent is the venue operator, 4, 4.1, 4.2, 4.3, 5, 6, and 9 | Raising a dispute against a counterparty, issuing a determination as the determiner the rules in force name, and checking whether a determination follows from its rules and inputs |
| [settlement-handoff-v0.1.md](../fulfillment/settlement-handoff-v0.1.md) | 2, 2.1, 2.2, 2.3, 3, 3.1, 3.2, 4, 4.1, 4.2, 5, 6, and 7 | The handoff interface. A payment rail is an external system, not an operator, and no rule in these sections requires a party other than the payer and the payee |
| [transaction-profile-extension-model-v0.1.md](../schemas/transaction-profile-extension-model-v0.1.md) | Profile resolution and validation as exercised by section 8 of the canonical model | Two parties referencing the same profile version, and failing closed on one that does not resolve |
| [conformance-grades-v0.1.md](conformance-grades-v0.1.md) | 2, 3, 4, 5, and 6, read under section 6 of this document | Grade emission and consumption, with the dimension reading this document fixes |
### 4.3 Fixtures in this scope
The partition is by family pattern, not by enumerated filename. A fixture added to a family below joins this scope with no change to this document; only a fixture that starts a new family requires a placement decision, which is made by the proposal that adds it. An earlier revision enumerated filenames with restated counts, and the enumeration was stale within a day of the fixture set growing; the pattern form is what stops that recurring.
The manifest is the source for how many fixtures each scope holds; a count restated here would go stale on the next addition, which is the defect this revision removed.
The patterns in this section and in section 5.3 are read as filename globs against the fixture set, and together they are **total and disjoint**: every fixture on disk matches at least one pattern, and no fixture matches a pattern in both sections or two patterns in one. A fixture matching no pattern is ungradeable in either scope, and a fixture matching two is gradeable under a claim its authorship does not support. Both were true of this partition before this revision, and both are checkable mechanically, which is the only form in which the property survives.
| Family | Pattern | What it exercises |
|---|---|---|
| Mandate | `valid-commercial-mandate.json`, `negative/mandate-*.json` | Parse, interval, boundedness, scope, subject, constraint registries, status transport |
| Delegation chain | `valid-mandate-delegation-chain.json`, `negative/delegation-chain-*.json` | Monotonic narrowing on every axis of the mandate specification section 7 |
| Offer | `valid-offer*.json`, `negative/offer-*.json` | Terms structure, money and percentage representation, profile resolution, private-field boundary, and the session identifier the offeror mints |
| Agreement | `valid-agreement-*.json`, `negative/agreement-*.json` | Dual signature, terms-hash recomputation, amendment through a fresh offer and acceptance, and direct formation with no venue |
| Obligation | `valid-obligation*.json`, `negative/obligation-*.json` | Due-condition typing, assertion and response binding, partial acceptance, rejection registry |
| Evidence and verification | `valid-evidence-*.json`, `valid-verification-report-*.json`, `negative/evidence-*.json` | The reference shape, the seven-step procedure, chains, replay, reports over a party-held record, selective disclosure |
| Dispute and determination | `valid-dispute.json`, `valid-determination.json`, `negative/dispute-*.json`, `negative/determination-*.json` | Raising against a counterparty, subject binding, grounds, supersession, effect, state result, with a party determiner throughout |
| Settlement | `valid-settlement-instruction.json`, `negative/settlement-*.json` | Rail registry, trigger binding, custody refusal |
| Termination | `valid-consensual-termination.json`, `negative/termination-*.json` | Consensual close by both signatures, obligation release, rules-version replay |
| Carrier declaration | `valid-extension-declaration.json`, `negative/declaration-*.json` | Capability declaration, version compatibility, uniform refusal |
| Key record | `valid-key-record.json`, `negative/key-record-*.json` | Registered suites, public members only, lifecycle interval |
| Approval | `valid-approval.json` | Binding one exact action hash to a named principal |
| Party family | `valid-organization.json`, `valid-agent.json`, `valid-principal.json`, `negative/organization-*.json`, `negative/agent-*.json`, `negative/principal-*.json` | The objects at the root of the authority chain: legal-entity reference, agent binding and custody disclosure, and a principal carrying a role without personal data |
| Envelope annotation | `negative/action-envelope-with-kernel-annotations.json`, `negative/envelope-*.json` | The closed envelope, in its refuse direction |
| Policy decision | `negative/policy-deny-visible-to-counterparty.json` | Denied-action privacy toward the counterparty, on a decision the acting party issued about its own action |
| Conformance grade | `valid-conformance-grade-bilateral-scope.json`, `negative/grade-scope-*.json`, `negative/grade-bilateral-*.json` | A grade naming this scope, and the refusals of section 3.2 and section 6.3 read on one |
A family's **identifier**, used by the `established_from` and `held_out_coverage` members of the grade object under section 4.1 of [conformance-grades-v0.1.md](conformance-grades-v0.1.md), is its name in the Family column above, lowercased with spaces replaced by hyphens: `delegation-chain`, `evidence-and-verification`, `party-family`. A grade that names a family from the other scope's table is the overclaim of section 6.3.
The three scope-refusal negatives sit here rather than in section 5.3 because none of them names a scope that resolves, so none can be placed by the scope it claims, and the duty they exercise is item 2 and item 5 of section 2, which binds any grade consumer. Refusing an unreadable grade requires no operator-issued object, no ordered stream, and no third participant, so the authorship test in section 5.4 does not reach them and they stay here. The two overclaim negatives name the bilateral scope and are placed by that, on the same rule as the valid grade above them.
The envelope-annotation and policy-decision rows straddle the boundary and are placed here for the reason given in section 4.4. The appeal fixtures are not in this scope even though they are determination-shaped: an appeal runs on the route the issuing venue resolves, which is the operated capability named in section 5.
The carrier-declaration row named `negative/extension-*.json` until this revision, and the fixtures it meant to name are `negative/declaration-*.json`. The pattern matched nothing and the two fixtures matched no pattern, so the family was in neither scope. The delegation-chain fixtures were renamed from `negative/mandate-chain-*.json` in the same revision, because that name was matched by the mandate row's pattern as well as by its own and the two families are graded through different fixture kinds.
### 4.4 Nothing in this scope requires an operator
Every inclusion above was checked against the document that defines it, using one test: does the specification require a party other than the two to author an object, to sign one, to order a stream, or to issue a status? Where the answer is yes for any part of a section, that part is named below rather than left inside a general claim.
| Inclusion | Why it holds without an operator | The part that does not, and where it goes |
|---|---|---|
| Mandate chain | A root mandate is issued by the represented organisation's own principal, and every delegation is issued by the parent's subject. Section 11.1 of the mandate document forbids the operator from appearing in an invited party's chain at any depth, so the chain is never operator-completed in either scope | The invitation-onboarded root mandate of section 11, and required negative tests 27 to 32, are in the operated scope: they presuppose an invitation, a control-plane authored acceptance, or operator key custody |
| Evidence verification steps 1 to 7 | The document states that every step is executable by a third party holding the bundle, the schemas, the referenced rule set versions, and the declared keys, with no operator access at any step | Step 2 item 4 requires an invitation acceptance to carry an operator `object_issuance` signature. That clause is inert in this scope because no invitation acceptance is in it. Step 5 item 4 requires the referenced `PolicyDecision` to resolve. See the next row |
| Policy decision resolution at step 5 | Section 9.1 of the canonical model states who discharges the evaluator role: each party evaluates its own proposed actions and issues its own decision, bound to its own `action_hash` and signed under its own key. A control plane is that role deployed by an operator, never a fourth participant. A verifier resolves the decisions it holds | A decision the verifier was not given is reported as `not_checkable` under step 7, never as verified. A bilateral verification report therefore states this gap explicitly rather than closing it |
| Envelope annotation refusal | An agent-authored `action_envelope` carrying `kernel_annotations` is refused by the closed envelope shape, and either party executes that check on bytes it received | The allow direction, an object carrying annotations written after signing, presupposes a control plane and is in the operated scope. Section 9.1 rule 3 of the canonical model states that a bilaterally emitted object carries no annotations at all, because there is no ordering service whose observations they would record |
| Denied-decision privacy | The first of the two denied-decision rows of section 12 of the canonical model, that a `deny` decision is private to the actor and is disclosed to no counterparty, refused with `A202-DISCLOSURE-DENIED`. A counterparty that receives one has received a disclosure, and either party checks this on what it receives | The second row, that a `deny` decision consumes no sequence number in a shared stream, refused with the same code, is in the operated scope. It is a property of a service that assigns sequence numbers; on the record of section 8.1 of the state machine a denied action is simply never countersigned in. The invariant was one row carrying both meanings until 30 July 2026, and this scope had to split a row against itself to state the division |
| Aggregate entry at `agreement.direct` | Two parties who already found each other enter at `draft` and reach `agreement_pending` with no publication, no qualification, and no negotiation room. The guards are offer currency, an acceptance over the exact offer hash, and both parties' signatures, all of which the two parties hold. Rules version 1.3 registers the transition | The refuse direction where a session stream already exists on the transaction is in the operated scope, because the record it is refused against contains a stream an operator ordered |
| Aggregate transitions from `agreement_pending` onward | Commitment, performance, acceptance, settlement, exception, amendment, and termination are moved by acts of the two parties | `request.published`, `qualification.started`, `negotiation.opened`, and the five invitation self-loops are in the operated scope |
| Session state machine | The session transitions are guarded by offer currency, signatures, and approval binding, all of which the two parties hold. The session identifier is minted by the offeror on its own offer and adopted by the counterparty, under section 9.2 of the canonical model | Session stream creation, ordering, and per-stream isolation are in the operated scope. Bilaterally there is no session stream at all, and the transaction record is the dual-held chain of section 8.1 of the state machine |
| Settlement handoff | The payer and the payee are the two parties. A rail adapter is an external execution system, and the specification already states that connectivity to it confers no authority | An instruction naming an operator as payee for onward transmission is refused in both scopes by the non-custodial rule |
| Determination checking and party issuance | Step 6 of the verification procedure checks whether a stated outcome follows from referenced rules and inputs, and a party executes it on any determination it holds. Section 4 of the determination document requires a `determiner` and leaves who that is to the rules in force; where no venue is present the rules the parties reference name one of them, and the ordinary case is the respondent conceding the question against itself. The effect is still read from the rules and never from the determiner, so a party cannot concede itself more than the rules granted | Issuance of a determination by a venue operator, the appeal route it resolves, and a dispute whose respondent is the venue operator are in the operated scope |
One row left this table on 30 July 2026. `offer.selected` was included here on the reading that with exactly one session on the transaction, selection is an act of the selecting party over an offer it holds. That reading requires a session, and a session is created by `negotiation.opened`, which is an operator act; the transition's own required side effect in section 5 of the state machine freezes a single-award selection version, which resolves which of several rooms wins. The whole transition is now in the operated scope. Where a transaction was formed directly there are no rooms, no rivals, and nothing to select among, so a bilateral implementation that never emits `offer.selected` has skipped nothing it needed.
An implementation assessed in this scope MUST NOT be required to hold, produce, or resolve any operator-issued object in order to pass. A fixture that cannot be executed without one is not in this scope.
That sentence was false when it was written. Until 30 July 2026 the bilateral scope had no route past `draft` that did not run through publication, qualification, and a negotiation room; the determination and evidence families it claimed were authored and signed by the operator in every fixture; the offer it claimed carried control-plane annotations and a session identifier only an operator could mint; and the `PolicyDecision` its own row above says each party issues was assigned to the control plane by the object inventory. [A202-0014](../proposals/A202-0014-bilateral-formation-and-scope-repair.md) is the repair, and the property is now checkable rather than asserted: no fixture matching a pattern in section 4.3 carries an object authored, signed, ordered, or annotated by an operator.
## 5. The operated scope
`a202-scope/operated/0.1`
### 5.1 Capabilities in this scope
1. **Invitation onboarding through a control plane.** Issuing an invitation, delivering a claim secret on a channel, authoring an `InvitationAcceptance` under the operator's own mandate while the claimant attests to the same bytes, and bounding the invited party's own root mandate to the invitation's transaction.
2. **Operator key custody.** Custodying a subject key and requiring a bound principal approval for every act under it.
3. **Negotiation rooms and session streams.** Creating a session and its stream on `negotiation.opened`, ordering appends per stream, and returning a sequence conflict that carries the sequence of that stream only.
4. **Isolation across concurrent counterparties.** The property that no participant infers the existence, activity, or terms of another from content, sequence numbers, refusal codes, or timing, and the session event allowlist that carries it.
5. **Awards.** Selection among rival sessions, single-award integrity under contention, award as a transition distinct from acceptance, rule freezing at open, and the declared disclosure policies of a competitive event.
6. **Operated determination issuance.** Determinations issued by a venue, the appeal route resolved from the rules in force, and disputes whose respondent is the venue operator.
7. **Publication and qualification.** Moving a request to `published` and `qualifying`, and the directory and qualification side effects those transitions require.
8. **Control-plane annotation.** Attaching `kernel_annotations` to a minted object after signing, recording the policy decision, the session, the session sequence, and the received time outside the hashed bytes and outside every signature.
9. **Refusing the direct formation path where a negotiation is open.** A transaction carrying a session stream may not enter at `agreement.direct`, and the record that refusal is checked against holds a stream an operator ordered.
### 5.2 Specification sections in this scope
| Document | Sections in scope | Why an operator is required |
|---|---|---|
| [counterparty-invitation-v0.1.md](../discovery/counterparty-invitation-v0.1.md) | The whole document | Section 3 states the adopted resolution: the `InvitationAcceptance` is authored by the control plane under the operator's own mandate, and section 4.2 requires the operator's `object_issuance` signature alongside the claimant's. Without a control plane the record cannot exist |
| [commercial-mandate-v0.1.md](../authority/commercial-mandate-v0.1.md) | 11, 11.1, 11.2, 11.3, 11.4, and required negative tests 27 to 32 in section 12 | Each of these is a rule about a party onboarded by invitation or about an operator-custodied key |
| [auction-event-semantics-v0.1.md](../negotiation/auction-event-semantics-v0.1.md) | The whole document | Bid authority, award, disclosure policy, rule freezing, and isolation are all properties of an event run for several bidders by a party that is none of them |
| [pilot-transaction-state-machine-v0.1.md](../negotiation/pilot-transaction-state-machine-v0.1.md) | 2, 3 for `published`, `qualifying`, and `negotiating`, 5 for the publication, qualification, negotiation-opening, invitation, and `offer.selected` rows, 5.1, 6.2 for the operated selection stated in its last three paragraphs, 8 other than 8.1, and required tests 1, 2, 3, 7, 15, 16, 17, and 18 to 21 in section 11 | Two levels of state exist for confidentiality across concurrent counterparties. Per-stream concurrency and isolation are properties of the ordering service. Tests 1 to 3 traverse the publication, qualification, and negotiation entry path, which is why they are here and not in section 4 |
| [canonical-commercial-model-v0.1.md](../schemas/canonical-commercial-model-v0.1.md) | 2 and 15 in their operated reading, 11, 12.1, the `kernel_annotations` row of section 3 in its allow direction, the `kernel_annotations` clause of step 4 of section 9, and the invariants of section 12 refused with `A202-INVITATION-EXPIRED`, `A202-INVITATION-SCOPE-EXCEEDED`, `A202-INVITATION-CLAIM-UNSIGNED`, `A202-ASSURANCE-UNSUPPORTED`, and `A202-CUSTODY-APPROVAL-REQUIRED`, together with the session-stream row and the policy-decision reason-code row refused with `A202-DISCLOSURE-POLICY-VIOLATION` and the shared-sequence row refused with `A202-DISCLOSURE-DENIED` | Stream kinds, the session event allowlist, and control-plane authored annotations presuppose a control plane. The invariants are cited by refusal code and by row rather than by nickname, so that the division between this table and section 4.2 can be checked against section 12 rather than interpreted |
| [determination-v0.1.md](../disputes/determination-v0.1.md) | 2 in its operated reading, 7, 7.1, 7.2, 7.3, 7.4, and any determination whose `determiner` or `respondent_party` is the venue operator | An appeal route is resolved and operated by the party that issued the determination |
| [conformance-grades-v0.1.md](conformance-grades-v0.1.md) | 2, 3, 4, 5, and 6 in their operated reading, under section 6 of this document | The dimensions cover behaviour that only exists where an operator does |
### 5.3 Fixtures in this scope
The partition is by family pattern, on the same maintenance rule as section 4.3.
The manifest is the source for how many fixtures this scope holds, on the same rule as section 4.3.
| Family | Pattern | What it exercises |
|---|---|---|
| Invitation | `valid-counterparty-invitation.json`, `valid-invitation-acceptance.json`, `negative/invitation-*.json` | Control-plane authored onboarding, claim binding, assurance, custody approval |
| Session event | `valid-session-*.json`, `negative/session-event-*.json` | The session-stream allowlist shapes, their registered reference prefixes, and verification over a stream an operator ordered |
| Auction | `negative/auction-*.json` | Close-reason discipline, disclosure-bearing refusals, stream registry, award units |
| Stream disclosure | `valid-transaction-event-references.json`, `negative/transaction-event-*.json`, `negative/stream-*.json`, `negative/policy-deny-award-disclosing-reason.json` | The transaction-stream allowlist, rival non-inference on streams an ordering service holds, the refusal to read continuity across two such streams, and award state kept out of reason codes |
| Annotated offer | `valid-annotated-offer.json` | The allow direction of `kernel_annotations`: a control plane records the policy decision, the session, the session sequence, and the received time after signing, outside the hashed bytes |
| Direct formation contention | `negative/direct-formation-*.json` | The guard that keeps the direct entry path out of an open negotiation: a transaction carrying a session stream may not enter at `agreement.direct` |
| Appeal | `valid-appeal-determination-superseding.json`, `negative/appeal-*.json` | The appeal route a venue resolves, and operated determination issuance: grounds, window, supersession |
| Conformance grade | `valid-conformance-grade-operated-scope.json` | A grade naming this scope, with dimension A explicitly null because the operated coverage of authority handling is the invitation-onboarded path this assessment did not exercise |
Family identifiers are formed as section 4.3 states, from the Family column above: `session-event`, `stream-disclosure`, `direct-formation-contention`. The grade refusals themselves are fixtured in section 4.3, for the reason given there.
### 5.4 The authorship test
A capability is in this scope when at least one of the following holds, and for no other reason.
1. The specification requires an object to be authored or signed by a control plane or an operator, as the invitation acceptance is.
2. The specification requires a stream that neither party alone holds to be ordered, as the transaction stream and the session streams are.
3. The property being graded is only meaningful with a third participant, as rival non-inference and single-award contention are.
4. The specification assigns issuance to a venue, as an operated determination and its appeal route are.
Custody of a key is placed here by test 1: an operator-custodied key acts only under an approval the invited organisation's own principal bound to the exact action hash, and the custody arrangement is a fact about the operator.
## 6. Grading alignment
### 6.1 One grade, one scope
A grade is issued against exactly one role scope. The scope is named in the grade's `scope` field, and the identifiers in section 3.1 are the registry for the role part of that field.
A consumer MUST evaluate the role scope identifier before it evaluates `dimensions`, in the same way it MUST evaluate `expires_at` before relying on `dimensions` under [conformance-grades-v0.1.md](conformance-grades-v0.1.md) section 4.
### 6.2 A grade claims nothing outside its scope
1. A grade against `a202-scope/bilateral/0.1` makes no claim about any capability in section 5. It says nothing about invitation onboarding, key custody, session stream ordering, isolation across concurrent counterparties, awards, or operated determination issuance.
2. A grade against `a202-scope/operated/0.1` makes no claim about any capability in section 4 that its assessment did not exercise.
3. A relying party MUST NOT infer a band in one scope from a band in the other, and a grade MUST NOT be presented in a way that invites the inference.
### 6.3 Dimensions are read within the named scope
All five dimensions are still reported. A grade MUST report dimensions A to E under [conformance-grades-v0.1.md](conformance-grades-v0.1.md) section 2, and this document does not change that.
1. A band in a dimension is established only from the sections and fixtures the named scope contains. A dimension with no coverage in the named scope is reported as unassessed, explicitly null, never omitted and never inferred.
2. Dimension B, disclosure and isolation, has a materially different meaning in each scope. In the bilateral scope it covers denied-action visibility to the counterparty, private field leakage in shared objects, and refusal codes that carry no state the recipient may not see. It does not cover rival non-inference, which has no meaning where there are no rivals. A band 1 in dimension B on a bilateral grade MUST NOT be read as a statement about rival non-inference.
3. A grade that reports a band established from fixtures outside the named scope is refused with `A202-GRADE-SCOPE-OVERCLAIM`. This is the case where a bilateral assessment reports an operated dimension, and it fails rather than being narrowed on the reader's behalf.
4. `held_out_coverage` reports coverage and case count within the named scope, and coverage in one scope is not coverage in the other.
### 6.4 Absence in a scope is unassessed
A subject holding a grade in one scope and no grade in the other is unassessed in the second. A consumer MUST read the absence as unassessed and MUST NOT read it as a pass, as a fail, or as a statement that the subject does not implement that scope.
This is the same rule the set applies to a null dimension and to a check that could not be executed. Absence of an assessment reads as unassessed everywhere it is consumed.
## 7. Two-party gradeability
### 7.1 What two organisations can establish about each other
Two organisations with no third party present can each establish the following about the other, and MAY issue a grade stating it.
1. **The published suite result.** Each runs the published fixture set, the manifest, and the runner in this directory against the objects the other emits, restricted to the bilateral scope families in section 4.3. The set is open and the runner is normative, so both parties execute the same checks.
2. **The verification procedure result.** Each executes steps 1 to 7 of [evidence-verification-v0.1.md](../evidence/evidence-verification-v0.1.md) section 4 over the records the other produced, and produces a report with per-check results in the three-valued output.
3. **Refusal behaviour on the negative direction.** Each presents the refused cases in the bilateral families and observes whether the other refuses with the stated code or guesses.
A grade issued on that basis is a statement by the party that issued it, about one implementation, scoped to one specification version and one role scope. It is not certification by any body.
Section 6 of [conformance-grades-v0.1.md](conformance-grades-v0.1.md) makes every grade a determination and every determination appealable, and section 4 of [determination-v0.1.md](../disputes/determination-v0.1.md) makes `appeal_route_ref` REQUIRED on the determination that carries it. The grounds and the effect rules of that document apply to a peer-issued grade as they apply to any determination. **The appeal route does not.** An appeal route is resolved and operated by the party that issued the determination, and section 5.2 of this document places that capability in the operated scope; where the issuer is the counterparty, the route resolves to the counterparty. This document does not state what a peer issuer puts in `appeal_route_ref`, and it does not claim that a peer-issued grade is appealable on the same terms as one a venue issued. The question is open and is the fourth item in section 10. Until it is answered, a peer-issued grade is a determination whose appeal route is unresolved, which is a stated gap rather than a resolved one.
### 7.2 What two organisations cannot claim
1. A result established entirely from the published set is band 1 for the dimensions it covered, and MUST NOT be reported as band 2 or band 3.
2. Bands 2 and 3 require the subject to be measured on inputs it was not given. A party that was given every input it was measured on has not been measured at those bands, whoever ran the suite. This document states the constraint and does not state how such inputs come about, which is outside it.
3. A self-run result is not a grade, under [conformance-grades-v0.1.md](conformance-grades-v0.1.md) section 5 rule 1. A peer-run result is a grade issued by the peer, and a relying party weighs it knowing who ran it.
4. Neither party may report a band in any dimension of the operated scope on the strength of a bilateral assessment. That is the overclaim refused in section 6.3.
### 7.3 The floor this sets
Two organisations that each hold a band 1 bilateral grade issued by the other have established that each refuses the published negative cases in the bilateral families and that each produces records the other can verify from bytes. They have established nothing about behaviour under inputs neither of them constructed, and the grade says so by carrying the band rather than a summary.
## 8. Refusal codes
All fail closed. These extend the table in [pilot-transaction-state-machine-v0.1.md](../negotiation/pilot-transaction-state-machine-v0.1.md) section 10.
| Code | Meaning |
|---|---|
| `A202-GRADE-SCOPE-UNKNOWN` | A grade names a role scope identifier that does not resolve in the registry in section 3.1 |
| `A202-GRADE-SCOPE-INVALID` | A grade names no role scope identifier, or names more than one |
| `A202-GRADE-SCOPE-OVERCLAIM` | A grade reports a band in a dimension established from sections or fixtures outside the scope it names |
A refusal under any of the three is a refusal of the grade. It is not a band 0 and MUST NOT be recorded as one, because an unreadable grade is an absence of assessment rather than a failed one.
## 9. What this document does not do
It does not create a new conformance level, a certification, or a tier. It does not state that either scope is a step toward the other, and an implementation may be assessed in either, both, or neither.
It does not describe how an assessment is operated, how held-out material comes about, how an assessor is chosen, or what an assessment costs. None of that is protocol.
It does not move any rule between documents. Every section cited above stays where it is and means what it meant.
## 10. Open questions
- Whether a subject holding grades in both scopes, at different bands and different issue dates, is evaluated by a relying party as two independent statements or as one. The existing open question in [conformance-grades-v0.1.md](conformance-grades-v0.1.md) section 7 asks the same thing about profiles and transports, and one answer should serve both.
- Whether the bilateral scope should be split further, so that a party that verifies but never issues can be graded separately from one that does both. The argument for is that verification is the capability a relying party most needs. The argument against is that a registry of narrow scopes is a registry nobody reads.
- Whether a grade issued by a counterparty should record the assessment relationship in the object, given that a peer assessor is not disinterested. Recording it exposes the relationship; not recording it lets a reader assume independence that does not exist.
- How a dispute over a peer-issued grade resolves when both parties to it are also the parties to the transaction the grade covers, and what a peer issuer puts in the REQUIRED `appeal_route_ref` of the determination that carries the grade. The appeal route in section 7 of the determination document assumes an issuer that is not the counterparty, and section 5.2 of this document places operating one in the operated scope. Section 7.1 states the gap rather than closing it, because closing it means designing an appeal path for two parties with no third between them, and that is a proposal rather than a sentence.
---
Source: https://a202.org/GOVERNANCE.md
# Governance
**Status:** Informative in full. This document describes how the project is run. It states no requirement on an implementation of the specification.
**Applies to:** the v0.x series. It is reviewed under the conditions in section 7.
## 1. Project status
A202 was initiated and is sponsored by Plural Worlds, referred to throughout this document as **the sponsor**.
The project is in its founding phase: it has a single maintainer and a single sponsor. This document records the constraints the sponsor has accepted for that phase, and section 7 defines the conditions under which the governance is reviewed and expanded. The processes below are the same ones that will apply as the contributor base grows, so that growth changes who participates rather than how decisions are made.
## 2. Roles
**Maintainers** are named individuals. Maintainer status is held by a person, not by an organisation and not by a job title at the sponsor. A maintainer who leaves the sponsor does not automatically lose maintainer status, and a person who joins the sponsor does not automatically gain it. Current maintainers are listed in [MAINTAINERS.md](MAINTAINERS.md).
**Contributors** are anyone proposing a change. Section 4 applies to all of them identically.
Additional governance bodies, such as a technical steering committee or working groups, are established as the contributor base grows, under the review process in section 7.
## 3. How a change lands
1. Every normative change lands by pull request. There is no direct push to the default branch for normative content.
2. Every normative change is reviewed by someone other than its author. An author cannot approve their own change, whoever the author is.
3. A normative change requires a proposal under the process in [proposals/README.md](proposals/README.md) before the pull request is opened.
4. A change to schemas, fixtures, the manifest, or the runner requires the conformance suite to pass, and any change in the pass or fail classification of a fixture requires an explicit statement of why the specification now says something different.
5. Editorial changes that alter no normative statement may land by pull request without a proposal, and are still reviewed by someone other than the author.
While the project has a single maintainer, requirement 2 cannot be satisfied for that maintainer's own changes. Such changes note on the pull request that independent review was not available. The requirement takes effect when a second maintainer is named in [MAINTAINERS.md](MAINTAINERS.md).
Requirement 4 is enforced by continuous integration: the conformance suite and the reference test suites run on every pull request and on every push to the default branch, under `.github/workflows/checks.yml`. Where a fixture changes its pass or fail classification, the statement of why the specification now says something different remains the responsibility of the author and the review.
## 4. Sponsor employees follow the same process
A maintainer or contributor employed by the sponsor uses the same pull requests, the same proposals, and the same review requirement as anyone else.
Where a change originates in the sponsor's own implementation work, that origin is stated in the proposal, as context for reviewers.
## 5. Technical and commercial decisions are separate
The sponsor makes commercial decisions about its own products, pricing, customers, and roadmap. None of those are decisions of this project, none of them are recorded here, and none of them are a reason to accept or reject a technical change.
Conversely, a technical decision of this project is not a commitment by the sponsor to build anything, and the sponsor's implementation is not the definition of correct behaviour. Where the sponsor's implementation and this specification disagree, the specification is what a counterparty relies on.
The sponsor exercises no technical control outside the process in section 3. A change that is rejected is rejected in the open, on the pull request, with a stated reason.
## 6. What the sponsor reserves
Four rights are reserved to the sponsor. This list is exhaustive.
| Reserved right | Scope |
|---|---|
| **Legal compliance** | The sponsor may decline or remove content that exposes it to legal risk it is not willing to carry, including export control, sanctions, and defamation. Exercised in the open, with the category of concern stated |
| **Security embargo** | The sponsor may delay publication of a change that would disclose an unpatched vulnerability before coordinated disclosure completes. See [SECURITY.md](SECURITY.md) |
| **Trademark stewardship** | The sponsor holds and defends the project's names and marks, and controls their use in a way that could imply endorsement. Trademark control is not editorial control over the specification text. The terms are in [TRADEMARK.md](TRADEMARK.md) |
| **Funding** | The sponsor decides what it funds, and may stop funding at any time. Funding decisions are not a channel for directing the specification outside the process in section 3 |
Everything not on this list is decided by the process in section 3.
## 7. When this document is reviewed
This governance is designed for a project with one sponsor and no external maintainers, and it is reviewed when that stops being true. Any one of the following triggers a review:
1. **Three independent implementations** of the specification exist, built by parties that are not the sponsor.
2. **Two external maintainers** hold maintainer status, from different organisations, neither of them the sponsor.
3. **A stable 1.0 approaches.** Version 1.0 carries a compatibility commitment that outlives any single sponsor, and the governance has to be capable of holding that commitment before the version claims it.
4. **A standards body adopts the work**, in whole or in part, at which point that body's process governs the adopted part and this document states the boundary.
A review under any of these produces either an amended governance document or a statement of why the current one still holds.
An antitrust policy is required before competing organisations participate in a structured way, and is drafted as part of the legal work that precedes that participation.
---
Source: https://a202.org/RELEASES.md
# Release policy
**Status:** Mixed. Sections 2, 3, and 4 are **normative**. Sections 1, 5, and 6 are **informative**.
## 1. The specification set versions as one thing
The specification documents, the schemas, the fixtures, the manifest, and the runner version together, as a single set, under one version number.
They are versioned together because they are only meaningful together. A schema without the invariants that schema validation cannot express is an incomplete statement of the rules. An invariant without a fixture is a rule nobody can be held to. A fixture without the runner is a file. Versioning them separately would let an implementer combine a schema from one release with fixtures from another and believe they had a coherent target.
Individual objects inside the set carry their own identifiers. A transaction profile versions independently of the kernel, as the canonical model states. Neither of those is a separate release.
## 2. Semantic versioning
The set uses semantic versioning, `MAJOR.MINOR.PATCH`, interpreted against what a conformant implementation has to do.
| Increment | When |
|---|---|
| **MAJOR** | A change that can make a previously conformant implementation non-conformant. Removing or narrowing a permission, adding a requirement, tightening a schema, changing the meaning of an existing field, or reclassifying a fixture from positive to negative |
| **MINOR** | A change that adds capability without invalidating a conformant implementation. Adding an optional field, adding a new object type, adding a fixture that exercises previously unspecified behaviour, adding an error code for a case previously undefined |
| **PATCH** | A change that alters no normative statement. Editorial corrections, clarifications whose meaning is unchanged, and fixes to examples |
A change that is genuinely ambiguous between MAJOR and MINOR is treated as MAJOR. An implementer who upgrades expecting compatibility and does not get it has been told something false by the version number, which is the one failure the scheme exists to prevent.
## 3. What a release is
A release is not a commit. A release MUST consist of all four of the following, published together:
1. **A tag** on the released commit, of the form `vMAJOR.MINOR.PATCH`.
2. **A digest for every schema file** in the release, so that an implementer can verify that the schema it validates against is the schema that was released. Any schema whose digest differs from the released digest is not that release.
3. **The conformance manifest** as released, naming every fixture in the set and its expected classification.
4. **Release notes** naming every `A202` identifier carried by the release, and, for a MAJOR release, the migration notes required by section 4.
A release whose conformance suite does not pass against its own schemas and fixtures MUST NOT be tagged.
## 4. Compatibility
An implementation declares two versions, and they may differ.
| Declaration | Meaning |
|---|---|
| **Read version** | The versions of the specification set whose objects the implementation accepts and validates |
| **Write version** | The single version of the specification set whose objects the implementation produces |
An implementation MUST declare both. A counterparty needs to know what it may send as well as what it will receive, and an implementation that declares one number is telling only half of that.
An implementation MAY read more versions than it writes. It MUST NOT write a version it cannot read.
### Before 1.0
Pre-1.0 releases may make breaking changes on a MINOR increment, which is the standard meaning of a `0.x` version.
A breaking change before 1.0 MUST still carry migration notes: what changed, which objects are affected, what an implementation has to do differently, and whether previously issued objects remain valid. The freedom is to break compatibility, not to break it silently.
### At and after 1.0
Version 1.0 is the point at which the compatibility guarantee in section 2 becomes binding. A breaking change after 1.0 requires a MAJOR increment, migration notes, and a stated period during which the previous MAJOR version continues to be a valid target.
The governance under which 1.0 is declared is reviewed before it is declared. See [GOVERNANCE.md](GOVERNANCE.md) section 7.
## 5. Pre-release status
No release has been made. The current contents are `v0.1` working documents, not a tagged release of the set. The policy above applies from the first release.
## 6. Where the release notes accumulate
The notes section 3.4 requires are written as changes land, in [CHANGELOG.md](CHANGELOG.md), under an Unreleased heading. At a release that heading is replaced by the version and the date, the tag is cut on that commit, and a fresh Unreleased section is opened above it.
They accumulate as changes land, so that each entry names the proposal the change landed under and states what an implementer has to do differently.