Build the T08 domain core with the engine behind a seam

approval-engine APPROVAL-WP-0002-T01 is still progress and its namespace has no
pods, so the live end-to-end proof cannot run. Built everything that does not
depend on it, with the engine behind a Protocol plus a fake carrying its real
refusal semantics, so its arrival is a wiring change rather than a build.

- memo.py: the Decision Memo, versions, binding document. Principal, Scope,
  Awareness and Hat are dataclasses rather than dicts because the canonicalizer
  requires a shape and a missing key should fail at construction rather than
  deep inside hashing — which is exactly how it failed twice while building
  this. Field names follow the governed canonicalizer (item_id, severity,
  locator): the published vectors are the contract, so the object was aligned to
  them rather than the reverse.
- presentation.py: the sole writer of view_hash. One writer, one canonicalizer,
  one place to audit. Acknowledgment is an explicit method call and nothing
  infers it from scroll, dwell or focus.
- disposition.py: verbs and guards G_NOAGENT, G_STEP, G_PRES, G_ACK, G_REASONS,
  G_SEALED. accept is ABSENT from weak steps rather than present-and-disabled,
  because a greyed-out accept still teaches the wrong model. Only accept reaches
  the engine; a memo return is not represented there at all.
- provenance.py: claim routes per A-16. assert_human_control_dischargeable
  refuses a registration-supplied human, so PR-11's limitation fires at the
  point of use instead of sitting in a document.
- evidence.py: local outbox, commitment-only records carrying the
  GH-DEC-2026-014 §4 existence assertion, per-class reconciliation counts, and a
  custody-locator guard that rejects credentialed URLs (PR-12).
- approval_client.py: 409 duplicate_approver is success, 409 conflict terminal,
  503 fail-closed, approval:consume refused before a token is requested.

87 tests pass, including every negative case in the Use Case Catalog and that a
fail-closed outcome is recorded as a stance application with no verb field —
never as a decline, because the human did not make one.

T08 stays progress: the live proof is the remainder.

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
This commit is contained in:
tegwick 2026-09-10 20:38:20 +02:00
parent 4379576abd
commit 9e1f77e32b
18 changed files with 1503 additions and 10 deletions

View file

@ -429,7 +429,7 @@ issuer, so it is not this repository's to decide alone.
```task
id: INFD-WP-0001-T08
status: todo
status: progress
priority: medium
state_hub_task_id: "b5c1d329-9580-5672-9640-2930cbbb729a"
@ -454,6 +454,39 @@ act is permitted.
Gated externally on `approval-engine` `APPROVAL-WP-0002-T01` reaching `done` and
on the service being deployed with an origin this surface can reach.
2026-09-10 — **domain core built and tested; the live proof remains gated.**
`approval-engine` `APPROVAL-WP-0002-T01` is still `progress` and the namespace
has no pods, so the end-to-end proof against a deployed engine cannot run. Built
everything that does not depend on it, with the engine behind a seam so its
arrival is a wiring change rather than a build:
- `memo.py` — the Decision Memo, versions, and the binding document.
`Principal`, `Scope`, `Awareness` and `Hat` are dataclasses rather than dicts
because the canonicalizer requires a shape, and a missing key should fail at
construction rather than deep inside hashing. Field names follow the governed
canonicalizer (`item_id`, `severity`, `locator`) — the vectors are the
contract, so the object was aligned to them rather than the reverse.
- `presentation.py` — the **sole writer** of `view_hash`. Acknowledgment is an
explicit method call; nothing infers it.
- `disposition.py` — the verb vocabulary and guards. `accept` is **absent** from
weak steps rather than present-and-disabled, because a greyed-out accept still
teaches the wrong model.
- `provenance.py` — claim routes (A-16). `assert_human_control_dischargeable`
refuses a registration-supplied `human`, so PR-11's limitation is visible at
the point of use rather than buried in a document.
- `evidence.py` — the local outbox, commitment-only records carrying the
`GH-DEC-2026-014` §4 existence assertion, and a custody-locator guard that
rejects secret-shaped values (PR-12).
- `approval_client.py` — Protocol plus a fake with the engine's real semantics:
`409 duplicate_approver` is success, `409 conflict` terminal, `503` fail-closed,
and `approval:consume` refused before a token is ever requested.
87 tests pass, including every negative case in the Use Case Catalog: NC-01
through NC-08, the humanity-provenance guard, the existence assertion, and that
a fail-closed outcome is recorded as a stance application with no `verb` field —
never as a decline, because the human did not make one.
Remaining for `done`: the live proof. Superseded context —
2026-09-09: additionally gated on `INFD-IN-0003``GH-DEC-2026-012` limit 3
requires the evidence copy to reach `audit-core` independently of this
component, and the payload question is open. Design and decision request in