CapacityOffer — Lifecycle State Machine
Generated from
specification/state-machines/capacity-offer.md. Edit the canonical source file, not this page.
RFC 2119 normative language applies throughout.
1. States
Section titled “1. States”| State | Meaning |
|---|---|
draft |
Offer assembled but not yet published. Visible only to the owning (carrier) organization. |
published |
Offer is live and visible to authorized partners. Available for matching. |
partiallyMatched |
One or more load requests have been tentatively matched but capacity is not fully committed. Further matching is still permitted. |
matched |
All available capacity is committed to one or more accepted matches. New matching is blocked. |
withdrawn |
Carrier voluntarily removed the offer before or after matching. No further commands are accepted. |
expired |
expiresAt passed before the offer was matched or withdrawn. No further commands are accepted. |
cancelled |
Administratively cancelled (e.g. due to policy violation or erroneous entry). No further commands are accepted. |
Terminal states: withdrawn, expired, cancelled
2. State-Transition Table
Section titled “2. State-Transition Table”| From State | Command | To State | Notes |
|---|---|---|---|
| (none) | POST /capacity-offers |
draft |
Owning organization creates the offer. |
draft |
publish (POST /capacity-offers/{id}/publish) |
published |
Carrier makes offer visible to authorized partners. |
published |
partialMatch (system) |
partiallyMatched |
System records a tentative match; capacity partially consumed. |
published |
fullMatch (system) |
matched |
System commits all available capacity to accepted matches. |
published |
withdraw (POST /capacity-offers/{id}/withdraw) |
withdrawn |
Carrier withdraws the live offer. |
published |
expire (system, clock) |
expired |
expiresAt reached with no accepted match. |
partiallyMatched |
fullMatch (system) |
matched |
Remaining capacity committed. |
partiallyMatched |
withdraw |
withdrawn |
Carrier withdraws; any tentative matches are cancelled. |
partiallyMatched |
expire (system, clock) |
expired |
expiresAt reached. |
matched |
unmatch (system, on match cancellation) |
partiallyMatched |
An accepted match is later cancelled releasing partial capacity. |
draft |
cancel (admin) |
cancelled |
Administrative cancellation before publication. |
published |
cancel (admin) |
cancelled |
Administrative cancellation after publication. |
partiallyMatched |
cancel (admin) |
cancelled |
Administrative cancellation; tentative matches voided. |
Invalid Transitions
Section titled “Invalid Transitions”Any command not listed above MUST return 422 Unprocessable Entity with error type urn:freyt:protocol:error:invalid-state-transition. Attempting to publish an already-published offer returns 409 Conflict with idempotency-conflict if the same Idempotency-Key is reused, or 422 otherwise.
3. Preconditions
Section titled “3. Preconditions”publish (draft → published)
Section titled “publish (draft → published)”- Authorization: Actor belongs to the owning organization.
- Scope required:
capacity:publish - Relationship state: Owning organization must have at least one
activepartner relationship unless the offer uses an open visibility policy. - Version check:
If-MatchMUST match currentresourceVersion. - Policy check:
availabilityWindow,originArea,destinationArea,vehicleProfile,availableCapacity,expiresAt, andvisibilityMUST all be present and valid.
withdraw (published | partiallyMatched → withdrawn)
Section titled “withdraw (published | partiallyMatched → withdrawn)”- Authorization: Actor belongs to the owning organization.
- Scope required:
capacity:publish - Version check:
If-MatchMUST match currentresourceVersion. - Policy check: Withdrawal reason MUST be provided. Accepted matches dependent on this offer MUST be transitioned to cancelled before or atomically with this withdrawal.
partialMatch / fullMatch (system commands)
Section titled “partialMatch / fullMatch (system commands)”- Authorization: Broker system only.
- Precondition: At least one
LoadRequestinpublishedstate is compatible and the relationship between orgs isactive. - Version check: Both offer and request
resourceVersionvalues MUST be observed before matching and included in theMatchProposal.
expire (system command)
Section titled “expire (system command)”- Authorization: System clock process.
- Precondition: Current time ≥
expiresAt. - Version check: No
If-Matchrequired for system clock transitions.
cancel (admin)
Section titled “cancel (admin)”- Authorization: Protocol operator with admin scope.
- Scope required:
capacity:publish+ operator flag. - Policy check: Cancellation reason MUST be recorded.
4. Postconditions
Section titled “4. Postconditions”| Command | Postconditions |
|---|---|
POST /capacity-offers |
status = draft; resourceVersion = 1; createdAt recorded. |
publish |
status = published; publishedAt recorded; resourceVersion incremented; event pcx.capacity-offer.published.v1 emitted. |
partialMatch |
status = partiallyMatched; matched capacity dimensions updated; resourceVersion incremented; event pcx.capacity-offer.partially-matched.v1 emitted. |
fullMatch |
status = matched; matchedAt recorded; resourceVersion incremented; event pcx.capacity-offer.matched.v1 emitted. |
withdraw |
status = withdrawn; withdrawnAt recorded; withdrawal reason stored; resourceVersion incremented; event pcx.capacity-offer.withdrawn.v1 emitted. |
expire |
status = expired; expiredAt recorded; resourceVersion incremented; event pcx.capacity-offer.expired.v1 emitted. |
unmatch |
status reverts to partiallyMatched (or published if no remaining matches); resourceVersion incremented. |
cancel |
status = cancelled; cancellation reason and actor recorded; resourceVersion incremented. |
5. Concurrency Rules
Section titled “5. Concurrency Rules”- All state-changing commands require
If-Matchwith the current ETag value. - Concurrent publish and withdraw: first writer wins; second receives
409 Conflictwithstale-resource-version. - Concurrent matching: the broker MUST use optimistic concurrency on both offer and request. A race between two match attempts on the same offer is resolved by the first committed transaction; the second attempt MUST re-read the updated offer version and re-evaluate compatibility before retrying.
- Withdraw racing with match commit: if a
withdrawcommand and afullMatchsystem event race, the outcome is determined by which transaction commits first. A committed match MUST NOT be reversed by a delayed withdrawal; the carrier MUST be notified and a separate cancellation flow initiated. - Expire racing with withdraw: system
expireand a carrierwithdrawmay race. Either outcome is valid; the resulting terminal state is recorded verbatim. - Transitions MUST be applied atomically within a single serializable transaction.
6. Invariants
Section titled “6. Invariants”- Capacity monotonicity:
availableCapacityin statematchedMUST be ≤availableCapacityat publication. - No re-publication: A
withdrawn,expired, orcancelledoffer MUST NOT be re-published. A new offer MUST be created. - ExpiresAt immutability:
expiresAtMUST NOT be changed after the offer reachespublished. - Visibility isolation: A
draftoffer MUST NOT be visible to any partner organization. - Active relationship required: Matching MUST only proceed if the carrier and shipper have an
activePartnerRelationshipcovering thecapacity:publishandloads:readscopes. - Partial-match consistency: The sum of capacity allocated to open
MatchProposalrecords MUST NOT exceedavailableCapacityat time of each match attempt.