Eleven findings (R1-R11) on net-kingdom's proposed SecurityCaseType oidc-service-client 0.1 for request f61cc81e: type changes R3, R4, R6, R7, R8; ops-mason builder follow-ups R10; rollback addition R11. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 5502@bnt-lap001 Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
22 KiB
| id | type | title | domain | repo | status | flavor | owner | topic_slug | created | updated | depends_on | related_repos | state_hub_workstream_id | |||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| MASON-WP-0010 | workplan | Materialize SecurityCases from their type, with acceptance as the only gate | infotech | ops-mason | blocked | implementation | claude-code | netkingdom | 2026-10-08 | 2026-10-31 |
|
eabf2c39-94bd-5a11-8f2e-76d7a95d1583 |
MASON-WP-0010 — Materialize SecurityCases from their type, with acceptance as the only gate
Why
NetKingdom is introducing SecurityCaseTypes (net-kingdom NK-WP-0048, canon
security-case-types_v0.1, proposed). A type is designed once, with
documentation, tests and an informed-decision. Each case of an implemented type
is then light: it is accepted, ops-mason arranges it, and ops-warden follows
through.
ops-mason's four phases (construction plan, review, executive summary, build) stay exactly as they are for new types and for structure that no type covers. For a KnownSecurityCase, the construction plan is rendered from the type's materialization templates, and the executive-summary gate collapses to the case acceptance, because the shape was decided when the type was acknowledged.
MASON-WP-0009 (a hand-built placement of a key-cape client secret) is the kind of work this replaces. It becomes a reference instance for the first type.
Tasks
id: MASON-WP-0010-T01
status: done
depends_on: [NK-WP-0048-T03]
priority: high
state_hub_task_id: "3382ea7b-65ca-59a9-97ae-273455af44c2"
T01 — Define the type's materialization section
Propose the materialization part of the SecurityCaseType schema together with
net-kingdom: policy, role, KV path structure and catalog-entry templates over
case parameters, with naming rules. These must respect ops-mason's existing
"respect, extend or compact before adding" review, so that a type cannot
template sprawl. Write it for the first type, oidc-browser-relying-party, from
MASON-WP-0009 and the existing key-cape client custody paths.
Review (2026-10-08) of net-kingdom security-case-types/oidc-browser-relying-party/type.yaml 1.0.
The structure (four items, all but the catalog entry conditional on
confidential) is right. The names add three new families where existing ones
cover the need, so they fail "respect, extend or compact before adding".
Requested refinements, each from the live CCR-2026-0033 placement
(plans/keycape-secrets-engine-openbao-client-custody.md):
- Fix the path in the type.
secret_refshould not be case-settable; a free pattern underplatform/workloads/lets every case pick its own layout. Derive it:platform/workloads/keycape/{client_id}(fieldCLIENT_SECRET), as forsecrets-engine-openbao. - Reuse the policy family. Existing readers are
workload-kv-read-keycape-<client>-<reader>. Renamekeycape-client-{client_id}-readtoworkload-kv-read-keycape-{client_id}-eso(KeyCape reads its client secret only through ESO), and{consuming_service}-oidc-client-{client_id}-readtoworkload-kv-read-keycape-{client_id}-{consuming_service}. Update verificationcustody-allowed-to-consumerand rollback to match. - Template the policy body, not just its name. An
openbao_policyitem needs its rules (readonplatform/data/workloads/keycape/{client_id}, nothing else: no metadata, list, parent or sibling), or the review cannot check invariant (ii) and a rendered plan is not buildable. - Add the missing ESO role.
deliver-secretneeds a Kubernetes-auth roleexternal-secrets-keycape-{client_id}(kindopenbao_role, bound to the ESO service account, policy from item 2, 15m TTL, no default policy), as the reference placement has. - Catalog naming. The one existing entry is
rapp-qonto-keycape-client; prefer{consuming_service}-keycape-clientoveroidc-client-{client_id}. - Rollback order.
mint-secret"destroy" andclient-secret-path"remove" act on the same KV path; collapse to one step (metadata delete after the ExternalSecret is gone) so rollback cannot fail on a missing path.
With 1-4 applied, the section is acceptable to ops-mason for NK-WP-0048-T05.
2026-10-08: net-kingdom folded in all six (net-kingdom 074b3a0, message
508e2a12); its schema/validator now require a body on policy and role items.
NK-WP-0048-T05 still waits on WARDEN-WP-0044-T01 and a founder answer.
2026-10-09: the founder accepted memo r4; hub decision e418263e resolved and
the type is acknowledged (net-kingdom 0e86f95). T02 is unblocked.
Review (2026-10-31) of net-kingdom security-case-types/oidc-service-client/type.yaml 0.1
(proposed, net-kingdom 5d5f15d, memo nk-wp-0056-t14-oidc-service-client-r1,
hub decision f8044f81, request message f61cc81e). Checked against the
browser type 1.1, the live CCR-2026-0041/0042 placement and the bounded
executor's native lanes CCR-2026-0058/0059, and by a trial render of
examples/nk-bind-executor-approval.yaml through case_plan.render and
case_build on a scratch copy marked acknowledged (no live survey, no
OpenBao). All six items render; names come out as
workload-kv-read-keycape-nk-bind-executor-approval-{eso,bounded-executor},
external-secrets-keycape-nk-bind-executor-approval and catalog entry
bounded-executor-keycape-nk-bind-executor-approval.
Verdict: acceptable with changes. The structure is right and reuses the browser type's custody, ESO and policy families. R3, R4, R6, R7 and R8 change the type (so memo r2 replaces r1); R10 is ops-mason's own builder work and does not block acknowledgment, but must land before the first case is built.
- R1 — client-secret-path: ok. Derived, not case-settable, same
platform/workloads/keycape/{client_id}/CLIENT_SECRETas the browser type and CCR-2026-0033. Unconditional (the type is always confidential), so nowhen.client_idis unique in KeyCape, so browser and service clients cannot collide in the onekeycape/subtree. - R2 — keycape-reader-policy and naming family: ok. Unchanged from the
browser type;
workload-kv-read-keycape-<client>-<reader>is respected for both readers. Longest rendered name is 68 characters, within OpenBao limits. - R3 — eso-role: change needed (small). Bound to
external-secrets/external-secrets, one policy, 15m, no default policy: right. But the body omitsaudience: openbaoandtoken_explicit_max_ttl: 15m, which the consumer role states and the liveexternal-secrets-keycape-forgejorole has; ops-mason's builder fills them in silently today. Add both so the template is what gets built, and the two roles read the same. - R4 — consumer-reader-policy self-lifecycle stanzas: change needed. The
auth/token/lookup-self(read) andauth/token/revoke-self(update) stanzas are correct and needed (a native per-call reader should revoke its token after the read, and the platform applier refuses atoken_self_lifecycle: truepolicy without them, see T04 2026-10-10). But only the consumer policy declares them, while the ESO policy needs them equally. Declare them in both policy bodies, so each body is the complete policy the review checks (item 3 of the 1.0 review). ops-mason then stops appending them (R10). - R5 — consumer-reader-role (native Kubernetes auth): ok. One
ServiceAccount, one namespace, audience
openbao, 15m ttl/max/explicit max, one policy, no default policy: this is exactly the CCR-2026-0058/0059 shape that is live and verified forsso/nk-bind-executor, and "policies are never combined in one role" holds (the executor gets a third separate role). Role name equal to the policy name matches the native-lane convention. Dropping ESO delivery to the consumer removes a Secret copy and a SecretStore per case; ops-mason prefers it. - R6 — consumer binding parameters: change needed. The patterns admit
consumer_service_account: defaultand namespaces such asexternal-secrets,openbaoorkube-system. Refusedefault(any pod in the namespace would read the client secret; CCR-2026-0059 verifies the default SA is denied), and refuse theexternal-secretsandopenbaonamespaces, in the type's constraints or the validator. - R7 — shared namespace with KeyCape's copy: change needed. The first case
binds
sso/nk-bind-executor, andssois wheredeliver-secretwriteskeycape-{client_id}-client. So "no Secret copy in the consumer's namespace" (README, purpose) is false for this case; isolation rests on the consumer SA having no Secret read in its namespace (true today: rapp-bounded-executormanifests/rbac.yamlgrants no Secret access). Add a verification:{consumer_namespace}/{consumer_service_account}cannot get Secrets in its namespace (in particularsso/keycape-{client_id}-client),can-idenial as proof; and reword the README claim to "the type creates no Secret copy for the consumer". - R8 — verification wording: change needed (small).
custody-allowed-to-consumersays the policy grants read on the path "and nothing else"; with R4 it also grants token self-lookup/revoke. Say "and no other path underplatform/, apart from token self-lifecycle". Optionally add the same allow check for the ESO policy. - R9 — catalog-entry: ok, one question.
{consuming_service}-keycape-{client_id}instead of the browser type's{consuming_service}-keycape-clientis right: a service holds one client per audience, so the browser form would collide. Question:consuming_service: bounded-executordiffers from the workload name the platform already uses for this executor (net-kingdom-bind-executorin CCR-2026-0058/0059 and their catalog ids). The case should use the established name, or the type README should say which oneworkload_refcarries. - R10 — ops-mason builder (ours, not a type change). The trial build shows
four defects to fix before the first service-client case is built:
(a)
_policy_hclalways appends the self-lifecycle stanzas, so the consumer policy gets them twice; append only when absent. (b) A policy with a role is treated as ESO: the consumer CCR getsaccess_frontdoor.type: external-secrets; for a native role it must beplatform-owner(as CCR-2026-0059) with adelivery: {surface: native-workload-login, namespace, service_account}block. (c)catalog_idis hard-coded to{consuming_service}-keycape-client; take it from the catalog item. (d)audienceandtoken_explicit_max_ttlare hard-coded / copied fromtoken_max_ttl; take them from the role body (with R3). The consumer CCR is no longerin_flight: with the role in the type it is completeproposed,bound_claims_confirmed: falseuntil the platform owner confirms the SA. - R11 — rollback order: ok, with one addition. Consumer configuration,
registration, env binding, delivery, then custody (one metadata delete, after
the ExternalSecret is gone, per item 6 of the 1.0 review), roles before their
policies, catalog last: consistent with the browser type. Addition: deleting
the consumer role does not revoke issued tokens; state that rollback is
complete only after the consumer's last token has expired (at most 15m after
the role is deleted) or was revoked, as the T03 deactivate note does.
Suggested, not required: remove
consumer-reader-roledirectly afterhand-overis reversed, so consumer access ends first. (ops-mason's ownrollback.yamllistsremoves_exactlyin plain reverse order, which differs from the type's steps; fixed with R10.)
T04 is unchanged by this type: secrets-engine-openbao's second reader is an
attended OIDC operator, not a ServiceAccount, so it fits neither type.
id: MASON-WP-0010-T02
status: done
depends_on: [MASON-WP-0010-T01, NK-WP-0048-T05]
priority: high
state_hub_task_id: "fd385537-7378-5539-8a6e-f45851296f42"
T02 — Render a construction plan from a case
mason plan --case <case.yaml> validates the case against its implemented
type, renders the construction plan from the type's templates, and runs the
existing consistency review against current OpenBao structure (read-only, no
values). The output is the usual plan, marked derived-from-type, plus the
case-acceptance reference in place of a fresh executive summary.
Tests: render the reference case; refuse a case whose type is not
implemented; refuse parameter values outside the type's validation; flag a
collision with existing structure.
2026-10-09 done: ops-mason plan --case <case.yaml> [--out F] [--no-live]
(src/ops_mason/case_plan.py). Validation and rendering are net-kingdom's own
security_case_validator.py (imported, not copied), with provider declarations
loaded through its composer. ops-mason adds the review against existing
structure: a read-only live survey (policy list, role lists, KV metadata,
ops-warden catalog ids) that fails loudly instead of returning an empty
structure, and flags each collision as a finding. Output is a construction
plan with derived_from_type and render_digest, and section 5 is the case
acceptance; status reviewed only when accepted and collision-free. Exit 0
clean, 1 collisions, 2 refused. tests/test_case_plan.py (8, on a copy of the
type marked implemented); suite 167 passed. The live Forgejo case is refused
today, correctly: the type is acknowledged, not yet implemented.
2026-10-09: net-kingdom accepted the Forgejo case
(net-kingdom/security-cases/forgejo-netkingdom-oidc.yaml, message d1bbcb44).
As the reference case it renders with --allow-unimplemented, mirroring the
validator's flag: allowed only for an acknowledged type, marked
reference_case: true; and reviewed now requires a live survey. Rendered
live into plans/case-forgejo-netkingdom-oidc.md: status reviewed, no
collision, digest sha256:fb8b114d….
id: MASON-WP-0010-T03
status: progress
depends_on: [MASON-WP-0010-T02]
blocked_on: message-from:railiance-platform
blocking_reason: "Typed exact structural evidence request is prepared. Native materialization waits on independently authenticated policy/role/layout/catalog observations and CCR42 consumer binding/application. Mint, delivery and first live verification are follow-through, not structural prerequisites."
priority: high
state_hub_task_id: "56cea6be-c613-586c-80cd-125340b374da"
T03 — Build from an accepted case
mason build --case <case.yaml> builds a rendered plan when the case's
acceptance record is present, with no separate executive-summary approval. Its
audit trail names the case, the type version and the acceptance. It proposes the
pointer-only ops-warden catalog entries the type defines, as today. Rollback
removes exactly what this case created.
Acceptance: the Forgejo reference case (NK-WP-0048-T06) materializes through this path.
2026-10-09: implemented. ops-mason build --case <case> --plan <plan> [--out] [--allow-unimplemented] (src/ops_mason/case_build.py) refuses unless the
case is accepted, the plan is reviewed and its render digest equals a fresh
render. It writes a bundle and never touches OpenBao: one workload-kv-read CCR
draft per policy (ESO policy + Kubernetes role, mirroring CCR-2026-0033; the
consumer policy in_flight with openbao.auth missing until the consumer owner
binds it), the policy HCL, an ops-warden catalog proposal, rollback.yaml
(exactly what the case creates, reverse order) and a manifest with file hashes.
Every file names the case, type version, render digest and acceptance record;
the audit log gets a case-build-prepared record with the case acceptance as
approver. The drafts are checked with railiance-platform's own
credential-change.py validate. tests/test_case_build.py (6); suite 175
passed. Forgejo bundle built into builds/case-forgejo-netkingdom-oidc/,
platform validation pass; handed to railiance-platform (message 2c409503).
id: MASON-WP-0010-T04
status: wait
depends_on: [MASON-WP-0010-T03]
blocking_reason: "After T03, and only once a type fits: secrets-engine-openbao is a service (JWT) client, which oidc-browser-relying-party does not cover."
priority: medium
state_hub_task_id: "f6ad0d3c-8f47-5fdf-8115-6ca9d8a1fead"
T04 — Adopt MASON-WP-0009 as a case
Once oidc-browser-relying-party (or the confidential service client type) is
implemented, express MASON-WP-0009's placement as a case of that type without
changing the live structure, and close the hand-built path.
2026-10-10 (T03): railiance-platform filed the drafts as CCR-2026-0041 (ESO
reader) and CCR-2026-0042 (consumer reader, in_flight until railiance-apps binds
it), messages 83465dcd/be3a762d; the founder applied CCR-2026-0041 at
2026-10-09T18:09Z. Its review found a render defect: the CCRs declare
token_self_lifecycle: true but the policies lacked the lookup-self/revoke-self
stanzas, so the applier refused them. Fixed in case_build.py (test asserts
the three stanzas; output now equals the platform-generated policy apart from
comments) and in the committed bundle. Two follow-ups for the next render:
net-kingdom changed the type to KEYCAPE_{client_id:env}_CLIENT_SECRET
(ops-warden 23ba584), so the render digest will change; and a live re-render
of a partly applied case will report its own objects as collisions. The KV
value waits on mint-secret (RPF-WP-0054).
2026-10-10 (T03 follow-up): plan --case now recognises objects listed in the
case's own committed bundle (builds/case-<id>/rollback.yaml, same case id) as
"already present from this case's own build", not collisions; anything else
existing still collides (2 tests, suite 177 passed). The committed Forgejo plan
is stale: net-kingdom's {client_id:env} change moves the render digest from
fb8b114d… to 5e97931e…. Re-render it live under the next survey grant.
2026-10-10: Forgejo plan re-rendered live: reviewed, no collision, digest
af1f4f81…; policy workload-kv-read-keycape-forgejo-eso shown as already
present from this case's own bundle. The ESO role did not appear as present;
check on the next grant.
2026-10-10: checked. auth/kubernetes/role/external-secrets-keycape-forgejo
exists as applied (external-secrets/external-secrets, audience openbao, 15m,
policy workload-kv-read-keycape-forgejo-eso, no default policy). Why the plan's
"already present" list omitted it is not yet confirmed (the survey should list
auth/kubernetes/role); reproduce under the next grant. The collision review
is unaffected: it never flagged the role.
2026-10-10: type 1.1 (net-kingdom e2174ce, message af66538c) adds the
consumer-enforces-groups verification and tightens redirect URIs; the case is
re-pinned to @1.1. The live re-render above was already 1.1. Bundle rebuilt
from it (builds/case-forgejo-netkingdom-oidc/, platform validation pass);
the ESO policy is unchanged against the applied CCR-2026-0041, so 1.1 changes
no OpenBao object.
2026-10-11 accepted-architecture source implementation: materialization is
structure only under the already accepted canon; the stale mint-before-
materialization blocker is removed, not replaced by a fake status label.
src/ops_mason/case_structure.py and read-only ops-mason structure-request
validate a separate current structural projection against the hashed bundle
and prepare typed per-owner observations. Full execution drift remains visible:
35880bb adds --check-config to register-client, changing full render af1f4f81
→ e64b56e4 while materialization/composition/verification/rollback remain equal.
No prior execution approval is carried forward. Candidate receipt verification
requires independent owner/manual provenance and derives undo only from
verified creation receipts; planned rollback identities are not ownership.
Thirty-three relevant tests and ruff pass. Status-only raw-hash binding previews
accepted→materialized only after independent observations, preserving every
parameter/acceptance field; it writes and signs nothing. Details and exact native owner seams:
docs/forgejo-structural-materialization-source-v1.md. T03 remains progress;
case remains accepted@1.1, not materialized/live. CCR42 consumer auth remains
in_flight, and native owner evidence is not manufactured by source preparation.
2026-10-16 (T03 source compatibility): plan, build and
structure-request now expose explicit --types-root, passing it to the existing
exact-version NetKingdom API. Actual acknowledged 1.2 source renders the proposed
Forgejo case into a six-object draft, preserving its absent acceptance.
Six actual-source integration checks prove the root selection, dedicated consumer
namespace/SA, ordinary unimplemented refusal, proposed build refusal, historical
1.1 bundle refusal and malformed namespace refusal. Existing 44 plan/build/
structure tests pass. No live survey, execution, case/type status, acceptance,
native owner evidence or CCR was changed. T03 remains progress; actual accepted
1.2 case/reviewed source and authenticated native owner evidence remain required.
2026-10-17 (T03 source review): ops-mason source is complete for T03.
structure-request prepares the typed request; case_structure.verify and
bind_status_transition take an owner-supplied authenticating verifier by
design, so no CLI that trusts observation JSON is added. Every remaining step
is an owner observation or an attended native run.
Attended checklist (in order; none may be simulated):
- net-kingdom: accept the Forgejo case at type 1.2 (acceptance record in the
case); ops-mason then re-renders
plan --case ... --types-root <1.2>toreviewedand rebuilds the bundle (source-only, worker may run). - railiance-platform: supply independently authenticated metadata-only
observations for ESO policy,
external-secrets-keycape-forgejorole and KV layout, each marked created (with change receipt) or preexisting-matched, against thestructure-requestdigest. No values. - railiance-apps + railiance-platform: bind CCR-2026-0042 consumer
openbao.auth(namespace/SA), reconcile it from @1.0 to the current type, review and apply; supply its receipt. - ops-warden: authenticate the pointer-only catalog entry observation.
- Owner (railiance-platform) supplies the authenticating verifier seam and,
with founder go, runs
bind_status_transitionto preview accepted -> materialized; Warden accepts the new raw case digest. - Follow-through after materialization, not prerequisites: mint in custody, ESO delivery, first live verification (NK-WP-0048-T06).