Request sitting-requester OpenBao custody without allocating a CCR.

Verifier + attended reader on a new informed-decision KV path, not a
widening of CCR-2026-0024/0025. Intents still match approval-engine
canonical binding. No secret, no apply, no bind.

Assistant: grok
Assistant-Session: 01a09dc1-b21e-77e1-919e-fcad2f82b267
This commit is contained in:
tegwick 2026-09-15 01:04:10 +02:00
parent c333063185
commit 8c03eb85c0
6 changed files with 123 additions and 3 deletions

View file

@ -63,6 +63,7 @@ Open in this order. One question each. No approve-all.
`live_accept` must stay `open`. `live_accept` must stay `open`.
2. Owning requester creates eight `human_control=true` objects, required_count 1, 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. 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 <exact-keycape-sub> --receipt <created.json>` 3. `uv run python tools/attach_compact_bindings.py --principal <exact-keycape-sub> --receipt <created.json>`
writes `bound/` copies. Unsigned drafts stay unsigned. writes `bound/` copies. Unsigned drafts stay unsigned.
4. Flex Auth admits a **new** package pinning those eight `memo:` ids to the 4. Flex Auth admits a **new** package pinning those eight `memo:` ids to the

View file

@ -5,7 +5,8 @@
**Copied to:** `approval-engine` (`docs/keycape-service-registrations.md` **Copied to:** `approval-engine` (`docs/keycape-service-registrations.md`
“no requester identity has been settled for `approval:create`”) “no requester identity has been settled for `approval:create`”)
**Status:** requested 2026-09-14. **Not registered. Not in OpenBao. No secret **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 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 protected side effect is a **work-record update** (`INFD-WP-0002-T04`), not a

View file

@ -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`.

View file

@ -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

View file

@ -1,4 +1,5 @@
import json import json
import sys
from pathlib import Path from pathlib import Path
from informed_decision.records import memo_from 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())) 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 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"] 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

View file

@ -150,8 +150,12 @@ create-only confidential client (`informed-decision-sitting-requester`,
`sub=informed-decision`, scope `approval:create` only, no approve/consume, `sub=informed-decision`, scope `approval:create` only, no approve/consume,
no redirect). Intents for the eight bindings are in no redirect). Intents for the eight bindings are in
`docs/batches/2026-09-14/approval-create-intents.json` (`posted: false`; `docs/batches/2026-09-14/approval-create-intents.json` (`posted: false`;
`c01` create-client undecided). No secret, no POST, no bind. Task stays `c01` create-client undecided). No secret, no POST, no bind.
`wait`.
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 ## Feed outcomes back to State Hub without hub-authoring