# Direct and delegated free exchange — draft-3 workbench

17 September 2026. This implements a bounded `1.2.0-draft.3` free exchange over real HTTPS. It is unpublished reference integration, not the complete v1.2 release candidate. The legacy `odexa_ref` server and CLI remain draft-2; the separate installed `odexa-current` entry point and [quickstart](CURRENT-QUICKSTART.md) now compose the draft-3 APIs without test fixtures. No global version switch or subclass relabels the legacy implementation.

An originator may host its own agreement service or appoint a separate provider with exactly the same free wire contracts. Both paths have been exercised by an independent Node client against the Python implementation. No payment module, spending mandate, billing provider or commercial provider account participates in this exchange. A provider's appointment is an operational capability scoped by the origin, not ownership of the originator's assets.

## Components and trusted interfaces

`profiles.free_service.FreeAgreementService` exposes `dispatch(method, path, headers, body)` and returns `(status, headers, exact_body_bytes)`. Construct it with a `FreeStore`, explicit publisher/service/issuer/delegation binding, service base URL, signing key, exact human terms, current-authority transport/history and a trusted live UTC clock. Provision local agent, gateway and administrative credentials through `provision_client`; this trusted setup method is not public registration or proof of a person's legal authority. Separate roles use separate credentials and keys. Random client secrets and opaque tokens require at least 256 bits of generated entropy; the durable store retains their hashes.

The [wire contracts](FREE-CONTRACTS.md) specify closed fields and semantic correlations. The [store](FREE-STORE.md) persists exact offers, acceptance payloads/JWS, original receipts, state transitions, status statements, intake records and authority decisions. The publisher independently serves `/odexa.json` and `/odexa-service.json`. A provider does not publish authoritative copies at its own origin. Current documents are acquired over certificate-verified HTTPS and checked against durable origin history for every new authorized operation, including in direct mode.

All routes are beneath the exact configured tenant `base_url`:

| Route | Role and behavior |
| --- | --- |
| `POST offers` | Agent requests exact resource/actions/purposes, principal and access/use windows; emits an unsigned offer pinned to exact policy, authority and human terms |
| `POST agreements` | Agent sends an ES256 acceptance; commits a single original signed receipt atomically |
| `POST tokens` | Owner agent obtains a short-lived opaque token for an active agreement |
| `POST introspect` | Separately provisioned publisher gateway requests one exact delivery admission |
| `GET agreements/{id}/receipt` | Owner agent, gateway or admin retrieves the original historical receipt |
| `GET agreements/{id}/status` | Authenticated authorized reader receives a newly signed current status |
| `POST agreements/{id}/revoke` | Owner agent or admin stops future access; exact command retry returns the original statement |
| `POST events` | Authenticated agent or gateway submits a signed source-qualified report bound to an existing agreement |
| `GET contexts/{sha256hex}` | Public immutable exact context bytes, fetched without client credentials |

Current authority outages and withdrawals fail closed. Authenticated historical receipt, acceptance and already-recorded event retries can return their exact original evidence during an outage; these paths create no new token, admission or permission. Introspection uses one request ID once; replay is rejected rather than reusing a previous access decision. Tokens are not idempotent bearer delivery: a retried token request creates a new short-lived token only after the same current checks.

## Acceptance, deadlines and policy changes

An offer lives for 60 seconds and is issued only if that full period fits the current authority, key, delegation and policy validity. Acceptance binds its exact bytes, client, principal, service, nonce and explicit intent. The accepted payload is the idempotency value; an alternate ES256 encoding of the same authenticated payload returns the original receipt. A different payload under the same idempotency key conflicts; an offer cannot be accepted twice under a different key.

Any exact policy or authority byte change before first acceptance makes the offer stale. Later token issuance and admission also require the accepted policy digest to remain current. Thus a policy edit stops future access under these agreements until a new offer is accepted. Original receipts and original use-window terms remain retained; this does not claim that already acquired copies can be recalled. Authority may rotate while a policy stays the same, provided the current service, capabilities, scope and keys authorize the new operation.

The service samples its live clock after acquiring the SQLite write lock, checks current origin history and repeats operational authorization before commit. Separate final predicates reject an acceptance past its original offer deadline, a newly expired access window, an expired token/admission or an active status statement whose access has expired. The independently reproduced two-second signing-delay race now rolls back instead of accepting after offer expiry. These are local commit guarantees bounded by the retained remote observation, not globally atomic remote revocation.

The original receipt is the initial version-1 acceptance proof. Signed status generation is lazy. `status_history` retains the first observed statement for each lifecycle version; `status_attestations` retains every emitted exact signed status, even when polling does not change the lifecycle version. Polling never invents a transition. Revoked and expired agreements cannot become active again.

## Publisher gateway and evidence boundary

`profiles.free_gateway.FreeGateway` is a separate publisher component with its own private durable journal and scoped gateway credential. `admit(token, resource_url=..., actions=..., purposes=...)` checks publisher authority, sends introspection only to the appointed service/tenant, and checks fresh publisher authority again after the reply. It validates the full response binding and persists an admission before returning its request ID. A trusted guard runs after TLS immediately before transmitting a bearer token and the gateway credential.

The host HTTP adapter must call `begin_delivery(request_id)` immediately before protected response headers/body. This checks current retained history, observation age and access expiry, and atomically consumes that admission once. The host calls `finish_delivery` after its write/flush, with the actual reported byte count, representation digest and completion flag. The supplied adapter in the TLS tests performs that sequence with an actual protected response. Cached bodies require a new admission too.

The journal distinguishes `admitted`, `in_flight`, `sent` and `partial`. A crash leaves an unresolved entry; restart neither retries a consumed admission nor invents a completed delivery. Tokens and client secrets are absent from this journal. Completion timestamps cannot precede delivery start. The journal trusts its local HTTP adapter's observations: successful writes do not prove remote consumption or downstream AI use. It is neither a signed portable export nor a complete registry of every ingress to an organisation.

The service can verify gateway event signatures and bind a delivery report to its exact recorded admission, including chronology. The optional [gateway reporter](GATEWAY-REPORTING.md) now atomically retains a signed terminal outcome and durable outbox, then publishes it to the appointed collector with authenticated acknowledgement/retry recovery. The host explicitly schedules its bounded drain; there is no hidden background worker. The independent wire run sends a separate signed **client-reported retrieval** only after receiving actual protected bytes. A valid signature authenticates the reporter and exact claim, not the truth of arbitrary downstream claims.

## Reproduce and inspect

Use the source workbench, Python 3.12 with its declared requirements and Node.js 24. From its root:

```sh
ODEXA_NODE="$(command -v node)" ODEXA_FREE_REPORT_DIR=verification/local-free-run \
  python -m unittest discover -s tests -p 'test_free*.py' -v
node --test verification/test_free_client.mjs
```

The Python suite creates two local HTTPS origins with an explicitly trusted ephemeral CA. It exercises the same contract in direct and delegated modes; the provider receives no request in the direct independent-client run. Real asset delivery, signed self-report, exact retry, status, revocation, outage/withdrawal and deadline failures are tested. Private fixture keys/configuration remain in temporary private directories. `verification/free-client.mjs` is a separate Node implementation; its signature rejection tests are distinct from successful wire interoperability checks.

For an existing prepared test deployment, run the independent client with `node verification/free-client.mjs --config /private/path/config.json --outdir /path/report`. Its config names `publisherOrigin`, `serviceId`, `baseUrl`, optional `delegationId`, `caFile`, `agent` (`clientId`, `principalId`, `password`, `keyId`, `reporterId`, `privateJwk`), exact `request`, `accessSeconds`, `useSeconds`, `expectedTermsDigest`, and `includeResource`. It performs a destructive-to-that-test-agreement self-revocation at the end. Reports exclude private configuration and tokens.

This is not full Agent Core conformance: the Node client verifies policy/terms and reports its tested retrieval, but does not demonstrate every attribution/display, continuing storage, training or derived-output duty. The additive [storage-session profile](STORAGE-SESSIONS.md) has its own explicit signed selection, collection and durable runtime; it is not automatically enabled by the ordinary CLI. [Paid agreement/gateway integration](PAID-SERVICE.md) and [bounded free evidence archives](PORTABILITY.md) are implemented separately. Paid/asset portability, fresh-agreement provider/key handover and declared coverage accounting have their own executed evidence. Independent paid Node coverage is now evidenced separately. The [independent native asset graph](INDEPENDENT-ASSET-CLIENT.md) is also now evidenced. The consolidated normative edition and final integrated review remain release gates.
