From 2de0f862f9875f344f090b633813239a33d63426 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 1 Nov 2026 00:40:35 +0100 Subject: [PATCH] FLEX-WP-0040-T07: compact-sitting v14 admits present-existing requester v14 (proposed, sha256:8911458f36f1a60598aa19592a2d752a74dffe2617fd86238407ac87bc300fde) keeps every v13 decision and admits approval requester service:nk-bind-executor only from submitter nk-bind-executor, memo class and approval action bounded-execution, an aal2 human reviewer, and never the approval actor itself (GH-DEC-2026-026 class C2). v13 stays unchanged as rollback reference. The admission generator skips present_existing registry rows. Co-Authored-By: Claude Opus 5.5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 5502@bnt-lap001 Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196 --- WORK-RECORDS.md | 1 + docs/compact-sitting-v14-present-existing.md | 98 ++ .../attribute_fixtures.json | 140 +++ .../fixtures.json | 21 + .../pin.json | 5 + .../policy.md | 973 ++++++++++++++++++ .../registry.json | 17 + ...informed_decision_present_existing_test.go | 131 +++ tools/gen_informed_decision_admission.py | 9 +- ...FLEX-WP-0040-bounded-execution-pdp-gate.md | 21 + 10 files changed, 1414 insertions(+), 2 deletions(-) create mode 100644 docs/compact-sitting-v14-present-existing.md create mode 100644 examples/informed-decision-present-existing-review/attribute_fixtures.json create mode 100644 examples/informed-decision-present-existing-review/fixtures.json create mode 100644 examples/informed-decision-present-existing-review/pin.json create mode 100644 examples/informed-decision-present-existing-review/policy.md create mode 100644 examples/informed-decision-present-existing-review/registry.json create mode 100644 internal/policy/informed_decision_present_existing_test.go diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index fa2c208..3533dcc 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -245,6 +245,7 @@ | task | FLEX-WP-0040-T04 | todo | — | workplans/FLEX-WP-0040-bounded-execution-pdp-gate.md | | task | FLEX-WP-0040-T05 | todo | — | workplans/FLEX-WP-0040-bounded-execution-pdp-gate.md | | task | FLEX-WP-0040-T06 | wait | — | workplans/FLEX-WP-0040-bounded-execution-pdp-gate.md | +| task | FLEX-WP-0040-T07 | done | — | workplans/FLEX-WP-0040-bounded-execution-pdp-gate.md | | intake | FLEX-IN-0001 | closed | — | intakes/intakes.md | | intake | FLEX-IN-0002 | closed | — | intakes/intakes.md | | intake | FLEX-IN-0003 | open | — | intakes/intakes.md | diff --git a/docs/compact-sitting-v14-present-existing.md b/docs/compact-sitting-v14-present-existing.md new file mode 100644 index 0000000..782baa2 --- /dev/null +++ b/docs/compact-sitting-v14-present-existing.md @@ -0,0 +1,98 @@ +# Owner note: compact-sitting v14, present-existing requester + +**Date:** 2026-11-01 +**Owner:** access-engine (flex-auth PDP) +**Workplan:** `workplans/FLEX-WP-0040-bounded-execution-pdp-gate.md`, task FLEX-WP-0040-T07 +**Answers:** informed-decision `docs/decision-services-alignment-answer.md` (commit `0e66a33`, +INFD-WP-0010), blocker 1 and open question 3. Doctrine: gate-house GH-DEC-2026-026 +(an approval something executes against is class C2: human approver at aal2, and +the actor that files the approval may never approve it). + +## Package + +| Field | Value | +| --- | --- | +| package | `informed-decision.compact-sitting` | +| version | `v14` (status `proposed`) | +| package_digest | `sha256:8911458f36f1a60598aa19592a2d752a74dffe2617fd86238407ac87bc300fde` | +| source | `examples/informed-decision-present-existing-review/policy.md` | +| pin | `examples/informed-decision-present-existing-review/pin.json` | +| rollback | v13 `sha256:31be5985…3711873`, unchanged in `examples/informed-decision-lifecycle-review/` | + +v14 lives in a new directory so the deployed v13 package, its pin and the release +values stay byte-identical as the rollback reference. + +## Behaviour + +Unchanged from v13: every exact-record and list-only decision; the attribute branch +for requester `informed-decision` (same generated submitter/class sets, same reasons); +any other requester is still `requester_mismatch` (or an earlier class/submitter refusal). + +Added: an owner-authored `present_existing_profiles` table with one entry: + +| approval_requester | intake_submitter | memo_class | approval_action | +| --- | --- | --- | --- | +| `service:nk-bind-executor` | `nk-bind-executor` | `bounded-execution` | `bounded-execution` | + +When `context.approval_requester` names this profile, the request is allowed +(`present_existing_eligible`) only if, in addition to every v13 attribute guard +(authenticated human, tenant, review group in the subject's groups, recipient for +`list`, resource shape, `approval_human_control == true`, UUID approval id, digest +shape, memo version >= 1): + +- `intake_submitter` and `memo_class` equal the profile (otherwise `unknown_submitter` / + `unknown_class`); the generated sets are not widened, so the executor cannot use the + create path and this requester cannot borrow state-hub's class; +- `context.approval_action == "bounded-execution"` (otherwise, or when absent, + `approval_action_not_admitted`); +- the reviewing human holds KeyCape AAL2 with MFA `pwd`+`otp`, 900 s fresh for read and + acts, 12 h for list (otherwise `identity_refused` / `stale_mfa`); +- the reviewing human's `subject.id` is neither `service:nk-bind-executor` nor + `nk-bind-executor` (`self_approval_refused`). + +The PDP still proves no pin. The consumer re-reads the approval and compares actor, +human-control flag and binding digest before asking, as today. + +## Consumer change required (informed-decision) + +v14 needs one new context key, `approval_action`, on present-existing requests only. +Today `policy.build_request` accepts exactly five intake keys and `store._intake_context` +does not carry the action, so a v14-pinned consumer would be refused with +`approval_action_not_admitted` until both are changed. Suggested shape: record the +approval's `binding.action` in the ledger metadata at intake (already verified there), +emit it as `approval_action` for present-existing memos, and in `review._authorize` +also compare the live `binding.action` to it. Requests for requester `informed-decision` +may omit the key; v14 ignores it there. + +`tools/gen_informed_decision_admission.py` now accepts a registry row with a +`present_existing` profile and skips it, so adding the nk-bind-executor row to +informed-decision `deploy/intake-submitters.json` does not change the generated sets +or break `--check`. + +## Tests + +- 46 embedded Rego tests (32 from v13 unchanged, 14 for v14: positive read/acts/list, + self-approval for acts and list, aal1/no-MFA/password-only, stale MFA, service subject, + wrong or missing action, wrong submitter/class, other service requester, executor on + the create path, and v13 requester unaffected by the new key or subject). +- `internal/policy/informed_decision_present_existing_test.go`: v14 package valid, pin + matches the computed digest, 12 engine evaluation cases, and the generator skips + present-existing rows. +- `go test ./internal/policy/` passes; generator `--check` in sync. + +## Live steps (founder; none performed by this owner) + +1. informed-decision lands the `approval_action` context change above. +2. Build the access-engine image from the commit carrying v14 (owner CI), scan it. +3. Update the review release values (`values/informed-decision-sitting.yaml` and the + `access-engine` twin) to the new image digest and + `--policy /opt/flex-auth/examples/informed-decision-present-existing-review/policy.md` + (registry path may stay on the lifecycle-review directory or the identical copy); + dry-run, diff, then upgrade the Helm release with `--atomic`. +4. Pin informed-decision to `informed-decision.compact-sitting` / `v14` / + `sha256:8911458f36f1a60598aa19592a2d752a74dffe2617fd86238407ac87bc300fde` in the same + release window: the `policy` object of its review runtime (`deploy/lifecycle/service.candidate.json` + `runtime.json`, and `deploy/lifecycle/admission.candidate.json`). A version or digest + mismatch fails closed. +5. Rollback: restore the release values in force before step 3 (v13 policy path and the + image then deployed) and the consumer's v13 pin (`sha256:31be5985…`). diff --git a/examples/informed-decision-present-existing-review/attribute_fixtures.json b/examples/informed-decision-present-existing-review/attribute_fixtures.json new file mode 100644 index 0000000..71a8d9d --- /dev/null +++ b/examples/informed-decision-present-existing-review/attribute_fixtures.json @@ -0,0 +1,140 @@ +[ + { + "id": "attribute-non-human-refused", + "request": { + "id": "attribute-non-human", + "tenant": "tenant:platform", + "subject": { + "id": "not-a-person", + "type": "service", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": { + "level": "aal2", + "mfa": true, + "source": "key-cape", + "methods": ["pwd", "otp"], + "at": 1 + } + } + }, + "action": "accept", + "resource": { + "id": "memo:intake-new-memo", + "type": "decision-memo", + "system": "informed-decision", + "tenant": "tenant:platform" + }, + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 1, + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, + "policy_version": "v8" + }, + "expect": { + "effect": "deny", + "reason": "identity_refused" + } + }, + { + "id": "attribute-stale-mfa", + "request": { + "id": "attribute-stale-mfa", + "tenant": "tenant:platform", + "subject": { + "id": "test-human", + "type": "human", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": { + "level": "aal2", + "mfa": true, + "source": "key-cape", + "methods": ["pwd", "otp"], + "at": 1 + } + } + }, + "action": "accept", + "resource": { + "id": "memo:intake-new-memo", + "type": "decision-memo", + "system": "informed-decision", + "tenant": "tenant:platform" + }, + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 1, + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, + "policy_version": "v8" + }, + "expect": { + "effect": "deny", + "reason": "stale_mfa" + } + }, + { + "id": "attribute-future-mfa", + "request": { + "id": "attribute-future-mfa", + "tenant": "tenant:platform", + "subject": { + "id": "test-human", + "type": "human", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": { + "level": "aal2", + "mfa": true, + "source": "key-cape", + "methods": ["pwd", "otp"], + "at": 9999999999 + } + } + }, + "action": "read", + "resource": { + "id": "memo:intake-new-memo", + "type": "decision-memo", + "system": "informed-decision", + "tenant": "tenant:platform" + }, + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 1, + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, + "policy_version": "v8" + }, + "expect": { + "effect": "deny", + "reason": "identity_refused" + } + } +] diff --git a/examples/informed-decision-present-existing-review/fixtures.json b/examples/informed-decision-present-existing-review/fixtures.json new file mode 100644 index 0000000..a4b6f93 --- /dev/null +++ b/examples/informed-decision-present-existing-review/fixtures.json @@ -0,0 +1,21 @@ +[ + { + "id": "unknown-request-denied", + "request": { + "id": "unknown", + "subject": { + "id": "unknown", + "type": "human" + }, + "action": "accept", + "resource": { + "id": "memo:unrelated", + "type": "decision-memo", + "system": "informed-decision" + } + }, + "expect": { + "effect": "deny" + } + } +] diff --git a/examples/informed-decision-present-existing-review/pin.json b/examples/informed-decision-present-existing-review/pin.json new file mode 100644 index 0000000..cef97e5 --- /dev/null +++ b/examples/informed-decision-present-existing-review/pin.json @@ -0,0 +1,5 @@ +{ + "package": "informed-decision.compact-sitting", + "package_digest": "sha256:8911458f36f1a60598aa19592a2d752a74dffe2617fd86238407ac87bc300fde", + "version": "v14" +} diff --git a/examples/informed-decision-present-existing-review/policy.md b/examples/informed-decision-present-existing-review/policy.md new file mode 100644 index 0000000..870792a --- /dev/null +++ b/examples/informed-decision-present-existing-review/policy.md @@ -0,0 +1,973 @@ +--- +id: informed-decision.compact-sitting +name: Compact sitting exact-record human review +namespace: informed-decision:decision-memo +version: v14 +status: proposed +package: flexauth.informed_decision.compact_sitting +allow_ttl: 30s +actions: [read, acknowledge, accept, return, discuss, decline, list] +owner: flex-auth +fixtures: [fixtures.json, attribute_fixtures.json] +caring: + profile: caring-0.4.0-rc2 + enforce: false +activation: + mode: local +--- + +# Lifecycle review release candidate + +This v12 candidate retains the deployed v11 exact records and adds only +State Hub / decision-record attribute admission for INFD-WP-0007. The unchanged +sitting directory and deployed values remain the v11 rollback reference. New +lifecycle decisions use this attribute branch without package regeneration. +Review groups remain verified by the consumer and the subject identity. + +# Compact sitting review mandate + +This is a new exact-record package for seven 2026-09-14 sitting memos. +v2 (2026-09-21) adds exactly one record, `memo:infd-20260921-b01` (the +SECRETS-WP-0009-T03 spend envelope), with native pins from informed-decision +`docs/evidence/2026-09-21-budget-approval-create.json`. The seven v1 records +and the identity bar are unchanged. +It is not an expansion of `examples/informed-decision-t03` / FLEX-WP-0027. +`memo:infd-20260914-c01` is omitted (create-client still undecided). + +v3 (2026-09-21, `FLEX-WP-0032`, `FLEX-DEC-2026-017`) adds one action, `list`, +for informed-decision's decision overview (`INFD-IN-0008`). It is a separate +rule, not an extra member of the act set, and it changes nothing else: + +- `read` and the five acts keep the v2 rule unchanged — the same eight records, + the same 900-second MFA window. A `list` allow satisfies no other action. +- `list` covers the **union** of this package's eight records and the three + `SECRETS-WP-0010-T03-*` records from `informed-decision.t03-review`, each + pinned by approval id, binding digest and its own memo version (T03 at 3). + Scope stays exact-record: a memo is listable only once someone admitted it. + The operator declined resource-type-wide scope because the PDP checks no + recipient, and a type-wide rule would have left consumer code — which the + consumer itself says is not an entitlement — as the only scope. +- `list` carries the same identity bar with a **12-hour** MFA window instead of + 900 seconds. Looking back at your own outcomes is not binding a new one. The + bar is lighter, not absent: the PDP still states a bound. +- The KeyCape stale-timestamp defect (`INFD-IN-0005`) is **not** worked around + here. `read` stays strict; that defect is key-cape's to fix. + +v4 (2026-09-27, REINAH-WP-0003-T06) adds exactly one record for the +EUR 10 single-attempt tool proof. Native pins come from informed-decision +`docs/evidence/2026-09-27-tool-budget-approval-create.json`. Existing records, +actions and identity requirements remain unchanged. + +v5 (2026-09-27, REINAH-WP-0003-T06) admits the replacement-window memo +`memo:infd-20260927-b02` with fresh native pins. The accepted b01 record is +preserved; this change does not extend its signed execution window. + +v6 (2026-09-27, REINAH-WP-0003-T06) admits six exact native metered +credential requests, apply/verify/exec per provider and worker lane. All acts +still require the authenticated human and fresh MFA. Ordinary worker approvals +retain their native control type and require exact-ID consumer configuration. + +v7 (2026-10-03, INFD-WP-ADHOC-2026-10-03-T02) adds the exact +`memo:infd-20261003-p01` delivery-proof acceptance record. Native pins come +from informed-decision `docs/evidence/2026-10-03-provisioning-window-approval-create.json`. +Existing records and identity requirements are preserved. + +v8 (2026-10-03, offline attribute admission) adds one branch beside those +exact-record rules. It is not the deployed sitting pin. Requests whose +resource id is in `records` or `listable` still take only the exact-record +rules, including when they also carry attribute fields, so an exact-record +decision does not change. `list` stays on the exact-record rule. + +The attribute branch may allow `read`, `acknowledge`, `accept`, `return`, +`discuss` and `decline` for any other memo id. `attribute_eligible` is +eligibility only. It checks that the carried approval id is a UUID and the +carried digest matches `sha256:` plus 64 lowercase hex digits. It does not +read an approval and it does not prove the memo pin. The effect owner still +reads the approval live and compares that id and the engine's native digest +with the memo. `approval_human_control` must be boolean true. No presentation +hash is read. Context fields are `review_group`, `memo_class`, +`intake_submitter`, `approval_requester`, `approval_human_control`, +`memo_version`, `approval_id` and `approval_binding_digest`. + +v9 (2026-10-04, EMERGENT-WP-0002 profile adoption) adds exactly one record, +`memo:emergent-profile-adoption-0-2-6`. Native pins come from informed-decision +`docs/evidence/2026-10-04-emergent-profile-approval-create.json`, approval +`05751e09-3a5b-4a41-a4e8-b79e96de6484`. Existing exact records stay. The v8 +attribute branch remains beside them and still admits nobody, because the +shipped submitter and class sets are empty. + +v10 (2026-10-04, STATE-WP-0094 production-steps sitting) adds exactly four +records: `memo:state-hub-edge-receiving-rollout-c287ed8`, +`memo:owner-return-pickup-plan-b6222af`, +`memo:freshness-runner-deploy-trial-e77f8b4` and +`memo:freshness-schedule-enable-retire-sweep`. Native pins come from +informed-decision `docs/evidence/2026-10-04-state-hub-wp0094-approval-create.json`, +approvals `974eae4d-782d-41ed-93be-e34433b5fe59`, +`77993081-1db2-4bba-99f2-74ca1f8652b1`, `4ae04c8e-d604-4d8b-b166-693eef855da8` +and `b75752d1-bab6-4cf3-9308-5368c1d5aa91`. Existing exact records, including +v9's, stay. The v8 attribute branch still admits nobody. + +v11 (2026-10-04, IR-WP-0005-T02) lets the three `list_only_records` be +reviewed. `read`, `acknowledge`, `accept`, `return`, `discuss` and +`decline` allow when the memo id, memo version 3, approval id and binding +digest match those records, with the same 900-second KeyCape bar as the +version-1 act rule. `list` is unchanged. A version other than 3, a foreign +approval id, or a different digest is refused. The version-1 `records` set +is unchanged, and these three ids stay out of it. + +`admitted_submitters` and `admitted_classes` are generated from +informed-decision `deploy/intake-submitters.json`. The shipped registry is +empty, so both sets are empty and this branch admits nobody. Regenerate with +`python3 tools/gen_informed_decision_admission.py`; `--check` reports drift. + +The identity bar matches T03: authenticated informed-decision caller, +`net-kingdom-admins`, fresh KeyCape AAL2 MFA. No permission follows from +memo content or presentation state. TokenReview must admit +`system:serviceaccount:informed-decision:review` before this package is +served. Membership tenant provenance may follow the accepted registration +route; it does not assert directory membership in tenant:platform. +Only a real human uses accept. This package neither issues nor consumes +approval. Native pins: `docs/evidence/2026-09-15-sitting-approval-creates.json`. + +v13 adds attribute-based `list` for the retained named recipient. The consumer +supplies `review_recipient` from the immutable memo and verifies the live native +approval requester, human-control flag and binding digest before asking the PDP. +Listing has the existing twelve-hour MFA window; read and all acts retain 900 +seconds. Existing exact-record branches retain precedence. A list allow exposes +only overview metadata and cannot stand in for a read or act decision. + +v14 (2026-11-01, FLEX-WP-0040-T07, INFD-WP-0010, gate-house GH-DEC-2026-026) +admits one present-existing requester beside the v13 attribute branch. Every +v13 decision for requester `informed-decision`, for exact records and for any +other requester is unchanged; the v13 package stays in +`examples/informed-decision-lifecycle-review` as the deployed rollback reference. + +`present_existing_profiles` is owner-authored here, not generated from the +consumer registry: it pins the triple (approval requester, intake submitter, +memo class) plus the approval action. The only entry is +`service:nk-bind-executor` from submitter `nk-bind-executor`, memo class +`bounded-execution`, approval action `bounded-execution`. Such approvals are +class C2 under GH-DEC-2026-026: human approver at aal2, and the actor that +files the approval may never approve it. A present-existing request therefore +also requires: + +- context `approval_action` equal to the profile's action. The consumer + carries it from the live approval binding it already re-reads; a request + without it is refused `approval_action_not_admitted`; +- KeyCape AAL2 with MFA (`pwd`, `otp`) for the reviewing human, with the same + 900-second window for read and acts and twelve hours for list. This is the + v13 identity bar, stated again explicitly for this branch; +- a reviewing human subject that is neither the approval actor nor the + submitter slug (`self_approval_refused`). + +The generated `admitted_submitters`/`admitted_classes` are not widened, so the +executor cannot pose on the create path, and a present-existing requester +cannot borrow the state-hub submitter or the decision-record class. The PDP +still proves no pin: the consumer re-reads the approval and compares actor, +human-control flag and binding digest before asking. + +```rego +import rego.v1 + +records := { + "memo:state-hub-edge-receiving-rollout-c287ed8": { + "approval_id": "974eae4d-782d-41ed-93be-e34433b5fe59", + "binding_digest": "sha256:84735b88b2db20d9445af369e28a8d40bd495dd20c0303ff3a764666ec976dde" + }, + "memo:owner-return-pickup-plan-b6222af": { + "approval_id": "77993081-1db2-4bba-99f2-74ca1f8652b1", + "binding_digest": "sha256:e745ef64658e77e41b2a5ef569ec0f6986e120d338656d75749425f5f29b7ca0" + }, + "memo:freshness-runner-deploy-trial-e77f8b4": { + "approval_id": "4ae04c8e-d604-4d8b-b166-693eef855da8", + "binding_digest": "sha256:f8ff89dd353c1d2006622c45996c0f05f050865f8c203373dc74da1ba3f05e24" + }, + "memo:freshness-schedule-enable-retire-sweep": { + "approval_id": "b75752d1-bab6-4cf3-9308-5368c1d5aa91", + "binding_digest": "sha256:cc3e8fe6ace5438f19d647d3de226407c8b009985c82ad9a993e81226e6f316f" + }, + "memo:infd-20261003-p01": { + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, + "memo:emergent-profile-adoption-0-2-6": { + "approval_id": "05751e09-3a5b-4a41-a4e8-b79e96de6484", + "binding_digest": "sha256:7843e660694dcd7c806dc02ee0cca05dd3a96488f614b1946291496259a02001" + }, + "memo:infd-20260914-c02": { + "approval_id": "ccfd8007-2061-48fd-9356-99b16279dac1", + "binding_digest": "sha256:942cf7d5e6805987df2077a0cc6cde75b148e1729a96dd0692153287820b6a0a" + }, + "memo:infd-20260914-c03": { + "approval_id": "a0611d0d-b9ef-4b9a-aa79-e94a9e3fcd5f", + "binding_digest": "sha256:95cae135b994acea6dad9da978dd3df1aa3354de900a1f43c2e5a03a20e8e844" + }, + "memo:infd-20260914-c04": { + "approval_id": "22ac6df3-e6a9-472b-a68e-c8caca4289a7", + "binding_digest": "sha256:dec960b30efaf654abbdcd8a2cdd20d4ff6bf52e170744f817dc55845d9cb192" + }, + "memo:infd-20260914-d01": { + "approval_id": "9f7c3506-68de-4826-86ff-e301f428408d", + "binding_digest": "sha256:8cb7cc93f38db9d19b49e201e0554b50af1891fec853fef333a06ea078541d15" + }, + "memo:infd-20260914-d02": { + "approval_id": "b3ce2c01-0a3b-401e-b4ed-c1954af169c9", + "binding_digest": "sha256:be0c7de123216740d2c2eeaed80384e03e6b6d1668104f94bb8f9cbae7dfe30f" + }, + "memo:infd-20260914-d03": { + "approval_id": "3b483e6b-0b92-45ff-83b6-bc5057c0a496", + "binding_digest": "sha256:3c562278e86bb5e7f747452cf2e99d3a136853ab748676e8359262b835cd1425" + }, + "memo:infd-20260914-d04": { + "approval_id": "356e67a3-5539-46a1-922a-f5591fd38ee5", + "binding_digest": "sha256:3c2d7759679d871cb7c5cac8999542a365e64c918d59d0c4ce864ffb837453e4" + }, + "memo:infd-20260921-b01": { + "approval_id": "ba5ce2d8-8b6d-40be-af89-2e8c147029a3", + "binding_digest": "sha256:e9eaf454e94871ec2060855fcfbd327bcaec873d2c61cae031bec9f95a34bcae" + }, + "memo:infd-20260927-b01": { + "approval_id": "e9521804-8fce-4fbc-bfdf-8a1aa959fd61", + "binding_digest": "sha256:7aefcee9b79eb3285997066b323ae1772e13d58f875aacc6eb5e9407bd1c1185" + }, + "memo:infd-20260927-b02": { + "approval_id": "1e73e514-ef74-4bff-83e7-fa88a3eaba0c", + "binding_digest": "sha256:3e5951847e1909abe1561f561b4591228848a2e1a939b9bf5acab61e2db42773" + }, + "memo:metered-20260927-provider-apply": { + "approval_id": "0c05bd0a-f81f-451d-840c-5565628e2edc", + "binding_digest": "sha256:a36b88244c26b1ffcd765cbadc201ff79260c7bcbd71d20d1584c3c0a3b1c3de" + }, + "memo:metered-20260927-provider-verify": { + "approval_id": "47f118a3-a86c-43ad-969d-42e09a0f45bb", + "binding_digest": "sha256:98636335ccd36286c02fe018f296fd7d4dfd091ee89d27fe1b629e3db00cf121" + }, + "memo:metered-20260927-provider-exec": { + "approval_id": "7b32443a-a817-400c-a130-01b9ef04c8ee", + "binding_digest": "sha256:8b31dd5911552b901b1f807a8cd6348d09547f3dbb2a52e3b42ad9fd948a2b01" + }, + "memo:metered-20260927-worker-apply": { + "approval_id": "2ce76d7f-01c3-4b63-8476-8d1230679769", + "binding_digest": "sha256:412b79fb537d4444e52e3358820b6ea204fa17173a4420ab6372c52d010ee018" + }, + "memo:metered-20260927-worker-verify": { + "approval_id": "c2af4bcf-15b4-4305-aac1-acc7e4dcb099", + "binding_digest": "sha256:bce6e2091ed076ec0040804d24c822078ac1eda18cd62703801fc9da77898303" + }, + "memo:metered-20260927-worker-exec": { + "approval_id": "dc22666a-d5d7-46bc-9490-ebe9fe09dc38", + "binding_digest": "sha256:ae261c9271a21b626be354646054f493d0951f69aecdbde3cfce3ac0c5667e8f" + } +} + +list_only_records := { + "memo:SECRETS-WP-0010-T03-apply": { + "approval_id": "b5fcbfcc-ad56-456f-bc36-c823052e9917", + "binding_digest": "sha256:03cc5b37437f14e86b686ce4054f976556334eff3fa260b03e8014ed3d9217e5", + "memo_version": 3 + }, + "memo:SECRETS-WP-0010-T03-verify": { + "approval_id": "4e9ff881-d59f-4143-9155-929afbe8dd43", + "binding_digest": "sha256:72d9267d038c17107c880ffc9009793ce9c70defbddfe2219628854b811a04b2", + "memo_version": 3 + }, + "memo:SECRETS-WP-0010-T03-exec": { + "approval_id": "a24e0898-b3c5-4e50-85a8-18a2c2aa229e", + "binding_digest": "sha256:f2cf0fb53b740900756ccde5587c3578fcff44beef2f1edab17b9a198a4033d8", + "memo_version": 3 + } +} + +# listable is the union list may range over. Sitting records are memo version 1. +# T03 records carry memo version 3 and are acted on only through list_only_records. +listable := object.union( + {id: object.union(r, {"memo_version": 1}) | some id, r in records}, + list_only_records, +) + +# BEGIN GENERATED admitted_sets (tools/gen_informed_decision_admission.py; do not edit by hand) +admitted_submitters := { + "state-hub", +} + +admitted_classes := { + "decision-record", +} +# END GENERATED admitted_sets + +# v14: owner-authored present-existing requesters (class C2, GH-DEC-2026-026). +present_existing_profiles := { + "service:nk-bind-executor": { + "intake_submitter": "nk-bind-executor", + "memo_class": "bounded-execution", + "approval_action": "bounded-execution", + }, +} + +exact_id if { + id := object.get(object.get(input, "resource", {}), "id", "") + listable[id] +} + +attribute_attempt if { + not exact_id + input.action in {"read", "acknowledge", "accept", "return", "discuss", "decline", "list"} + ctx := object.get(input, "context", {}) + some key in {"review_group", "memo_class", "intake_submitter", "approval_requester", "approval_human_control"} + object.get(ctx, key, null) != null +} + +present_existing_attempt if { + attribute_attempt + is_string(input.context.approval_requester) + present_existing_profiles[input.context.approval_requester] +} + +present_existing_profile := present_existing_profiles[input.context.approval_requester] + +identity_core if { + input.tenant == "tenant:platform" + input.subject.tenant == "tenant:platform" + input.subject.type == "human" + is_string(input.subject.id) + input.subject.id != "" + input.subject.attributes.principal_type_source == "authentication-derived" + input.subject.attributes.tenant_source in {"registration-supplied", "directory-asserted"} + assurance := input.subject.attributes.assurance + assurance.level == "aal2" + assurance.mfa == true + assurance.source == "key-cape" + assurance.methods == ["pwd", "otp"] + is_number(assurance.at) + assurance.at > 0 +} + +mfa_window := 43200 if input.action == "list" else := 900 + +mfa_fresh if { + identity_core + age := time.now_ns() / 1000000000 - input.subject.attributes.assurance.at + age >= -30 + age <= mfa_window +} + +stale_mfa if { + identity_core + age := time.now_ns() / 1000000000 - input.subject.attributes.assurance.at + age > mfa_window +} + +recipient_ok if input.action != "list" + +recipient_ok if { + input.action == "list" + input.context.review_recipient == input.subject.id +} + +resource_ok if { + input.resource.tenant == "tenant:platform" + input.resource.system == "informed-decision" + input.resource.type == "decision-memo" + is_string(input.resource.id) + regex.match(`^memo:[a-z0-9][a-z0-9-]{2,62}$`, input.resource.id) +} + +review_group_ok if { + group := input.context.review_group + is_string(group) + group != "" + group in input.subject.attributes.groups +} + +class_ok if { + not present_existing_attempt + is_string(input.context.memo_class) + input.context.memo_class in admitted_classes +} + +class_ok if { + present_existing_attempt + input.context.memo_class == present_existing_profile.memo_class +} + +submitter_ok if { + not present_existing_attempt + is_string(input.context.intake_submitter) + input.context.intake_submitter in admitted_submitters +} + +submitter_ok if { + present_existing_attempt + input.context.intake_submitter == present_existing_profile.intake_submitter +} + +requester_ok if { + input.context.approval_requester == "informed-decision" +} + +requester_ok if present_existing_attempt + +approval_action_ok if not present_existing_attempt + +approval_action_ok if { + present_existing_attempt + input.context.approval_action == present_existing_profile.approval_action +} + +# The actor that files a C2 approval may never approve it. +approver_distinct_ok if not present_existing_attempt + +approver_distinct_ok if { + present_existing_attempt + input.subject.id != input.context.approval_requester + input.subject.id != present_existing_profile.intake_submitter +} + +c2_assurance_ok if not present_existing_attempt + +c2_assurance_ok if { + present_existing_attempt + input.subject.attributes.assurance.level == "aal2" + input.subject.attributes.assurance.mfa == true +} + +human_control_ok if { + input.context.approval_human_control == true +} + +approval_id_ok if { + is_string(input.context.approval_id) + regex.match(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, input.context.approval_id) +} + +digest_ok if { + is_string(input.context.approval_binding_digest) + regex.match(`^sha256:[0-9a-f]{64}$`, input.context.approval_binding_digest) +} + +version_ok if { + is_number(input.context.memo_version) + input.context.memo_version >= 1 +} + +decision := {"effect": "allow", "reason": "operator_admitted_compact_sitting"} if { + input.tenant == "tenant:platform" + input.subject.tenant == "tenant:platform" + input.subject.type == "human" + is_string(input.subject.id) + input.subject.id != "" + input.subject.attributes.principal_type_source == "authentication-derived" + input.subject.attributes.tenant_source in {"registration-supplied", "directory-asserted"} + "net-kingdom-admins" in input.subject.attributes.groups + assurance := input.subject.attributes.assurance + assurance.level == "aal2" + assurance.mfa == true + assurance.source == "key-cape" + assurance.methods == ["pwd", "otp"] + is_number(assurance.at) + assurance.at > 0 + age := time.now_ns() / 1000000000 - assurance.at + age >= -30 + age <= 900 + input.resource.tenant == "tenant:platform" + input.resource.system == "informed-decision" + input.resource.type == "decision-memo" + record := records[input.resource.id] + input.context.memo_version == 1 + input.context.approval_id == record.approval_id + input.context.approval_binding_digest == record.binding_digest + input.action in {"read", "acknowledge", "accept", "return", "discuss", "decline"} +} else := {"effect": "allow", "reason": "operator_admitted_t03_review_act"} if { + input.action in {"read", "acknowledge", "accept", "return", "discuss", "decline"} + input.tenant == "tenant:platform" + input.subject.tenant == "tenant:platform" + input.subject.type == "human" + is_string(input.subject.id) + input.subject.id != "" + input.subject.attributes.principal_type_source == "authentication-derived" + input.subject.attributes.tenant_source in {"registration-supplied", "directory-asserted"} + "net-kingdom-admins" in input.subject.attributes.groups + assurance := input.subject.attributes.assurance + assurance.level == "aal2" + assurance.mfa == true + assurance.source == "key-cape" + assurance.methods == ["pwd", "otp"] + is_number(assurance.at) + assurance.at > 0 + age := time.now_ns() / 1000000000 - assurance.at + age >= -30 + age <= 900 + input.resource.tenant == "tenant:platform" + input.resource.system == "informed-decision" + input.resource.type == "decision-memo" + record := list_only_records[input.resource.id] + input.context.memo_version == record.memo_version + input.context.approval_id == record.approval_id + input.context.approval_binding_digest == record.binding_digest +} else := {"effect": "allow", "reason": "operator_admitted_overview_list"} if { + input.action == "list" + input.tenant == "tenant:platform" + input.subject.tenant == "tenant:platform" + input.subject.type == "human" + is_string(input.subject.id) + input.subject.id != "" + input.subject.attributes.principal_type_source == "authentication-derived" + input.subject.attributes.tenant_source in {"registration-supplied", "directory-asserted"} + "net-kingdom-admins" in input.subject.attributes.groups + assurance := input.subject.attributes.assurance + assurance.level == "aal2" + assurance.mfa == true + assurance.source == "key-cape" + assurance.methods == ["pwd", "otp"] + is_number(assurance.at) + assurance.at > 0 + age := time.now_ns() / 1000000000 - assurance.at + age >= -30 + age <= 43200 + input.resource.tenant == "tenant:platform" + input.resource.system == "informed-decision" + input.resource.type == "decision-memo" + record := listable[input.resource.id] + input.context.memo_version == record.memo_version + input.context.approval_id == record.approval_id + input.context.approval_binding_digest == record.binding_digest +} else := {"effect": "allow", "reason": "present_existing_eligible"} if { + present_existing_attempt + mfa_fresh + c2_assurance_ok + recipient_ok + resource_ok + review_group_ok + class_ok + submitter_ok + requester_ok + approval_action_ok + approver_distinct_ok + human_control_ok + approval_id_ok + digest_ok + version_ok +} else := {"effect": "allow", "reason": "attribute_eligible"} if { + attribute_attempt + not present_existing_attempt + mfa_fresh + recipient_ok + resource_ok + review_group_ok + class_ok + submitter_ok + requester_ok + human_control_ok + approval_id_ok + digest_ok + version_ok +} else := {"effect": "deny", "reason": "identity_refused"} if { + attribute_attempt + not identity_core +} else := {"effect": "deny", "reason": "stale_mfa"} if { + attribute_attempt + stale_mfa +} else := {"effect": "deny", "reason": "identity_refused"} if { + attribute_attempt + not mfa_fresh +} else := {"effect": "deny", "reason": "recipient_refused"} if { + attribute_attempt + not recipient_ok +} else := {"effect": "deny", "reason": "resource_refused"} if { + attribute_attempt + not resource_ok +} else := {"effect": "deny", "reason": "review_group_not_verified"} if { + attribute_attempt + not review_group_ok +} else := {"effect": "deny", "reason": "unknown_class"} if { + attribute_attempt + not class_ok +} else := {"effect": "deny", "reason": "unknown_submitter"} if { + attribute_attempt + not submitter_ok +} else := {"effect": "deny", "reason": "requester_mismatch"} if { + attribute_attempt + not requester_ok +} else := {"effect": "deny", "reason": "self_approval_refused"} if { + attribute_attempt + not approver_distinct_ok +} else := {"effect": "deny", "reason": "approval_action_not_admitted"} if { + attribute_attempt + not approval_action_ok +} else := {"effect": "deny", "reason": "identity_refused"} if { + attribute_attempt + not c2_assurance_ok +} else := {"effect": "deny", "reason": "human_control_false"} if { + attribute_attempt + not human_control_ok +} else := {"effect": "deny", "reason": "approval_id_malformed"} if { + attribute_attempt + not approval_id_ok +} else := {"effect": "deny", "reason": "digest_mismatch"} if { + attribute_attempt + not digest_ok +} else := {"effect": "deny", "reason": "memo_version_refused"} if { + attribute_attempt + not version_ok +} else := {"effect": "deny", "reason": "compact_sitting_scope_or_identity_refused"} if { + true +} +``` + +```rego test +package flexauth.informed_decision.compact_sitting_test +import rego.v1 +import data.flexauth.informed_decision.compact_sitting + +test_unknown_request_denied if { + compact_sitting.decision.effect == "deny" with input as {} +} + +test_listable_is_exact_union if { + count(compact_sitting.listable) == count(compact_sitting.records) + count(compact_sitting.list_only_records) +} + +test_t03_records_not_in_act_scope if { + every id, _ in compact_sitting.list_only_records { + not compact_sitting.records[id] + } +} + +tool_proof_input := { + "tenant": "tenant:platform", + "subject": {"id": "test-human", "type": "human", "tenant": "tenant:platform", + "attributes": {"principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", "groups": ["net-kingdom-admins"], + "assurance": {"level": "aal2", "mfa": true, "source": "key-cape", + "methods": ["pwd", "otp"], "at": time.now_ns() / 1000000000}}}, + "resource": {"tenant": "tenant:platform", "system": "informed-decision", + "type": "decision-memo", "id": "memo:infd-20260927-b02"}, + "action": "accept", + "context": {"memo_version": 1, + "approval_id": "1e73e514-ef74-4bff-83e7-fa88a3eaba0c", + "approval_binding_digest": "sha256:3e5951847e1909abe1561f561b4591228848a2e1a939b9bf5acab61e2db42773"}, +} + +test_tool_proof_exact_record_allowed if { + compact_sitting.decision.effect == "allow" with input as tool_proof_input +} + +test_tool_proof_wrong_native_id_denied if { + compact_sitting.decision.effect == "deny" with input as tool_proof_input + with input.context.approval_id as "another-approval" +} + +test_tool_proof_wrong_digest_denied if { + compact_sitting.decision.effect == "deny" with input as tool_proof_input + with input.context.approval_binding_digest as "sha256:wrong" +} + +test_tool_proof_without_mfa_denied if { + compact_sitting.decision.effect == "deny" with input as tool_proof_input + with input.subject.attributes.assurance.mfa as false +} + + +test_six_exact_metered_memos_allow_human_review if { + every id in ["memo:metered-20260927-provider-apply", "memo:metered-20260927-provider-verify", "memo:metered-20260927-provider-exec", "memo:metered-20260927-worker-apply", "memo:metered-20260927-worker-verify", "memo:metered-20260927-worker-exec"] { + record := compact_sitting.records[id] + request := object.union(tool_proof_input, { + "resource": object.union(tool_proof_input.resource, {"id": id}), + "context": {"memo_version": 1, "approval_id": record.approval_id, + "approval_binding_digest": record.binding_digest}, + }) + compact_sitting.decision.effect == "allow" with input as request + } +} + +attribute_input := { + "tenant": "tenant:platform", + "subject": {"id": "test-human", "type": "human", "tenant": "tenant:platform", + "attributes": {"principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", "groups": ["net-kingdom-admins"], + "assurance": {"level": "aal2", "mfa": true, "source": "key-cape", + "methods": ["pwd", "otp"], "at": time.now_ns() / 1000000000}}}, + "resource": {"tenant": "tenant:platform", "system": "informed-decision", + "type": "decision-memo", "id": "memo:intake-new-memo"}, + "action": "accept", + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 1, + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, +} + +patched_context(req, patch) := object.union(req, {"context": object.union(req.context, patch)}) + +with_at(req, at) := object.union(req, {"subject": object.union(req.subject, {"attributes": object.union(req.subject.attributes, {"assurance": object.union(req.subject.attributes.assurance, {"at": at})})})}) + +eligible(req) := result if { + result := compact_sitting.decision with input as req + with data.flexauth.informed_decision.compact_sitting.admitted_classes as {"credential-lane"} + with data.flexauth.informed_decision.compact_sitting.admitted_submitters as {"state-hub"} +} + +classes_only(req) := result if { + result := compact_sitting.decision with input as req + with data.flexauth.informed_decision.compact_sitting.admitted_classes as {"credential-lane"} +} + +test_initial_release_sets if { + compact_sitting.admitted_classes == {"decision-record"} + compact_sitting.admitted_submitters == {"state-hub"} + compact_sitting.decision.reason == "unknown_class" with input as attribute_input +} + +test_initial_release_allows_only_decision_record_class if { + every action in ["read", "acknowledge", "accept", "return", "discuss", "decline"] { + req := object.union(patched_context(attribute_input, {"memo_class": "decision-record"}), {"action": action}) + compact_sitting.decision.reason == "attribute_eligible" with input as req + } +} + +test_attribute_acts_are_eligible_only_when_sets_admit if { + every action in ["read", "acknowledge", "accept", "return", "discuss", "decline"] { + eligible(object.union(attribute_input, {"action": action})).reason == "attribute_eligible" + } +} + +test_allow_does_not_prove_the_pin if { + other := "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + attribute_input.context.approval_binding_digest != other + eligible(attribute_input).reason == "attribute_eligible" + eligible(patched_context(attribute_input, {"approval_binding_digest": other})).reason == "attribute_eligible" +} + +test_unknown_class if { + eligible(patched_context(attribute_input, {"memo_class": "not-a-class"})).reason == "unknown_class" +} + +test_unknown_submitter if { + classes_only(patched_context(attribute_input, {"intake_submitter": "unknown"})).reason == "unknown_submitter" +} + +test_requester_mismatch if { + eligible(patched_context(attribute_input, {"approval_requester": "other-service"})).reason == "requester_mismatch" +} + +test_human_control_false if { + eligible(patched_context(attribute_input, {"approval_human_control": false})).reason == "human_control_false" + eligible(patched_context(attribute_input, {"approval_human_control": "true"})).reason == "human_control_false" +} + +test_digest_mismatch if { + eligible(patched_context(attribute_input, {"approval_binding_digest": "sha256:wrong"})).reason == "digest_mismatch" + eligible(patched_context(attribute_input, {"approval_binding_digest": "sha256:C8A1A9C8FB22616E3AACFB877BA7974FEB5EEDAED491F856A6C90719C502EBE4"})).reason == "digest_mismatch" +} + +test_approval_id_malformed if { + eligible(patched_context(attribute_input, {"approval_id": "not-a-uuid"})).reason == "approval_id_malformed" +} + +test_memo_version_refused if { + eligible(patched_context(attribute_input, {"memo_version": 0})).reason == "memo_version_refused" +} + +test_stale_mfa_on_attribute_branch if { + eligible(with_at(attribute_input, time.now_ns() / 1000000000 - 901)).reason == "stale_mfa" +} + +test_future_mfa_is_identity_refused if { + eligible(with_at(attribute_input, time.now_ns() / 1000000000 + 300)).reason == "identity_refused" +} + +test_service_subject_is_identity_refused if { + eligible(object.union(attribute_input, {"subject": object.union(attribute_input.subject, {"type": "service"})})).reason == "identity_refused" +} + +test_review_group_must_be_verified if { + req := object.union(attribute_input, {"subject": object.union(attribute_input.subject, {"attributes": object.union(attribute_input.subject.attributes, {"groups": ["other-group"]})})}) + eligible(req).reason == "review_group_not_verified" +} + +test_memo_review_group_is_not_hardcoded if { + req := object.union(attribute_input, { + "subject": object.union(attribute_input.subject, {"attributes": object.union(attribute_input.subject.attributes, {"groups": ["cabinet-reviewers"]})}), + "context": object.union(attribute_input.context, {"review_group": "cabinet-reviewers"}), + }) + eligible(req).reason == "attribute_eligible" +} + +test_bad_memo_id_refused if { + eligible(object.union(attribute_input, {"resource": object.union(attribute_input.resource, {"id": "memo:NO"})})).reason == "resource_refused" +} + +test_list_requires_explicit_recipient if { + got := eligible(object.union(attribute_input, {"action": "list"})) + got.effect == "deny" + got.reason == "recipient_refused" +} + +list_input := patched_context(object.union(attribute_input, {"action": "list"}), {"review_recipient": "test-human"}) + +test_list_admits_recipient_with_twelve_hour_mfa if { + eligible(with_at(list_input, time.now_ns() / 1000000000 - 3600)).effect == "allow" + eligible(with_at(list_input, time.now_ns() / 1000000000 - 43201)).reason == "stale_mfa" +} + +test_list_refuses_other_recipient if { + eligible(patched_context(list_input, {"review_recipient": "other-human"})).reason == "recipient_refused" +} + +test_list_does_not_relax_read_or_accept if { + every action in {"read", "accept"} { + eligible(with_at(object.union(list_input, {"action": action}), time.now_ns() / 1000000000 - 3600)).reason == "stale_mfa" + } +} + +test_list_retains_approval_and_group_guards if { + eligible(patched_context(list_input, {"approval_human_control": false})).reason == "human_control_false" + eligible(patched_context(list_input, {"approval_requester": "other"})).reason == "requester_mismatch" + eligible(patched_context(list_input, {"review_group": "other"})).reason == "review_group_not_verified" + eligible(patched_context(list_input, {"memo_class": "other"})).reason == "unknown_class" +} + +test_exact_record_ignores_attribute_context if { + req := object.union(tool_proof_input, {"context": object.union(tool_proof_input.context, { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "view_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + })}) + got := compact_sitting.decision with input as req + got.effect == "allow" + got.reason == "operator_admitted_compact_sitting" +} + +test_exact_pin_mismatch_is_not_rescued if { + req := object.union(tool_proof_input, {"context": object.union(tool_proof_input.context, { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "approval_binding_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + })}) + got := eligible(req) + got.effect == "deny" + got.reason == "compact_sitting_scope_or_identity_refused" +} + +# v14 present-existing requester (FLEX-WP-0040-T07). + +pe_input := object.union(attribute_input, { + "subject": object.union(attribute_input.subject, {"id": "founder-human"}), + "context": object.union(attribute_input.context, { + "memo_class": "bounded-execution", + "intake_submitter": "nk-bind-executor", + "approval_requester": "service:nk-bind-executor", + "approval_action": "bounded-execution", + }), +}) + +with_subject_id(req, id) := object.union(req, {"subject": object.union(req.subject, {"id": id})}) + +with_assurance(req, patch) := object.union(req, {"subject": object.union(req.subject, {"attributes": object.union(req.subject.attributes, {"assurance": object.union(req.subject.attributes.assurance, patch)})})}) + +shipped(req) := result if { + result := compact_sitting.decision with input as req +} + +test_present_existing_profile_is_exactly_one_triple if { + compact_sitting.present_existing_profiles == {"service:nk-bind-executor": { + "intake_submitter": "nk-bind-executor", + "memo_class": "bounded-execution", + "approval_action": "bounded-execution", + }} + not "nk-bind-executor" in compact_sitting.admitted_submitters + not "bounded-execution" in compact_sitting.admitted_classes +} + +test_present_existing_allows_read_and_acts_with_shipped_sets if { + every action in ["read", "acknowledge", "accept", "return", "discuss", "decline"] { + got := shipped(object.union(pe_input, {"action": action})) + got.effect == "allow" + got.reason == "present_existing_eligible" + } +} + +test_present_existing_list_for_named_recipient if { + req := patched_context(object.union(pe_input, {"action": "list"}), {"review_recipient": "founder-human"}) + shipped(with_at(req, time.now_ns() / 1000000000 - 3600)).reason == "present_existing_eligible" + shipped(patched_context(req, {"review_recipient": "other-human"})).reason == "recipient_refused" + shipped(with_at(req, time.now_ns() / 1000000000 - 43201)).reason == "stale_mfa" +} + +test_present_existing_accept_needs_fresh_mfa if { + shipped(with_at(pe_input, time.now_ns() / 1000000000 - 901)).reason == "stale_mfa" +} + +test_present_existing_requires_aal2 if { + shipped(with_assurance(pe_input, {"level": "aal1"})).reason == "identity_refused" + shipped(with_assurance(pe_input, {"mfa": false})).reason == "identity_refused" + shipped(with_assurance(pe_input, {"methods": ["pwd"]})).reason == "identity_refused" +} + +test_present_existing_refuses_service_subject if { + shipped(object.union(pe_input, {"subject": object.union(pe_input.subject, {"type": "service"})})).reason == "identity_refused" +} + +test_present_existing_refuses_self_approval if { + shipped(with_subject_id(pe_input, "service:nk-bind-executor")).reason == "self_approval_refused" + shipped(with_subject_id(pe_input, "nk-bind-executor")).reason == "self_approval_refused" +} + +test_present_existing_self_approval_refused_for_list if { + req := patched_context(object.union(with_subject_id(pe_input, "service:nk-bind-executor"), {"action": "list"}), + {"review_recipient": "service:nk-bind-executor"}) + shipped(req).reason == "self_approval_refused" +} + +test_present_existing_requires_the_admitted_action if { + shipped(patched_context(pe_input, {"approval_action": "other-action"})).reason == "approval_action_not_admitted" + # object.union merges nested objects, so drop the context before replacing it. + without := object.union(object.remove(pe_input, ["context"]), {"context": object.remove(pe_input.context, ["approval_action"])}) + not without.context.approval_action + shipped(without).reason == "approval_action_not_admitted" +} + +test_present_existing_requires_its_own_submitter_and_class if { + shipped(patched_context(pe_input, {"intake_submitter": "state-hub"})).reason == "unknown_submitter" + shipped(patched_context(pe_input, {"memo_class": "decision-record"})).reason == "unknown_class" +} + +test_present_existing_retains_v13_guards if { + shipped(patched_context(pe_input, {"approval_human_control": false})).reason == "human_control_false" + shipped(patched_context(pe_input, {"review_group": "other"})).reason == "review_group_not_verified" + shipped(patched_context(pe_input, {"approval_id": "not-a-uuid"})).reason == "approval_id_malformed" + shipped(patched_context(pe_input, {"approval_binding_digest": "sha256:wrong"})).reason == "digest_mismatch" + shipped(patched_context(pe_input, {"memo_version": 0})).reason == "memo_version_refused" +} + +test_other_service_requester_still_mismatch if { + shipped(patched_context(pe_input, {"approval_requester": "service:other-executor"})).reason == "unknown_class" + eligible(patched_context(attribute_input, {"approval_requester": "service:other-executor"})).reason == "requester_mismatch" + # The registered requester never borrows another submitter's admitted class. + eligible(patched_context(attribute_input, {"approval_requester": "service:nk-bind-executor"})).reason == "unknown_class" +} + +test_executor_cannot_use_create_path if { + req := patched_context(pe_input, {"approval_requester": "informed-decision"}) + shipped(req).reason == "unknown_class" + shipped(patched_context(req, {"memo_class": "decision-record"})).reason == "unknown_submitter" +} + +test_informed_decision_requester_unchanged_by_action_or_subject if { + req := patched_context(attribute_input, {"memo_class": "decision-record", "approval_action": "anything"}) + shipped(req).reason == "attribute_eligible" + shipped(with_subject_id(patched_context(attribute_input, {"memo_class": "decision-record"}), "informed-decision")).reason == "attribute_eligible" +} + +``` diff --git a/examples/informed-decision-present-existing-review/registry.json b/examples/informed-decision-present-existing-review/registry.json new file mode 100644 index 0000000..0536a09 --- /dev/null +++ b/examples/informed-decision-present-existing-review/registry.json @@ -0,0 +1,17 @@ +{ + "subjects": [ + { + "id": "sitting-reviewer", + "type": "Human", + "display_name": "Compact sitting reviewer", + "organization_relation": "ServiceProvider", + "groups": ["net-kingdom-admins"], + "claims": { + "assurance": "aal2", + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied" + } + } + ], + "resources": [] +} diff --git a/internal/policy/informed_decision_present_existing_test.go b/internal/policy/informed_decision_present_existing_test.go new file mode 100644 index 0000000..f9156f2 --- /dev/null +++ b/internal/policy/informed_decision_present_existing_test.go @@ -0,0 +1,131 @@ +package policy_test + +import ( + "context" + "encoding/json" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/netkingdom/access-engine/internal/policy" + "github.com/netkingdom/access-engine/pkg/api" +) + +// FLEX-WP-0040-T07: compact-sitting v14 admits the present-existing requester +// service:nk-bind-executor (INFD-WP-0010, GH-DEC-2026-026 class C2). v13 stays +// in examples/informed-decision-lifecycle-review as the rollback reference. +func TestInformedDecisionPresentExistingV14(t *testing.T) { + dir := filepath.Join(repoRoot(t), "examples", "informed-decision-present-existing-review") + pkg, err := policy.LoadAndValidateFile(context.Background(), filepath.Join(dir, "policy.md")) + if err != nil { + t.Fatal(err) + } + if !pkg.Valid { + t.Fatalf("invalid package: %+v", pkg.Validation) + } + if pkg.Metadata.ID != "informed-decision.compact-sitting" || pkg.Metadata.Version != "v14" { + t.Fatalf("package %s %s", pkg.Metadata.ID, pkg.Metadata.Version) + } + raw, err := os.ReadFile(filepath.Join(dir, "pin.json")) + if err != nil { + t.Fatal(err) + } + var pin map[string]string + if err := json.Unmarshal(raw, &pin); err != nil { + t.Fatal(err) + } + if pin["package_digest"] != pkg.Digest() || pin["version"] != pkg.Metadata.Version || pin["package"] != pkg.Metadata.ID { + t.Fatalf("stale pin %+v; digest %s", pin, pkg.Digest()) + } + + base := func() map[string]any { + return map[string]any{ + "id": "v14", "tenant": "tenant:platform", + "subject": map[string]any{"id": "founder-human", "type": "human", "tenant": "tenant:platform", "attributes": map[string]any{ + "principal_type_source": "authentication-derived", "tenant_source": "registration-supplied", "groups": []any{"net-kingdom-admins"}, + "assurance": map[string]any{"level": "aal2", "mfa": true, "source": "key-cape", "methods": []any{"pwd", "otp"}, "at": time.Now().Unix()}, + }}, + "resource": map[string]any{"id": "memo:bea-bind-rotation-01", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"}, + "action": "accept", + "context": map[string]any{ + "review_group": "net-kingdom-admins", "memo_class": "bounded-execution", "intake_submitter": "nk-bind-executor", + "approval_requester": "service:nk-bind-executor", "approval_human_control": true, "approval_action": "bounded-execution", + "memo_version": 1, "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4", + }, + } + } + subject := func(d map[string]any) map[string]any { return d["subject"].(map[string]any) } + assurance := func(d map[string]any) map[string]any { + return subject(d)["attributes"].(map[string]any)["assurance"].(map[string]any) + } + ctx := func(d map[string]any) map[string]any { return d["context"].(map[string]any) } + tests := []struct { + name, effect, reason string + change func(map[string]any) + }{ + {"executor approval accepted by founder at aal2", "allow", "present_existing_eligible", func(map[string]any) {}}, + {"read", "allow", "present_existing_eligible", func(d map[string]any) { d["action"] = "read" }}, + {"actor may not approve its own approval", "deny", "self_approval_refused", func(d map[string]any) { subject(d)["id"] = "service:nk-bind-executor" }}, + {"submitter slug may not approve", "deny", "self_approval_refused", func(d map[string]any) { subject(d)["id"] = "nk-bind-executor" }}, + {"aal1 refused", "deny", "identity_refused", func(d map[string]any) { assurance(d)["level"] = "aal1" }}, + {"stale mfa refused", "deny", "stale_mfa", func(d map[string]any) { assurance(d)["at"] = time.Now().Unix() - 901 }}, + {"other approval action refused", "deny", "approval_action_not_admitted", func(d map[string]any) { ctx(d)["approval_action"] = "rotate" }}, + {"missing approval action refused", "deny", "approval_action_not_admitted", func(d map[string]any) { delete(ctx(d), "approval_action") }}, + {"other submitter refused", "deny", "unknown_submitter", func(d map[string]any) { ctx(d)["intake_submitter"] = "state-hub" }}, + {"other class refused", "deny", "unknown_class", func(d map[string]any) { ctx(d)["memo_class"] = "decision-record" }}, + {"unregistered service requester refused", "deny", "unknown_class", func(d map[string]any) { ctx(d)["approval_requester"] = "service:other" }}, + {"v13 informed-decision requester unchanged", "allow", "attribute_eligible", func(d map[string]any) { + ctx(d)["approval_requester"] = "informed-decision" + ctx(d)["intake_submitter"] = "state-hub" + ctx(d)["memo_class"] = "decision-record" + delete(ctx(d), "approval_action") + }}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := base() + tc.change(d) + encoded, _ := json.Marshal(d) + var request api.CheckRequest + if err := json.Unmarshal(encoded, &request); err != nil { + t.Fatal(err) + } + got, err := pkg.Evaluate(context.Background(), request) + if err != nil { + t.Fatal(err) + } + if string(got.Effect) != tc.effect || got.Reason != tc.reason { + t.Fatalf("got %s %s; want %s %s", got.Effect, got.Reason, tc.effect, tc.reason) + } + }) + } +} + +// A present-existing registry row is skipped by the generated sets: it is +// admitted only by the owner-authored present_existing_profiles. +func TestAdmissionGeneratorSkipsPresentExistingRows(t *testing.T) { + root := repoRoot(t) + dir := t.TempDir() + registry := filepath.Join(dir, "submitters.json") + body := `{ + "system:serviceaccount:sso:nk-bind-executor": { + "submitter_id": "nk-bind-executor", + "memo_classes": ["bounded-execution"], + "review_groups": ["net-kingdom-admins"], + "daily_limit": 10, + "present_existing": {"approval_actor": "service:nk-bind-executor", "actions": ["bounded-execution"]} + } + }` + if err := os.WriteFile(registry, []byte(body), 0o644); err != nil { + t.Fatal(err) + } + check := exec.Command("python3", filepath.Join(root, "tools/gen_informed_decision_admission.py"), + "--source", registry, "--policy", sittingPolicyPath(t), "--check") + if out, err := check.CombinedOutput(); err != nil || !strings.Contains(string(out), "0 submitters, 0 classes; in sync") { + t.Fatalf("present-existing row changed the generated sets: %v\n%s", err, out) + } +} diff --git a/tools/gen_informed_decision_admission.py b/tools/gen_informed_decision_admission.py index 51a83f5..1db9b3b 100644 --- a/tools/gen_informed_decision_admission.py +++ b/tools/gen_informed_decision_admission.py @@ -4,7 +4,8 @@ The compact-sitting attribute branch does not author who may pose. It copies submitter ids and the union of their memo classes from ``deploy/intake-submitters.json``. An empty registry generates empty sets, so -the branch admits nobody. A malformed registry refuses to write. +the branch admits nobody. Rows with a ``present_existing`` profile are skipped: +they are admitted only by the owner-authored ``present_existing_profiles``. A malformed registry refuses to write. Usage: python3 tools/gen_informed_decision_admission.py [--source PATH] @@ -48,7 +49,7 @@ def load_sets(path: Path) -> tuple[list[str], list[str]]: for username, row in document.items(): if not isinstance(username, str) or not SA.fullmatch(username) or not isinstance(row, dict): fail(f"refusing submitter row {username!r}") - if set(row) != ROW_KEYS: + if set(row) not in (ROW_KEYS, ROW_KEYS | {"present_existing"}): fail(f"refusing submitter row {username!r}: keys must be {sorted(ROW_KEYS)}") submitter_id = row["submitter_id"] if not isinstance(submitter_id, str) or not SLUG.fullmatch(submitter_id): @@ -60,6 +61,10 @@ def load_sets(path: Path) -> tuple[list[str], list[str]]: values = row[field] if not isinstance(values, list) or any(not isinstance(item, str) or not SLUG.fullmatch(item) for item in values): fail(f"refusing {field} for {username}") + if "present_existing" in row: + # Present-existing submitters are admitted only by the owner-authored + # present_existing_profiles (compact-sitting v14), never by these sets. + continue submitters.append(submitter_id) classes.update(row["memo_classes"]) if len(set(submitters)) != len(submitters): diff --git a/workplans/FLEX-WP-0040-bounded-execution-pdp-gate.md b/workplans/FLEX-WP-0040-bounded-execution-pdp-gate.md index 62b99bc..3aeb32c 100644 --- a/workplans/FLEX-WP-0040-bounded-execution-pdp-gate.md +++ b/workplans/FLEX-WP-0040-bounded-execution-pdp-gate.md @@ -119,6 +119,27 @@ Wait: FLEX-WP-0039 readiness surface and a founder rollout decision for a pin serving the pack, with the executor's declared fail-closed stance. Gate: founder-approved rollout; no live change from this repo's agents. +## 7. Review policy v14: present-existing requester for founder approval + +```task +id: FLEX-WP-0040-T07 +status: done +priority: high +``` + +informed-decision INFD-WP-0010 (`present_existing`, commit 0e66a33) lets the +executor's own approval be presented to the founder; v13 refuses it with +`requester_mismatch`. Compact-sitting v14 (`proposed`, +`sha256:8911458f36f1a60598aa19592a2d752a74dffe2617fd86238407ac87bc300fde`, +`examples/informed-decision-present-existing-review/`) keeps every v13 decision +and admits requester `service:nk-bind-executor` only from submitter +`nk-bind-executor`, memo class and approval action `bounded-execution`, a human +reviewer at KeyCape aal2, and never the approval actor itself (gate-house +GH-DEC-2026-026, class C2). Owner note: `docs/compact-sitting-v14-present-existing.md`. +Gate met: 46 embedded tests and the Go pin/evaluation tests pass; v13 untouched. +Live use waits on informed-decision carrying `approval_action` and a founder +release (image, values, consumer pin); no deployment from this task. + ## Founder decision 2026-10-31 Approved by the founder (`proposed` -> `ready`) after the NK-WP-0056-T02 owner answers (hub decision 19afd15e). The T06 pilot runs without the gate and records it as `not-evaluated`; a second admission needs this gate or a founder-recorded exception.