informed-decision/docs/specs/ProductRequirementsDocument.md

541 lines
26 KiB
Markdown
Raw Normal View History

Add PRD and Use Case Catalog; file the gate-house request (T02-T04) T02 — intake INFD-IN-0001 filed with gate-house and messages sent to gate-house, key-cape and approval-engine. Status progress; it now waits on an external ruling. key-cape was told explicitly why T07 is not answering KEY-WP-0013-T02 yet — a placeholder callback URI would either fail closed or register an origin no component owns — and given the token shape to check now rather than at T07. T03 — ProductRequirementsDocument.md. 30 requirements, each traced to a GOAL.md DoD item, an INTENT principle or wrongness condition, a state-transition guard, or a named external contract, and each with an observable pass condition. Anti-requirements are stated as testable absences: no dwell timers, no attention analytics, no dark patterns, no auto-approval, no approval-state caching, no authorization endpoint. Four limitations are recorded up front, including that escalate without a mandate graph is forwarding and that view_hash is computed by the renderer. T04 — UseCaseCatalog.md. L0-L5 with counterparties, plus ten negative cases bound to guards and isolation vectors. Each case records the constraint it places on the shared schema, so scale invariance is testable rather than asserted. Closes with the four changes that would fork the object. T05 and T07 remain gated on the ruling. T06 is independent and is next. 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:11:36 +02:00
# Product Requirements Document — Stage 1
**Repo:** informed-decision
**Stage:** 1 — the L3 approval approver surface
**Workplan task:** `INFD-WP-0001-T03`
Revise specs against approval-engine's approver-surface requirements approval-engine replied to INFD-IN-0001 with docs/approver-surface-requirements.md (31da1af, 5203f46) and two corrections. Several of my requirements were wrong or incomplete; revised rather than appended to. Corrected: - PR-02 listed only approval:approve. Wrong — the surface also needs approval:read to fetch what it renders. As drafted it would have shipped a client able to submit an entry it could never display. That changes a registration key-cape has already implemented, so it is their call (open question A). - NC-03 implied approval-engine refuses non-human approver entries. It does not; only /consume is principal-restricted, and the operator service client holds approval:approve. Enforcement of "humans bind, agents draft" is therefore ours alone, and is auditable via schema v4's entries[].principal_type — never from the shape of subject_id. Added: - PR-04 assurance shape. approval-engine persists it verbatim and accepts an empty object, so it is the only place MFA survives into the approval record. Needs auth method, acr/amr, auth_time, agreed with key-cape. - PR-05 entitlement. A 200 from the engine is not permission to view; we owe access-engine a check before rendering. Consuming a decision, not making one. - PR-06 response mapping, including 409 duplicate_approver rendered as SUCCESS (a browser double-submit is routine and the first entry stands) and 503 as fail-closed. - PR-07 and a matching INTENT wrongness condition: never render `approved` as permission to act. That is a PDP in the browser wearing UI copy. - L-05, L-06 and EvidenceModel 8b: view_hash cannot ride into the entry — the POST discards its body by design — so Stage 1 correlates by (approval_id, subject, approved_at). DoD-3 is satisfied by the triple, not by a stored hash. PRD open question 1 is answered by construction: there is no inbox endpoint and there will not be one, so the approvals-inbox shape is foreclosed upstream. 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:20:13 +02:00
**Status:** draft. **Revised 2026-09-09** against
`approval-engine/docs/approver-surface-requirements.md` (commits `31da1af`,
`5203f46`) and two corrections `approval-engine` sent with it. Revised
requirements are marked **[rev-1]**
Add PRD and Use Case Catalog; file the gate-house request (T02-T04) T02 — intake INFD-IN-0001 filed with gate-house and messages sent to gate-house, key-cape and approval-engine. Status progress; it now waits on an external ruling. key-cape was told explicitly why T07 is not answering KEY-WP-0013-T02 yet — a placeholder callback URI would either fail closed or register an origin no component owns — and given the token shape to check now rather than at T07. T03 — ProductRequirementsDocument.md. 30 requirements, each traced to a GOAL.md DoD item, an INTENT principle or wrongness condition, a state-transition guard, or a named external contract, and each with an observable pass condition. Anti-requirements are stated as testable absences: no dwell timers, no attention analytics, no dark patterns, no auto-approval, no approval-state caching, no authorization endpoint. Four limitations are recorded up front, including that escalate without a mandate graph is forwarding and that view_hash is computed by the renderer. T04 — UseCaseCatalog.md. L0-L5 with counterparties, plus ten negative cases bound to guards and isolation vectors. Each case records the constraint it places on the shared schema, so scale invariance is testable rather than asserted. Closes with the four changes that would fork the object. T05 and T07 remain gated on the ruling. T06 is independent and is next. 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:11:36 +02:00
**Companion specs:** `UseCaseCatalog.md`, `ArchitectureBlueprint.md` (gated on
`INFD-WP-0001-T02`), `EvidenceModel.md`
---
## 1. Scope
Stage 1 delivers **one** thing: a browser-facing surface on which a human
approver is shown an `approval-engine` approval request, understands it, acts on
it with the full disposition vocabulary, and leaves a reconstructable record of
what they were shown when they bound themselves.
Everything in this document is L3 on the depth spectrum. L0, L1, L2, L4 and L5
appear only in `UseCaseCatalog.md`, and only to constrain the schema so it
cannot fork later.
**Traceability.** Every requirement below carries a `trace:` line pointing at
either a `GOAL.md` definition-of-done item (`DoD-n`), an `INTENT.md` principle
(`P-n`) or wrongness condition (`W-n`), or a named external contract. A
requirement that traces to nothing is out of scope by construction.
**Testability.** Every requirement states an observable pass condition. "The UI
should be clear" is not a requirement; "a bind attempt with an unacknowledged
required highlight returns an error and creates no disposition" is.
---
## 2. Personas
### 2.1 Approver (primary)
Holds a mandate. Receives a memo because the routing said they are the person
who may bind this act — not because they were available. Is usually busy, is
frequently not a specialist in the subject matter, and is accountable for the
outcome afterwards.
What they need: to know why this is on their desk, what happens if they say yes,
what happens if they say nothing, and what they are actually binding. What they
must be able to do without leaving the object: ask a question, send it back, and
push it upward.
### 2.2 Requester (secondary)
Assembled the memo, or an agent assembled it on their behalf. Needs to know
where it is, who is holding it, and what was asked for when it comes back.
Stage 1 does **not** give the requester an authoring UI — memos arrive from
`approval-engine`. Authoring is Stage 2+.
### 2.3 Observer / auditor (tertiary)
Arrives months later with a question of the form *"who decided this, having seen
what?"* Never uses the live surface. Consumes the evidence bundle export and
`audit-core`. Their requirement is that the answer exists and verifies offline.
### 2.4 Non-persona: the agent
An agent may assemble and enrich a memo. It has **no** disposition capability.
This is stated as a persona exclusion because the temptation to add "auto-approve
low-risk items" arrives in every approval product, and it is the single change
that would void the identity half of the promise.
`trace: P-10, W-"An agent binds", guard G_NOAGENT`
---
## 3. Functional requirements
### 3.1 Identity and session
**PR-01 — Identity is imported, never issued.**
Every principal is authenticated by `key-cape` via authorization-code + PKCE.
The surface holds no password, no local credential store, and issues no
assurance level of its own.
*Pass:* no code path creates a session not derived from a `key-cape` token;
authentication failure yields no session.
`trace: DoD-2, P-9, GOAL invariant "Identity is imported"`
Revise specs against approval-engine's approver-surface requirements approval-engine replied to INFD-IN-0001 with docs/approver-surface-requirements.md (31da1af, 5203f46) and two corrections. Several of my requirements were wrong or incomplete; revised rather than appended to. Corrected: - PR-02 listed only approval:approve. Wrong — the surface also needs approval:read to fetch what it renders. As drafted it would have shipped a client able to submit an entry it could never display. That changes a registration key-cape has already implemented, so it is their call (open question A). - NC-03 implied approval-engine refuses non-human approver entries. It does not; only /consume is principal-restricted, and the operator service client holds approval:approve. Enforcement of "humans bind, agents draft" is therefore ours alone, and is auditable via schema v4's entries[].principal_type — never from the shape of subject_id. Added: - PR-04 assurance shape. approval-engine persists it verbatim and accepts an empty object, so it is the only place MFA survives into the approval record. Needs auth method, acr/amr, auth_time, agreed with key-cape. - PR-05 entitlement. A 200 from the engine is not permission to view; we owe access-engine a check before rendering. Consuming a decision, not making one. - PR-06 response mapping, including 409 duplicate_approver rendered as SUCCESS (a browser double-submit is routine and the first entry stands) and 503 as fail-closed. - PR-07 and a matching INTENT wrongness condition: never render `approved` as permission to act. That is a PDP in the browser wearing UI copy. - L-05, L-06 and EvidenceModel 8b: view_hash cannot ride into the entry — the POST discards its body by design — so Stage 1 correlates by (approval_id, subject, approved_at). DoD-3 is satisfied by the triple, not by a stored hash. PRD open question 1 is answered by construction: there is no inbox endpoint and there will not be one, so the approvals-inbox shape is foreclosed upstream. 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:20:13 +02:00
**PR-02 [rev-1] — Token shape is fixed by the resource server.**
Tokens presented to `approval-engine` carry `aud=approval-engine` (the resource
server, never the OAuth client id), `principal_type: human`, `tenant:
tenant:platform` compared by **exact string equality** (no alias, no prefix
handling — `platform` and `tenant:coulomb` are refused), non-empty `sub` and
`scope`, `assurance` as a non-null JSON object, and RS256 verification against
the issuer's `/jwks`. The access token is the evidence; `id_token` is never
presented here.
Scopes required: **`approval:read`** to fetch the approval and its claim, and
**`approval:approve`** to record an entry. The surface never holds
`approval:consume`.
Add PRD and Use Case Catalog; file the gate-house request (T02-T04) T02 — intake INFD-IN-0001 filed with gate-house and messages sent to gate-house, key-cape and approval-engine. Status progress; it now waits on an external ruling. key-cape was told explicitly why T07 is not answering KEY-WP-0013-T02 yet — a placeholder callback URI would either fail closed or register an origin no component owns — and given the token shape to check now rather than at T07. T03 — ProductRequirementsDocument.md. 30 requirements, each traced to a GOAL.md DoD item, an INTENT principle or wrongness condition, a state-transition guard, or a named external contract, and each with an observable pass condition. Anti-requirements are stated as testable absences: no dwell timers, no attention analytics, no dark patterns, no auto-approval, no approval-state caching, no authorization endpoint. Four limitations are recorded up front, including that escalate without a mandate graph is forwarding and that view_hash is computed by the renderer. T04 — UseCaseCatalog.md. L0-L5 with counterparties, plus ten negative cases bound to guards and isolation vectors. Each case records the constraint it places on the shared schema, so scale invariance is testable rather than asserted. Closes with the four changes that would fork the object. T05 and T07 remain gated on the ruling. T06 is independent and is next. 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:11:36 +02:00
*Pass:* a token request including `approval:consume` fails a build-time check;
Revise specs against approval-engine's approver-surface requirements approval-engine replied to INFD-IN-0001 with docs/approver-surface-requirements.md (31da1af, 5203f46) and two corrections. Several of my requirements were wrong or incomplete; revised rather than appended to. Corrected: - PR-02 listed only approval:approve. Wrong — the surface also needs approval:read to fetch what it renders. As drafted it would have shipped a client able to submit an entry it could never display. That changes a registration key-cape has already implemented, so it is their call (open question A). - NC-03 implied approval-engine refuses non-human approver entries. It does not; only /consume is principal-restricted, and the operator service client holds approval:approve. Enforcement of "humans bind, agents draft" is therefore ours alone, and is auditable via schema v4's entries[].principal_type — never from the shape of subject_id. Added: - PR-04 assurance shape. approval-engine persists it verbatim and accepts an empty object, so it is the only place MFA survives into the approval record. Needs auth method, acr/amr, auth_time, agreed with key-cape. - PR-05 entitlement. A 200 from the engine is not permission to view; we owe access-engine a check before rendering. Consuming a decision, not making one. - PR-06 response mapping, including 409 duplicate_approver rendered as SUCCESS (a browser double-submit is routine and the first entry stands) and 503 as fail-closed. - PR-07 and a matching INTENT wrongness condition: never render `approved` as permission to act. That is a PDP in the browser wearing UI copy. - L-05, L-06 and EvidenceModel 8b: view_hash cannot ride into the entry — the POST discards its body by design — so Stage 1 correlates by (approval_id, subject, approved_at). DoD-3 is satisfied by the triple, not by a stored hash. PRD open question 1 is answered by construction: there is no inbox endpoint and there will not be one, so the approvals-inbox shape is foreclosed upstream. 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:20:13 +02:00
`approval-engine`'s verifier accepts the issued token on both routes.
`trace: approval-engine docs/approver-surface-requirements.md §1-2; DoD-2`
*Correction from the first draft:* this requirement previously listed only
`approval:approve`. That was wrong and would have shipped a surface able to
submit an entry it was never able to display — `key-cape` specified the human
client as `[openid, approval:approve]`. Raised as `approval-engine` open
question A; see PR-05.
**PR-04 [rev-1] — `assurance` carries authentication facts in a documented
shape.**
`approval-engine` persists the token's `assurance` claim verbatim into the
approver entry and interprets nothing. It is therefore the **only** place where
"this approval was bound under MFA" survives into the approval record. An empty
object `{}` is *accepted* by the engine and evidences nothing.
The shape must carry, at minimum: authentication method, `acr`/`amr`, and
`auth_time`. `informed-decision` and `key-cape` own it between them.
*Pass:* a documented `assurance` shape exists and is agreed with `key-cape`; an
entry produced by this surface allows MFA to be established from the approval
record alone.
`trace: approval-engine §4 open question B and its 2026-09-09 correction`
**PR-05 [rev-1] — Entitlement to view is checked before rendering.**
`approval-engine` returning `200` is **not** a statement that this human is
entitled to see the approval — it never answers "may this actor do X". The
surface owes `access-engine` a check before rendering an approval to a person.
Consuming a decision is not rendering one: this requirement does not make the
surface a PDP, and satisfying it must not be done by local judgment.
*Pass:* no approval is rendered without a decision obtained from
`access-engine`; no local rule substitutes for it when `access-engine` is
unavailable (fail closed — see PR-06).
`trace: approval-engine §3; DoD-8; W-"It starts deciding"`
**PR-06 [rev-1] — Engine responses map to the vocabulary, and are not swallowed.**
| Status | Reason | Required surface behaviour |
| --- | --- | --- |
| 401 | `unauthenticated` | re-authenticate; never retry blindly |
| 403 | `forbidden` | wrong scope/tenant; surface as a boundary error, not a decline |
| 404 | `not_found` | no such approval in this tenant |
| 409 | `duplicate_approver` | **render as SUCCESS.** This subject already has an entry; the first stands |
| 409 | `conflict` | approval is terminal (revoked/superseded/consumed/expired); the act is no longer available |
| 422 | `unprocessable` | malformed request — a defect in this surface |
| 503 | `store_unavailable` | **fail closed.** Never present success |
A browser double-submit producing `duplicate_approver` is routine; rendering it
as a failure is a lie to the approver.
*Pass:* each row has a test; `duplicate_approver` produces a success state and
`503` produces no success state under any timing.
`trace: approval-engine §7`
**PR-07 [rev-1] — `approved` is never rendered as permission to act.**
`approved` is a state of an object. `approval-engine` actively refuses to
serialize a decision (`_assert_not_decision`). A surface that renders "approved"
as "you may now do the thing" has re-implemented a PDP in the browser.
Approval status is read from the response body, never inferred from a `200`.
*Pass:* no copy or affordance in the surface presents approval status as
authorization; status is read from the response object.
`trace: approval-engine §7; W-"It starts deciding"`
Add PRD and Use Case Catalog; file the gate-house request (T02-T04) T02 — intake INFD-IN-0001 filed with gate-house and messages sent to gate-house, key-cape and approval-engine. Status progress; it now waits on an external ruling. key-cape was told explicitly why T07 is not answering KEY-WP-0013-T02 yet — a placeholder callback URI would either fail closed or register an origin no component owns — and given the token shape to check now rather than at T07. T03 — ProductRequirementsDocument.md. 30 requirements, each traced to a GOAL.md DoD item, an INTENT principle or wrongness condition, a state-transition guard, or a named external contract, and each with an observable pass condition. Anti-requirements are stated as testable absences: no dwell timers, no attention analytics, no dark patterns, no auto-approval, no approval-state caching, no authorization endpoint. Four limitations are recorded up front, including that escalate without a mandate graph is forwarding and that view_hash is computed by the renderer. T04 — UseCaseCatalog.md. L0-L5 with counterparties, plus ten negative cases bound to guards and isolation vectors. Each case records the constraint it places on the shared schema, so scale invariance is testable rather than asserted. Closes with the four changes that would fork the object. T05 and T07 remain gated on the ruling. T06 is independent and is next. 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:11:36 +02:00
**PR-03 — The surface displays which identity and which scope are being used.**
Before any binding act, the approver sees the identity they are acting as and
the tenant/scope they are acting in, as committed values — not as inferred
context.
*Pass:* both values appear in the rendered binding document and therefore in
`view_hash`.
`trace: P-11, EvidenceModel binding slice`
### 3.2 Presentation
Apply GH-DEC-2026-013 and GH-DEC-2026-014; close INFD-IN-0002 Two rulings landed and both corrected something. GH-DEC-2026-013 accepted our binding-versus-awareness argument, wrote it into the record as its §6, and did not change the outcome — it sharpened the defect. Two different facts share one field named tenant: the act-scope, a property of the act that our binding slice commits, and the principal's membership, a property of the person that approval-engine exact-matches. Gate House's correction stands: 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 — binding.target IS the act-scope and is inside view_hash — so no field was added, only a statement (PR-08) and a provenance record (PR-09), since key-cape emits tenant as a bare string. key-cape had already implemented registration-bound tenancy on 2026-09-09, correct under both candidate rulings, so the fail-closed-at-first-use risk that made us withhold the client strings was already retired. IN-0002 closed. The one remaining input to T07 is the deployed origin. GH-DEC-2026-014 granted commitment-only evidence and bounded it. It satisfies non-alteration and NOT reconstructability, and must not be described otherwise anywhere. It also corrected our wording of the gap: we wrote that it leaves us able to erase the content, which understates 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. Limit 3's condition reduced, not removed. The grant carries a condition we did not propose and would not have thought of: the path must assert that committed content exists and where custody sits, so non-production is a finding attributable to the custodian rather than an unremarkable blank. A commitment with no assertion that something is being committed to is indistinguishable from a commitment to nothing. Booked as PR-53, and marked not-a-reversal-candidate. Recorded the meta-rule Gate House named, now in its third setting here: unknown versus absent in the stance map, directory-asserted versus registration-supplied in the tenant claim, erased versus never held in the evidence path. Wherever a system reaches one appearance by two routes, the record must say which route. 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-10 07:57:57 +02:00
**PR-08 [rev-2] — The act-scope is committed; the membership claim is not
borrowed to stand in for it.**
`binding.target` — the tenant, environment and system being entered — is the
authoritative act-scope and is inside `view_hash`. The token's `tenant` claim is
a membership fact about the *principal* and is never used as the act-scope.
*Pass:* no code path derives `binding.target` from the token's `tenant` claim;
changing `binding.target` changes `view_hash` (existing isolation vector 3).
`trace: GH-DEC-2026-013 §5; EvidenceModel §8c`
**PR-09 [rev-2] — The `tenant` claim is stored with its provenance.**
`key-cape` emits `tenant` as a bare string, so a consumer cannot distinguish a
tenant the *directory asserted about the person* from one a *registration
supplied about the client they came through*. Until the claim carries its own
provenance, this surface records which route the value arrived by.
This surface must not declare a tenant the directory record does not carry
without saying so in the record — doing so asserts a fact about a person on this
registration's authority, which `GH-DEC-2026-013` permits only as a bounded gap.
*Pass:* every stored tenant value carries a provenance marker; a value with an
undifferentiated or absent provenance is a validation failure, not a default.
`trace: GH-DEC-2026-013 §5; key-cape 329e48f`
Apply GH-DEC-2026-015/016 and the audit-core registration; both blockers cleared Origin and evidence path both landed today. T07 origin: railiance-apps deployed decisions.coulomb.social and corrected the hostname in this repo — not the decide.coulomb.social this workplan proposed. Verified here rather than taken on report: both paths 200, TLS verify 0, Let's Encrypt cert valid to 2026-12-09. T08: audit-core registered the source with every field as proposed and landed the detection half. INFD-IN-0003 closed. Their refinements booked — reconciliation on the high-volume class too, since rate detects a stream stopping but never a stream missing the particular renders that mattered, which is exactly our threat model; and PR-12, the custody locator must be a stable non-secret identifier because redact scans data and an existence declaration arriving without its pointer looks complete while being useless. INFD-IN-0004 ruled as GH-DEC-2026-015: gate-house reversed itself and nesting is permitted for this pair. The decisive ground was not the cycle argument we led with — our binding slice canonicalizes principal and target, two of the five digest fields, so co-reference left us performing a partial recomputation of one act in a second vocabulary, closer to the translation R3 forbade than nesting is. Our ordering objection was withdrawn as mistaken. The permission is conditioned and NOT ACTIVE until approval-engine states its presentation exclusion as normative and tested. layer.yaml is deliberately unchanged and carries nesting_permission_active false — we do not activate on our own initiative. GH-DEC-2026-016 ruled NC-03. Its §5 is live rather than hypothetical and is booked as PR-11: principal_type: human is a property of the client registration, structurally the same shape as the gap-route tenant, so a human-in-the-loop control must not be discharged on it as verified humanity. T07 stays progress: the submission to key-cape is written but unsent, blocked by the local permission classifier rather than by any repository. 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-10 19:19:10 +02:00
**PR-11 [rev-3] — A human-in-the-loop control is never discharged on an
unverified assertion of humanity.**
`GH-DEC-2026-016` requires that where an approval is *declared* as discharging a
human-in-the-loop control, the approver must be a human principal and
`approval-engine` must refuse at bind time. Its §5 lands here: what makes a
principal `human` belongs to the identity layer and **inherits A-16** — if
`human` is reachable by two routes, the control must not be discharged on a
registration-supplied claim. Refusing a service principal while accepting an
unverified assertion of humanity moves the defect rather than closing it.
**This is live for us, not hypothetical.** `principal_type: human` is a property
of the *client registration*, the same shape as our registration-supplied
`tenant`. Until its provenance is distinguishable, this surface treats it as
registration-supplied and does not present it as verified humanity.
*Pass:* `principal_type` is stored with its provenance like `tenant` (PR-09); no
copy, export field or evidence record describes a bind as human-verified on the
strength of the claim alone.
`trace: GH-DEC-2026-016 §5; A-16 with the marker-independence rider; PR-09`
**PR-12 [rev-3] — The custody locator is a stable non-secret identifier.**
`audit-core` applies `secret_policy: redact`, which scans `data`. A
credentialed URL or secret-shaped path in the custody field is redacted out and
the existence declaration (PR-53) arrives without its pointer. It fails visibly
`details.redaction.paths` records it — but the declaration is then useless.
*Pass:* the custody locator is an identifier the custodian resolves, never a
credentialed URL; a redaction finding on the custody field is a build-breaking
defect, not a warning.
`trace: audit-core docs/informed-decision-source-registration.md`
Add PRD and Use Case Catalog; file the gate-house request (T02-T04) T02 — intake INFD-IN-0001 filed with gate-house and messages sent to gate-house, key-cape and approval-engine. Status progress; it now waits on an external ruling. key-cape was told explicitly why T07 is not answering KEY-WP-0013-T02 yet — a placeholder callback URI would either fail closed or register an origin no component owns — and given the token shape to check now rather than at T07. T03 — ProductRequirementsDocument.md. 30 requirements, each traced to a GOAL.md DoD item, an INTENT principle or wrongness condition, a state-transition guard, or a named external contract, and each with an observable pass condition. Anti-requirements are stated as testable absences: no dwell timers, no attention analytics, no dark patterns, no auto-approval, no approval-state caching, no authorization endpoint. Four limitations are recorded up front, including that escalate without a mandate graph is forwarding and that view_hash is computed by the renderer. T04 — UseCaseCatalog.md. L0-L5 with counterparties, plus ten negative cases bound to guards and isolation vectors. Each case records the constraint it places on the shared schema, so scale invariance is testable rather than asserted. Closes with the four changes that would fork the object. T05 and T07 remain gated on the ruling. T06 is independent and is next. 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:11:36 +02:00
**PR-10 — A memo renders question, requested act, binding level, brief and
consequences before any action control is reachable.**
*Pass:* the disposition controls are not operable until the brief region has
been rendered; a memo missing a question fails validation and does not render.
`trace: INTENT "One question per memo"; GOAL invariant`
**PR-11 — The packet is always reachable in full.**
Highlights are an aid to attention, never a filter. Every document in the packet
is openable from the memo, and the count of packet items is visible without
interaction.
*Pass:* for a memo with *n* packet items, all *n* are reachable; no item is
hidden behind a highlight-only view.
`trace: P-8, W-"Highlights shrink the legal object"`
**PR-12 — The UI states unmistakably that the whole instrument is bound.**
Acknowledging highlights must never read as binding only the acknowledged
sections. This is a legal exposure, not a copy preference.
*Pass:* the bind control carries scope language naming the whole instrument;
reviewed and signed off explicitly as a requirement, not left to implementation.
`trace: P-8, W-"Highlights shrink the legal object"; exploration open question 5`
Revise specs against approval-engine's approver-surface requirements approval-engine replied to INFD-IN-0001 with docs/approver-surface-requirements.md (31da1af, 5203f46) and two corrections. Several of my requirements were wrong or incomplete; revised rather than appended to. Corrected: - PR-02 listed only approval:approve. Wrong — the surface also needs approval:read to fetch what it renders. As drafted it would have shipped a client able to submit an entry it could never display. That changes a registration key-cape has already implemented, so it is their call (open question A). - NC-03 implied approval-engine refuses non-human approver entries. It does not; only /consume is principal-restricted, and the operator service client holds approval:approve. Enforcement of "humans bind, agents draft" is therefore ours alone, and is auditable via schema v4's entries[].principal_type — never from the shape of subject_id. Added: - PR-04 assurance shape. approval-engine persists it verbatim and accepts an empty object, so it is the only place MFA survives into the approval record. Needs auth method, acr/amr, auth_time, agreed with key-cape. - PR-05 entitlement. A 200 from the engine is not permission to view; we owe access-engine a check before rendering. Consuming a decision, not making one. - PR-06 response mapping, including 409 duplicate_approver rendered as SUCCESS (a browser double-submit is routine and the first entry stands) and 503 as fail-closed. - PR-07 and a matching INTENT wrongness condition: never render `approved` as permission to act. That is a PDP in the browser wearing UI copy. - L-05, L-06 and EvidenceModel 8b: view_hash cannot ride into the entry — the POST discards its body by design — so Stage 1 correlates by (approval_id, subject, approved_at). DoD-3 is satisfied by the triple, not by a stored hash. PRD open question 1 is answered by construction: there is no inbox endpoint and there will not be one, so the approvals-inbox shape is foreclosed upstream. 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:20:13 +02:00
**PR-13 [rev-1] — Every render creates a presentation record.**
Add PRD and Use Case Catalog; file the gate-house request (T02-T04) T02 — intake INFD-IN-0001 filed with gate-house and messages sent to gate-house, key-cape and approval-engine. Status progress; it now waits on an external ruling. key-cape was told explicitly why T07 is not answering KEY-WP-0013-T02 yet — a placeholder callback URI would either fail closed or register an origin no component owns — and given the token shape to check now rather than at T07. T03 — ProductRequirementsDocument.md. 30 requirements, each traced to a GOAL.md DoD item, an INTENT principle or wrongness condition, a state-transition guard, or a named external contract, and each with an observable pass condition. Anti-requirements are stated as testable absences: no dwell timers, no attention analytics, no dark patterns, no auto-approval, no approval-state caching, no authorization endpoint. Four limitations are recorded up front, including that escalate without a mandate graph is forwarding and that view_hash is computed by the renderer. T04 — UseCaseCatalog.md. L0-L5 with counterparties, plus ten negative cases bound to guards and isolation vectors. Each case records the constraint it places on the shared schema, so scale invariance is testable rather than asserted. Closes with the four changes that would fork the object. T05 and T07 remain gated on the ruling. T06 is independent and is next. 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:11:36 +02:00
Carrying memo id, memo version, locale, UI release, timestamp, principal, and
the computed `view_hash` and `awareness_hash`.
*Pass:* rendering a memo twice creates two presentation records; a disposition
references exactly one.
`trace: DoD-3, DoD-5, guard G_PRES`
Revise specs against approval-engine's approver-surface requirements approval-engine replied to INFD-IN-0001 with docs/approver-surface-requirements.md (31da1af, 5203f46) and two corrections. Several of my requirements were wrong or incomplete; revised rather than appended to. Corrected: - PR-02 listed only approval:approve. Wrong — the surface also needs approval:read to fetch what it renders. As drafted it would have shipped a client able to submit an entry it could never display. That changes a registration key-cape has already implemented, so it is their call (open question A). - NC-03 implied approval-engine refuses non-human approver entries. It does not; only /consume is principal-restricted, and the operator service client holds approval:approve. Enforcement of "humans bind, agents draft" is therefore ours alone, and is auditable via schema v4's entries[].principal_type — never from the shape of subject_id. Added: - PR-04 assurance shape. approval-engine persists it verbatim and accepts an empty object, so it is the only place MFA survives into the approval record. Needs auth method, acr/amr, auth_time, agreed with key-cape. - PR-05 entitlement. A 200 from the engine is not permission to view; we owe access-engine a check before rendering. Consuming a decision, not making one. - PR-06 response mapping, including 409 duplicate_approver rendered as SUCCESS (a browser double-submit is routine and the first entry stands) and 503 as fail-closed. - PR-07 and a matching INTENT wrongness condition: never render `approved` as permission to act. That is a PDP in the browser wearing UI copy. - L-05, L-06 and EvidenceModel 8b: view_hash cannot ride into the entry — the POST discards its body by design — so Stage 1 correlates by (approval_id, subject, approved_at). DoD-3 is satisfied by the triple, not by a stored hash. PRD open question 1 is answered by construction: there is no inbox endpoint and there will not be one, so the approvals-inbox shape is foreclosed upstream. 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:20:13 +02:00
*Constraint discovered 2026-09-09:* `POST /entries` **reads the request body and
discards it**. Approver identity, `assurance` and `evidence_ref` come only from
the verified token, deliberately — a caller-supplied approver field is
provenance, not evidence. So `view_hash` cannot ride into the approval entry.
Stage 1 therefore holds the presentation record here, emits it to `audit-core`,
and correlates to the entry by **`(approval_id, subject, approved_at)`**, all
three returned by the engine after a successful entry. Putting the hash *inside*
the approval object is a change request against `approval-engine` and is
doctrine first — it goes to `gate-house`. `GOAL.md` DoD-3 is satisfied by the
correlation triple, not by a hash stored on the entry.
Add PRD and Use Case Catalog; file the gate-house request (T02-T04) T02 — intake INFD-IN-0001 filed with gate-house and messages sent to gate-house, key-cape and approval-engine. Status progress; it now waits on an external ruling. key-cape was told explicitly why T07 is not answering KEY-WP-0013-T02 yet — a placeholder callback URI would either fail closed or register an origin no component owns — and given the token shape to check now rather than at T07. T03 — ProductRequirementsDocument.md. 30 requirements, each traced to a GOAL.md DoD item, an INTENT principle or wrongness condition, a state-transition guard, or a named external contract, and each with an observable pass condition. Anti-requirements are stated as testable absences: no dwell timers, no attention analytics, no dark patterns, no auto-approval, no approval-state caching, no authorization endpoint. Four limitations are recorded up front, including that escalate without a mandate graph is forwarding and that view_hash is computed by the renderer. T04 — UseCaseCatalog.md. L0-L5 with counterparties, plus ten negative cases bound to guards and isolation vectors. Each case records the constraint it places on the shared schema, so scale invariance is testable rather than asserted. Closes with the four changes that would fork the object. T05 and T07 remain gated on the ruling. T06 is independent and is next. 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:11:36 +02:00
**PR-14 — A disposition binds to a presentation of the same memo version.**
A bind attempt against a stale version is refused, not silently upgraded.
*Pass:* incrementing the memo version invalidates outstanding presentations; a
bind against the old presentation returns a version conflict.
`trace: guard G_PRES; INTENT invariant "Binding acts reference a presentation of
the same version"`
### 3.3 The acknowledgment gate
**PR-20 — Required highlights must be acknowledged before a binding verb.**
Where `highlights[].required_ack` is true, `accept`, `decline` on a bind step,
and `acknowledge` are unavailable until every required highlight id is in the
presentation's acked set.
*Pass:* a bind attempt with an unacknowledged required highlight fails closed,
creates no disposition, and returns an error naming the outstanding ids.
`trace: DoD-4, guard G_ACK`
**PR-21 — Acknowledgment is an explicit act, never inferred.**
Scroll position, time on page, focus events and viewport intersection MUST NOT
acknowledge a highlight.
*Pass:* no acknowledgment is recorded by any event other than a deliberate
control activation.
`trace: P-12, INTENT "Informedness without surveillance"`
### 3.4 Disposition vocabulary
**PR-30 — The surface implements the full verb set for the step kind, not
approve/reject.**
Overlay verbs `comment` and `discuss` are available whenever the memo status
allows. `return`, `forward` and `escalate` are available to the assignee on
every step kind. `accept` and `decline` follow the legality table.
*Pass:* every verb in `state-transitions.md` §B2 legal for the step kind is
reachable in the UI for that step.
`trace: DoD-6, INTENT §"The Disposition Vocabulary"`
**PR-31 — `accept` is not offered on a weak step.**
On `inform`, `comment`, `review` and `acknowledge` steps, `accept` is absent —
not present-and-disabled. Kenntnisnahme is not approval.
*Pass:* the control does not exist in the rendered UI for weak steps; an API
call attempting it is refused.
`trace: state-transitions §B2 ("accept: —" on weak); INTENT design principle 6`
**PR-32 — `return` carries at least one coded reason.**
Free text alone is not a return. Return is a first-class success path and must
be as easy to reach as accept.
*Pass:* a return with zero coded reasons is refused (guard `G_REASONS`); the
return control has equal visual weight to accept.
`trace: DoD-6, P-4, guard G_REASONS`
**PR-33 — `return` and `decline` are distinguishable in the record.**
A memo sent back for improvement and a memo refused are different outcomes and
must not collapse to one status.
*Pass:* `returned` and `completed/declined` are distinct in the stored record
and in the export.
`trace: DoD-6, GOAL definition of done 6`
**PR-34 — `discuss` keeps the dialogue on the object.**
A question to the requester creates a thread entry on the memo. The surface
provides no "email the requester" affordance.
*Pass:* no outbound side-channel exists; the thread is part of the exported
evidence.
`trace: P-5`
**PR-35 — `escalate` requires a strictly higher mandate target.**
Where the target's mandate is not strictly higher for this act, the verb is
`forward`, not `escalate`.
*Pass:* guard `G_ESC` is evaluated; a same-level target is refused as an
escalation.
`trace: guard G_ESC`
*Known Stage 1 limitation:* without the mandate graph, `G_ESC` cannot be fully
evaluated. Stage 1 MUST record the distinction the approver asserted rather than
silently treating escalate as forward. See §6.
### 3.5 The binding / awareness split
**PR-40 — Awareness fields never enter `view_hash`.**
Proposed role, other available roles, other tenants, and last-session summary
are rendered on the same surface, hashed into `awareness_hash`, and excluded
from the signed binding document.
*Pass:* isolation vector 2 — editing an awareness field does not change
`view_hash` — is green in CI.
`trace: DoD-4, P-11, W-"Awareness leaks into the signature", EvidenceModel`
**PR-41 — Awareness is visually distinguished from binding.**
The approver can tell, without reading documentation, which part of the surface
they are committing to and which part is orientation.
*Pass:* the two regions are separately labelled; the labelling is part of the
reviewed design, not incidental styling.
`trace: P-11`
**PR-42 — Promotion is explicit and versions the memo.**
Moving a field from awareness into binding creates a new memo version and
invalidates outstanding presentations.
*Pass:* guard `G_PROMOTE`; a presentation taken before promotion cannot be bound
against after it.
`trace: guard G_PROMOTE, INTENT invariant on `awareness_promoted``
### 3.6 Evidence
**PR-50 — An evidence bundle exports and verifies offline.**
Containing the memo, its versions, the presentations with both hashes, the
dispositions, the thread, and the hash chain.
*Pass:* an exported bundle verifies with no network access; verification detects
a mutated field.
`trace: DoD-7, P-7`
**PR-51 — The export is the evidence, not a screenshot.**
No requirement is satisfied by a rendered image of the surface.
*Pass:* the bundle is structured data; the canonicalizer reproduces `view_hash`
from it.
`trace: P-7`
**PR-52 — The surface states the bound of its own guarantee.**
Documentation and the export state that attestation covers accident and later
tampering, not a compromised surface presenting X and attesting Y.
*Pass:* the residual appears in `EvidenceModel.md` and in the bundle metadata.
`trace: gate-house decision request §3; approval-engine's equivalent residual`
Apply GH-DEC-2026-013 and GH-DEC-2026-014; close INFD-IN-0002 Two rulings landed and both corrected something. GH-DEC-2026-013 accepted our binding-versus-awareness argument, wrote it into the record as its §6, and did not change the outcome — it sharpened the defect. Two different facts share one field named tenant: the act-scope, a property of the act that our binding slice commits, and the principal's membership, a property of the person that approval-engine exact-matches. Gate House's correction stands: 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 — binding.target IS the act-scope and is inside view_hash — so no field was added, only a statement (PR-08) and a provenance record (PR-09), since key-cape emits tenant as a bare string. key-cape had already implemented registration-bound tenancy on 2026-09-09, correct under both candidate rulings, so the fail-closed-at-first-use risk that made us withhold the client strings was already retired. IN-0002 closed. The one remaining input to T07 is the deployed origin. GH-DEC-2026-014 granted commitment-only evidence and bounded it. It satisfies non-alteration and NOT reconstructability, and must not be described otherwise anywhere. It also corrected our wording of the gap: we wrote that it leaves us able to erase the content, which understates 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. Limit 3's condition reduced, not removed. The grant carries a condition we did not propose and would not have thought of: the path must assert that committed content exists and where custody sits, so non-production is a finding attributable to the custodian rather than an unremarkable blank. A commitment with no assertion that something is being committed to is indistinguishable from a commitment to nothing. Booked as PR-53, and marked not-a-reversal-candidate. Recorded the meta-rule Gate House named, now in its third setting here: unknown versus absent in the stance map, directory-asserted versus registration-supplied in the tenant claim, erased versus never held in the evidence path. Wherever a system reaches one appearance by two routes, the record must say which route. 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-10 07:57:57 +02:00
**PR-53 [rev-2] — Emitted evidence asserts that content exists and where custody
sits.**
Commitment-only emission carries, alongside the hashes, an assertion that the
committed content exists and the custodian holding it, so that failure to
produce at retrieval is a **conformance failure attributable to the custodian**
rather than an unremarkable blank. Without it, a reviewer cannot distinguish
*erased*, *withheld*, *lost* and *never held*.
*Pass:* every emitted commitment carries `content_exists` and `custody`; a
retrieval failure produces a finding naming the custodian.
`trace: GH-DEC-2026-014 §4 — a granting condition, not a reversal candidate`
**PR-54 [rev-2] — Commitment-only evidence is never described as
reconstructability.**
It satisfies **non-alteration**. It does not carry what it commits to, and
establishes what was committed to only conditionally — if a document is later
produced, whether it is the one.
*Pass:* no document, export field or conformance claim in this repository
describes the independent evidence path as reconstructing content.
`trace: GH-DEC-2026-014`
Add PRD and Use Case Catalog; file the gate-house request (T02-T04) T02 — intake INFD-IN-0001 filed with gate-house and messages sent to gate-house, key-cape and approval-engine. Status progress; it now waits on an external ruling. key-cape was told explicitly why T07 is not answering KEY-WP-0013-T02 yet — a placeholder callback URI would either fail closed or register an origin no component owns — and given the token shape to check now rather than at T07. T03 — ProductRequirementsDocument.md. 30 requirements, each traced to a GOAL.md DoD item, an INTENT principle or wrongness condition, a state-transition guard, or a named external contract, and each with an observable pass condition. Anti-requirements are stated as testable absences: no dwell timers, no attention analytics, no dark patterns, no auto-approval, no approval-state caching, no authorization endpoint. Four limitations are recorded up front, including that escalate without a mandate graph is forwarding and that view_hash is computed by the renderer. T04 — UseCaseCatalog.md. L0-L5 with counterparties, plus ten negative cases bound to guards and isolation vectors. Each case records the constraint it places on the shared schema, so scale invariance is testable rather than asserted. Closes with the four changes that would fork the object. T05 and T07 remain gated on the ruling. T06 is independent and is next. 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:11:36 +02:00
### 3.7 Locale and accessibility
**PR-60 — German and English, with German as a first-class source language.**
The *Umlaufmappe* vocabulary — Kenntnisnahme, Mitzeichnung, Schlusszeichnung,
Rücksprache, Rückgabe — is the domain vocabulary, not a translation of English
product terms.
*Pass:* both locales render every verb; locale is part of the presentation
record and therefore of `view_hash`.
`trace: DoD-6, INTENT §"The Disposition Vocabulary"; exploration open question 7`
**PR-61 — Locale changes the memo version's presentation, not its meaning.**
Because locale enters `view_hash`, a memo read in German and one read in English
produce different presentations of the same binding. This is intended and must
be documented, not smoothed over.
*Pass:* the two presentations differ in `view_hash` and reference the same memo
version.
`trace: canonicalize.md allow-list`
**PR-62 — Keyboard-operable and screen-reader-navigable throughout.**
An approver using assistive technology can read the brief, open packet items,
acknowledge highlights and bind, without a mouse.
*Pass:* every disposition control and every acknowledgment control is reachable
and operable by keyboard; the acknowledgment state is programmatically
determinable.
`trace: P-8; PR-21 (acknowledgment must remain explicit under AT too)`
---
## 4. Anti-requirements
These are requirements *not* to build things a reasonable implementer would
otherwise add. Each is testable as an absence.
**PR-70 — No dwell timers.** Minimum reading time, "you must spend 30 seconds on
this page", and countdown-gated buttons are forbidden.
**PR-71 — No keystroke, mouse-movement or attention analytics.** Informedness is
evidenced by the hash of the presented view plus explicit acknowledgment, never
by behavioural telemetry.
**PR-72 — No dark patterns.** Accept is not visually privileged over return.
Decline is not hidden behind a secondary menu. No pre-checked acknowledgments.
No confirm-shaming copy.
Revise specs against approval-engine's approver-surface requirements approval-engine replied to INFD-IN-0001 with docs/approver-surface-requirements.md (31da1af, 5203f46) and two corrections. Several of my requirements were wrong or incomplete; revised rather than appended to. Corrected: - PR-02 listed only approval:approve. Wrong — the surface also needs approval:read to fetch what it renders. As drafted it would have shipped a client able to submit an entry it could never display. That changes a registration key-cape has already implemented, so it is their call (open question A). - NC-03 implied approval-engine refuses non-human approver entries. It does not; only /consume is principal-restricted, and the operator service client holds approval:approve. Enforcement of "humans bind, agents draft" is therefore ours alone, and is auditable via schema v4's entries[].principal_type — never from the shape of subject_id. Added: - PR-04 assurance shape. approval-engine persists it verbatim and accepts an empty object, so it is the only place MFA survives into the approval record. Needs auth method, acr/amr, auth_time, agreed with key-cape. - PR-05 entitlement. A 200 from the engine is not permission to view; we owe access-engine a check before rendering. Consuming a decision, not making one. - PR-06 response mapping, including 409 duplicate_approver rendered as SUCCESS (a browser double-submit is routine and the first entry stands) and 503 as fail-closed. - PR-07 and a matching INTENT wrongness condition: never render `approved` as permission to act. That is a PDP in the browser wearing UI copy. - L-05, L-06 and EvidenceModel 8b: view_hash cannot ride into the entry — the POST discards its body by design — so Stage 1 correlates by (approval_id, subject, approved_at). DoD-3 is satisfied by the triple, not by a stored hash. PRD open question 1 is answered by construction: there is no inbox endpoint and there will not be one, so the approvals-inbox shape is foreclosed upstream. 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:20:13 +02:00
**PR-73 [rev-1] — No auto-approval, no delegation-to-agent, no "approve all".**
Bulk selection may exist for reading; it must not exist for binding.
*Correction:* the first draft assumed `approval-engine` would refuse a non-human
approver entry. It does not — only `/consume` is restricted by principal type,
and the `approval-engine-operator` service client holds `approval:approve`. So a
non-human principal **can** supply approver evidence today. Whether it should is
`gate-house` doctrine. Enforcement of "humans bind, agents draft" is therefore
this surface's own obligation, and is *auditable* via schema v4's
`entries[].principal_type`, which records the verified value from the token.
Read that field; **never** infer it from the shape of `subject_id`, which is a
naming convention, not a verified claim. Entries written before v4 are `null`
and must not be read as `human`.
Add PRD and Use Case Catalog; file the gate-house request (T02-T04) T02 — intake INFD-IN-0001 filed with gate-house and messages sent to gate-house, key-cape and approval-engine. Status progress; it now waits on an external ruling. key-cape was told explicitly why T07 is not answering KEY-WP-0013-T02 yet — a placeholder callback URI would either fail closed or register an origin no component owns — and given the token shape to check now rather than at T07. T03 — ProductRequirementsDocument.md. 30 requirements, each traced to a GOAL.md DoD item, an INTENT principle or wrongness condition, a state-transition guard, or a named external contract, and each with an observable pass condition. Anti-requirements are stated as testable absences: no dwell timers, no attention analytics, no dark patterns, no auto-approval, no approval-state caching, no authorization endpoint. Four limitations are recorded up front, including that escalate without a mandate graph is forwarding and that view_hash is computed by the renderer. T04 — UseCaseCatalog.md. L0-L5 with counterparties, plus ten negative cases bound to guards and isolation vectors. Each case records the constraint it places on the shared schema, so scale invariance is testable rather than asserted. Closes with the four changes that would fork the object. T05 and T07 remain gated on the ruling. T06 is independent and is next. 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:11:36 +02:00
**PR-74 — No approval-state caching.** The surface never stores or serves whether
an approval is valid. It asks `approval-engine`.
`trace: W-"It reimplements approval-engine", GOAL invariant`
**PR-75 — No authorization endpoint.** Nothing in this repository answers "may
this actor do X".
`trace: DoD-8, W-"It starts deciding"`
*Pass for §4:* each is a review checklist item and, where mechanically checkable,
a test asserting the absence.
---
## 5. Non-requirements for Stage 1
Not deficiencies — deliberate exclusions carried from `GOAL.md`:
- Memo authoring. Memos arrive from `approval-engine`.
- The mandate graph.
- L4/L5, QES, QTSP, qualified archival retention.
- Multi-party parallel Mitzeichnung.
- EUDI Wallet / eID identity import.
- Notification transport. A memo may be announced elsewhere; the dialogue stays
on the object.
- Mobile-native clients. Responsive web only.
- Offline operation.
---
## 6. Known limitations, stated up front
**L-01 — `escalate` without a mandate graph is forwarding.** Guard `G_ESC`
requires knowing that the target's mandate is strictly higher. Stage 1 has no
such map. The surface records the approver's asserted intent to escalate and
marks it unverified rather than either blocking the verb or pretending the guard
passed. `GOAL.md` records this as accepted; `EvidenceModel.md` must state that
an escalation in a Stage 1 bundle is an assertion, not a verified claim.
**L-02 — `view_hash` is computed by the renderer.** A compromised surface can
present one thing and attest another. Not closed, not closable at this layer,
and not claimed to be closed.
**L-03 — Binding level is organizational only.** Stage 1 produces no AES or QES
signature. `binding_level` is carried in the object and in `view_hash` so that
higher levels are expressible without a schema change, but nothing above
organizational is implemented or claimed.
**L-04 — `view_hash` versus `approval-engine`'s binding digest is unresolved.**
Filed as `INFD-IN-0001` R3. Until ruled, the surface computes `view_hash` and
does not assert any relationship to the approval claim's digest.
Revise specs against approval-engine's approver-surface requirements approval-engine replied to INFD-IN-0001 with docs/approver-surface-requirements.md (31da1af, 5203f46) and two corrections. Several of my requirements were wrong or incomplete; revised rather than appended to. Corrected: - PR-02 listed only approval:approve. Wrong — the surface also needs approval:read to fetch what it renders. As drafted it would have shipped a client able to submit an entry it could never display. That changes a registration key-cape has already implemented, so it is their call (open question A). - NC-03 implied approval-engine refuses non-human approver entries. It does not; only /consume is principal-restricted, and the operator service client holds approval:approve. Enforcement of "humans bind, agents draft" is therefore ours alone, and is auditable via schema v4's entries[].principal_type — never from the shape of subject_id. Added: - PR-04 assurance shape. approval-engine persists it verbatim and accepts an empty object, so it is the only place MFA survives into the approval record. Needs auth method, acr/amr, auth_time, agreed with key-cape. - PR-05 entitlement. A 200 from the engine is not permission to view; we owe access-engine a check before rendering. Consuming a decision, not making one. - PR-06 response mapping, including 409 duplicate_approver rendered as SUCCESS (a browser double-submit is routine and the first entry stands) and 503 as fail-closed. - PR-07 and a matching INTENT wrongness condition: never render `approved` as permission to act. That is a PDP in the browser wearing UI copy. - L-05, L-06 and EvidenceModel 8b: view_hash cannot ride into the entry — the POST discards its body by design — so Stage 1 correlates by (approval_id, subject, approved_at). DoD-3 is satisfied by the triple, not by a stored hash. PRD open question 1 is answered by construction: there is no inbox endpoint and there will not be one, so the approvals-inbox shape is foreclosed upstream. 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:20:13 +02:00
**L-05 — `view_hash` is not inside the approval entry.** By the engine's
deliberate design (PR-13). Correlation is by `(approval_id, subject,
approved_at)`. An auditor holding only the approval object cannot reach the
presentation without also holding this surface's record or `audit-core`.
**L-06 — A memo `return` is invisible to `approval-engine`.** `accept` is the
only disposition that becomes a `POST …/entries`. `return`, `discuss`,
`escalate` and the rest are dispositions of a *memo* and are not represented in
that engine at all. Correct separation, but it means the approval object alone
does not show that a human sent the question back.
Add PRD and Use Case Catalog; file the gate-house request (T02-T04) T02 — intake INFD-IN-0001 filed with gate-house and messages sent to gate-house, key-cape and approval-engine. Status progress; it now waits on an external ruling. key-cape was told explicitly why T07 is not answering KEY-WP-0013-T02 yet — a placeholder callback URI would either fail closed or register an origin no component owns — and given the token shape to check now rather than at T07. T03 — ProductRequirementsDocument.md. 30 requirements, each traced to a GOAL.md DoD item, an INTENT principle or wrongness condition, a state-transition guard, or a named external contract, and each with an observable pass condition. Anti-requirements are stated as testable absences: no dwell timers, no attention analytics, no dark patterns, no auto-approval, no approval-state caching, no authorization endpoint. Four limitations are recorded up front, including that escalate without a mandate graph is forwarding and that view_hash is computed by the renderer. T04 — UseCaseCatalog.md. L0-L5 with counterparties, plus ten negative cases bound to guards and isolation vectors. Each case records the constraint it places on the shared schema, so scale invariance is testable rather than asserted. Closes with the four changes that would fork the object. T05 and T07 remain gated on the ruling. T06 is independent and is next. 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:11:36 +02:00
---
## 7. Open questions for review
Revise specs against approval-engine's approver-surface requirements approval-engine replied to INFD-IN-0001 with docs/approver-surface-requirements.md (31da1af, 5203f46) and two corrections. Several of my requirements were wrong or incomplete; revised rather than appended to. Corrected: - PR-02 listed only approval:approve. Wrong — the surface also needs approval:read to fetch what it renders. As drafted it would have shipped a client able to submit an entry it could never display. That changes a registration key-cape has already implemented, so it is their call (open question A). - NC-03 implied approval-engine refuses non-human approver entries. It does not; only /consume is principal-restricted, and the operator service client holds approval:approve. Enforcement of "humans bind, agents draft" is therefore ours alone, and is auditable via schema v4's entries[].principal_type — never from the shape of subject_id. Added: - PR-04 assurance shape. approval-engine persists it verbatim and accepts an empty object, so it is the only place MFA survives into the approval record. Needs auth method, acr/amr, auth_time, agreed with key-cape. - PR-05 entitlement. A 200 from the engine is not permission to view; we owe access-engine a check before rendering. Consuming a decision, not making one. - PR-06 response mapping, including 409 duplicate_approver rendered as SUCCESS (a browser double-submit is routine and the first entry stands) and 503 as fail-closed. - PR-07 and a matching INTENT wrongness condition: never render `approved` as permission to act. That is a PDP in the browser wearing UI copy. - L-05, L-06 and EvidenceModel 8b: view_hash cannot ride into the entry — the POST discards its body by design — so Stage 1 correlates by (approval_id, subject, approved_at). DoD-3 is satisfied by the triple, not by a stored hash. PRD open question 1 is answered by construction: there is no inbox endpoint and there will not be one, so the approvals-inbox shape is foreclosed upstream. 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:20:13 +02:00
1. ~~Does the approver need to see other pending memos?~~ **Answered
2026-09-09 by construction.** `approval-engine` exposes get-by-id only: no
list, no search, no "awaiting me" query, and there will not be one. Approval
ids reach this surface from whatever requested the approval, never by
polling. A listing surface would itself be an authorization question and
would need scoping by something other than that engine's judgment. The
approvals-inbox shape is foreclosed upstream, which is the outcome this
repository wanted anyway.
Add PRD and Use Case Catalog; file the gate-house request (T02-T04) T02 — intake INFD-IN-0001 filed with gate-house and messages sent to gate-house, key-cape and approval-engine. Status progress; it now waits on an external ruling. key-cape was told explicitly why T07 is not answering KEY-WP-0013-T02 yet — a placeholder callback URI would either fail closed or register an origin no component owns — and given the token shape to check now rather than at T07. T03 — ProductRequirementsDocument.md. 30 requirements, each traced to a GOAL.md DoD item, an INTENT principle or wrongness condition, a state-transition guard, or a named external contract, and each with an observable pass condition. Anti-requirements are stated as testable absences: no dwell timers, no attention analytics, no dark patterns, no auto-approval, no approval-state caching, no authorization endpoint. Four limitations are recorded up front, including that escalate without a mandate graph is forwarding and that view_hash is computed by the renderer. T04 — UseCaseCatalog.md. L0-L5 with counterparties, plus ten negative cases bound to guards and isolation vectors. Each case records the constraint it places on the shared schema, so scale invariance is testable rather than asserted. Closes with the four changes that would fork the object. T05 and T07 remain gated on the ruling. T06 is independent and is next. 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:11:36 +02:00
2. What does the surface do when `approval-engine` is reachable but
`audit-core` is not? Fail closed on binding, or bind and queue? The
`ArchitectureBlueprint` must answer this per-dependency (T05).
3. Who is the requester for an `approval-engine`-originated memo, given the
engine does not model one? Likely the approval's `principal`, but this needs
confirming against the approval object.
4. Is `abstain` in Stage 1? It is in the state tables and it is real in
Mitzeichnung practice, but it has no obvious `approval-engine` counterpart.