Skip to content

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.

MatchDecision is an append-only record. It has no lifecycle state machine. Once recorded, a MatchDecision MUST NOT be mutated or deleted. This document describes the decision types, their preconditions, postconditions, and how the decision record drives the MatchProposal state machine.

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.

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.

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 organizationId named in the decision. The organization MUST be a required party under requiredDecisionPolicy.
  • Scope required: matches:decide
  • Proposal state: MatchProposal MUST be in open state. A decision against a non-open proposal MUST return 422 with invalid-state-transition.
  • Version check: observedProposalVersion MUST match the current resourceVersion of the proposal. A mismatch MUST return 409 Conflict with stale-resource-version. The server MUST NOT silently apply a decision made against a changed proposal.
  • Idempotency: A duplicate accept from the same organization with the same Idempotency-Key returns the original record. A second accept from the same organization without an idempotency key MUST return 409 idempotency-conflict if one already exists.

Postconditions:

  • Decision record appended with decidedAt timestamp, decidedBy actor reference, and observedProposalVersion.
  • Broker re-evaluates whether all required accept decisions are now recorded. If policy is satisfied, the proposal transitions to accepted atomically.
  • Event pcx.match-decision.recorded.v1 emitted.

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 under requiredDecisionPolicy.
  • Scope required: matches:decide
  • Proposal state: MatchProposal MUST be in open state.
  • Version check: observedProposalVersion MUST match current resourceVersion.
  • Policy check: reasonCode is optional but SHOULD be provided. comment is access-controlled and MAY be provided.

Postconditions:

  • Decision record appended with decidedAt and reasonCode.
  • Proposal transitions to rejected atomically.
  • Event pcx.match-decision.recorded.v1 emitted. Event pcx.match-proposal.rejected.v1 emitted on the proposal.

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 withdraw decision MUST NOT reverse a proposal that has already reached accepted state. If the proposal is accepted by the time the withdraw is processed, the server MUST return 422 Unprocessable Entity with invalid-state-transition. A committed match cannot be unwound by a delayed withdrawal.

Preconditions:

  • Authorization: Actor belongs to organizationId.
  • Scope required: matches:decide
  • Proposal state: MatchProposal MUST still be in open state. If accepted, reject with 422 invalid-state-transition.
  • Prior decision: An accept decision from the same organizationId MUST exist on this proposal. If not, return 422.
  • Version check: observedProposalVersion MUST match current resourceVersion.

Postconditions:

  • Withdrawal decision record appended (the prior accept record is NOT deleted; the log is append-only).
  • If policy was previously satisfied, the policy-satisfaction state is re-evaluated; proposal status remains open unless another accept decision now meets the threshold without this withdrawal.
  • Event pcx.match-decision.recorded.v1 emitted.
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.
  1. Append-only: No MatchDecision record MUST be mutated or deleted after recording.
  2. Stale-version guard: The server MUST reject any decision where observedProposalVersion does not match the current resourceVersion of the proposal.
  3. No post-commit withdrawal: A withdraw decision MUST be rejected if the proposal is already accepted. A committed match is irrevocable through the decision API; it requires a separate cancellation flow on the ExecutionReference.
  4. Policy evaluation atomicity: The check of whether all required decisions meet requiredDecisionPolicy MUST be performed within the same transaction as the decision append. Double-firing of the accept transition is prohibited.
  5. Comment access control: The comment field MUST only be returned to the organization that authored it or to authorized protocol operators. It MUST NOT be disclosed to counterparty organizations.
  6. Single decision per org per proposal (accept/reject): Each organization MUST record at most one non-withdrawn accept or reject on a given MatchProposal. Duplicate detection uses the organizationId + proposalId combination.