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 <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:
parent
18db517e00
commit
2de0f862f9
10 changed files with 1414 additions and 2 deletions
|
|
@ -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 |
|
||||
|
|
|
|||
98
docs/compact-sitting-v14-present-existing.md
Normal file
98
docs/compact-sitting-v14-present-existing.md
Normal file
|
|
@ -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…`).
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"package": "informed-decision.compact-sitting",
|
||||
"package_digest": "sha256:8911458f36f1a60598aa19592a2d752a74dffe2617fd86238407ac87bc300fde",
|
||||
"version": "v14"
|
||||
}
|
||||
973
examples/informed-decision-present-existing-review/policy.md
Normal file
973
examples/informed-decision-present-existing-review/policy.md
Normal file
|
|
@ -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"
|
||||
}
|
||||
|
||||
```
|
||||
|
|
@ -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": []
|
||||
}
|
||||
131
internal/policy/informed_decision_present_existing_test.go
Normal file
131
internal/policy/informed_decision_present_existing_test.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue