Canon section 3 defines warden_executes narrowly and adds per-step lane, value_handling and ready_check; catalog entries need a templated body. The type gets lanes, generated-in-custody minting, a Ready gate before bind-env, memberships only from the accepted case, mfa-pinned and secret-delivered checks, and the R4-prime catalog body. 110 tests pass. Memo r3 supersedes r2 and validates on both sides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 5457@bnt-lap001 Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
18 KiB
| id | type | title | domain | repo | status | flavor | owner | topic_slug | created | updated | depends_on | related_repos | state_hub_workstream_id | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| NK-WP-0048 | workplan | Establish SecurityCaseTypes so that known security cases are light | infotech | net-kingdom | active | implementation | claude-code | netkingdom | 2026-10-08 | 2026-10-08 |
|
e31f3f9d-0748-5137-b0a3-7b3ca46a66eb |
NK-WP-0048 - Establish SecurityCaseTypes so that known security cases are light
Why
The founder's direction (2026-10-08): setting up a security integration of a kind that has been done before should be easy, meaning accept the case, then ops-mason arranges it and ops-warden follows through. Setting up a new kind needs ample documentation and tests and an informed-decision, because its shape is part of the security architecture.
Today every integration is hand-built: per-client helper scripts, per-lane custody, founder checklists. The trigger was KEY-WP-0038 (Forgejo as an OIDC client), whose next step was going to be yet another hand-written rollout script.
This plan continues the pattern work of NK-WP-0008 (pattern infospace),
NK-WP-0009 (tutorials) and NK-WP-0030 (scenario composition). It turns patterns
into SecurityCaseTypes that ops-mason and ops-warden can execute. Canon:
canon/standards/security-case-types_v0.1.md (proposed).
Companion plans: ops-mason MASON-WP-0010 (materialization) and ops-warden
WARDEN-WP-0044 (follow-through).
Tasks
id: NK-WP-0048-T01
status: done
priority: high
state_hub_task_id: "930ac3e9-20ec-5473-98cd-3fe8fddb730f"
T01 — Accept the terminology and the canon
Founder review of canon/standards/security-case-types_v0.1.md, including its
section 10 open questions. On acceptance the canon status goes from proposed
to accepted.
2026-10-08: accepted by the founder (in session). Canon status accepted;
section 10's open questions are carried forward to the first type (T04, T05).
id: NK-WP-0048-T02
status: done
priority: high
state_hub_task_id: "19c25d96-a55c-58f0-9168-3fdbc4955fc2"
T02 — Inventory the de-facto types already built by hand
Read-only. List every security integration built so far and group them by shape. Known starting points:
- browser OIDC relying parties on key-cape: informed-decision-approver, coulomb-social, user-engine portal, openbao-admin, and Forgejo (pending)
- confidential service clients with
client_credentials: secrets-engine approval, requester and openbao clients, approval-engine operator - workload KV read lanes on OpenBao: the railiance-platform credential lane
designs and CCRs, for example
railiance-backup-offsite-lane - short-lived SSH certificates through ops-warden (
warden sign) - tunnelled operator access (ops-bridge)
For each: the repositories involved, the hand-built steps, attended human steps,
custody paths, verification evidence, and rollback (if any). Output
docs/security-case-type-inventory.md, ranking candidate types by how often the
shape recurs and how much manual work each instance costs today.
2026-10-08: done, docs/security-case-type-inventory.md. 36 of 37 CCRs are
already request_type: workload-kv-read, a de-facto type reviewed each time as
if it were new. Browser clients: 5 hand-built plus Forgejo, each with its own
rollout script. Confidential service clients need a registration plus two or
three KV-read CCRs each. A forge read identity is needed in bulk by
ORGREF-WP-0002-T06. Recommendation: oidc-browser-relying-party first (unblocks
the forge), workload-kv-read second (largest payoff), then forge-read-identity.
id: NK-WP-0048-T03
status: done
depends_on: [NK-WP-0048-T01]
priority: high
state_hub_task_id: "e487975b-73b1-516c-971d-da4cbbb93063"
T03 — Schema, template and validator
canon/schemas/security-case-type_v0.1.schema.jsonandsecurity-case_v0.1.schema.jsonsecurity-case-types/_template/withtype.yaml,README.mdand tests to copy- a validator, extending
tools/security-scenario-composer/, that checks a type, checks a case against its type, and renders the case into the composer's deterministic plan (stillexecution.permitted: false)
Acceptance: the validator rejects missing sections, secret-shaped parameter
values, unpinned providers and cases against non-implemented types, with tests
for each.
2026-10-08: done. canon/schemas/security-case-type_v0.1.schema.json and
security-case_v0.1.schema.json; tools/security-case-types/security_case_validator.py
(validate-type, validate-case, render, reusing the scenario composer);
security-case-types/_template/ (a valid, runnable example type over
net-kingdom.local-identity, with README sections, an example case and tests that
travel with every copy). 31 tests pass, including the composer's existing suite.
id: NK-WP-0048-T04
status: done
depends_on: [NK-WP-0048-T02, NK-WP-0048-T03]
priority: high
state_hub_task_id: "2fffe112-7d55-5680-9952-78d74f44f87f"
T04 — Define the first type: browser OIDC relying party
security-case-types/oidc-browser-relying-party/, version 1.0:
- parameters: client id, display name, exact redirect URIs, scopes, client type (confidential or public), MFA pin, the groups the consuming service maps, custody path for the secret (confidential only), consuming service and its owner
- composition: key-cape client registration (key-cape declares it as a playbook capability), secret minting into custody (railiance-platform), LLDAP group existence and membership (net-kingdom identity-provisioner), handover to the consuming service
- materialization (ops-mason) and follow-through (ops-warden) sections
- verification: discovery lists the redirect URI; a test sign-in returns the expected claims for one human; anonymous access to the custody path is denied
- rollback: remove exactly this client and its custody path
- README: threat model, MFA rationale, why redirect URIs are exact, which steps still need an attended login and how that is minimised
- reference instances: informed-decision-approver, coulomb-social, user-engine portal, openbao-admin
Acceptance: complete per canon section 3, tests green, status proposed.
2026-10-08: done. security-case-types/oidc-browser-relying-party/ 1.0 (proposed):
type.yaml with 12 parameters (the custody reference is required only for
confidential clients), composition over key-cape's new
key-cape.oidc-client-registration declaration (contract PASS, maturity: planned), materialization and follow-through, verification and rollback, all
conditional on client type where it matters. One attended step,
register-client, justified by the live registry being a Kubernetes Secret.
README with threat model, alternatives, an honest fit table (portal and vergabe
fit; Forgejo is the first confidential case; openbao-admin, coulomb.social and
informed-decision-approver do not fit 1.0), and known limits. Examples: Forgejo
(checked against the accepted request) and a public portal. The validator gained
when / required_when conditions and provider discovery. 43 tests pass.
ops-mason's and ops-warden's sections are drafted in the type. Their owners
review them in MASON-WP-0010-T01 and WARDEN-WP-0044-T01 before acknowledgment
(T05).
id: NK-WP-0048-T05
status: wait
depends_on: [NK-WP-0048-T04, MASON-WP-0010-T01, WARDEN-WP-0044-T01]
needs_human: true
blocking_reason: "A new SecurityCaseType is acknowledged as an informed-decision by the founder."
priority: high
state_hub_task_id: "3d987d3d-0797-538b-86e4-51185a327ae2"
T05 — Acknowledge the type through informed-decision
Prepare the Decision Memo from the type's README and type.yaml and submit it
through informed-decision's production intake. On acknowledgment the type
moves to acknowledged.
2026-10-08: Decision Memo prepared. Hub decision e418263e-a71f-4f01-a970-c87c9e187c09.
security-case-types/oidc-browser-relying-party/decision/: packet.md (question,
one act, what acknowledging does not do, the shape, alternatives, attended
steps, limits, evidence), build_intake.py and the built intake.json (memo
class decision-record, review group net-kingdom-admins, resolves the hub
decision on answer). The envelope passes informed-decision's own
validate_submission. Queuing it is an attended operator step, a write inside
the production dispatcher pod:
kubectl -n state-hub exec -i deploy/state-hub-memo-dispatch -- \
python -m api.services.memo_dispatch --queue /data/private enqueue --envelope /dev/stdin \
< security-case-types/oidc-browser-relying-party/decision/intake.json
The answer then appears for review in informed-decision.
2026-10-08: queued by the founder (pending, queued) after one refusal: the
first envelope's idempotency key was not State Hub's stable
state-hub.<decision>.<memo> form, and the builder now validates against both
sides. The dispatcher delivered it to informed-decision (ledger 7 → 8
delivered). T05 waits for the founder's answer in informed-decision, which
resolves hub decision e418263e.
2026-10-08: ops-mason's review (MASON-WP-0010-T01) folded into the type, still
1.0 proposed: secret_ref is no longer a parameter, the type derives
platform/workloads/keycape/{client_id}; policies are
workload-kv-read-keycape-{client_id}-eso and
workload-kv-read-keycape-{client_id}-{consuming_service}, each with a templated
body (read on that one data path); new openbao_role
external-secrets-keycape-{client_id} (Kubernetes auth, ESO service account,
15m, no default policy); catalog entry {consuming_service}-keycape-client;
the two rollback steps on the KV path collapse into one metadata delete. The
schema and validator now require a body on policy and role items. 105 tests
pass. Two consequences: the delivered memo describes the type before these
changes (a case-set custody reference, two differently named policies, no ESO
role), so the founder answers on a superseded shape unless the memo is
re-sent; and Forgejo's custody path moves from the accepted request's proposed
platform/workloads/forgejo/netkingdom-oidc-client to
platform/workloads/keycape/forgejo, which key-cape (KEY-WP-0038) and
railiance-apps (RAPPS-WP-0016-T02) need to adopt in T06. ops-warden's review
(WARDEN-WP-0044-T01) is still in progress.
2026-10-08: revised memo nk-wp-0048-t05-oidc-browser-relying-party-r2 built
(packet.md, intake.json, packet sha256:af6435e2…); it states that it
supersedes the first memo and passes informed-decision's and the dispatcher's
validation. It needs a new memo id because the dispatcher refuses new content
under a delivered idempotency key (idempotency_conflict). Queuing it is the
same attended step as above. Both memos serve hub decision e418263e; the
first answer resolves it, so the first memo must not stay open: an accept on it
would acknowledge the superseded shape, and a decline would resolve the
decision against r2. Close it with return (a reviewer verb that requests a
new revision and does not resolve the hub decision; only accept and decline
do), commenting that r2 supersedes it, then answer r2. withdraw exists in
the vocabulary but is not offered to reviewers, and lifecycle revise only
applies to lifecycle-registered memos, not intake-delivered ones.
2026-10-09: the founder reports r2 enqueued and the first memo returned. At
close, hub decision e418263e still showed open (not verified whether r2
was answered or the answer is still in transit; the dispatcher ledger was not
read). ops-warden's review (WARDEN-WP-0044-T01, ops-warden
history/2026-10-08-oidc-rp-follow-through-review.md, d470f2e) arrived at
21:25, before r2 was built, and is not in r2. Required before WARDEN-WP-0044-T02:
R1 a per-step lane (missing: identity-provisioner caller credential,
railiance-platform broker grant), R2 canon meaning of warden_executes: true
(caller proxy, caller identity, no standing ops-warden token; ADR-0002), R3 a
per-step value_handling. Refinements R4 (one type-level catalog lane instead
of a per-case entry, which conflicts with ops-mason's item 5), R5 (ESO Ready gate
before bind-env), R6 (exact accepted memberships), R7 (mfa-pinned and
secret-delivered checks). Next: if r2 was accepted, fold R1-R3 and R5-R7 in as
a follow-up and settle R4 between ops-warden and ops-mason before T06; if not,
hold r2, fold them in and build r3.
key-cape and railiance-apps were told about Forgejo's new custody
path.
2026-10-09: r2 was not answered (hub decision e418263e open; dispatcher 9
delivered), so r2 is held and r3 replaces it. ops-warden withdrew R4 for R4′
(message a66a9204): one catalog entry per case with its body templated in the
type. Folded in:
- Canon section 3: a narrow meaning of
warden_executes: true(caller proxy, caller identity, no standing credential; ops-warden ADR-0002), plus per-steplane,value_handlingand an optionalready_check. A catalog entry body is now required, like policy and role bodies. - Schema and validator enforce all of these.
- The type: lanes
identity-provisioner-groupsandkeycape-client-secret-generate(both new) andkey-cape-oidc-login;mint-secretisgenerated_in_custody; a Ready gate ondeliver-secret;ensure-groupsadds exactly the accepted case's memberships; new checkssecret-deliveredandmfa-pinned; the catalog-entry body follows R4′. - 110 tests pass.
Memo r3 (nk-wp-0048-t05-oidc-browser-relying-party-r3, packet
sha256:726dfc01…) passes informed-decision's and the dispatcher's validation.
The founder queues it with the same command, returns r2 with a comment that r3
supersedes it, then answers r3.
Prerequisites for T06, not for T05:
- the two new lanes, each through its owner;
- a founder-approved rollout of an identity-provisioner image containing
630ebfd(the deployed one is main-6c4fcaf from 2026-09-24, without/v1/groups/ensure).
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.
id: NK-WP-0048-T06
status: wait
depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
blocking_reason: "Implementation needs the acknowledged type and both executors' light paths."
priority: high
state_hub_task_id: "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
T06 — First KnownSecurityCase: Forgejo
Write security-cases/forgejo-netkingdom-oidc.yaml from the parameters the
founder accepted in KEY-WP-0038-T01 (key-cape/docs/forgejo-oidc-client-request.yaml),
so that acceptance carries over as the case acceptance. ops-mason materializes it;
ops-warden follows through and verifies it. The type becomes implemented once
this reference case is live.
This replaces the hand-built path proposed for KEY-WP-0038-T02.
id: NK-WP-0048-T07
status: wait
depends_on: [NK-WP-0048-T06]
blocking_reason: "The next types follow once the first one is proven end to end."
priority: medium
state_hub_task_id: "c3c9c3ae-4f23-5ba7-9e6f-e63bcf29b9f7"
T07 — Queue the next types
From the T02 ranking, open the next type drafts (likely the confidential service client and the workload KV read lane), each to be acknowledged on its own. Record which existing hand-built instances get adopted as cases, without rewriting them in place.
id: NK-WP-0048-T08
status: done
priority: high
state_hub_task_id: "8e519082-dc8b-5e46-b660-03d03e55dcf6"
T08 — identity-provisioner: ensure service-access groups
The type's ensure-groups step calls identity-provisioner groups ensure --groups <list> --members <group=user+user;...>. The provisioner already has idempotent
_ensure_group and _add_group, but exposes only tenant-scoped operations. Add
the general operation (create if absent, add the listed memberships, never
remove others), its rollback (remove only this case's memberships), tests, and
the ops-warden route for its service identity.
2026-10-08: done. identity-provisioner/provisioner.py gained
ensure_service_groups (POST /v1/groups/ensure) and revoke_service_groups
(POST /v1/groups/revoke), behind the existing service-token check. Names
matching tenant*, netkingdom-* and lldap_* are reserved; members must
already exist; everything is validated before any change; memberships are read
back before success; nothing else is ever removed; groups are deleted only when
asked and empty. 7 new tests (39 in the provisioner suite). The type's
ensure-groups step and its rollback now name these endpoints. The ops-warden
route for the provisioner's service token belongs to WARDEN-WP-0044-T01.
id: NK-WP-0048-T09
status: done
priority: high
state_hub_task_id: "0ee12dfc-8260-5038-bd39-cc7fb4113be0"
T09 — The keycape-client entry point and its declaration
Planned first as key-cape KEY-WP-0038-T04. Moved here because the machinery a
live registration needs already lives in net-kingdom, where KeyCape is deployed
(sso-mfa/k8s/keycape/): the contained kubectl, the cluster-identity pin and the
issuer pin. A copy in key-cape would drift.
2026-10-08: done. sso-mfa/k8s/keycape/keycape-client.py derives the KeyCape
client record from a case (client-from-case). It adds or removes exactly one
client with the existing safeguards (byte-preserving merge, exact match or
refuse, shared-reference refusal, compare-and-swap, read-back, contained errors),
requires the Deployment env binding first for confidential clients, and requires
the case acceptance record for apply instead of a per-client founder decision
id. 15 tests, including a regression showing that the vergabe case reproduces
its hand-built registration exactly; all 71 helper tests pass. Declaration
capabilities/playbooks/net-kingdom.keycape-client-registration.yaml (contract
PASS, maturity: implemented) replaces key-cape's draft. The type now pins it,
and for confidential clients adds deliver-secret (delivery from custody into
the sso namespace) and bind-env (Deployment env), attended in the same session
as register-client. The per-client helpers stay until their clients are
adopted as cases.