Skip to content
New kind of attack — caught and fixed before it was publicly known·Read the timeline
Probity Open Predicate Standard
/predicate/v1/adversarial-execution-evidence-failure-codes
View raw .md →

Adversarial Execution Evidence — Verifier Failure Codes

THIS DOCUMENT IS NOT THE SPECIFICATION, AND IT IS NOT A RESTATEMENT OF ONE EITHER

adversarial-execution-evidence.md in this same directory is a local restatement of the upstream authority (spec/predicates/adversarial-execution-evidence.md in the in-toto/attestation fork, mirrored in the aee-conformance repository), checked against that authority by scripts/lint_spec_restatement.py and listed in that script's _DOCUMENTS registry.

This document is a different kind of thing, and it is deliberately NOT in that registry. It documents packages/verify/src/evidence.ts's Code union — the exhaustive set of failure codes this repository's TypeScript reference verifier can emit — grounded in the verifier's own source, not in the upstream specification's prose. The upstream authority does not enumerate an implementation's internal code strings at all (the spec constrains observable behavior, not a particular verifier's diagnostic vocabulary), so there is no authority text for a restatement gate to check this document against. This file's authority is evidence.ts itself, and the thing that keeps it honest is scripts/lint_failure_codes.py: every code documented here is cross-checked against the live Code union (bidirectionally — nothing may exist on only one side) and every citation below is cross-checked against the live conformance corpus, not trusted as prose.

Do not cite this file as normative, and do not treat it as a second implementation of the spec's failure taxonomy. It is a reference glossary over one file in this codebase.

This verifier's Code union (packages/verify/src/evidence.ts:140-220) has 67 members. verifyStatement (evidence.ts:2542) runs a pinned sequence — GATE 0 (statement well-formedness) → GATE 1 (coverage validity, both the per-row pipeline and the statement-level requirements v0.7 adds) → the recompute-equality check → GATE 2 (evidence tier) → the consumer-policy anchor step — and stops at the first gate that emits a non-empty code list. Every code below is grouped the way evidence.ts's own Code union comments already group it (lines 141, 168, 176, 197, 205, 211, 213), because that grouping is the verifier's own structure, not one invented for this document.

63 of the 67 codes are exercised by at least one vector in the aee-conformance conformance corpus. Each is cited below by the condition its forcing vector exercises — an aee-c-N id from that suite's condition registry, naming a requirement of the specification — and the lint script resolves that condition through the corpus manifest to a vector whose expected.codes carries the code, so a citation is verified rather than merely present. 4 codes are structurally exempt from vector coverage; see Exemptions for why, and each of the four entries below repeats the specific reason.

Why a condition and not a vector name. These entries cited vectors by id until 2026-09-02, when the corpus renamed every vector to a content address for the express purpose of stopping identifiers carrying meaning. Sixty-three citations here went dangling at once. Substituting the new addresses would have produced a specification citing sixty-three opaque digests, unreadable to a person and broken again by the next regeneration. A condition survives that change and every change like it: the rename altered every id and every file in the manifest and left the (kind, conditions, expected) content of all 272 vectors identical, vector for vector. It also says more than a filename ever did. bad-729-duplicate-attackid-rows asserted in its own name, checked by nothing, what the vector was for; aee-c-90 is a requirement, and the corpus is what says which vectors force it.

Corpus pin. Every citation below was checked against the conformance corpus this repository vendors at packages/verify/vectors/aee-v06/ — release v0.8.0, source commit 0c4e27ec4f28b03d9688885bb5a00c6e96334d1e, manifest digest eb24532be577ee6a936410e6850482847cecac07147aa3c76d737fc730d4bf2b — which carries 272 vectors: 61 accept, 209 reject, 2 indeterminate. This paragraph is not decoration and it is not a snapshot that rots. scripts/lint_failure_codes.py reads it, holds the vendored copy to it on all three axes, and REFUSES to check a single citation below when they disagree, naming what was expected and what is there. Until 2026-09-02 this document was instead graded against whatever the aee-conformance sibling checkout had most recently committed, and a local rename of every vector in that checkout invalidated roughly forty citations in this published document with no version boundary anywhere in between. A corpus that moves under a specification is a corpus the specification never agreed to; moving this pin is now a deliberate act, taken by somebody who has re-read the citations against the new corpus.

How to read an entry

Each code below gets one subsection: a plain-language statement of the condition that makes the verifier append it to a report's codes array, grounded in the actual call site(s) in evidence.ts (cited as evidence.ts:LINE), followed by a **Condition:** line naming the conformance condition some vector exercises while expecting that code. The aee-c-N id is the checked half of that line: scripts/lint_failure_codes.py resolves it through the pinned manifest and fails when no vector declares it, and when vectors do declare it but none of them expects this code. The clause after the id is this document's own summary of what the condition requires, for a reader who does not want to open the registry; the conformance suite's condition registry is the authority for its exact wording. A code with more than one emission site lists all of them — the appendCode helper (evidence.ts:224) is idempotent (it will not add a code already present), so two call sites for the same code are two independent ways of reaching the same diagnosis, not a double-count.

No RFC 2119 language appears below. This is a description of what the code does, not a new set of obligations.

GATE 0 — statement well-formedness

GATE 0 (evidence.ts:1520, function gate0) is a pure function of the carried bytes: no signatures, no consumer policy. It runs first; any non-empty result here means the statement never reaches GATE 1.

statement-malformed

The catch-all code for a structural shape violation: the statement or a member inside it is not the JSON shape the parser expects. It has many independent emission sites, all through appendCode(codes, "statement-malformed"):

  • The whole statement fails to parse as JSON, carries a duplicate member anywhere (at any depth, not only inside an observation-record payload), exceeds the 128-level nesting bound, contains an ill-formed string scalar, or has no predicate member at all — parseStatement returns null in any of these cases, and verifyStatement synthesizes statement-malformed directly rather than running any gate (evidence.ts:2544-2545).
  • The predicate member itself is present but is not a JSON object (evidence.ts:1044).
  • predicate.coverage is present but is not an object (evidence.ts:1062).
  • predicate.attackResults is present but is not an array (evidence.ts:1068).
  • predicate.observationRecords is present but is not an array (evidence.ts:1083).
  • predicate.batchRoot is present but is not a string (evidence.ts:1088).
  • predicate.observationEnvironment is present but is not an object (evidence.ts:1102).
  • observationEnvironment.observationVocabulary is present but is not an object (evidence.ts:1140).
  • An entry of attackResults is present but is not an object (evidence.ts:1156).
  • A row member that must be a string (attackId, containmentObserved, basis, method, attribution, actualLayer) is present with the wrong JSON type (evidence.ts:1168) — this also counts the member as absent, so for actualLayer it additionally triggers malformed-missing-actual-layer at GATE 0 (the Go reference verifier's "nil-pointer double-append" parity, documented at evidence.ts:1159-1163).
  • observationEnvironment.corpus.manifest is present but its RFC 8785 canonicalization throws (a lone UTF-16 surrogate or an unsafe-integer number inside it) — gate0Corpus, evidence.ts:1373-1375.
  • Two rows in attackResults carry the same attackIdgate0CoverageIntegrity, evidence.ts:1418.
  • predicate.attackResults is entirely absent (rowsPresent is false) — evidence.ts:1554.

Condition: aee-c-90 — no two attackResults rows share an attackId

statement-type-unsupported

The statement's top-level _type does not equal "https://in-toto.io/Statement/v1" (STATEMENT_TYPE), checked at evidence.ts:1524.

Condition: aee-c-77 — the statement _type and predicateType URIs

predicate-type-unsupported

The statement's predicateType does not equal this verifier's implemented AEE predicate type URI (PREDICATE_TYPE, https://in-toto.io/attestation/adversarial-execution-evidence/v0.7), checked at evidence.ts:1525.

Condition: aee-c-77 — the statement _type and predicateType URIs

member-spelling

The predicate carries the member name does_not_assert — the retired snake_case spelling of doesNotAssert that a past spec version renamed with no accepted alias. evidence.ts:104 names the constant (REJECTED_SNAKE_SPELLING); evidence.ts:1529 fires the code whenever has(p.raw, "does_not_assert") is true, regardless of what the member's value is.

Condition: aee-c-84doesNotAssert has a single canonical spelling

result-vocabulary

predicate.result is either absent, present with the wrong JSON type (caught earlier at parse time, evidence.ts:1050), or present as a string that is not one of the four closed result tokens (pass / pass_indirect / degraded / fail) — checked again at GATE 0 proper via isResultToken (evidence.ts:1531).

Condition: aee-c-1 — the closed lowercase result vocabulary

environment-incomplete

Three distinct absences collapse to this one code:

  • observationEnvironment is absent from the predicate entirely (evidence.ts:1535).
  • observationEnvironment is present but is missing one of substrate, corpus, catchPolicy, or networkPosture (evidence.ts:1538-1539).
  • observationEnvironment.corpus is present but has no manifest member at all — gate0Corpus, evidence.ts:1362-1363.

Condition: aee-c-78observationEnvironment's required members

posture-vocabulary

observationEnvironment.networkPosture is present, but its posture string is not a member of the closed EGRESS_POSTURES set imported from ./bindings (evidence.ts:1546-1547). This check only runs when networkPosture itself is present — an absent networkPosture reports only environment-incomplete, never a second code for the same absence.

Condition: aee-c-93networkPosture.posture is a registered value

vocabulary-missing

observationEnvironment.observationVocabulary is absent — either because the whole environment is absent (evidence.ts:1536) or because the environment is present but carries no observationVocabulary sub-object (evidence.ts:1540).

Condition: aee-c-51observationVocabulary is required

vocabulary-not-canonical

gate0Vocabulary (evidence.ts:1336-1358) fires this for any of three independent shape violations of observationVocabulary:

  • labels or caught is missing from the vocabulary object entirely (evidence.ts:1337-1339).
  • labels or caught is present but not strictly ascending and duplicate-free under UTF-16 code-unit order (evidence.ts:1341-1342).
  • Some entry of labels or caught contains a UTF-16 surrogate code unit — the normative BMP-only string profile (evidence.ts:1346-1347).

Condition: aee-c-53 — the vocabulary arrays are sorted ascending and duplicate-free

vocabulary-caught-not-subset

observationVocabulary.caught contains an entry that does not appear in observationVocabulary.labels (evidence.ts:1348-1354).

Condition: aee-c-52caught is a subset of labels

vocabulary-digest-mismatch

observationVocabulary.digest.sha256 does not equal the SHA-256 of the canonical {"caught":[...],"labels":[...]} reconstruction of the carried arrays (evidence.ts:1355-1357).

Condition: aee-c-54 — the vocabulary digest is JCS over {caught, labels}

corpus-digest-mismatch

observationEnvironment.corpus.digest.sha256 does not equal the SHA-256 of the RFC 8785 canonical bytes of corpus.manifest (gate0Corpus, evidence.ts:1366-1369).

Condition: aee-c-79 — the corpus digest re-derives from the embedded manifest

manifest-duplicate-attack

The same attack identifier appears under more than one class in corpus.manifest.classes (gate0Corpus, evidence.ts:1377-1384).

Condition: aee-c-80 — an attackId falls under at most one manifest class

corpus-manifest-no-attacks

corpus.manifest.classes declares zero attack identifiers across every one of its classes — every class array is empty, or the classes object itself is empty (gate0ManifestDeclaresAttacks, evidence.ts:1450-1456). This is deliberately a well-formedness fault rather than a scoring outcome (see the doc comment at evidence.ts:1425-1449 for why): with zero declared attacks, the coverage and substrate-row machinery pass vacuously and a substrate-free, no-signature statement about an arbitrary subject would otherwise be mintable.

Condition: aee-c-92 — the corpus manifest declares at least one attack identifier

manifest-expected-payloads-malformed

corpus.manifest.expectedPayloads is present but malformed — either its top-level shape is not a map of string arrays (expectedPayloadsShapeOk false), or expectedPayloadsOk fails because some key names an attack ID the same manifest's classes never declares, or some commitment array is empty, unsorted, duplicate-bearing, or not entirely lowercase 64-hex (gate0Corpus, evidence.ts:1386-1389, delegating to expectedPayloadsOk, evidence.ts:1658-1665).

Condition: aee-c-103corpus.manifest.expectedPayloads is well formed

coverage-missing

predicate.coverage is absent entirely (evidence.ts:1553).

Condition: aee-c-83 — the coverage member is required

coverage-incomplete

gate0CoverageIntegrity (evidence.ts:1393-1423) fires this for either of two independent violations:

  • The three coverage partitions — assessedClasses, the keys of outOfScope, and the keys of routedElsewhere — do not exhaustively and disjointly cover the manifest's declared classes: some class is missing from all three, appears in more than one, or an entry names a class the manifest does not declare (evidence.ts:1406-1409).
  • The set of attackIds carried across every row does not equal the set of attack identifiers implied by the classes actually listed under assessedClasses (evidence.ts:1411-1412, 1422).

Condition: aee-c-82 — coverage equals the manifest exactly, at attack granularity

row-attack-unknown

A row's attackId does not appear anywhere in the corpus manifest's declared classes (gate0CoverageIntegrity, evidence.ts:1420).

Condition: aee-c-81 — a row's attackId appears in the manifest

malformed-missing-actual-layer

A row's actualLayer member is absent — this also covers the case where it was present with the wrong JSON type, which the parser already counted as absent (evidence.ts:1566-1568, and the parity note at 1159-1163).

Condition: aee-c-47 — a missing actualLayer is a malformed statement rather than a fail

clean-row-layer-not-none

The vocabulary is otherwise well-formed, the row's containmentObserved is a clean (non-caught) vocabulary label, and the row's actualLayer is not the literal string "none" (evidence.ts:1570-1571).

Condition: aee-c-48 — a clean row's actualLayer is the literal none

subject-cardinality

The statement's subject array does not contain exactly one entry — checked unconditionally, regardless of row basis (evidence.ts:1559).

Condition: aee-c-58 — exactly one subject, on a statement of any basis

subject-sha256-missing

The statement carries at least one basis: substrate row, and subject[0].digest.sha256 is not a string (gate0SubstrateBindingInputs, evidence.ts:1502-1504). This check, and every other check in gate0SubstrateBindingInputs, only runs when hasSubstrateRows(p) is true (evidence.ts:1577).

Condition: aee-c-59 — binding digest inputs are lowercase 64-hex sha256

digest-not-canonical

Scoped the same way as subject-sha256-missing (statement carries a substrate row): a hex digest value that is present is not exactly 64 lowercase hex characters. Three call sites inside gate0SubstrateBindingInputs:

  • observationEnvironment.runEntropy.digest.sha256 (evidence.ts:1490).
  • subject[0].digest.sha256, when it is present as a string but fails the lowercase-64-hex shape check (evidence.ts:1505).
  • Any of catchPolicy.digest.sha256, corpus.digest.sha256, networkPosture.digest.sha256, or substrate.digest.sha256 that is present but not lowercase 64-hex (evidence.ts:1507-1515).

Condition: aee-c-59 — binding digest inputs are lowercase 64-hex sha256

run-entropy-missing

The statement carries at least one basis: substrate row, and observationEnvironment.runEntropy is absent (gate0SubstrateBindingInputs, evidence.ts:1489).

Condition: aee-c-57runEntropy is required alongside any substrate row

issued-at-missing

predicate.issuedAt is absent (evidence.ts:1579).

Condition: aee-c-85issuedAt is required and follows the Timestamp profile

issued-at-malformed

Three sites converge on this code:

  • issuedAt is present but not a string, caught at parse time (evidence.ts:1093).
  • issuedAt is a string but does not parse under the strict RFC 3339-UTC timestamp profile parseTimestamp enforces — an uppercase T/Z date-time separator and zone designator only, any numeric offset accepted syntactically but only a literal zero offset passing the value check (evidence.ts:1479-1485, invoked at GATE 0 proper: evidence.ts:1580).
  • The same re-derivation fails again inside gate1WithContext when the statement carries substrate rows and the run binding must be computed from a parsed issuedAt (evidence.ts:2388-2390) — this is the identical parse re-run at a second call site, not a second condition.

Condition: aee-c-85issuedAt is required and follows the Timestamp profile

GATE 1 — statement-level observation-record codes

These run once over the whole observationRecords array, before any per-row work, inside checkRecordsStatementLevel (evidence.ts:1709-1756) and gate1WithContext (evidence.ts:2357-2402).

batch-root-missing

observationRecords is present with at least one entry, every record decodes successfully, and predicate.batchRoot is absent (evidence.ts:1752).

Condition: aee-c-24batchRoot is present whenever records are

batch-root-mismatch

observationRecords is present with at least one entry, every record decodes successfully, and the carried batchRoot does not equal the recomputed RFC 6962 Merkle root over the records' leaf hashes (evidence.ts:1753).

Condition: aee-c-25 — RFC 6962 domain-separated hashing

batch-root-orphaned

observationRecords is absent, or present but empty, while predicate.batchRoot is present anyway (evidence.ts:1713-1714).

Condition: aee-c-31batchRoot is omitted exactly when records are absent

duplicate-record

Two or more carried observation records fold to the same leaf hash — the same canonical identity (hasDuplicateLeaf, invoked at evidence.ts:1750).

Condition: aee-c-29 — byte-identical duplicate records are invalid

records-absent

The statement carries at least one basis: substrate row, and observationRecords is entirely absent (gate1WithContext, evidence.ts:2382-2383).

Condition: aee-c-31batchRoot is omitted exactly when records are absent

record-undecodable

A record's payload string fails strict base64 decode (strictBase64Decode returning null), checked once over the whole record set (evidence.ts:1737-1741) and again wherever a specific record already flagged with decodeErr is analyzed for per-row purposes (analyzePayload, evidence.ts:1787-1789) — the second site is the same underlying decode failure surfacing at a second read, not an independent condition.

Condition: aee-c-19 — a covering record's media type ends in +json

record-signatures-empty

At least one observation record's signatures member carries zero entries — an absent signatures member, an empty array, and a non-array value all collapse to the same "zero entries" fault (anyRecordSignaturesEmpty, evidence.ts:1781-1783, invoked at evidence.ts:1732). This is checked statement-wide, before the per-record decode loop, so its ordering against record-undecodable is deterministic regardless of which record in the array carries which fault (rationale at evidence.ts:1722-1731).

Condition: aee-c-91 — every observation record's signatures carries at least one entry

GATE 1 — per-row coverage-validity codes

These run per substrate row, inside checkSubstrateRow (evidence.ts:1954-2054), which resolves the row's observationRefs to specific records and evaluates each resolved record's payload (analyzePayload, evidence.ts:1785-1832) and its aeeKind-specific constraints (evaluateKind, evidence.ts:1841-1949).

A structural note that applies to several codes below. Codes sourced from analyzePayload and evaluateKindpayload-not-ijson, payload-not-canonical, payload-media-type, payload-missing-reserved, run-binding-mismatch, payload-commitment-malformed, arming-covers-nothing, and sealed-covers-nothing — have a second possible emission path beyond a row resolving a bad record: carriedRecordsCover (evidence.ts:2240-2258, called from gate1CommitmentsSubstrate at evidence.ts:2318) re-runs the identical analyzePayload/evaluateKind pipeline over every carried record whose aeeKind is one of the four defined covering kinds (isCoveringKind, evidence.ts:2196-2198), whether or not any row's observationRefs resolves to it. This closes the gap where a producer carries a defective record inside a signed batchRoot but points every row's refs at a healthy twin instead — see the rationale at evidence.ts:2205-2225. examination-covers-nothing is also reachable this way. The three registered-non-covering codes (record-kind-unknown-covers-nothing, moat-drop-covers-nothing, uncommitted-observation-covers-nothing) are NOT reachable through carriedRecordsCover, because isCoveringKind excludes their kinds by construction — those three fire only through a row's own resolution path.

refs-empty

A substrate row's observationRefs member is either absent (evidence.ts:1974) or present as an empty array (evidence.ts:1976).

Condition: aee-c-10 — a substrate row carries a non-empty observationRefs

ref-malformed

A substrate row's observationRefs is present but is not an array of non-negative integers (row.refsErr, checked at evidence.ts:1975).

Condition: aee-c-11 — every observationRefs entry is an in-range integer index

ref-out-of-range

Two independent call sites, one condition: a row's observationRefs entry indexes past the end of observationRecords. Checked per substrate row inside checkSubstrateRow (evidence.ts:1980-1983), and again statement-wide over every row of any basis — including rows nothing else in GATE 1 reads — via anyObservationRefOutOfRange (evidence.ts:2063-2069, invoked at evidence.ts:2369), so an artifact-only statement with a dangling ref is still rejected even though it never enters the substrate-row pipeline.

Condition: aee-c-11 — every observationRefs entry is an in-range integer index

fail-closed-substrate-row

A substrate row's three closed vocabularies are jointly checked, and the row fails if any is out-of-vocabulary: containmentObserved is neither a caught nor a clean label, method is neither intercepted nor reconstructed, or attribution is neither pinned nor paired (evidence.ts:1964-1972).

Condition: aee-c-42method is required and closed to intercepted/reconstructed

payload-not-ijson

A resolved record's decoded payload bytes either fail to parse as strict I-JSON (parseIJSON throws, evidence.ts:1791-1796) or fail the raw-byte string-scalar well-formedness scan (checkStringScalars, evidence.ts:1803-1806).

Condition: aee-c-18 — a covering payload is valid I-JSON

payload-not-canonical

A resolved record's decoded payload has one of four independent problems, all inside analyzePayload:

  • The parsed I-JSON value is not an object at all (evidence.ts:1807-1810).
  • Re-emitting the parsed value in RFC 8785 canonical form does not byte-match the original payload bytes (evidence.ts:1812-1813).
  • Canonical re-emission throws — for example, a lone UTF-16 surrogate inside a string value (evidence.ts:1814-1816).
  • Some object member name anywhere in the payload contains a UTF-16 surrogate code unit, the BMP-only profile applied to member names rather than values (evidence.ts:1817-1820).

Condition: aee-c-17 — a covering payload is canonical RFC 8785

payload-media-type

A resolved record's payloadType does not end with +json (evidence.ts:1821).

Condition: aee-c-19 — a covering record's media type ends in +json

payload-missing-reserved

Two distinct absences share this code:

  • A resolved record's payload lacks one of the three universally-reserved members — aeeRunBinding, aeeKind, or aeeMethod — checked in analyzePayload before any kind-specific work happens (evidence.ts:1823-1828).
  • An interception-kind record (one that does carry all three universal members) has no aeePayloadCommitment member at all — the initial failCode value evaluateKind assigns the interception case, which stands unless overridden by the commitment being present-but-malformed (evidence.ts:1848, 1857, 1867).

Condition: aee-c-20 — a covering payload carries the reserved aee members

run-binding-mismatch

A resolved record's aeeRunBinding value does not equal the run binding this verifier derives for the statement from its digest-pinned environment members (deriveRunBinding / deriveStatementBinding, evidence.ts:1211-1265; compared at evidence.ts:1830).

Condition: aee-c-62 — the run binding is anti-splice

method-cap-exceeded

A row declares method: intercepted, but among the records it validly resolves, the weakest signed method present is reconstructed — the row's claimed method is capped downward by what its covering evidence actually establishes (evidence.ts:2048-2050).

Condition: aee-c-23 — a row's method is capped by the weakest signed aeeMethod

caught-row-uncovered

A row whose containmentObserved is a caught label (and whose method is not reconstructed) resolves no record that validly evaluates as aeeKind: interception (evidence.ts:2023, the generic fallback when neither a specific record failure nor a covers-nothing kind explains the gap).

Condition: aee-c-12 — a caught intercepted row resolves an interception record

reconstructed-row-uncovered

A row declares method: reconstructed but resolves no record that validly evaluates as aeeKind: examination (evidence.ts:2022).

Condition: aee-c-13 — a reconstructed row resolves an examination record

clean-row-uncovered

A clean-label row resolves no record that validly evaluates as aeeKind: arming, or none that validly evaluates as aeeKind: sealed — both are required (evidence.ts:2024-2027), and the code is shared between the two requirements.

Condition: aee-c-14 — a clean intercepted row resolves both arming and a covering seal

arming-covers-nothing

A record's aeeKind is arming, but it fails one of the kind's constraints (evidence.ts:1870-1902): an explicit aeeBindingVersion this verifier does not implement; a missing armedAt or aeePostureDigest; a method other than intercepted; an armedAt that fails the timestamp profile or falls after issuedAt; a posture digest that does not equal the statement's pinned posture; malformed run-chaining syntax (aeeRunSeq/aeePrevRunBinding/aeeChainScope); or a missing or malformed aeeAssessedAttacks array. See the structural note above this section for the carried-but-unreferenced path this code also reaches through.

Condition: aee-c-63 — the arming record's kind constraints

sealed-covers-nothing

A record's aeeKind is sealed, but it fails one of the kind's constraints (evidence.ts:1904-1929): aeeStillArmed missing or false; a missing aeeDropCount; a missing aeePostureDigest; a method other than intercepted; a non-zero drop count with no declared bound or one that exceeds it; a posture digest that does not match the pinned posture or (when arming postures were collected) one of them; or a missing or malformed aeeObservedSet/aeeObservedAttacks. See the structural note above this section for the carried-but-unreferenced path this code also reaches through.

Condition: aee-c-65 — the sealed record's covering conditions

examination-covers-nothing

A record's aeeKind is examination, but its method is not reconstructed (evidence.ts:1931-1933). See the structural note above this section for the carried-but-unreferenced path this code also reaches through.

Condition: aee-c-66 — an examination record's signed aeeMethod is reconstructed

record-kind-unknown-covers-nothing

A record's aeeKind string is not one of the four defined covering kinds (interception/arming/sealed/examination) and not one of the two registered non-covering kinds — it falls to evaluateKind's default arm (evidence.ts:1945-1946). Reachable only through a row's own resolution path, never through carriedRecordsCover (see the structural note above).

Condition: aee-c-71 — a record of unknown aeeKind covers nothing

moat-drop-covers-nothing

A record's aeeKind is explicitly moat-drop — one of the two kinds the specification registers as covering nothing whatever its payload otherwise says (evidence.ts:1935-1940). No member on such a record is consulted; there is no state in which it could change the outcome. Reachable only through a row's own resolution path (see the structural note above).

Condition: aee-c-106 — a moat-drop record covers nothing in every state

uncommitted-observation-covers-nothing

A record's aeeKind is explicitly uncommitted-observation — the second registered non-covering kind (evidence.ts:1942-1943). Reachable only through a row's own resolution path (see the structural note above).

Condition: aee-c-107 — an uncommitted-observation record covers nothing in every state

payload-commitment-malformed

An interception-kind record carries an aeePayloadCommitment member, but its array is not well-formed: not sorted ascending and duplicate-free, empty, or containing an entry that is not lowercase 64-hex (commitmentArrayOk, invoked at evidence.ts:1858-1866). This is distinct from payload-missing-reserved's interception case: that fires when the member is absent; this fires when it is present and wrong.

Condition: aee-c-104 — an interception record's aeePayloadCommitment is well formed

GATE 1 — the statement-level requirements v0.7 adds

These hold over the whole statement, or over every row regardless of basis, rather than only over basis: substrate rows — the per-row gate above skips any row that is not substrate, so a rule belonging here would silently acquire that scope if it were checked there instead. Three run on every statement, in gate1CommitmentsAnyBasis (evidence.ts:2131-2164); three read the derived run binding and so only run when the statement has substrate rows, in gate1CommitmentsSubstrate (evidence.ts:2263-2355).

clean-row-contradicted

Some row's containmentObserved is a clean vocabulary label, yet one of the records its observationRefs resolves to has aeeKind: interception (evidence.ts:2140-2146). A clean row is a producer's assertion that nothing was intercepted for that attack; pointing it at an interception record contradicts that assertion regardless of the row's declared basis.

Condition: aee-c-94 — a clean row resolves no index to an interception record

interception-record-orphaned

Some carried record's aeeKind is interception, but no row whose containmentObserved is a caught label resolves an observationRefs index to it (evidence.ts:2148-2160). The converse of a caught row being uncovered: this is an interception record nothing claims.

Condition: aee-c-95 — every carried interception record is resolved by some caught row

sealed-record-absent

The statement carries at least one basis: substrate row, and no carried record both passes every byte-level payload check and validly evaluates as aeeKind: sealed under the derived run binding — checked unconditionally, whether or not any row resolves an index to it (evidence.ts:2293-2312).

Condition: aee-c-96 — a statement carrying a substrate row carries a satisfying sealed record

observed-set-mismatch

A sealed record bound to this run (aeeRunBinding equals the derived binding) carries an aeeObservedSet digest that does not equal the recomputed digest over the leaf hashes of every interception and examination record (observedSetDigest, compared at evidence.ts:2279-2290).

Condition: aee-c-97 — a seal's aeeObservedSet equals the value recomputed over the carried records

observed-attack-uncaught

A sealed record bound to this run names an attack identifier in aeeObservedAttacks for which no row exists whose containmentObserved is a caught label — the rule reads in one direction only: a seal omitting an attack obliges nothing (evidence.ts:2320-2333).

Condition: aee-c-98 — every attack a seal names has a row whose containmentObserved is in the caught set

assessed-set-exceeds-declaration

An arming record bound to this run declares an aeeAssessedAttacks set that is not a superset of the attack identifiers implied by the statement's own assessedClasses coverage — a subset check, not equality, so a run that lost coverage mid-way can still disclose the loss honestly (evidence.ts:2336-2353).

Condition: aee-c-99 — the carried assessedClasses are a subset of the arming record's aeeAssessedAttacks

GATE 1 — the attribution binding rule

Three parts of one rule, written in attributionBindings (evidence.ts:2171-2188) in the order the specification states them: existence, then the manifest's expectation, then the match. The existence part is checked first because the other two are vacuously true over an empty set otherwise — a producer that deletes the interception records would keep the stronger pinned label unless something asks whether any remain.

attribution-pinned-recordless

A row declares attribution: pinned, but the records its observationRefs resolves to include zero that evaluate to aeeKind: interception (evidence.ts:2175-2176).

Condition: aee-c-100 — a row declaring attribution: pinned resolves at least one interception record

attribution-unpinnable

A row declares attribution: pinned and resolves at least one interception record, but corpus.manifest.expectedPayloads carries no entry (or an empty one) for that row's attackId (evidence.ts:2177-2178).

Condition: aee-c-101 — a pinned row names an attack the manifest carries an expectedPayloads entry for

attribution-pin-unmatched

A row declares attribution: pinned, resolves at least one interception record, the manifest has a non-empty expectation for its attackId, but none of the resolved interception records' aeePayloadCommitment values intersects that expectation (evidence.ts:2179-2184).

Condition: aee-c-102 — every interception a pinned row resolves matches that attack's expectedPayloads

Recompute-equality gate

Runs after GATE 1 passes, comparing the carried result against the pure recompute.

result-recompute-mismatch

The carried predicate.result does not equal recompute(s.predicate) — the deterministic minimum, under fail < degraded < pass_indirect < pass, of three independent conditions computed purely from the rows, the on-wire vocabulary, and the coverage maps (recompute, evidence.ts:2420-2447; compared at evidence.ts:2554-2555).

Condition: aee-c-2result equals the value recomputed from the rows

Consumer-policy step — anchor codes

The four codes in this section are checked by anchorPolicyCodes (evidence.ts:2508-2520), which runs after GATE 2 (evidence tier) and writes its findings to Report.policyCodes — a field entirely separate from Report.codes. None of the four can ever appear in Report.codes, none can ever be primaryCode, and none ever changes verdict: a statement carrying one of these codes is still valid evidence, about a corpus or substrate context the consumer never pinned or pinned to the wrong value. It feeds only Report.admitted, the one consumer-facing admission result (spec ## Verification).

corpus-anchor-mismatch

The consumer's ConsumerPolicy.expectedCorpusDigest is set, and it does not equal the statement's carried observationEnvironment.corpus.digest.sha256 (evidence.ts:2511-2513).

Condition: none — structurally exempt. See Exemptions.

substrate-anchor-mismatch

The consumer's ConsumerPolicy.expectedSubstrateDigest is set, and it does not equal the statement's carried observationEnvironment.substrate.digest.sha256 (evidence.ts:2515-2517).

Condition: none — structurally exempt. See Exemptions.

corpus-anchor-unpinned

The consumer supplied no expectedCorpusDigest at all, and did not declare allowUnpinnedAnchors: true (evidence.ts:2514). Pinning both anchors is a spec MUST, so silence is treated as a denial rather than a vacuous pass — see the rationale at evidence.ts:2489-2501.

Condition: none — structurally exempt. See Exemptions.

substrate-anchor-unpinned

The consumer supplied no expectedSubstrateDigest at all, and did not declare allowUnpinnedAnchors: true (evidence.ts:2518).

Condition: none — structurally exempt. See Exemptions.

Exemptions

corpus-anchor-mismatch, corpus-anchor-unpinned, substrate-anchor-mismatch, and substrate-anchor-unpinned are the only four codes in the Code union with zero conformance-vector coverage, and that absence is structural rather than a gap to close. Three reasons, all necessary:

  1. They are consumer-policy-dependent, not statement-dependent. Every other code in this document is a pure function of the bytes a producer signed. These four are a function of a ConsumerPolicy value (expectedCorpusDigest, expectedSubstrateDigest, allowUnpinnedAnchors) supplied out of band by whoever calls verifyStatement (evidence.ts:2542) — the identical statement bytes produce all four possible outcomes (no policy code, -mismatch, -unpinned, or a match) depending on nothing the statement itself carries.

  2. They are recorded outside validity, on a field the conformance harness's expected block has no key for. anchorPolicyCodes writes to Report.policyCodes, never Report.codes (evidence.ts:2567-2572); verifyStatement's verdict and codes are already settled by the time anchor codes are computed. aee-conformance's vectors/gen_manifest.py derives exactly five kinds of expectation from a vector — verdict, the failure-code set for a reject vector, the recomputed result for an accept vector, the declared readings for an indeterminate vector, and per-row evidence tiers (gen_manifest.py:6-12) — and none of the five is a consumer-policy admission outcome.

  3. The vector schema's expected block has no admitted key, and this is not an oversight to file a corpus request against. A vector is a statement's bytes plus a kind; there is no schema slot in MANIFEST.json for a second input (a policy object) that a single vector would need to be paired with to make one of these four codes reproducible. Grepped confirmed empty on 2026-08-11: no admitted key appears in vectors/MANIFEST.json, any vector fixture, or gen_manifest.py's output shape. Citing a forcing condition for one of these four would require inventing a policy-input schema extension the corpus does not have, which is a conformance-suite design question for the aee-conformance maintainers, not a documentation gap in this file.

scripts/lint_failure_codes.py hardcodes this four-code exemption list independently of this prose section, and separately recomputes — straight from vectors/MANIFEST.json, never from what this document claims — the full set of codes with zero real vector coverage. If that recomputed set is ever not exactly these four (a fifth code loses its only vector, or one of these four unexpectedly gains one), the lint fails rather than silently agreeing with whichever of this prose or the corpus happens to be stale.

Get access

Run your agents through Probity.

A few details about your setup and we'll get you gating agents fast.