Adversarial Execution Evidence — Verifier Failure Codes
THIS DOCUMENT IS NOT THE SPECIFICATION, AND IT IS NOT A RESTATEMENT OF ONE EITHER
adversarial-execution-evidence.mdin this same directory is a local restatement of the upstream authority (spec/predicates/adversarial-execution-evidence.mdin thein-toto/attestationfork, mirrored in theaee-conformancerepository), checked against that authority byscripts/lint_spec_restatement.pyand listed in that script's_DOCUMENTSregistry.This document is a different kind of thing, and it is deliberately NOT in that registry. It documents
packages/verify/src/evidence.ts'sCodeunion — 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 isevidence.tsitself, and the thing that keeps it honest isscripts/lint_failure_codes.py: every code documented here is cross-checked against the liveCodeunion (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
predicatemember at all —parseStatementreturnsnullin any of these cases, andverifyStatementsynthesizesstatement-malformeddirectly rather than running any gate (evidence.ts:2544-2545). - The
predicatemember itself is present but is not a JSON object (evidence.ts:1044). predicate.coverageis present but is not an object (evidence.ts:1062).predicate.attackResultsis present but is not an array (evidence.ts:1068).predicate.observationRecordsis present but is not an array (evidence.ts:1083).predicate.batchRootis present but is not a string (evidence.ts:1088).predicate.observationEnvironmentis present but is not an object (evidence.ts:1102).observationEnvironment.observationVocabularyis present but is not an object (evidence.ts:1140).- An entry of
attackResultsis 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 foractualLayerit additionally triggersmalformed-missing-actual-layerat GATE 0 (the Go reference verifier's "nil-pointer double-append" parity, documented atevidence.ts:1159-1163). observationEnvironment.corpus.manifestis 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
attackResultscarry the sameattackId—gate0CoverageIntegrity,evidence.ts:1418. predicate.attackResultsis entirely absent (rowsPresentis 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-84 — doesNotAssert 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:
observationEnvironmentis absent from the predicate entirely (evidence.ts:1535).observationEnvironmentis present but is missing one ofsubstrate,corpus,catchPolicy, ornetworkPosture(evidence.ts:1538-1539).observationEnvironment.corpusis present but has nomanifestmember at all —gate0Corpus,evidence.ts:1362-1363.
Condition: aee-c-78 — observationEnvironment'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-93 — networkPosture.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-51 — observationVocabulary is required
vocabulary-not-canonical
gate0Vocabulary (evidence.ts:1336-1358) fires this for any of three independent shape violations of
observationVocabulary:
labelsorcaughtis missing from the vocabulary object entirely (evidence.ts:1337-1339).labelsorcaughtis present but not strictly ascending and duplicate-free under UTF-16 code-unit order (evidence.ts:1341-1342).- Some entry of
labelsorcaughtcontains 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-52 — caught 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-103 — corpus.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 ofoutOfScope, and the keys ofroutedElsewhere— 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 underassessedClasses(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, orsubstrate.digest.sha256that 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-57 — runEntropy is required alongside any substrate row
issued-at-missing
predicate.issuedAt is absent (evidence.ts:1579).
Condition: aee-c-85 — issuedAt is required and follows the Timestamp profile
issued-at-malformed
Three sites converge on this code:
issuedAtis present but not a string, caught at parse time (evidence.ts:1093).issuedAtis a string but does not parse under the strict RFC 3339-UTC timestamp profileparseTimestampenforces — an uppercaseT/Zdate-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
gate1WithContextwhen the statement carries substrate rows and the run binding must be computed from a parsedissuedAt(evidence.ts:2388-2390) — this is the identical parse re-run at a second call site, not a second condition.
Condition: aee-c-85 — issuedAt 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-24 — batchRoot 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-31 — batchRoot 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-31 — batchRoot 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
evaluateKind — payload-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-42 — method 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, oraeeMethod— checked inanalyzePayloadbefore any kind-specific work happens (evidence.ts:1823-1828). - An
interception-kind record (one that does carry all three universal members) has noaeePayloadCommitmentmember at all — the initialfailCodevalueevaluateKindassigns theinterceptioncase, 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-2 — result 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:
-
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
ConsumerPolicyvalue (expectedCorpusDigest,expectedSubstrateDigest,allowUnpinnedAnchors) supplied out of band by whoever callsverifyStatement(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. -
They are recorded outside validity, on a field the conformance harness's
expectedblock has no key for.anchorPolicyCodeswrites toReport.policyCodes, neverReport.codes(evidence.ts:2567-2572);verifyStatement'sverdictandcodesare already settled by the time anchor codes are computed.aee-conformance'svectors/gen_manifest.pyderives exactly five kinds of expectation from a vector — verdict, the failure-code set for arejectvector, the recomputed result for anacceptvector, the declared readings for anindeterminatevector, and per-row evidence tiers (gen_manifest.py:6-12) — and none of the five is a consumer-policy admission outcome. -
The vector schema's
expectedblock has noadmittedkey, and this is not an oversight to file a corpus request against. A vector is a statement's bytes plus akind; there is no schema slot inMANIFEST.jsonfor 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: noadmittedkey appears invectors/MANIFEST.json, any vector fixture, orgen_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 theaee-conformancemaintainers, 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.