NK-WP-0048-T05: take ops-warden R8 option 1; ensure-groups attended; memo r4

ensure-groups and its rollback run attended in the register-client session
(key-cape-oidc-login), inside the identity-provisioner pod: the provisioner's
only HTTP credential is user-engine's runtime token, so a caller-proxy lane
would act as user-engine. Lane identity-provisioner-groups is dropped.
Memo r4 supersedes r3; acknowledges ops-warden's WARDEN-WP-0044-T01 review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 469893@bnt-lap001
Assistant-Session: 49296f15-2171-4137-b972-2443bab95edb
This commit is contained in:
tegwick 2026-10-09 11:47:34 +02:00
parent fbcb76055d
commit 0e86f957a2
8 changed files with 88 additions and 40 deletions

View file

@ -40,7 +40,7 @@
| workplan | NK-WP-0043 | finished | — | workplans/NK-WP-0043-cadence-assessment-and-operating-guidance.md |
| workplan | NK-WP-0044 | finished | — | workplans/NK-WP-0044-runbook-packs-for-runbook-tutorials.md |
| workplan | NK-WP-0045 | finished | — | workplans/NK-WP-0045-root-grant-custody-record.md |
| workplan | NK-WP-0046 | active | — | workplans/NK-WP-0046-secrets-engine-openbao-client-registration.md |
| workplan | NK-WP-0046 | finished | — | workplans/NK-WP-0046-secrets-engine-openbao-client-registration.md |
| workplan | NK-WP-0047 | finished | — | workplans/NK-WP-0047-platform-password-reset-mail.md |
| workplan | NK-WP-0048 | active | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-ADHOC-2026-07-02-T01 | done | — | workplans/ADHOC-2026-07-02.md |

View file

@ -79,7 +79,8 @@ Secret, and agents do not read or write Secrets. For confidential clients,
`bind-env` is attended as well, being a change to the identity provider's
Deployment. KeyCape resolves a confidential client's secret from an environment
variable at start, so the binding (and `deliver-secret` before it) must exist
before registration. ops-warden groups both attended steps into one operator
before registration. `ensure-groups` is attended too (see Lanes below). ops-warden
groups the attended steps into one operator
session (WARDEN-WP-0044). The way to remove this
step is a KeyCape change: load clients from a non-secret registry and reference
confidential secrets from custody. That would be a 1.1 or 2.0 of this type.
@ -87,14 +88,17 @@ confidential secrets from custody. That would be a 1.1 or 2.0 of this type.
## Lanes, value handling and gates
Every step ops-warden executes or groups names its catalog lane
(`identity-provisioner-groups` and `keycape-client-secret-generate` are new and
must exist before the first case runs; the attended steps use the existing
`key-cape-oidc-login`). "ops-warden executes" means ops-warden invokes the owner's
(`keycape-client-secret-generate` is new and must exist before the first case
runs; the attended steps use the existing `key-cape-oidc-login`). "ops-warden executes" means ops-warden invokes the owner's
entry point through its caller proxy, under the caller's identity: it holds no
credential (canon section 3). `mint-secret` generates the value inside the
broker and custody (`value_handling: generated_in_custody`), so no value passes
through the run. `deliver-secret` gates `bind-env` with a status-only Ready check.
`ensure-groups` adds exactly the memberships recorded in the accepted case.
`ensure-groups` adds exactly the memberships recorded in the accepted case. It is
attended, in the same session as `register-client`, and runs inside the
provisioner pod: the provisioner's only HTTP credential is user-engine's runtime
token, so a caller-proxy lane would act as user-engine (ops-warden review R8). It
can become unattended once the provisioner has an operator credential of its own.
## Known limits

View file

@ -13,10 +13,11 @@ from pathlib import Path
HERE = Path(__file__).resolve().parent
HUB_DECISION = "e418263e-a71f-4f01-a970-c87c9e187c09"
# r3 supersedes r2 (built before ops-warden's review) and the first memo (built
# before ops-mason's review). Each revision needs a new id: the dispatcher
# r4 supersedes r3 (ensure-groups as a caller-proxy step; ops-warden R8), r2
# (built before ops-warden's review) and the first memo (built before ops-mason's
# review). Each revision needs a new id: the dispatcher
# refuses new content under a delivered idempotency key.
MEMO_ID = "nk-wp-0048-t05-oidc-browser-relying-party-r3"
MEMO_ID = "nk-wp-0048-t05-oidc-browser-relying-party-r4"
ITEM = f"{MEMO_ID}-packet"
packet = (HERE / "packet.md").read_text()

File diff suppressed because one or more lines are too long

View file

@ -4,11 +4,13 @@ Workplan: NK-WP-0048 (net-kingdom), task NK-WP-0048-T05
Hub decision: e418263e-a71f-4f01-a970-c87c9e187c09
Canon: canon/standards/security-case-types_v0.1.md (accepted 2026-10-08), section 5
Type: security-case-types/oidc-browser-relying-party/ (type.yaml, README.md, tests)
Memo: nk-wp-0048-t05-oidc-browser-relying-party-r3. It supersedes r2
(nk-wp-0048-t05-oidc-browser-relying-party-r2, which lacked ops-warden's review)
and the first memo (already returned). Please return r2 with a comment that r3
supersedes it, then answer this one. All three serve the same hub decision, and
the first accept or decline resolves it.
Memo: nk-wp-0048-t05-oidc-browser-relying-party-r4. It supersedes r3 (which
made `ensure-groups` an ops-warden caller-proxy step; ops-warden's addendum R8
showed the provisioner has no caller credential for that), r2 (which lacked
ops-warden's review) and the first memo (already returned). Please return any
of r2 and r3 still open with a comment that r4 supersedes them, then answer
this one. All memos serve the same hub decision, and the first accept or
decline resolves it.
## Question
@ -33,8 +35,9 @@ ops-mason materializes it and ops-warden follows through. The type becomes
the request's proposed `platform/workloads/forgejo/netkingdom-oidc-client`.
- It does not adopt the existing hand-built clients. Adoption, when it happens,
does not change their live registrations.
- It does not create the two catalog lanes listed below. They are needed before
the first case runs (T06), each through its own owner.
- It does not create the new catalog lane listed below
(`keycape-client-secret-generate`, railiance-platform). It is needed before
the first case runs (T06).
## The shape being acknowledged
@ -49,7 +52,7 @@ Steps, in order, each on its owner's entry point and its catalog lane:
| Step | Owner | Runs | Lane | Values | Confidential only |
| --- | --- | --- | --- | --- | --- |
| ensure-groups | net-kingdom (identity-provisioner) | ops-warden | identity-provisioner-groups (new) | none | no |
| ensure-groups | net-kingdom (identity-provisioner, in-pod call) | attended operator | key-cape-oidc-login | none | no |
| mint-secret | railiance-platform (credential broker) | ops-warden | keycape-client-secret-generate (new) | generated in custody | yes |
| deliver-secret | railiance-platform (delivery into the sso namespace) | owner; gate: Ready, status only | — | none | yes |
| bind-env | net-kingdom (KeyCape Deployment) | attended operator | key-cape-oidc-login | none | yes |
@ -99,9 +102,14 @@ exact inverse, in reverse order.
## Attended steps
`register-client` (and, for confidential clients, `bind-env`) need one attended
operator session, grouped by ops-warden on the existing `key-cape-oidc-login`
lane. KeyCape's live client registry is a Kubernetes Secret, and agents do not
`ensure-groups`, `register-client` (and, for confidential clients, `bind-env`)
need one attended operator session, grouped by ops-warden on the existing
`key-cape-oidc-login` lane. `ensure-groups` is attended because the
identity-provisioner's only HTTP credential is user-engine's runtime token and
its NetworkPolicy admits only user-engine pods: a caller-proxy lane would act as
user-engine (ops-warden R8). It runs inside the provisioner pod, and no token is
read. An operator credential of its own on the provisioner would make it
unattended in a later version. KeyCape's live client registry is a Kubernetes Secret, and agents do not
read or write Secrets. Removing this needs a KeyCape change (clients from a
non-secret registry, secrets referenced from custody). That would be a new
version of this type, with its own decision.

View file

@ -125,7 +125,9 @@ def test_confidential_case_renders_custody_delivery_and_one_attended_session():
order = [s["id"] for s in r["follow_through"]]
assert order == ["ensure-groups", "mint-secret", "deliver-secret", "bind-env", "register-client", "hand-over"]
steps = {s["id"]: s for s in r["follow_through"]}
assert [s["id"] for s in r["follow_through"] if s.get("attended")] == ["bind-env", "register-client"]
attended = [s for s in r["follow_through"] if s.get("attended")]
assert [s["id"] for s in attended] == ["ensure-groups", "bind-env", "register-client"]
assert {s["lane"] for s in attended} == {"key-cape-oidc-login"}
assert "keycape-client.py apply" in steps["register-client"]["entry_point"]
assert "sso/keycape-forgejo-client" in steps["deliver-secret"]["entry_point"]
assert steps["mint-secret"]["entry_point"].endswith("bao:platform/workloads/keycape/forgejo")
@ -140,7 +142,8 @@ def test_ops_warden_review_is_folded_in():
assert msgs(issues) == []
steps = {s["id"]: s for s in r["follow_through"]}
lanes = {k: s.get("lane") for k, s in steps.items()}
assert lanes["ensure-groups"] == "identity-provisioner-groups"
assert lanes["ensure-groups"] == lanes["register-client"]
assert steps["ensure-groups"]["attended"] and not steps["ensure-groups"]["warden_executes"]
assert lanes["mint-secret"] == "keycape-client-secret-generate"
assert lanes["bind-env"] == lanes["register-client"] == "key-cape-oidc-login"
assert steps["mint-secret"]["value_handling"] == "generated_in_custody"

View file

@ -156,12 +156,19 @@ spec:
follow_through:
- id: ensure-groups
owner: net-kingdom
entry_point: "identity-provisioner POST /v1/groups/ensure (groups {access_groups}; members exactly {initial_members} as recorded in the accepted case, nothing else)"
auth: "the caller's own identity-provisioner credential through its lane; ops-warden invokes it via the caller proxy and holds no token"
lane: identity-provisioner-groups
warden_executes: true
attended: false
entry_point: "identity-provisioner ensure_service_groups, run inside the provisioner pod through the session's contained cluster access (groups {access_groups}; members exactly {initial_members} as recorded in the accepted case, nothing else)"
auth: "operator cluster access in the attended register-client session; the in-pod call uses the provisioner's own directory binding, and no provisioner or user-engine token is read or sent"
lane: key-cape-oidc-login
warden_executes: false
attended: true
value_handling: none
attended_justification: >-
The provisioner's only HTTP credential is user-engine's runtime token, and
its NetworkPolicy admits only user-engine pods. A proxy lane would act as
user-engine (ops-warden review R8; canon section 3; ops-warden ADR-0002).
Running it in the session register-client already needs adds no attended
session. It can become unattended once the provisioner has an operator
credential of its own and an admitted path.
- id: mint-secret
owner: railiance-platform
when: "client_type == confidential"
@ -265,7 +272,7 @@ spec:
when: "client_type == confidential"
action: "covered by client-secret-path: one metadata delete, after deliver-secret is reversed"
- reverses: ensure-groups
action: "identity-provisioner POST /v1/groups/revoke with the case's memberships; delete_if_empty only if no other case uses {access_groups}"
action: "identity-provisioner revoke_service_groups inside the provisioner pod (attended, as ensure-groups) with the case's memberships; delete_if_empty only if no other case uses {access_groups}"
- reverses: client-secret-path
when: "client_type == confidential"
action: "delete metadata and all versions at platform/workloads/keycape/{client_id}, after the ExternalSecret is gone (this also reverses mint-secret)"

View file

@ -9,7 +9,7 @@ flavor: implementation
owner: claude-code
topic_slug: netkingdom
created: "2026-10-08"
updated: "2026-10-08"
updated: "2026-10-09"
depends_on: []
related_repos:
- ops-mason
@ -291,6 +291,31 @@ ops-mason's request to create LLDAP group `fluid-telegram-operators` (message
259ec26d) was answered: not possible from here by that path; the founder creates
it in the LLDAP admin UI.
2026-10-09: ops-warden finished its side of WARDEN-WP-0044-T01 (message
`58d33f40`, commit 0e7ceb8): the revised type conforms to `warden case
check-type`, with no further changes asked of the type. This acknowledges that
review. Its later addendum R8 (message `af274783`, ops-warden
`history/2026-10-08-oidc-rp-follow-through-review.md`) showed that lane
`identity-provisioner-groups` cannot exist as declared: the provisioner's only
HTTP credential is user-engine's runtime token, and its NetworkPolicy admits
only user-engine pods, so a caller-proxy lane would act as user-engine. Taken
option 1 (ops-warden's recommendation): `ensure-groups` and its rollback are
attended, in the `register-client` session on `key-cape-oidc-login`, run inside
the provisioner pod; no token is read. Option 2 (an operator credential of its
own on the provisioner) stays open for a later version. `warden case
check-type` now reports only `keycape-client-secret-generate`
(railiance-platform) as missing. 89 tests pass.
Hub decision `e418263e` was still open, so memo r4
(`nk-wp-0048-t05-oidc-browser-relying-party-r4`, packet `sha256:452ea421…`)
replaces r3; it passes informed-decision's and the dispatcher's validation. The
founder queues it with the same command, returns r2/r3 if still open with a
comment that r4 supersedes them, then answers r4.
Prerequisites for T06 are now: lane `keycape-client-secret-generate`
(railiance-platform), and a founder-approved rollout of an identity-provisioner
image containing 630ebfd.
```task
id: NK-WP-0048-T06
status: wait