approval-engine met the condition. Verified here rather than taken on report: their docs/approval-claim.md carries "Presentation exclusion — GH-DEC-2026-015 §4" in normative language, and I ran tests/test_claim_contract.py::test_presentation_changes_cannot_change_the_approved_act myself — 1 passed. That test pins the digest input set from BOTH sides, and the narrowing half is what makes it real: without it a digest over four fields, or over a constant, would pass the widening half perfectly. view_hash now carries binding.digest and the act-scope is no longer independently canonicalized here, so the act has exactly one canonicalization computed by the layer that owns it. approval_binding_digest is validated for shape and refused without its approval id — it is carried, never computed. The three published vectors are unchanged: they do not carry the new key, so pick omits it. Asserted, not assumed. The cycle condition did not disappear, its protection moved — from refusing nesting to approval-engine's normative exclusion. layer.yaml carries it as cycle_condition with a test, so a future widening meets a rule rather than silence. One thing not assumed. Both gate-house and approval-engine said our binding slice canonicalizes principal and target, two of their five fields. target plainly is act material and is now dropped. But their principal is the party ON WHOSE BEHALF the approval was issued, while ours is the person being BOUND — the approver. Different roles, and dropping ours would remove who was shown this from view_hash and gut the promise. Kept it, declared principal_role_overlap open in layer.yaml, tested that changing the approver still moves view_hash, and raised it rather than silently resolving it either way. L0/L2 are unaffected: with no approval there is no digest to defer to, and test_act_scope_still_binds_when_there_is_no_carried_digest pins that. 100 tests pass. 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
461 lines
23 KiB
Markdown
461 lines
23 KiB
Markdown
# 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`
|
|
- `phase` — `pre_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.
|
|
|
|
**Ruled `GH-DEC-2026-012` R3, and now re-opened as `INFD-IN-0004`.** The ruling:
|
|
distinct attestations with an authority rule — the binding digest is
|
|
authoritative for what the request *is*, `view_hash` for what was *shown*,
|
|
neither substitutable, and a disagreement between them is a finding against the
|
|
presenting surface rather than a fact about the request. Linkage by
|
|
co-reference, not nesting.
|
|
|
|
`approval-engine` has since published what its digests actually cover
|
|
(`docs/approval-claim.md`) and recommends the opposite linkage — that our
|
|
binding document carry their `binding.digest` as a field, which is the option
|
|
`GH-DEC-2026-012` refused. Their reason is the stronger version of the risk we
|
|
raised: our binding slice canonicalizes `principal` and `target`, two of their
|
|
five digest fields, so co-reference by identifier alone leaves **two
|
|
independent canonicalizations of one act** rather than removing the duplication.
|
|
|
|
Raised as a finding rather than resolved bilaterally
|
|
(`docs/finding-r3-linkage-conflict.md`). **Re-ruled by `GH-DEC-2026-015`:
|
|
gate-house reversed itself — nesting is permitted for this pair, so `view_hash`
|
|
may carry `binding.digest` and our binding slice stops independently
|
|
canonicalizing act material.**
|
|
|
|
The decisive ground was not the cycle argument we led with. It was that our
|
|
binding slice canonicalizes `principal` and `target`, two of the five fields in
|
|
their digest, so co-reference by identifier left us performing *a partial
|
|
recomputation of one act in a second vocabulary* — **closer to the translation
|
|
R3 forbade than nesting is**. Nesting removes the duplication; co-reference
|
|
manages it.
|
|
|
|
**Activated 2026-09-10.** `approval-engine` met the condition and we verified it
|
|
here rather than taking it on report: their `docs/approval-claim.md` carries
|
|
*"Presentation exclusion — GH-DEC-2026-015 §4"* in normative language, and
|
|
`test_presentation_changes_cannot_change_the_approved_act` pins the input set
|
|
from **both** sides — widening (presentation material leaves the digest
|
|
unchanged) *and* narrowing (each of the five act fields changes it). The
|
|
narrowing half is what makes it real: without it, a digest over four fields, or
|
|
over a constant, would pass the widening half perfectly. Run and confirmed
|
|
passing.
|
|
|
|
So `view_hash` now **carries** `binding.digest`, and the act-scope is no longer
|
|
independently canonicalized here. The act has exactly one canonicalization,
|
|
computed by the layer that owns it, and `view_hash` means *this person was shown
|
|
this presentation **of this act***.
|
|
|
|
**The cycle condition did not go away — its protection moved.** It is no longer
|
|
enforced by refusing nesting but by `approval-engine`'s normative exclusion:
|
|
containment stays one-directional because `binding.digest` must never cover
|
|
presentation material. If that exclusion is relaxed, this linkage must be
|
|
revisited *before* the widening ships. `layer.yaml` carries it as
|
|
`cycle_condition` so a future widening meets a rule rather than silence.
|
|
|
|
**One thing we did not assume.** `approval-engine`'s `principal` is the party
|
|
*on whose behalf* the approval was issued; ours is the person being **bound** —
|
|
the approver. Different roles. Dropping ours on the strength of "the digest
|
|
covers principal" would remove *who was shown this* from `view_hash` and gut the
|
|
promise. We kept it, declared the overlap `open` in `layer.yaml`, and raised it
|
|
with `approval-engine`. If the two are the same field, ours drops too.
|
|
|
|
Our ordering-dependency objection to option (c) was **withdrawn as mistaken**
|
|
and recorded as withdrawn: `binding.digest` is over act material, determined
|
|
before anyone is presented anything. We stated that cost, gate-house took it
|
|
from us, and neither of us checked it.
|
|
|
|
For reference, three hashes answering three questions:
|
|
|
|
| Hash | Owner | Covers | Answers |
|
|
| --- | --- | --- | --- |
|
|
| `binding.digest` | `approval-engine` | `action`, `actor`, `principal`, `purpose`, `target` | which **act** is approved |
|
|
| `binding.pdp_digest` | `approval-engine` | the PDP's decision-request binding | which **decision request** |
|
|
| `view_hash` | here | brief, packet, highlights, locale, UI release, binding slice | what a **person was shown** | 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.
|
|
|
|
## 8b. Where `view_hash` actually lives (Stage 1)
|
|
|
|
**It is not in the approval entry, by the engine's deliberate design.**
|
|
|
|
`POST /v1/approvals/{id}/entries` reads the request body and **discards it**.
|
|
Approver identity, `assurance` and `evidence_ref` come only from the verified
|
|
token (`evidence_ref` is `jwt-sha256:<digest of the presented token>`). The
|
|
reasoning is sound and we adopt it rather than argue it: a caller-supplied
|
|
approver field is provenance, not evidence, and that entry is currently the one
|
|
record containing no caller-supplied data.
|
|
|
|
Stage 1 arrangement, requiring no change to `approval-engine`:
|
|
|
|
1. this repository holds the presentation record and computes both hashes;
|
|
2. it emits that record to `audit-core` as its own evidence;
|
|
3. correlation to the approval entry is by **`(approval_id, subject,
|
|
approved_at)`** — all three returned by the engine after a successful entry.
|
|
|
|
**Consequence for an auditor, stated plainly:** someone holding only the
|
|
approval object cannot reach the presentation. They need this surface's record
|
|
or `audit-core` as well. `GOAL.md` DoD-3 — "the approval entry is
|
|
reconstructable from a `view_hash`" — is satisfied by the correlation triple,
|
|
not by a hash stored on the entry.
|
|
|
|
Putting the binding *inside* the approval object would be a real change request
|
|
against `approval-engine` (an optional, verified-alongside-token `view_hash` on
|
|
the entry) and it is doctrine before it is a change request, so it goes to
|
|
`gate-house` first. Not raised at Stage 1; recorded here so the option is not
|
|
lost.
|
|
|
|
### `assurance` is the only carrier of authentication facts
|
|
|
|
`approval-engine` persists the token's `assurance` claim verbatim and
|
|
interprets nothing — an empty `{}` is *accepted* and evidences nothing. It is
|
|
therefore the single place where "this was bound under MFA" survives into the
|
|
approval record. The shape must carry authentication method, `acr`/`amr` and
|
|
`auth_time` at minimum, and `informed-decision` and `key-cape` own it between
|
|
them. Nothing downstream reconstructs it.
|
|
|
|
### Principal type is verified evidence; `subject_id` is not
|
|
|
|
Schema v4 records `entries[].principal_type` from the verified token. Design
|
|
principle 10 (humans bind, agents draft) is enforceable from the evidence chain
|
|
by reading that field. Never infer it from the shape of `subject_id` — a naming
|
|
convention is not a verified claim. Entries written before v4 are `null` and
|
|
must not be read as `human`.
|
|
|
|
## 8c. Act-scope is not membership — `GH-DEC-2026-013` §5
|
|
|
|
Gate House made the sharpest observation anyone has made about this object
|
|
model, and it is worth carrying in full because it changes what we record even
|
|
though it changes no field.
|
|
|
|
Two different facts are being asked to travel in one claim named `tenant`:
|
|
|
|
| Fact | Property of | Who needs it |
|
|
| --- | --- | --- |
|
|
| Which scope is this act being entered into | the **act** | this repository's binding slice |
|
|
| Which tenant is this person a member of | the **principal** | `approval-engine`'s exact-match admission |
|
|
|
|
> *"A binding slice that must commit the scope being entered should commit THAT
|
|
> SCOPE, not borrow a membership claim to stand in for it."*
|
|
|
|
**Our schema already does the right thing.** `binding.target` is the act-scope:
|
|
the tenant, environment and system being entered, committed in the binding
|
|
document and therefore inside `view_hash`. It is not derived from, and must
|
|
never be replaced by, the token's `tenant` claim.
|
|
|
|
What was missing is not a field but a **statement and a record**:
|
|
|
|
- `binding.target` is the authoritative act-scope commitment. The `tenant` claim
|
|
is a membership fact about the principal and is **not** the act-scope.
|
|
- The two must never be collapsed. Where they agree, that agreement is itself a
|
|
fact worth recording; where they disagree, issuance is refused upstream
|
|
(`key-cape` `329e48f`) and this surface records the refusal rather than
|
|
choosing a winner.
|
|
- **The `tenant` claim's provenance is recorded.** `key-cape` emits `tenant` as
|
|
a bare string, so a consumer cannot tell a tenant the *directory asserted about
|
|
the person* from one a *registration supplied about the client they came
|
|
through*. `GH-DEC-2026-013` §5 requires the claim to carry its provenance;
|
|
until it does, this surface records which route the value arrived by rather
|
|
than storing an undifferentiated string.
|
|
|
|
This is the same rule as `unknown` versus `absent` in the stance map and
|
|
`erased` versus `never held` in §8d: **wherever a system reaches one appearance
|
|
by two routes, the record must say which route**, or the safer reading becomes
|
|
unavailable to everyone.
|
|
|
|
## 8d. What commitment-only does and does not establish — `GH-DEC-2026-014`
|
|
|
|
Commitment-only emission is **granted for Stage 1**. It is granted on the test
|
|
`GH-DEC-2026-013` set: its distinguishing case fails closed. A reviewer who
|
|
cannot obtain the content gets **no** reconstruction rather than a **wrong** one.
|
|
|
|
**It satisfies non-alteration. It does not satisfy reconstructability, and must
|
|
not be described as doing so** — not here, not in `audit-core`'s documents, and
|
|
not in any conformance claim.
|
|
|
|
A commitment-only record is weaker than an archive in a specific way. An archive
|
|
proves records were not altered or truncated after arrival, never that one was
|
|
never sent (§2, E-04). A commitment additionally **does not carry what it commits
|
|
to**. It establishes that the commitment was made when it says and has not
|
|
changed. About *what was committed to* it establishes nothing — except
|
|
conditionally: **if** a document is later produced, whether it is the one.
|
|
|
|
### The gap, in the right words
|
|
|
|
An earlier draft of ours said commitment-only "leaves us able to erase the
|
|
content". That understates where the problem sits, and Gate House corrected it:
|
|
|
|
> **Commitment-only moves *integrity* out of our control and leaves
|
|
> *availability* entirely inside it. The party that can withhold the content is
|
|
> the party the evidence is about.**
|
|
|
|
That is the condition limit 3 exists to prevent, **reduced and not removed**. The
|
|
reduction is real. It is not sufficient alone — which is why the grant carries
|
|
the condition below.
|
|
|
|
### The condition that makes the grant safe
|
|
|
|
**Non-production must be detectable as a finding, not present as an absence.**
|
|
|
|
If the independent path holds only a commitment, a reviewer who asks for content
|
|
and gets nothing cannot distinguish *erased*, *withheld*, *lost*, and *never
|
|
held*. The absence reads as an unremarkable blank.
|
|
|
|
So the evidence path must carry, alongside the commitment:
|
|
|
|
1. an assertion that committed content **exists**, and
|
|
2. **where custody sits**,
|
|
|
|
such that failure to produce at retrieval is a **conformance failure
|
|
attributable to the custodian**.
|
|
|
|
> *A commitment with no assertion that something is being committed to is
|
|
> indistinguishable from a commitment to nothing.*
|
|
|
|
This is **not a reversal candidate**. If it proves expensive, the answer is a
|
|
cheaper mechanism for the same property, never removal of the property.
|
|
|
|
### Residual, again
|
|
|
|
Not closed, not credited. The existence assertion narrows the erasure gap — a
|
|
detectable non-production tells a reviewer something is missing and who owed it.
|
|
It does not produce the missing thing.
|
|
|
|
## 8e. Cadence — declared, and now supportable
|
|
|
|
`audit-core` registered this source as proposed (`AUDIT-IN-0003`,
|
|
`docs/informed-decision-source-registration.md`) and landed the detection half
|
|
(`AUDIT-WP-0009` T04/T06/T07). The cadence can stop being described as
|
|
declared-but-not-operating once heartbeats are flowing.
|
|
|
|
**Per class, not per source** — the shape this repository argued for and
|
|
`audit-core` adopted. A per-source heartbeat from a mixed-volume emitter is
|
|
satisfied by its chattiest class and says nothing about the quiet,
|
|
security-relevant one, which is the only reason heartbeats exist.
|
|
|
|
| Class | Volume | Heartbeat | Reconciliation |
|
|
| --- | --- | --- | --- |
|
|
| `presentation` | one per render | yes — legitimately silent for days at Stage 1 | yes |
|
|
| `disposition` | low, security-relevant | yes | yes |
|
|
| `stance_application` | low, security-relevant | yes | yes |
|
|
|
|
Reconciliation applies to **every** class including the high-volume one: rate
|
|
detects a stream stopping, never a stream missing the particular renders that
|
|
mattered.
|
|
|
|
A heartbeat is an **ordinary event** — same envelope, same append-only custody,
|
|
same chain. Deliberately so: a heartbeat stored outside the chain would be the
|
|
one record that could be back-dated.
|
|
|
|
Note `no_heartbeat_since_registration`: declaring a heartbeat and never sending
|
|
one is **its own finding**, not a skip.
|
|
|
|
### The bound on both controls
|
|
|
|
Reconciliation compares `audit-core`'s counts against counts we compute from our
|
|
own state. **Where the emitter is compromised, both controls agree with it** — a
|
|
compromised surface suppresses the event and its own count together, and emits a
|
|
truthful-looking `nothing-to-report`.
|
|
|
|
Both cover loss, outage, drain failure and accident, which is most of what
|
|
actually goes wrong. **Neither covers the residual we already declare**, and
|
|
neither may be described as covering it. Closing it needs an observer
|
|
independent of the emitter, which §16 put outside `audit-core`'s scope.
|
|
|
|
### Tenant provenance is not in the envelope
|
|
|
|
`audit-core`'s tenant is not an identity claim they resolve; it is a value our
|
|
credential is permitted to write, checked by exact string equality. Recording a
|
|
route in the audit event would be them restating something they did not observe
|
|
— the same error as claiming an event occurred. It lands in the registration
|
|
document instead, whose authority is `GH-DEC-2026-013`'s bounded gap rather than
|
|
a populated directory record. If that gap closes, the entry is revisited rather
|
|
than assumed still correct.
|
|
|
|
## 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.
|