Internet-Draft Agent Audit Record October 2026
Gilda Expires 19 April 2027 [Page]
Workgroup:
Network Working Group
Internet-Draft:
draft-gilda-audit-agent-audit-record-00
Published:
Intended Status:
Informational
Expires:
Author:
S. Gilda

An Audit Record Format for AI Agent Authorization Decisions

Abstract

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.

About This Document

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/.

Status of This Memo

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.

▲

Table of Contents

1. Introduction

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:

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.

2. Conventions and Definitions

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.

3. Terminology

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.

Agent, Tool, Service, Resource, LLM:

as [AIMS] uses them.

Agent identifier:

a WIMSE identifier as defined in [WIMSE-ID].

Statement, subject, predicate:

as [IN-TOTO] uses them.

Observer:

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.

Decision point:

the party that evaluated the authorization request, whatever its architecture.

Interval:

the period between the before-state and the after-state that one record covers.

Retaining party:

a party that keeps a record as evidence, whether it produced the record or received it from another party.

Anchor:

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.

Skew tolerance:

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.

Tier:

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.

4. Audit Record Specification

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.

4.1. Canonical form

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.

4.1.1. JSON profile

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).

4.1.2. Duplicate members and nesting depth

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).

4.2. Subject convention

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.

4.3. Timestamps and correlation

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.

4.4. Tamper-evidence

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.

4.5. Mapping to the seven minimum fields

Table 1: The seven minimum fields of Section 11 and the members that carry them
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.

4.6. Verifying a record

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.

4.7. Retained records

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.

4.7.1. The dating check

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.

4.7.2. Anchor validation rules

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.

5. Predicate members

{
  "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"
}

5.1. Record identity and hash algorithm

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.

5.2. Identity, delegation and resource

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.

5.3. Decision, effect and agreement

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).

Table 2: Deriving agreement from the reported decision and the observed effect
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.

5.4. Evaluation

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.

Table 3: The inputs an evaluation entry or unavailableInput names
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.

5.5. Oversight

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.

5.6. Correlation, posture and remediation

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.

5.7. Observation and tier

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).

5.8. A worked reading of the example

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.

6. Security Considerations

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.

6.1. The vantage claim is not self-proving

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).

6.2. A producer that observes only itself

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.

6.3. Withheld rows

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.

6.4. Well-formed disagreement

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.

6.5. A decision that was not evaluated

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.

6.6. Revocation without a bound

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.

6.7. Relationship to other tamper-evidence mechanisms

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.

6.8. Two implementations that both differ from the canonical form

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.

6.9. Receipt time and late anchoring

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.

7. Privacy Considerations

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.

8. IANA Considerations

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.

9. Implementation Status

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.

10. Normative References

[AIMS]
Kasselman, P., Lombardo, J., Rosomakho, Y., Campbell, B., Steele, N., and A. Parecki, "AI Identity Management System", Work in Progress, Internet-Draft, draft-ietf-wimse-aims-00, , <https://datatracker.ietf.org/doc/html/draft-ietf-wimse-aims-00>.
[DSSE]
Secure Systems Lab, "Dead Simple Signing Envelope", , <https://github.com/secure-systems-lab/dsse>.
[IN-TOTO]
in-toto, "in-toto Attestation Framework Specification", , <https://github.com/in-toto/attestation/blob/main/spec/README.md>.
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/rfc/rfc2119>.
[RFC3339]
Klyne, G. and C. Newman, "Date and Time on the Internet: Timestamps", RFC 3339, DOI 10.17487/RFC3339, , <https://www.rfc-editor.org/rfc/rfc3339>.
[RFC7493]
Bray, T., Ed., "The I-JSON Message Format", RFC 7493, DOI 10.17487/RFC7493, , <https://www.rfc-editor.org/rfc/rfc7493>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/rfc/rfc8174>.
[RFC8785]
Rundgren, A., Jordan, B., and S. Erdtman, "JSON Canonicalization Scheme (JCS)", RFC 8785, DOI 10.17487/RFC8785, , <https://www.rfc-editor.org/rfc/rfc8785>.
[WIMSE-ID]
Rosomakho, Y. and J. A. Salowey, "Workload Identifier", Work in Progress, Internet-Draft, draft-ietf-wimse-identifier-03, , <https://datatracker.ietf.org/doc/html/draft-ietf-wimse-identifier-03>.

11. Informative References

[AUDIT-BOFREQ]
Kuehlewind, M., "AUDIT - Agent Use of Delegation and Interaction Traceability", , <https://datatracker.ietf.org/doc/bofreq-kuhlewind-audit-agent-use-of-delegation-and-interaction-traceability/>.
[AUDIT-CHARTER]
AUDIT BoF proponents, "Agent Use of Delegation and Interaction Traceability (AUDIT) Working Group Charter (proposed)", , <https://github.com/mirjak/audit-bof-preparation/blob/main/audit-charter.md>.
[EVALUATION]
Jackson, W., "Verifier-Side Evaluation Semantics for Delegated Authority Chains", Work in Progress, Internet-Draft, draft-jackson-wimse-evaluation-04, , <https://datatracker.ietf.org/doc/html/draft-jackson-wimse-evaluation-04>.
[EVALUATION-STATE]
Konda, G., "Preserving Evaluation State in Agent Protocol Decisions", Work in Progress, Internet-Draft, draft-konda-agentproto-evaluation-state-01, , <https://datatracker.ietf.org/doc/html/draft-konda-agentproto-evaluation-state-01>.
[OVERSIGHT-ACTS]
Ehstand, A., "Terminology for Human Oversight Acts in Automated and Agentic Systems", Work in Progress, Internet-Draft, draft-ehstand-oversight-acts-00, , <https://datatracker.ietf.org/doc/html/draft-ehstand-oversight-acts-00>.
[PQ-AGENT-ID]
Uppalapati, V. K., "Post-Quantum Requirements for Software and AI Agent Identity", Work in Progress, Internet-Draft, draft-uppalapati-wimse-pq-agent-identity-00, , <https://datatracker.ietf.org/doc/html/draft-uppalapati-wimse-pq-agent-identity-00>.
[RFC3161]
Adams, C., Cain, P., Pinkas, D., and R. Zuccherato, "Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP)", RFC 3161, DOI 10.17487/RFC3161, , <https://www.rfc-editor.org/rfc/rfc3161>.
[RFC4998]
Gondrom, T., Brandner, R., and U. Pordesch, "Evidence Record Syntax (ERS)", RFC 4998, DOI 10.17487/RFC4998, , <https://www.rfc-editor.org/rfc/rfc4998>.
[RFC6962]
Laurie, B., Langley, A., and E. Kasper, "Certificate Transparency", RFC 6962, DOI 10.17487/RFC6962, , <https://www.rfc-editor.org/rfc/rfc6962>.
[RFC7942]
Sheffer, Y. and A. Farrel, "Improving Awareness of Running Code: The Implementation Status Section", BCP 205, RFC 7942, DOI 10.17487/RFC7942, , <https://www.rfc-editor.org/rfc/rfc7942>.
[RFC9162]
Laurie, B., Messeri, E., and R. Stradling, "Certificate Transparency Version 2.0", RFC 9162, DOI 10.17487/RFC9162, , <https://www.rfc-editor.org/rfc/rfc9162>.
[VECTORS]
"Agent evidence conformance vectors", n.d., <https://github.com/probityai/agent-evidence-vectors>.
[VOCABULARY]
"Agent evidence vocabulary", n.d., <https://github.com/probityai/agent-evidence-vocabulary>.

Appendix A. Design rationale

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.

A.1. The canonicalization contract

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.

A.2. The decision and the observed effect

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.

A.3. Why the tier carries five clauses

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.

Appendix B. Conformance vectors

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.

Table 4: The conformance corpus, one member for every normative rule this document adds
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

Appendix C. Mapping to the AUDIT problem statement

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.

Table 5: AUDIT problems and the parts of this document that address them
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

Document History

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:

Changes in draft-gilda-wimse-agent-audit-record-02:

Changes in draft-gilda-wimse-agent-audit-record-01:

draft-gilda-wimse-agent-audit-record-00 was the first revision.

Acknowledgments

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.

Author's Address

Sankalp Gilda