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:
parent
c333063185
commit
8c03eb85c0
6 changed files with 123 additions and 3 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
39
docs/sitting-requester-custody-request.md
Normal file
39
docs/sitting-requester-custody-request.md
Normal 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`.
|
||||||
51
docs/sitting-requester-custody-request.yaml
Normal file
51
docs/sitting-requester-custody-request.yaml
Normal 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
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue