Odexa 1.2.0-draft.2
Wire profile
The implemented same-origin free service and evidence contracts.
1.2.0-draft.2 · 16 September 2026 · development implementation
This document describes the implemented same-origin free profile. Where earlier planning documents discuss payments, delegated providers or future key rotation, those are design directions rather than supported capabilities of this server. Machine schemas accompany this document; cross-field, cryptographic and state checks remain mandatory.
Discovery and authority
An origin publishes /odexa.json for its policy and /odexa-service.json for its service metadata. The policy identifies its own origin, revision, validity window, exact resource scope, actions, purposes and obligations. The default is no_grant. All requested action-purpose pairs must be permitted or eligible for agreement, and all applicable obligations must be supported. Unknown vocabulary is rejected.
The service metadata identifies its issuer, base URL, capabilities and public signing keys, including each key’s allowed uses and validity. This reference uses one service on the origin with no delegation. Discovery and resource requests use HTTPS. The test client additionally receives independent public key pins during provisioning; it does not trust a key merely because a signed record names it.
Exact policy, authority and human-terms bytes are retained under immutable URLs. An offer binds their SHA-256 digests. JSON parsing rejects duplicate object members and ambiguous integer forms; do not parse a JSON object and reserialize it to reconstruct signed or hashed bytes.
The policy vocabulary stays at draft 1 while the new service and evidence envelopes use draft 2. Metadata names both versions explicitly. Neither version label silently implies support for the other service capabilities.
Participants and credentials
The administrator provisions clients locally. An agent client has a random secret, a P-256 signing key, a reporter identifier and an explicit list of allowed principal identifiers. Gateway and administrator roles have separate credentials. No public registration service or verified business-identity service is included.
Service API authentication uses HTTP Basic over HTTPS with generated high-entropy secrets. Protected assets use separately issued opaque bearer tokens. Client secrets and access tokens are different credentials. Bearer possession does not establish who actually operated an agent. Store only token hashes in the service database.
Compact JWS uses ES256 with exact UTF-8 payload bytes, a recognized typ and a provisioned kid. P-256 signatures use the JWS fixed-width encoding. Unsupported algorithms, untrusted keys, malformed signatures and type substitution are rejected. The artifact types are acceptance, receipt, status, event, event intake and asset manifest; a valid signature for one type cannot be substituted for another.
API surface
All paths in this table are relative to the discovered base URL, /api/ in the fixture. JSON bodies use application/json; signed messages use application/jose. Read APIs require authentication unless explicitly public.
| Method and path | Caller | Result |
|---|---|---|
POST offers |
Agent | A zero-price offer for its principal, URL, requested actions/purposes and bounded access/use duration |
GET offers/{offer_id} |
Owning agent | Original offer bytes |
POST agreements |
Agent | Verify signed acceptance and persist agreement; return original signed receipt |
GET agreements/{id}/receipt |
Owner or operator role | Original immutable receipt, including after revocation |
GET agreements/{id}/status |
Owner or operator role | Signed current access state |
POST agreements/{id}/revoke |
Administrator | Idempotent future-access revocation |
POST tokens |
Owning agent | Opaque token for its active agreement |
POST introspect |
Gateway | Live token/scope decision bound to the actual request; persist admission before allowing access |
POST events |
Agent or gateway | Verify signed evidence and return signed intake |
POST events |
Anonymous | Accept only an unverified client-use declaration in a separate namespace |
GET export?limit=N&after=CURSOR |
Authenticated caller | Paginated original signed intake records within the caller’s scope |
GET materials |
Authenticated caller | Retained offers, acceptances, receipts, status history, public context documents and admissions within its scope |
Administrator and gateway accounts are trusted origin-operator roles with wider access. Agent exports include their own records and verified gateway records for their own agreements. Anonymous claims cannot attach themselves to the authenticated owner index by naming an agreement.
Agreement lifecycle
- The client discovers the policy and service, then requests a scope it supports. The fixture requests
retrieveandstoreforpublic_retrievalon one exact URL. - The service checks principal ownership, policy validity, every requested action-purpose pair and current service authority. The narrow profile requires
access_seconds <= use_seconds. - The client fetches all retained context, verifies the exact digests and separately approves human terms. The fixture uses a preapproved synthetic terms digest; accepting arbitrary real terms automatically is not demonstrated.
- The client signs an acceptance binding the service, offer ID, exact offer digest, principal, nonce, acceptance time and idempotency key.
- The service checks signature, client/principal binding, time, current policy/authority and offer freshness. A single SQLite transaction persists the acceptance, agreement, original signed receipt, status and local transition evidence.
- An identical authenticated retry returns the original receipt, even when the ES256 signature bytes differ. A changed decoded acceptance under the same idempotency key conflicts. An offer cannot create a second agreement.
- The client requests an access token separately. The receipt is evidence of the agreement, not a bearer access token.
A free agreement becomes active directly. No paid state is simulated. Expiry stops new access; administrator revocation changes future access without rewriting the accepted receipt or claiming to recall delivered bytes. A request already admitted may finish after revocation. Use rights and duties remain governed by the retained terms and use window; the server cannot inspect a recipient’s storage or model.
Successful transaction acknowledgements follow commit. The tests kill child processes immediately before and after commit and check recovery. These tests establish process-crash behavior in the tested environment, not arbitrary power-loss or storage-hardware guarantees.
Protected delivery
The fixture serves only /licensed/about.txt through its protected gateway. The caller supplies:
Authorization: Bearer <opaque-token>
Odexa-Actions: retrieve
Odexa-Purposes: public_retrieval
These are explicit declarations of the requested use. The gateway does not infer purpose from a User-Agent string. It calls live introspection for each GET, HEAD, range and conditional request and checks the returned request ID, URL, origin, method, actions and purposes before returning content.
The admission is durable before content is served. The completed observation binds to that admission. If a process dies between admission and final observation, retained material exposes an admission with no terminal event. That is an unknown delivery outcome, not a completed transfer or a zero-use assertion.
The transport rejects duplicate critical headers, unsupported transfer encoding, malformed content lengths, oversized bodies, wrong Host and noncanonical request lines. It compares the raw request target because the underlying HTTP library can normalize leading slashes. Public manifests contain metadata and digests, not the protected text. Internal files have no public file-serving route.
The asset response supplies an immutable manifest link, selected representation ID, media type, language, ETag and explicit private/no-store cache behavior. Full GET, HEAD, a single supported byte range and conditional 304 have separate evidence classifications. Unsupported range forms receive an error. Authorization failure or unavailable authority withholds the protected body.
Export and verification
Preserve original compact JWS values, exact decoded payloads, offer/context bytes and manifest bytes. The independent client verifies cryptography, identities, context digests, acceptance/receipt linkage, status history and representation binding without importing Python verification code.
The export cursor is scoped to the authenticated client and pages records in insertion order. It is not a signed snapshot token or a general cross-database cursor. The materials endpoint takes an atomic, bounded local snapshot, with a 1,000-record global ceiling and a 1 MiB serialized-response limit; it is not a production bulk-export service. Oversize responses return 413. Close the workload before comparing snapshots. The demonstration exports the pre-revocation history, then separately tests revocation.
Offline verification uses separately retained public trust. It can check the included records and their historical snapshot bindings. It cannot infer that an exporter supplied every record, establish trusted historical time, or prove that a key remains trusted now. A tampered record must fail verification; a missing record requires an independent inventory or expectation to detect.
Optional providers
No field in the free path appoints Ortello implicitly. A future delegated provider profile must name its capabilities, permitted scopes, keys, lifetime, revocation boundary and payment authority explicitly. Payment authorization remains separate from agreeing to terms and observing traffic. Those wire contracts and adapters are not implemented by this release.