Say what the tenant check is, and what view_hash is not

gate-house (GH-DEC-2026-013) was right that our wording implied a fact
about the person. The tenant comparison is store isolation — does this
caller belong to the store this engine serves — and a registration-
supplied claim satisfies that while satisfying no doctrine about the
approver's own membership. Exact equality cannot see the difference, so
state it, and note that provenance gets recorded on the entry the way v4
records principal_type once the claim carries it.

Answer informed-decision's R3 in the claim contract: view_hash and
binding.digest answer different questions and must not be merged. Three
hashes, three questions. Recommend their binding document carry our
digest rather than re-canonicalize the same five fields, so the act has
one canonicalization and a mismatch is detectable.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1275879@bnt-lap001
Assistant-Session: eb464208-f821-41b2-bc5a-a6c33d92a8ad
This commit is contained in:
tegwick 2026-09-10 07:56:28 +02:00
parent 849c75bb09
commit 62233c7c52
3 changed files with 89 additions and 0 deletions

View file

@ -204,6 +204,39 @@ a service bind legible, but legibility is not permission, and the should-it
question remains gate-house's. `secrets-engine-approval` is unaffected: it has a
real presenter and is the client that unblocks T05. T01 stays `progress`.
2026-09-10: three inbound items, all answered; one is a live blocker on the
human client and it is not ours to fix. (i) `key-cape` reports that a human
access token **cannot carry `tenant:platform` today**: `effectiveTenant(user)`
resolves the claim from the directory record, `domain.User.Tenant` is populated
by no adapter, and every human token falls back to `tenant:coulomb`; the
per-client `tenant` field is read only on the client-credentials path. Our exact
equality would refuse such a token, surfacing as a rejected approval rather than
a registration defect. We answered **(2) registration-bound**, matching decision
`5ed3fb35-eca9-413a-82b9-95171ba85bf6` and symmetric with the service clients,
and only because registrations are static, deployment-owned, and dynamic client
registration is excluded by design. (ii) `gate-house` `GH-DEC-2026-013` names
the consequence of exactly that: a registration-supplied tenant is the client's
say-so, not the directory's, and exact matching cannot see the difference — the
same shape as our own `pdp_digest` finding one layer down. They were right that
our wording implied a fact about the person. `docs/caller-authentication.md` now
states that the check is **store isolation** and not membership, that a
registration-supplied tenant is admissible for admission but **not** for any
future doctrine turning on the approver's own membership, and that provenance
will be recorded on the entry the way v4 records `principal_type` once the claim
carries it. (iii) `informed-decision` R3, answered in `docs/approval-claim.md`:
`view_hash` and `binding.digest` are **not** the same hash and must not be
merged. Three hashes, three questions — which act (five fields), which decision
request (`pdp_digest`), what a person was shown (`view_hash`). Recommended that
their binding document *carry* our digest as a field rather than re-canonicalize
the same five fields, so the estate has one canonicalization of the act with a
detectable mismatch instead of two independently correct answers. Also accepted
from key-cape: `approval:read` added to the human client, and a documented
`assurance` shape (`level`/`methods`/`mfa`/`source`/`at`) — with `at` fixed to
carry the original authentication instant through session reuse, which matters
here because this engine stores that object verbatim as the only downstream
record that MFA happened. T01 stays `progress`; the human client cannot be
registered until the tenant source is settled by its owner.
## Harden durable storage and migrations
```task