#!/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"