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.
1. Cross-Cutting Rules
Section titled “1. Cross-Cutting Rules”1.1 404-vs-403 Rule (R-001)
Section titled “1.1 404-vs-403 Rule (R-001)”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.
1.2 Token Validity Preconditions
Section titled “1.2 Token Validity Preconditions”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).
1.3 Scope Hierarchy
Section titled “1.3 Scope Hierarchy”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.
1.4 Broker-Scoped Authority
Section titled “1.4 Broker-Scoped Authority”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.
1.5 Relationship Preconditions
Section titled “1.5 Relationship Preconditions”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.
2. Roles
Section titled “2. Roles”| 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 |
3. Scope-to-Resource Authorization Matrix
Section titled “3. Scope-to-Resource Authorization Matrix”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
ownerOrganizationIdmust match the tokensub - Relationship precondition — whether an
activePartnerRelationship 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.
3.2 capacity:publish — CapacityOffer
Section titled “3.2 capacity:publish — CapacityOffer”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.
3.3 capacity:read — CapacityOffer
Section titled “3.3 capacity:read — CapacityOffer”Operations: read, list, search.
| Operation | Allowed Roles | Ownership Precondition | Relationship Precondition | Visibility Rule |
|---|---|---|---|---|
| Read partner offer | OrganizationService, OrganizationMember | Token sub ≠ ownerOrganizationId |
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).
3.4 loads:publish — LoadRequest
Section titled “3.4 loads:publish — LoadRequest”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 |
3.5 loads:read — LoadRequest
Section titled “3.5 loads:read — LoadRequest”Operations: read, list, search.
| Operation | Allowed Roles | Ownership Precondition | Relationship Precondition | Visibility Rule |
|---|---|---|---|---|
| Read partner request | OrganizationService, OrganizationMember | Token sub ≠ ownerOrganizationId |
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.
4. Field-Level Disclosure (R-005)
Section titled “4. Field-Level Disclosure (R-005)”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.
4.3 MatchProposal — Field Visibility
Section titled “4.3 MatchProposal — Field Visibility”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.
4.4 OutcomeRecord — Field Visibility
Section titled “4.4 OutcomeRecord — Field Visibility”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.
5. Scope × Role Eligibility
Section titled “5. Scope × Role Eligibility”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:writeforOrganizationMemberis further constrained to members of the carrier organization (capacity owner). Broker implementations MUST enforce this at token-issuance time.outcomes:reviewis never granted toOrganizationServiceorOrganizationMemberacting in a non-compliance role. It is a dedicated, separately-provisioned role.ComplianceReviewerreceivingmatches:readmay only access proposals and decisions within their assigned broker scope.
6. Combined Resource × Scope Summary
Section titled “6. Combined Resource × Scope Summary”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 |
7. Authorization Enforcement Algorithm
Section titled “7. Authorization Enforcement Algorithm”Broker implementations MUST evaluate authorization in this sequence for every request:
1. Validate token signature and expiry → 401 on failure2. Validate token audience (aud = this broker) → 401 on mismatch3. Parse organizationId from token sub claim4. Check required scope is in token scope list → 401 on missing scope5. 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 access7. 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 → 4048. Resource does not exist → respond 4049. 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.
8. Audit Requirements
Section titled “8. Audit Requirements”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.
9. Normative References
Section titled “9. Normative References”| 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 |