From aeb88faf7e46c0b956985d5f7e80c0bfc71cda87 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 21 Sep 2026 14:35:29 +0200 Subject: [PATCH] Record the founder's plan-approval and Kubernetes change-gate decisions. INTENT.md: the pep_stance gap and the three phase-4 write contacts are marked accepted under the founder's plan-approval exception (GOVERN @ estate, 2026-09-21, review 2026-12-21). They stay declared; no stance map is published. kubernetes-plane-apply records the readiness-tiered change gate and states that phase 4 does not enforce it yet. MASON-WP-0006 plans the readiness-tier check. It waits on a founder answer for the whitehat plane, which has no readiness_state and so defaults to production. Co-Authored-By: Claude Opus 5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 63291@bnt-lap001 Assistant-Session: 8bd77868-ca68-4f49-bb1e-d539ecc0d703 --- INTENT.md | 65 +++++- .../MASON-WP-0006-readiness-tier-check.md | 198 ++++++++++++++++++ 2 files changed, 260 insertions(+), 3 deletions(-) create mode 100644 workplans/MASON-WP-0006-readiness-tier-check.md diff --git a/INTENT.md b/INTENT.md index fd7c088..46b3837 100644 --- a/INTENT.md +++ b/INTENT.md @@ -35,13 +35,35 @@ ruling: GH-DEC-2026-017 # record, so there is no engine outage for a stance to govern until ops-mason # decides whether its build path consults access-engine at all. Publishing a # fail-closed map today would describe behaviour the code does not have. +# +# 2026-09-21: the founder, exercising GOVERN @ estate, settled the open option: +# phase-4 builds remain founder-approval-gated only (activation=APPROVED by +# plan.is_approved()). That is a founder-accepted exception to §6.4, not +# conformance and not a change to the standard. The gap stays declared; it is +# now accepted rather than undecided. No stance map is published: the code does +# not consult access-engine, so a map describing access-engine gating would +# describe behaviour ops-mason does not have. pep_stance: status: declared-gap + gap_state: accepted path: null blocked_on: >- - Undecided whether phase-4 builds consult access-engine (§6.4 obligation 1) - or remain founder-approval-gated only; a stance map is only meaningful once - that is settled. + Phase-4 builds do not consult access-engine (§6.4 obligation 1). The + founder accepted founder plan approval (activation=APPROVED) as the gate + instead, so there is no engine outage for a stance map to govern. + accepted_exception: &plan-approval-gate + decision: the-custodian/docs/ops-mason-plan-approval-gate-decision.md + decided_by: "Bernd Worsch (founder), GOVERN @ estate" + decided_at: "2026-09-21" + gate: "activation=APPROVED: founder approval of the construction plan (plan.is_approved())" + covers: >- + The phase-4 writes declared here at ops-mason@0ff263a: OpenBao policies, + auth roles on auth/approle and auth/kubernetes, AppRole secret_id + creation, and kubectl apply. A new class of protected change is not + covered until the founder accepts it. + holds_while: "Changes stay structure-only; no secret value is read, written or logged." + conformance_report: accepted-exception + review: "2026-12-21" review: "2026-12-21" # §11: every direct Tooling contact, one entry each. Empty would be a claim. @@ -59,6 +81,8 @@ tooling_contacts: No engine exposes a policy / auth-role construction surface; ops-mason runs the bao CLI under a scoped, time-boxed ops-mason-build token. review: "2026-12-21" + gap_state: accepted + accepted_exception: *plan-approval-gate - id: openbao-approle-credential-delivery shape: "5.3" module: src/ops_mason/executor.py @@ -72,6 +96,8 @@ tooling_contacts: No engine exposes credential issuance for AppRole lanes; secrets-engine owns credential lifecycle but fronts no issuance API today. review: "2026-12-21" + gap_state: accepted + accepted_exception: *plan-approval-gate - id: openbao-custody-metadata-read shape: "5.1" module: scripts/custody-inventory.py @@ -98,6 +124,39 @@ tooling_contacts: the contact rather than treating it as out of scope; raised with gate-house as a catalog question. review: "2026-12-21" + gap_state: accepted + accepted_exception: *plan-approval-gate + # 2026-09-21, founder (GOVERN @ estate): no engine owns this contact. The + # gate on ADMINISTER @ realm:kubernetes/railiance01 is a quality gate, not + # an authorization decision, tiered by the target's railiance-master + # ADR-0006 readiness_state. The owner question above is answered: the + # Kubernetes API stays a Tooling contact owned by rail-kubernetes. + change_gate: + decision: the-custodian/docs/kubernetes-change-gate-decision.md + decided_by: "Bernd Worsch (founder), GOVERN @ estate" + decided_at: "2026-09-21" + engine_owner: none + tooling_owner: rail-kubernetes + tiers: + - readiness_state: [declared, installed, verified] + path: "direct ADMINISTER @ realm:kubernetes by ops-mason" + activation: APPROVED + evidence_boundary: target-audited + - readiness_state: [production-approved] + path: "CONSTRUCT @ manifest repository, reconciled by ArgoCD (railiance-platform)" + activation: APPROVED + evidence_boundary: external-audited + - readiness_state: [production-approved] + path: "direct ADMINISTER, emergency only; recorded and reconciled back into the manifest repository" + activation: BREAK_GLASS + evidence_boundary: target-audited + no_readiness_state: production-tier + transition: + target: rapp-policy-nexus + until: "2026-12-21" + rule: "Direct ADMINISTER under activation=APPROVED, each change recorded as production-tier, until ArgoCD onboarding." + relies_on_limits: "One expected namespace per plan; Pod and Secret kinds refused; no data or stringData. Widening them is a new decision." + enforcement: "Not yet in code: phase 4 does not check readiness_state. Planned in workplans/MASON-WP-0006-readiness-tier-check.md." - id: bao-session-grant shape: "5.2" module: scripts/bao-session.sh diff --git a/workplans/MASON-WP-0006-readiness-tier-check.md b/workplans/MASON-WP-0006-readiness-tier-check.md new file mode 100644 index 0000000..cece6be --- /dev/null +++ b/workplans/MASON-WP-0006-readiness-tier-check.md @@ -0,0 +1,198 @@ +--- +id: MASON-WP-0006 +type: workplan +title: "Check the target's readiness tier before a direct Kubernetes apply" +domain: infotech +repo: ops-mason +status: proposed +flavor: implementation +owner: claude +topic_slug: ops-mason +created: "2026-09-21" +updated: "2026-09-21" +--- + +# MASON-WP-0006 — Check the target's readiness tier before a direct Kubernetes apply + +## Goal + +Make phase 4 enforce the founder's Kubernetes change-gate decision +(`the-custodian/docs/kubernetes-change-gate-decision.md`, 2026-09-21, the +founder exercising `GOVERN @ estate`). Before any direct +`ADMINISTER @ realm:kubernetes/railiance01`, `ops-mason plane apply` must +establish the target's railiance-master ADR-0006 `readiness_state` and: + +- allow a direct apply under `activation=APPROVED` (founder plan approval) when + the target is `declared`, `installed` or `verified`; +- refuse a direct apply to a production-tier target (`production-approved`, or + no readiness state at all), except: + - under `activation=BREAK_GLASS`, which is recorded and must be reconciled + back into the manifest repository afterwards; or + - for `rapp-policy-nexus` under the transition, which runs until 2026-12-21. + Each such change keeps `activation=APPROVED` and is recorded as + production-tier, so the transition does not look like conformance. + +The production path itself (`CONSTRUCT @ manifest repository`, reconciled by +ArgoCD) belongs to railiance-platform. ops-mason only refuses; it does not +build that path. + +## Boundaries + +- **The executor's limits are not widened.** One expected namespace per plan, + `Pod` and `Secret` refused, no `data` or `stringData`. The decision relies on + them; widening any of them is a new founder decision. +- The readiness check is a quality gate, not an authorization decision. It does + not consult access-engine and must not be described as a PDP. +- No live change is part of this workplan. Tests use the injected runner. + +## Design choice: how ops-mason obtains the readiness state + +The authoritative states live in another repository, +`reef-railiance/bindings/rapps.yaml`. Three options were weighed: + +1. **Read the sibling checkout at apply time.** Simple, but unpinned: the + evidence would name no revision, and the result depends on whatever the + workstation happens to have checked out. +2. **Vendor a copy of `rapps.yaml` into ops-mason.** Pinned, but it goes stale + silently. A rApp promoted to `production-approved` upstream would still read + as `verified` here and pass the gate: the failure mode is fail-open. +3. **Pin the reference in the bundle and verify it against the source at + preflight (chosen).** The bundle names the target and pins the readiness + source the same way it already pins manifests: + + ```yaml + readiness: + target: {kind: rapp, rapp_id: rapp-user-engine} # or {kind: platform} + source: + repo: reef-railiance + path: bindings/rapps.yaml + revision: + sha256: + ``` + + Preflight, through the existing runner: + - `git -C show :bindings/rapps.yaml` must hash to + `sha256`; + - `git -C diff --quiet HEAD -- bindings/rapps.yaml` + must show the file unchanged since the pin, so a later promotion cannot be + missed. If the file changed, the bundle is re-pinned through review; + - the `readiness_state` for the target is read from the pinned content. + + **Fail closed.** A missing `readiness` block, `kind: platform`, a target not + listed in `rapps.yaml`, a source that cannot be found or verified, or an + unknown state value all resolve to the production tier. The decision says + platform objects without a readiness state default to production; an + unverifiable state is treated the same way. + + The checkout location comes from a `--readiness-repo` CLI option, defaulting + to the sibling path `../reef-railiance`. It is a location, not a trust + anchor: the pinned revision and digest are. + +The evidence record gains a `readiness` section: target, source repo, revision +and digest, resolved state, tier, `activation`, and a `transition` flag when the +policy-nexus rule was used. + +## Open question for the founder: the whitehat plane + +```task +id: MASON-WP-0006-T01 +status: wait +priority: high +``` + +`bundles/whitehat-foundational-plane.yaml` is the only bundle today, and its plan +is `built`. It targets the `whitehat` namespace, which is not a rApp and carries +no ADR-0006 `readiness_state`. Under the decision it therefore defaults to the +production tier, and once T03 lands, a further direct apply of it would be +refused. + +This workplan was told the check must not change behaviour for today's +non-production plans. Whether the whitehat plane is one of them is not a +question ops-mason can answer for itself. The founder chooses one of: + +- (a) it is a platform object and production-tier, as the default says; future + changes to it go through the manifest repository or `BREAK_GLASS`; +- (b) it gains a readiness state from its owner (whitehat-security), in which + case `readiness.target` names that record; or +- (c) an explicit, dated exception. + +T03 must not merge before this is answered. + +## Extend the bundle schema with the readiness block + +```task +id: MASON-WP-0006-T02 +status: todo +priority: high +``` + +Add the optional `readiness` block to `PlaneBundle.load` as described above. It +is included in the bundle digest, as the bundle file already is. Add a +`--activation {APPROVED,BREAK_GLASS}` option to `plane apply`, defaulting to +`APPROVED`, and a `--readiness-repo` option. `BREAK_GLASS` requires a +`--break-glass-reason` string. No existing refusal is relaxed. + +## Enforce the tier in preflight and apply + +```task +id: MASON-WP-0006-T03 +status: todo +priority: high +``` + +Add a pure function, `resolve_tier(state, target, activation, today)`, returning +the tier and whether a direct apply is allowed, and call it from `apply` after +the plan-approval and digest checks and before any `kubectl` write. `preflight` +reports the resolved tier without refusing, so a production-tier plane can +still be validated. + +Tests, all against the injected runner: + +- a `verified` rApp target applies under `APPROVED` (non-production tier); +- a `production-approved` target is refused under `APPROVED`, with no `kubectl + apply` call made; +- the same target is allowed under `BREAK_GLASS`, and the evidence records it; +- `rapp-policy-nexus` is allowed under `APPROVED` on 2026-12-20, is recorded + with `transition: true` and tier production, and is refused on 2026-12-22; +- no `readiness` block, `kind: platform`, an unlisted rApp, a digest mismatch, + and a file changed since the pin each resolve to production and are refused; +- the existing forbidden-kind, `data`/`stringData` and namespace tests still pass + unchanged. + +## Record BREAK_GLASS and its reconcile-back obligation + +```task +id: MASON-WP-0006-T04 +status: todo +priority: medium +``` + +A `BREAK_GLASS` apply writes its reason, actor and time into the evidence +record and prints the follow-up that is owed: the same change, committed to the +manifest repository that ArgoCD reconciles. ops-mason does not open that change +itself. + +## Update the docs and the declaration + +```task +id: MASON-WP-0006-T05 +status: todo +priority: medium +``` + +Update `docs/kubernetes-plane.md` with the tier table and the readiness block, +and change the `enforcement` line of the `kubernetes-plane-apply` entry in +`INTENT.md` from "not yet in code" to point at the check. + +## Review on 2026-12-21 + +```task +id: MASON-WP-0006-T06 +status: wait +priority: medium +``` + +On 2026-12-21 the policy-nexus transition ends and the plan-approval exception +comes up for review. Confirm with railiance-platform that policy-nexus is +onboarded to ArgoCD. The date check in T03 ends the transition in code on that +date either way.