Skip to content

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.

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

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.

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.

  • Authorization: Actor belongs to the owning organization.
  • Scope required: capacity:publish
  • Relationship state: Owning organization must have at least one active partner relationship unless the offer uses an open visibility policy.
  • Version check: If-Match MUST match current resourceVersion.
  • Policy check: availabilityWindow, originArea, destinationArea, vehicleProfile, availableCapacity, expiresAt, and visibility MUST 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-Match MUST match current resourceVersion.
  • 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 LoadRequest in published state is compatible and the relationship between orgs is active.
  • Version check: Both offer and request resourceVersion values MUST be observed before matching and included in the MatchProposal.
  • Authorization: System clock process.
  • Precondition: Current time ≥ expiresAt.
  • Version check: No If-Match required for system clock transitions.
  • Authorization: Protocol operator with admin scope.
  • Scope required: capacity:publish + operator flag.
  • Policy check: Cancellation reason MUST be recorded.
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.
  • All state-changing commands require If-Match with the current ETag value.
  • Concurrent publish and withdraw: first writer wins; second receives 409 Conflict with stale-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 withdraw command and a fullMatch system 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 expire and a carrier withdraw may race. Either outcome is valid; the resulting terminal state is recorded verbatim.
  • Transitions MUST be applied atomically within a single serializable transaction.
  1. Capacity monotonicity: availableCapacity in state matched MUST be ≤ availableCapacity at publication.
  2. No re-publication: A withdrawn, expired, or cancelled offer MUST NOT be re-published. A new offer MUST be created.
  3. ExpiresAt immutability: expiresAt MUST NOT be changed after the offer reaches published.
  4. Visibility isolation: A draft offer MUST NOT be visible to any partner organization.
  5. Active relationship required: Matching MUST only proceed if the carrier and shipper have an active PartnerRelationship covering the capacity:publish and loads:read scopes.
  6. Partial-match consistency: The sum of capacity allocated to open MatchProposal records MUST NOT exceed availableCapacity at time of each match attempt.