# Delegated operational authority — draft-3 candidate work

17 September 2026. This is an unpublished `1.2.0-draft.3` contract and pure decision engine. It leaves the published draft-2 service and the `1.2.0-draft.1` policy language unchanged. It is not an integrated HTTP service, an RC, a payment processor or an independent security certification.

The originator can run all services itself, appoint an external service for free agreements, or appoint separate agreement, reporting, asset and payment services. A provider acquires only the capabilities and scope explicitly published by the origin. Neither appointment nor a payment confirmation transfers ownership of assets or establishes a principal's legal authority.

## Artifacts and API

- [delegation.py](../profiles/delegation.py): semantic validators and `evaluate_authority(authority_bytes, policy_bytes, operation, *, now, observation)`.
- [delegation.schema.json](../profiles/delegation.schema.json): closed structural schemas in `$defs`; root is `authority`.
- [delegation-cases.json](../profiles/fixtures/delegation-cases.json): positive and negative structural/semantic cases.
- [test_delegation_profile.py](../tests/test_delegation_profile.py): executable authority, substitution, expiry, scope and payment-correlation tests.

The schema identifier is reserved for this local candidate; it does not assert that an artifact exists at that public URL. JSON Schema validation is necessary but insufficient. Exact input bytes, duplicate members, integer tokens, calendar dates, canonical URLs, curve points, intervals and scope intersections require semantic validation. The engine reads at most 128 KiB each for authority and policy. It performs no network access, signing, authentication, spending, state persistence or access issuance.

`validate_authority(document, policy_document)` validates the complete document against the current policy identity and scope. `validate_operation(operation)` validates an operation's shape and internal semantics. `evaluate_authority` returns a fail-closed allow/deny result and catches malformed input errors. Standalone validators raise errors; callers must treat any error as invalid input.

## Publication and identity

The policy origin publishes `/odexa.json` and `/odexa-service.json` using HTTPS. An authority document contains exactly:

```text
protocol_version: "1.2.0-draft.3"
policy_protocol_version: "1.2.0-draft.1"
origin, policy_id, revision, issued_at, expires_at
services: [service, ...]
delegations: [delegation, ...]
```

`origin` and `policy_id` must equal the decoded policy. `revision` is a positive safe integer. Validity uses whole-second UTC timestamps, inclusive start and exclusive expiry. Published integer values use unsigned JSON integer tokens; fractional/exponent tokens and negative zero are rejected even when a JSON Schema validator considers them integral. Unknown members and duplicate members are rejected. Current revision/digest rollback protection and cross-revision identifier immutability require the adapter's retained history; the pure engine has no prior-state input.

A service has exactly `id`, `base_url`, `issuer`, `capabilities`, `signing_keys`, `limits` and `scope`. Its `id` is an origin-scoped canonical HTTPS identifier. The `base_url` is a canonical HTTPS directory URL, optionally on another origin, including any tenant-specific path. The independent `issuer` is the exact provider identity. Names, DNS ownership or an endpoint prefix alone do not substitute for these bindings. Query strings and fragments are prohibited on service, issuer, key, delegation and endpoint identifiers.

There are at most 16 services, 16 keys per service and 64 delegations. Canonical URL rules reuse the frozen policy parser: normalized HTTPS origin and path, no userinfo, unsafe encoded separators, dot segments, duplicate slashes or noncanonical port spelling. Request URLs may contain a query under policy rules; metadata identifiers may not. A source that uses an alternative URL alias must publish and authorize that exact canonical identity.

`scope` has `resource_ids`, `actions`, `purposes`, `asset_id_prefixes` and `version_id_prefixes`. Resource IDs name existing policy scopes, not immutable asset versions. Action and purpose arrays use the policy vocabulary. Prefix arrays contain origin-scoped directory URLs ending in `/`, at most 64 each; asset publication requires both arrays, other services leave them empty.

Services with any of `issue_agreements`, `issue_tokens` or `introspect` have non-null `limits` containing `max_offer_seconds`, `max_access_seconds`, `max_use_seconds` and `payment_mode`. Other services set `limits` to null. Maximum offer lifetime is 600 seconds; access/use bounds are positive seconds up to 31,536,000. `payment_mode` is `none` or `external`; `external` permits an optional payment condition and does not require a nonzero price.

## Explicit delegation and intersections

A delegation contains exactly:

```text
id, origin, delegate_service_id, capabilities
resource_ids, actions, purposes
issued_at, expires_at
max_access_seconds, max_use_seconds, payment_mode
allow_subdelegation: false
asset_id_prefixes, version_id_prefixes
```

Its ID and origin belong to the publisher. The delegate references one declared service. Its capability/resource/action/purpose sets must be subsets of that service's sets. Validity must fit inside authority validity. Access/use ceilings cannot exceed the service's ceilings. A delegation without grant capabilities uses zero duration ceilings. Manifest prefixes must fit inside the service's prefixes. Delegation never authorizes subdelegation.

An external `base_url` requires one explicit, current delegation ID for every supported operation, including a zero-price agreement and reporting. A same-origin service may omit delegation; if it specifies one, its restrictions apply. The engine never combines two partial delegations to create a broader effective grant. A provider that needs another role obtains a separate direct appointment from the originator.

Effective authority is the intersection of current origin metadata, service capability and scope, the selected delegation where applicable, the operation's exact identity/endpoint/key binding, and the requested duration/payment bounds. Every policy resource matched by the URL must be covered; overlapping scopes cannot bypass a narrower appointment. Every requested action and purpose must be covered. Policy evaluation retains its Cartesian action-by-purpose semantics and obligation aggregation.

For a new offer or acceptance, the current policy must return `permit` or `require_agreement`, with no unsupported duty. For other kinds the result answers operational authority only: an appointed collector may report prohibited behavior, for example. Current policy decisions and duties are returned, but token/access consumers must separately evaluate the authenticated, pinned agreement, its rights and obligations, current status and token binding. An operational allow never asserts a resource permission.

## Supported operation contract

An operation contains exactly `operation_id`, `payment_request_digest`, `kind`, `origin`, `service_id`, `issuer`, `endpoint`, `delegation_id`, `key_id`, `key_use`, `request`, `offer_seconds`, `access_seconds`, `use_seconds`, `payment_mode`, `asset_id` and `version_id`. The kinds `status`, `revoke`, `payment_mandate`, `payment_check` and `export_evidence` additionally require a canonical UUIDv4 `agreement_id`; that member is forbidden on other operation kinds.

`operation_id` is a new canonical lowercase UUIDv4. `request` contains the canonical same-origin URL and explicit nonempty action/purpose arrays plus `supported_obligations` (which may be empty). Nonapplicable IDs/digests are null and durations zero, not omitted. Applicable grant durations are positive and access duration cannot exceed use duration.

| Kind | Required capability | Exact endpoint | Required signing use |
| --- | --- | --- | --- |
| `offer` | `issue_agreements` | `base_url + "offers"` | `odexa-receipt+jws` |
| `accept` | `issue_agreements` | `base_url + "agreements"` | `odexa-receipt+jws` |
| `status` | `issue_agreements` | `base_url + "agreements/" + agreement_id + "/status"` | `odexa-status+jws` |
| `revoke` | `issue_agreements` | `base_url + "agreements/" + agreement_id + "/revoke"` | `odexa-status+jws` |
| `payment_mandate` | `issue_agreements` | `base_url + "agreements/" + agreement_id + "/payment-mandate"` | null |
| `payment_check` | `issue_agreements` | `base_url + "agreements/" + agreement_id + "/payment-check"` | null |
| `export_evidence` | `export_evidence` | `base_url + "exports"` | `odexa-evidence-export+jws` |
| `token` | `issue_tokens` | `base_url + "tokens"` | null |
| `introspect` | `introspect` | `base_url + "introspect"` | null |
| `report` | `receive_events` | `base_url + "events"` | `odexa-event-record+jws` |
| `verify_payment` | `verify_payments` | `base_url + "verify"` | `odexa-payment-status+jws` |
| `publish_manifest` | `publish_assets` | exact `version_id` | `odexa-asset-manifest+jws` |

Offer/acceptance checks bind the receipt-capable service key; this does not introduce a signed-offer format. Offer lifetime must also fit the remaining policy, authority, key and delegation lifetime. A manifest operation separately intersects logical asset and immutable version prefixes. Token/introspection operations use null signing fields because their current request/response channel is authenticated HTTPS, not a new JWS type.

Status and revocation use zero offer/access/use durations and payment mode `none`; neither creates a grant. Their agreement ID binds the exact dynamic route. The [draft-3 free service](FREE-SERVICE.md) now integrates those operations and authenticated original-receipt retrieval. Historical retrieval is not a new operational grant. The [portable free-evidence builder](PORTABILITY.md) integrates `export_evidence` as a trusted in-process API. Its operation has zero durations and payment mode `none`. The [paid agreement service](PAID-SERVICE.md) integrates the mandate/check operations with zero durations and payment mode `external`; the service and selected delegation must permit that mode. Payer signing authority is separately provisioned and is not an origin service signing role. Services must resolve each actual `report_usage` destination and cannot treat declared duty support or this decision as proof that a report was sent.

## Current trust, keys and revocation boundary

The trusted adapter supplies `observation` with exactly `source`, `origin`, `authority_url`, `policy_url`, `authority_digest`, `policy_digest`, `checked_at`, `available`, `tls_verified`, `redirected` and `revalidated`. This object is internal testimony from the authenticated transport or colocated transaction, never an agent-supplied claim.

The two digests cover the exact retrieved bytes. URLs must name the two origin publication paths. An unavailable origin, redirect, missing revalidation, future observation or observation older than five seconds produces denial. HTTPS observations require verified TLS. An external service requires the `https` observation source; `colocated` is only the direct origin-service path. The five-second maximum bounds computation/queue delay, not permission to reuse an observation for unrelated operations: adapters must acquire current authority for each operation and bind that operation ID and digest.

Current metadata is served with `Cache-Control: no-store`; clients request revalidation and do not accept a stale cache fallback. Policy must be revalidated for the same operation. Immutable historical records may be cached. A colocated service can read authority and commit its decision in one database transaction. Remote observation and local commit cannot be globally atomic. Remote revocation becomes effective for operations that observe the changed authority; already admitted in-flight delivery may finish according to the gateway's declared boundary. The adapter retains revision, exact digest and check time and must not claim instantaneous distributed revocation. Protected cached bodies need the same current authorization check as newly generated bodies.

A service key has `kid`, exact public P-256 `public_jwk`, `uses`, `not_before`, `not_after`, `state`, `retired_at` and `revoked_at`. No private JWK material is accepted. The engine checks curve validity, service/key-use compatibility and unique key IDs. A key ID cannot name different material within the authority document. Active signing requires an active key inside its validity interval. Retired/revoked keys cannot authorize new operations. Historical receipt verification may retain earlier authority/key bytes, but requires an explicit historical verification path and must expose missing provenance or known compromise. A historical signature is never current delegation authority.

Rotation requires a new key ID, overlapping publication before use, historical retention and durable revision checks. These are adapter/history requirements; this pure module tests current key selection and denial states, not a deployed rotation lifecycle.

## Decision use and payment binding

A denial has `decision: "deny"`, `authority: null` and `resource_permission_granted: false`. An allow also has `resource_permission_granted: false`, plus `policy_decision`, aggregated `obligations`, `obligation_status: "not_verified"` and an authority context containing:

```text
operation_id, operation_digest, payment_request_digest
request, request_digest
origin, service_id, issuer, capability, endpoint
key_id, key_use, delegation_id
authority_revision, authority_digest, policy_digest, checked_at
```

Consumers must check the full decision's explicit `allow` value before using its context, then compare every relevant binding to their pending operation. This object is not a bearer credential, signed proof or an assertion that an action/duty happened. The operation/request digests use SHA-256 over the reference `crypto.json_bytes` encoding (UTF-8, sorted object keys, compact JSON; array order retained). They are deterministic internal correlation values, not an invitation to replace exact signed-payload byte retention with JSON reserialization.

For `verify_payment`, the operation ID equals the pending check ID. `payment_request_digest` is required and binds the exact canonical check bytes constructed from the retained quote, accepted offer, request scope and separate payer mandate. The authority engine treats this digest as opaque. The payment consumer must reconstruct and compare it, compare `request_digest` against the accepted resource scope, and require the exact verifier service, provider issuer, endpoint and payment-status key use. Reusing a valid decision for another quote/check/resource must fail. A free agreement uses `payment_mode: "none"` and requires no payment provider. A provider's service fee and originator's resource price remain separate payment line items. Acceptance alone never authorizes a debit.

## Provider replacement and completion gates

Replacing a provider publishes a new origin-owned descriptor/delegation revision with the intended endpoint, issuer and key; an old provider cannot nominate its replacement. Removing the old appointment denies its next current operation. Existing signed agreements and evidence retain their original service, key, bytes and authority snapshots. Importing them must not silently rewrite the issuer, transfer client secrets/tokens or reactivate access. A migration requires authenticated export/import, explicit new authority, preserved exact evidence and a documented credential transition; the supported [fresh-agreement handover](PROVIDER-HANDOVER.md) now demonstrates those steps using existing contracts. It preserves original paid evidence without claiming transparent rights assignment.

The focused command for the original resolver component is:

```sh
python3 -m unittest discover -s tests -p 'test_delegation_profile.py' -v
```

The bundled Python runtime passed 24 test methods on 17 September 2026, including same-origin/free external operation, scope intersections, endpoint/tenant/key substitution, no subdelegation, policy prohibitions/duties, expiry/freshness/outage, key states, byte parsing, asset prefixes, payment correlation and fixture semantics. This is a pure-module result. Independent structural validation and combined payment integration are recorded separately by the integrator.

The original resolver-only review required authenticated two-origin HTTPS transport, credential isolation, authority history/rollback and rotation, agreement/status/revocation handling, durable payment state and export/import, with independent-client wire tests. The subsequent integration section below identifies what now exists. Those earlier requirements are not a claim that every component is still missing. The complete protocol must retain direct free operation and optional providers; neither the original test result nor this profile establishes a complete RC.

## Subsequent integration evidence

The workbench includes [bounded HTTPS acquisition](AUTHORITY-TRANSPORT.md), [durable authority history](AUTHORITY-HISTORY.md), [two-origin payment verification](NETWORK-PAYMENTS.md) and an explicitly separate [direct/delegated draft-3 free service and gateway](FREE-SERVICE.md). The current report records 26 resolver tests and actual independent-client wire results. These capabilities do not relabel the original draft-2 service. Later integrations add [durable gateway reporting](GATEWAY-REPORTING.md), [paid agreement/gateway integration](PAID-SERVICE.md), [free-evidence export/import](PORTABILITY.md), [selected storage](STORAGE-INTEGRATION.md) and [native asset/free-archive closure](ASSET-EVIDENCE.md). Explicit [paid archive variants](PAID-PORTABILITY.md) now preserve original payer/provider evidence. Operational fresh-agreement handover now has real two-provider TLS evidence. The separate observation registry/reducer now has focused M4 evidence. Installed operator tooling and independent native-asset/paid clients are subsequently evidenced under M5. The [consolidated review edition and audit](RELEASE-AUDIT.md) record current complete checks; final integrated review and release approval remain open. Earlier counts in this document describe the original pure-module slice; use the current verification report for integrated results.
