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 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 63291@bnt-lap001
Assistant-Session: 8bd77868-ca68-4f49-bb1e-d539ecc0d703
This commit is contained in:
tegwick 2026-09-21 14:35:29 +02:00
parent 8fd572f766
commit aeb88faf7e
2 changed files with 260 additions and 3 deletions

View file

@ -35,13 +35,35 @@ ruling: GH-DEC-2026-017
# record, so there is no engine outage for a stance to govern until ops-mason # 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 # decides whether its build path consults access-engine at all. Publishing a
# fail-closed map today would describe behaviour the code does not have. # 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: pep_stance:
status: declared-gap status: declared-gap
gap_state: accepted
path: null path: null
blocked_on: >- blocked_on: >-
Undecided whether phase-4 builds consult access-engine (§6.4 obligation 1) Phase-4 builds do not consult access-engine (§6.4 obligation 1). The
or remain founder-approval-gated only; a stance map is only meaningful once founder accepted founder plan approval (activation=APPROVED) as the gate
that is settled. 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" review: "2026-12-21"
# §11: every direct Tooling contact, one entry each. Empty would be a claim. # §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 No engine exposes a policy / auth-role construction surface; ops-mason
runs the bao CLI under a scoped, time-boxed ops-mason-build token. runs the bao CLI under a scoped, time-boxed ops-mason-build token.
review: "2026-12-21" review: "2026-12-21"
gap_state: accepted
accepted_exception: *plan-approval-gate
- id: openbao-approle-credential-delivery - id: openbao-approle-credential-delivery
shape: "5.3" shape: "5.3"
module: src/ops_mason/executor.py module: src/ops_mason/executor.py
@ -72,6 +96,8 @@ tooling_contacts:
No engine exposes credential issuance for AppRole lanes; secrets-engine No engine exposes credential issuance for AppRole lanes; secrets-engine
owns credential lifecycle but fronts no issuance API today. owns credential lifecycle but fronts no issuance API today.
review: "2026-12-21" review: "2026-12-21"
gap_state: accepted
accepted_exception: *plan-approval-gate
- id: openbao-custody-metadata-read - id: openbao-custody-metadata-read
shape: "5.1" shape: "5.1"
module: scripts/custody-inventory.py module: scripts/custody-inventory.py
@ -98,6 +124,39 @@ tooling_contacts:
the contact rather than treating it as out of scope; raised with the contact rather than treating it as out of scope; raised with
gate-house as a catalog question. gate-house as a catalog question.
review: "2026-12-21" 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 - id: bao-session-grant
shape: "5.2" shape: "5.2"
module: scripts/bao-session.sh module: scripts/bao-session.sh

View file

@ -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: <commit>
sha256: <digest of the file at that commit>
```
Preflight, through the existing runner:
- `git -C <reef-railiance> show <revision>:bindings/rapps.yaml` must hash to
`sha256`;
- `git -C <reef-railiance> diff --quiet <revision> 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.