A9. Read receipts and the log
Status: normative. Document version 1.0-draft-1.
Every fresh disclosure commits a receipt before returning. Its read ID matches the signed envelope. It records the provider, grant, client, purpose, trusted time, surface, canonical request, internal entry/version/level references and response hash. provider is the provider's HTTPS issuer URL as discovery publishes it (A3.3), the same value as the signed disclosure's provider; it is part of the signed bytes, and a verifier binds the receipt to that provider's key set through it (A5.4). Internal references are owner-only; recipient-facing evidence uses the signed disclosure and its pairwise references.
The canonical request, the receipt's request, is the validated context request with its A5.2 defaults written out. max_level (L3 when omitted), declared_only (false when omitted) and min_confidence (0 when omitted) are always present. domains, and keys when the request has them, are in ascending order without repeats. keys and since are present only when the request has them, and since uses the canonical instant spelling (A5.4). Requests that differ only in list order, repeated names or omitted defaults therefore have the same canonical request, and its JCS form is what the receipt's hash and signature cover. Writing the canonical form is the provider's duty; a verifier checks the signature over the request the receipt carries and does not reject it for its order or spelling.
response_hash is sha256: plus lower-case hexadecimal SHA-256 of the exact JCS UTF-8 envelope without proof, the same bytes signed in A5. The receipt's hash covers its JCS form excluding hash and proof, including prev_hash. The first event uses null previous hash. Sign the complete receipt excluding proof using the A5.4 method and protected typ:autonym-receipt+jws; its signed time is at.
The owner log is append-only and hash-chained per record partition. Non-read events use {event_id,at,kind,actor_client_id,subject_id,details,prev_hash,hash}. kind is one of grant_created, grant_revoked, grant_expired, rule_applied, delegation_redeemed, proposal_decided, overlay_enabled, overlay_disabled, overlay_selected, read_not_modified, purge_issued, purge_acknowledged, operator_action or migration_imported. details contains only decision codes and relevant opaque IDs, never entry values, utterances or tokens: details.decision is one of the codes of log-event.json or null, details.ids lists opaque IDs and details.response_hash is an A9 hash or null. These events use the same hash rule and are included with receipts in export.
The kinds that grant issuance, revocation, expiry, conditional reads and operators write have this content:
grant_created:subject_idis the new grant's ID,actor_client_idthe client it is issued to anddetails.decisionapproved.details.idsnames the grant and, where the provider keeps a record of the person's consent review, that review.grant_revoked:subject_idanddetails.idsname the grant. When the person revokes it,actor_client_idis null and the decision iscompleted. When its client revokes it through the revocation endpoint, with the proof of possession A6.5 asks of a public client, the actor is that client and the decision iscompleted. When the provider revokes it because its client used an authorisation code or a spent refresh token a second time (A6.3), the actor is that client and the decision isfailed. When the operator deregisters the client (A6.8), the actor is null, the decision isdisabledand anoperator_actionis appended in the same transaction.grant_expired:subject_idanddetails.idsname the grant, and the actor and the decision are null. The event records an expiry; every action checks expiry itself (A6.5).read_not_modified:subject_idnames the grant,actor_client_idthe reading client and the decision isunchanged.details.idsnames the earlier read, anddetails.response_hashis that read's response hash.operator_action:subject_idis the record partition's ID, the actor is null, anddetails.idssays what the action concerned. A verification of the partition's log names the event IDs it reports, with the decisioncompletedwhen the log verified,failedwhen it found a divergence and null when verification could not run. An action on a client names the client's grants in the partition that were active when the action began, with the decisiondisabledfor a suspension or a deregistration,enabledfor a resumption andcompletedfor a change of class; thegrant_revokedevents a deregistration appends in the same transaction tell it apart from a suspension. One event names at most 32 IDs (log-event.json), so an action on a client with more grants in the partition appends furtheroperator_actionevents with the same decision, in the same transaction, until every grant is named.
Each other kind's content is defined with the operation that writes it, within the rule above.
Grant/counter updates and log append occur in the disclosure transaction. Failure cannot leave a successful unlogged read. A 304 appends an event referencing its previous read ID and response hash, without a fresh disclosure or counter increment.
Owners can inspect reads, consent changes, temporal activation, withdrawals and purge state. Operational logs and telemetry have a separate allowlist and never contain context values, approved renderings, source evidence, uploaded content, credentials or tokens. A cryptographic chain reveals tampering relative to a trusted checkpoint; a host controlling all keys and checkpoints is not magically unable to rewrite history. Owners should be able to export signed checkpoints.
ISO/IEC 27560 is a useful consent-record reference, but this draft does not claim certified mapping to its unavailable full text. The explicit fields here are the wire contract.
Receipts MUST stay verifiable after the signing key changes. A key ID is the RFC 7638 thumbprint of its public key (A5.4), so one key ID cannot name two keys, and a provider MUST NOT reuse a key ID. A retired signing key MUST NOT sign anything at or after its retirement. Its public key MUST stay in the provider's key set, with exp giving the time of its retirement (A5.4), for as long as any receipt or log event it signed is retained, and a published exp MUST NOT be removed or changed. A verifier rejects an object whose signed time is at or after its key's retirement (A5.4, KEY-01). Retirement is not a response to key compromise: whoever holds a retired private key can still produce an object dated before its retirement. Log retention and compaction MUST keep everything an old receipt needs to verify independently: the receipt, the hash-chain events it links to, any checkpoint it is anchored to and the public key that signed it. A receipt signed before a key rotation therefore verifies afterwards from the provider's published keys and the owner's export alone (LOG-02).
Checks: LOG-01, LOG-02, KEY-01, ADS-02, SIGN-01, PORT-01.
References to other standards and projects describe intended technical alignment only and do not imply endorsement or partnership.