Phase 0 clarification follow-ups + reconcile CI integration seams

Adopt the spec's Phase-0 clarifications (spec now at pin a7fc19f) into the impl:
- schema v1.1: enforce exposure:public => auth.mode!=none (positive+negative tested).
- Kyverno: add longhorn=>size and longhorn=>NOT-reacquirable invariants (R-STATE-02/03).
- Ratify the homelab.keithjackson.dev/* label-key convention (now in spec docs/03).
- R-TEST-06 spec-immutability gate: ci/SPEC_PIN + ci/spec-immutability.sh + gate.sh
  stage + Makefile target + a Gitea job (checks spec repo clean & at pin).
- touched-requirements.sh: conformance tags are the authoritative source.

Reconcile 3 CI seams the CI worker flagged (gate was RED, now GREEN):
- create schemas/validate.sh (SCHEMA_VALIDATE_CMD entrypoint) — L0 schema gate runs.
- fix l1.sh/conformance.sh to run.sh's real interface (fixed _artifacts/ paths; no
  --junit/--coverage) and collect artifacts.
- exec bits on new scripts.

make -C ci gate => PASS (l0 skips locally w/o kyverno binary; green in CI).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
jakman85andClaude Opus 4.8 committed 2026-08-16 00:42:43 -05:00
1 parent 1949f4e71f
commit 8c1368977e
12 files changed
+177 -41

No files matched your search

+4 -1
View File
@@ -11,11 +11,14 @@ CI_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
.DEFAULT_GOAL := help
.PHONY: help lint schema l0 l1 conformance gate all touched versions tools
.PHONY: help spec-immutability lint schema l0 l1 conformance gate all touched versions tools
help: ## Show this help
@awk 'BEGIN{FS=":.*##"} /^[a-zA-Z0-9_-]+:.*##/{printf " \033[36m%-14s\033[0m %s\n",$$1,$$2}' $(MAKEFILE_LIST)
spec-immutability: ## R-TEST-06: fail if the spec repo is modified or drifted from ci/SPEC_PIN
@"$(CI_DIR)spec-immutability.sh"
lint: ## yaml/lint (no cluster; live now)
@"$(CI_DIR)lint.sh"
+6
View File
@@ -0,0 +1,6 @@
# The spec commit (home-network-k3s main) this implementation is built against.
# R-TEST-06: agents MUST NOT modify the spec. The spec changes only via the human/
# System Architect; each legitimate spec advance is adopted here by bumping this pin
# in a reviewed change. CI (ci/spec-immutability.sh) fails if the spec repo is dirty
# or its HEAD != this pin — catching any unauthorized/unadopted spec modification.
a7fc19fad57c70e5c7b142afa142ac16e8b13091
+16 -14
View File
@@ -38,20 +38,22 @@ else
log "no touched requirements detected — running full L2/L3 subset as a safety net"
fi
# Contract we ASSUME of conformance/run.sh (flag for PM to reconcile):
# --layer l2,l3 behavioral + fault-injection layers
# --trigger in-cluster-job create/await the in-cluster Chainsaw Job (Job holds
# cluster creds via its scoped SA; CI uses only a
# narrow trigger credential, referenced by SECRET NAME
# in the workflow: CONFORMANCE_TRIGGER_KUBECONFIG)
# --requirements R-A,R-B touched subset (R-TEST-03)
# --junit <file> --coverage <dir> collected artifacts feed the gate
"$CONFORMANCE_CMD" \
--layer l2,l3 \
--trigger in-cluster-job \
"${req_args[@]}" \
--junit "${JUNIT_DIR}/l2l3.junit.xml" \
--coverage "${COVERAGE_DIR}"
# RECONCILED (PM 2026-08-16): run.sh writes artifacts to conformance/_artifacts/ and does
# NOT accept --junit/--coverage. This stage is CLUSTER-GATED (only runs when
# CLUSTER_AVAILABLE=true, Phase 1+), so its trigger path is confirmed against a real
# cluster in Phase 1 — TODO(Phase 1): drive the in-cluster Chainsaw Job
# (conformance/incluster/chainsaw-job.yaml) under the narrow CONFORMANCE_TRIGGER_KUBECONFIG,
# and confirm multi-layer (l2 + l3) selection syntax against run.sh. Artifacts are then
# COLLECTED from the fixed location below.
"$CONFORMANCE_CMD" --layer l2,l3 "${req_args[@]}"
CONF_ART="$(dirname "$CONFORMANCE_CMD")/_artifacts"
if [ -f "${CONF_ART}/report.junit.xml" ]; then
cp "${CONF_ART}/report.junit.xml" "${JUNIT_DIR}/l2l3.junit.xml"
fi
if [ -d "${CONF_ART}/coverage" ]; then
cp -r "${CONF_ART}/coverage/." "${COVERAGE_DIR}/"
fi
ok "L2/L3 conformance passed for touched subset"
summary "- conformance-l2-l3: **passed**"
+9
View File
@@ -37,6 +37,7 @@ run_stage() {
summary "## Merge gate (R-TEST-03)"
summary ""
run_stage spec-immutability "${CI_DIR}/spec-immutability.sh"
run_stage lint "${CI_DIR}/lint.sh"
run_stage l0 "${CI_DIR}/l0.sh"
run_stage l1 "${CI_DIR}/l1.sh"
@@ -46,6 +47,14 @@ echo >&2
log "==== gate decision ===="
BLOCK=0
# spec-immutability (R-TEST-06): a FAIL always blocks (the spec was modified/drifted).
# A skip means the spec repo wasn't available to verify — surfaced, not green.
case "${OUTCOME[spec-immutability]}" in
fail) err "spec-immutability: FAILED -> blocks merge (R-TEST-06)"; BLOCK=1 ;;
skip) warn "spec-immutability: skipped (spec repo unavailable) — not verified" ;;
pass) ok "spec-immutability: green" ;;
esac
# L0/L1 stages: any failure blocks. A SKIP here means a seam is unwired — in
# Phase 0 that is tolerated (placeholders), but surfaced.
for s in lint l0 l1; do
+13 -10
View File
@@ -28,16 +28,19 @@ if [ "${#reqs[@]}" -gt 0 ]; then
req_args=(--requirements "$(IFS=,; echo "${reqs[*]}")")
fi
# Contract we ASSUME of conformance/run.sh (flag for PM to reconcile):
# --layer l1 select the L1 rendering layer (no cluster)
# --requirements R-A,R-B filter to the touched subset
# --junit <file> write JUnit XML
# --coverage <dir> write the requirement-coverage report
"$CONFORMANCE_CMD" \
--layer l1 \
"${req_args[@]}" \
--junit "${JUNIT_DIR}/l1.junit.xml" \
--coverage "${COVERAGE_DIR}"
# RECONCILED (PM 2026-08-16): conformance/run.sh's real interface is `--layer <l1|l2|l3>`
# [--requirements R-A,R-B]; it ALWAYS writes artifacts to a FIXED location
# (conformance/_artifacts/) and does NOT accept --junit/--coverage. Invoke by layer +
# touched subset, then COLLECT its artifacts into the CI artifact dirs.
"$CONFORMANCE_CMD" --layer l1 "${req_args[@]}"
CONF_ART="$(dirname "$CONFORMANCE_CMD")/_artifacts"
if [ -f "${CONF_ART}/report.junit.xml" ]; then
cp "${CONF_ART}/report.junit.xml" "${JUNIT_DIR}/l1.junit.xml"
fi
if [ -d "${CONF_ART}/coverage" ]; then
cp -r "${CONF_ART}/coverage/." "${COVERAGE_DIR}/"
fi
ok "L1 rendering checks passed"
summary "- l1-render: **passed**"
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# ci/spec-immutability.sh — R-TEST-06 gate: the specification is read-only to agents.
#
# The spec lives in a SEPARATE repo (home-network-k3s). Agents implement against it and
# never modify it; only the human/System Architect changes the spec. This gate fails CI if
# the spec repo has uncommitted edits or its HEAD has drifted from the pinned commit in
# ci/SPEC_PIN — catching any unauthorized (or un-adopted) spec change.
#
# Adopting a legitimate spec advance = bump ci/SPEC_PIN to the new spec main SHA in a
# reviewed PR. That makes every spec movement explicit and gated, per docs/14 / R-TEST-06.
#
# Exit codes (per ci/gate.sh contract): 0 pass · 1 fail (blocks merge) · 3 skip (spec
# repo not available to verify — honest skip, never fake green).
set -uo pipefail
CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${CI_DIR}/lib.sh"
set +e
PIN="$(grep -vE '^\s*#' "${CI_DIR}/SPEC_PIN" | tr -d '[:space:]')"
: "${SPEC_REPO:=${REPO_ROOT}/../home-network-k3s}"
if [ -z "$PIN" ]; then err "ci/SPEC_PIN has no pinned SHA"; exit 1; fi
if [ ! -d "${SPEC_REPO}/.git" ]; then
skip "spec repo not present at ${SPEC_REPO} (set SPEC_REPO) — cannot verify immutability"
exit 3
fi
dirty="$(git -C "$SPEC_REPO" status --porcelain 2>/dev/null)"
head="$(git -C "$SPEC_REPO" rev-parse HEAD 2>/dev/null)"
rc=0
if [ -n "$dirty" ]; then
err "spec repo has UNCOMMITTED changes — the spec is read-only to agents (R-TEST-06):"
printf '%s\n' "$dirty" >&2
rc=1
fi
if [ "$head" != "$PIN" ]; then
err "spec HEAD (${head:0:12}) != pinned (${PIN:0:12})."
err " If this is a legitimate spec advance by the System Architect, adopt it by bumping ci/SPEC_PIN."
err " Otherwise the spec was modified without authorization — reject."
rc=1
fi
[ "$rc" -eq 0 ] && ok "spec immutable: clean and at pinned ${PIN:0:12} (R-TEST-06)"
exit "$rc"
+13 -8
View File
@@ -8,15 +8,20 @@
# Output (stdout): newline-separated, sorted, unique R-* IDs. Nothing else goes
# to stdout, so callers can `mapfile`/`$(...)` it directly.
#
# Detection = union of two signals:
# 1. R-* tokens literally present in the diff of changed files (tests, policies,
# and ServiceDefinitions carry their R-IDs per docs/14 + ADR-0011).
# 2. A coarse path->requirement map for changes that don't self-declare IDs.
# SOURCE OF TRUTH (reconciled, PM 2026-08-16): the conformance requirement TAGS are
# authoritative. Each Chainsaw test declares the requirements it verifies via
# `conformance.home.k3s/<R-ID>` labels; the coverage report (conformance/_artifacts/
# coverage/coverage.json) is generated from those tags. This script's job is only to
# select which tagged subset a change TOUCHES, then hand those IDs to
# `conformance.sh --requirements ...` which runs exactly the tests carrying those tags.
#
# ASSUMPTION (flag for PM): the authoritative change->requirement mapping is
# really WS-TEST's coverage tooling (tests tag their own R-IDs). This script is a
# CI-side approximation good enough to *filter* the subset; at integration it
# should defer to / cross-check the coverage report emitted by conformance/.
# Detection = union of:
# 1. R-* tokens literally present in the diff of changed files — including the
# authoritative `conformance.home.k3s/R-*` tags on changed tests, and the R-IDs
# that policies/ServiceDefinitions self-declare (docs/14 + ADR-0011).
# 2. A coarse path->requirement fallback for changes that don't self-declare IDs.
# The coarse map (2) NEVER overrides an explicit tag; it only widens coverage for
# untagged changes so nothing silently escapes the gate.
set -euo pipefail
CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091