R-TEST-03 runs the conformance subset a change TOUCHES. That has one blind spot, and it
is the worst-shaped kind: an invariant that quantifies over the whole tracked tree — or
over subtrees ci/touched-requirements.sh's coarse path map keys to NOTHING (apps/, infra/,
most of platform/) — cannot be selected BY the requirements a change touches, because the
change most likely to break it attributes no requirement at all. Requirement-scoped
selection then filters the check out of exactly that change.
The repo's answer had been a hand-written "UNCONDITIONAL" stage per such check in
ci/l0.sh: 3c flux-postbuild-substitution, 3d keycloak-client-secret-reconciler, 3e
bulk-tier-distinct-from-replicated. Three near-identical 12-line rationales, three
hardcoded check.sh paths, three copies of the same rc 0/3/* case block — and worst, a
mechanism that failed SILENTLY, because whoever added the NEXT whole-repo invariant had
to REMEMBER to write a stage, and if they forgot the check ran only when some unrelated
requirement happened to be touched.
A test now DECLARES the scope of its invariant and the runner enforces it:
metadata:
annotations:
conformance.home.k3s/scope: "whole-repo"
* conformance/lib/common.sh conf_dirs_for_scope(s) resolves the annotation. Carries the
general rationale, once, instead of per stage.
* conformance/run.sh --include-scope SCOPE unions declaring tests into select_dirs,
still bounded by --layer. New --print-selection resolves the
selection and exits, running nothing.
* lib/run_l1_offline.py --unwired-scope: a SKIP of a declaring test exits 3, not 0.
--summary-out: one step-summary line per executed check.
* ci/l0.sh Stage 3b gains --include-scope whole-repo and an rc-3 arm.
Stages 3c/3d/3e DELETED (-150 lines).
* ci/l1.sh, ci/conformance.sh pass --include-scope too, so the next whole-repo invariant
at ANY layer needs only the annotation.
rc 3 SEMANTICS PRESERVED EXACTLY. A whole-repo check reporting "nothing to audit" is
reporting that the tree it audits has VANISHED. The deleted stages called that "not a
pass" and set the gate's rc=3; the runner now returns 3 and ci/l0.sh maps it to rc=3.
A missing check file still degrades to not-green, never to silence.
THE REASONING WAS RELOCATED, NOT DELETED. Each stage's incident narrative moved into the
header of the test it describes, beside its scope annotation: the Cilium k8sServiceHost
empty-substitution CNI outage (2026-09-01), 12 unusable Keycloak clients behind a green
cluster (2026-09-06), and a multi-TB bulk volume landing on the 3-replica tier.
AUDITED ALL 13 EXISTING l0-static TESTS, individually. 10 declare whole-repo scope; 3 are
DELIBERATELY left requirement-scoped and say so in their own headers:
* placeholder-guard-coupling — inputs are bootstrap/ + clusters/, both already mapped to
a requirement it declares. Scoping cannot hide a breaking change.
* dev-nodes-provisioned-as-code — inputs are infra/ and nothing else.
* spec-immutability-enforced — inputs are ci/ and the read-only spec repo.
The latter two were nonetheless under-gated because the PATH MAP was incomplete, which is
the right place to fix a path-scoped invariant: infra/* -> R-ENV-01 added, and R-TEST-06
added to .gitea/*|ci/* (it is no longer an uncovered ID — spec-immutability-enforced
carries the label, so attributing it no longer false-blocks the coverage gate).
NEW conformance/tests/l0-static/whole-repo-invariants-wired (R-TEST-03) keeps the
declarative mechanism honest for ALL declaring tests at once, replacing the three tests'
individual "ci/l0.sh greps my own check.sh path" self-assertions. It proves the union
EMPIRICALLY: with a requirement filter matching no test the L0 selection is empty, and
with --include-scope it is every declaring test; a scope-declared synthetic SKIP exits 3
while an undeclared one exits 0; and a ci/*.sh that hardcodes a check.sh path must belong
to a test that also declares the scope, so the bespoke pattern cannot creep back.
TASK 2 — a changed test attributes the requirements it CERTIFIES.
ci/touched-requirements.sh gains detection rule 3: a change anywhere under
conformance/tests/<test>/ attributes that test's own conformance.home.k3s/R-* labels, read
from its chainsaw-test.yaml. The content scan could not close this — it reads only
added/removed lines, so it sees a change to the LABEL BLOCK but not to the test BODY,
which is exactly the case that matters. Labels are read from HEAD or, failing that, from
the BASE, so DELETING a test still attributes what it used to certify; otherwise removing
the only verifier of a requirement is the cheapest way to make the gate stop checking it.
Whole-line `#` comments are dropped first and *.md is untouched, honouring the existing
"reference, not declaration" rules.
Both changes are fail-closed: every one of them can only ADD tests or requirements to a
run, never remove one.
TESTS: new ci/tests/touched-requirements-test-attribution.test.sh (15 assertions) covers
rule 3 — body-only edit, nested-fixture walk-up, test deletion, header-comment R-IDs not
attributed, non-test paths unaffected, and both new path-map entries. The two existing
suites still pass (39 assertions). `make -C ci test` runs all three.
Co-Authored-By: Claude Opus 5 <[email protected]>
84 lines
5.2 KiB
YAML
84 lines
5.2 KiB
YAML
# =============================================================================
|
||
# L0 STATIC — prod is bare metal, and is not a development surface (R-ENV-03) [EXECUTABLE]
|
||
# Catalog: docs/14 L0 row ("Pre-apply, in CI") — runs WITHOUT a cluster.
|
||
# Spec: docs/00 R-ENV-03 ("prod is a 3× mini-PC bare-metal k3s cluster with local
|
||
# storage; it MUST NOT be an active development surface until the roadmap
|
||
# declares it ready"); docs/02 (prod = 3 physical nodes, local SSD/NVMe to
|
||
# Longhorn, "the *same* Ansible bootstrap runs; only the machine-creation step
|
||
# differs", "Never develop against prod"); roadmap/phases.md Phase 7.
|
||
#
|
||
# HOW A "MUST NOT" IS MADE TESTABLE — the same discipline as R-TEST-06's test: a
|
||
# governance rule is verified through the property that ENFORCES it, never by observing
|
||
# that nobody misbehaved today. Here that property is structural: prod receives the base
|
||
# configuration plus environment-scoped overrides and nothing else, from the same branch
|
||
# and the same Ansible code as dev, so there is no place to put prod-only work at all.
|
||
#
|
||
# ENVIRONMENT-AGNOSTIC: the prod inventory group is DERIVED (it is the group whose
|
||
# group_vars retargets the k3s roles away from the global default), the prod overlay is
|
||
# derived from CLUSTER_ENV, and the storage/replica expectations are compared against the
|
||
# node count that inventory resolves to. No hostname, address or environment name is
|
||
# written into the test.
|
||
#
|
||
# GIVEN the repository as git tracks it (plus the read-only spec repo when mounted),
|
||
# WHEN the prod inventory, overlay, IaC module and readiness record are inspected — and
|
||
# the shared provisioning chain is actually retargeted at prod with --list-hosts,
|
||
# THEN prod resolves to exactly three nodes reached directly on their own sshd; no IaC
|
||
# resource creates any of them (bare metal is imaged, not instantiated); prod's
|
||
# Longhorn path is node-local with one replica per node and the default
|
||
# StorageClass is the local driver; prod runs the same playbooks/roles as dev,
|
||
# retargeted only by variables the shared code already defines; the prod overlay
|
||
# carries only the base plus environment-scoped patches on the same Git ref dev
|
||
# tracks, with no prod-only manifest anywhere in the shared configuration; and prod
|
||
# artifacts exist only alongside the operator's recorded opening of the prod phase.
|
||
#
|
||
# STATUS: EXECUTABLE. `check.sh` is a pure offline assertion script (git, PyYAML, and —
|
||
# where present — ansible-playbook in --list-hosts mode, which opens no connection);
|
||
# conformance/run.sh --layer l0 and ci/l0.sh run it directly, and this Chainsaw Test
|
||
# wraps the SAME script.
|
||
#
|
||
# KNOWN, DELIBERATE GAPS (enumerated in check.sh's header; this test claims none of them):
|
||
# * THE HARDWARE. That the three machines are mini-PCs, are bare metal, and hold local
|
||
# SSD/NVMe is operator-attested (infra/PROD-STANDUP.md, PROD-PROVISIONING.md); the
|
||
# node-health/BIOS surveys that establish it write to gitignored artifacts, and the
|
||
# Kubernetes API exposes no "am I virtual" fact, so nothing here asserts it.
|
||
# * The LIVE prod cluster's shape (three Ready nodes, all control-plane+etcd, Longhorn
|
||
# disks at the local path) — cluster state, belonging to R-PLAT-01's still-uncovered
|
||
# HA test; asserting it here would also make this test environment-specific, since
|
||
# the same suite must pass against dev.
|
||
# * Git HISTORY: this is the working tree at HEAD.
|
||
# * Whether the roadmap SHOULD have opened prod — only that prod work is accompanied by
|
||
# the recorded declaration.
|
||
# =============================================================================
|
||
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||
kind: Test
|
||
metadata:
|
||
name: prod-baremetal-not-dev-surface
|
||
labels:
|
||
conformance.home.k3s/R-ENV-03: "true"
|
||
annotations:
|
||
conformance.home.k3s/requirements: "R-ENV-03"
|
||
conformance.home.k3s/layer: "L0"
|
||
# WHOLE-REPO SCOPE. Universally quantified over the tracked tree (`git ls-files`): its
|
||
# core assertion is that NO dev-only surface leaks into what prod renders, over
|
||
# platform/, platform-config/, apps/, catalog/ and clusters/base/ together, plus infra/
|
||
# (which the path map keys to nothing at all). No single requirement can select the set
|
||
# of files that can break it.
|
||
conformance.home.k3s/scope: "whole-repo"
|
||
conformance.home.k3s/safety-critical: "false"
|
||
conformance.home.k3s/executable: "true"
|
||
conformance.home.k3s/intent: >-
|
||
Given the tracked tree, when prod's inventory, overlay, IaC and readiness record are
|
||
inspected, then prod is authored as three bare-metal nodes with node-local storage,
|
||
bootstrapped by dev's own code retargeted by variables, receiving only
|
||
environment-scoped overrides on the same branch — so there is nowhere to develop
|
||
against prod — and it exists only because the roadmap phase was opened.
|
||
spec:
|
||
steps:
|
||
- name: then-prod-is-bare-metal-and-carries-no-development-of-its-own
|
||
description: >-
|
||
Run the offline assertion script over the tracked tree (same script ci/l0.sh and
|
||
conformance/run.sh --layer l0 execute).
|
||
try:
|
||
- script:
|
||
content: bash ./check.sh
|