A202-0014: Bilateral formation, and the scope partition made true¶
Status: Fixtures and compatibility. Stage 3 of the five stages in README.md section 3. Every change in this proposal is implemented in the schemas, the runner's rule sets, the fixture set, the manifest, and the reference implementation, and the suite passes with the changes in place.
Date: 30 July 2026
Status of this document: Informative in full. It states no requirement on an implementation. The normative text this proposal adopts and amends is carried by the documents it names, each of which marks its own normative sections.
1. Problem¶
A review of the specification set, conducted 30 July 2026, examined conformance-role-scopes-v0.1.md against the documents it partitions. The document's central claim is one sentence, section 4.4:
An implementation assessed in this scope MUST NOT be required to hold, produce, or resolve any operator-issued object in order to pass.
The claim was false, and it was false in five independent ways at once. Each of them alone would have been enough. The failure class is the one A202-0009 closed in the runner, appearing this time in a partition: a document states a property, nothing checks the property, and the property is not true.
- There was no route into the aggregate. The only transition into
agreement_pendingwasnegotiatingtoagreement_pendingonoffer.selected, and the only route tonegotiatingrandrafttopublishedtoqualifyingtonegotiating. Publication adds a directory index, qualification opens a window against a profile, andnegotiation.openedcreates a session and its stream. All three are operator acts, and the state machine's own required tests 1 to 3, which the partition assigned to the bilateral scope, traverse them. Two organisations that already knew each other, held each other's mandates, and had agreed terms could not record an agreement at all. An implementation graded on the bilateral surface could not leavedraft. - The
PolicyDecisionowner was the control plane. The object inventory in section 5.2 of the canonical model assigned the object to the control plane. Replay requires every referenced decision to resolve, step 5 item 4 of the verification procedure requires it again, and both of those sections are in the bilateral scope. Meanwhile section 4.4 of the role-scope document asserted that each party issues its own decisions. No normative text said so, and the inventory said otherwise. The scope document was arguing with the model. - Section 9 of the canonical model was double-booked, and offers needed an operated session. The partition assigned section 9 wholesale to the bilateral scope. Step 4 of that section has the kernel mint the object and attach
kernel_annotations, and the allow direction of annotations is assigned to the operated scope by section 5.2 of the same partition. Section 9 also requires an offer payload to carrysession_id, the schema makes it REQUIRED, and a session was created only bynegotiation.opened. The bilateral reference offer carried operator-written annotations and an operator-minted session identifier. - The bilateral fixture families were authored by the operator.
valid-determination.jsonwas created by, signed by, and determined byorg_a202_operatorunderkey_a202_control_01, and so was every determination negative. The evidence family's report fixtures replayed a session stream the operator ordered and signed. The reference offer carried the control plane's annotations. Section 4.3 placed all of them in the bilateral scope while section 5.2 assigned venue determinations to the operated scope, and section 4.4 said the fixtures contained no operator-issued object. - The bilateral aggregate rode an operator-ordered stream. Section 5.4 item 2 of the partition names the transaction stream as ordered by something neither party holds, and the specification defined no other way to order aggregate events. Section 6.2 of the state machine, assigned to the bilateral scope, described appending
offer.selectedand pausing competing sessions, which is venue machinery on its face.
Four further defects were found in the same pass, mechanical rather than structural, and are repaired here because they are in the same sentences.
- A peer-issued grade was declared appealable "on the same terms ... unchanged". A grade is a determination,
appeal_route_refis REQUIRED on one, and the partition places operating an appeal route in the operated scope. Section 10 of the partition already recorded the resulting question as open, and section 7.1 claimed it closed. - Two fixtures matched no family pattern. The carrier-declaration row named
negative/extension-*.json; the fixtures arenegative/declaration-*.json. The row matched nothing, the fixtures were in neither scope, and the family was ungradeable. Four fixtures matched two patterns, becausenegative/mandate-chain-*.jsonis insidenegative/mandate-*.json. - Normative material was in neither scope. Required tests 22 to 25 of the state machine, added by A202-0010, were assigned to neither. Nor were section 2 and section 15 of the canonical model, or section 2 of the determination document, each of which states what an implementation has to do to conform.
- One invariant row was split against itself. Section 12 of the canonical model carried "a
denydecision is private to the actor and consumes no shared sequence" as one row with one code, and section 4.4 of the partition assigned its two halves to different scopes. Section 5.2 named the invariants it claimed by nickname, so the division could be read but not checked.
2. Change¶
- Direct formation, state machine sections 4, 5, and 5.3.
agreement.directmoves a transaction fromdrafttoagreement_pending, skippingpublished,qualifying, andnegotiating, guarded by four conditions: no session stream exists on the transaction, the referenced offer is current and unexpired, anAcceptancesigns the exact offer hash, and the offer and the acceptance each carry their author's signature. It reachesagreement_pendingand nevercommitted, because approval, authority, and the dual signature over the agreement bytes are separate acts under a separate guard. Skipping publication, qualification, and negotiation removes no check that bore on the agreement: those three steps make a request discoverable, decide who may bid, and isolate concurrent counterparties, and where there is one counterparty, already found and not concurrent with anyone, all three answer questions nobody asked. - Rules version 1.3 registers the transition. Versions 1.0, 1.1, and 1.2 stay immutable, so a record made under any of them replays against the set in force when it appended and the direct entry is illegal there. This is the treatment 1.2 gave
termination.agreed, and a fixture replaysagreement.directagainst 1.2 to prove it. - The guard is checked, not asserted. The runner refuses a bundle carrying an
agreement.directevent alongside a session-stream event on the same transaction, withA202-EVIDENCE-TRANSITION-ILLEGAL, and a fixture exercises it. 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. PolicyDecisionbelongs to the acting party, canonical model section 5.2 and new section 9.1. The owner is the acting party's own policy evaluator; a control plane is the operated deployment of that role and never a fourth participant. Section 9.1 states that where no operator is present each party's own runtime discharges the kernel role of step 4 over its own proposed actions, that each decision is bound to that party's ownaction_hashand signed under its own key, and thatkernel_annotationsare control-plane only and are absent from a bilaterally emitted object because there is no ordering service whose observations they would record. The claim section 4.4 of the partition was already making became true rather than being withdrawn.- The session identifier is party minted bilaterally, canonical model section 9.2.
session_idstays REQUIRED and the schema is unchanged. A bilateral exchange is a session in the sense the model uses the word, one relationship with one counterparty; what differs is who establishes it. The offeror mints theses_identifier on its first offer and the counterparty adopts it as it adopts the offer's other bytes. There is no session stream, no session sequence, and no ordering service. Where an operator is present the identifier is the operator's, and only the minting party differs. - The bilateral transaction record, state machine section 8.1. Where no operator is present the record is the hash-chained event sequence each party holds and countersigns, ordered by predecessor reference rather than by a counter, which is the mechanism evidence bundles already replay at step 4 of the verification procedure. Two consequences are stated: the shared-sequence rules apply where an operated stream exists and only there, and a fork is a disagreement rather than a sequence conflict, resolved as a dispute and never by preferring one's own chain, a timestamp, or a length.
- Section 6.2 of the state machine says which selection it is describing. Competing-session closure is the operated selection. On the direct path there are no rooms, no rivals, and no ordering service to pause them, so selection is moot and a bilateral implementation that never emits
offer.selectedhas skipped nothing it needed. Theoffer.selectedrow of section 4.4 of the partition moves wholly to the operated scope; its previous reading, that selection with exactly one session is a party act, required a session an operator had opened. - The fixture families are split by authorship. No fixture matching a bilateral family pattern carries an object authored, signed, ordered, or annotated by an operator. The reference offer loses its annotations and gains an operated counterpart that carries them; the determination family is re-authored to a party determiner; the report fixtures replay a party-held transaction record instead of an operated session stream; the session-stream report and the cross-stream continuity bundle move to the operated scope, where the streams they replay were always ordered.
- The partition is total and disjoint, and section 4.3 says so. Every fixture on disk matches at least one family pattern, and none matches two. The carrier-declaration pattern is corrected to
negative/declaration-*.json; the delegation-chain fixtures are renamed tonegative/delegation-chain-*.json, because the previous name was matched by the mandate row as well as by its own; required tests 22 to 25 join the bilateral scope with the two new ones; and section 2 and section 15 of the canonical model and section 2 of the determination document appear in both scope tables with a stated reading, as the conformance-grades sections already did. - The denied-decision invariant becomes two rows, canonical model section 12, and section 5.2 of the partition cites invariants by refusal code and by row rather than by nickname. Privacy toward the counterparty holds wherever a decision exists and is bilateral; the shared-sequence half is a property of a service that assigns sequence numbers and is operated. Both refuse with
A202-DISCLOSURE-DENIED, because a relying party's correct response to either is the same refusal. - Section 7.1 of the partition states the appeal-route gap. A peer-issued grade is a determination, and the grounds and effect rules apply to it. The route does not, because a route is resolved and operated by the issuer and the issuer here is the counterparty. The claim that a peer-issued grade is appealable "on the same terms ... unchanged" is withdrawn and replaced by a statement of the open question, which section 10 already carried and now carries in full.
- Section 15 of the canonical model stops restating fixture counts. The sentence declared the manifest the single source and then restated a count, which had gone stale by two proposals. The count is removed and the coverage list gains item 22, direct formation.
3. What this proposal does not change¶
No object changes shape. No schema changes: session_id stays REQUIRED on an offer payload, kernel_annotations stay optional and control-plane only, and the event data allowlists are untouched. No registered scope identifier changes meaning; both mean today what section 3.1 said they meant, and the bilateral one is now true of the fixtures assigned to it. No appeal system is designed, and no rule about who may determine a question is added: section 4 of the determination document already required a determiner and already read the effect from the rules in force rather than from the determiner.
4. Alternatives considered¶
Why not narrow the bilateral scope instead of adding a formation path? The partition could have been made true by deleting the claim: state that a bilateral assessment covers object shapes and refusals but no aggregate lifecycle, and move the state machine wholly to the operated scope. That is honest and it is also a statement that two organisations cannot transact under this specification without a venue, which contradicts section 1 of the partition, the charter, and the reason the scope exists. A scope whose answer to "can two parties do this alone" is no does not need a registry entry.
Why not let offer.selected run from draft? Reusing the existing event would have avoided a new transition and a new rules version. It would also have overloaded one event with two meanings, one of which requires an ordering service and one of which forbids it, and the required side effect in the transition table, freezing a single-award selection version, is meaningless where there is nothing to select among. Two names for two acts is the same choice the set already made in keeping acceptance, selection, approval, and agreement apart.
Why not edit rules version 1.2 to carry the transition? Editing a rules version in place is cheaper by exactly one table entry and it makes every record ever replayed against 1.2 replay against different rules. The set's position on this is settled: a window, an effect, and a legal transition are read from a hash-addressed version, and changing one would change the answer to a question that was already asked. 1.3 is registered and the earlier versions are immutable, and a fixture proves the direct entry is illegal under 1.2.
Why not let the direct path run while a negotiation room is open? Permitting it would remove the fourth guard and one fixture. It would also let a participant in a multi-counterparty negotiation form an agreement outside the room while rivals were still bidding in it, which defeats the single-award property the transaction stream exists to enforce. The guard fails closed, is checked by the runner rather than stated in prose, and its refuse direction is fixtured.
Why not keep the operator-authored determination fixtures and add bilateral ones alongside? Adding rather than re-authoring would have kept both readings fixtured at the cost of doubling the family. It would also have left the bilateral family holding operator-authored fixtures, which is the defect. The determination fixtures were operator-authored incidentally, because they were cut from one synthetic scenario, and nothing any of them exercises depends on who authored the object: effect overclaim, supersession forking, subject binding, and the not-following check are the same checks under any determiner. Operated determination issuance stays fixtured through the appeal family, which section 5.3 already held and which is operator-authored throughout, so no coverage is lost on either side.
Why strip the annotations from valid-offer.json rather than move it to the operated scope? Moving it would have disturbed the offer family's pattern, which covers valid-offer*.json and would then have had to exclude one of its own members by name. Stripping the annotations and adding valid-annotated-offer.json to the operated scope disturbs one manifest note and adds one entry, keeps the offer family's pattern intact, and has the side benefit of fixturing the allow direction of kernel_annotations explicitly, which nothing did before: the annotations rode on the reference offer, where they were incidental to what the fixture was for.
Why not resolve the peer-appeal question here? Because it is a design question about what an appeal means when there is no third party, and answering it in a sentence inside a partition document would be answering it badly. Section 10 recorded it as open before this proposal, and this proposal makes section 7.1 agree with section 10 rather than contradict it.
Why not do nothing? The claim in section 4.4 is the reason the bilateral scope exists. A grade issued against a202-scope/bilateral/0.1 tells a relying party that the subject transacts without a venue, and until this proposal an implementation could not have passed that assessment without one. Leaving it would mean shipping a registry entry whose meaning no assessment could establish, which is the defect the registry was built to prevent.
5. Compatibility¶
Under RELEASES.md section 2 the set is pre-release and pre-1.0, and section 5 of that document records that no release has been made. This change set is classified MAJOR-shaped, and the classification is by the rule rather than by feel.
It is MINOR-shaped where it adds: a transition, a rules version, two subsections of the canonical model, one subsection of the state machine, five fixtures, and two rows of the fixture partition. Nothing that was conformant before becomes non-conformant on account of any of those.
It is MAJOR-shaped in three places. Required tests 26 and 27 of the state machine are added requirements. The direct-formation guard is a new refusal the runner enforces. And section 3.3 of the role-scope document states its own rule: 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. The bilateral scope's fixture set changes, its capability list gains direct formation, and required tests 1 to 3 move out of it. By that document's own test this is a change to what a202-scope/bilateral/0.1 covers, and section 2 of RELEASES.md resolves an ambiguous case as MAJOR.
Nobody is owed a migration. No release exists, no grade has been issued against either scope identifier, and the repository was private when this landed. The migration surface, stated for the record, is four items: an implementation pinned to fixture paths sees six renames, listed in section 6; an implementation that emitted a bilateral offer carrying kernel_annotations stops, because a bilaterally emitted object carries none; an implementation that treated PolicyDecision as a control-plane object issues its own; and a verifier that resolved rules versions from a fixed table adds 1.3. Records made under 1.0, 1.1, and 1.2 replay unchanged, which is the point of registering 1.3 rather than editing 1.2.
6. Fixture plan¶
Implemented, not planned.
Added. valid-agreement-direct-formation, a bundle carrying a party-minted offer, an acceptance over its exact hash, a dual-signed agreement, and the agreement.direct and agreement.committed events under rules 1.3, with no object authored, ordered, or annotated by a control plane. valid-verification-report-bilateral, a report over that record naming the counterparty's own decision stream as a stated gap, which is the bilateral shape of partial disclosure. valid-annotated-offer, the allow direction of kernel_annotations, in the operated scope. negative/agreement-direct-under-prior-rules, the direct entry replayed against 1.2, refused with A202-EVIDENCE-TRANSITION-ILLEGAL. negative/direct-formation-over-open-session, the direct entry on a transaction that already carries a session stream, refused with the same code by the new guard, in the operated scope because the record it is refused against holds an operated stream.
Re-authored. valid-determination and every determination negative now carry a party determiner: the respondent conceding the question against itself, which is the determiner the rules in force name where no venue is present, and whose effect is still bounded by what those rules granted. negative/policy-deny-visible-to-counterparty is issued by the party whose action was denied, on the record both parties hold. The three evidence-report-* negatives replay the party-held transaction record instead of an operated session stream. negative/evidence-bundle-illegal-transition replays draft straight to committed, which no rules version permits and which is the refuse direction of the direct path. valid-offer loses its annotations.
Renamed. negative/mandate-chain-* to negative/delegation-chain-*, four fixtures, so that the delegation-chain family's pattern is not inside the mandate family's. negative/evidence-bundle-cross-stream-continuity-asserted to negative/stream-cross-continuity-asserted, and valid-verification-report-partial-disclosure to valid-session-verification-report-partial-disclosure, both moving to the operated scope under its patterns because both replay a stream an operator ordered.
The suite passes at the totals the manifest carries, with the reason assertion active and the partition total and disjoint over every fixture on disk.
7. Ordering¶
Depends on A202-0006, which defined the scopes this proposal repairs, and on A202-0010, whose rules version 1.2 this proposal extends rather than edits and whose required tests 22 to 25 it places. It touches the documents A202-0009 touched and relies on the reason-code assertion that proposal added to the runner. Nothing depends on this proposal.