| Internet-Draft | Agent Audit Record | October 2026 |
| Gilda | Expires 19 April 2027 | [Page] |
This document defines a record format for AI agent authorization decisions and the effects that followed them. The format is one in-toto predicate type, signed inside a DSSE envelope. A record carries the decision and the observed effect with a derived agreement between them, whether the decision point could evaluate the request and how fresh each input it relied on was, and a canonicalization contract under which a party that trusts neither the agent nor its operator recomputes the record's consistency from its bytes. The document also defines what a party that retains a record keeps beside it, so that the record can later be dated from anchors that party does not control. The format carries the seven minimum audit fields of the WIMSE AI Identity Management System framework, and it is offered as a data model for the audit records the proposed AUDIT working group would specify. It defines no policy.¶
This note is to be removed before publishing as an RFC.¶
Status information for this document may be found at https://datatracker.ietf.org/doc/draft-gilda-audit-agent-audit-record/.¶
Discussion of this document takes place on the AUDIT Birds of a Feather mailing list (mailto:audit@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/audit/. Subscribe at https://www.ietf.org/mailman/listinfo/audit/.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 19 April 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
This document defines one record format for an agent's authorization decisions and their effects, and it defines nothing else. It specifies no policy model, no compliance criteria and no retention period, and it does not extend or amend [AIMS].¶
Two bodies of text ask for such a format. Section 11 of [AIMS] requires audit records for agent authorization decisions and leaves their form open. The proposed charter of the AUDIT working group [AUDIT-CHARTER] asks for data models that represent "interaction records, references to the identities of participating entities, delegation context, authorization state over time, or action provenance", and states that "audit information must be verifiable by a party that trusts neither the agent nor its operator". Appendix C maps each problem the AUDIT BoF request [AUDIT-BOFREQ] and that charter name to the section of this document that addresses it, and names the ones this document leaves to other work.¶
This document replaces draft-gilda-wimse-agent-audit-record-02. Sections 1 to 8 and Appendices A and B keep the numbers they had there, so a reference to one of those sections of that revision resolves to the same section here.¶
The goals are:¶
to make an audit record from one deployment comparable with one from another;¶
to make "tamper-evident" a property a verifier can test on the bytes it holds;¶
to carry the authorization decision and the observed effect in one signed record, so a disagreement between them is detectable;¶
to give the seven minimum fields of Section 11 of [AIMS] one member each.¶
Policy belongs to a deployment, and a record format belongs to everyone who has to read the record. Two implementations that each satisfy Section 11 in their own format produce audit trails that cannot be compared, correlated across a service boundary, or checked by a party that trusts neither producer. Section 11 asks for all three.¶
Comparable has a narrow meaning here. Two records are comparable when a third party holding both can decide, from the bytes alone, whether they describe the same request, whether their decisions agree, and whether either was enforced.¶
The consequence is concrete: two deployments that each hash a request differently cannot join their records on a request digest, so a delegation that crossed between them is reconstructable in neither log.¶
Section 11 of [AIMS] is normative about the existence of agent audit records and silent about their form. It requires that "deployments MUST produce durable audit logs covering authorization decisions and subsequent remediations", that "Audit records MUST be tamper-evident and retained according to the security policy of the deployment", and that audit events record seven minimum fields.¶
Section 12 then places the policy model and document format out of scope, stating that they are "not recommended as a target for standardization within this specification". Section 13 places compliance criteria out of scope, and Section 16 records no IANA actions. Appendix A sets out the canonicalization contract and the reason the decision and the observed effect belong in one record.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
The following terms are used throughout this document. Two of them, observer and decision point, are defined here; the rest are taken from the documents named beside them.¶
the party that produces a record under this document. An observer watches from a layer the agent cannot address. A host-side view of a guest filesystem, a hypervisor-level read of guest state, and kernel-level supervision below the observed process are such layers. An in-process library, a wrapper the agent links, and an importer holding another party's log are not.¶
the party that evaluated the authorization request, whatever its architecture.¶
the period between the before-state and the after-state that one record covers.¶
a party that keeps a record as evidence, whether it produced the record or received it from another party.¶
evidence, obtained from a source the retaining party does not control, that a given digest existed no later than a given time: an [RFC3161] time-stamp token, an inclusion proof in an append-only log [RFC9162], or an OpenTimestamps proof.¶
300 seconds. Every comparison in this document between times written by two different parties allows it. A relying party MAY apply a smaller tolerance and MUST NOT apply a larger one.¶
the degree to which a record's own members corroborate what it claims about how it was observed. A verifier derives the tier under the rule in Section 5.7 and never reads it from the record.¶
Every digest in this document is written as lowercase hexadecimal in the algorithm the record declares in its hashAlgorithm member.¶
A record is an in-toto Statement [IN-TOTO] carried in a DSSE envelope [DSSE]. Its predicateType is¶
=========== NOTE: "\" line wrapping per RFC 8792 ============ https://probityai.github.io/agent-evidence-vectors/predicate/v1/\ agent-audit-record¶
Seventeen required predicate members and one optional member are defined in Section 5. Each of the seventeen is required unless a rule in that section makes it conditional, no member has a default, and a verifier MUST NOT supply one for an absent member (vectors F1 and EV3). Two members are conditional on a sibling value, resource.argumentsDigest and correlation.externalAnchor, and each is specified where it is defined. The optional member is oversight (Section 5.5); its absence says nothing about whether a person acted.¶
A verifier MUST reject a value outside any closed vocabulary this document defines, and MUST NOT ignore the member (vector V1). The closed vocabularies reuse terms registered in [VOCABULARY].¶
A closed vocabulary is extended only by a revision of this document. Once a revision has frozen the predicate type URI (Section 8), a revision that adds a value to any closed vocabulary publishes the extended set under a new predicate type URI and leaves the set under the earlier URI unchanged, so a verifier never meets, under a URI it accepts, a value it is required to refuse. A verifier MUST NOT accept a value outside the set because it is well formed, prefixed, registered elsewhere or accompanied by a description. No closed vocabulary in this document carries a catch-all value such as other, because a catch-all satisfies the vocabulary while saying nothing two records can be compared on.¶
Two implementations derive the same bytes from the same Statement only if they agree on the serialization and on what makes a Statement malformed. Both are fixed below.¶
Producers and verifiers MUST canonicalize the Statement with [RFC8785], and the signature covers those bytes and no other serialization (vector T2). Producers and verifiers MUST enforce the [RFC7493] I-JSON safe-integer profile: an integer of magnitude at or above 2^53 makes the Statement malformed (vector T5).¶
A member name repeated at any depth makes the Statement malformed. A verifier MUST reject such a Statement and MUST NOT retain any one occurrence (vector T3). A Statement nested deeper than 128 levels MUST be rejected (vector T4).¶
A record carries two subject entries. The first is the request, by digest: its name is the correlation identifier and its sha256 digest is the request digest defined in Section 5. The second is the after-state root of the interval the predicate carries, and its name is the first entry's name with the suffix /after.¶
"subject": [
{ "name": "urn:example:corr:7f3a",
"digest": { "sha256": "9f86d081884c7d659a2feaa0c55ad015..." } },
{ "name": "urn:example:corr:7f3a/after",
"digest": { "sha256": "60303ae22b998861bce3b28f33eec1be..." } }
]
¶
A verifier MUST bind on the digest map and MUST NOT bind on either name. A name is a correlation identifier a producer chooses; a digest is a value a verifier recomputes from the bytes it holds. The names exist so that a reader can tell the two entries apart.¶
The second entry MUST be present if and only if the predicate carries an interval (vectors S1 and I1r), and its digest MUST equal the interval's after-state root (vector S2).¶
A decision that permitted nothing, and a decision that was denied, still carry an interval whose before-state and after-state roots are equal and whose write set is empty (vector A2). A consumer therefore reads an absence of effect from the members of a record it holds, and never from a record it does not have.¶
Every time value in a record MUST be an [RFC3339] date-time. A producer SHOULD express it in UTC with the "Z" offset, so that two records from different deployments sort together without a conversion step. The correlation identifier is the first subject entry's name, so one identifier binds the request digest, the interval and the record.¶
A record satisfies the tamper-evidence requirement of Section 11 of [AIMS] when all three of the following hold.¶
The DSSE pre-authentication encoding covers the [RFC8785] canonical bytes of the whole Statement, and every one of the seven minimum fields is inside that pre-image.¶
DSSE PAE = "DSSEv1" SP len("application/vnd.in-toto+json") SP
"application/vnd.in-toto+json" SP
len(JCS(statement)) SP JCS(statement)
¶
The request digest, the ordered write chain of the effect, and both agreement values are recomputable from other members of the same record. A verifier therefore still refuses a record that the key holder altered and signed again (vector T1).¶
The canonical form is fixed as in Section 4.1, so two verifiers derive identical bytes from identical documents. Any one of the three failing is a refusal.¶
The retention half of the Section 11 requirement is a property of a deployment, and a record cannot assert it about itself. A record under this document SHOULD state that in its doesNotAssert member, so that a reader need not infer coverage. Section 4.7 defines what a retaining party keeps beside a record so that the record can still be dated and validated after its keys and algorithms age; how long a deployment keeps it remains the deployment's policy.¶
| Section 11 field | Members |
|---|---|
| authenticated agent identifier |
agent.id, agent.credentialDigest, agent.authentication, agent.signers
|
| delegated subject, when present |
delegation.subject, delegation.subjectKind, delegation.authorityDigest
|
| resource or tool being accessed |
resource.kind, resource.id, resource.binding, resource.argumentsDigest
|
| action requested and authorization decision |
decision, evaluation and effect, with the derived agreement, and oversight when present |
| timestamp and correlation identifier |
correlation and the interval timestamps |
| posture assessment or risk state |
posture, carrying both vantages and assessedAt
|
| remediation or revocation events and cause |
remediation
|
Each field also carries an evidence partition in fieldEvidence, stating whether the observing substrate covered the value or the producer asserted it. All seven fields can be recorded by an agent about itself. A record that says which ones were is checkable; a record that does not say is not.¶
The fourth field pairs an action with a decision, and this format carries decision, effect and the derived agreement together. A record whose decision and effect disagree is well formed, and the disagreement is visible in signed bytes:¶
"decision": { "action": "write", "reported": "deny" },
"effect": {
"observed": "occurred",
"writes": [ { "path": "/srv/ledger/settlements.jsonl" } ]
},
"agreement": "disagree"
¶
A log that carries the decision alone records that case as a clean denial, because it has no other value to disagree with.¶
A verifier holds a DSSE envelope and reaches a verdict from the bytes inside it. The checks below are the ones this document has already stated, in the order a verifier can apply them without reading a member twice.¶
The JSON profile comes first, applied while the payload is parsed: the safe-integer bound of [RFC7493], no member name repeated at any depth, and no nesting past 128 levels. Each of the three makes a Statement malformed, so a verifier that finds one of them has nothing further to check. It has to come first because the signature covers [RFC8785] bytes, and a verifier derives those bytes from the parsed Statement: a parser that keeps one occurrence of a repeated name has already done what Section 4.1 forbids before any signature is checked (vector T3).¶
The envelope's signature comes second, over the [RFC8785] canonical bytes of the Statement that parse produced. A statement whose signature does not verify over those bytes is malformed, and nothing further is read from it. That includes a statement signed over the bytes it carries, which is what a DSSE verifier that does not derive the canonical form checks (vector T2).¶
Membership comes third. Every one of the seventeen required members is present unless a rule in Section 5 makes it conditional on a sibling value, and every value sits inside the closed vocabulary its member declares.¶
The recomputes come next, and a signature cannot supply them. The three that Section 4.4 requires come first: a verifier derives the request digest from the four members Section 5 names and compares it with the first subject entry, it replays the write chain from the before-state root and compares the result with the second subject entry, and it derives both agreement values from the members they are functions of.¶
A fourth recompute binds the prior commitment rather than the record's content. A verifier recomputes the commitment digest, checks that the commitment precedes the interval, and checks that the commitment key is absent from agent.signers. A fifth derives, from evaluation.inputs, the set of inputs that were stale or never established at decision.reportedAt, and compares it with evaluation.status and unavailableInput (Section 5.4).¶
The tier follows from those results. A verifier reads the five clauses of Section 5.7 against the members it has just checked and assigns authoritative or voluntary itself.¶
A verifier that holds a retained record (Section 4.7) applies one more check, the dating check, after all of the above. It validates each anchor over the envelope digest under the offline rule for its kind, takes the time of the earliest anchor that validates as the time the record is established to have existed, and, for a record received from another party, checks the stated receipt time against the bracket Section 4.7 defines. The dating check produces its own result beside the record's verdict: an envelope that is well formed stays well formed when none of its anchors validates, and it is then simply not dated.¶
This document requires no particular order beyond the first two checks, and no particular reporting shape. A verifier that stops at the first failure and a verifier that reports every failure are both conformant.¶
A record's signature says who signed it and nothing about when. A party that keeps a record as evidence therefore keeps a retention entry beside the envelope: a JSON object under the profile of Section 4.1, which the producer did not sign and which this section defines. A retaining party MAY keep the entry in any container. The entry carries no predicate member and changes none, so a record's verdict under Section 4.6 never depends on it.¶
{
"envelopeDigest": "...",
"received": true,
"receivedAt": "2026-09-19T11:04:20Z",
"anchorWithin": 300,
"anchors": [
{ "kind": "rfc3161", "origin": "retainer", "proof": "..." },
{ "kind": "transparency-log", "origin": "received",
"proof": "..." }
],
"validationData": [
{ "for": "envelope", "mediaType": "application/pkix-cert",
"value": "..." },
{ "for": 0, "mediaType": "application/pkix-cert",
"value": "..." }
]
}
¶
envelopeDigest is the digest, under the record's hashAlgorithm, of the [RFC8785] canonical bytes of the DSSE envelope object, so it covers the payload and every signature. Every anchor in the entry is over this value.¶
anchors is an array in the order the retaining party obtained its members. A retaining party appends to it and never removes from it. Each anchor carries kind, one of the three values of correlation.externalAnchor.kind (Section 5.6); origin, one of retainer and received; and proof, the base64 of the time-stamp token, inclusion proof or OpenTimestamps proof. An anchor of origin retainer is one the retaining party obtained itself, and one of origin received arrived with the record from the party that sent it. The entry carries no time and no algorithm family for an anchor: a verifier derives both from the proof when it validates it (Section 4.7.2), and MUST NOT read either from anywhere else.¶
validationData carries what a verifier needs to validate the envelope's signature and each anchor offline, after the sources it came from may be gone: the certification path or key set of the record's signing key, status information as the retaining party obtained it, and each time-stamping authority's own path and status. Each element names what it is for, either envelope or the zero-based index of an anchor, gives a media type, and carries the value in base64. Where status information was already unobtainable when the retaining party first anchored the record, the entry carries no status element for it, and that absence is the record of the fact.¶
The member received is true for a record the retaining party received from another party and false for one it produced. The member receivedAt is the time the retaining party states it received the record. It is present if and only if received is true, and an entry that carries it beside received of false, or omits it beside received of true, is malformed (vector R9). The member anchorWithin is the maximum interval, in seconds, that the retaining party commits to between issuance, or receipt for a received record, and its own first anchor.¶
A verifier validates each anchor under Section 4.7.2. The established time of the record is the time of the earliest anchor, of either origin, that validates over envelopeDigest. An anchor that does not validate dates nothing and makes nothing malformed (vector R8). No issuance time a record states establishes when it existed.¶
A retaining party that sets received to true MUST obtain an anchor of origin retainer on receipt, within the anchorWithin it declares, and an entry with received of true and no such anchor is malformed (vector R4). The retention time is the time of the earliest anchor of origin retainer that validates. A verifier MUST reject the entry as malformed when any of these holds:¶
receivedAt is earlier than the record's issuedAt by more than the skew tolerance (vector R6);¶
receivedAt is later than the retention time by more than the skew tolerance (vector R3);¶
the retention time is later than receivedAt, or than issuedAt for a record the retaining party produced, by more than anchorWithin plus the skew tolerance (vector R7).¶
A malformed entry does not change the envelope's verdict. A relying party MUST NOT apply an allowance it grants to received records, such as dating a record from its receipt, on a malformed entry or on an entry in which no anchor of origin retainer validates.¶
This check is a consistency check, not proof of receipt. Its lower edge, issuedAt, is a value the sender signed, and the signature binds the value without saying anything about the sender's clock. Its upper edge is an anchor over a digest, which shows only that the retaining party held the digest by that time, and a retaining party can hold the digest before the envelope arrives. A digest anchored before delivery therefore does not establish when the retaining party received the record. A verifier MUST report a receivedAt that passes the check as asserted and consistent with the anchors, and MUST NOT report it as established (vector R5). The check rests on two assumptions, stated so that a relying party can decide whether it holds them: that the sender's clock was correct when it signed, and that the retaining party anchored on delivery and not before.¶
A retaining party also chooses when it anchors. One that anchors late can state a correspondingly late receipt, and nothing in the entry contradicts it, because the bracket's upper edge is an anchor whose time the retaining party chose. The check removes the cheaper case, a receipt time stated with no anchor at all, and what remains is an assumption that the retaining party anchored promptly after the true receipt, within the anchorWithin it declared. That assumption is narrower than trusting the retaining party's statement of receipt, and no function of the entry tests it.¶
Each kind of anchor rests on one of two bases. A hash commitment is validated by recomputation to a value the verifier holds independently, and a signed token is validated by a signature under a path the verifier trusts.¶
rfc3161:a signed token. It validates when its message imprint equals envelopeDigest and its signature verifies under the time-stamping authority's certificate, by a certification path in validationData from that certificate to a trust anchor the verifier holds. Its time is the token's genTime.¶
transparency-log:an inclusion proof that recomputes from envelopeDigest to a tree head. It is a hash commitment when the verifier holds that tree head by a means other than its signature, such as a checkpoint the verifier already witnessed, and a signed token otherwise: it then validates when the tree head's signature verifies under the log's key, by a key or path in validationData that the verifier trusts. Its time is the tree head's timestamp.¶
opentimestamps:a hash commitment. It validates when the proof recomputes from envelopeDigest to a commitment the verifier holds independently, and its time is that commitment's time.¶
An anchor rests only on post-quantum assumptions when its validity rests on no quantum-vulnerable algorithm: a hash commitment under a hash with an output of at least 256 bits, or a signed token whose signature and whole path use post-quantum signature algorithms. A verifier derives this from the proof and the path. A retaining party SHOULD hold at least one anchor that rests only on post-quantum assumptions, and SHOULD append a new anchor on a schedule its policy sets, before the algorithms protecting its existing anchors weaken, using the renewal procedures of [RFC4998] or an equivalent. Where a relying party requires post-quantum anchoring from a transition date, as [PQ-AGENT-ID] proposes, this section supplies the anchors, their order and the material to validate each one, and the date is the relying party's.¶
{
"recordId": "...",
"tier": "authoritative",
"hashAlgorithm": "sha256",
"agent": {
"id": "spiffe://prod.example.org/ns/payments/sa/reconciler",
"credentialDigest": "...",
"authentication": "wimse-wpt",
"signers": ["..."]
},
"delegation": {
"subject": "user:alice@example.org",
"subjectKind": "user",
"authorityDigest": "..."
},
"resource": {
"kind": "tool",
"id": "mcp://files/write",
"binding": "digest-bound",
"argumentsDigest": "..."
},
"decision": {
"action": "write",
"requestDigest": "...",
"reported": "deny",
"decisionPointId": "pdp://prod.example.org/authz-1",
"policyDigest": "...",
"reportedAt": "2026-09-19T11:04:02Z"
},
"evaluation": {
"status": "evaluated",
"inputs": [
{ "input": "policy-source",
"lastResult": "2026-09-19T10:00:00Z", "maxAge": 86400 },
{ "input": "standing-source",
"lastResult": "2026-09-19T11:00:00Z", "maxAge": 3600 }
]
},
"effect": {
"observed": "occurred",
"interval": {
"beforeRoot": "...",
"afterRoot": "...",
"baseResolution": "supplied",
"openedAt": "2026-09-19T11:04:01Z",
"sealedAt": "2026-09-19T11:04:06Z"
},
"pathScope": ["/srv/ledger/"],
"writes": [
{
"path": "/srv/ledger/settlements.jsonl",
"preStateDigest": "...",
"postStateDigest": "...",
"requestDigest": "...",
"inScope": true
}
]
},
"agreement": "disagree",
"correlation": {
"id": "req:7f3a91c4",
"scope": "cross-party",
"timeBasis": "beacon-anchored",
"externalAnchor": { "kind": "rfc3161", "digest": "..." }
},
"posture": {
"reported": "sinkhole",
"reportedDigest": "...",
"observed": "allowlist",
"observedDigest": "...",
"assessedAt": "2026-09-19T06:00:00Z",
"agreement": "disagree"
},
"remediation": [
{
"cause": "session-revoked",
"signalReceivedAt": "2026-09-19T11:04:07Z",
"enforcedAt": "2026-09-19T11:04:09Z",
"enforcement": "session-terminated",
"postEnforcementEffect": "none",
"postEnforcementRoot": "..."
}
],
"observation": {
"vantage": "below-observed",
"coverage": { "scopeComplete": true, "gaps": [] },
"priorCommitment": {
"committedAt": "2026-09-19T11:03:58Z",
"witnessNonce": "...",
"commitmentDigest": "...",
"keyid": "...",
"sig": "..."
}
},
"fieldEvidence": {
"agent": "producer-asserted",
"correlation": "producer-asserted",
"decision": "producer-asserted",
"delegation": "producer-asserted",
"posture": "substrate-covered",
"remediation": "substrate-covered",
"resource": "substrate-covered"
},
"doesNotAssert": ["..."],
"issuedAt": "2026-09-19T11:04:11Z"
}
¶
recordId is the producer's identifier for this record. It is opaque to a verifier except where the commitment digest is recomputed, which reads it as one of its four inputs.¶
hashAlgorithm names the algorithm every digest in the record is taken under. One value governs the whole record: the subject digests, the credential, authority, policy and posture digests, the interval roots, the write pre-state and post-state digests, and the commitment digest are all taken under it.¶
A record carrying digests in two algorithms is therefore not representable, and that is the point. A verifier comparing two roots has to know they were taken under the same algorithm, and a per-member choice would let a producer choose which comparison a reader could make.¶
issuedAt is the time the producer sealed the record, and it is at or after the interval's sealedAt because the record cannot be issued before the interval it describes has closed.¶
A verifier that does not implement the declared algorithm cannot recompute anything the record binds. It has no verdict to reach on such a record, and reporting that it could not read one is the honest outcome rather than a refusal on the record's content.¶
agent.id carries the WIMSE identifier verbatim, and agent.credentialDigest the digest of the credential the identifier was read from. agent.authentication is the mechanism, over the closed set wimse-wpt, http-message-signature, mtls, oauth-access-token, none, drawn from Section 9 of [AIMS]. agent.signers is the set of key identifiers the agent signs its own records with, as the observer knows them, and it MAY be empty.¶
delegation.subjectKind is one of user, system, none. Field 2 of Section 11 is the only conditional one, so an absent delegated subject is conformant and is spelled subjectKind of none beside a subject of the literal none.¶
A subjectKind of none beside any other subject value is malformed and a verifier MUST NOT prefer either member (vector F2). delegation.authorityDigest is required unconditionally, and a decision taken under no authority carries the digest of the explicit deny-all document.¶
resource.kind is one of path, uri, tool, the three shapes the single category of external endpoints in Section 4 of [AIMS] takes. resource.binding of digest-bound MUST carry argumentsDigest, and not-bindable MUST NOT carry it in any spelling, including null and the empty string (vectors F3, F3b and F3c).¶
Where a decision point can canonicalize the arguments of a call, it SHOULD bind them by digest. A tool call whose arguments it cannot canonicalize has nothing to bind, so that call is representable and it cannot reach the strongest tier.¶
decision.reported is one of permit, deny, permit-with-conditions. decision.requestDigest MUST equal the first subject entry's sha256 digest and MUST equal the [RFC8785] digest over the members action, argumentsDigest, resourceId and resourceKind (vector F4). The hashed object holds argumentsDigest exactly when resource.binding is digest-bound. Under not-bindable it holds the other three members and no argumentsDigest member at all, so every conforming implementation derives one digest for a given request (vector A6). Altering the action, the resource or the arguments after signing therefore breaks a recompute even where the signature was applied again.¶
Each write carries requestDigest: the request digest of the request the observer attributes the write to, or the literal unattributed where the observer cannot attribute it. An attribution is an observation, and an observer that cannot tie a write to a request says so rather than guessing.¶
effect.observed describes this record's request, not the whole interval, and it is one of occurred, none, unknown. It MUST be derivable from the write set: occurred when at least one write is attributed to decision.requestDigest; none when no write is attributed to it and no unattributed write lies inside pathScope; and unknown otherwise. A write attributed to a different request belongs to that request's record and does not enter this one's value (vectors A7 and T1). A value of none beside a write attributed to this record's request is malformed (vector E1).¶
The ordered write chain MUST reproduce the after-state root from the before-state root. A verifier replays it with a running root that starts at beforeRoot. Each write in effect.writes, in array order and whatever its requestDigest or inScope, MUST carry a preStateDigest equal to the running root, and its postStateDigest then becomes the running root. The running root after the last write MUST equal afterRoot, so a record with no write carries equal roots. Vector E4 breaks only the start, E2 a link between two writes, E5 only the end, and E6 is the case with no write. A baseResolution of empty-tree MUST carry the empty-tree constant for the declared hash algorithm (vector E3).¶
A member of pathScope MUST NOT contain a glob metacharacter, and a universal scope is the single literal / (vector V2). A write outside pathScope MUST carry inScope of false, which is derived from the path and never a producer opinion (vector V3).¶
agreement is one of agree, disagree, not-exercised, indeterminate, one-sided, and it MUST be derivable from decision.reported and effect.observed by the table below (vectors D1, D2 and D3). posture.agreement MUST be derivable from the two carried postures on the same terms: agree when they are equal byte for byte, disagree when both are present and unequal (vector F6).¶
| decision.reported | effect.observed | agreement |
|---|---|---|
permit or permit-with-conditions
|
occurred
|
agree
|
deny
|
none
|
agree
|
deny
|
occurred
|
disagree
|
permit or permit-with-conditions
|
none
|
not-exercised
|
| any |
unknown
|
indeterminate
|
A permit allows an action and does not require it, so a permitted call that failed or was never made is not-exercised, and a deny that leaked is disagree. The two are different facts and an incident review needs to tell them apart (vector A9). An unattributed write inside pathScope leaves the observer unable to rule the request's effect in or out, so the record says indeterminate rather than choosing (vector A8).¶
The table never produces one-sided. one-sided is in the closed set and this version never produces it, because decision and effect are both required and neither side can be absent. It is reserved so that a version admitting a record with one side missing does not have to change a closed vocabulary, and a record carrying it under this version is malformed (vector D2).¶
A verifier MUST NOT reject a record because either agreement value is disagree, not-exercised or indeterminate (vectors A3, A8 and A9). A disagreement is the record working, and what it means for admission is a consumer policy decision.¶
evaluation says whether the decision point evaluated the request, and how fresh each input it relied on was. evaluation.status is one of evaluated, not-evaluated (vector EV11). A decision point records evaluated when its reported decision does not depend on any input it could not obtain, and not-evaluated otherwise. Where delegation paths are judged independently (Section 4.2 of [EVALUATION]), a permit on a fully valid path is evaluated even when an input for another path was unavailable, because no value of that input could change the decision. A deny is not-evaluated whenever a path the decision point could not judge might have been valid.¶
decision.reported always records what the decision point enforced. Under evaluated it is the outcome of evaluating the policy. Under not-evaluated it is the outcome the decision point fell back to, and it is the member that says whether the decision point failed closed (deny) or failed open (permit or permit-with-conditions). A record from a decision point that lost an input and denied therefore carries deny, and beside an effect of none derives agree. Without evaluation that record is byte for byte the record of a correctly enforced denial (vectors A2 and A10), and an incident review cannot tell the two apart. decision.policyDigest names the policy the decision point enforced. Where the policy was the unavailable input, the decision point cannot name that policy's digest, so decision.policyDigest is the digest of the fallback it applied, as delegation.authorityDigest names the deny-all document for a decision taken under no authority (vectors A18 and A20). A decision point that holds the profile a request names and finds that it grants no comparison has evaluated the request, and records evaluated with deny (vector A2); one that does not hold the profile records not-evaluated with policy-source (vector A20).¶
evaluation.inputs is required under both statuses (vector EV13). It is an array with one entry for each input the reported decision depended on, each input named at most once (vector FR10). An entry carries exactly three members, each required (vectors FR5 and FR6), and no other (vector FR11):¶
input, a value from the closed set in the table below (vector FR12);¶
lastResult, the time at which the source produced the last value of that input the decision point held, or the literal none if it never held one;¶
maxAge, the maximum age in seconds, a non-negative integer, that the decision point applies to that input (vector FR13).¶
The reference time for every entry is decision.reportedAt, the time the decision point reported its decision. An input's age is decision.reportedAt minus lastResult. An input is fresh when its age is at most maxAge, so an age equal to maxAge is fresh (vector FR2); it is stale when its age is greater (vector FR3); and it was never established when lastResult is none (vectors A10 and FR4). A lastResult later than decision.reportedAt by more than the skew tolerance is malformed, because a decision cannot rest on a value produced after it was reported (vector FR9). Where the decision depended on more than one value of one input, as when several delegation paths are judged, the entry carries the oldest lastResult among them, none if any was never held, and the smallest maxAge. A permit on a fully valid path depends only on the values that path used, so its entries carry those values and not the values of the other paths. issuedAt is the time the record was sealed, and it is not a reference time for any input.¶
A held value older than the maximum age the decision point applies to its input is one it could not obtain, as Section 3.5 of [EVALUATION] treats standing that cannot be established within the pinned maximum age. The entries are therefore enough to derive the unavailable inputs, and a verifier derives them: the set of entries that are stale or were never established. Under evaluated that set MUST be empty (vector FR8). Under not-evaluated the member MUST carry unavailableInput, a non-empty array naming each input of that set exactly once and no other (vectors EV1, EV5, EV6, EV7 and FR7), from the closed set in the table below (vector EV4). Under evaluated it MUST NOT carry unavailableInput in any spelling, including null, the empty array and the empty string (vectors EV2, EV8, EV9 and EV10). No member other than status, inputs and unavailableInput is defined, and a verifier MUST reject an evaluation member carrying one (vector EV12). A free-text reason beside the closed set would name an input no other record can be compared with.¶
A decision point that receives an input's value from another hop, rather than from its source, copies the source's lastResult into its entry unchanged, so the input's age runs from the source and not from the hop (vectors FR1, FR3 and FR4). It applies its own maxAge in its own record. A hop that later obtains an input an earlier record named as unavailable writes a new record with its own evaluation, and never amends the earlier record, which its producer's signature has sealed. An entry carries what REQ-4 of [EVALUATION-STATE] asks a decision point to record for each input, its as-of time and whether it was accepted, stale or never established, and a forwarded entry carries what REQ-6 asks a hop to preserve: the input name, its evaluation state and its freshness bound.¶
| input | what it names |
|---|---|
standing-source
|
the source issuer standing is established from (Section 3.5 of [EVALUATION]) |
key-source
|
the source of the issuer or evaluator keys a decision verifies under (Section 3.4 of [EVALUATION]) |
consumption-state
|
the record of which approvals have been consumed (Section 4.3 of [EVALUATION]) |
policy-source
|
the policy the decision point evaluates the request against, including a profile the request names that the decision point does not hold |
The first three are the inputs Section 5 of [EVALUATION] names as making a refusal transient. A refusal that document classifies as transient is recorded as not-evaluated, naming the inputs that were unavailable. A refusal it classifies as permanent (an unsupported entry type, a signature that does not verify, an expired grant, an issuer whose standing is withdrawn) is recorded as evaluated with deny. The record then carries what Section 5 requires a verifier's own record of a refusal to carry, and what the caller is told is unaffected.¶
"decision": { "action": "write", "reported": "deny",
"reportedAt": "2026-09-19T11:04:02Z" },
"evaluation": {
"status": "not-evaluated",
"inputs": [
{ "input": "policy-source",
"lastResult": "2026-09-19T10:00:00Z", "maxAge": 86400 },
{ "input": "standing-source",
"lastResult": "2026-09-19T09:30:00Z", "maxAge": 3600 }
],
"unavailableInput": ["standing-source"]
},
"effect": { "observed": "none", "writes": [] },
"agreement": "agree"
¶
The record above is a decision point whose last standing value was 5642 seconds old against a maximum of 3600, and which failed closed. Its agreement is agree, because the deny held, and its evaluation says the deny was not a policy outcome and which input was stale.¶
evaluation is not an input to agreement. "Could not evaluate" is not a decision, so it is not a fourth value of decision.reported, and the agreement table is unchanged: a deny enforced during an outage that leaked anyway is still disagree (vector A19). A verifier MUST NOT reject a record because evaluation.status is not-evaluated, whatever decision.reported and agreement are (vectors A10, A12 to A14, and A18 to A20). A permit recorded as not-evaluated is a decision point failing open, and the format represents it so that it can be found (vector A18). The evidence class of evaluation is the one fieldEvidence declares for decision.¶
oversight is optional. When present it records the kind of act a person took on this request before or at the decision, so that a permit bound to a user confirmation (Section 10.7 of [AIMS]) says whether that person observed the request, checked a stated property of it, chose, or released an action already within authority they held. The four kinds are those of [OVERSIGHT-ACTS], Sections 2.3 to 2.9.¶
oversight.act is required in the member and is one of observation, check, decision, release (vector OV1). oversight.recordDigest is optional and, when present, is the digest of the overseer's own signed record, bound as delegation.authorityDigest binds the authority document. No other member is defined, and a verifier MUST reject an oversight member carrying one (vector OV2). The act is producer-asserted: its evidence class is the one fieldEvidence declares for decision, and a verifier does not resolve recordDigest.¶
correlation.scope is one of producer, cross-party, and it is required because Section 11 asks systems to "correlate events across Agents, Tools, Services, Resources and LLMs", which a producer-scoped identifier cannot do. A record a producer distributes beyond its own deployment SHOULD carry a scope of cross-party.¶
correlation.timeBasis of beacon-anchored MUST carry externalAnchor, and asserted MUST NOT carry it (vector F5). externalAnchor.kind is one of rfc3161, transparency-log, opentimestamps, of which [RFC3161] and [RFC9162] define the first two. externalAnchor.digest is the digest of the anchor's proof and not the proof, so a verifier that holds only the record cannot validate the anchor. Such a verifier reads beacon-anchored as a statement the producer made, accepts the record, and does not date it from the anchor (vector N1). A record is dated from the anchors of a retention entry (Section 4.7), which are over the envelope digest and are validated under the rules in Section 4.7.2. The record's verdict and its dating are two results, and a verifier reports them separately, so vector N1 has one conforming verdict where draft-gilda-wimse-agent-audit-record-02 admitted two.¶
posture.reported and posture.observed are over the closed set no_network, allowlist, sinkhole, unsafe_bypass_egress, each with a digest pinning the posture document. posture.assessedAt carries the time the assessment was made, which Appendix A gives the reason for. A consumer weighing either posture SHOULD read assessedAt beside it.¶
remediation carries one member per event. Its cause is one of session-revoked, risk-elevated, subject-disabled, token-replay-suspected, policy-changed, operator-action. Its enforcement is one of access-attenuated, session-terminated, tokens-discarded, privileges-reduced, reevaluated, not-enforced. In each member enforcedAt MUST be at or after signalReceivedAt (vector F7).¶
A verifier MUST NOT reject a record because enforcement is not-enforced (vector A4). A record carries that value when a remediation signal arrived and enforcement did not follow it.¶
postEnforcementEffect and postEnforcementRoot make the Section 11 delay requirement checkable by a third party. A revocation that arrived, was recorded, and was followed by an observed effect is a recorded enforcement failure. This document defines no bound on the delay, because Section 11 carries no unit for "undue", and vector N2 records that case as indeterminate.¶
observation.vantage is one of below-observed, peer, self. An observer that could not read part of pathScope SHOULD name that path in coverage.gaps, because the tier recompute below reads the gap list and not the observer's confidence. observation.priorCommitment is the observer's commitment, made before the interval opened, to the before-state and to a nonce the observer chose.¶
Its committedAt MUST be strictly before the interval's openedAt (vector C2). Its commitmentDigest MUST recompute as the [RFC8785] digest over authorityDigest, beforeRoot, recordId and witnessNonce (vector C3). Its keyid MUST NOT appear in agent.signers (vector C1), and that disjointness is the one byte-pure discriminator between an observer and a self-observing party.¶
doesNotAssert is the negative-scope member the sections above refer to: an array of explicit statements about what this record does not claim. A record under this version SHOULD carry two entries. The first entry records that it makes no claim about retention. The second records that it makes no claim that the reported decision is the decision the policy engine evaluated.¶
fieldEvidence carries exactly the seven keys named in the mapping table, each one of substrate-covered or producer-asserted. Both terms are reused unchanged from the field-evidence partition registered in [VOCABULARY], as the four posture values above are reused from its containment-posture terms. A missing key is malformed (vector FE1), and no key may declare substrate-covered while vantage is self (vector FE2).¶
tier is one of voluntary, authoritative. A verifier MUST recompute it and MUST NOT take the declared value as the tier. Vectors TI1 through TI5 each declare authoritative while failing one clause, so a verifier that reads the declared value passes all five.¶
A record is authoritative if and only if all five of these hold, and voluntary otherwise: vantage is below-observed (vector TI1); priorCommitment is present and complete (vector TI2); pathScope is non-empty (vector TI3); coverage.scopeComplete is true or every gap names a path outside pathScope (vector TI4); and resource.binding is digest-bound (vector TI5).¶
A record declaring authoritative that fails a clause is malformed, and a verifier MUST NOT downgrade it to voluntary (vector TI1). Downgrading would let a producer emit an authoritative-shaped record and rely on the verifier to relabel it.¶
The opposite mismatch is well formed. A record declaring voluntary while meeting all five clauses is not malformed, and a verifier reads it at the tier the recompute gives. Treating the two directions differently is deliberate: a producer that over-claims is relying on somebody else to correct it, and a producer that under-claims is not.¶
A verifier MUST NOT read a voluntary record as evidence that its content corresponds to any independently observed fact (vector A5).¶
The record at the head of Section 5 reports a write that was denied and happened anyway. Each rule above lands on one of its members.¶
decision.reported is deny, and the single write is attributed to the record's own request, so effect.observed derives as occurred. The third row of the agreement table maps that pair to disagree, which is the value the record carries, so the derivation holds and vector D1 does not apply.¶
posture.reported is sinkhole and posture.observed is allowlist. Both are present and they are unequal, so posture.agreement derives as disagree, which is again the carried value.¶
The tier recompute passes all five clauses. vantage is below-observed; priorCommitment carries all five of its own members; pathScope holds one entry; coverage.scopeComplete is true with no gaps; and resource.binding is digest-bound. The declared authoritative is therefore the tier a verifier derives.¶
evaluation.status is evaluated and the member names no unavailable input, so vector EV2 does not apply. Each of its two inputs is fresh at decision.reportedAt, 11:04:02Z: the policy was produced 3842 seconds earlier under a maxAge of 86400, and the standing 242 seconds earlier under a maxAge of 3600, so the recompute gives an empty set of unavailable inputs and vector FR8 does not apply. The record carries no oversight member and claims nothing about a human act.¶
The ordering checks hold on the timestamps as written. Vector C2 tests the first: committedAt at 11:03:58Z falls strictly before openedAt at 11:04:01Z. Vector F7 tests the second: enforcedAt at 11:04:09Z falls two seconds after signalReceivedAt at 11:04:07Z.¶
The single write lies under /srv/ledger/, the one member of pathScope, so its inScope of true is derived from the path and vector V3 does not apply. fieldEvidence carries all seven keys, so vector FE1 does not apply, and vantage is not self, so vector FE2 does not either.¶
What the record says, read whole, is that a write was denied and happened anyway, that the posture the decision point read was not the posture an observer saw, and that a revocation signal was enforced two seconds after it arrived. All three are well formed, and a verifier accepts the record.¶
A record is checkable for internal coherence and says nothing about the world outside its own bytes. Where a consumer needs a fact about that world, it has to establish that fact somewhere else.¶
The vantage claim in a record is an assertion about the world, and the record makes it coherent with its own members without making it self-proving. A consumer that requires the vantage to be true MUST anchor the observer's key out of band, exactly as it would anchor any signer. Such a consumer MUST read authoritative as coherence with a claimed observation.vantage, and never as proof of one (vector A5).¶
A producer that observes only itself can satisfy every field of this format, and the observation.priorCommitment member raises the cost of doing so. It binds the before-state, the authority and the record identifier under the observer's signature before the interval opens. A forgery therefore has to be decided on before the forger knows what it will be lying about.¶
It does not reduce the cost to zero, and a producer holding two keys satisfies every clause. That residual is detectable by a key inventory outside the record and by no function of the bytes inside it. A consumer that depends on the vantage SHOULD keep such an inventory.¶
A record can omit a remediation event, or a claimed access that the observer saw, and no function of a Statement detects a row that was never written. No quantity of extra carried material changes that, because extra material is material a withholding producer also declines to carry.¶
The coverage, fieldEvidence and doesNotAssert members exist so that a blind spot travels where the observer is honest about it. An observer that knows of a blind spot SHOULD record it in the doesNotAssert member. None of these members is a defence against a producer that conceals one.¶
A disagreement between the reported decision and the observed effect is well formed, as is a disagreement between the two postures, and as is a remediation event recording that enforcement did not occur. A verifier MUST accept all three (vectors A3 and A4). A format that refused to represent a failed enforcement would mean that no record ever reports one. This clause prevents that.¶
A record whose evaluation.status is not-evaluated is well formed beside any reported decision and any agreement, and a verifier MUST accept it (vectors A10, A12 to A14, and A18 to A20). A deny that was not evaluated is a decision point failing closed, and a permit that was not evaluated is one failing open. Both are facts an audit trail exists to carry. A consumer SHOULD treat a not-evaluated permit as an enforcement failure for policy purposes, and this document leaves that to the consumer.¶
The reason is the one Section 6.4 gives for a disagreement. A format with no way to say that a decision point could not evaluate records every outage as policy: each lost standing source reads as a correctly enforced denial, and each fail-open reads as a permit somebody decided. An incident review that reads such a trail finds no outage, because none was ever written down.¶
Every source a decision point reads is also a way to stop it (Section 5 of [EVALUATION]). An attacker who can make a source unavailable turns fail-closed evaluation into a denial of service, and the not-evaluated records are the evidence of it. A consumer SHOULD read a rate of not-evaluated records naming one input as an availability event, not as a series of policy decisions.¶
evaluation is producer-asserted. A decision point that records evaluated for a decision that depended on an input it did not obtain is withholding, as in Section 6.3, and no function of the record's bytes detects it. The same holds for evaluation.inputs: the recompute catches an entry whose own times contradict the status, and it cannot catch a lastResult written later than the source produced the value, or a hop that wrote the time it received a forwarded value in place of the source's. A consumer that relies on freshness SHOULD read each entry's evidence class as the one fieldEvidence declares for decision.¶
Section 11 of [AIMS] requires that revoked or downgraded authorization be enforced "without undue delay" and specifies no bound. This document carries signalReceivedAt and enforcedAt, and defines no bound of its own. A record with an arbitrarily large gap between them is well formed. A consumer SHOULD set a bound on that gap as policy and read a record that exceeds it accordingly.¶
A signature over canonical bytes detects an editor who cannot sign. A trusted timestamp [RFC3161] adds an assertion about when the bytes existed, and an append-only log [RFC6962] [RFC9162] adds an assertion that they were published. This document requires neither, and a deployment MAY carry a record under this format in such a log.¶
None of the three detects a producer that signed a false record. The recomputes in Section 4.4 address that case.¶
Appendix A gives the mechanism by which two implementations can share a serialization that is not [RFC8785]. Neither party can see the failure from inside it. Every signature each of them produces verifies for the other, and the records fail only for a third party that canonicalizes correctly.¶
A consumer therefore derives the canonical bytes itself and does not accept a producer's report of them. Section 4.1 is normative for that reason, and vector T2 is the member that tests it.¶
A retention entry's receivedAt is asserted by the retaining party, and Section 4.7.1 makes it checkable for consistency, not true. A retaining party that anchors a digest before the envelope arrives, or anchors late and states a correspondingly late receipt, produces an entry that passes the check. A relying party that grants a received record an allowance, such as dating it from its receipt rather than from its issuance, is relying on the two assumptions Section 4.7.1 states, and SHOULD limit the allowance to retaining parties it trusts to anchor on delivery.¶
An anchor of origin received was obtained by the party that sent the record. It can date the record's existence, and it says nothing about when the retaining party received it, so it never forms the upper edge of the receipt bracket.¶
A record under this document carries an agent identifier, a delegated subject, a resource identifier and a set of paths. The delegated subject in particular may identify a natural person. Deployments SHOULD consider which of these a given consumer needs before distributing a record. The doesNotAssert member is the place to record what a distributed record deliberately omits.¶
The format binds five documents by digest and carries none of them: the credential, the authority document, the policy, and both posture documents. A consumer that needs their content SHOULD resolve them out of band.¶
That separation is deliberate and is the privacy-preserving default, because it lets a record travel to a party entitled to verify the decision without carrying the material the decision was made from. A consumer entitled to the decision receives the digest and resolves the document only if it needs the content.¶
A retention entry's anchors are over the envelope digest alone, so an anchoring service, a transparency log or a time-stamping authority learns that a record existed and nothing in it. A retaining party that discloses an entry discloses its receipt time and the parties whose validation data it holds, and SHOULD treat the entry with the same care as the record.¶
This document has no IANA actions. The predicate type is a URI under a namespace the author controls and requires no registration to be used, consistent with the in-toto Attestation Framework's treatment of predicate type identifiers [IN-TOTO].¶
A registered identifier would be the alternative. It is not proposed here, because a record carries its predicate type inside the bytes its producer signed. A later revision SHOULD NOT move the URI once records carrying it exist, since moving it invalidates every signature over them.¶
No revision has yet frozen the URI. Until a revision states in its Document History that it does, the members published in [VECTORS] are test records signed under published test keys, and a revision may change what a record under the URI must carry, as draft-gilda-wimse-agent-audit-record-02 did (vector EV3) and as this revision does (vector EV13). After the freeze, a change to what a record must carry is published under a new URI, as Section 5 requires for an extended vocabulary.¶
Note to the RFC Editor: please remove this section before publication.¶
This section records the status of known implementations at the time of writing, following [RFC7942]. Listing an implementation here does not imply endorsement by the IETF.¶
[VECTORS] is an open conformance corpus for this format, licensed under Apache-2.0. Its vectors-agent-audit-record family publishes each member of Appendix B as a signed DSSE envelope with the verdict a conforming verifier must reach. It ships two verifiers written from the text, one in Python that uses only the standard library and one in Go. Release v0.18.1, published on 13 October 2026, carries the 68 members defined by draft-gilda-wimse-agent-audit-record-02, and anyone can rerun every one of them with a single command:¶
uvx agent-evidence-vectors==0.18.1 \
--corpus vectors-agent-audit-record
¶
The run prints the verdict counts and whether every member behaves as its manifest declares; on release v0.18.1 it reports 17 members to accept, 49 to reject and 2 indeterminate, each behaving as declared. Two parties outside the corpus's authorship ran its members themselves and posted their results on the issue tracker of [AIMS]: 47 of 47 members behaved as specified at release 0.14.1, and 47 of 47 at release 0.16.0, including both readings that the earlier revision allowed for vectors N1 and N2.¶
Those 68 members are records under the earlier revision, which this revision makes malformed because it requires evaluation.inputs (vector EV13). The corpus is regenerated for this revision in the release that follows it, with the 31 members this revision adds, which Appendix B marks as next.¶
Two requirements of this document are not derivable from Section 11 of [AIMS] on its own: the canonicalization contract, and carrying the observed effect beside the reported decision. Neither is restated normatively here.¶
The word "tamper-evident" appears once in [AIMS] and is not defined there. Read narrowly it means a record whose alteration is detectable, which a signature provides. Two gaps remain.¶
A signature over a serialization is only as good as the agreement on which bytes were signed, and that agreement is a property of a written contract between two implementations; neither one's code establishes it. Two implementations can agree with each other on a canonical form and both differ from [RFC8785], and no amount of signing or hash-chaining detects that.¶
Each verifies its own records and rejects the other's, or worse, accepts a record whose meaning it has silently changed. A canonicalization contract is therefore part of the tamper-evidence requirement; it is not an implementation detail, and Section 4.1 makes it normative.¶
A signature also says nothing about the producer. The party that holds the signing key can alter a record and sign it again, and every signature check still passes.¶
Detecting that requires fields whose values are recomputable from other fields in the same record, so that an internally inconsistent record is refusable regardless of who signed it. A hash chain catches an alteration by a party with no signing key; only a recompute catches the key holder, and Section 4.4 specifies three of them.¶
The fourth of the seven minimum fields pairs "action requested and authorization decision". A record carrying only the decision is a self-report: it states what an enforcement point concluded, and a deployment that mis-enforces its own conclusion produces a clean audit trail of the decision it failed to apply. Section 11 asks for enough to "reconstruct agent behavior and authorization context after execution", and behaviour is not reconstructable from a decision.¶
This document therefore requires both sides in one record: the decision as reported by the enforcement point, and the effect as observed. It also requires a three-valued agreement between them, which a verifier derives from the two values; it is not a claim the record makes. A decision reported as denied beside an effect observed as having occurred is then a refusable inconsistency in signed bytes. Today it is undetectable.¶
The same construction applies to the sixth field. Section 8 of [AIMS] defines posture assessment as an evaluation performed at credential provisioning, while the sixth field asks for the posture "influencing the decision".¶
A conformant record may therefore carry a posture that was accurate at issuance and inaccurate at decision time, with nothing in the record to say which. Carrying posture as the decision point read it, beside the posture as it was observed and its assessedAt, makes that difference visible without constraining either value.¶
Each clause of the tier recompute closes one route by which a record could claim an observer's vantage while carrying nothing that distinguishes it from a self-report.¶
vantage of below-observed is the claim itself, and the four clauses under it make the claim cost something. The prior commitment binds the before-state before the interval opens, so a producer cannot decide after the fact what it saw. A non-empty pathScope means the record names the ground it covers. A complete coverage claim, or a gap list naming only paths outside that ground, means the record does not quietly exclude the interesting part. And a digest-bound resource means the arguments of the call are pinned.¶
Dropping any one of them admits a record that carries the authority of the rest while resting on nothing. That is why the corpus in Appendix B carries one reject member per clause.¶
The table below is the conformance corpus for this format. Each row gives a member identifier, the accept member it derives from, its input, and the verdict a conforming verifier reaches. A reject differs from the accept member in its from column by one mutation, so refusing every input scores nothing on this corpus.¶
An accept member has no parent and its from column reads root. An indeterminate member has more than one conforming verdict and its row gives each. A member whose input is a retained record (R1 to R9) is an envelope with its retention entry, and its row gives the envelope's verdict and the result of the dating check separately.¶
The first in column names the release of [VECTORS] that first published a member under that identifier, or next for a member this revision adds. Because evaluation.inputs is required here, every member published under draft-gilda-wimse-agent-audit-record-02 is malformed under this revision (vector EV13), and the whole corpus is regenerated in the release that follows it. Each member keeps its identifier and its row, with the entries it needs in evaluation.inputs; vector A10 carries standing-source with a lastResult of none, and vector N1 changes verdict as Section 5.6 states.¶
Every row names the rule it tests. Three rules this document adds have no member in the table: the binding on the digest map in Section 4.2, the [RFC3339] requirement in Section 4.3, and the single hash algorithm of Section 5.1. An implementation SHOULD be run against every member of the corpus before it is described as conforming to this document, and the three rules above are checked by reading them.¶
The corpus travels with the predicate schema: the revision of this document that freezes the predicate type URI is the revision that publishes these members in [VECTORS], in that repository's manifest layout.¶
| id | from | what it is | expected | first in |
|---|---|---|---|---|
A1
|
root | every member present, permit beside occurred, all five tier clauses met |
valid | v0.18.1 |
A2
|
root |
deny beside none, equal roots, empty write set |
valid | v0.18.1 |
A3
|
root |
A1 with deny beside occurred and agreement of disagree
|
valid | v0.18.1 |
A4
|
root |
A1 plus a remediation event with enforcement of not-enforced
|
valid | v0.18.1 |
A5
|
root |
A1 with vantage of self, no prior commitment, tier of voluntary
|
valid | v0.18.1 |
A6
|
root |
A1 with binding of not-bindable, argumentsDigest absent from the record and from the request-digest preimage, tier of voluntary
|
valid | v0.18.1 |
A7
|
root |
A2 plus one in-scope write inside the interval attributed to a different request, roots and chain moved to match |
valid, agreement of agree
|
v0.18.1 |
A8
|
root |
A2 plus one in-scope unattributed write inside the interval, effect.observed of unknown
|
valid, agreement of indeterminate
|
v0.18.1 |
A9
|
root |
A1 with permit beside an empty write set, equal roots, effect.observed of none
|
valid, agreement of not-exercised
|
v0.18.1 |
A10
|
root |
A2 with evaluation.status of not-evaluated and unavailableInput of standing-source
|
valid, agreement of agree
|
v0.18.1 |
A11
|
root |
A1 plus oversight with act of check and a recordDigest
|
valid | v0.18.1 |
A12
|
root |
A10 with unavailableInput of key-source
|
valid, agreement of agree
|
v0.18.1 |
A13
|
root |
A10 with unavailableInput of consumption-state
|
valid, agreement of agree
|
v0.18.1 |
A14
|
root |
A10 with unavailableInput naming all three inputs of -02, each once, in neither the table order nor sorted order |
valid, agreement of agree
|
v0.18.1 |
A15
|
root |
A11 with oversight.act of observation
|
valid | v0.18.1 |
A16
|
root |
A11 with oversight.act of decision and no recordDigest, which is optional |
valid | v0.18.1 |
A17
|
root |
A11 with oversight.act of release
|
valid | v0.18.1 |
A18
|
root |
A1 with evaluation.status of not-evaluated, policy-source never established, unavailableInput of policy-source and the allow-all fallback's decision.policyDigest
|
valid, agreement of agree
|
next |
A19
|
root |
A3 with evaluation.status of not-evaluated, key-source and consumption-state stale, and unavailableInput naming both |
valid, agreement of disagree
|
next |
A20
|
root |
A2 with evaluation.status of not-evaluated, a profile the request names not held, unavailableInput of policy-source and the deny-all fallback's decision.policyDigest
|
valid, agreement of agree
|
next |
F1
|
A1
|
A1 with the whole agent member removed |
malformed | v0.18.1 |
F2
|
A1
|
subjectKind of none beside a named subject |
malformed | v0.18.1 |
F3
|
A1
|
digest-bound with argumentsDigest removed |
malformed | v0.18.1 |
F3b
|
A6
|
not-bindable carrying argumentsDigest of null
|
malformed | v0.18.1 |
F3c
|
A6
|
not-bindable carrying argumentsDigest of the empty string |
malformed | v0.18.1 |
F4
|
A1
|
action changed, requestDigest left at its original value |
malformed | v0.18.1 |
F5
|
A1
|
beacon-anchored with externalAnchor removed |
malformed | v0.18.1 |
F6
|
A1
|
two unequal postures declared as agree
|
malformed | v0.18.1 |
F7
|
A4
|
enforcedAt one second before signalReceivedAt
|
malformed | v0.18.1 |
T1
|
A1
|
effect.observed edited to none, writes intact, signed again with the real key |
malformed | v0.18.1 |
T2
|
A1
|
serialized in declaration order, not [RFC8785] order | malformed | v0.18.1 |
T3
|
A1
|
a member name repeated at depth three | malformed | v0.18.1 |
T4
|
A1
|
nesting 129 levels deep | malformed | v0.18.1 |
T5
|
A1
|
an integer of magnitude exactly 2^53 | malformed | v0.18.1 |
D1
|
A1
|
agreement of disagree with both sides agreeing |
malformed | v0.18.1 |
D2
|
A1
|
agreement of one-sided with both values present |
malformed | v0.18.1 |
D3
|
A9
|
agreement of disagree beside permit and none
|
malformed | v0.18.1 |
S1
|
A1
|
second subject entry removed, interval left present | malformed | v0.18.1 |
S2
|
A1
|
second subject entry's digest set to the before-state root | malformed | v0.18.1 |
I1r
|
A1
|
interval removed, second subject entry left present | malformed | v0.18.1 |
TI1
|
A1
|
authoritative declared with vantage of self
|
malformed | v0.18.1 |
TI2
|
A1
|
authoritative declared with no prior commitment |
malformed | v0.18.1 |
TI3
|
A1
|
authoritative declared with an empty pathScope
|
malformed | v0.18.1 |
TI4
|
A1
|
authoritative declared with a coverage gap inside pathScope
|
malformed | v0.18.1 |
TI5
|
A6
|
authoritative declared on A6
|
malformed | v0.18.1 |
C1
|
A1
|
commitment keyid added to agent.signers
|
malformed | v0.18.1 |
C2
|
A1
|
committedAt one second after openedAt
|
malformed | v0.18.1 |
C3
|
A1
|
witnessNonce changed, commitmentDigest left at its original value |
malformed | v0.18.1 |
E1
|
A2
|
observed of none with a non-empty write set |
malformed | v0.18.1 |
E2
|
A1
|
a second write whose pre-state is not the first write's post-state | malformed | v0.18.1 |
E3
|
A1
|
empty-tree under sha256 carrying the sha1 constant |
malformed | v0.18.1 |
E4
|
A1
|
the only write's pre-state is not the before-state root, its post-state is the after-state root | malformed | next |
E5
|
A1
|
the only write starts from the before-state root and its post-state is not the after-state root | malformed | next |
E6
|
A2
|
an empty write set beside an after-state root unequal to the before-state root, second subject moved to match | malformed | next |
V1
|
A4
|
enforcement of quarantined, outside the closed set |
malformed | v0.18.1 |
V2
|
A1
|
a pathScope member carrying a glob metacharacter |
malformed | v0.18.1 |
V3
|
A1
|
a write outside pathScope carrying inScope of true
|
malformed | v0.18.1 |
FE1
|
A1
|
six of the seven fieldEvidence keys present |
malformed | v0.18.1 |
FE2
|
A5
|
substrate-covered declared beside vantage of self
|
malformed | v0.18.1 |
FR1
|
root |
A1 written by the second of two hops: each input fresh at decision.reportedAt, one carried from the first hop with the source's lastResult unchanged |
valid | next |
FR2
|
root |
FR1 with one input's age at decision.reportedAt exactly equal to its maxAge
|
valid, the input fresh | next |
FR3
|
root |
A10 written by the second of two hops, the forwarded standing-source one second older than its maxAge
|
valid, agreement of agree
|
next |
FR4
|
root |
A10 written by the second of two hops, which received standing-source as never established, lastResult of none
|
valid, agreement of agree
|
next |
FR5
|
FR1
|
an entry with lastResult removed |
malformed | next |
FR6
|
FR1
|
an entry with maxAge removed |
malformed | next |
FR7
|
FR3
|
unavailableInput naming key-source, whose entry is fresh, in place of standing-source
|
malformed | next |
FR8
|
FR1
|
evaluated beside an entry one second older than its maxAge
|
malformed | next |
FR9
|
FR1
|
an entry whose lastResult is later than decision.reportedAt by more than the skew tolerance |
malformed | next |
FR10
|
FR1
|
one input named in two entries | malformed | next |
FR11
|
FR1
|
an entry carrying a fourth member this document does not define | malformed | next |
FR12
|
FR1
|
an entry whose input is network, outside the closed set |
malformed | next |
FR13
|
FR1
|
an entry with a negative maxAge
|
malformed | next |
EV1
|
A10
|
not-evaluated with unavailableInput removed |
malformed | v0.18.1 |
EV2
|
A2
|
evaluated carrying unavailableInput of standing-source
|
malformed | v0.18.1 |
EV3
|
A1
|
the evaluation member removed, which is the shape of a revision 01 record |
malformed | v0.18.1 |
EV4
|
A10
|
unavailableInput of network, outside the closed set |
malformed | v0.18.1 |
EV5
|
A10
|
not-evaluated with unavailableInput of the empty array |
malformed | v0.18.1 |
EV6
|
A10
|
not-evaluated with standing-source named twice |
malformed | v0.18.1 |
EV7
|
A10
|
not-evaluated with unavailableInput of the string standing-source, not an array |
malformed | v0.18.1 |
EV8
|
A2
|
evaluated carrying unavailableInput of null
|
malformed | v0.18.1 |
EV9
|
A2
|
evaluated carrying unavailableInput of the empty array |
malformed | v0.18.1 |
EV10
|
A2
|
evaluated carrying unavailableInput of the empty string |
malformed | v0.18.1 |
EV11
|
A10
|
evaluation.status of partially-evaluated, outside the closed set |
malformed | next |
EV12
|
A10
|
evaluation carrying a free-text reason member this document does not define |
malformed | next |
EV13
|
A1
|
evaluation.inputs removed, the shape of a record under draft-gilda-wimse-agent-audit-record-02 |
malformed | next |
OV1
|
A11
|
oversight.act of approval, outside the closed set |
malformed | v0.18.1 |
OV2
|
A11
|
oversight carrying a member this document does not define |
malformed | v0.18.1 |
OV3
|
A11
|
oversight with act removed and recordDigest kept |
malformed | v0.18.1 |
N1
|
A1
|
an anchor token digest carried in correlation.externalAnchor, the proof not held |
valid, not dated by the anchor | v0.18.1, verdict changed |
N2
|
A4
|
enforcement forty days after the signal, ordering intact | indeterminate | v0.18.1 |
R1
|
root |
A1 retained by its producer: received of false and one anchor of origin retainer inside anchorWithin of issuedAt
|
record valid, dated at the anchor's time | next |
R2
|
root |
A1 received from another party, receivedAt between issuedAt and the retaining party's anchor obtained on delivery |
record valid, receipt time asserted and consistent | next |
R3
|
R2
|
receivedAt later than the retaining party's earliest anchor by more than the skew tolerance |
record valid, entry malformed | next |
R4
|
R2
|
received of true and no anchor of origin retainer
|
record valid, entry malformed | next |
R5
|
root |
R2 with the retaining party's anchor obtained before delivery and receivedAt inside the bracket |
record valid, receipt time asserted and consistent, never established | next |
R6
|
R2
|
receivedAt earlier than issuedAt by more than the skew tolerance |
record valid, entry malformed | next |
R7
|
R1
|
the retaining party's first anchor later than issuedAt by more than anchorWithin plus the skew tolerance |
record valid, entry malformed | next |
R8
|
R1
|
the only anchor's proof is over a different envelope digest | record valid, not dated | next |
R9
|
R1
|
receivedAt carried beside received of false
|
record valid, entry malformed | next |
The table below takes each problem the AUDIT BoF request [AUDIT-BOFREQ] and the proposed charter [AUDIT-CHARTER] name, quoted or closely paraphrased from those texts, and gives the part of this document that addresses it. Where this document leaves a problem to other work, the row says so. The charter's first deliverable, an architecture, and its third and fourth, protocol extensions and deployment practice, are outside a record format; the rows below fall under its second deliverable, audit data models and semantics, unless a row names another.¶
| AUDIT problem | where it is stated | this document |
|---|---|---|
| who initiated an action | BoF request |
agent.id, agent.credentialDigest, delegation.subject and delegation.subjectKind, Section 5
|
| under which authority it occurred | BoF request |
delegation.authorityDigest and decision.policyDigest, with evaluation saying whether the authority was evaluated, Section 5 and Section 5.4
|
| how permissions evolved during execution | BoF request | one record per decision, joined by correlation.id; remediation for each revocation or downgrade with its enforcement and the effect after it, Section 5.6. A record carries one delegation hop and no delegation chain |
| attributing actions to a user, an agent instance or a delegation context | charter, challenges |
agent and delegation, with the request bound by digest to the action, resource and arguments, Section 5
|
| visibility across long-running or distributed workflows | charter, challenges |
correlation.id with correlation.scope of cross-party, and the subject convention that binds one identifier to the request, the interval and the record, Section 4.2 and Section 5.6
|
| delegation relationships, dynamic authorization context and identity transitions | charter, challenges | the authorization context of each decision, including the freshness of every input it relied on, Section 5.4; identity transitions are left to the identity work the charter relies on |
| interoperable means to exchange or verify audit information across domains | charter, challenges | the canonical form, Section 4.1; the verification order, Section 4.6; the open conformance corpus, Appendix B and Section 9 |
| verifiable by a party that trusts neither the agent nor its operator | charter | the recomputes, which refuse a record its own key holder altered, Section 4.4; the derived tier, Section 5.7; and what a record cannot prove, Section 6 |
| user intent | BoF request, charter | not carried; a record can bind an intent record by digest as it binds the authority document, and keeps the user's statement apart from the decision |
| invoked or declined actions | BoF request, charter |
decision.reported beside effect.observed and the derived agreement, so a declined action that took effect is visible, Section 5
|
| exchanged data | BoF request, charter |
resource.argumentsDigest binds the arguments by digest and carries none of them, Section 5 and Section 7
|
| resulting outcomes | BoF request, charter |
effect, its write chain and the before-state and after-state roots, Section 5
|
| a common identifier linking user intent to resulting actions across boundaries | BoF request, charter |
correlation.id, the first subject entry's name, Section 4.2 and Section 4.3
|
| authorization as a time-evolving state | charter |
evaluation.inputs, posture.assessedAt and remediation, Section 5.4 and Section 5.6
|
| selective disclosure and privacy | charter | digests in place of the five bound documents, and anchors over a digest alone, Section 7; no mechanism for disclosing part of a record's own members |
| collecting, aggregating, verifying and transparency-logging records, possibly by an independent auditor | charter, scope and deliverable 2 | what a retaining party keeps, the anchor kinds including a transparency log, and the dating check, Section 4.7; no protocol for collection or retrieval |
| a data model for records | BoF request, new work | this document |
| protocols to store and retrieve records | BoF request, new work | not defined; Section 4.7 defines what is stored and leaves the protocol open |
This document replaces draft-gilda-wimse-agent-audit-record. Its target is the AUDIT BoF and the working group it proposes. The revisions listed below as -02, -01 and -00 are revisions of that document.¶
Changes in draft-gilda-audit-agent-audit-record-00:¶
Section 4.7 defines the retention entry a retaining party keeps beside a record: anchors over the envelope digest with their kind and origin, the validation data for the envelope and each anchor, and, for a received record, receivedAt. A verifier derives each anchor's time and algorithm family from its proof.¶
Section 4.6 gains the dating check. The earliest anchor that validates dates the record, and no issuance time a record states does. A received record must carry an anchor the retaining party obtained on delivery, and a receivedAt outside the bracket from issuedAt to that anchor, within the skew tolerance, makes the entry malformed. The check is stated to be a consistency check: it rests on the sender's clock and on the retaining party anchoring on delivery, a digest anchored before delivery does not establish receipt, and the late-anchor limit is stated as an assumption.¶
Section 4.7.2 gives an offline validation rule for each anchor kind, and says when a transparency-log anchor is a hash commitment and when a signed token. The predicate's own correlation.externalAnchor dates nothing by itself, so vector N1 has one conforming verdict.¶
Section 5.4 gains evaluation.inputs, required under both statuses: one entry per input the decision depended on, with lastResult and maxAge, both required. Age is measured from lastResult to decision.reportedAt, an age equal to maxAge is fresh, and unavailableInput is derived from the entries and must match them. A forwarded value keeps the source's lastResult. Under draft-gilda-wimse-agent-audit-record-02 freshness could not be recorded for an input that was accepted.¶
unavailableInput gains policy-source, for a decision point that could not read its policy or does not hold the profile a request names. decision.policyDigest then names the fallback the decision point enforced.¶
The rule for evaluated is stated: the reported decision does not depend on any input the decision point could not obtain. decision.reported under not-evaluated is stated to be the outcome the decision point fell back to, so a fail-open is a permit and stays findable.¶
evaluation carries no member other than status, inputs and unavailableInput. Under the earlier revision a free-text reason beside the closed set was not refused.¶
Each input is mapped to the section of [EVALUATION] that defines it, and a transient refusal under that document is mapped to not-evaluated and a permanent one to evaluated with deny.¶
The write-chain replay is stated in Section 5: where the running root starts, what each write must match, and where it must end. Under the earlier revision the body required the replay without defining it.¶
Section 4.6 applies the JSON profile before the signature, because deriving the [RFC8785] bytes the signature covers requires a parse the duplicate-member rule constrains.¶
A closed vocabulary is extended only by a revision, and after the predicate type URI freezes only under a new URI. No vocabulary carries a catch-all value. Section 8 states that no revision has yet frozen the URI.¶
A skew tolerance of 300 seconds is defined for every comparison between times written by two parties.¶
Section 9 records the open implementation, and Appendix C maps the problems the AUDIT BoF request and proposed charter name to this document.¶
A record under the earlier revision is malformed under this one, because it carries no evaluation.inputs (vector EV13). The corpus is regenerated, and 31 members are added to Appendix B: A18 to A20, E4 to E6, EV11 to EV13, FR1 to FR13 and R1 to R9.¶
Changes in draft-gilda-wimse-agent-audit-record-02:¶
A required evaluation member records whether the decision point evaluated the request, and under not-evaluated names the unavailable inputs from a closed set (Section 5.4). Under -01 a verifier that lost its standing source and denied wrote a record identical to a correctly enforced denial. decision.reported and the agreement table are unchanged.¶
An optional oversight member records the kind of act a person took behind a decision, and may bind the overseer's own signed record by digest (Section 5.5).¶
A record conforming to -01 is malformed under -02, because it carries no evaluation member and no member has a default (vector EV3). The predicate type URI does not change: no revision has yet frozen it, and the corpus in [VECTORS] is regenerated against this revision.¶
Eight members are added to Appendix B: A10, A11, EV1 to EV4, OV1 and OV2.¶
Changes in draft-gilda-wimse-agent-audit-record-01:¶
The request-digest preimage under not-bindable is stated: three members, with no argumentsDigest member in any spelling. Under -00 the text named four members and forbade the fourth, so the preimage was open and three spellings gave three digests.¶
Each write carries the request digest it is attributed to, or unattributed. effect.observed describes this record's request, and it gains unknown for an unattributed write inside the scope. A write that belongs to another request no longer changes this record's agreement.¶
agreement gains not-exercised, for a permit beside no effect, and indeterminate, for an effect the observer could not attribute. Under -00 a permitted call that was never made read as disagree, the value a leaked deny also takes.¶
A statement whose signature fails is malformed, so Section 4.6 and vector T2 report the same verdict.¶
Six members are added to Appendix B: A7, A8, A9, F3b, F3c and D3.¶
draft-gilda-wimse-agent-audit-record-00 was the first revision.¶
Cliff Singletary pointed out that an earlier revision could not record a decision point that was unable to evaluate, and proposed the separate member that leaves decision.reported and the agreement table untouched. Girish Konda reviewed that member against Section 5 of [EVALUATION], proposed adding the policy input to its closed set, and asked for the reference time, the boundary case and the freshness of accepted inputs that evaluation.inputs now carries. Wes Jackson proposed reading stale and never-established inputs from the as-of time and the maximum age. Venkata Uppalapati asked whether a receipt time could be bound to anything a later reader can check, which led to the receipt bracket, and named the late-anchor limit it leaves. Iman Schrock pointed out that the bracket is a consistency check and not proof of receipt, and proposed the case of a digest anchored before delivery. Andreas Ehstand asked whether the human act behind a permit belongs in this format and proposed the member that names its kind. Brian Campbell pointed this work to the AUDIT BoF.¶