Decision services alignment review: approval-engine, informed-decision, State Hub decisions
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Founder-requested review (2026-10-31), driven by NK-WP-0056-T17. Findings:
informed-decision couples presentation with creation (actor informed-decision),
one weight for every decision, two unlinked decision stores, executor-filed
approvals invisible. Proposal: present-existing-approval contract, decision
classes C0-C3 (executed means C2), hub decisions resolved by memo write-back;
first slice S1-S6 with owners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
This commit is contained in:
tegwick 2026-10-31 21:15:52 +01:00
parent 96c859f77c
commit b76456134c
2 changed files with 176 additions and 0 deletions

View file

@ -0,0 +1,171 @@
# Decision services alignment: approval-engine, informed-decision and State Hub decisions (review, 2026-10-31)
**Status:** review and proposal. Nothing here changes another repository; every change named below belongs to its
owner.
**Trigger:**
- NK-WP-0056-T17 found that the founder has no path today to approve a Bounded Execution Admission
([finding](2026-10-31-nk56-t17-founder-approval-path.md)).
- The founder asked for an architecture, code, INTENT and SCOPE review of informed-decision and approval-engine. It
should align them, attach State Hub's decision records, and make the facility proportionate: easy for simple
cases, complete for complex ones (founder, 2026-10-31).
## 1. Practical needs (what the estate does today)
| Need | How it is handled now | Problem |
| --- | --- | --- |
| **N1. Executor admissions** (NK-WP-0056-T06): a service files an approval for one exact admission, and the founder approves it at aal2 before a bounded run | No path. The executor must be the approval's actor (approval-engine create rule, BEA), but informed-decision presents only approvals it creates itself | The pilot cannot start |
| **N2. Routine founder decisions**: workplan approvals, adopting owner proposals, choosing between options | An agent writes "approved by the founder in session" into a workplan, a CCR comment or a State Hub decision (`decided_by` is free text) | No binding evidence. The same decision is repeated in several places. The full memo path is too heavy for these, so it is not used |
| **N3. Credential approvals (CCRs)**: platform-owner and net-kingdom-owner approve one request | The `credential-change.py approve` tool appends a review comment; for some types the second approval is recorded by hand (CCR-2026-0058) | Evidence is a YAML comment written by an agent on the founder's instruction |
| **N4. Hub coordination**: an open question waits for a human (`needs_human`, `decision_id`) | State Hub `decisions`, resolved through the API with a rationale | Not linked to any presented or bound evidence. informed-decision's write-back to the hub is disabled |
## 2. The architecture as built (evidence)
```text
requester (agent / service) founder (browser, KeyCape aal1/aal2)
│ │
│ State Hub memo dispatch (STATE-WP-0096, │ decisions.coulomb.social
│ private queue, POST /v1/memos) ▼
└──────────────► informed-decision ──── presents memo, records view_hash,
intake: creates approval highlights, disposition
actor="informed-decision" │
│ accept → POST /entries (approval:approve)
▼
approval-engine ◄── consume (approval:consume, service)
object, CAS, by whoever holds the scope
outbox → audit-core
State Hub decisions table: title, rationale, decided_by (string), status open/resolved. No link to the above.
```
- **approval-engine** (INTENT, SCOPE, `docs/bounded-execution-admission-carrier.md`):
- It is a PIP. It owns the durable approval object: binding to action, target, actor, principal and purpose,
authenticated entries, atomic consume and supersede, and revocation.
- It is explicitly *not an inbox, workflow or notification surface*. It has get-by-id and no list.
- Only the actor creates (`binding.actor` must equal the token subject).
- Consume does not check consumer = actor (APPROVAL-WP-0007-T02, waiting).
- aal2 is stored, not enforced (APPROVAL-WP-0007-T01).
- **informed-decision** (INTENT, SCOPE, GOAL, code):
- It is the live approver surface. Its thesis is *scale invariance*: one memo model from an L0 informed login to an
L4 contract, with L1 being privileged JIT authorization. Only L3 (management approval) is built; L0 and L2 are
"Stage 2, direction only".
- Intake (`intake.py`) refuses a memo that pins `approval_id` (`approval_pinned_by_submitter`).
- `intake_approval.py` creates every approval with `actor: "informed-decision"`, 24 h validity and
`pdp_path: false`.
- Submitters are configured per username with `memo_classes`, `review_groups` and a `daily_limit`
(`intake_service.configuration`).
- On accept it posts the human's entry, scopes `openid approval:read approval:approve`.
- `view_hash` is not inside the approval entry; correlation is `(approval_id, subject, approved_at)`.
- **State Hub:**
- `decisions` is a coordination record (`made|pending`, `open|resolved|escalated|superseded`, free-text
`decided_by`).
- `memo_dispatch` forwards a validated envelope (`informed-decision.memo-intake` v1) to informed-decision's private
intake. Hub resolution write-back is disabled.
## 3. Findings
1. **Creation is coupled to presentation.** informed-decision creates the approval with itself as actor. Two
consequences:
- An actor that will *use* an approval (the executor, secrets-engine's PEP, a deploy agent) cannot have it
presented. That is N1.
- Approvals created this way name the surface, not the user, as actor. That weakens approval-engine's "actor who
may use it" binding, which is the very thing BEA relies on.
Neither INTENT requires this coupling. informed-decision's INTENT says it "never holds approval current-state",
and approval-engine's says only the actor creates.
2. **One weight for every decision.** Every bound decision takes the full memo path: packet, highlights,
presentation record, view_hash, intake budget. The tiers that would make simple decisions cheap (L0 and L1, and
L2 "ADR accept against the estate's own decision records") are not built. In practice, routine decisions (N2)
bypass the surface entirely and become free text. The heaviness pushes the estate to *less* evidence, not more.
3. **Two decision stores with no link.** A State Hub decision and an informed-decision memo about the same question
do not reference each other. Write-back is off, so a hub decision is resolved by an agent's string. The same
founder decision often lives in three places (hub decision, workplan text, CCR comment) with no binding between
them.
4. **An approval filed by someone else is invisible.** approval-engine has no list (correctly), and informed-decision
lists only its own memos. An executor-filed approval therefore reaches the founder only if a memo is filed for
it. That is the missing contract in Finding 1.
5. **The evidence join is partial.** The approval entry does not carry `view_hash`, and assurance is checked by the
surface, not enforced by the object (APPROVAL-WP-0007-T01). For bounded execution the executor checks aal2 itself
today.
## 4. Proposed alignment
### 4.1 Roles (no change of ownership, sharper boundaries)
| Service | Owns | Does not do |
| --- | --- | --- |
| approval-engine | The object, created **only by the actor who will use it** (or by informed-decision when informed-decision itself is the actor); entries, consume, revoke | Present, list or notify |
| informed-decision | **The one human-facing surface for every bound decision.** It presents questions, collects dispositions and records what was shown. For approvals it adds the human entry to an approval *someone else created*, after verifying its binding | Create approvals on behalf of other actors; hold approval state |
| State Hub decisions | **The index and routing of open questions** for work coordination: what is waiting, for whom, in which workplan. It links to evidence and is resolved by it | Hold binding evidence; be resolved by free text for decisions that need binding |
| gate-house | Doctrine: which decision classes exist, which weight each needs, who may approve | Runtime state |
### 4.2 Contract: "present an existing approval" (closes Findings 1 and 4, needed for N1)
1. **Create.** The actor creates the approval in approval-engine, as itself, with `human_control: true`.
2. **Submit.** The actor submits a memo to informed-decision that references `approval_id` and the binding digest.
The memo carries the question, the packet (for an admission: the admission YAML and its digest) and the
highlights (scope ceiling, window, budget, revert).
3. **Verify.** informed-decision accepts the reference only from a registered submitter whose profile allows
`present_existing` for that action class. It then `GET`s the approval and requires all of:
- status `requested`;
- no entries;
- `human_control: true`;
- `binding.actor` equal to the submitter's subject;
- binding digest equal to the memo's;
- the action class allowed for the submitter.
4. **Accept.** Accepting posts the founder's entry with their token (aal2 where the class requires it).
informed-decision never consumes and never creates in this path.
5. **Run.** The actor reads the approval (`GET`) and consumes it. The executor's existing checks (actor, digest,
aal2, listed approvers) still hold.
### 4.3 Proportional weight: decision classes (closes Finding 2)
One memo model, as informed-decision's thesis requires, with the *amount of ceremony* set per class by doctrine:
| Class | Example | Surface | Assurance | approval-engine object | Evidence |
| --- | --- | --- | --- | --- | --- |
| **C0 note** | Record a finding; acknowledge a report | State Hub only | none | no | hub record |
| **C1 confirm** | Approve a workplan; adopt an owner proposal; choose option A/B | informed-decision single-screen confirm: question, one packet item, one required highlight, options | fresh session (aal1) | no | memo acceptance with `view_hash`; resolves the hub decision |
| **C2 bound approval** | Bounded-execution admission; credential lane (CCR); production deploy | informed-decision full memo, presenting an existing approval (4.2) | aal2, fresh | yes, created by the actor | memo + approval entry + audit-core |
| **C3 instrument** | Contract or multi-party | later (informed-decision L4) | per party | per party | as informed-decision Stage 3+ |
The rule: **anything a service will execute on the strength of the decision is C2.** An admission's `risk_class` can
add highlights or required approvers within C2, but cannot lower it to C1.
### 4.4 Attaching State Hub decisions (closes Finding 3)
- A hub decision gains optional links: `memo_id`, `approval_id` and `evidence_ref`.
- When it needs C1 or C2, the hub dispatches a memo (existing STATE-WP-0096 path), and informed-decision's write-back
(now disabled) resolves the hub decision with:
- the authenticated subject;
- the memo revision and `view_hash`;
- for C2, the approval id.
- An agent must not resolve a C1 or C2 decision by text.
- C0 stays as today.
This makes the hub the place to see *what is waiting*, and informed-decision the place where it is *decided*, without
either duplicating the other.
## 5. First slice, driven by what blocks today
| # | Change | Owner | Unblocks |
| --- | --- | --- | --- |
| S1 | informed-decision intake profile `present_existing` (4.2), with tests against approval-engine's real source | informed-decision | N1 / NK-WP-0056-T06 |
| S2 | Executor: KeyCape client `nk-bind-executor-approval`, OpenBao lane for its secret, a `request` phase that creates the approval and submits the memo | net-kingdom (NK-WP-0056-T14) + KeyCape registration + railiance-platform CCR | N1 |
| S3 | Write-back for memos that carry a hub `decision_id`, and decision links in State Hub | informed-decision + state-hub | N4, part of N2 |
| S4 | The C1 single-screen confirm in informed-decision | informed-decision | N2, and N3 later |
| S5 | Doctrine for decision classes C0–C3 and the "executed means C2" rule | gate-house | Every class assignment |
| S6 | approval-engine: aal2 floor (APPROVAL-WP-0007-T01) and `actor_consumes` (T02) | approval-engine | Moves the executor's own checks into the object |
S1 and S2 are the minimum for the pilot. S3–S5 make routine founder decisions cheaper *and* better evidenced than
today's free text.
## 6. Open questions
1. **For the founder:** are the classes C0–C3 and the "executed means C2" rule the right cut?
2. **For informed-decision:** does `present_existing` fit the intake model as a profile, or should it be a separate
route? Should the memo's packet carry the admission YAML itself, or a digest plus link?
3. **For approval-engine:** should an approval created by an actor and presented elsewhere carry a
`presentation_ref` (the memo id) at create, so that the object points at its evidence? (Finding 5)
4. **For State Hub:** decision links as new columns, or as typed `evidence_ref`s?

View file

@ -722,6 +722,11 @@ founder approves it, and the executor then consumes it. Needed:
`human_control: true`, `pdp_path: false`) and prints its id; `human_control: true`, `pdp_path: false`) and prints its id;
- the existing run phase then consumes it. - the existing run phase then consumes it.
**2026-10-31:** T17 showed that the founder cannot be presented an executor-filed approval today. The founder asked
for an alignment review of informed-decision, approval-engine and State Hub decisions
([review](../docs/proposals/2026-10-31-decision-services-alignment-review.md)). T14's request phase follows its
"present an existing approval" contract (S1 informed-decision, S2 here).
**Done when:** **Done when:**
- the client and the CCR are prepared; - the client and the CCR are prepared;