ops-mason/workplans/MASON-WP-0010-security-case-materialization.md
tegwick 20b92e2d46 MASON-WP-0010-T03: source review complete; attended owner checklist
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 903097@bnt-lap001
Assistant-Session: aa320c8f-c3fa-46e7-8541-0b58e1e45d23
2026-10-17 04:31:10 +02:00

15 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-11
net-kingdom
ops-warden
railiance-platform
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):

  1. Fix the path in the type. secret_ref should not be case-settable; a free pattern under platform/workloads/ lets every case pick its own layout. Derive it: platform/workloads/keycape/{client_id} (field CLIENT_SECRET), as for secrets-engine-openbao.
  2. Reuse the policy family. Existing readers are workload-kv-read-keycape-<client>-<reader>. Rename keycape-client-{client_id}-read to workload-kv-read-keycape-{client_id}-eso (KeyCape reads its client secret only through ESO), and {consuming_service}-oidc-client-{client_id}-read to workload-kv-read-keycape-{client_id}-{consuming_service}. Update verification custody-allowed-to-consumer and rollback to match.
  3. Template the policy body, not just its name. An openbao_policy item needs its rules (read on platform/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.
  4. Add the missing ESO role. deliver-secret needs a Kubernetes-auth role external-secrets-keycape-{client_id} (kind openbao_role, bound to the ESO service account, policy from item 2, 15m TTL, no default policy), as the reference placement has.
  5. Catalog naming. The one existing entry is rapp-qonto-keycape-client; prefer {consuming_service}-keycape-client over oidc-client-{client_id}.
  6. Rollback order. mint-secret "destroy" and client-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.

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):

  1. 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> to reviewed and rebuilds the bundle (source-only, worker may run).
  2. railiance-platform: supply independently authenticated metadata-only observations for ESO policy, external-secrets-keycape-forgejo role and KV layout, each marked created (with change receipt) or preexisting-matched, against the structure-request digest. No values.
  3. 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.
  4. ops-warden: authenticate the pointer-only catalog entry observation.
  5. Owner (railiance-platform) supplies the authenticating verifier seam and, with founder go, runs bind_status_transition to preview accepted -> materialized; Warden accepts the new raw case digest.
  6. Follow-through after materialization, not prerequisites: mint in custody, ESO delivery, first live verification (NK-WP-0048-T06).