MatchDecision — Decision Type Specification
Generated from
specification/state-machines/match-decision.md. Edit the canonical source file, not this page.
RFC 2119 normative language applies throughout.
MatchDecisionis an append-only record. It has no lifecycle state machine. Once recorded, aMatchDecisionMUST NOT be mutated or deleted. This document describes the decision types, their preconditions, postconditions, and how the decision record drives theMatchProposalstate machine.
1. Nature of the Record
Section titled “1. Nature of the Record”A MatchDecision is an immutable decision event appended to a proposal. Its purpose is to record the explicit, auditable intent of an authorized actor at a specific point in time. The broker evaluates the set of decisions against the requiredDecisionPolicy of the linked MatchProposal and triggers the appropriate proposal state transition.
There is no status field on MatchDecision. The decision itself is the terminal artifact.
2. Decision Types
Section titled “2. Decision Types”decision Value |
Meaning |
|---|---|
accept |
The deciding organization accepts the proposed match. If policy is satisfied, the proposal transitions to accepted. |
reject |
The deciding organization declines the proposed match. The proposal transitions to rejected. |
withdraw |
The deciding organization retracts a previously submitted accept decision, provided the proposal has not yet been committed. See withdrawal rules below. |
accept
Section titled “accept”An accept decision signals that the deciding organization agrees to the terms of the MatchProposal as of the observed proposal version.
Preconditions:
- Authorization: Actor belongs to
organizationIdnamed in the decision. The organization MUST be a required party underrequiredDecisionPolicy. - Scope required:
matches:decide - Proposal state:
MatchProposalMUST be inopenstate. A decision against a non-openproposal MUST return422withinvalid-state-transition. - Version check:
observedProposalVersionMUST match the currentresourceVersionof the proposal. A mismatch MUST return409 Conflictwithstale-resource-version. The server MUST NOT silently apply a decision made against a changed proposal. - Idempotency: A duplicate
acceptfrom the same organization with the sameIdempotency-Keyreturns the original record. A secondacceptfrom the same organization without an idempotency key MUST return409 idempotency-conflictif one already exists.
Postconditions:
- Decision record appended with
decidedAttimestamp,decidedByactor reference, andobservedProposalVersion. - Broker re-evaluates whether all required
acceptdecisions are now recorded. If policy is satisfied, the proposal transitions toacceptedatomically. - Event
pcx.match-decision.recorded.v1emitted.
reject
Section titled “reject”A reject decision signals that the deciding organization declines the proposed match and the negotiation for this proposal ends.
Preconditions:
- Authorization: Actor belongs to
organizationId. The organization MUST be a required party underrequiredDecisionPolicy. - Scope required:
matches:decide - Proposal state:
MatchProposalMUST be inopenstate. - Version check:
observedProposalVersionMUST match currentresourceVersion. - Policy check:
reasonCodeis optional but SHOULD be provided.commentis access-controlled and MAY be provided.
Postconditions:
- Decision record appended with
decidedAtandreasonCode. - Proposal transitions to
rejectedatomically. - Event
pcx.match-decision.recorded.v1emitted. Eventpcx.match-proposal.rejected.v1emitted on the proposal.
withdraw
Section titled “withdraw”A withdraw decision retracts a previously recorded accept decision from the same organization, provided the proposal has not yet been committed (i.e. is still open).
Critical concurrency rule: A
withdrawdecision MUST NOT reverse a proposal that has already reachedacceptedstate. If the proposal isacceptedby the time thewithdrawis processed, the server MUST return422 Unprocessable Entitywithinvalid-state-transition. A committed match cannot be unwound by a delayed withdrawal.
Preconditions:
- Authorization: Actor belongs to
organizationId. - Scope required:
matches:decide - Proposal state:
MatchProposalMUST still be inopenstate. Ifaccepted, reject with422 invalid-state-transition. - Prior decision: An
acceptdecision from the sameorganizationIdMUST exist on this proposal. If not, return422. - Version check:
observedProposalVersionMUST match currentresourceVersion.
Postconditions:
- Withdrawal decision record appended (the prior
acceptrecord is NOT deleted; the log is append-only). - If policy was previously satisfied, the policy-satisfaction state is re-evaluated; proposal status remains
openunless anotheracceptdecision now meets the threshold without this withdrawal. - Event
pcx.match-decision.recorded.v1emitted.
3. Decision Record Fields
Section titled “3. Decision Record Fields”| Field | Type | Required | Description |
|---|---|---|---|
id |
string (UUID) | yes | Stable immutable identifier for this decision record. |
proposalId |
string | yes | Reference to the MatchProposal. |
organizationId |
string | yes | The deciding organization. |
decision |
enum (accept, reject, withdraw) |
yes | The decision type. |
decidedAt |
RFC 3339 timestamp | yes | When the decision was recorded (server-assigned). |
decidedBy |
actor reference | yes | The specific user or system acting on behalf of the organization. |
reasonCode |
string (vocabulary TBD) | no | Structured reason code; REQUIRED for reject. |
comment |
string | no | Human-readable comment; access-controlled, not exposed to all parties. |
observedProposalVersion |
integer | yes | The resourceVersion of the proposal at decision time. |
4. Invariants
Section titled “4. Invariants”- Append-only: No
MatchDecisionrecord MUST be mutated or deleted after recording. - Stale-version guard: The server MUST reject any decision where
observedProposalVersiondoes not match the currentresourceVersionof the proposal. - No post-commit withdrawal: A
withdrawdecision MUST be rejected if the proposal is alreadyaccepted. A committed match is irrevocable through the decision API; it requires a separate cancellation flow on theExecutionReference. - Policy evaluation atomicity: The check of whether all required decisions meet
requiredDecisionPolicyMUST be performed within the same transaction as the decision append. Double-firing of theaccepttransition is prohibited. - Comment access control: The
commentfield MUST only be returned to the organization that authored it or to authorized protocol operators. It MUST NOT be disclosed to counterparty organizations. - Single decision per org per proposal (accept/reject): Each organization MUST record at most one non-withdrawn
acceptorrejecton a givenMatchProposal. Duplicate detection uses theorganizationId+proposalIdcombination.