informed-decision/docs/specs/EvidenceModel.md
tegwick a8e227851e Promote schema and canonicalizer out of history; add EvidenceModel (T06)
Verified the three published hashes reproduce byte for byte before promoting
anything, then moved the schema, canonicalizer and vectors into governed assets.
history/20260909-initial-exploration/ is untouched and stays the provenance
record.

- schemas/, informed_decision/, tests/vectors/ populated; the reference
  canonicalizer's ad-hoc __main__ block replaced by a real
  `python -m informed_decision` entry point.
- tests/test_canonicalize.py — 20 tests, all green. Published vectors, all four
  isolation properties, canonical-form round-trip, key sorting, and a provenance
  test asserting the governed fixtures have not drifted from history/.
- docs/specs/EvidenceModel.md — the two hashes, the split and why it exists, the
  four isolation properties, the presentation record, the bundle, and the
  relationship to audit-core.
- pyproject.toml, Makefile.

One test of mine was wrong on first run: it scanned for ", " to assert no
insignificant whitespace, which fires on prose inside a brief. Replaced with a
canonical round-trip comparison, which is the property actually meant. The
canonicalizer was correct.

EvidenceModel leads with what the model does NOT claim — no proof of
comprehension, no proof of reading (deliberately, since the alternative is
surveillance), no survival of a compromised surface, and audit-core's inherited
bound that a hash chain cannot prove a record was never sent.

T06 stays progress: the SCOPE.md rewrite is gated on the T02 ruling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3W1dQG7GFFM9d94jFx7iR

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1565372@bnt-lap001
Assistant-Session: 16bb2f25-b34c-49ef-8e94-5fec3567a568
2026-09-09 14:16:28 +02:00

9 KiB

Evidence Model

Repo: informed-decision Workplan task: INFD-WP-0001-T06 Status: draft, pending review Companions: ProductRequirementsDocument.md, UseCaseCatalog.md, ArchitectureBlueprint.md (gated on INFD-WP-0001-T02) Normative algorithm: history/20260909-initial-exploration/canonicalize.md Governed implementation: informed_decision/canonicalize.py Tests: tests/test_canonicalize.py


1. What this model claims

This person, in this role, was shown this view, and bound this act.

Not "the file was signed". The difference is the whole product. A signature over a document proves a key was used on some bytes; it does not establish what the signer was looking at when they decided, which is the question an auditor actually asks and the question no e-signature product answers.

The mechanism is dynamic linking: the human-readable content actually presented is canonicalized and its digest is bound into the act.

2. What this model does not claim

Stated first, because an evidence model that oversells is worse than none.

E-01 — It does not prove the person understood. It proves what was presented and what was explicitly acknowledged. Comprehension is not observable and this model does not pretend to observe it.

E-02 — It does not prove the person read anything. Deliberately. Evidence of informedness is the hash of the presented view plus explicit acknowledgment — never dwell time, scroll depth, or attention telemetry. That choice trades a weaker claim for the absence of surveillance, and the trade is intentional (INTENT.md principle 12, PR-70/PR-71).

E-03 — It does not survive a compromised surface. view_hash is computed by the same component that renders. A compromised surface can present X and attest Y. This is structurally the same residual approval-engine names for adversarial omission at a compromised source, and it is not closed here, not closable at this layer, and never to be described as closed.

E-04 — A hash chain proves records were not altered or truncated after arrival. It cannot prove a record was never sent. Carried over from approval-engine's reasoning rather than rediscovered: a suppressed event leaves the chain perfectly intact and verification reports intact. Completeness is a separate obligation met by cadence, not by chaining.

E-05 — Stage 1 binding is organizational only. No AES, no QES. The object carries binding_level so higher levels are expressible without a schema change; nothing above organizational is implemented or claimed.

E-06 — A Stage 1 escalation is an assertion, not a verified claim. Without the mandate graph, guard G_ESC cannot be fully evaluated. The bundle records what the approver asserted and marks it unverified.

3. The two hashes

view_hash awareness_hash
Over the binding document the awareness document
Signed yes, from organizational level up no
Purpose what the person committed to what they were oriented with
In AES/QES signed attributes yes no, unless promotion ran

Binding allow-list: memo_id, memo_version, question, requested_act, binding_level, brief, locale, ui_release, normalized packet, normalized highlights, binding, and optionally awareness_promoted.

Awareness allow-list: proposed hat and its source, available hats and scopes, last session, situation note.

Profile: JCS-inspired restricted JSON — UTF-8, keys sorted by code unit, no insignificant whitespace, integers only, unknown keys stripped, nulls dropped.

canonical = dumps(document)
hash      = SHA-256(UTF-8(canonical))

Why the split exists

The login case forced it and it now applies everywhere. Defaulting a role to last-used is required for situational awareness — an operator who cannot see which hat they are about to wear is less safe, not more — and is forbidden from silently entering the signed payload. A person must not be bound to orientation material they did not commit to.

Promotion is the only bridge: moving a field from awareness into binding creates a new memo version, invalidates outstanding presentations, and copies the named fields into awareness_promoted where view_hash covers them (guard G_PROMOTE).

4. The four isolation properties

These are the model. Each is a test in tests/test_canonicalize.py and each must stay green; a failure means view_hash no longer means what §1 claims.

# Property Protects Test
1 Shuffling object keys does not change either hash Any verifier, in any language, agrees with us test_key_order_does_not_change_*
2 Editing an awareness field does not change view_hash The person is bound only to what they committed to test_editing_awareness_does_not_change_view_hash
3 Changing binding.target does change view_hash Positive control — without it, 2 could pass vacuously test_changing_binding_target_changes_view_hash
4 Selecting a hat after login does not rewrite view_hash Post-bind session state is a different object test_post_bind_hat_selection_does_not_change_view_hash

Property 3 is not decorative. Properties 1, 2 and 4 are all negative — they assert that things do not change the hash. A canonicalizer that returned a constant would pass all three. Property 3, plus the per-field variants over question, requested_act, binding_level and packet, is what makes the suite meaningful.

Published vectors reproduce byte for byte and are asserted in CI:

login       view_hash       492d9d31…1b06b8
login       awareness_hash  2be77429…4fe05d1
ADR-0042    view_hash       1c89ec07…e91f202

Provenance is tested. history/20260909-initial-exploration/ is never edited. test_governed_vectors_match_the_preserved_history_copy asserts the governed fixtures have not drifted from the founding copies, so a silent edit to a vector to make a failing test pass is itself a test failure.

5. The presentation record

Every render creates one. It carries:

  • memo_id, memo_version
  • principal — who it was shown to
  • locale, ui_release — both inside view_hash
  • rendered_at
  • acked_highlight_ids
  • view_hash, awareness_hash
  • phasepre_bind | bind | post_bind

A disposition references exactly one presentation, of the same memo version (guard G_PRES). A presentation taken at version n cannot bind after the memo advances to n+1 — no silent upgrade (NC-07).

Because locale is inside view_hash, the same memo read in German and in English produces two different presentations of one binding. Intended, and documented rather than smoothed over (PR-61).

6. The evidence bundle

An export, not a screenshot (INTENT.md principle 7). Contains the memo and all its versions, every presentation with both hashes, every disposition, the thread, the route as executed, and the append-only hash chain.

Requirements:

  • Verifies offline. No network, no access to this service. A verifier recomputes view_hash from the bundle's own binding document and compares.
  • Detects mutation. Altering any covered field breaks verification.
  • States its own bound. The residuals in §2 travel with the bundle as metadata. An auditor must not have to read this document to learn that E-03 exists.
  • Marks unverified assertions as such — see E-06.

7. Relationship to audit-core

audit-core holds the archive; this repository holds the operative record and produces the export. This surface emits its transitions there and does not duplicate the archive's role.

Its guarantee is bounded and the bound is inherited, not re-litigated: audit-core is not WORM or object-locked, its tamper evidence is conditional on live preconditions, and stronger custody was decided against in statute §16. This repository must not plan around a stronger archive, and must not imply the bundle is more durable than the archive it reconciles against.

8. Open — view_hash versus the approval binding digest

Unresolved. Filed as INFD-IN-0001 R3 with gate-house.

approval-engine's claim carries "a digest over the same canonical binding the decision point already computes." view_hash is also a digest over "the binding". They cover different material: the approval digest exists with no human in the loop; view_hash additionally covers brief, packet, highlights, locale and UI release.

Until ruled, this repository computes view_hash and asserts no relationship between the two. Three candidate outcomes and their costs are set out in docs/gate-house-decision-request-layer-placement.md §2 R3. The outcome to be avoided is both digests shipping with no stated authority rule, leaving the estate with two canonicalizations of one act.

9. Signed attributes (L4+, horizon)

When AES/QES arrives, the signed attributes carry memo_id, memo_version, disposition.verb and presentation.view_hash. They do not carry awareness_hash unless promotion ran. Recorded here so nothing built at Stage 1 forecloses it.