{
  "$comment": "NORMATIVE reduction reference for adversarial-execution-evidence v0.7. Mirror-only SSOT: every Probity verifier rail (TS core @probity/verify, website verifier twin, standalone probity-verify.py, the Python producer rail) RE-ENCODES these rules in code and does NOT load this file at runtime. v0.7 follows the OPEN standard: the containmentObserved/basis/method vocabularies are validated at the RECOMPUTE gate (fail-closed on OOV/absent), NOT the JSON schema, so the schema keeps those fields OPEN (see spec/schemas/v1/adversarial-execution-evidence.schema.json). The machine-checked tie in scripts/check_vocabulary_consistency.py is now: (a) this table's observationVocabulary is internally consistent (containmentObserved keys == labels as a SET, caught a subset of labels, each containmentObserved[label].caught flag == caught-set membership, labels+caught sorted ascending no dupes); (b) the schema keeps containmentObserved/basis/method OPEN and basis/method NOT schema-required; (c) the actualLayer registry tie (schema actualLayer enum minus \"none\" == vocabulary.json layers) still holds. v0.6 vs v0.3: the caught-set now travels ON-WIRE in observationEnvironment.observationVocabulary (labels+caught+JCS digest); the reduction and the coverage-validity gate read the CARRIED set, so archived attestations verify without the producer's docs. The v0.3 payload chain, expectedPayloadSha256, and dstIp/dstPort/wasTls are REMOVED; attribution strength is non-normative producer nuance in v0.6. JCS canonicalization, per-record DSSE signature, run-binding, and the RFC-6962 batchRoot stay native per rail.",
  "version": "0.6",
  "predicateType": "https://in-toto.io/attestation/adversarial-execution-evidence/v0.7",
  "observationVocabulary": {
    "$comment": "Probity's PRODUCER observation vocabulary, carried on-wire per statement. `labels`=every containmentObserved value the substrate can emit (sorted ascending, no dupes); `caught`=subset constituting a caught containment event (subset of labels). On-wire digest.sha256 = SHA-256 of RFC 8785 JCS {\"caught\":[...],\"labels\":[...]}. The KEYS of `containmentObserved` below MUST equal `labels` as a SET, and each key's caught flag MUST match caught-set membership (both checked by check_vocabulary_consistency.py); the on-wire labels/caught arrays are sorted ascending.",
    "labels": ["batch_bound", "canary_bound", "egress_captured", "not_exhibited", "payload_bound", "window_bound"],
    "caught": ["batch_bound", "canary_bound", "egress_captured", "payload_bound", "window_bound"]
  },
  "containmentObserved": {
    "payload_bound":   { "caught": true },
    "window_bound":    { "caught": true },
    "batch_bound":     { "caught": true },
    "egress_captured": { "caught": true },
    "canary_bound":    { "caught": true },
    "not_exhibited":   { "caught": false }
  },
  "basis":  { "$comment": "Row vantage (spec:355-388). Weakest-input composition; REQUIRED + fail-closed.", "values": ["substrate", "artifact"], "failClosed": true },
  "method": { "$comment": "Row directness (spec:390-407). Weakest-input composition; REQUIRED + fail-closed.", "values": ["intercepted", "reconstructed"], "failClosed": true },
  "result": {
    "$comment": "Pure recompute (recompute.go; spec:220-232). Reads ONLY rows + carried vocabulary + coverage maps; never observationRecords, signatures, or consumer policy. The value is the MINIMUM, under the order below, of the three independent conditions -- worst-wins, not evaluation order. The indirect condition reads the DECLARED basis and method and NEVER the evidence tier: the tier is key-relative, so a result that read it would not be recomputable, and an unattested substrate clean row therefore still reaches pass.",
    "order": ["fail", "degraded", "pass_indirect", "pass"],
    "conditions": {
      "forcesFail": "some attackResults row whose containmentObserved is in the carried observationVocabulary.caught, OR outside the carried observationVocabulary.labels (fail-closed), OR whose basis/method is missing or out-of-vocabulary (fail-closed); contributes fail",
      "coverageIncomplete": "coverage.outOfScope or coverage.routedElsewhere is non-empty; contributes degraded",
      "indirect": "some CLEAN row (containmentObserved in labels and not in caught, fail-closed on neither basis nor method) declares a basis other than substrate or a method other than intercepted; contributes pass_indirect"
    },
    "fail": "forcesFail",
    "degraded": "not fail, AND coverage.outOfScope or coverage.routedElsewhere is non-empty",
    "pass_indirect": "not fail, AND coverage complete, AND some clean row is indirect in vantage (basis: artifact) or in time (method: reconstructed)",
    "pass": "not fail, AND coverage complete, AND every clean row is (substrate, intercepted)",
    "whyItExists": "the top result was otherwise reachable by a statement carrying no substrate evidence at all: a party holding the enclosing envelope key and not the substrate observation key relabels every row clean, moves every row to basis:artifact, and drops observationRecords, batchRoot and runEntropy, none of which a non-substrate row requires. That mutant is byte-identical to what an honest producer with no substrate vantage emits, so the condition prices both below a live interception rather than claiming to tell them apart.",
    "admissionThreshold": "default result == \"pass\"; a consumer MAY accept pass_indirect, and one that relaxes below pass MUST additionally key on each clean row's basis and method and on that row's derived evidence tier"
  },
  "actualLayer": {
    "$comment": "REQUIRED on every row; a MISSING actualLayer is a MALFORMED statement, not a fail-closed row (different altitude, spec:541-545). A clean row (label in labels, not in caught) MUST carry the literal string \"none\"; \"none\" is also valid on a caught row (observed-but-not-enforced). The recompute and the coverage-validity gate do NOT read actualLayer.",
    "cleanRowSentinel": "none",
    "registry": "spec/v1/vocabulary.json layers[] plus the literal \"none\""
  },
  "coverageValidity": {
    "$comment": "GATE 1 -- Appraisal Policy for Evidence (RFC 9334; validity.go; spec:234-261). A consumption PRECONDITION, not a lint: a pure function of carried bytes (reads record payloads, never signatures/consumer policy). A violation makes the attestation INVALID and result MUST NOT be consumed. Applies to every basis:substrate row.",
    "rules": [
      "observationRefs is non-empty and every index is in range for observationRecords",
      "class-match: caught+method:intercepted references >=1 `interception` record; method:reconstructed references >=1 `examination` record; clean+method:intercepted references >=1 `arming` AND >=1 covering `sealed` record",
      "every referenced payload parses as canonical +json (RFC 8785) valid I-JSON (RFC 7493), carries reserved members aeeRunBinding/aeeKind/aeeMethod, and aeeRunBinding == the run binding derived from this statement",
      "the row's method is no stronger than the weakest signed aeeMethod across its COVERING records (reconstructed weaker than intercepted; records that cover nothing do not participate)",
      "batchRoot recomputes over observationRecords (RFC 6962, domain-separated, duplicate-leaf REJECT, largest-power-of-two split)"
    ],
    "failClosedSubstrateRow": "a basis:substrate row whose containmentObserved/basis/method is fail-closed cannot class-match and is therefore INVALID (not merely weak)"
  },
  "runBinding": {
    "$comment": "For any statement with >=1 basis:substrate row, digest = lowercase 64-hex SHA-256 of RFC 8785 canonicalization of the preimage below. runEntropy required exactly when substrate rows exist; a basis:artifact-only statement derives no binding and need not carry runEntropy. aeeBindingVersion 2 is the ONLY construction; reject an unknown version fail-closed, and an absent declaration defaults to the implemented version so that omitting the optional member stays legal. No verifier nonce (anti-splice, not a freshness challenge); replay of an identical-config run is bounded by a stateful consumer rejecting runEntropy reuse.",
    "aeeBindingVersion": "2",
    "preimage": {"aeeBindingVersion": "2", "catchPolicy": "<catchPolicy.digest.sha256>", "corpus": "<corpus.digest.sha256>", "networkPosture": "<sha256(JCS(the carried networkPosture OBJECT))>", "observationVocabulary": "<observationVocabulary.digest.sha256>", "runEntropy": "<runEntropy.digest.sha256>", "subject": "<subject[0].digest.sha256>", "substrate": "<substrate.digest.sha256>"},
    "networkPostureInput": "Version 2 hashes the carried object, not the value of its own digest member: binding the digest left the posture string beside it outside every signature, and the posture configuration that digest is taken over travels nowhere in the statement, so nothing could compare the two.",
    "observationVocabularyInput": "Version 2 adds it. Its caught array decides which labels are caught and both the recompute and coverage validity read it, so without it a producer could narrow the caught set after the run, re-derive the vocabulary digest against the arrays beside it for free, and leave every record's binding intact."
  },
  "observationRecordKinds": {
    "$comment": "Reserved payload members inside each covering DSSE record (validity.go evaluateKind; spec:583-618). An unrecognized aeeKind covers nothing and is otherwise ignored (still contributes its batchRoot leaf).",
    "interception": {"aeeMethod": "intercepted", "covers": "caught rows"},
    "arming":       {"aeeMethod": "intercepted", "requires": ["armedAt (under the issuedAt timestamp profile: uppercase separator/designator, zone Z|+00:00|-00:00; <= issuedAt)", "aeePostureDigest == pinned networkPosture digest"], "covers": "clean rows (with a sealed record)"},
    "sealed":       {"aeeMethod": "intercepted", "requires": ["aeeStillArmed == true", "aeeDropCount == 0 OR (0 <= aeeDropCount <= aeeDropBound declared in same payload)", "aeePostureDigest == pinned networkPosture digest AND == every covering arming record's aeePostureDigest"], "covers": "clean rows (with an arming record)"},
    "examination":  {"aeeMethod": "reconstructed", "covers": "reconstructed rows"}
  },
  "evidenceTier": {
    "$comment": "GATE 2 -- Appraisal Policy for Attestation Results (RFC 9334; tier.go; spec:273-288). Trust-relative, derived per row, NEVER carried; {declared|unattested|attested}. Derived BEFORE crediting any basis:substrate row or applying either strength ordering; NEVER alters result. No TOFU: with no policy-pinned substrate root, every basis:substrate row is `unattested`, and the substrate root MUST NOT be inferred from the predicate. A record keyid is an unauthenticated lookup hint, never the check.",
    "declared":   "a basis:artifact row (and a row fail-closed on basis)",
    "attested":   "a basis:substrate row where every covering record's signature verifies against a consumer-policy-named substrate observation key",
    "unattested": "a basis:substrate row otherwise",
    "consumerGuidance": "MAY reject, NEVER downgrade; an unattested substrate row ranks with artifact in both strength orderings; signed bytes are never rewritten. A carried predicate member `evidenceTier`, or any predicate-level member beginning with `aee`, MUST be ignored."
  },
  "unknownContainmentObserved": "fail-closed: a containmentObserved outside the carried observationVocabulary.labels forces result=fail and can support nothing stronger (never a default pass)."
}
