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
333 lines
14 KiB
Markdown
333 lines
14 KiB
Markdown
# Use Case Catalog
|
|
|
|
**Repo:** informed-decision
|
|
**Workplan task:** `INFD-WP-0001-T04`
|
|
**Status:** draft, pending review
|
|
**Companion specs:** `ProductRequirementsDocument.md` (Stage 1 scope),
|
|
`ArchitectureBlueprint.md`, `EvidenceModel.md`
|
|
|
|
---
|
|
|
|
## Why this catalog exists
|
|
|
|
Scale invariance is the thesis of this repository: one object model from a
|
|
ten-second login to a multi-party instrument. A thesis stated in a vision
|
|
document is decoration. This catalog exists to make it **testable** — every case
|
|
below must map onto the single Decision Memo schema with **no level-specific
|
|
object**.
|
|
|
|
Only UC-30 is in Stage 1 build scope. The rest are here to constrain the schema
|
|
now, so it cannot fork later when they arrive.
|
|
|
|
**The test this catalog applies to every case:** can it be expressed with the
|
|
fields in `decision-memo.schema.json` plus values, or does it need a new field?
|
|
A new field is permitted only if it is also expressible for UC-00 — the login
|
|
banner, the shallowest case. A field that only makes sense at L4 is a fork.
|
|
|
|
---
|
|
|
|
## Depth spectrum overview
|
|
|
|
| Case | L | Act | Binding | Counterparty | Stage |
|
|
| --- | --- | --- | --- | --- | --- |
|
|
| UC-00 | L0 | Informed login | acknowledgment / organizational | `key-cape` | 2 |
|
|
| UC-10 | L1 | JIT privilege elevation | organizational | `key-cape`, `secrets-engine` | 2 |
|
|
| UC-20 | L2 | Formal ADR accept | organizational / AES | estate decision records | 2 |
|
|
| **UC-30** | **L3** | **Approval entry** | **organizational** | **`approval-engine`** | **1** |
|
|
| UC-40 | L4 | Contract / instrument | AES / QES | external counterparty | horizon |
|
|
| UC-50 | L5 | Multi-party instrument | QES per party | multiple | horizon |
|
|
|
|
---
|
|
|
|
## UC-00 — Informed login (L0)
|
|
|
|
**Actor:** any principal signing in.
|
|
**Trigger:** authentication at a system boundary.
|
|
**Question:** *"Sign in to Payroll-Prod as Bernd Worsch?"*
|
|
**Requested act:** `login`.
|
|
|
|
**Binding slice (signed):** which identity (ordinary vs break-glass), which
|
|
scope/tenant/system, privileges granted at the gate, monitoring and consent
|
|
terms.
|
|
|
|
**Awareness slice (shown, hashed, not signed):** the role you will wear after
|
|
login and where that default came from (`last_used` | `system_default` |
|
|
`policy` | `explicit`); other roles available inside the bound scope; other
|
|
tenants you support; last session summary.
|
|
|
|
**Verbs:** `acknowledge` (completes), `decline`, `discuss`. **Not** `accept` —
|
|
this is Kenntnisnahme.
|
|
|
|
**Post-bind:** `configure(hat)` for non-elevating roles only, while
|
|
`session.status = active`. Switching to a tenant with `requires_new_bind` opens
|
|
a **child memo**, never a silent scope change.
|
|
|
|
**Evidence:** presentation with both hashes; `session.hat_selected` events that
|
|
do not rewrite `view_hash`.
|
|
|
|
**Why it constrains the schema:** UC-00 is the shallowest case and therefore the
|
|
strictest constraint. It forced the pre-sign/post-sign split that now applies at
|
|
every level — see `INTENT.md` principle 11. Any field proposed for a deeper case
|
|
must be expressible here or be justified as genuinely level-specific.
|
|
|
|
---
|
|
|
|
## UC-10 — JIT privilege elevation (L1)
|
|
|
|
**Actor:** an operator needing elevated privilege for a bounded task.
|
|
**Question:** *"Elevate to Payroll Admin on ACME/Payroll-Prod for 60 minutes?"*
|
|
**Requested act:** `elevate`.
|
|
|
|
**Binding slice:** the privilege, the scope, the validity window, the stated
|
|
justification, the monitoring terms.
|
|
**Awareness slice:** current role, what the elevation adds, prior elevations.
|
|
|
|
**Verbs:** `acknowledge` or `accept` depending on whether the elevation is
|
|
self-service-with-record or requires a co-signer; `decline`; `escalate` where
|
|
the operator's own mandate is insufficient.
|
|
|
|
**Counterparty:** `key-cape` for identity; `secrets-engine` if the elevation
|
|
materialises credentials — which this repository never does itself.
|
|
|
|
**Constraint contributed:** an **elevating** role can never be reached by
|
|
`configure`. It requires either promotion into the binding slice with a new memo
|
|
version, or its own elevation memo. Guard `G_HAT`.
|
|
|
|
---
|
|
|
|
## UC-20 — Formal ADR accept (L2)
|
|
|
|
**Actor:** an architect or owner with the mandate to accept a decision record.
|
|
**Question:** *"Accept ADR-0042 — Adopt the local outbox pattern?"*
|
|
**Requested act:** `accept_adr`.
|
|
|
|
**Binding slice:** the ADR id and version, the decision statement, consequences,
|
|
the accepting role.
|
|
**Packet:** the ADR document, plus any superseded record.
|
|
**Highlights:** the consequences section, typically `required_ack: true`.
|
|
|
|
**Verbs:** the full set. `return` with coded reasons is the common path and is
|
|
the point — an ADR sent back for a clearer consequences section is the surface
|
|
working.
|
|
|
|
**Constraint contributed:** *"accepted" must stop being a Git status and become
|
|
an identity act.* The evidence must answer who accepted it, in what role, having
|
|
seen which version. This is the case that most directly tests whether `view_hash`
|
|
carries enough to be useful.
|
|
|
|
**Note:** the estate's own decision records (`GH-DEC-*`, `FLEX-DEC-*`,
|
|
`APPROVAL-*`) are the natural first real consumer of UC-20. Stage 2.
|
|
|
|
---
|
|
|
|
## UC-30 — Approval entry (L3) — **Stage 1**
|
|
|
|
**Actor:** a human approver holding the mandate, authenticated by `key-cape`.
|
|
**Trigger:** an approval exists in `approval-engine` in state `requested`,
|
|
awaiting an authenticated approval entry.
|
|
**Question:** *"Approve rotation of the production database credential for
|
|
task T-1183?"*
|
|
**Requested act:** `approve`.
|
|
|
|
**Binding slice:** the approval id; the action and target it authorizes; the
|
|
actor who may use it and the principal on whose behalf; the purpose; the
|
|
validity window; the approving identity and tenant.
|
|
|
|
**Awareness slice:** the approver's current role; other approvals pending for
|
|
them (orientation only — see PRD open question 1); prior related approvals.
|
|
|
|
**Packet:** the change or task the approval is for, and any policy citation
|
|
supplied by the requester.
|
|
**Highlights:** the target and the validity window, typically
|
|
`required_ack: true` — these are the two fields whose misreading is most costly.
|
|
|
|
**Verbs:** `accept` (submits the approval entry), `decline`, `return` with coded
|
|
reasons, `discuss`, `forward`, `escalate`, `comment`.
|
|
|
|
**Only `accept` reaches the engine.** `approval-engine` models entries against
|
|
an approval object and knows nothing of memo dispositions. `accept` becomes a
|
|
`POST /v1/approvals/{id}/entries`; `return`, `discuss`, `escalate` and the rest
|
|
are memo-level and must not be represented in that engine at all. This is the
|
|
correct separation, and it means the approval object alone does not show that a
|
|
human sent the question back — see `EvidenceModel.md` §8 and PRD L-06.
|
|
|
|
**Routes used:** `GET /v1/approvals/{id}` and `GET /v1/approvals/{id}/claim`
|
|
(scope `approval:read`), `POST /v1/approvals/{id}/entries` (scope
|
|
`approval:approve`). Never `/consume`. Any path containing `check` or ending in
|
|
`/authorize` returns 404 by design — there is no "may this person approve"
|
|
surface to call.
|
|
|
|
**Entitlement:** a `200` from the engine is not a statement that this human may
|
|
see the approval. `access-engine` is asked before rendering (PR-05).
|
|
|
|
**No inbox.** The engine exposes get-by-id only — no list, no search, no
|
|
"awaiting me". Approval ids arrive from whatever requested the approval.
|
|
|
|
**Boundary — the part that must not drift:**
|
|
|
|
- The surface calls `approval-engine`'s approval-entry mutation with a token
|
|
carrying scope `approval:approve`. It never mutates approval state itself.
|
|
- It never requests or performs `approval:consume`. Consumption is the PEP's,
|
|
at the moment of use, per `GH-DEC-2026-003`.
|
|
- It never caches or serves approval validity.
|
|
- It never answers whether the underlying action is permitted. That is
|
|
`access-engine`.
|
|
|
|
**Evidence:** presentation record with `view_hash`, the disposition, and the
|
|
approval entry id returned by `approval-engine`, so the entry is reconstructable
|
|
back to what the approver saw.
|
|
|
|
**Why this is Stage 1:** it is the only case currently costing another
|
|
repository progress. `key-cape`'s `KEY-WP-0013-T02` is blocked on a browser
|
|
client for exactly this flow.
|
|
|
|
---
|
|
|
|
## UC-40 — Contract or instrument (L4) — horizon
|
|
|
|
**Actor:** a signatory with commercial authority.
|
|
**Binding:** AES or QES. Identity imported from a QTSP or EUDI Wallet, never
|
|
issued here.
|
|
**Constraint contributed:** the signed attributes carry `memo_id`,
|
|
`memo_version`, `disposition.verb` and `presentation.view_hash` — and **not**
|
|
`awareness_hash` unless promotion ran. This is dynamic linking: the
|
|
human-readable content actually presented is bound into the signature.
|
|
|
|
**Open, not scoped:** retention and qualified archive
|
|
(exploration open question 6).
|
|
|
|
---
|
|
|
|
## UC-50 — Multi-party international instrument (L5) — horizon
|
|
|
|
**Actors:** several parties, each with their own QTSP and jurisdiction.
|
|
**Constraint contributed:** parallel Mitzeichnung, per-party signature levels,
|
|
and per-party presentations of the same memo version. The schema must already
|
|
tolerate `parties[]` and multiple presentations per version — which it does.
|
|
|
|
Explicitly not v1, and named here only so nothing shipped earlier forecloses it.
|
|
|
|
---
|
|
|
|
## Negative cases
|
|
|
|
These are as load-bearing as the positive ones. Each protects a named invariant
|
|
and each must be a test.
|
|
|
|
### NC-01 — `accept` on a Kenntnisnahme step
|
|
|
|
**Attempt:** the approver presses accept on an `inform`/`review`/`acknowledge`
|
|
step.
|
|
**Required behaviour:** the verb is **absent from the UI**, not present and
|
|
disabled; an API call attempting it is refused.
|
|
**Invariant protected:** Kenntnisnahme is not approval. Collapsing them is how
|
|
"I saw it" silently becomes "I approved it".
|
|
`state-transitions.md §B2 · PR-31`
|
|
|
|
### NC-02 — Bind with unacknowledged required highlights
|
|
|
|
**Attempt:** `accept` while a `required_ack` highlight is unacked.
|
|
**Required behaviour:** fail closed; no disposition created; error names the
|
|
outstanding highlight ids.
|
|
**Invariant protected:** the acknowledgment gate is the only positive evidence
|
|
of informedness the model has.
|
|
`guard G_ACK · DoD-4 · PR-20`
|
|
|
|
### NC-03 — Agent attempts a disposition
|
|
|
|
**Attempt:** a principal with `actor.kind = agent` submits any binding verb.
|
|
**Required behaviour:** refused at *this repository's* API, not merely hidden in
|
|
the UI.
|
|
**Invariant protected:** humans bind, agents draft.
|
|
`guard G_NOAGENT · P-10 · W-"An agent binds"`
|
|
|
|
**Corrected 2026-09-09.** The first draft implied the refusal was also
|
|
`approval-engine`'s. It is not: that engine restricts only `/consume` by
|
|
principal type, and its `approval-engine-operator` service client holds
|
|
`approval:approve`, so a non-human principal can supply approver evidence there
|
|
today. Whether it should is `gate-house` doctrine, not ours.
|
|
|
|
Two consequences. First, enforcement of P-10 is **this surface's own
|
|
obligation** — there is no upstream backstop. Second, it is *auditable*:
|
|
schema v4 records the verified `entries[].principal_type` from the token. Read
|
|
that field. **Never** infer the answer from the shape of `subject_id`, which is
|
|
a naming convention, not a verified claim; entries predating v4 are `null` and
|
|
must not be read as `human`.
|
|
|
|
### NC-04 — Tenant switch requiring a new bind
|
|
|
|
**Attempt:** after an L0/L1 bind, the subject selects a tenant marked
|
|
`requires_new_bind`.
|
|
**Required behaviour:** `scope.rebind_required` is set and a **child memo** is
|
|
opened. The current session's `view_hash` is not rewritten.
|
|
**Invariant protected:** scope is committed, not configured.
|
|
`guard G_HAT · isolation vector 4`
|
|
|
|
### NC-05 — Awareness field edited after presentation
|
|
|
|
**Attempt:** the proposed role or last-session summary changes between render
|
|
and bind.
|
|
**Required behaviour:** `awareness_hash` changes; `view_hash` does **not**; the
|
|
bind remains valid.
|
|
**Invariant protected:** the person is bound to what they committed to, not to
|
|
orientation material.
|
|
`isolation vector 2 · PR-40`
|
|
|
|
### NC-06 — Binding target changed
|
|
|
|
**Attempt:** `binding.target` is altered from ACME to Beta.
|
|
**Required behaviour:** `view_hash` changes, invalidating any outstanding
|
|
presentation.
|
|
**Invariant protected:** the binding slice is genuinely covered by the hash —
|
|
the positive control for NC-05's negative one.
|
|
`isolation vector 3`
|
|
|
|
### NC-07 — Bind against a stale memo version
|
|
|
|
**Attempt:** a presentation taken at version *n* is used to bind after the memo
|
|
advanced to *n+1*.
|
|
**Required behaviour:** version conflict; no silent upgrade.
|
|
**Invariant protected:** binding acts reference a presentation of the same
|
|
version.
|
|
`guard G_PRES · PR-14`
|
|
|
|
### NC-08 — `return` with no coded reason
|
|
|
|
**Attempt:** return with free text only.
|
|
**Required behaviour:** refused.
|
|
**Invariant protected:** return is structured feedback, not a shrug.
|
|
`guard G_REASONS · PR-32`
|
|
|
|
### NC-09 — `escalate` to an equal or lower mandate
|
|
|
|
**Attempt:** escalate to a target whose mandate is not strictly higher.
|
|
**Required behaviour:** refused as an escalation; offered as `forward`.
|
|
**Stage 1 caveat:** without the mandate graph this guard cannot be fully
|
|
evaluated. Stage 1 records the asserted intent and marks it **unverified**
|
|
rather than pretending the guard passed.
|
|
`guard G_ESC · limitation L-01`
|
|
|
|
### NC-10 — Key shuffling
|
|
|
|
**Attempt:** serialise the same binding document with object keys in a different
|
|
order.
|
|
**Required behaviour:** identical `view_hash`.
|
|
**Invariant protected:** canonicalization is real; otherwise every hash is an
|
|
artifact of serialisation order.
|
|
`isolation vector 1`
|
|
|
|
---
|
|
|
|
## Schema pressure — cases that would fork the object
|
|
|
|
Recorded so a future contributor recognises them as forks rather than features:
|
|
|
|
- **A level-specific status.** e.g. `awaiting_notarisation` for L4. Belongs as a
|
|
step kind or a route step, never a new memo status.
|
|
- **Multiple questions per memo.** An approval bundling three unrelated acts is
|
|
three memos. The single-question invariant is what makes `view_hash` mean
|
|
something.
|
|
- **A per-level packet model.** L4 attachments are packet items, hashed exactly
|
|
like an L0 consent text.
|
|
- **An approvals-inbox object.** A list of memos is a query result, not a
|
|
schema entity. If it acquires state, the product has become a queue.
|