MatchProposal — Lifecycle State Machine
Generated from
specification/state-machines/match-proposal.md. Edit the canonical source file, not this page.
RFC 2119 normative language applies throughout.
1. States
Section titled “1. States”| State | Meaning |
|---|---|
open |
Proposal created and active; awaiting decisions from required parties. |
accepted |
All required MatchDecision records agree to accept; the match is committed. |
rejected |
At least one required party has rejected the proposal; the match will not proceed. |
expired |
validUntil passed before a conclusive decision was reached. |
superseded |
A newer proposal has been created for the same offer/request pair; this proposal is no longer actionable. |
withdrawn |
The broker or an authorized party has retracted the proposal before a decision. |
Terminal states: accepted, rejected, expired, superseded, withdrawn
2. State-Transition Table
Section titled “2. State-Transition Table”| From State | Command | To State | Notes |
|---|---|---|---|
| (none) | Broker creates proposal (system) | open |
Broker records a compatible offer/request pair. Proposal is immutable once created. |
open |
accept (triggered by MatchDecision records meeting policy) |
accepted |
All required decisions recorded; policy satisfied. Atomic. |
open |
reject (triggered by any rejecting MatchDecision) |
rejected |
Any required party rejects; match cannot proceed. |
open |
expire (system, clock) |
expired |
validUntil reached with no conclusive decision. |
open |
supersede (system, on new proposal for same pair) |
superseded |
A newer proposal is created; existing open proposal is closed. |
open |
withdraw (broker or authorized party) |
withdrawn |
Broker retracts the proposal before decision. |
Invalid Transitions
Section titled “Invalid Transitions”Any command applied to a terminal state MUST return 422 Unprocessable Entity with error type urn:freyt:protocol:error:invalid-state-transition. A MatchProposal is immutable after creation; its fields MUST NOT be updated. A newer rationale requires a new proposal, transitioning this one to superseded.
3. Preconditions
Section titled “3. Preconditions”Proposal creation (open)
Section titled “Proposal creation (open)”- Authorization: Broker system only.
- Scope required: Broker holds
matches:readandcapacity:read+loads:readon behalf of both sides. - Relationship state:
PartnerRelationshipbetween the carrier and shipper organizations MUST beactive. - Version check:
capacityOfferId+ observed offerresourceVersionANDloadRequestId+ observed requestresourceVersionMUST be recorded on the proposal at creation time. - Policy check:
requiredDecisionPolicyMUST be set (e.g.both-accept,carrier-only,shipper-only).validUntilMUST be set and MUST be in the future. The proposal MUST NOT disclose protected fields from either resource.
accept (open → accepted)
Section titled “accept (open → accepted)”- Authorization: Broker system, triggered when the count and roles of
MatchDecisionrecords withdecision: acceptsatisfiesrequiredDecisionPolicy. - Version check: The
observedProposalVersionon eachMatchDecisionMUST match the currentresourceVersionof the proposal. A stale decision returns409 Conflictwithstale-resource-version; the server MUST NOT silently apply a decision made against a changed proposal. - Policy check: All required organizations for the given
requiredDecisionPolicyMUST have anacceptdecision recorded. - Atomicity: The transition to
acceptedand the corresponding transitions onCapacityOfferandLoadRequestMUST commit atomically.
reject (open → rejected)
Section titled “reject (open → rejected)”- Authorization: Triggered by any
MatchDecisionrecord withdecision: rejectfrom a required party. - Version check:
observedProposalVersionMUST match currentresourceVersion. - Policy check: Rejection by a single required party is sufficient unless policy requires unanimous rejection.
expire (system command)
Section titled “expire (system command)”- Authorization: System clock process.
- Precondition: Current time ≥
validUntilandstatusis stillopen.
supersede (system command)
Section titled “supersede (system command)”- Authorization: Broker system only.
- Precondition: A new
MatchProposalreferencing the same(capacityOfferId, loadRequestId)pair is being created. All existingopenproposals for that pair MUST be transitioned tosupersededatomically with the creation of the new proposal.
withdraw (open → withdrawn)
Section titled “withdraw (open → withdrawn)”- Authorization: Broker system or an authorized protocol operator.
- Scope required:
matches:read+ operator flag. - Version check:
If-MatchMUST match currentresourceVersion. - Policy check: Withdrawal reason MUST be provided.
4. Postconditions
Section titled “4. Postconditions”| Command | Postconditions |
|---|---|
| Proposal creation | status = open; resourceVersion = 1; createdAt recorded; capacityOfferId + version + loadRequestId + version recorded; event pcx.match-proposal.created.v1 emitted. |
accept |
status = accepted; acceptedAt recorded; resourceVersion incremented; CapacityOffer status updated to matched or partiallyMatched; LoadRequest status updated to matched; ExecutionReference record created; event pcx.match-proposal.accepted.v1 emitted. |
reject |
status = rejected; rejectedAt recorded; rejecting organizationId and reasonCode noted; resourceVersion incremented; event pcx.match-proposal.rejected.v1 emitted. |
expire |
status = expired; expiredAt recorded; resourceVersion incremented; linked LoadRequest may revert to published if no other proposals remain. |
supersede |
status = superseded; supersededAt recorded; supersededByProposalId set; resourceVersion incremented. |
withdraw |
status = withdrawn; withdrawnAt recorded; withdrawal reason stored; resourceVersion incremented. |
5. Concurrency Rules
Section titled “5. Concurrency Rules”- Accept and withdraw racing (critical rule):
acceptandwithdrawcommands on the same proposal MUST be handled atomically within a single serializable transaction. Whichever acquires the exclusive lock first wins. A committedacceptedstate MUST NOT be reversed by a delayedwithdrawcommand. Ifwithdrawarrives after the proposal isaccepted, it MUST return422 Unprocessable Entitywithinvalid-state-transition. - Multiple decisions racing: Two
MatchDecisionrecords from different organizations may arrive concurrently. Each decision MUST be appended independently. The broker evaluates whether the policy threshold is met only after both are durably stored. The policy-satisfaction check MUST be serialized to prevent double-firing of theaccepttransition. - Supersede and accept racing: If a new proposal creation (which would supersede the current one) races with an
accepttrigger, the accept MUST take precedence if it commits first. A supersede MUST NOT overwriteaccepted. - Expire and accept racing: If
validUntilis reached at the same moment a decision is committed, the accept transition takes precedence if it commits within the same transaction boundary; otherwiseexpiredwins. - All proposal state transitions MUST be applied atomically.
6. Invariants
Section titled “6. Invariants”- Immutability after creation: A
MatchProposal’s fields (offer/request versions, compatibility data, rationale, policy) MUST NOT be modified afterstatusisopen. A correction requires a new proposal. - Version snapshot integrity:
observedCapacityOfferVersionandobservedLoadRequestVersionMUST be recorded on creation and MUST match the versions at creation time. - No protected field disclosure: The proposal MUST NOT surface protected fields (customer identity, exact address, price, complete goods details) unless the field-disclosure policy explicitly permits it.
- Decision policy completeness:
requiredDecisionPolicyMUST be set before a proposal is created and MUST NOT be changed. - Terminal state finality: No transition out of
accepted,rejected,expired,superseded, orwithdrawnis permitted. - Cascading consistency: When a proposal enters
accepted, the linkedCapacityOfferandLoadRequestMUST be updated in the same atomic operation.