Skip to content

OpenFreyt Protocol — Authorization Matrix

Generated from security/authorization-matrix.md. Edit the canonical source file, not this page.

Status: Normative
Version: 1.0.0-draft
Locked by: M001-S01-T02
Requirement refs: R-001 (404-vs-403 rule), R-005 (field-level disclosure)

This document is the authoritative access-control specification for all PCX protocol operations. Every broker implementation MUST enforce all rules stated here. The matrix governs nine authorization scopes, seven resources, and four roles.


When a requester lacks authorization to know a resource exists, the server MUST respond with HTTP 404 (Not Found), not HTTP 403 (Forbidden).

Rationale: returning 403 confirms the resource exists and enables enumeration attacks. Applying 404 universally removes this signal.

Application: this rule applies to all read, mutate, and delete operations on all seven resources. The broker evaluates existence and authorization simultaneously; if authorization fails, the response is 404 regardless of whether the resource actually exists.

Exceptions:

  • If the requesting token belongs to the owning organization and the resource exists, the broker MAY return 403 to distinguish “resource exists but operation is forbidden” from “resource not found”. This is an implementation option; 404 is always permissible.
  • System-level errors (malformed IDs, invalid scopes) return 400 or 401 as appropriate regardless of resource existence.

All requests require a valid, non-expired bearer token issued by the authoritative broker for the resource namespace. Tokens MUST include:

Claim Requirement
iss Broker service identifier (URI)
sub Subject (organizationId or service account ID)
aud Broker service identifier (exact match required)
scope Space-separated list of granted PCX scopes
exp Expiry timestamp (MUST be in future)
iat Issue timestamp

A token with an aud that does not exactly match the receiving broker is rejected with HTTP 401 (audience confusion protection).

Scopes are discrete; there is no implicit hierarchy. Possessing capacity:publish does not imply capacity:read. Each operation requires exactly the scopes listed in the matrix below.

All scopes are broker-scoped: a token issued by Broker A grants no rights on Broker B. Cross-broker operations are not defined in PCX 1.x.

Several operations require an active PartnerRelationship between the requesting organization (sub claim) and the resource-owning organization (ownerOrganizationId field). “Active” means the PartnerRelationship resource has state active and has not been suspended or terminated.

A pending, suspended, or terminated relationship does not satisfy a relationship precondition.


Role Identity Source Auth Flow
BrokerService Platform operator; has elevated internal authority Service token, internally signed
OrganizationService Machine client (TMS/ERP) acting for an org OAuth 2.0 Client Credentials
OrganizationMember Human user affiliated with an org OAuth 2.0 Authorization Code + PKCE
ComplianceReviewer Human or service performing compliance review OAuth 2.0 Authorization Code or Client Credentials

The table below maps each (scope × resource × role) combination to the preconditions required. Columns:

  • Allowed Roles — roles that may hold this scope
  • Ownership precondition — whether ownerOrganizationId must match the token sub
  • Relationship precondition — whether an active PartnerRelationship with the owner is required
  • Visibility rule — what the requester may see (field-level detail in §4)

3.1 relationships:manage — PartnerRelationship

Section titled “3.1 relationships:manage — PartnerRelationship”

Operations: create invitation, accept invitation, update permitted scopes, suspend, terminate.

Operation Allowed Roles Ownership Precondition Relationship Precondition Visibility Rule
Create (invite) OrganizationService, OrganizationMember, BrokerService Token sub becomes initiatorOrganizationId None (creating the relationship) Own record only
Accept OrganizationService, OrganizationMember, BrokerService Token sub must equal acceptorOrganizationId None (the relationship is pending) Own record only
Read own OrganizationService, OrganizationMember, BrokerService Token sub equals initiator or acceptor N/A Full fields
Update permitted scopes OrganizationService, OrganizationMember, BrokerService Token sub equals initiator or acceptor Must be active
Suspend OrganizationService, OrganizationMember, BrokerService Token sub equals initiator or acceptor Must be active
Terminate OrganizationService, OrganizationMember, BrokerService Token sub equals initiator or acceptor Any state
List (all visible) OrganizationService, OrganizationMember, BrokerService Token sub is party to each result N/A Full fields on own records
Admin read-any BrokerService None None Full fields

ComplianceReviewer does not hold relationships:manage; reviewers have read-only visibility to relationships they are party to via outcomes:review.

Operations: create, update, withdraw, cancel, expire (server-side).

Operation Allowed Roles Ownership Precondition Relationship Precondition Visibility Rule
Create OrganizationService, OrganizationMember Token sub becomes ownerOrganizationId None Own record full fields
Update OrganizationService, OrganizationMember Token sub = ownerOrganizationId None Own record full fields
Withdraw / Cancel OrganizationService, OrganizationMember Token sub = ownerOrganizationId None
Read own OrganizationService, OrganizationMember Token sub = ownerOrganizationId None Full fields including pre-match sensitive fields
Admin mutate BrokerService None None Full fields

Ownership is mandatory: an organization may only publish, update, or retract its own CapacityOffers.

Operations: read, list, search.

Operation Allowed Roles Ownership Precondition Relationship Precondition Visibility Rule
Read partner offer OrganizationService, OrganizationMember Token subownerOrganizationId active PartnerRelationship with ownerOrganizationId; scope capacity:read must be in permittedScopes Pre-match restricted (§4)
Read own offer OrganizationService, OrganizationMember Token sub = ownerOrganizationId None Full fields
List / Search OrganizationService, OrganizationMember N/A Each result filtered: only items where relationship precondition is satisfied appear Pre-match restricted per item
Admin read-any BrokerService None None Full fields

If the relationship precondition is not met, the resource MUST be omitted from list/search results (treated as if it does not exist — 404 rule applied at collection level).

Operations: create, update, withdraw, cancel.

Operation Allowed Roles Ownership Precondition Relationship Precondition Visibility Rule
Create OrganizationService, OrganizationMember Token sub becomes ownerOrganizationId None Own record full fields
Update OrganizationService, OrganizationMember Token sub = ownerOrganizationId None Own record full fields
Withdraw / Cancel OrganizationService, OrganizationMember Token sub = ownerOrganizationId None
Read own OrganizationService, OrganizationMember Token sub = ownerOrganizationId None Full fields
Admin mutate BrokerService None None Full fields

Operations: read, list, search.

Operation Allowed Roles Ownership Precondition Relationship Precondition Visibility Rule
Read partner request OrganizationService, OrganizationMember Token subownerOrganizationId active PartnerRelationship with ownerOrganizationId; scope loads:read must be in permittedScopes Pre-match restricted (§4)
Read own request OrganizationService, OrganizationMember Token sub = ownerOrganizationId None Full fields
List / Search OrganizationService, OrganizationMember N/A Filtered per-item as above Pre-match restricted per item
Admin read-any BrokerService None None Full fields

3.6 matches:read — MatchProposal, MatchDecision

Section titled “3.6 matches:read — MatchProposal, MatchDecision”

Operations: read, list proposals/decisions.

Operation Allowed Roles Ownership Precondition Relationship Precondition Visibility Rule
Read MatchProposal OrganizationService, OrganizationMember Token sub equals org owning capacity offer or load request within the proposal Active relationship between the two parties Full proposal fields (counterparty identity revealed post-proposal-creation)
Read MatchDecision OrganizationService, OrganizationMember Token sub is party to the matched resources Active relationship Full fields
List proposals OrganizationService, OrganizationMember N/A Filtered to proposals where requester is a party Full fields per item
ComplianceReviewer read ComplianceReviewer None None Full fields on proposals/decisions in assigned scope
Admin read-any BrokerService None None Full fields

3.7 matches:decide — MatchProposal (transition to accepted/rejected)

Section titled “3.7 matches:decide — MatchProposal (transition to accepted/rejected)”

Operations: submit PartyDecision, trigger state machine transition.

Operation Allowed Roles Ownership Precondition Relationship Precondition Visibility Rule
Submit decision OrganizationService, OrganizationMember Token sub is party to the proposal (capacity or load owner) Active relationship
Override decision (admin) BrokerService None None

Decision policy enforcement (from DecisionPolicy enum defined in Glossary §1.4):

Policy Required decisions before accepted
BILATERAL_REQUIRED Both capacity owner and load owner must submit accept
INITIATOR_ONLY Only the organization that initiated the MatchProposal request must accept
ACCEPTOR_ONLY Only the non-initiating organization must accept

A party MUST NOT be permitted to submit multiple decisions on the same proposal (idempotent accept re-submission is allowed; changing from accept to reject after the proposal is accepted is not allowed and returns HTTP 409).

3.8 outcomes:write — ShipmentExecution, OutcomeRecord

Section titled “3.8 outcomes:write — ShipmentExecution, OutcomeRecord”

Operations: create/update ShipmentExecution, submit OutcomeRecord.

Operation Allowed Roles Ownership Precondition Relationship Precondition Visibility Rule
Create ShipmentExecution OrganizationService, OrganizationMember Token sub = carrier org (capacity owner of matched offer) Active MatchDecision must exist Own record full fields
Update ShipmentExecution OrganizationService, OrganizationMember Token sub = carrier org Active MatchDecision must exist Own record full fields
Submit OutcomeRecord OrganizationService, OrganizationMember Token sub = carrier org or broker Active ShipmentExecution must exist Own record full fields
Admin write BrokerService None None Full fields

An OutcomeRecord MUST NOT be submitted unless an active MatchDecision exists referencing the same CapacityOffer/LoadRequest pair.

3.9 outcomes:review — OutcomeRecord (compliance review)

Section titled “3.9 outcomes:review — OutcomeRecord (compliance review)”

Operations: read OutcomeRecord, update review status, add compliance notes.

Operation Allowed Roles Ownership Precondition Relationship Precondition Visibility Rule
Read OutcomeRecord ComplianceReviewer, BrokerService None ComplianceReviewer must be assigned or broker-affiliated Full fields including audit fields
Update review status ComplianceReviewer, BrokerService None ComplianceReviewer must be assigned or broker-affiliated
List pending reviews ComplianceReviewer, BrokerService None Filtered to assigned/unassigned in broker scope Full fields
Cancel MatchDecision (compliance) BrokerService None None

Only BrokerService may cancel a MatchDecision after it reaches active state (e.g., for fraud, compliance failure). ComplianceReviewer surfaces the finding; the broker acts.


Certain fields on CapacityOffer and LoadRequest are sensitive and MUST be suppressed during the pre-match phase when accessed by a non-owning organization. The pre-match phase is defined as: after a resource is published but before a MatchDecision with state active links the requesting organization to the resource’s owning organization.

4.1 CapacityOffer — Suppressed Pre-Match Fields

Section titled “4.1 CapacityOffer — Suppressed Pre-Match Fields”

The following fields are suppressed (replaced with null or omitted) when a non-owning organization reads a CapacityOffer before an active MatchDecision exists:

Field Pre-Match Visible Post-Match Visible Scope Required to Unlock
ownerOrganizationId No — suppressed Yes matches:read + active MatchDecision
carrierIdentifier (if present) No Yes matches:read + active MatchDecision
exactPrice / pricePerKm No Yes matches:read + active MatchDecision
driverContact No Yes outcomes:write + active ShipmentExecution
vehicleLicensePlate No Yes outcomes:write + active ShipmentExecution
origin (full address) Partial — region only Full matches:read + active MatchDecision
destination (full address) Partial — region only Full matches:read + active MatchDecision
departureAt (exact time) Partial — date only Full capacity:read (date) + matches:read (exact time)

Fields always visible to a relationship-authorized reader: id, resourceVersion, bodyType, capacityKg, capacityM3, availableFrom (date), region (adminRegionCode), state, createdAt, updatedAt.

4.2 LoadRequest — Suppressed Pre-Match Fields

Section titled “4.2 LoadRequest — Suppressed Pre-Match Fields”
Field Pre-Match Visible Post-Match Visible Scope Required to Unlock
ownerOrganizationId No — suppressed Yes matches:read + active MatchDecision
shipperIdentifier No Yes matches:read + active MatchDecision
exactBudget / maxRatePerKm No Yes matches:read + active MatchDecision
consigneeContact No Yes outcomes:write + active ShipmentExecution
pickupAddress (full) Partial — region only Full matches:read + active MatchDecision
deliveryAddress (full) Partial — region only Full matches:read + active MatchDecision
requestedPickupAt (exact) Partial — date only Full loads:read (date) + matches:read (exact time)

Fields always visible to a relationship-authorized reader: id, resourceVersion, cargoType, weightKg, volumeM3, pickupRegion (adminRegionCode), deliveryRegion (adminRegionCode), requiredBodyTypes, state, createdAt, updatedAt.

When a MatchProposal is created, both parties (capacity owner and load owner) MAY read the full MatchProposal including the counterparty’s resource reference (capacityOfferId, loadRequestId), but not the counterparty’s organization identity until matches:read scope is present and a MatchDecision is active.

OutcomeRecord fields are restricted to parties with outcomes:write (for own records) or outcomes:review (for compliance). No pre-match suppression applies to OutcomeRecords because they cannot exist without an active MatchDecision.


This table specifies which roles are eligible to be granted each scope. A BrokerService token may carry any scope by design; eligibility applies to externally-issued tokens.

Scope BrokerService OrganizationService OrganizationMember ComplianceReviewer
relationships:manage ✓ (full)
capacity:publish
capacity:read
loads:publish
loads:read
matches:read
matches:decide
outcomes:write ✓ (carrier member only)
outcomes:review

Notes:

  • outcomes:write for OrganizationMember is further constrained to members of the carrier organization (capacity owner). Broker implementations MUST enforce this at token-issuance time.
  • outcomes:review is never granted to OrganizationService or OrganizationMember acting in a non-compliance role. It is a dedicated, separately-provisioned role.
  • ComplianceReviewer receiving matches:read may only access proposals and decisions within their assigned broker scope.

The table below summarizes which scope governs each resource’s write and read operations. “—” means the scope does not apply to that resource.

Resource Write Scope Read Scope Decide Scope Review Scope
PartnerRelationship relationships:manage relationships:manage
CapacityOffer capacity:publish capacity:read
LoadRequest loads:publish loads:read
MatchProposal BrokerService only matches:read matches:decide matches:read (ComplianceReviewer)
MatchDecision Auto-created (BrokerService) matches:read outcomes:review
ShipmentExecution outcomes:write outcomes:write (own), outcomes:review outcomes:review
OutcomeRecord outcomes:write outcomes:write (own), outcomes:review outcomes:review

Broker implementations MUST evaluate authorization in this sequence for every request:

1. Validate token signature and expiry → 401 on failure
2. Validate token audience (aud = this broker) → 401 on mismatch
3. Parse organizationId from token sub claim
4. Check required scope is in token scope list → 401 on missing scope
5. Determine resource existence (internal lookup, not exposed to caller yet)
6. Evaluate ownership precondition:
a. If ownership required AND sub ≠ ownerOrganizationId → skip to step 7
b. If ownership matches → grant full-owner access
7. Evaluate relationship precondition (if applicable):
a. Load active PartnerRelationship between sub and ownerOrganizationId
b. If no active relationship → respond 404 (R-001)
c. If relationship active but required scope not in permittedScopes → 404
8. Resource does not exist → respond 404
9. Grant access; apply field-level disclosure rules (§4)

Key property: steps 5–8 all result in 404, never 403, for non-owning requesters. This prevents enumeration of both resource existence and authorization failures.


Every operation that mutates a resource (create, update, state transition, decision submission, cancellation) MUST produce an immutable audit log entry containing:

Field Value
eventId UUIDv7
occurredAt ISO 8601 timestamp with millisecond precision
actorOrganizationId Token sub value
actorRole Role extracted from token
resourceType One of the 7 PCX resource types
resourceId UUIDv7 of the affected resource
operation Verb (create, update, withdraw, decide, etc.)
scopeUsed The PCX scope from the request token
outcome permitted or denied

Audit entries for denied operations MUST be produced before the 404 response is returned. Denied audit entries MUST NOT include the resource payload.


Ref Document
R-001 requirements/requirements.yaml — 404-vs-403 rule
R-005 requirements/requirements.yaml — field-level disclosure
Glossary §4 specification/glossary.md — scope definitions
Glossary §5 specification/glossary.md — role definitions
Glossary §7 specification/glossary.md — key terms (pre-match, post-match, relationship-gated)
OWASP API Top 10 security/threat-model.md — threat mapping
Abuse Cases security/abuse-cases.md — adversarial scenarios for each authorization rule