the-custodian/docs/kubernetes-change-gate-decision.md
codex e98dd26153
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Place the whitehat namespace in the non-production tier (founder).
An explicit placement overrides the unmapped-is-production default for
that target only; it unblocks ops-mason's only bundle without loosening
the default for platform objects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 15:47:36 +02:00

82 lines
7.3 KiB
Markdown

# Kubernetes change gate: tiered by readiness state
**Decided by:** Bernd Worsch (founder), exercising `GOVERN @ estate`. **Date:** 2026-09-21. **Recorded by:** the-custodian.
**Vocabulary:** SecurityCanon Mode of Authority v0.2.0 (`security-canon/infospace/vocabulary/mode-of-authority/`), which is still a draft. Where that vocabulary changes, this record follows it.
This record answers gate-house's open question: which engine, if any, should own ops-mason's contact with the Kubernetes API.
## The question, stated in the canon's terms
Applying manifests to a running cluster is `ADMINISTER @ realm:kubernetes/railiance01`. Changing the manifests is `CONSTRUCT @ <manifest repository>`. A GitOps controller separates the two: `CONSTRUCT` produces the definition, and the controller reconciles the cluster to it.
The question is what gates `ADMINISTER @ realm:kubernetes`, and whether that gate differs between experimental and production workloads.
## Decision
1. **No engine owns the contact.** The gate is a quality gate on a change, not an authorization decision. railiance-master ADR-0006 already keeps readiness and permission on separate axes: `production-approved` "is not an authorization decision". So no PDP belongs in this path. The contact is with `realm:kubernetes/railiance01`. This record assigns no layer to that realm and no layer to any rail. Mapping rails onto layers is statute §20.3, which ADR-0009 Decision 4 deliberately leaves unset. The first version of this record said the contact "stays a Tooling contact, owned by rail-kubernetes". That was an unintended §20.3 mapping, and it is withdrawn.
2. **The gate is tiered by ADR-0006 `readiness_state`, not by `Environment`.** railiance01 is a single cluster, and every workload on it shares one `Environment`. The canon's `Environment` dimension therefore cannot separate experimental from production here. The readiness state can.
| Readiness state of the target | Path for a change | `Activation` |
|---|---|---|
| `declared`, `installed`, `verified` | Direct `ADMINISTER @ realm:kubernetes` by ops-mason | `APPROVED`: founder approval of the construction plan |
| `production-approved` | `CONSTRUCT @ manifest repository`, reconciled by ArgoCD (`railiance-platform`) | `APPROVED` for the change; the merge is the gate |
| `production-approved`, emergency | Direct `ADMINISTER` | `BREAK_GLASS`, recorded, reconciled back into the repository afterwards |
Three rules settle the edges of the table (founder, 2026-09-21):
- **Unmapped targets are production-tier until mapped.** The readiness contract defines states only for rails and bound rapps, and nothing yet maps a Kubernetes object or namespace to its binding. So a target without a mapping is production-tier. The friction this creates is deliberate: it is what motivates building the namespace-to-binding mapping (ops-mason MASON-WP-0006, with railiance-master).
- **An evidence lapse does not loosen the path.** When a binding's evidence fails or expires, the contract returns it to its last proven state. The change path does not follow it down. Once a binding has reached `production-approved`, its changes stay on the git/ArgoCD path until the binding is explicitly re-scoped. A lapse weakens the readiness claim, and that is exactly when the gate must not loosen.
- **`deprecated` keeps the tier it had before deprecation.** A deprecated production workload may still carry production traffic until it is removed.
3. **Why production goes through git: the `EvidenceBoundary` dimension.** A direct apply is `target-audited`: the only evidence of the change is held by the realm being changed. A change through the manifest repository adds `external-audited` evidence, which survives the actor and the realm. For production that is the property that matters, and it is a statement about evidence, not about trust in ops-mason.
## Explicit tier placements
Some targets have no binding readiness state, but the founder has placed them in a tier explicitly. An explicit placement overrides the "unmapped is production-tier" default for that target only.
| Target | Tier | Decided | Reason |
|---|---|---|---|
| namespace `whitehat` (whitehat-security; ops-mason bundle `whitehat-foundational-plane`) | non-production | founder, 2026-09-21 | A security-testing workload that no other layer depends on at runtime. Placing it explicitly keeps the unmapped default strict for real platform objects, and makes whitehat a deliberate exception rather than an accidental block. |
A placement is revisited if the target gains dependants, or if it gets a binding with its own readiness state. If it gets a binding, the binding's state governs from then on.
## Transition for rapp-policy-nexus
`rapp-policy-nexus` is `production-approved` (reef-railiance `bindings/rapps.yaml`), but ArgoCD does not manage it. Its `railiance-apps-root` applications are external-secrets, issue-core, openbao-secretstore and target-revenue. So the production path does not yet exist for the one workload that needs it.
- **Until 2026-12-21:** changes to policy-nexus keep `activation=APPROVED` by founder plan approval. Each change additionally records that its target is production-tier, so the transition is visible and does not look like conformance.
- **By 2026-12-21:** policy-nexus is onboarded to ArgoCD. From then on, the production row of the table applies to it.
2026-12-21 is the same review date as the ops-mason plan-approval gate, so both come up together.
## Scope of ops-mason's contact, as found
ops-mason's executor is narrower than a generic `kubectl apply`:
- it confines each plan to one expected namespace;
- it refuses the `Pod` and `Secret` kinds;
- it rejects any manifest carrying `data` or `stringData`.
This decision relies on those limits. Widening them is a new decision.
## Not verified here, and possibly not true
Whether ArgoCD runs on railiance01 at all is unverified, and railiance-platform's own records point the other way:
- Its later records name the controller "coulombcore ArgoCD".
- Its only health record, "healthy on railiance01", predates the 2026-07-02 correction, before which "railiance01" often meant coulombcore.
- No evidence records `railiance-apps-root` syncing on 92.205.62.239.
If ArgoCD is not on railiance01, **the production row has no working path there yet**. Until one exists, production-tier changes on railiance01 fall under the transition rule in the next paragraph, and installing ArgoCD on railiance01 is a cluster-layer change needing its own go-ahead. railiance-platform RPF-WP-0043-T01 holds the read-only check.
Until the production row has a working path, the policy-nexus transition rule applies to every production-tier target on railiance01, not only to policy-nexus: `activation=APPROVED`, with each change recorded as production-tier.
## Owners
- **gate-house:** the open question is answered. No engine owns the contact, and the gate is quality, not authorization.
- **ops-mason:** phase 4 checks the target's readiness state and refuses a direct apply to a production-tier target, except under `BREAK_GLASS` or the policy-nexus transition.
- **railiance-platform:** the ArgoCD lane is the production path, and policy-nexus onboarding is due by 2026-12-21.
- **railiance-master:** ADR-0006 gains a deploy-path consequence for `production-approved`.
- **rapp-policy-nexus:** the transition and its date.