1.2.0-rc.1 / Evidence and duties

Delivery evidence

Distinguish completed local writes, partial delivery and unresolved admissions.

This 1.2.0-draft.3 capability records delivery observations and authenticated collector intake for direct or delegated gateways. It does not establish downstream use, human assent or client receipt. See current validation for executed checks and release status for this implementation candidate.

Construction and host responsibilities

from profiles.free_gateway import FreeGateway
from profiles.gateway_reports import GatewayReporter
from profiles.authority_transport import ScopedCredential

gateway = FreeGateway(
    private_journal_path,
    binding=selected_service_binding, base_url=selected_service_base_url,
    transport=verified_https_transport, history=authority_history,
    credential=introspection_credential, clock=trusted_utc_clock,
    reporting=GatewayReporter(
        reporter_id=registered_gateway_reporter_id,
        key_id=registered_gateway_key_id,
        private_key=gateway_signing_key,
        credential=ScopedCredential(
            selected_service_origin, selected_service_path_prefix,
            explicitly_supplied_report_authorization,
        ),
    ),
)

The collector is the exact selected service identified by binding and base URL. The report credential is supplied separately and must have that exact origin and directory scope. The service authenticates it as the registered gateway account responsible for the admission, then verifies the registered gateway signing key. Reusing the account’s secret is possible in the reference fixture; passing a different account does not transfer admission ownership. Credentials and private keys are absent from the journal, event, acknowledgement and object repr.

reporting=None explicitly retains legacy journal-only operation. Once a reporter identity is saved, reopening that journal with a different identity/key or with reporting disabled fails. Replacing a private key under the same key ID, switching service/base URL or changing collectors requires an explicit migration; this slice does not implement that migration or redirect queued material. Adding reporting to a prior journal does not fabricate events for old terminal records.

The HTTP host calls admit(...), then begin_delivery(request_id) immediately before its protected response. Cached responses require the same fresh admission. After actual transport writes it calls:

gateway.finish_delivery(
    request_id,
    bytes_written=observed_content_byte_count,
    complete=transport_completed,
    status=actual_http_status,
    observed_digest=hash_of_actual_written_content_or_none,
    representation_metadata={
        'media_type': 'text/plain',
        'media_parameters': {'charset': 'utf-8'},
        'languages': ['en'],
    },
    cache_status='miss',
)
results = gateway.publish_reports(limit=100)

representation_metadata may be null when not observed. Optional fields are content codings, independently observed decoded byte count/digest, a supported identity-coded single range, asset reference, and explicit ingress/boundary identifiers. The gateway uses the admission’s exact method, resource, policy, agreement, action/purpose and delivery/request ID. source=origin_observed, hop_role=end_client, event and hop UUIDs, and timestamps are written by the trusted gateway. It cannot promote a client claim into observed delivery.

bytes_written means content bytes the hosting transport can attest it wrote; a library exception may leave the amount unknown to that host. The host must not pass intended buffer length as if it were measured, or invent zero when the count is unknown. If it cannot attest a count, it leaves the record unresolved in_flight for investigation. Socket-write completion does not prove that a remote client received, retained or used the bytes. No network/database transaction can eliminate the interval between sending bytes and durably recording the outcome.

The compatibility parameter representation_digest is a prepared content-byte digest, not a decoded-asset digest. It is used only after attested complete transport, with no range/206 response. Failed transport discards that parameter. Only observed_digest can identify a written prefix or range; unknown partial digests remain null. Coded content does not acquire a decoded digest unless independently supplied. Unsupported partials carry no decoded claim. HEAD/204/205/304 carry zero content bytes and no body digest. Full, partial, failed, HEAD, not-modified, error and unsupported-response events preserve the existing closed HTTP classification. An asset reference alone is not proof that a version’s bytes were delivered.

Durable state and recovery

The private SQLite journal uses journal_mode=DELETE, synchronous=FULL, immediate transactions and a closed connection per transaction. It pins service and reporter identities. Existing states remain:

Journal state Meaning Publication
admitted Authenticated current service decision saved; no protected write started No terminal report
in_flight Single-use admission consumed immediately before transport No fabricated completion
sent Host attests terminal transport completion One queued signed outcome
partial Host attests incomplete/failed terminal transport One queued signed failure outcome

The terminal update and its signed odexa-event+jws outbox row commit in one transaction. Event ID, exact payload bytes, compact JWS and its randomized ES256 signature are created once and retained. A signing error, SQL failure or crash before commit leaves in_flight and no published terminal outcome. A crash after commit preserves both the terminal record and queued report. sent describes a completed HTTP transport, including a no-body/error response; the event’s HTTP classification determines whether this was an asset delivery.

There is deliberately no automatic conversion of admitted or in_flight into successful delivery after restart. Operators can investigate unresolved records; neither a retry nor a background reconciliation invents missing byte evidence. No report publication happens inside the response-writing callback: finish_delivery only signs and commits locally. The host decides when a bounded drain runs; this module creates no background scheduler.

publish_reports(limit=1..1000) sends queued records at least once. Failures preserve the same event ID and exact JWS. A lost response or crash after collector intake is resolved by the collector’s exact-payload deduplication returning its original signed acknowledgement. Concurrent drains may send duplicates but cannot replace the locally committed original acknowledgement; the collector must return identical original bytes. report_records() exposes the trusted local evidence rows for inspection/export, not a public endpoint. Completed rows are retained, not automatically deleted.

Current authority and authenticated acknowledgement

For each attempt the gateway fetches the publisher’s current /odexa-service.json and /odexa.json, applies durable authority history, and checks receive_events, the selected endpoint, service/issuer/delegation, resource/action/purpose scope and an active odexa-event-record+jws key. Reporting grants no access duration or payment authority. The exact authenticated snapshot is saved before transmitting the signed report.

The explicit POST goes only to selected_base_url + 'events'. Neither a policy reporting-duty endpoint nor a redirect can choose another collector. The HTTPS transport isolates the report credential to its configured origin/tenant path, checks current authority/history again after DNS/TLS immediately before HTTP transmission, accepts only status 201 or 200, and retains exact response bytes. There is no stale authority fallback or anonymous downgrade.

After the reply, the gateway fetches current publisher metadata again and rechecks its history and current collector/key authority. It verifies the intake signature, full selected-service binding, origin_key_verified assurance, authenticated reporter, exact original report payload and JWS, timestamp bounds, and exact authority context reference. Checks run after obtaining the journal transaction lock and again after cryptographic verification. The original verified intake JWS, verification time and current exact authority/policy/observation snapshot commit together with acknowledged state. Remote origin revocation and a local commit remain separate events: the guarantee is bounded by those recorded authenticated observations, not instantaneous global revocation.

A retry can return an acknowledgement referring to older publisher metadata. It is accepted only if those exact authority bytes were retained from this gateway’s authenticated pre/post-send observations, and the referenced collector/key was authorized in that retained context. The old intake is not rewritten to cite a new authority revision. Current collector/key authorization is still required. Unknown historical context, an inactive old signer, an endpoint migration, withdrawn delegation, origin outage, wrong signature, altered report or forged source keeps the report queued with a bounded error class. This conservative archival boundary may require operator investigation when metadata changes during a lost reply; it is not silent acceptance of provider-supplied history. A retained observation demonstrates what was fetched at its recorded time, not that remote authority never changed between observations.

No signed report is sent to arbitrary duty endpoints, and no payment credential/mandate is created. The same selected-service delivery boundary can be used by the paid service through the shared introspection/report contracts, but this reporting module does not decide whether payment authorizes access.

Executed checks and limits

The earlier component checkpoint recorded 7 local journal/semantics tests and a complete 15-method gateway-reporting run, including 8 actual TLS tests. The local checks cover exact signed bytes across restart, unknown/known failed-prefix handling, range and no-body/coded classification, reporter isolation, signer/SQL rollback and a child-process exit after outbox insertion but before terminal commit. These are overlapping component counts; use current validation for current totals and scope.

python3 -m unittest discover -s tests -p 'test_gateway_reports.py' -v
python3 -m unittest discover -s tests -p 'test_authority_transport.py' -v

The eight TLS tests at that checkpoint cover direct and two-origin publication, lost-acknowledgement exact retry/restart, origin outage/withdrawal before send, a stale pre-send decision, withdrawal after collector commit, wrong intake signature/reporter, and refusal to follow a changed tenant endpoint. The transport adds explicit (200,201) acceptance and trusted per-call JOSE-type selection; those changes preserve the default crypto registry. These are bounded reference checks, not an external security audit or certification.

Odexa / Protocol explorer

This page. Your terms.

Inspect this website’s published policy and see how a proposed use is evaluated.

Current pagehttps://odexa.io/releases/1.2.0-rc.1/docs/delivery-reporting/
Loading policy…

Published JSON
Open JSON

This is a local policy check, not a signed agreement or proof of agent compliance. Other published licences and applicable rights still apply. How policy evaluation works →

Odexa / Get in touch

Start a conversation.

Tell us what you have in mind. We’ll respond where we can.

We use these details to review and respond to your enquiry. Please leave out confidential information. Submitting does not subscribe you to marketing. Privacy policy.