diff --git a/docs/batches/2026-09-14/OPERATOR.md b/docs/batches/2026-09-14/OPERATOR.md index 1c0fb1d..2849de8 100644 --- a/docs/batches/2026-09-14/OPERATOR.md +++ b/docs/batches/2026-09-14/OPERATOR.md @@ -63,6 +63,7 @@ Open in this order. One question each. No approve-all. — `live_accept` must stay `open`. 2. Owning requester creates eight `human_control=true` objects, required_count 1, no entries. Carry the native id and `binding.digest`; do not invent a digest. + Platform custody (unallocated CCR): `docs/sitting-requester-custody-request.md`. 3. `uv run python tools/attach_compact_bindings.py --principal --receipt ` writes `bound/` copies. Unsigned drafts stay unsigned. 4. Flex Auth admits a **new** package pinning those eight `memo:` ids to the diff --git a/docs/keycape-sitting-requester-registration.md b/docs/keycape-sitting-requester-registration.md index 1255bf8..b605a22 100644 --- a/docs/keycape-sitting-requester-registration.md +++ b/docs/keycape-sitting-requester-registration.md @@ -5,7 +5,8 @@ **Copied to:** `approval-engine` (`docs/keycape-service-registrations.md` — “no requester identity has been settled for `approval:create`”) **Status:** requested 2026-09-14. **Not registered. Not in OpenBao. No secret -is in this repository.** +is in this repository.** Custody request (no CCR id): +`docs/sitting-requester-custody-request.md`. This is the missing presenter for compact Decision Memo sittings whose protected side effect is a **work-record update** (`INFD-WP-0002-T04`), not a diff --git a/docs/sitting-requester-custody-request.md b/docs/sitting-requester-custody-request.md new file mode 100644 index 0000000..cbee7bb --- /dev/null +++ b/docs/sitting-requester-custody-request.md @@ -0,0 +1,39 @@ +# Sitting requester custody — requested, not allocated + +**Workplan:** `INFD-WP-0002-T03` +**Owner:** `railiance-platform` (OpenBao KV + ESO + attended reader) +**Peer:** `key-cape` (client row; not yet registered) +**Status:** requested 2026-09-14. **No CCR id is allocated here. No secret +is in this repository. Do not apply from this file.** + +Same split as `CCR-2026-0024` / `CCR-2026-0025` for +`secrets-engine-requester`: one KeyCape **verifier** path (ESO) and one +**attended operator reader**. Do not widen those CCRs or that KV path. + +## What to allocate + +Platform assigns the next CCR pair. Suggested shape, names only: + +| Piece | Suggested value | +| --- | --- | +| KV path | `platform/workloads/informed-decision/sitting-requester` | +| Field | `CLIENT_SECRET` only | +| Verifier policy | read that path; Kubernetes auth for `external-secrets` in `external-secrets` | +| Reader policy | read that path; OIDC attended operator; sibling paths and parent listing denied | +| ESO / env | `KEYCAPE_INFORMED_DECISION_SITTING_REQUESTER_CLIENT_SECRET` (already the `secretRef` name in the KeyCape request) | +| Workload | `informed-decision` | +| Tenant | `platform` / `tenant:platform` | + +Do not put `approval:approve` or `approval:consume` material on this path. +The human PKCE client stays public and secretless. + +## Activation conditions (when platform and key-cape accept) + +1. KeyCape owns `informed-decision-sitting-requester` (`applied: false` today). +2. Attended authority writes CAS=0 custody to the new path only. +3. Exact policy/auth readback; sibling `secrets-engine/approval-requester` denied. +4. Excess scopes (`approval:approve`, `approval:consume`, `approval:read`) refused at token exchange. +5. Wrong secret refused. No human entry synthesized. No POST of sitting intents until that proof exists. + +Registration contract: `docs/keycape-sitting-requester-registration.md`. +Create intents (not posted): `docs/batches/2026-09-14/approval-create-intents.json`. diff --git a/docs/sitting-requester-custody-request.yaml b/docs/sitting-requester-custody-request.yaml new file mode 100644 index 0000000..cfb74b7 --- /dev/null +++ b/docs/sitting-requester-custody-request.yaml @@ -0,0 +1,51 @@ +# Request only. railiance-platform allocates the CCR id. +# status is requested — not applied, not approved, no secret values. +id: unallocated +kind: credential-change-request +schema_version: 1 +request_type: workload-kv-read +title: Informed Decision sitting-requester KeyCape client secret (verifier + attended reader) +status: requested +created: "2026-09-14" +updated: "2026-09-14" +origin: INFD-WP-0002-T03 +origin_ref: informed-decision/docs/keycape-sitting-requester-registration.md +requester: + agent: grok + reason: >- + Compact sitting needs a create-only presenter whose binding.actor is + informed-decision. Do not widen CCR-2026-0024/0025 or secrets-engine-requester. +review: + required: true + required_approvers: + - platform-operator + - key-cape-owner +target: + domain: infotech + tenant: platform + workload: informed-decision + environment: production + purpose: create-only sitting requester; work-record decisions, not PEP consume +openbao: + mount: platform + kv_path: platform/workloads/informed-decision/sitting-requester + fields: + - CLIENT_SECRET +delivery: + surface: external-secrets + env_name: KEYCAPE_INFORMED_DECISION_SITTING_REQUESTER_CLIENT_SECRET +risk: + classification: high + notes: + - Scope must remain approval:create only. + - Human disposition uses informed-decision-approver (public PKCE). + - infd-20260914-c01 may stay on secrets-engine-requester if consume is in-scope. +verification: + negative: + - approval:approve and approval:consume refused at token exchange + - sibling secrets-engine/approval-requester denied + - parent listing denied + activation_conditions: + - KeyCape row exists + - attended CAS=0 write to this path only + - no sitting POST until exchange proof exists diff --git a/tests/test_sitting_requester_registration.py b/tests/test_sitting_requester_registration.py index 9f02438..7d79ef1 100644 --- a/tests/test_sitting_requester_registration.py +++ b/tests/test_sitting_requester_registration.py @@ -1,4 +1,5 @@ import json +import sys from pathlib import Path from informed_decision.records import memo_from @@ -45,3 +46,26 @@ def test_create_intents_cover_the_sitting_and_are_not_posted(): memo = memo_from(json.loads((batch_root / batch / f"{row['memo_id']}.memo.json").read_text())) assert binding["target"]["id"] == memo.binding.target.id assert memo.question.startswith(binding["purpose"][:20]) or binding["purpose"] in memo.question or memo.question.rstrip("?") in binding["purpose"] + + +def test_create_intents_match_approval_engine_canonical_binding(): + engine = Path("/home/worsch/approval-engine") + sys.path.insert(0, str(engine)) + from approval_engine.binding import BINDING_FIELDS, binding_digest, canonical_binding + + for row in INTENTS["intents"]: + canon = canonical_binding(row["binding"]) + assert tuple(canon) == BINDING_FIELDS + digest = binding_digest(canon) + assert digest.startswith("sha256:") and len(digest) == 71 + + +def test_custody_request_is_unallocated_and_does_not_widen_t03_path(): + text = (ROOT / "docs" / "sitting-requester-custody-request.yaml").read_text() + assert "status: requested" in text + assert "id: unallocated" in text + assert "kv_path: platform/workloads/informed-decision/sitting-requester" in text + assert "kv_path: platform/workloads/secrets-engine/approval-requester" not in text + md = (ROOT / "docs" / "sitting-requester-custody-request.md").read_text() + assert "Do not widen" in md + assert "No secret" in md diff --git a/workplans/INFD-WP-0002-compact-signoff-batches.md b/workplans/INFD-WP-0002-compact-signoff-batches.md index 7b8df9a..231da14 100644 --- a/workplans/INFD-WP-0002-compact-signoff-batches.md +++ b/workplans/INFD-WP-0002-compact-signoff-batches.md @@ -150,8 +150,12 @@ create-only confidential client (`informed-decision-sitting-requester`, `sub=informed-decision`, scope `approval:create` only, no approve/consume, no redirect). Intents for the eight bindings are in `docs/batches/2026-09-14/approval-create-intents.json` (`posted: false`; -`c01` create-client undecided). No secret, no POST, no bind. Task stays -`wait`. +`c01` create-client undecided). No secret, no POST, no bind. + +2026-09-14 23:00 UTC — **custody requested from railiance-platform, no CCR +id allocated.** `docs/sitting-requester-custody-request.md` asks for a new +KV path `platform/workloads/informed-decision/sitting-requester` (verifier + +attended reader), not a widening of CCR-2026-0024/0025. Task stays `wait`. ## Feed outcomes back to State Hub without hub-authoring