From 58f1eafe3770170bfb46e42495e0cfa0a95c4bdd Mon Sep 17 00:00:00 2001 From: "6759885+hyperpolymath@users.noreply.github.com" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:22:32 +0000 Subject: [PATCH 1/8] =?UTF-8?q?feat(canon):=20add=20canon.lock=20=E2=80=94?= =?UTF-8?q?=20the=20released=20identity=20of=20the=20canon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WHAT THIS IS `canon.lock` makes "which standards?" a question with a hashable answer. Today the standards -> template binding is two free-text strings in rsr-template-repo/machine-readable/rsr-profile.a2ml: spec = "rsr-criteria-v2" declares-against = "2.0.0-draft" Nothing fails when the criteria change, when the gate table changes, or when the applicability policy changes. A grep for any version identifier across both repos returns exactly ONE hit: that string. This file is the mechanism rsr-criteria-v2.a2ml already asks for under [versioning]: freeze-mechanism = "A released major is frozen by pinning this file's byte-hash ... NOT by a prose 'FROZEN' banner." It promotes that per-file pin to a single release point for the whole canon: version + sha256 of every law artefact + the reusable-workflow refs + the roles. WHAT IT RECORDS [canon.artifacts] criteria, gates, applicability, lifecycle, constitution — each with a REAL sha256 computed against this tree [canon.split-candidates] deed-grammar, contractile-spec, rsr-spec — listed NOW so a future split does not orphan a standards-map slot [canon.workflows] the reusable gates every repo calls [canon.roles] canon / spine / composer / oracle / fleet [canon.lockstep] Gate A's five assertions and their ORDER THE RULE Any change to a file named in [canon.artifacts] is a CANON CHANGE and MUST, in the same commit, bump [canon].version and rewrite the hash. That is assertion 2 of Gate A, implemented in scripts/check-canon-lockstep.sh. Assertion 4 is a deliberate reversal and is the point of the exercise: YOU MAY NOT TIGHTEN THE CRITERIA UNTIL THE REFERENCE IMPLEMENTATION PASSES THEM. PATHS AT THIS REVISION The paths recorded are the CURRENT in-tree locations, not the post-reorg paths in docs/binding/03-STANDARDS-REORG.md. This file must be green on the tree it lands on. Because every artefact move is a path change to a hash-pinned file, THE MOVE COMMIT MUST BUMP THIS FILE IN THE SAME COMMIT — which is assertion 2 doing exactly what it was built for. WHAT IS STILL RED, AND WHY THAT IS CORRECT Assertion 5 fails: this repo has NO rsr-profile.a2ml, so scripts/check-rsr-profile.sh exits 2 on the repo that ships it. The law is not subject to the law. That is fixed by the next commit in this series; it is left failing here rather than stubbed green. Witnesses: scripts/check-canon-lockstep.sh --canon . --base HEAD -> assertion 1: 5/5 artefacts verified against the tree -> assertion 2: PASS -> assertion 5: FAIL (correct — no profile exists yet) --- canon.lock | 212 ++++++++++++++++++++++ scripts/check-canon-lockstep.sh | 307 ++++++++++++++++++++++++++++++++ 2 files changed, 519 insertions(+) create mode 100644 canon.lock create mode 100644 scripts/check-canon-lockstep.sh diff --git a/canon.lock b/canon.lock new file mode 100644 index 000000000..9c64af773 --- /dev/null +++ b/canon.lock @@ -0,0 +1,212 @@ +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# canon.lock — the RELEASED identity of the Hyperpolymath canon. +# Proposed location: hyperpolymath/standards/canon.lock (repository root) +# +# --------------------------------------------------------------------------- +# WHY THIS FILE EXISTS +# +# rsr-template-repo's machine-readable/rsr-profile.a2ml currently declares its +# conformance with: +# +# spec = "rsr-criteria-v2" +# declares-against = "2.0.0-draft" +# +# Both are free-text strings. Nothing fails when the criteria change, when the +# gate table changes, or when the applicability policy changes. That is the +# whole of the standards -> template binding today. +# +# This file turns "which standards?" into a question with a SHA-able answer. +# It is the mechanism rsr-criteria-v2.a2ml already asks for under [versioning]: +# +# freeze-mechanism = "A released major is frozen by pinning this file's +# byte-hash ... NOT by a prose 'FROZEN' banner." +# +# canon.lock is that mechanism, promoted from a per-file pin to a single +# release point for the whole canon. +# +# --------------------------------------------------------------------------- +# PATHS AT THIS REVISION (v2.0.0) +# +# The paths above are the CURRENT in-tree locations, not the post-reorg paths +# in 03-STANDARDS-REORG.md. That is deliberate: this file must be green on the +# tree it lands on. The reorganisation is a separate change, and because every +# artefact move is a path change to a hash-pinned file, THE MOVE COMMIT MUST +# BUMP THIS FILE IN THE SAME COMMIT - which is assertion 2 of Gate A doing +# exactly what it was built for. +# +# standards-map.toml records both: `from` = current, `target` = post-reorg. +# --------------------------------------------------------------------------- + +# --------------------------------------------------------------------------- +# WHO READS IT +# +# rsr-template-repo/machine-readable/rsr-profile.a2ml [canon] block +# rsr-template-repo/build/just/repo-init.just writes PROVENANCE.a2ml +# minted repos' machine-readable/PROVENANCE.a2ml +# hypatia -> Hypatia.Rules.RsrConformance +# scaffoldia +# scripts/check-canon-lockstep.sh (canon CI, Gate A) +# scripts/check-standards-map.sh (canon CI, Gate D) +# +# --------------------------------------------------------------------------- +# THE RULE +# +# Any change to a file named in [canon.artifacts] is a CANON CHANGE. +# It MUST, in the SAME commit: +# 1. bump [canon].version (semver-with-errata, per rsr-criteria [versioning]): +# MAJOR = era change, may remove/redefine criteria, may move a criterion +# between tiers; freezes the prior major under 0-canon/rsr/archive/ +# MINOR = additive only; MUST NOT make a previously-conforming repo +# non-conforming at the same tier +# PATCH = editorial; no criteria-set change +# 2. rewrite the affected sha256 here +# 3. pass Gate A (canon-spine-lockstep) - which requires the SPINE to have +# adopted the new hashes and to be GREEN against them before this lands. +# +# Gate A is a deliberate reversal of the current direction of travel: +# +# YOU MAY NOT TIGHTEN THE CRITERIA UNTIL THE REFERENCE IMPLEMENTATION +# PASSES THEM. +# +# --------------------------------------------------------------------------- + +[canon] +# A released major is immutable. Bumping `version` to a new MAJOR means the +# prior major's criteria file is copied to 0-canon/rsr/archive/ and pinned +# there; a freeze guard fails any PR that mutates a frozen major. +version = "2.0.0" +spec_family = "rhodium-standard-repositories" +status = "draft" # draft | stable +released = "2026-09-17" +authority = "0-canon/constitution/ESTATE-CONSTITUTION.adoc" + +# The full git ref that realises this canon. A tag alone is not sufficient +# (tags move); a commit alone is not sufficient (it has no version). Both. +commit = "0000000000000000000000000000000000000000" # ← fill at release +tag = "canon-v2.0.0" + +# --------------------------------------------------------------------------- +# THE ARTEFACT SET — the files that ARE the canon. +# +# These are also the join keys for standards-map.toml `canon_slot`, so +# check-standards-map.sh can prove every canon_slot resolves to a real slot. +# +# HASHES BELOW ARE REAL, computed 2026-09-17 against standards@bbecaee. +# --------------------------------------------------------------------------- + +[canon.artifacts] +criteria = { path = "rhodium-standard-repositories/spec/rsr-criteria-v2.a2ml", # from rhodium-standard-repositories/spec/ + sha256 = "efd024ad9cbdf0d36d4dbce7e491531dd4ccccbfc747a04223ce4149f4b9a53d", + slot = "criteria", normative = true } + +gates = { path = ".machine_readable/template-capability-gates.toml", # from .machine_readable/ + sha256 = "da0e9861641e5c6becc619458f1a0b9b3cc3117799b3a1f329023431b50bbc22", + slot = "gates", normative = true } + +applicability = { path = "TEMPLATE-APPLICABILITY-POLICY.adoc", + sha256 = "7a31dc7e555b05c16a755c5ee599105572f023a2c72f85ec0fb0097436d627b5", + slot = "applicability", normative = true } + +lifecycle = { path = "rhodium-standard-repositories/spec/SCAFFOLD-LIFECYCLE.adoc", + sha256 = "0b25b3d8f2d4ce57fa2c1861b5c7e155383979c03bfcc55bc61c63a929721fdd", + slot = "lifecycle", normative = true } + +constitution = { path = "constitution/", + # directory slots use the registry's own method: + # sha256 over `git ls-files -s ` + sha256 = "7fbd1015a184d33391957e73f6348c7409b6298e15ef9c8bcd26bf1a6fd7d739", + method = "sha256(git ls-files -s )", + slot = "constitution", normative = true } + +# --------------------------------------------------------------------------- +# SPLIT-OUT CANDIDATE SLOTS. +# +# These name formats that 03-STANDARDS-REORG.md proposes moving to their own +# repos. They are listed here NOW, at v2.0.0, with their current in-tree hashes, +# for two reasons: +# +# * standards-map.toml's canon_slot values must all resolve (Gate D), and a +# slot that vanishes at split time would silently orphan a map entry; +# * when the split lands, the slot's `path` changes from an in-tree path to +# an external pointer (`url + version + source_hash`) and the owning repo +# takes over its cadence — which is a MINOR canon bump by definition +# ("additive only; MUST NOT make a previously-conforming repo +# non-conforming at the same tier"). +# +# Same shape as the registry's existing EXTERNAL entries for AffineScript: +# a verified POINTER, never a copy. +# --------------------------------------------------------------------------- + +[canon.split-candidates] +deed-grammar = { path = "deed/spec/DEED-GRAMMAR-SPEC.adoc", + sha256 = "PENDING-FIRST-SYNC", # DRAFT v0.2.0, not yet ratified + slot = "deed-grammar", normative = false, + target_repo = "hyperpolymath/deed", + note = "v0.2.0 DRAFT; ABNF normative at spec/abnf/deed.abnf; unblocks estate task #64 (.a2ml -> .deed)" } + +contractile-spec = { path = "contractiles/README.adoc", + sha256 = "PENDING-FIRST-SYNC", + slot = "contractile-spec", normative = false, + target_repo = "hyperpolymath/contractiles", + note = "two-file vs four-file Trident is ruling 2 in STANDARDS-CRITICAL-PATH; the linter is estate task #19" } + +rsr-spec = { path = "rhodium-standard-repositories/spec/RSR-SPEC-v2.adoc", # extracted from the vendored copy + sha256 = "PENDING-FIRST-SYNC", + slot = "rsr-spec-home", normative = true, + target_repo = null, # stays in the canon + note = "the prose authority for rsr-criteria-v2.a2ml; extracted from rhodium-standard-repositories/spec/ before that directory is deleted (D1)" } + +# --------------------------------------------------------------------------- +# THE WORKFLOWS EVERY REPO CALLS. +# +# A repo's canon identity is these refs at `commit`. This is why the pin must +# move as one unit: a consumer that re-pins one reusable workflow but not the +# others is running two canons at once, and nothing reports that today. +# --------------------------------------------------------------------------- + +[canon.workflows] +governance = "hyperpolymath/standards/.github/workflows/governance-reusable.yml" +hypatia = "hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml" +allowlist = "hyperpolymath/standards/.github/workflows/allowlist-preflight-reusable.yml" +codeql = "hyperpolymath/standards/.github/workflows/codeql-reusable.yml" +scorecard = "hyperpolymath/standards/.github/workflows/scorecard-reusable.yml" +secrets = "hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml" +mirror = "hyperpolymath/standards/.github/workflows/mirror-reusable.yml" +changelog = "hyperpolymath/standards/.github/workflows/changelog-reusable.yml" +readme = "hyperpolymath/standards/.github/workflows/readme-derive-reusable.yml" + +# --------------------------------------------------------------------------- +# THE ROLES. One owner per concern; a change belongs at its owning layer +# ("solutions at source"). Mirrors SCAFFOLD-LIFECYCLE.adoc §Roles, but as data. +# --------------------------------------------------------------------------- + +[canon.roles] +canon = "hyperpolymath/standards" +spine = "hyperpolymath/rsr-template-repo" +composer = "hyperpolymath/scaffoldia" +oracle = "hyperpolymath/hypatia" +fleet = "hyperpolymath/gitbot-fleet" +telemetry= "hyperpolymath/estate-telemetry" # ← proposed split, see 03-STANDARDS-REORG.md + +# --------------------------------------------------------------------------- +# GATE A — the five assertions. check-canon-lockstep.sh implements all five. +# --------------------------------------------------------------------------- + +[canon.lockstep] +# 1. every sha256 in [canon.artifacts] matches the working tree +require-verified-hashes = true +# 2. [canon].version was bumped in the same commit as any artefact change +require-version-bump = true +# 3. spine@HEAD declares criteria_sha256 == [canon.artifacts].criteria.sha256 +require-spine-adopted = true +# 4. spine dogfood-gate is GREEN against THESE criteria +require-spine-green = true +# 5. canon itself scores Gold on its applicable set (Gate B, role = "canon") +require-canon-gold = true + +# A canon change that cannot satisfy 3 and 4 is not a rejected change; it is a +# change that has been made IN THE WRONG ORDER. Land the spine's adoption +# first, then the canon change becomes a one-line version bump. +order = "spine-adopts-then-canon-releases" diff --git a/scripts/check-canon-lockstep.sh b/scripts/check-canon-lockstep.sh new file mode 100644 index 000000000..64778f444 --- /dev/null +++ b/scripts/check-canon-lockstep.sh @@ -0,0 +1,307 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# check-canon-lockstep.sh — GATE A. +# +# Proposed location: hyperpolymath/standards/scripts/check-canon-lockstep.sh +# Runs from: .github/workflows/canon-spine-lockstep.yml +# +# This is the gate that makes `standards` BOUND BY `rsr-template-repo`. +# +# --------------------------------------------------------------------------- +# WHAT IT ENFORCES +# +# 1 every sha256 in canon.lock [canon.artifacts] matches the working tree +# 2 touching a canon artefact forces a version bump +# 3 the spine declares criteria_sha256 == canon.lock's criteria hash +# 4 the spine is GREEN against those criteria +# 5 the canon itself scores Gold on its own applicable set +# +# Assertion 4 is the load-bearing one, and it is a deliberate reversal: +# +# YOU MAY NOT TIGHTEN THE CRITERIA UNTIL THE REFERENCE IMPLEMENTATION +# PASSES THEM. +# +# --------------------------------------------------------------------------- +# USAGE +# check-canon-lockstep.sh [--canon DIR] [--spine DIR] [--base REF] [--strict] +# +# --canon DIR path to hyperpolymath/standards (default: .) +# --spine DIR path to a cloned rsr-template-repo +# --base REF the ref to diff against for assertion 2 (default: origin/main) +# --strict promote assertions 3/4/5 from SKIP to FAIL +# +# EXIT +# 0 all enabled assertions passed +# 1 at least one assertion failed +# 2 setup error (missing file, unresolvable ref) +# +# DEPENDENCIES +# bash (no python - estate policy: see docs/JS-RUNTIME-POLICY.adoc, +# NO-JAVASCRIPT-SOURCE-POLICY.adoc; bash + awk + git only) +# awk, git, sha256sum, grep +# --------------------------------------------------------------------------- +set -uo pipefail + +CANON="." +SPINE="" +BASE_REF="origin/main" +STRICT=0 +FAILED=0 +PASSED=0 +SKIPPED=0 + +while [ $# -gt 0 ]; do + case "$1" in + --canon) CANON="$2"; shift 2 ;; + --spine) SPINE="$2"; shift 2 ;; + --base) BASE_REF="$2"; shift 2 ;; + --strict) STRICT=1; shift ;; + -h|--help) sed -n '2,40p' "$0"; exit 0 ;; + *) echo "unknown argument: $1" >&2; exit 2 ;; + esac +done + +pass() { PASSED=$((PASSED + 1)); printf ' \033[32mPASS\033[0m %s\n' "$*"; } +fail() { FAILED=$((FAILED + 1)); printf ' \033[31mFAIL\033[0m %s\n' "$*"; } +skip() { + if [ "$STRICT" -eq 1 ]; then fail "$* (SKIP promoted to FAIL by --strict)" + else SKIPPED=$((SKIPPED + 1)); printf ' \033[33mSKIP\033[0m %s\n' "$*"; fi +} + +LOCK="$CANON/canon.lock" +[ -f "$LOCK" ] || { echo "ERROR: canon.lock not found at $LOCK" >&2; exit 2; } + +# --------------------------------------------------------------------------- +# A minimal TOML reader. The estate mandates bash+awk (no Python, no Deno), +# and canon.lock is authored in a deliberately flat, single-line shape so that +# this is sufficient. This is the same discipline template-capability-gates.toml +# already imposes on itself ("Arrays are kept single-line so the checker can +# parse them with grep"). +# --------------------------------------------------------------------------- + +# toml_get
-> last assignment wins, comments stripped +toml_get() { + awk -v want="[$1]" -v key="$2" ' + /^[[:space:]]*\[/ { cur=$0; gsub(/[[:space:]]/,"",cur); next } + cur == want { + line=$0; sub(/#.*/,"",line) + if (line ~ "^[[:space:]]*"key"[[:space:]]*=") { + sub(/^[^=]*=[[:space:]]*/,"",line); gsub(/[[:space:]]*$/,"",line) + gsub(/^"|"$/,"",line); v=line + } + } + END { if (v != "") print v } + ' "$LOCK" +} + +# The artefact records are inline tables that may SPAN LINES, e.g. +# +# criteria = { path = "0-canon/rsr/rsr-criteria-v2.a2ml", # from spec/ +# sha256 = "efd024ad…", +# slot = "criteria", normative = true } +# +# so both readers below accumulate a record: they open on ` = {`, take +# every line until the closing `}` (or a line that does not continue the +# record), strip comments, and join. Same problem, and the same solution, as +# check-rsr-profile.sh's array_on_key(). +record() { # $1 = key name (criteria|gates|applicability|lifecycle|constitution) + awk -v slot="$1" ' + /^[[:space:]]*#/ { next } + $0 ~ "^[[:space:]]*" slot "[[:space:]]*=[[:space:]]*\\{" { on=1 } + on { + line=$0; sub(/#.*/, "", line); rec = rec " " line + if (line ~ /}/) { on=0 } + } + END { sub(/^[[:space:]]*/, "", rec); print rec } + ' "$LOCK" +} + +# toml_hash -> sha256 from that record +toml_hash() { + record "$1" | grep -oE 'sha256[[:space:]]*=[[:space:]]*"[0-9a-f]{64}"' \ + | grep -oE '[0-9a-f]{64}' | head -1 +} + +# toml_path -> path from that record +toml_path() { + record "$1" | grep -oE 'path[[:space:]]*=[[:space:]]*"[^"]+"' \ + | sed 's/.*"\(.*\)"/\1/' | head -1 +} + +# sha256 of a path: file -> plain hash; directory -> git-ls-files method, +# matching the registry's own source_hash definition. +hash_path() { + local p="$1" + if [ -d "$p" ]; then + ( cd "$CANON" && git ls-files -s "$p" | sha256sum | cut -d' ' -f1 ) + else + sha256sum "$p" | cut -d' ' -f1 + fi +} + +CANON_VERSION="$(toml_get canon version)" +echo "canon.lock: version=$CANON_VERSION lock=$(sha256sum "$LOCK" | cut -d' ' -f1 | cut -c1-12)…" +echo + +# =========================================================================== +# ASSERTION 1 — declared hashes match the working tree +# =========================================================================== +echo "[1] canon artefact hashes match the working tree" +for slot in criteria gates applicability lifecycle constitution; do + want="$(toml_hash "$slot")" + path="$(toml_path "$slot")" + if [ -z "$want" ] || [ -z "$path" ]; then skip "$slot: not declared in canon.lock"; continue; fi + if [ ! -e "$CANON/$path" ]; then + fail "$slot: declared path does not exist: $path" + continue + fi + got="$(hash_path "$CANON/$path")" + if [ "$want" = "$got" ]; then + pass "$slot ${path} $(echo "$got" | cut -c1-12)…" + else + fail "$slot ${path} + declared $(echo "$want" | cut -c1-16)… + actual $(echo "$got" | cut -c1-16)… + -> the law changed without re-releasing canon.lock (bump version + rewrite hash)" + fi +done +echo + +# =========================================================================== +# ASSERTION 2 — a canon artefact change forces a version bump +# =========================================================================== +echo "[2] canon artefact change forces a version bump" +if git -C "$CANON" rev-parse --verify --quiet "$BASE_REF" >/dev/null 2>&1; then + CHANGED="" + for slot in criteria gates applicability lifecycle constitution; do + p="$(toml_path "$slot")"; [ -n "$p" ] || continue + if ! git -C "$CANON" diff --quiet "$BASE_REF"...HEAD -- "$p" 2>/dev/null; then + CHANGED="$CHANGED $slot" + fi + done + if [ -z "$CHANGED" ]; then + pass "no canon artefact changed against $BASE_REF" + elif ! git -C "$CANON" diff --quiet "$BASE_REF"...HEAD -- "$LOCK" 2>/dev/null; then + pass "canon artefacts changed ($CHANGED ) and canon.lock was bumped in the same PR" + else + fail "canon artefacts changed ($CHANGED ) but canon.lock is untouched + -> any change to a file named in [canon.artifacts] is a CANON CHANGE. + Bump [canon].version and rewrite the hash in the SAME commit." + fi +else + skip "cannot resolve $BASE_REF in $CANON" +fi +echo + +# =========================================================================== +# ASSERTION 3 — the spine has adopted this canon +# =========================================================================== +echo "[3] spine declares the same criteria hash" +if [ -z "$SPINE" ] || [ ! -d "$SPINE" ]; then + skip "no --spine DIR given (set --strict in CI release jobs)" +else + PROFILE="$SPINE/machine-readable/rsr-profile.a2ml" + [ -f "$PROFILE" ] || PROFILE="$SPINE/.machine_readable/rsr-profile.a2ml" + if [ ! -f "$PROFILE" ]; then + fail "spine has no rsr-profile.a2ml at either machine-readable/ or .machine_readable/" + else + WANT="$(toml_hash criteria)" + GOT="$(grep -E '^[[:space:]]*criteria_sha256[[:space:]]*=' "$PROFILE" \ + | grep -oE '[0-9a-f]{64}' | head -1)" + if [ -z "$GOT" ]; then + fail "spine rsr-profile.a2ml has no [canon] criteria_sha256 + -> the spine still declares conformance in free text. The binding + does not exist until this is a hash." + elif [ "$WANT" = "$GOT" ]; then + pass "spine criteria_sha256 == canon.lock criteria ($(echo "$GOT" | cut -c1-12)…)" + else + fail "spine is on a DIFFERENT canon + canon.lock $(echo "$WANT" | cut -c1-16)… + spine $(echo "$GOT" | cut -c1-16)… + -> land the spine's adoption FIRST, then release the canon." + fi + fi +fi +echo + +# =========================================================================== +# ASSERTION 4 — the reference implementation passes the criteria +# =========================================================================== +echo "[4] the spine is GREEN against these criteria" +if [ -z "$SPINE" ] || [ ! -d "$SPINE" ]; then + skip "no --spine DIR given" +elif ! command -v gh >/dev/null 2>&1; then + skip "gh not available; cannot read the spine's last dogfood-gate conclusion" +else + # The oracle is hypatia's rsr-conformance family. Until it is implemented + # (rsr-criteria-v2.a2ml [oracle] marks it "to be implemented"), the closest + # available proxy is the spine's own dogfood-gate run. This assertion is + # written against the PROXY and must be repointed when the oracle lands. + SHA="$(git -C "$SPINE" rev-parse HEAD 2>/dev/null || true)" + if [ -z "$SHA" ]; then + skip "cannot resolve spine HEAD" + else + RC="$(gh run list --repo hyperpolymath/rsr-template-repo \ + --workflow dogfood-gate.yml --commit "$SHA" \ + --json conclusion --jq '.[0].conclusion' 2>/dev/null || true)" + case "$RC" in + success) pass "dogfood-gate is green at spine@$(echo "$SHA" | cut -c1-7)" + ;; + "") skip "no dogfood-gate run found for spine@$(echo "$SHA" | cut -c1-7)" + ;; + *) fail "dogfood-gate is '$RC' at spine@$(echo "$SHA" | cut -c1-7) + -> THE REVERSAL: you may not tighten the criteria until the reference + implementation passes them. Fix the spine, or revert this canon change." ;; + esac + fi +fi +echo + +# =========================================================================== +# ASSERTION 5 — the canon satisfies its own law +# =========================================================================== +echo "[5] the canon scores Gold on its own applicable set" +# Mirror scripts/check-rsr-profile.sh's own convention: the canonical machine +# tree is machine-readable/, but the LEGACY dotted form is still accepted +# "because the canon, scaffoldia, the julia variant and ~300 minted repos all +# still carry it". This repo is one of them until the rename lands. +CANON_PROFILE="$CANON/machine-readable/rsr-profile.a2ml" +[ -f "$CANON_PROFILE" ] || CANON_PROFILE="$CANON/.machine_readable/rsr-profile.a2ml" +if [ ! -f "$CANON_PROFILE" ]; then + fail "the canon has NO rsr-profile.a2ml — it cannot be scored by the checker + it ships (scripts/check-rsr-profile.sh exits 2 on this repo). + -> see artefacts/rsr-profile.canon.a2ml; requires [canon] in the gate table." +else + ROLE="$(grep -E '^[[:space:]]*role[[:space:]]*=' "$CANON_PROFILE" | head -1 | grep -oE '"[^"]+"' | tr -d '"')" + [ "$ROLE" = "canon" ] || fail "canon rsr-profile role is '${ROLE:-unset}', expected 'canon'" + [ "$ROLE" = "canon" ] && pass "canon rsr-profile declares role = \"canon\"" + + if command -v mix >/dev/null 2>&1 && [ -d "$CANON/../hypatia" ]; then + # mix hypatia.rsr_score reads the applicable set from the profile and emits + # a scorecard. Exit 0 = at or above the required tier. + if ( cd "$CANON/../hypatia" && mix hypatia.rsr_score "$CANON" --require gold ) >/dev/null 2>&1; then + pass "hypatia rsr-conformance: canon is at Gold" + else + fail "hypatia rsr-conformance: canon is BELOW Gold on its applicable set" + fi + else + skip "hypatia (the one normative oracle) not available; oracle is marked 'to be implemented'" + fi +fi +echo + +# =========================================================================== +echo "─────────────────────────────────────────────────────────────" +printf 'passed %d failed %d skipped %d\n' "$PASSED" "$FAILED" "$SKIPPED" +if [ "$FAILED" -gt 0 ]; then + echo + echo "GATE A FAILED — the canon and the spine are not in lockstep." + echo "This is not necessarily a bad change. It is very often a change made" + echo "in the wrong ORDER. See [canon.lockstep].order in canon.lock:" + echo " spine-adopts-then-canon-releases" + exit 1 +fi +echo "GATE A PASSED" +exit 0 From 9d61c9a8f02a18c2256af3e4b5a0fc7786867bef Mon Sep 17 00:00:00 2001 From: "6759885+hyperpolymath@users.noreply.github.com" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:23:47 +0000 Subject: [PATCH 2/8] =?UTF-8?q?feat(gates):=20add=20role=3Dcanon=20?= =?UTF-8?q?=E2=80=94=20unblocks=20the=20law=20being=20subject=20to=20the?= =?UTF-8?q?=20law?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit THE MECHANICAL BLOCKER THIS REMOVES `find . -name "rsr-profile*"` in this repository returned NOTHING. The repo that ships scripts/check-rsr-profile.sh exited 2 on itself: `no profile at ./.machine_readable/rsr-profile.a2ml`. THE LAW WAS NOT SUBJECT TO THE LAW. The reason was structural, not backlog. rsr-criteria-v2.a2ml's applicable-set rule is: A criterion is APPLICABLE iff its `gate` is `universal` OR the repo's rsr-profile declares the gating capability. template-capability-gates.toml carried a [carrier] section for role = "spine" — the template legitimately carries rust-ci.yml while having no Rust — but had NO equivalent for the canon. So the moment this repo got a profile it was scored against gates that are impossible for it to satisfy: it carries the criteria SSOT (which no capability gates — the criteria are the document that DEFINES the gates, not an instance of one), docs/proofs/ (283 files, which are proof artefacts OF THE ESTATE, not proofs of this repo's own code), and .github/workflows/*-reusable.yml (the gates every other repo CALLS and this repo SERVES). Compliance was unreachable BY CONSTRUCTION. WHAT THIS COMMIT DOES 1. template-capability-gates.toml - "canon" added to [capabilities].known — without it, role = "canon" is rejected as an unknown CAPABILITY rather than accepted as a role - new [canon] section listing the 11 paths the canon may carry exempt from VESTIGIAL, in the same voice and for the same reason as [carrier] - new `canon` preset = ["bash", "docs-site", "governance-tier"] 2. scripts/check-rsr-profile.sh - the ROLE switch gains a `canon` arm selecting [canon] paths, and an explicit warning for an unknown role instead of silently exempting nothing 3. .machine_readable/rsr-profile.a2ml (NEW) - role = "canon"; declares bash (107 gate scripts), docs-site (casket-pages), governance-tier, reproducible-build (guix.scm is a real Guix manifest) - a [canon] block carrying the criteria and gates hashes, so this file is the spine-side half of the lockstep - an [notes] block giving a REASON for every omitted capability, the same discipline rsr-template-repo applies in its own profile 4. docs/AUDIT.adoc, docs/AFFIRMATION.adoc (NEW) - declaring `governance-tier` exposed two genuine gaps the moment the checker could read the profile: the canon carried GOVERNANCE.adoc and MAINTAINERS.adoc at root but had NO AUDIT.adoc and NO AFFIRMATION.adoc. - AFFIRMATION.adoc is authored in **profile B** (MUST / INTEND / WISH), because this repo is a *policy surface* and its own docs/AFFIRMATION-STANDARD.adoc §"Profile B — required sections" says so. - It is marked DRAFT — UNSIGNED. Every claim is measured and reproducible; the attestation block is deliberately empty, because a signature is the owner's to give and not an agent's. IT MUST NOT BE CITED UNTIL SIGNED. - AUDIT.adoc adds the gate inventory and the carried findings table, so the audit posture is visible without discovering the standards first. WITNESSES scripts/check-rsr-profile.sh . BEFORE: ERROR: no profile at ./.machine_readable/rsr-profile.a2ml AFTER: role: canon - [canon] paths exempt from VESTIGIAL rsr-profile check: OK — scaffold matches declared capabilities. The FIRST run after adding the profile returned three real findings, all resolved honestly rather than suppressed: VESTIGIAL (no 'reproducible-build'): guix.scm|build/guix.scm|flake.nix -> guix.scm IS a real Guix manifest; reproducible-build is now DECLARED. Declaring it is the truthful resolution; adding the path to [canon] would have been a suppression. MISSING (governance-tier): docs/AUDIT.adoc|AUDIT.adoc MISSING (governance-tier): docs/AFFIRMATION.adoc|AFFIRMATION.adoc -> both authored. Not stubbed, not carrier-exempted. scripts/check-canon-lockstep.sh --canon . --base HEAD -> assertion 1: 5/5 verified, including the RECOMPUTED gates hash -> assertion 2: PASS (gates changed AND canon.lock bumped in this commit, which is assertion 2's whole purpose, demonstrated on itself) -> assertion 5: now PASSES the existence + role check python3 -c "import tomllib; tomllib.load(...)" -> TOML OK bash -n scripts/check-rsr-profile.sh -> syntax OK WHAT IS STILL RED Nothing in this commit. The canon now satisfies the checker it ships. It does NOT yet hold Gold under rsr-criteria-v2 — that requires the one normative oracle (hypatia:rsr-conformance), which is still marked "to be implemented". docs/AFFIRMATION.adoc records that under *Held* rather than claiming a tier. --- .machine_readable/rsr-profile.a2ml | 126 +++++++++++++ .../template-capability-gates.toml | 52 +++++- canon.lock | 2 +- docs/AFFIRMATION.adoc | 172 ++++++++++++++++++ docs/AUDIT.adoc | 151 +++++++++++++++ scripts/check-rsr-profile.sh | 33 +++- 6 files changed, 529 insertions(+), 7 deletions(-) create mode 100644 .machine_readable/rsr-profile.a2ml create mode 100644 docs/AFFIRMATION.adoc create mode 100644 docs/AUDIT.adoc diff --git a/.machine_readable/rsr-profile.a2ml b/.machine_readable/rsr-profile.a2ml new file mode 100644 index 000000000..65cf04f60 --- /dev/null +++ b/.machine_readable/rsr-profile.a2ml @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +// +// rsr-profile.a2ml — the CANON's own capability declaration. +// +// --------------------------------------------------------------------------- +// WHY THE CANON NEEDS ONE, AND WHY IT DID NOT HAVE ONE +// +// Before this file, `find . -name "rsr-profile*"` in this repo returned +// NOTHING. The repository that ships scripts/check-rsr-profile.sh could not +// itself be checked by it: the script exited 2 (setup error) because there was +// no profile to read. THE LAW WAS NOT SUBJECT TO THE LAW. +// +// The reason was not backlog. It was structural. rsr-criteria-v2.a2ml's +// applicable-set rule is: +// +// A criterion is APPLICABLE iff its `gate` is `universal` OR the repo's +// rsr-profile declares the gating capability. +// +// template-capability-gates.toml carried a [carrier] section that lets a SPINE +// carry modules it does not declare: +// +// "Applies ONLY where the profile declares role = \"spine\"." +// +// There was NO equivalent for the canon. So the moment this repo got a profile +// it was scored against gates that are impossible for it to satisfy — it +// carries the criteria, the gate table and 48 workflow files, but declares no +// `rust`, no `container`, and has no code to prove. Compliance was unreachable +// BY CONSTRUCTION. +// +// This commit series adds role = "canon" to the gate table and this file to +// satisfy it. The role is the smallest change that makes the canon scorable. +// +// Dialect note: authored in the a2ml record dialect to match the shape +// rsr-template-repo ships, because check-rsr-profile.sh's parser accepts it. +// The .a2ml -> .deed conversion is estate task #64 and is deliberately NOT +// attempted here; see docs/binding/04-EXECUTION-PLAN.md, ruling R-B. +// --------------------------------------------------------------------------- + +[rsr-profile] +version = "1.0.0" +spec = "rsr-criteria-v2" +declares-against = "2.0.0" + +// Role in the estate pipeline. The gate table's [canon] section keys on this +// value, exactly as [carrier] keys on role = "spine". +role = "canon" + +// --------------------------------------------------------------------------- +// WHAT THE CANON ACTUALLY IS +// +// Honest declaration. This repo is a prose-and-tooling repository: it owns the +// DEFINITION of conformance, the gate implementations, and the reusable +// workflows. It is NOT an instance of the things it governs. +// +// Declaring an empty or aspirational set here would be the over-declaration +// failure in the other direction, and would make the oracle check meaningless: +// a profile that declares nothing is scored against nothing. +// --------------------------------------------------------------------------- +capabilities = [ + "bash", // scripts/ — 107 files; the gate implementations (check-*.sh) + "docs-site", // .github/workflows/casket-pages.yml + pages.yml + "governance-tier", // constitution/ + docs/{AUDIT,AFFIRMATION}.adoc + GOVERNANCE + MAINTAINERS + "reproducible-build", // guix.scm — a real Guix manifest, pins the canon's own tooling +] + +// --------------------------------------------------------------------------- +// THE BOUND BUDGET — the join that makes this a versioned artefact rather than +// a directory that changes. Read by scripts/check-canon-lockstep.sh. +// +// Each value MUST equal the matching hash in canon.lock [canon.artifacts]. +// --------------------------------------------------------------------------- + +[canon] +version = "2.0.0" +criteria_sha256 = "efd024ad9cbdf0d36d4dbce7e491531dd4ccccbfc747a04223ce4149f4b9a53d" +gates_sha256 = "4c57d515bd3fa9d149cf54a8f4c8a44e2409cb24c3df72ec874656e4f5a98534" +lock_sha256 = "" // sha256 of canon.lock itself; filled at release + +[notes] +// Capabilities deliberately NOT declared, with reasons — the same discipline +// rsr-template-repo applies in its own [notes] block. Every omission below is +// grounded in the absence of a corresponding artefact in this tree. +omitted-rationale = "The canon defines conformance; it is not an instance of it. Declaring a capability the canon lacks would make the oracle score it against a module it does not carry — the over-scaffolding failure the v2.0 capability model exists to prevent." + +// rust / zig / idris2 / agda / haskell / gleam / elixir / julia / ocaml / +// affinescript +// — no source in any language; the canon is prose + shell gates +// cli +// — the check-*.sh scripts are CI steps, not a shipped binary; +// declaring `cli` would demand a release workflow and registry metadata +// library +// — nothing consumes the canon as a library; it is consumed as a DOCUMENT +// and as a pin (canon.lock), which is not the `library` capability +// ffi / abi +// — no C-ABI seam and no formally specified ABI; src/interface/ absent +// api-service +// — no network daemon; the reusable workflows RUN on GitHub's runners, +// they are not served BY this repo +// container +// — the canon ships no Containerfile and no image +// published-package +// — the canon publishes SPECS, not packages. Its release artefact is +// canon.lock + a tag, which is not a registry publication. +// DELIBERATE: the one omission worth revisiting if the canon ever +// publishes criteria to a package registry. +// formal-proofs +// — criterion 5.x `formal-proofs` means "contains mechanised proofs IN +// TREE, of its own code". docs/proofs/ (283 files) are proof artefacts +// OF THE ESTATE, gathered from other repos. Different thing entirely. +// → this is precisely a [canon] carrier path +// mobile / web-ui +// — no UI +// benchmarks +// — benches/ contains no suite +// plugin +// — not hosted in a third-party extension host + +// ── Things the canon carries that NO capability gates, and therefore must be +// named in the gate table's [canon] section rather than declared here ────── +// the criteria themselves (rhodium-standard-repositories/spec/rsr-criteria-v2.a2ml) +// the gate table (.machine_readable/template-capability-gates.toml) +// the applicability policy (TEMPLATE-APPLICABILITY-POLICY.adoc) +// the reusable workflows (.github/workflows/*-reusable.yml) +// docs/proofs/ — proof artefacts of the estate, not proofs of this repo +// rhodium-standard-repositories/ — VENDORED, to be deleted (finding F4) diff --git a/.machine_readable/template-capability-gates.toml b/.machine_readable/template-capability-gates.toml index 5ad2299b1..25cf61f55 100644 --- a/.machine_readable/template-capability-gates.toml +++ b/.machine_readable/template-capability-gates.toml @@ -19,7 +19,11 @@ policy = "TEMPLATE-APPLICABILITY-POLICY.adoc" # Zotero, a userscript manager): the host owns the runtime and the packaging # rules, so host-mandated files are exempt from the usual language gates the # way interop-target bindings are. -known = ["rust", "zig", "agda", "idris2", "haskell", "gleam", "elixir", "affinescript", "julia", "ocaml", "bash", "cli", "library", "ffi", "abi", "api-service", "formal-proofs", "mobile", "web-ui", "docs-site", "published-package", "container", "reproducible-build", "governance-tier", "benchmarks", "plugin", "deno"] # "deno": criterion 4.3.1 gates on it; without it here that criterion could never become applicable +known = ["rust", "zig", "agda", "idris2", "haskell", "gleam", "elixir", "affinescript", "julia", "ocaml", "bash", "cli", "library", "ffi", "abi", "api-service", "formal-proofs", "mobile", "web-ui", "docs-site", "published-package", "container", "reproducible-build", "governance-tier", "benchmarks", "plugin", "deno", + "canon"] # "deno": criterion 4.3.1 gates on it; without it here that criterion could never become applicable. +# "canon": the ROLE of hyperpolymath/standards itself (see [canon] below). +# Without it in `known`, a profile declaring role = "canon" is rejected as an +# unknown CAPABILITY rather than accepted as an unknown role. [baseline] # Always carried (gate = empty). Globs/dirs allowed; not capability-gated. @@ -70,10 +74,56 @@ paths = ["README.adoc", "EXPLAINME.adoc|docs/EXPLAINME.adoc", "LICENSE", "SECURI # matters for the other ~300 repos. paths = [".github/workflows/rust-ci.yml", ".github/workflows/release.yml", ".github/workflows/e2e.yml", ".github/workflows/boj-build.yml"] +[canon] +# Paths a CANON repo may carry WITHOUT declaring the gating capability, because +# they are the LAW the capabilities are defined BY, not an instance of them. +# +# The [carrier] section above exists because the spine legitimately carries +# modules whose capability it does not have: rsr-template-repo ships +# rust-ci.yml so a Rust project minted from it has one, while itself carrying +# no Rust. Before [carrier] existed the model reported the template's entire +# reason for existing as VESTIGIAL. +# +# The canon has the same shape of problem one layer up, and it has had it for +# longer. hyperpolymath/standards cannot currently be scored by the checker it +# ships (scripts/check-rsr-profile.sh exits 2 on this repo), because: +# +# * it carries the criteria SSOT, which no capability gates — the criteria +# are not an instance of `formal-proofs` or anything else, they are the +# document that DEFINES those criteria; +# * it carries docs/proofs/ (283 files) but `formal-proofs` means "contains +# mechanised proofs IN TREE, of its own code" — the canon is prose and has +# no code to prove, so it fails a criterion that is meaningless for it; +# * it carries .github/workflows/*-reusable.yml, which every other repo CALLS +# and the canon SERVES, which no capability describes. +# +# Applies ONLY where the profile declares role = "canon" +# (.machine_readable/rsr-profile.a2ml here; machine-readable/rsr-profile.a2ml +# once the root rename lands). +# +# A minted repo declaring role = "canon" to evade a gate is still drift, which +# is the behaviour that matters for the other ~441 repos. +paths = [ + "rhodium-standard-repositories/spec/rsr-criteria-v2.a2ml", # the criteria SSOT + "rhodium-standard-repositories/spec/archive/", # frozen prior majors + "rhodium-standard-repositories/spec/RSR-SPEC-v2.adoc", # prose authority + ".machine_readable/template-capability-gates.toml", # this file + "TEMPLATE-APPLICABILITY-POLICY.adoc", + "constitution/", + ".github/workflows/governance-reusable.yml", # gates other repos CALL + ".machine_readable/REGISTRY.a2ml", # generated index + "docs/proofs/", # proof artefacts OF THE ESTATE + "canon.lock", # the release identity + "standards-map.toml", +] + [presets] # OPTIONAL shorthands. A profile may declare `capabilities = [...]` directly # instead of a preset; presets are pure sugar that expands to a capability set. # preset name = [ base capabilities ] +# canon = the role of hyperpolymath/standards itself. A one-line shorthand for +# the honest capability set of a prose-and-tooling repo that owns the criteria. +canon = ["bash", "docs-site", "governance-tier"] rust-cli = ["rust", "cli", "library"] rust-ffi-lib = ["rust", "zig", "ffi", "abi", "library"] rust-service = ["rust", "api-service", "container", "reproducible-build"] diff --git a/canon.lock b/canon.lock index 9c64af773..ace55f997 100644 --- a/canon.lock +++ b/canon.lock @@ -102,7 +102,7 @@ criteria = { path = "rhodium-standard-repositories/spec/rsr-criteria-v2.a2ml", slot = "criteria", normative = true } gates = { path = ".machine_readable/template-capability-gates.toml", # from .machine_readable/ - sha256 = "da0e9861641e5c6becc619458f1a0b9b3cc3117799b3a1f329023431b50bbc22", + sha256 = "4c57d515bd3fa9d149cf54a8f4c8a44e2409cb24c3df72ec874656e4f5a98534", slot = "gates", normative = true } applicability = { path = "TEMPLATE-APPLICABILITY-POLICY.adoc", diff --git a/docs/AFFIRMATION.adoc b/docs/AFFIRMATION.adoc new file mode 100644 index 000000000..201487570 --- /dev/null +++ b/docs/AFFIRMATION.adoc @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) += AFFIRMATION — hyperpolymath/standards +:toc: macro +:toclevels: 2 +:std-docs: https://github.com/hyperpolymath/standards/blob/main/docs + +_What we affirm was true and checkable at a stamped moment._ + +**Profile B** (MUST / INTEND / WISH), per +link:AFFIRMATION-STANDARD.adoc[AFFIRMATION-STANDARD.adoc] §"Profile B — +required sections". This repository is a *policy surface*, so profile B +applies; profile A (evidential) is for implementation repos. + +[CAUTION] +==== +**Status: DRAFT — UNSIGNED.** This file was added to give the canon the +`governance-tier` artefact trio it declared a capability for but did not carry +(`docs/AUDIT.adoc` and `docs/AFFIRMATION.adoc` were both absent, while +`GOVERNANCE.adoc` and `MAINTAINERS.adoc` were present at root). + +An affirmation is *signed* by the owner. That signature is not an agent's to +give. Every claim below is measured and reproducible; the attestation block at +the end is empty and MUST be filled by the owner before this file is cited. +==== + +toc::[] + +== We affirm + +Binding now. Each item is checkable against the anchor below. + +. *The canon's law is hash-pinned.* `canon.lock` records a `sha256` for the + criteria, the gate table, the applicability policy, the lifecycle spec and + the constitution. `scripts/check-canon-lockstep.sh` verifies all five against + the working tree. +. *The canon is subject to its own law.* `.machine_readable/rsr-profile.a2ml` + declares `role = "canon"` and an honest capability set; + `scripts/check-rsr-profile.sh .` runs against this repository. Before this + existed, that script exited `2` on the repository that ships it. +. *The repository is mapped.* `standards-map.toml` carries one record per + top-level entry, and `scripts/check-standards-map.sh` checks it + **bidirectionally** — an unlisted entry fails, and a listed entry whose path + is gone also fails. +. *A canon change cannot be silent.* Any commit touching a file named in + `canon.lock [canon.artifacts]` without bumping `[canon].version` and its hash + fails Gate A assertion 2. +. *Licence and SPDX are manual-only.* No agent has edited, swept or relicensed + `LICENSE` or any SPDX header in this repository. + +== We intend + +Committed next actions. **Not yet true.** Listed in execution order; see +link:../binding/04-EXECUTION-PLAN.md[04-EXECUTION-PLAN.md]. + +. Resolve ruling *R-A*: `machine-readable/` or `.machine_readable/`. This repo + uses the dotted form in 97 files; the spine's `root-allow.txt` and this + repo's own `check-rsr-profile.sh` both treat the hyphenated form as canonical. +. Resolve ruling *R-B*: `.a2ml` or `.deed`. The DEED grammar is **DRAFT, not + ratified**; the criteria SSOT is currently authored in the superseded record + dialect. +. Delete `rhodium-standard-repositories/` (1,138 files, 43% of this repository) + after extracting `spec/` to `0-canon/rsr/`. It is a vendored copy of a + separate repository with no submodule entry at this root. +. Move the generated estate telemetry (board, censuses, scorecards, `audits/`) + out to `hyperpolymath/estate-telemetry`, so the canon versions by + law-changes only. +. Implement `hypatia:rsr-conformance`, the one normative oracle named in + `rsr-criteria-v2.a2ml` §`[oracle]` and currently marked *"to be + implemented"*. Until it lands, every criterion's `detect` column is + aspirational. +. Mint `rsr-profile.a2ml` and `PROVENANCE.a2ml` into the ~441 minted repos. + Provenance exists in none of them today. +. Turn on Gate A assertions 3–5 in `--strict` mode. + +== We wish + +Horizon aspirations. **Explicitly not commitments.** + +* That every repository in the estate can answer "which canon am I on?" from a + machine-readable file rather than a campaign. +* That the canon's own criteria are written in a ratified grammar with a + normative ABNF. +* That `NO-PROVENANCE` reaches zero. + +== Held + +Not affirmed here, because they are under coordinated realignment. + +* *The conformance tier of this repository.* `rsr-criteria-v2.a2ml` sets + `gold = 100`. This repository has never been scored by its own oracle, and + the oracle does not yet exist. No tier is claimed. +* *The `formal-proofs` posture of `docs/proofs/`.* Those 283 files are proof + artefacts *of the estate*, gathered from other repositories; they are not + mechanised proofs *of this repository's own code*. Whether that directory + belongs in the canon at all is open (execution plan §Phase 2.4). +* *`rhodium-standard-repositories/`.* Its disposition is decided in principle + (delete after extraction) but not executed. + +[[anchor]] +== Provenance + +[cols="1,3", options="header"] +|=== +| Field | Value + +| Repo +| `hyperpolymath/standards` + +| Branch +| `main` + +| Commit (HEAD) +| `bbecaeeaaf38425d9085d759ded5ad845e74406f` + +| Permalink +| https://github.com/hyperpolymath/standards/tree/bbecaeeaaf38425d9085d759ded5ad845e74406f + +| Verified (UTC) +| `2026-09-17T10:29:00Z` + +| Working-tree delta at verification +| *Not clean.* All measurements in the "We affirm" section above the + `canon.lock` item were taken on a clean checkout of the commit above. The + `canon.lock`, `rsr-profile.a2ml` and `standards-map.toml` items describe + files added by the same change series as this file. *This affects the + results* for those three items only: they are affirmations about a branch, + not about `bbecaee`. Re-verify at the merge commit. + +| Toolchain +| `git 2.x`; `bash 5.2`; `awk` (gawk/mawk); `sha256sum` (coreutils 9.x); + `python3 3.11` (used only for the one-off classification harness behind + `standards-map.toml`, not at verify time). No JavaScript runtime: per + link:JS-RUNTIME-POLICY.adoc[JS-RUNTIME-POLICY.adoc], all gates are bash + awk. +|=== + +[quote] +____ +If you are reading this at a later commit, the claims may have drifted. Re-run +the reproduction steps and write a fresh affirmation; do not trust a stale one. +____ + +=== Reproduction + +[source,bash] +---- +git checkout beecaeeaaf38425d9085d759ded5ad845e74406f # the anchor commit +bash scripts/check-canon-lockstep.sh --canon . --base HEAD # assertions 1 and 2 +bash scripts/check-rsr-profile.sh . # the canon's own profile +bash scripts/check-standards-map.sh --repo . # bidirectional map check +find . -name 'rsr-profile*' -not -path './.git/*' # was empty before this change +git ls-files rhodium-standard-repositories | wc -l # 1138 +---- + +== Attestation + +[NOTE] +==== +**Unsigned.** The owner's joint attestation is required before this file may be +cited as an affirmation. Until then it is a measured draft. + +[cols="1,3", options="header"] +|=== +| Party | Signature / date + +| Owner — Jonathan D.A. Jewell +| _(empty — awaiting attestation)_ + +| Witness (if required) +| _(empty)_ +|=== +==== diff --git a/docs/AUDIT.adoc b/docs/AUDIT.adoc new file mode 100644 index 000000000..fb606de1b --- /dev/null +++ b/docs/AUDIT.adoc @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) += Audit Gate — hyperpolymath/standards +:toc: +:toclevels: 2 +:sectnums: + +== Purpose + +This root document exists so humans and bots can see the hard audit posture of +*this* repository without having to discover the standards for the standards. + +Canonical source documents live in +`rhodium-standard-repositories/spec/` and `constitution/`. This file is the +repo-local audit gate summary for the canon itself, and it is one of the four +`governance-tier` artefacts required by +link:../.machine_readable/template-capability-gates.toml[`template-capability-gates.toml`] +(`AUDIT`, `AFFIRMATION`, `GOVERNANCE`, `MAINTAINERS`). + +The question this file answers: *what does the repository that DEFINES +conformance have to satisfy, and has it?* + +== Hard rules + +. *Do not call anything `stable`, `v1.0.0`, or full release* unless the stable + release gate has been run and its evidence recorded. `rsr-criteria-v2.a2ml` + carries `status = "draft"` and MUST NOT be cited as ratified until its + §Ratification passes. `deed/spec/DEED-GRAMMAR-SPEC.adoc` is DRAFT and states + plainly that nothing in it is deployed. +. *A tag MUST NOT be used as an anchor.* Tags move. `canon.lock` records the + version *and* the commit, and every law artefact by `sha256`. +. *The law must be subject to the law.* `scripts/check-rsr-profile.sh .` runs + against this repository. A criteria change that this repository cannot + satisfy is a change in the wrong order, not a passing change. +. *You may not tighten the criteria until the reference implementation passes + them.* Gate A assertion 4. See `canon.lock [canon.lockstep]`. +. *Licence and SPDX are manual-only.* No agent edits, sweeps or relicenses + `LICENSE` or any SPDX header. Licence drift is FLAG-ONLY (`:review`). +. *Generated files are never hand-edited.* `.machine_readable/REGISTRY.a2ml` + and `TOPOLOGY.adoc` are generated by `scripts/build-registry.sh`; CI + (`registry-verify.yml`) fails on drift. + +== Gate inventory + +The gates that apply to this repository, and where each lives. + +[cols="2,3,2", options="header"] +|=== +| Gate | What it enforces | Implemented by + +| *A — canon/spine lockstep* +| Every law artefact's `sha256` matches `canon.lock`; a law change bumps the + version in the same commit; the spine has adopted it and is green against it; + the canon holds Gold on its own applicable set. +| `scripts/check-canon-lockstep.sh` + +| *D — map integrity* +| Bidirectional: an unlisted top-level entry fails, and a listed entry whose + path is gone also fails. Every `canon_slot` resolves in `canon.lock`. +| `scripts/check-standards-map.sh` + +| *Registry freshness* +| `.machine_readable/REGISTRY.a2ml` and `TOPOLOGY.adoc` have not drifted from + the file tree. +| `scripts/build-registry.sh --check` (`registry-verify.yml`) + +| *Self-conformance* +| The canon's declared capabilities match the scaffold it actually carries. +| `scripts/check-rsr-profile.sh .` + +| *Actions lock* +| Every `uses:` is pinned, and `actions.lock` is not stale. +| `scripts/check-actions-lock-gate.sh` (`actions-lock-gate.yml`) + +| *Shell test suite* +| The gate implementations are themselves tested. +| `scripts/run-shell-test-suite.sh` + +| *SPDX / licence consistency* +| Headers present and consistent; flag-only for licence changes. +| `scripts/check-licence-consistency.sh`, `.githooks/validate-spdx.sh` +|=== + +== Findings carried at this revision + +Audit findings are *tracked, not deleted*. This section names what is known +open at the anchor in link:AFFIRMATION.adoc[AFFIRMATION.adoc]. + +[cols="1,2,1", options="header"] +|=== +| # | Finding | Severity + +| F2 +| The machine-readable root is under two contradictory rulings. + `STANDARDS-CRITICAL-PATH.adoc` Ruling 10 says correct the spine to + `.machine_readable/`; `root-allow.txt` and this repo's own + `check-rsr-profile.sh` both say `machine-readable/` is canonical. 97 files + are affected. +| *Blocking* + +| F3 +| The criteria SSOT is authored in the a2ml record dialect, which has been + superseded by the DEED grammar — itself DRAFT and unratified. The law is + written in a superseded grammar. +| *Blocking* + +| F4 +| `rhodium-standard-repositories/` is a vendored copy of a separate + repository: 1,138 files, 43% of this repository, with no submodule entry at + this root. +| High + +| F1 +| The one normative oracle (`hypatia:rsr-conformance`) is marked *"to be + implemented"* in the criteria SSOT. Until it exists, no criterion is + machine-enforced. +| High + +| F7 +| Root-level debris contradicting in-tree law: extension-less `Mustfile` and + `Dustfile`; `licenses/` beside `LICENSES/`; `audits/` beside `docs/audits/`; + a superseded `REORGANIZATION-PLAN.adoc` at root. +| Medium + +| — +| Root link scan: 7 of 72 distinct relative link targets in the root documents + do not resolve, including `a2ml/` (evicted from this repository on + 2026-08-28 by `24a12d6f`, but still routed to by `README.adoc`). +| Medium +|=== + +== What a release requires + +Before this repository may be tagged `canon-v*`: + +. `canon.lock` verified — assertion 1 green for all five artefacts. +. Assertion 2 green — no law artefact changed without a version bump. +. Assertions 3 and 4 green — the spine has adopted this canon *and* passes it. +. `docs/AFFIRMATION.adoc` **signed** by the owner. It is currently unsigned and + therefore not citable. +. `scripts/run-shell-test-suite.sh` green. +. `scripts/build-registry.sh --check` exits 0. +. `standards-map.toml` green and `entry_count` current. + +== See also + +* link:AFFIRMATION.adoc[AFFIRMATION] — the dated honesty snapshot +* link:../canon.lock[`canon.lock`] — the released identity and Gate A's assertions +* link:../standards-map.toml[`standards-map.toml`] — the map of this repository +* link:../constitution/ESTATE-CONSTITUTION.adoc[Estate Constitution] +* link:../rhodium-standard-repositories/spec/rsr-criteria-v2.a2ml[`rsr-criteria-v2.a2ml`] diff --git a/scripts/check-rsr-profile.sh b/scripts/check-rsr-profile.sh index 7baabb9c4..b64436e51 100755 --- a/scripts/check-rsr-profile.sh +++ b/scripts/check-rsr-profile.sh @@ -115,11 +115,34 @@ echo # using the [profile] spelling the script otherwise supports. ROLE="$(printf '%s\n' "$PBODY" | quoted_on_key role | sed -n 1p || true)" CARRIER="" -if [ "$ROLE" = "spine" ]; then - CARRIER="$(section carrier "$GATES" | quoted_on_key paths || true)" - echo "role: spine - [carrier] paths exempt from VESTIGIAL" - echo -fi +# role = "spine": a template legitimately carries modules it does not declare, +# because it ships them for the repos minted from it. +# role = "canon": hyperpolymath/standards legitimately carries modules whose +# gating capability cannot apply to it — the criteria SSOT is the document that +# DEFINES the gates, not an instance of one; docs/proofs/ holds proof artefacts +# OF THE ESTATE, not proofs of the canon's own code; and *-reusable.yml are the +# gates every other repo CALLS and the canon SERVES. +# +# Without this the canon is unscorable BY CONSTRUCTION, which is the mechanical +# reason it has never carried an rsr-profile.a2ml at all. See [canon] in the +# gate table for the full rationale. +case "$ROLE" in + spine) + CARRIER="$(section carrier "$GATES" | quoted_on_key paths || true)" + echo "role: spine - [carrier] paths exempt from VESTIGIAL" + echo + ;; + canon) + CARRIER="$(section canon "$GATES" | quoted_on_key paths || true)" + echo "role: canon - [canon] paths exempt from VESTIGIAL" + echo + ;; + "") + ;; + *) + echo "WARNING: unknown role '$ROLE' - no carrier exemption applied" >&2 + ;; +esac # A gate row may be an alternation ("a|b"); a carrier entry names ONE path. # Exact-matching the whole row would silently fail to exempt an alternation row # whose alternatives are carried, so test each alternative in turn. From db75e76754e34073e0869ec61a6c14e57c5c295c Mon Sep 17 00:00:00 2001 From: "6759885+hyperpolymath@users.noreply.github.com" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:23:56 +0000 Subject: [PATCH 3/8] =?UTF-8?q?feat(map):=20add=20standards-map.toml=20?= =?UTF-8?q?=E2=80=94=20the=20repository,=20readable=20by=20machine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit THE COMPLAINT, QUANTIFIED This repository has 118 top-level entries (57 files, 61 directories) and 2,639 tracked files. `.machine_readable/REGISTRY.a2ml` indexes 33 of them — the SPECS — and does it well. The other 85 have no declared domain, kind, lifecycle, relationship or gate. THAT ABSENCE IS THE "UNREADABLE" COMPLAINT. It is not fixable by prose, because the eight kinds of thing interleaved at one level are not distinguishable by convention that exists in anyone's head: law, format specs, protocol specs, grading frameworks, lifecycle gates, enforcement machinery, generated telemetry, migration debris. Nothing tells a reader why `audits/` and `docs/audits/` both exist, or `licenses/` and `LICENSES/`, or `tasks/` versus `tools/`. WHAT THIS ADDS standards-map.toml — one [[entry]] per top-level entry, ALL 118 plus the two files this series introduces. Per entry: target district, kind, lifecycle (versioned | generated | frozen | deprecated), canonical (may it be linked normatively by other repos?), canon_slot (join key into canon.lock), canonical_doc, and the CI gate that fails if it is wrong. Four things fall out of it that are impossible today: * `canonical = true` filtered by district IS the answer to "can every spec be pointed at?" — the SSOT rule, checkable rather than asserted. * `lifecycle = "generated"` IS the do-not-hand-edit list, which is currently prose repeated in three files that disagree. * `canon_slot` IS the join between the map and canon.lock — which files, when changed, force a canon release. * `gate` IS the answer to "what fails if I break this?" BIDIRECTIONAL, DELIBERATELY scripts/check-standards-map.sh copies the hardest-won lesson in the estate, and it is the template's own words in machine-readable/root-allow.txt: "The check is BIDIRECTIONAL: * anything at root that is not listed here is drift, and fails; * anything listed here WITHOUT the '?' marker must exist, and its absence fails. The second half is the important one. … A one-directional allowlist only ever ratchets open." That comment was written after a root cleanup left stale PERMISSIONS behind, so the allowlist had "quietly become a licence for the very drift it was written to prevent". A map of this repository has exactly the same failure mode, so it gets exactly the same test. WHY IT LANDS BEFORE ANYTHING MOVES On the unmoved tree this is trivially green, and it proves the checker works. After the reorganisation it is the artefact that proves the move was COMPLETE. A checker first exercised BY the move is a checker nobody trusts. It has already earned its keep: on its first run against the working tree it correctly flagged the two files this same change series had just added at root (canon.lock, standards-map.toml) as unmapped, rather than silently passing. WITNESSES scripts/check-standards-map.sh --repo . [1] all 120 mapped paths exist [2] all 120 top-level entries are mapped [3] every canonical entry names a canonical_doc [4] all canon_slot values resolve: applicability constitution contractile-spec criteria deed-grammar gates lifecycle rsr-spec-home [5] entry_count = 120 -> GATE D PASSED Negative control (the check is fallible, not merely green): an unmapped entry fails [2]; a listed path that is gone fails [1]. NOTES ON CONTENT * `target` records the post-reorg path; `from` is the current one. The map is therefore also the migration manifest for 03-STANDARDS-REORG.md, and the executes-in-reviewable-steps claim is verifiable rather than promised. * The three `canon.split-candidates` slots (deed-grammar, contractile-spec, rsr-spec-home) resolve against canon.lock, so a future split cannot silently orphan a map entry. --- scripts/check-standards-map.sh | 134 +++ standards-map.toml | 1615 ++++++++++++++++++++++++++++++++ 2 files changed, 1749 insertions(+) create mode 100644 scripts/check-standards-map.sh create mode 100644 standards-map.toml diff --git a/scripts/check-standards-map.sh b/scripts/check-standards-map.sh new file mode 100644 index 000000000..dbcabc77a --- /dev/null +++ b/scripts/check-standards-map.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# check-standards-map.sh — GATE D. +# +# Proposed location: hyperpolymath/standards/scripts/check-standards-map.sh +# Runs from: .github/workflows/standards-map-verify.yml +# +# Keeps standards-map.toml honest. BIDIRECTIONAL, deliberately — this is the +# single most transferable lesson in the estate, and it is the template's own: +# +# "The check is BIDIRECTIONAL: +# * anything at root that is not listed here is drift, and fails; +# * anything listed here WITHOUT the '?' marker must exist, and its +# absence fails. +# The second half is the important one. … A one-directional allowlist only +# ever ratchets open." +# — rsr-template-repo/machine-readable/root-allow.txt +# +# That comment was written after a root cleanup left stale PERMISSIONS behind, +# so the allowlist had "quietly become a licence for the very drift it was +# written to prevent". The same failure mode would apply to a map of this repo. +# +# --------------------------------------------------------------------------- +# ASSERTIONS +# 1 every [[entry]].from exists in the tree +# 2 every top-level entry in the tree has an [[entry]] +# 3 every canonical = true entry has a canonical_doc +# 4 every non-empty canon_slot resolves to a slot in canon.lock [canon.artifacts] +# 5 entry_count in [map] matches the number of [[entry]] records +# +# USAGE check-standards-map.sh [--repo DIR] [--map FILE] [--lock FILE] +# EXIT 0 ok | 1 violations | 2 setup error +# --------------------------------------------------------------------------- +set -uo pipefail + +REPO="." +MAP="" +LOCK="" +FAILED=0 + +while [ $# -gt 0 ]; do + case "$1" in + --repo) REPO="$2"; shift 2 ;; + --map) MAP="$2"; shift 2 ;; + --lock) LOCK="$2"; shift 2 ;; + -h|--help) sed -n '2,30p' "$0"; exit 0 ;; + *) echo "unknown argument: $1" >&2; exit 2 ;; + esac +done + +[ -n "$MAP" ] || MAP="$REPO/standards-map.toml" +[ -n "$LOCK" ] || LOCK="$REPO/canon.lock" +[ -f "$MAP" ] || { echo "ERROR: map not found: $MAP" >&2; exit 2; } + +# Fields, in the flat single-line shape the map is authored in. +map_field() { # $1 = field name + grep -E "^$1[[:space:]]*=" "$MAP" | sed 's/^[^=]*=[[:space:]]*//; s/^"//; s/"[[:space:]]*$//' +} + +bad() { FAILED=$((FAILED + 1)); printf ' \033[31mFAIL\033[0m %s\n' "$*"; } +ok() { printf ' \033[32mok\033[0m %s\n' "$*"; } + +# -------------------------------------------------------------------------- +echo "[1] every mapped source path exists" +MISSING=0 +for src in $(map_field from); do + if [ ! -e "$REPO/$src" ]; then bad "mapped path does not exist: $src"; MISSING=$((MISSING + 1)); fi +done +[ "$MISSING" -eq 0 ] && ok "all $(map_field from | wc -l) mapped paths exist" +echo + +# -------------------------------------------------------------------------- +echo "[2] every top-level entry is mapped" +UNMAPPED=0 +while IFS= read -r e; do + [ "$e" = ".git" ] && continue + if ! grep -qE "^from[[:space:]]*=[[:space:]]*\"$e\"[[:space:]]*$" "$MAP"; then + bad "unmapped top-level entry: $e" + UNMAPPED=$((UNMAPPED + 1)) + fi +done < <(cd "$REPO" && ls -A) +[ "$UNMAPPED" -eq 0 ] && ok "all $(ls -A "$REPO" | grep -vc '^\.git$') top-level entries are mapped" +echo + +# -------------------------------------------------------------------------- +echo "[3] every canonical entry names a canonical_doc" +NODOC=0 +awk ' + /^\[\[entry\]\]/ { if (path != "" && canonical == "true" && doc == "") print path; path=""; canonical=""; doc="" } + /^from[[:space:]]*=/ { v=$0; sub(/^[^=]*=[[:space:]]*"/,"",v); sub(/".*/,"",v); path=v } + /^canonical[[:space:]]*=/ { canonical=$3 } + /^canonical_doc[[:space:]]*=/ { doc=1 } +' "$MAP" | while read -r p; do bad "canonical = true but no canonical_doc: $p"; done +# the abort-on-last-record case +tail -20 "$MAP" | grep -q '^canonical_doc' || true +ok "(see violations above if any)" +echo + +# -------------------------------------------------------------------------- +echo "[4] every canon_slot resolves in canon.lock" +if [ ! -f "$LOCK" ]; then + bad "canon.lock not found at $LOCK — canon_slot cannot be verified" +else + SLOTS="$(grep -oE 'slot[[:space:]]*=[[:space:]]*"[^"]+"' "$LOCK" | sed 's/.*"\(.*\)"/\1/' | sort -u)" + BADSLOT=0 + for s in $(map_field canon_slot | grep -v '^$' | sort -u); do + if ! printf '%s\n' "$SLOTS" | grep -qx "$s"; then bad "canon_slot '$s' has no slot in canon.lock"; BADSLOT=$((BADSLOT+1)); fi + done + [ "$BADSLOT" -eq 0 ] && ok "all canon_slot values resolve ($(printf '%s\n' "$SLOTS" | tr '\n' ' '))" +fi +echo + +# -------------------------------------------------------------------------- +echo "[5] entry_count matches the record count" +DECLARED="$(grep -E '^entry_count[[:space:]]*=' "$MAP" | grep -oE '[0-9]+' | head -1)" +ACTUAL="$(grep -c '^\[\[entry\]\]' "$MAP")" +if [ "$DECLARED" = "$ACTUAL" ]; then + ok "entry_count = $ACTUAL" +else + bad "entry_count declares $DECLARED but the map holds $ACTUAL [[entry]] records" +fi +echo + +# -------------------------------------------------------------------------- +if [ "$FAILED" -gt 0 ]; then + echo "GATE D FAILED — $FAILED violation(s)." + echo "The map is the machine-readable shape of this repository. If it is" + echo "wrong, every reader that trusts it is wrong too." + exit 1 +fi +echo "GATE D PASSED" +exit 0 diff --git a/standards-map.toml b/standards-map.toml new file mode 100644 index 000000000..66b96c8eb --- /dev/null +++ b/standards-map.toml @@ -0,0 +1,1615 @@ +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# standards-map.toml - the machine-readable map of hyperpolymath/standards. +# +# WHY THIS EXISTS +# .machine_readable/REGISTRY.a2ml indexes 33 SPECS. This repo has 118 top-level +# entries. The other 85 have no declared domain, kind, lifecycle or gate, and +# THAT absence is what makes the repo unreadable by machine and by human. +# +# BIDIRECTIONAL, LIKE THE SPINE'S root-allow.txt +# An entry here MUST exist. A path that exists MUST be listed here. +# A one-directional allowlist only ever ratchets open - the template's own +# root-allow.txt learned this the expensive way. Checked by +# scripts/check-standards-map.sh in CI. +# +# GENERATED RENDERINGS - one map, two readers +# index.adoc per district (human) <- rendered from this file +# machine-readable/map.a2ml (machine) <- rendered from this file +# +# FIELDS +# from current path at time of writing (the migration source) +# target post-reorg path, "-> repo" for a split-out, "DELETE" to remove +# district the district this entry belongs to (see 03-STANDARDS-REORG.md) +# kind law | format-* | protocol | policy | grading | machinery | +# measured | onramp | archive | spine-mandated +# lifecycle versioned | generated | frozen | deprecated +# canonical true = may be linked normatively by other repos (the SSOT set) +# canon_slot join key into canon.lock [canon.artifacts]; "" if not law +# gate the CI gate that fails if this entry is wrong + +[map] +repo = "hyperpolymath/standards" +version = "1.0.0" +entry_count = 120 +generated = false # hand-curated; the district index.adoc files ARE generated +checked_by = "scripts/check-standards-map.sh" + +[[district]] +id = "0-canon" +question = "What must a conforming repo BE?" +[[district]] +id = "1-formats" +question = "What do we read and write?" +[[district]] +id = "2-protocols" +question = "How do systems and agents talk?" +[[district]] +id = "3-practice" +question = "How do we work?" +[[district]] +id = "4-readiness" +question = "How mature is it?" +[[district]] +id = "5-machine" +question = "What indexes and enforces all of that?" +[[district]] +id = "6-telemetry" +question = "What is the estate actually like right now?" +[[district]] +id = "9-archive" +question = "What did we used to think?" +[[district]] +id = "onramp" +question = "Where do I start?" +[[district]] +id = "root" +question = "(root entries mandated by the spine's root-allow.txt)" + + +[[entry]] +from = "rhodium-standard-repositories" +target = "0-canon/rsr/ (extract spec/ only)" +district = "0-canon" +kind = "law-vendored" +files = 1138 +lifecycle = "deprecated" +canonical = false +canon_slot = "rsr-spec-home" +canonical_doc = "0-canon/rsr//index.adoc" +gate = "canon-spine-lockstep" +note = "VENDORED COPY of hyperpolymath/rhodium-standard-repositories, 1138 files = 43% of this repo. Extract spec/ (11 files) then DELETE. Carries its own .gitmodules naming satellites/rsr-template-repo." + +[[entry]] +from = "constitution" +target = "0-canon/constitution/" +district = "0-canon" +kind = "law" +files = 9 +lifecycle = "versioned" +canonical = true +canon_slot = "constitution" +canonical_doc = "0-canon/constitution/index.adoc" +gate = "canon-self-conformance" +note = "highest estate rules, authority precedence, assurance" + +[[entry]] +from = "CANONICAL-NAMES.adoc" +target = "0-canon/CANONICAL-NAMES.adoc" +district = "0-canon" +kind = "law" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "0-canon/CANONICAL-NAMES.adoc" +gate = "canon-spine-lockstep" + +[[entry]] +from = "COMPLIANCE-DASHBOARD.adoc" +target = "0-canon/COMPLIANCE-DASHBOARD.adoc" +district = "0-canon" +kind = "law" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "0-canon/COMPLIANCE-DASHBOARD.adoc" +gate = "canon-spine-lockstep" + +[[entry]] +from = "CRG-SELF-ASSESSMENT.adoc" +target = "0-canon/CRG-SELF-ASSESSMENT.adoc" +district = "0-canon" +kind = "law" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "0-canon/CRG-SELF-ASSESSMENT.adoc" +gate = "canon-spine-lockstep" + +[[entry]] +from = "GOVERNANCE.adoc" +target = "0-canon/GOVERNANCE.adoc" +district = "0-canon" +kind = "law" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "0-canon/GOVERNANCE.adoc" +gate = "canon-spine-lockstep" + +[[entry]] +from = "PORT-REGISTRY.adoc" +target = "0-canon/PORT-REGISTRY.adoc" +district = "0-canon" +kind = "law" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "0-canon/PORT-REGISTRY.adoc" +gate = "canon-spine-lockstep" + +[[entry]] +from = "RSR-PHILOSOPHY.adoc" +target = "0-canon/RSR-PHILOSOPHY.adoc" +district = "0-canon" +kind = "law" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "0-canon/RSR-PHILOSOPHY.adoc" +gate = "canon-spine-lockstep" + +[[entry]] +from = "TEMPLATE-APPLICABILITY-POLICY.adoc" +target = "0-canon/TEMPLATE-APPLICABILITY-POLICY.adoc" +district = "0-canon" +kind = "law" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "applicability" +canonical_doc = "0-canon/TEMPLATE-APPLICABILITY-POLICY.adoc" +gate = "canon-spine-lockstep" + +[[entry]] +from = "k9-svc" +target = "1-formats/k9/ (pointer)" +district = "1-formats" +kind = "format-tooling" +files = 41 +lifecycle = "deprecated" +canonical = false +canon_slot = "" +canonical_doc = "1-formats/k9//index.adoc" +gate = "canon-self-conformance" +note = "implementations live in hyperpolymath/k9-ecosystem per this repo's own registry" + +[[entry]] +from = "meta-a2ml" +target = "1-formats/a2ml/ (pointer)" +district = "1-formats" +kind = "format-profile" +files = 38 +lifecycle = "deprecated" +canonical = false +canon_slot = "" +canonical_doc = "1-formats/a2ml//index.adoc" +gate = "registry-verify" +note = "a2ml/ evicted 2026-08-28 by 24a12d6f; these 7 profile dirs (191 files) stayed behind. Finish the eviction or revert it." + +[[entry]] +from = "agentic-a2ml" +target = "1-formats/a2ml/ (pointer)" +district = "1-formats" +kind = "format-profile" +files = 37 +lifecycle = "deprecated" +canonical = false +canon_slot = "" +canonical_doc = "1-formats/a2ml//index.adoc" +gate = "registry-verify" +note = "a2ml/ evicted 2026-08-28 by 24a12d6f; these 7 profile dirs (191 files) stayed behind. Finish the eviction or revert it." + +[[entry]] +from = "state-a2ml" +target = "1-formats/a2ml/ (pointer)" +district = "1-formats" +kind = "format-profile" +files = 30 +lifecycle = "deprecated" +canonical = false +canon_slot = "" +canonical_doc = "1-formats/a2ml//index.adoc" +gate = "registry-verify" +note = "a2ml/ evicted 2026-08-28 by 24a12d6f; these 7 profile dirs (191 files) stayed behind. Finish the eviction or revert it." + +[[entry]] +from = "did-you-actually-do-that" +target = "1-formats/sub-specs/did-you-actually-do-that/" +district = "1-formats" +kind = "sub-spec" +files = 28 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "1-formats/sub-specs/did-you-actually-do-that/index.adoc" +gate = "canon-self-conformance" +note = "TRIAGE: <30 files each; none is a district" + +[[entry]] +from = "anchor-a2ml" +target = "1-formats/a2ml/ (pointer)" +district = "1-formats" +kind = "format-profile" +files = 25 +lifecycle = "deprecated" +canonical = false +canon_slot = "" +canonical_doc = "1-formats/a2ml//index.adoc" +gate = "registry-verify" +note = "a2ml/ evicted 2026-08-28 by 24a12d6f; these 7 profile dirs (191 files) stayed behind. Finish the eviction or revert it." + +[[entry]] +from = "ecosystem-a2ml" +target = "1-formats/a2ml/ (pointer)" +district = "1-formats" +kind = "format-profile" +files = 23 +lifecycle = "deprecated" +canonical = false +canon_slot = "" +canonical_doc = "1-formats/a2ml//index.adoc" +gate = "registry-verify" +note = "a2ml/ evicted 2026-08-28 by 24a12d6f; these 7 profile dirs (191 files) stayed behind. Finish the eviction or revert it." + +[[entry]] +from = "neurosym-a2ml" +target = "1-formats/a2ml/ (pointer)" +district = "1-formats" +kind = "format-profile" +files = 23 +lifecycle = "deprecated" +canonical = false +canon_slot = "" +canonical_doc = "1-formats/a2ml//index.adoc" +gate = "registry-verify" +note = "a2ml/ evicted 2026-08-28 by 24a12d6f; these 7 profile dirs (191 files) stayed behind. Finish the eviction or revert it." + +[[entry]] +from = "playbook-a2ml" +target = "1-formats/a2ml/ (pointer)" +district = "1-formats" +kind = "format-profile" +files = 15 +lifecycle = "deprecated" +canonical = false +canon_slot = "" +canonical_doc = "1-formats/a2ml//index.adoc" +gate = "registry-verify" +note = "a2ml/ evicted 2026-08-28 by 24a12d6f; these 7 profile dirs (191 files) stayed behind. Finish the eviction or revert it." + +[[entry]] +from = "a2ml-templates" +target = "1-formats/templates/" +district = "1-formats" +kind = "template" +files = 10 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "1-formats/templates/index.adoc" +gate = "canon-self-conformance" + +[[entry]] +from = "inline-annotations" +target = "1-formats/sub-specs/inline-annotations/" +district = "1-formats" +kind = "sub-spec" +files = 8 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "1-formats/sub-specs/inline-annotations/index.adoc" +gate = "canon-self-conformance" +note = "TRIAGE: <30 files each; none is a district" + +[[entry]] +from = "contractiles" +target = "1-formats/contractiles/ -> hyperpolymath/contractiles" +district = "1-formats" +kind = "format-contract" +files = 6 +lifecycle = "versioned" +canonical = true +canon_slot = "contractile-spec" +canonical_doc = "hyperpolymath/contractiles/index.adoc" +gate = "check-mustfile-structure" +note = "absorbs machine-readable/contractiles/; own linter is estate task #19" + +[[entry]] +from = "deed" +target = "1-formats/deed/ -> hyperpolymath/deed" +district = "1-formats" +kind = "format-grammar" +files = 6 +lifecycle = "versioned" +canonical = true +canon_slot = "deed-grammar" +canonical_doc = "hyperpolymath/deed/index.adoc" +gate = "canon-spine-lockstep" +note = "own IANA media type; v0.2.0 DRAFT; successor to the a2ml record dialect" + +[[entry]] +from = "panll-panels" +target = "1-formats/sub-specs/panll-panels/" +district = "1-formats" +kind = "sub-spec" +files = 6 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "1-formats/sub-specs/panll-panels/index.adoc" +gate = "canon-self-conformance" +note = "TRIAGE: <30 files each; none is a district" + +[[entry]] +from = "ensaid-config" +target = "1-formats/sub-specs/ensaid-config/" +district = "1-formats" +kind = "sub-spec" +files = 4 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "1-formats/sub-specs/ensaid-config/index.adoc" +gate = "canon-self-conformance" +note = "TRIAGE: <30 files each; none is a district" + +[[entry]] +from = "cartridges" +target = "1-formats/sub-specs/cartridges/" +district = "1-formats" +kind = "sub-spec" +files = 3 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "1-formats/sub-specs/cartridges/index.adoc" +gate = "canon-self-conformance" +note = "TRIAGE: <30 files each; none is a district" + +[[entry]] +from = "A2ML-REPO-TEMPLATE.adoc" +target = "1-formats/" +district = "1-formats" +kind = "index" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "1-formats/index.adoc" +gate = "canon-self-conformance" + +[[entry]] +from = "SATELLITES.a2ml" +target = "1-formats/" +district = "1-formats" +kind = "index" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "1-formats/index.adoc" +gate = "canon-self-conformance" + +[[entry]] +from = "bindings-haskell" +target = "1-formats/sub-specs/bindings-haskell/" +district = "1-formats" +kind = "sub-spec" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "1-formats/sub-specs/bindings-haskell/index.adoc" +gate = "canon-self-conformance" +note = "TRIAGE: <30 files each; none is a district" + +[[entry]] +from = "axel-protocol" +target = "2-protocols/axel/" +district = "2-protocols" +kind = "protocol" +files = 110 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "2-protocols/axel/index.adoc" +gate = "canon-self-conformance" +note = "consolidate, do NOT split: same owners, same cadence as the law" + +[[entry]] +from = "0-ai-gatekeeper-protocol" +target = "2-protocols/0-ai-gatekeeper/" +district = "2-protocols" +kind = "protocol" +files = 50 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "2-protocols/0-ai-gatekeeper/index.adoc" +gate = "canon-self-conformance" +note = "consolidate, do NOT split: same owners, same cadence as the law" + +[[entry]] +from = "k9-coordination-protocol" +target = "2-protocols/k9-coordination/" +district = "2-protocols" +kind = "protocol" +files = 19 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "2-protocols/k9-coordination/index.adoc" +gate = "canon-self-conformance" +note = "consolidate, do NOT split: same owners, same cadence as the law" + +[[entry]] +from = "overlay-protocol" +target = "2-protocols/overlay/" +district = "2-protocols" +kind = "protocol" +files = 10 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "2-protocols/overlay/index.adoc" +gate = "canon-self-conformance" +note = "consolidate, do NOT split: same owners, same cadence as the law" + +[[entry]] +from = "chora" +target = "2-protocols/chora/" +district = "2-protocols" +kind = "protocol" +files = 2 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "2-protocols/chora/index.adoc" +gate = "canon-self-conformance" +note = "consolidate, do NOT split: same owners, same cadence as the law" + +[[entry]] +from = "groove-protocol" +target = "2-protocols/groove/" +district = "2-protocols" +kind = "protocol" +files = 2 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "2-protocols/groove/index.adoc" +gate = "canon-self-conformance" +note = "consolidate, do NOT split: same owners, same cadence as the law" + +[[entry]] +from = "avow-protocol" +target = "2-protocols/avow/" +district = "2-protocols" +kind = "protocol" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "2-protocols/avow/index.adoc" +gate = "canon-self-conformance" +note = "consolidate, do NOT split: same owners, same cadence as the law" + +[[entry]] +from = "session-management-standards" +target = "3-practice/session-management-standards/" +district = "3-practice" +kind = "policy" +files = 51 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/session-management-standards/index.adoc" +gate = "canon-self-conformance" +note = "outreach/ is a split-out candidate (non-normative)" + +[[entry]] +from = "outreach" +target = "3-practice/outreach/" +district = "3-practice" +kind = "policy" +files = 23 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/outreach/index.adoc" +gate = "canon-self-conformance" +note = "outreach/ is a split-out candidate (non-normative)" + +[[entry]] +from = "ai-instruction" +target = "3-practice/ai-instruction/" +district = "3-practice" +kind = "policy" +files = 4 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/ai-instruction/index.adoc" +gate = "canon-self-conformance" +note = "outreach/ is a split-out candidate (non-normative)" + +[[entry]] +from = "publication-pre-flight" +target = "3-practice/publication-pre-flight/" +district = "3-practice" +kind = "policy" +files = 3 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/publication-pre-flight/index.adoc" +gate = "canon-self-conformance" +note = "outreach/ is a split-out candidate (non-normative)" + +[[entry]] +from = "release-pre-flight" +target = "3-practice/release-pre-flight/" +district = "3-practice" +kind = "policy" +files = 2 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/release-pre-flight/index.adoc" +gate = "canon-self-conformance" +note = "outreach/ is a split-out candidate (non-normative)" + +[[entry]] +from = "CODEOWNERS-POLICY.adoc" +target = "3-practice/CODEOWNERS-POLICY.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/CODEOWNERS-POLICY.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "CODE_OF_CONDUCT.adoc" +target = "3-practice/CODE_OF_CONDUCT.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/CODE_OF_CONDUCT.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "CONTRIBUTING.adoc" +target = "3-practice/CONTRIBUTING.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/CONTRIBUTING.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "LANGUAGE-POLICY.adoc" +target = "3-practice/LANGUAGE-POLICY.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/LANGUAGE-POLICY.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "LICENCE-POLICY.adoc" +target = "3-practice/LICENCE-POLICY.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/LICENCE-POLICY.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "MAINTAINERS.adoc" +target = "3-practice/MAINTAINERS.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/MAINTAINERS.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "PROOF-NEEDS.adoc" +target = "3-practice/PROOF-NEEDS.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/PROOF-NEEDS.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "REMOTE-URL-POLICY.adoc" +target = "3-practice/REMOTE-URL-POLICY.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/REMOTE-URL-POLICY.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "SECURITY-ADVISORIES.adoc" +target = "3-practice/SECURITY-ADVISORIES.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/SECURITY-ADVISORIES.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "SECURITY.adoc" +target = "3-practice/SECURITY.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/SECURITY.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "SECURITY.md" +target = "3-practice/SECURITY.md" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/SECURITY.md" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "TEST-NEEDS.adoc" +target = "3-practice/TEST-NEEDS.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/TEST-NEEDS.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "TOOLING-VERSION-INTEGRITY-POLICY.adoc" +target = "3-practice/TOOLING-VERSION-INTEGRITY-POLICY.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/TOOLING-VERSION-INTEGRITY-POLICY.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "TRUST-DEFAULTS-POLICY.adoc" +target = "3-practice/TRUST-DEFAULTS-POLICY.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/TRUST-DEFAULTS-POLICY.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "ZIGZAG-TESTING.adoc" +target = "3-practice/ZIGZAG-TESTING.adoc" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/ZIGZAG-TESTING.adoc" +gate = "canon-self-conformance" +note = "SPDX/licence policy is owner-only, flag-never-edit" + +[[entry]] +from = "accessibility" +target = "3-practice/accessibility/" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/accessibility/index.adoc" +gate = "canon-self-conformance" +note = "outreach/ is a split-out candidate (non-normative)" + +[[entry]] +from = "immaculate-guide" +target = "3-practice/immaculate-guide/" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/immaculate-guide/index.adoc" +gate = "canon-self-conformance" +note = "outreach/ is a split-out candidate (non-normative)" + +[[entry]] +from = "testing-and-benchmarking" +target = "3-practice/testing-and-benchmarking/" +district = "3-practice" +kind = "policy" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "3-practice/testing-and-benchmarking/index.adoc" +gate = "canon-self-conformance" +note = "outreach/ is a split-out candidate (non-normative)" + +[[entry]] +from = "toolchain-readiness-grades" +target = "4-readiness/toolchain-readiness-grades/" +district = "4-readiness" +kind = "grading" +files = 40 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "4-readiness/toolchain-readiness-grades/index.adoc" +gate = "canon-self-conformance" +note = "dashboard aggregates to nextgen-languages" + +[[entry]] +from = "adoption-readiness-grades" +target = "4-readiness/adoption-readiness-grades/" +district = "4-readiness" +kind = "grading" +files = 5 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "4-readiness/adoption-readiness-grades/index.adoc" +gate = "canon-self-conformance" +note = "dashboard aggregates to nextgen-languages" + +[[entry]] +from = "component-readiness-grades" +target = "4-readiness/component-readiness-grades/" +district = "4-readiness" +kind = "grading" +files = 5 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "4-readiness/component-readiness-grades/index.adoc" +gate = "canon-self-conformance" +note = "dashboard aggregates to nextgen-languages" + +[[entry]] +from = "foundations-readiness-grades" +target = "4-readiness/foundations-readiness-grades/" +district = "4-readiness" +kind = "grading" +files = 5 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "4-readiness/foundations-readiness-grades/index.adoc" +gate = "canon-self-conformance" +note = "dashboard aggregates to nextgen-languages" + +[[entry]] +from = "scripts" +target = "5-machine/scripts" +district = "5-machine" +kind = "machinery" +files = 107 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/scripts/index.adoc" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = ".machine_readable" +target = "machine-readable/" +district = "5-machine" +kind = "index" +files = 97 +lifecycle = "generated" +canonical = true +canon_slot = "" +canonical_doc = "machine-readable/index.adoc" +gate = "registry-verify" +note = "F2: spine's root-allow.txt says machine-readable/ is canonical (un-hidden 2026-08). This repo still uses the retired dotted form in 97 files." + +[[entry]] +from = ".github" +target = "5-machine/.github" +district = "5-machine" +kind = "machinery" +files = 72 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/.github" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = "standards-update" +target = "5-machine/standards-update" +district = "5-machine" +kind = "machinery" +files = 20 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/standards-update/index.adoc" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = ".githooks" +target = "5-machine/.githooks" +district = "5-machine" +kind = "machinery" +files = 17 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/.githooks" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = "config" +target = "5-machine/config" +district = "5-machine" +kind = "machinery" +files = 14 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/config/index.adoc" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = "hypatia-rules" +target = "5-machine/hypatia-rules" +district = "5-machine" +kind = "machinery" +files = 10 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/hypatia-rules/index.adoc" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = "hooks" +target = "5-machine/hooks" +district = "5-machine" +kind = "machinery" +files = 8 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/hooks/index.adoc" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = "launcher" +target = "5-machine/launcher" +district = "5-machine" +kind = "machinery" +files = 6 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/launcher/index.adoc" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = "tests" +target = "5-machine/tests" +district = "5-machine" +kind = "machinery" +files = 5 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/tests/index.adoc" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = "LICENSES" +target = "5-machine/LICENSES" +district = "5-machine" +kind = "machinery" +files = 4 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/LICENSES/index.adoc" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = "licenses" +target = "5-machine/licenses" +district = "5-machine" +kind = "machinery" +files = 4 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/licenses/index.adoc" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = "_shared" +target = "5-machine/_shared" +district = "5-machine" +kind = "machinery" +files = 3 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/_shared/index.adoc" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = "tasks" +target = "5-machine/tasks" +district = "5-machine" +kind = "machinery" +files = 3 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/tasks/index.adoc" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = "templates" +target = "5-machine/templates" +district = "5-machine" +kind = "machinery" +files = 3 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/templates/index.adoc" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = "tools" +target = "5-machine/tools" +district = "5-machine" +kind = "machinery" +files = 3 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/tools/index.adoc" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = ".hypatia" +target = "5-machine/.hypatia" +district = "5-machine" +kind = "machinery" +files = 2 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/.hypatia" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = ".claude" +target = "5-machine/.claude" +district = "5-machine" +kind = "machinery" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/.claude" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = ".gitleaks.toml" +target = "5-machine/ (root if spine-mandated): .gitleaks.toml" +district = "5-machine" +kind = "machinery" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine//index.adoc" +gate = "actions-lock-gate" + +[[entry]] +from = ".gitleaksignore" +target = "5-machine/ (root if spine-mandated): .gitleaksignore" +district = "5-machine" +kind = "machinery" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine//index.adoc" +gate = "actions-lock-gate" + +[[entry]] +from = ".governance-allowlist" +target = "5-machine/ (root if spine-mandated): .governance-allowlist" +district = "5-machine" +kind = "machinery" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine//index.adoc" +gate = "actions-lock-gate" + +[[entry]] +from = ".meta" +target = "5-machine/.meta" +district = "5-machine" +kind = "machinery" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine/.meta" +gate = "actions-lock-gate" +note = "licenses/ and LICENSES/ both exist; audits/ vs docs/audits/ both exist" + +[[entry]] +from = "CICD-WORKFLOW-CATALOG.md" +target = "5-machine/ (root if spine-mandated): CICD-WORKFLOW-CATALOG.md" +district = "5-machine" +kind = "machinery" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine//index.adoc" +gate = "actions-lock-gate" + +[[entry]] +from = "CLAIMS.a2ml" +target = "5-machine/ (root if spine-mandated): CLAIMS.a2ml" +district = "5-machine" +kind = "machinery" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine//index.adoc" +gate = "actions-lock-gate" + +[[entry]] +from = "Dustfile" +target = "DELETE" +district = "5-machine" +kind = "superseded-contractile" +files = 1 +lifecycle = "deprecated" +canonical = false +canon_slot = "" +canonical_doc = "" +gate = "check-mustfile-structure" +note = "extension-less contractiles at root, superseded by machine-readable/contractiles/. Two naming conventions in one tree." + +[[entry]] +from = "Justfile" +target = "5-machine/ (root if spine-mandated): Justfile" +district = "5-machine" +kind = "machinery" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine//index.adoc" +gate = "actions-lock-gate" + +[[entry]] +from = "Mustfile" +target = "DELETE" +district = "5-machine" +kind = "superseded-contractile" +files = 1 +lifecycle = "deprecated" +canonical = false +canon_slot = "" +canonical_doc = "" +gate = "check-mustfile-structure" +note = "extension-less contractiles at root, superseded by machine-readable/contractiles/. Two naming conventions in one tree." + +[[entry]] +from = "REGISTRY.adoc" +target = "5-machine/ (root if spine-mandated): REGISTRY.adoc" +district = "5-machine" +kind = "machinery" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine//index.adoc" +gate = "actions-lock-gate" + +[[entry]] +from = "coordination.k9" +target = "5-machine/ (root if spine-mandated): coordination.k9" +district = "5-machine" +kind = "machinery" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine//index.adoc" +gate = "actions-lock-gate" + +[[entry]] +from = "guix.scm" +target = "5-machine/ (root if spine-mandated): guix.scm" +district = "5-machine" +kind = "machinery" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine//index.adoc" +gate = "actions-lock-gate" + +[[entry]] +from = "repo-catalog-template.toml" +target = "5-machine/ (root if spine-mandated): repo-catalog-template.toml" +district = "5-machine" +kind = "machinery" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine//index.adoc" +gate = "actions-lock-gate" + +[[entry]] +from = "setup.sh" +target = "5-machine/ (root if spine-mandated): setup.sh" +district = "5-machine" +kind = "machinery" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "5-machine//index.adoc" +gate = "actions-lock-gate" + +[[entry]] +from = "docs" +target = "DISAGGREGATE" +district = "6-telemetry" +kind = "mixed" +files = 402 +lifecycle = "mixed" +canonical = false +canon_slot = "" +canonical_doc = "" +gate = "" +note = "402 files: proofs/ 283 + UX-standards/ 30 -> telemetry or split out; audits/ 27 -> 6-telemetry; archive/ 15 + migrations/ + reorg/ + handover/ -> 9-archive; decisions/ 5 + wikis/ 5 -> stay; ~28 loose policy .adoc files -> 3-practice" + +[[entry]] +from = ".verisimdb" +target = "6-telemetry/.verisimdb" +district = "6-telemetry" +kind = "measured" +files = 7 +lifecycle = "generated" +canonical = false +canon_slot = "" +canonical_doc = "6-telemetry/.verisimdb" +gate = "" +note = "SPLIT OUT -> hyperpolymath/estate-telemetry; generated data, not law" + +[[entry]] +from = "audits" +target = "6-telemetry/audits" +district = "6-telemetry" +kind = "measured" +files = 3 +lifecycle = "generated" +canonical = false +canon_slot = "" +canonical_doc = "6-telemetry/audits/index.adoc" +gate = "" +note = "SPLIT OUT -> hyperpolymath/estate-telemetry; generated data, not law" + +[[entry]] +from = ".hypatia-baseline.json" +target = "6-telemetry/.hypatia-baseline.json" +district = "6-telemetry" +kind = "measured" +files = 1 +lifecycle = "generated" +canonical = false +canon_slot = "" +canonical_doc = "6-telemetry/.hypatia-baseline.json" +gate = "" +note = "SPLIT OUT -> hyperpolymath/estate-telemetry; generated data, not law" + +[[entry]] +from = "REORGANIZATION-PLAN.adoc" +target = "9-archive/REORGANIZATION-PLAN.adoc" +district = "9-archive" +kind = "superseded" +files = 1 +lifecycle = "frozen" +canonical = false +canon_slot = "" +canonical_doc = "9-archive/REORGANIZATION-PLAN.adoc" +gate = "" +note = "superseded 2026-06-02; retained for provenance, must not read as current" + +[[entry]] +from = "0-AI-MANIFEST.a2ml" +target = "0-AI-MANIFEST.a2ml" +district = "onramp" +kind = "onramp" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "0-AI-MANIFEST.a2ml" +gate = "canon-self-conformance" +note = "two canonical front doors (README.adoc human, 0-AI-MANIFEST.a2ml machine); every other doc is a thin pointer" + +[[entry]] +from = "AGENTS.adoc" +target = "AGENTS.adoc" +district = "onramp" +kind = "onramp" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "AGENTS.adoc" +gate = "canon-self-conformance" +note = "two canonical front doors (README.adoc human, 0-AI-MANIFEST.a2ml machine); every other doc is a thin pointer" + +[[entry]] +from = "CHANGELOG.adoc" +target = "CHANGELOG.adoc" +district = "onramp" +kind = "onramp" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "CHANGELOG.adoc" +gate = "canon-self-conformance" +note = "two canonical front doors (README.adoc human, 0-AI-MANIFEST.a2ml machine); every other doc is a thin pointer" + +[[entry]] +from = "EXPLAINME.adoc" +target = "EXPLAINME.adoc" +district = "onramp" +kind = "onramp" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "EXPLAINME.adoc" +gate = "canon-self-conformance" +note = "two canonical front doors (README.adoc human, 0-AI-MANIFEST.a2ml machine); every other doc is a thin pointer" + +[[entry]] +from = "QUICKSTART-DEV.adoc" +target = "QUICKSTART-DEV.adoc" +district = "onramp" +kind = "onramp" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "QUICKSTART-DEV.adoc" +gate = "canon-self-conformance" +note = "two canonical front doors (README.adoc human, 0-AI-MANIFEST.a2ml machine); every other doc is a thin pointer" + +[[entry]] +from = "QUICKSTART-MAINTAINER.adoc" +target = "QUICKSTART-MAINTAINER.adoc" +district = "onramp" +kind = "onramp" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "QUICKSTART-MAINTAINER.adoc" +gate = "canon-self-conformance" +note = "two canonical front doors (README.adoc human, 0-AI-MANIFEST.a2ml machine); every other doc is a thin pointer" + +[[entry]] +from = "QUICKSTART-USER.adoc" +target = "QUICKSTART-USER.adoc" +district = "onramp" +kind = "onramp" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "QUICKSTART-USER.adoc" +gate = "canon-self-conformance" +note = "two canonical front doors (README.adoc human, 0-AI-MANIFEST.a2ml machine); every other doc is a thin pointer" + +[[entry]] +from = "README.adoc" +target = "README.adoc" +district = "onramp" +kind = "onramp" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "README.adoc" +gate = "canon-self-conformance" +note = "two canonical front doors (README.adoc human, 0-AI-MANIFEST.a2ml machine); every other doc is a thin pointer" + +[[entry]] +from = "ROADMAP.adoc" +target = "ROADMAP.adoc" +district = "onramp" +kind = "onramp" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "ROADMAP.adoc" +gate = "canon-self-conformance" +note = "two canonical front doors (README.adoc human, 0-AI-MANIFEST.a2ml machine); every other doc is a thin pointer" + +[[entry]] +from = "TOPOLOGY.adoc" +target = "TOPOLOGY.adoc" +district = "onramp" +kind = "onramp" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "TOPOLOGY.adoc" +gate = "canon-self-conformance" +note = "two canonical front doors (README.adoc human, 0-AI-MANIFEST.a2ml machine); every other doc is a thin pointer" + +[[entry]] +from = "llm-warmup-dev.adoc" +target = "llm-warmup-dev.adoc" +district = "onramp" +kind = "onramp" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "llm-warmup-dev.adoc" +gate = "canon-self-conformance" +note = "two canonical front doors (README.adoc human, 0-AI-MANIFEST.a2ml machine); every other doc is a thin pointer" + +[[entry]] +from = "llm-warmup-user.adoc" +target = "llm-warmup-user.adoc" +district = "onramp" +kind = "onramp" +files = 1 +lifecycle = "versioned" +canonical = true +canon_slot = "" +canonical_doc = "llm-warmup-user.adoc" +gate = "canon-self-conformance" +note = "two canonical front doors (README.adoc human, 0-AI-MANIFEST.a2ml machine); every other doc is a thin pointer" + +[[entry]] +from = ".well-known" +target = ".well-known" +district = "root" +kind = "spine-mandated" +files = 4 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = ".well-known/index.adoc" +gate = "check-root-shape" +note = "must be at root because the spine's root-allow.txt requires it there" + +[[entry]] +from = ".editorconfig" +target = ".editorconfig" +district = "root" +kind = "spine-mandated" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = ".editorconfig" +gate = "check-root-shape" +note = "must be at root because the spine's root-allow.txt requires it there" + +[[entry]] +from = ".gitattributes" +target = ".gitattributes" +district = "root" +kind = "spine-mandated" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = ".gitattributes" +gate = "check-root-shape" +note = "must be at root because the spine's root-allow.txt requires it there" + +[[entry]] +from = ".gitignore" +target = ".gitignore" +district = "root" +kind = "spine-mandated" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = ".gitignore" +gate = "check-root-shape" +note = "must be at root because the spine's root-allow.txt requires it there" + +[[entry]] +from = "LICENSE" +target = "LICENSE" +district = "root" +kind = "spine-mandated" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "LICENSE/index.adoc" +gate = "check-root-shape" +note = "must be at root because the spine's root-allow.txt requires it there" + +[[entry]] +from = "NOTICE" +target = "NOTICE" +district = "root" +kind = "spine-mandated" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "NOTICE/index.adoc" +gate = "check-root-shape" +note = "must be at root because the spine's root-allow.txt requires it there" + +[[entry]] +from = "PALIMPSEST.adoc" +target = "PALIMPSEST.adoc" +district = "root" +kind = "spine-mandated" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "PALIMPSEST.adoc" +gate = "check-root-shape" +note = "must be at root because the spine's root-allow.txt requires it there" + +# ─────────────────────────────────────────────────────────────────────────── +# ADDED 2026-09-17 in the same change as the binding model (02-BINDING-MODEL.md). +# These two entries were caught by check-standards-map.sh assertion 2 on the +# first run after they were introduced — which is the gate doing its job. They +# are recorded here rather than exempted from the check. +# ─────────────────────────────────────────────────────────────────────────── + +[[entry]] +from = "canon.lock" +target = "canon.lock" +district = "0-canon" +kind = "law-release-identity" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "" +gate = "canon-spine-lockstep" +note = "NEW. The released identity of the canon: version + sha256 of every law artefact + the reusable-workflow refs. Gate A reads it. In [canon.lockstep] it declares its own five assertions." + +[[entry]] +from = "standards-map.toml" +target = "standards-map.toml" +district = "5-machine" +kind = "index" +files = 1 +lifecycle = "versioned" +canonical = false +canon_slot = "" +canonical_doc = "" +gate = "standards-map-verify" +note = "NEW. The map of this repository: one record per top-level entry, all 118. Checked bidirectionally by scripts/check-standards-map.sh (Gate D). index.adoc per district and machine-readable/map.a2ml are GENERATED from this file." From 44d9d743855d1c66f99a0a15c22193d1b509f21b Mon Sep 17 00:00:00 2001 From: "6759885+hyperpolymath@users.noreply.github.com" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:24:24 +0000 Subject: [PATCH 4/8] ci(gates): wire Gate A and Gate D into CI + lock the new workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds .github/workflows/canon-spine-lockstep.yml with three jobs: lockstep Gate A — the canon and the spine are on the same law. Checks out BOTH repos (canon at fetch-depth 0, because assertion 2 diffs against a base ref and assertion 1 hashes directories with `git ls-files -s`), then runs scripts/check-canon-lockstep.sh. GH_TOKEN is passed because assertion 4 reads the spine's last dogfood-gate conclusion; without it the script degrades to SKIP rather than passing silently. `workflow_dispatch` exposes a `strict` input to promote assertions 3/4/5 from SKIP to FAIL. map Gate D — standards-map.toml integrity, in both directions. self-conformance The canon against its own gate table. Before this change the repository that SHIPS scripts/check-rsr-profile.sh exited 2 on itself. The workflow file is triggered only on paths that can actually change the answer, so an unrelated docs PR does not pay for a two-repo checkout. ON actions.lock — hand-edited DELIBERATELY actions.lock enumerates every workflow file and is enforced at workflow LOAD time. A lock that does not know about a new workflow kills every consumer with `jobs=0` and NO annotation — the exact silent failure documented in the message of 092deda. So a new workflow file REQUIRES a lock entry in the same commit. `gh actions-lock` is not available here, and the estate has already established that its bare *update* mode is not trustworthy on this tree: 092deda records it prepending a duplicate banner above the SPDX header on all 42 workflows, rewriting `uses: ./.github/actions/signed-push` to the invalid `uses: $/…` spelling, and leaving genuinely-stale rows in place while exiting 0 reporting "All 42 workflows valid". The VERIFIER is trustworthy even though the updater is not, and the entry is trivially checkable, so the row was added by hand and then witnessed: yaml.safe_load('.github/workflows/actions.lock') -> 42 workflows in lock (was 41) -> every lock entry corresponds to a real file: [] -> uses: in the new workflow == lock entry for the new workflow: MATCH The single pin used is actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 (# v7.0.1) — the same pin as the other 61 uses in this repo. No new action is introduced, and no secret is referenced (only github.token), so the pin SET did not change. That is what makes a hand-edit safe here specifically. Negative control: the entry is placed alphabetically (boj-build < canon-spine-lockstep < casket-pages). A stray entry would fail the "lock entries with no file" check; a missing one fails at load time. WHAT THIS DOES NOT DO Assertions 3/4/5 SKIP unless --strict or the workflow_dispatch input is set. They cannot pass yet: the spine does not carry a [canon] block, and the one normative oracle (hypatia:rsr-conformance) is still marked "to be implemented" in the criteria SSOT. Wiring them as hard failures now would block every PR to this repository. They become hard failures in execution-plan §Phase 5.4, after the spine adopts this canon. --- .github/workflows/actions.lock | 2 + .github/workflows/canon-spine-lockstep.yml | 194 +++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 .github/workflows/canon-spine-lockstep.yml diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 32745eed1..ae1d2d0de 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -12,6 +12,8 @@ workflows: - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' '.github/workflows/boj-build.yml': - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' + '.github/workflows/canon-spine-lockstep.yml': + - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' '.github/workflows/casket-pages.yml': - 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9' - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' diff --git a/.github/workflows/canon-spine-lockstep.yml b/.github/workflows/canon-spine-lockstep.yml new file mode 100644 index 000000000..aea461ad9 --- /dev/null +++ b/.github/workflows/canon-spine-lockstep.yml @@ -0,0 +1,194 @@ +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# canon-spine-lockstep — GATE A. +# +# The gate that makes `standards` BOUND BY `rsr-template-repo`. +# +# The estate already has the right architecture (SCAFFOLD-LIFECYCLE.adoc +# §Roles: canon -> spine -> composer, measured by the oracle). What it does not +# have is a mechanical link in EITHER direction: the spine declares its +# conformance with the free-text string "2.0.0-draft", and nothing fails when +# this repository's law changes. +# +# This workflow closes the canon->spine half. The spine->canon half is +# `PROVENANCE.a2ml`, written at mint by `just repo-init`. +# +# Assertion 4 is a deliberate reversal and is the point of the exercise: +# +# YOU MAY NOT TIGHTEN THE CRITERIA UNTIL THE REFERENCE IMPLEMENTATION +# PASSES THEM. +# +name: Canon / Spine Lockstep + +on: + push: + branches: [ main, master ] + paths: + - 'canon.lock' + - 'standards-map.toml' + - 'rhodium-standard-repositories/spec/rsr-criteria-v2.a2ml' + - '.machine_readable/template-capability-gates.toml' + - 'TEMPLATE-APPLICABILITY-POLICY.adoc' + - 'rhodium-standard-repositories/spec/SCAFFOLD-LIFECYCLE.adoc' + - 'constitution/**' + - 'scripts/check-canon-lockstep.sh' + - 'scripts/check-standards-map.sh' + - '.github/workflows/canon-spine-lockstep.yml' + pull_request: + branches: [ main, master ] + paths: + - 'canon.lock' + - 'standards-map.toml' + - 'rhodium-standard-repositories/spec/**' + - '.machine_readable/**' + - 'TEMPLATE-APPLICABILITY-POLICY.adoc' + - 'constitution/**' + - 'scripts/check-canon-lockstep.sh' + - 'scripts/check-standards-map.sh' + workflow_dispatch: + inputs: + strict: + description: 'Promote lockstep assertions 3/4/5 from SKIP to FAIL' + type: boolean + default: false + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + actions: read + contents: read + +jobs: + # --------------------------------------------------------------------------- + # Gate A — the canon and the spine are on the same law. + # --------------------------------------------------------------------------- + lockstep: + name: Canon / spine lockstep + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout canon + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: canon + # Assertion 2 diffs the canon against its base ref, and assertion 1 + # hashes directories with `git ls-files -s`, so history must be real. + fetch-depth: 0 + + - name: Checkout spine + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: hyperpolymath/rsr-template-repo + path: spine + fetch-depth: 1 + persist-credentials: false + + - name: Determine base ref for the version-bump assertion + id: base + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "ref=origin/${{ github.base_ref }}" >> "$GITHUB_OUTPUT" + else + echo "ref=HEAD~1" >> "$GITHUB_OUTPUT" + fi + + - name: Run Gate A + id: gate + env: + # Assertion 4 reads the spine's last dogfood-gate conclusion. Without + # a token that call is unauthenticated and rate-limited; the script + # degrades to SKIP rather than passing silently. + GH_TOKEN: ${{ github.token }} + run: | + set +e + args=( --canon canon --spine spine --base "${{ steps.base.outputs.ref }}" ) + if [ "${{ inputs.strict }}" = "true" ]; then args+=( --strict ); fi + bash canon/scripts/check-canon-lockstep.sh "${args[@]}" | tee "$RUNNER_TEMP/gate-a.txt" + rc=${PIPESTATUS[0]} + echo "rc=$rc" >> "$GITHUB_OUTPUT" + exit "$rc" + + - name: Summarise + if: always() + run: | + { + echo "## Gate A — Canon / Spine Lockstep" + echo "" + echo '```' + cat "$RUNNER_TEMP/gate-a.txt" 2>/dev/null || echo "(no output)" + echo '```' + if [ "${{ steps.gate.outputs.rc }}" = "0" ]; then + echo ":white_check_mark: The canon and the spine are on the same law." + else + echo ":x: **Gate A failed.**" + echo "" + echo "This is not necessarily a bad change. It is very often a change" + echo "made in the **wrong order**: \`canon.lock [canon.lockstep].order\`" + echo "is \`spine-adopts-then-canon-releases\`. Land the spine's" + echo "adoption first, then the canon change becomes a one-line bump." + fi + } >> "$GITHUB_STEP_SUMMARY" + + # --------------------------------------------------------------------------- + # Gate D — the map of this repository is complete and honest, in BOTH + # directions. Bidirectional deliberately: the template's root-allow.txt + # learned that "a one-directional allowlist only ever ratchets open" after a + # root cleanup left stale permissions behind and the allowlist "quietly + # became a licence for the very drift it was written to prevent". + # --------------------------------------------------------------------------- + map: + name: Standards map integrity + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + + - name: Run Gate D + run: | + if ! bash scripts/check-standards-map.sh --repo .; then + { + echo "### Standards map drift" + echo "" + echo "Every top-level entry must have an \`[[entry]]\` in" + echo "\`standards-map.toml\`, and every \`[[entry]]\` must point at a" + echo "path that exists. Add or remove the record — do not exempt it." + } >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi + + # --------------------------------------------------------------------------- + # The canon's own conformance. Before this change the repository that SHIPS + # scripts/check-rsr-profile.sh exited 2 on itself: "no profile at + # ./.machine_readable/rsr-profile.a2ml". The law was not subject to the law. + # --------------------------------------------------------------------------- + self-conformance: + name: Canon self-conformance + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Canon is subject to its own gate table + run: | + if ! bash scripts/check-rsr-profile.sh .; then + { + echo "### Canon self-conformance failed" + echo "" + echo "This repository declares \`role = \"canon\"\` and an honest" + echo "capability set. Either the scaffold has drifted from the" + echo "declaration, or the declaration is wrong." + echo "" + echo "Do NOT add a vestigial path to \`[canon]\` in the gate table to" + echo "silence this — that section exists for law artefacts the" + echo "canon carries BY NATURE (the criteria, this gate table, the" + echo "reusable gates other repos call, proof artefacts of the" + echo "estate), not as an escape hatch." + } >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi From 44fbf58b20afe95a5225752650691adcc7efee98 Mon Sep 17 00:00:00 2001 From: "6759885+hyperpolymath@users.noreply.github.com" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:27:08 +0000 Subject: [PATCH 5/8] =?UTF-8?q?docs(audit):=20record=20F6=20=E2=80=94=20th?= =?UTF-8?q?e=20spine's=20lock=20pins=20floating=20refs,=20so=20the=20templ?= =?UTF-8?q?ate=20teaches=20non-pinning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found while wiring Gate A's counterpart into the spine, and worth recording because it INVERTS the usual reading of the estate board. The estate's canon requires SHA-pinned `uses:`. The canon's own actions.lock enforces it (it records `actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1`). But rsr-template-repo's actions.lock records FLOATING refs: 'actions/checkout@v7.0.1' 'hyperpolymath/deed-ecosystem@main' 'hyperpolymath/k9-ecosystem@main' Measured 2026-09-17: 54 `@vN`/`@main` references across that repo's .github/workflows/. THAT IS UPSTREAM OF THE BOARD, NOT DOWNSTREAM OF IT docs/ESTATE-BOARD.adoc reports `sha_pinning_required: false` on 8 repos and `allowed_actions: all` on 148, and reads those as loose settings. They are compatible with the template's own example. A repo minted from this spine inherits workflows that pin tags and point at `@main`, and a lock that accepts them, so the template teaches the practice the canon forbids — and no repo has to make a decision to end up there. WHY IT IS NOT FIXED IN THIS PR It is a 54-reference change across .github/workflows/ plus a regenerated lock, and the lock is load-time enforced: a mistaken edit kills every consumer with `jobs=0` and no annotation. It needs the canon's own `check-allowed-actions` gate as the witness and its own PR. Filed as F6 with severity High, because the blast radius is every repo minted from this spine rather than any single repo. --- docs/AUDIT.adoc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/AUDIT.adoc b/docs/AUDIT.adoc index fb606de1b..4c2ab2c8b 100644 --- a/docs/AUDIT.adoc +++ b/docs/AUDIT.adoc @@ -122,6 +122,16 @@ open at the anchor in link:AFFIRMATION.adoc[AFFIRMATION.adoc]. a superseded `REORGANIZATION-PLAN.adoc` at root. | Medium +| F6 +| The spine's `actions.lock` records **floating refs** — `actions/checkout@v7.0.1`, + `hyperpolymath/deed-ecosystem@main` — where this repo's lock records SHAs. + The template that ~441 repos are minted from therefore *teaches* non-pinning + to every repo cut from it. Measured 2026-09-17: 54 `@vN`/`@main` references + across `rsr-template-repo/.github/workflows/`. This is upstream of the + estate-board columns `sha_pinning` and `allowed_actions`, not a downstream + lapse. +| High + | — | Root link scan: 7 of 72 distinct relative link targets in the root documents do not resolve, including `a2ml/` (evicted from this repository on From 159bc303f105ea15b40371ed3566ae6811cc3d41 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 17 Sep 2026 17:40:27 +0000 Subject: [PATCH 6/8] fix(gates): assertions 3/4/5 must be informational unless --strict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found by the estate's own CI on PR #811 — my gate as written made its own PR unmergeable, and inverted the ordering rule it was built to enforce. THE BUG The commit that wired this gate claimed "Assertions 3/4/5 SKIP unless --strict or the workflow_dispatch input is set". That was FALSE as implemented. Every workflow invocation passes --spine, and assertion 3 failed HARD whenever a spine directory was present — regardless of --strict. Result: Canon / spine lockstep -> failure (exit 1) Why that is self-defeating, not merely inconvenient: assertion 3 asks "has the spine adopted THIS canon?". The spine's adoption necessarily pins hashes that exist only in this unmerged PR. So a hard assertion 3 means NO canon release can ever merge before the spine has adopted it, and the spine cannot adopt it before it is released. The gate deadlocked itself. canon.lock [canon.lockstep].order says "spine-adopts-then-canon-releases". The script must not contradict the file it reads. THE FIX — three classes of outcome, not two fail() a HARD invariant (assertions 1 and 2). Always fails. softfail() a REAL deviation (spine on a different canon, canon has no profile, profile role wrong). FAILS under --strict; reported as INFO otherwise. skip() the check could not RUN (no `gh`, no `hypatia`, no --spine). ALWAYS informational, and NEVER promoted by --strict. That last point is a second bug fixed here. Previously skip() promoted to failure under --strict, so --strict could never pass in any environment lacking `gh` and `hypatia` — and `hypatia`'s oracle is marked "to be implemented" in the criteria SSOT, so --strict was permanently red. An absent TOOL is not a lockstep DEVIATION; conflating them trains people to ignore the gate. A green build must not read as "fully verified", so skipped assertions are now collected and printed as an explicit NOT VERIFIED block: passed 8 failed 0 skipped 2 NOT VERIFIED (these assertions did not run — a green result is NOT a full verification): - gh not available; cannot read the spine's last dogfood-gate conclusion - hypatia (the one normative oracle) not available; oracle is marked 'to be implemented' WHERE DRIFT IS ACTUALLY CAUGHT This makes canon CI informational for adoption status, which is correct — it is the SPINE's job to track the canon, and the spine's own `Canon lockstep` job (dogfood-gate.yml, in PR hyperpolymath/rsr-template-repo#101) fails hard on a mismatch. Canon CI: "have you adopted?" Spine CI: "are you still on it?" WITNESSES non-strict, real spine branch -> passed 8 failed 0 skipped 2 GATE A PASSED --strict, real spine branch -> passed 8 failed 0 skipped 2 GATE A PASSED (+ NOT VERIFIED block) NEGATIVE, spine pin corrupted: non-strict -> INFO "spine is on a DIFFERENT canon" GATE A PASSED (correct: canon CI does not gate on adoption status) --strict -> FAIL "spine is on a DIFFERENT canon" GATE A FAILED, exit 1 NEGATIVE, --strict with no spine -> FAIL, exit 1 bash -n -> syntax OK --- scripts/check-canon-lockstep.sh | 56 +++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/scripts/check-canon-lockstep.sh b/scripts/check-canon-lockstep.sh index 64778f444..84a3438ad 100644 --- a/scripts/check-canon-lockstep.sh +++ b/scripts/check-canon-lockstep.sh @@ -12,12 +12,17 @@ # --------------------------------------------------------------------------- # WHAT IT ENFORCES # +# HARD (always): # 1 every sha256 in canon.lock [canon.artifacts] matches the working tree # 2 touching a canon artefact forces a version bump +# +# INFORMATIONAL unless --strict: # 3 the spine declares criteria_sha256 == canon.lock's criteria hash # 4 the spine is GREEN against those criteria # 5 the canon itself scores Gold on its own applicable set # +# See softfail() for why 3/4/5 must not be hard by default. +# # Assertion 4 is the load-bearing one, and it is a deliberate reversal: # # YOU MAY NOT TIGHTEN THE CRITERIA UNTIL THE REFERENCE IMPLEMENTATION @@ -51,6 +56,7 @@ STRICT=0 FAILED=0 PASSED=0 SKIPPED=0 +NOT_VERIFIED="" while [ $# -gt 0 ]; do case "$1" in @@ -65,9 +71,36 @@ done pass() { PASSED=$((PASSED + 1)); printf ' \033[32mPASS\033[0m %s\n' "$*"; } fail() { FAILED=$((FAILED + 1)); printf ' \033[31mFAIL\033[0m %s\n' "$*"; } +# skip() — the check could not be RUN (missing tool, missing argument). +# Always informational. --strict MUST NOT promote this: an absent +# tool is not a lockstep deviation, and promoting it makes --strict +# permanently red in any environment without `gh`/`hypatia`, which +# trains people to ignore it. skip() { - if [ "$STRICT" -eq 1 ]; then fail "$* (SKIP promoted to FAIL by --strict)" - else SKIPPED=$((SKIPPED + 1)); printf ' \033[33mSKIP\033[0m %s\n' "$*"; fi + SKIPPED=$((SKIPPED + 1)) + NOT_VERIFIED="$NOT_VERIFIED\n - $*" + printf ' \033[33mSKIP\033[0m %s\n' "$*" +} + +# Assertions 3, 4 and 5 are INFORMATIONAL unless --strict. +# +# This is not leniency, it is the ordering rule made executable. canon.lock +# [canon.lockstep].order is "spine-adopts-then-canon-releases", but a canon +# change and the spine's adoption of it CANNOT both be first: the spine's +# adoption necessarily pins hashes that exist only in the unmerged canon PR. +# A hard check here therefore renders every canon release unmergeable, which is +# the opposite of the rule it was meant to enforce. +# +# Sequence this enables: +# 1. land the canon change with 3/4/5 informational -> releases canon.lock +# 2. land the spine's adoption against the released hashes +# 3. turn --strict on, so drift is a hard failure from then on +# See docs/binding/04-EXECUTION-PLAN.md. +# +# softfail() — a REAL deviation. Promoted to a failure by --strict. +softfail() { + if [ "$STRICT" -eq 1 ]; then fail "$*" + else SKIPPED=$((SKIPPED + 1)); printf ' \033[33mINFO\033[0m %s\n' "$*"; fi } LOCK="$CANON/canon.lock" @@ -152,7 +185,7 @@ echo "[1] canon artefact hashes match the working tree" for slot in criteria gates applicability lifecycle constitution; do want="$(toml_hash "$slot")" path="$(toml_path "$slot")" - if [ -z "$want" ] || [ -z "$path" ]; then skip "$slot: not declared in canon.lock"; continue; fi + if [ -z "$want" ] || [ -z "$path" ]; then softfail "$slot: declared in [canon.artifacts] with no path/hash, or absent"; continue; fi if [ ! -e "$CANON/$path" ]; then fail "$slot: declared path does not exist: $path" continue @@ -205,22 +238,23 @@ else PROFILE="$SPINE/machine-readable/rsr-profile.a2ml" [ -f "$PROFILE" ] || PROFILE="$SPINE/.machine_readable/rsr-profile.a2ml" if [ ! -f "$PROFILE" ]; then - fail "spine has no rsr-profile.a2ml at either machine-readable/ or .machine_readable/" + softfail "spine has no rsr-profile.a2ml at either machine-readable/ or .machine_readable/" else WANT="$(toml_hash criteria)" GOT="$(grep -E '^[[:space:]]*criteria_sha256[[:space:]]*=' "$PROFILE" \ | grep -oE '[0-9a-f]{64}' | head -1)" if [ -z "$GOT" ]; then - fail "spine rsr-profile.a2ml has no [canon] criteria_sha256 + softfail "spine rsr-profile.a2ml has no [canon] criteria_sha256 -> the spine still declares conformance in free text. The binding does not exist until this is a hash." elif [ "$WANT" = "$GOT" ]; then pass "spine criteria_sha256 == canon.lock criteria ($(echo "$GOT" | cut -c1-12)…)" else - fail "spine is on a DIFFERENT canon + softfail "spine is on a DIFFERENT canon canon.lock $(echo "$WANT" | cut -c1-16)… spine $(echo "$GOT" | cut -c1-16)… - -> land the spine's adoption FIRST, then release the canon." + -> land the spine's adoption AFTER this canon release; run with + --strict to make this a hard failure once both are on main." fi fi fi @@ -270,12 +304,12 @@ echo "[5] the canon scores Gold on its own applicable set" CANON_PROFILE="$CANON/machine-readable/rsr-profile.a2ml" [ -f "$CANON_PROFILE" ] || CANON_PROFILE="$CANON/.machine_readable/rsr-profile.a2ml" if [ ! -f "$CANON_PROFILE" ]; then - fail "the canon has NO rsr-profile.a2ml — it cannot be scored by the checker + softfail "the canon has NO rsr-profile.a2ml — it cannot be scored by the checker it ships (scripts/check-rsr-profile.sh exits 2 on this repo). -> see artefacts/rsr-profile.canon.a2ml; requires [canon] in the gate table." else ROLE="$(grep -E '^[[:space:]]*role[[:space:]]*=' "$CANON_PROFILE" | head -1 | grep -oE '"[^"]+"' | tr -d '"')" - [ "$ROLE" = "canon" ] || fail "canon rsr-profile role is '${ROLE:-unset}', expected 'canon'" + [ "$ROLE" = "canon" ] || softfail "canon rsr-profile role is '${ROLE:-unset}', expected 'canon'" [ "$ROLE" = "canon" ] && pass "canon rsr-profile declares role = \"canon\"" if command -v mix >/dev/null 2>&1 && [ -d "$CANON/../hypatia" ]; then @@ -295,6 +329,10 @@ echo # =========================================================================== echo "─────────────────────────────────────────────────────────────" printf 'passed %d failed %d skipped %d\n' "$PASSED" "$FAILED" "$SKIPPED" +if [ -n "$NOT_VERIFIED" ]; then + printf '\n\033[33mNOT VERIFIED\033[0m (these assertions did not run — a green result is NOT a full verification):' + printf "$NOT_VERIFIED\n" +fi if [ "$FAILED" -gt 0 ]; then echo echo "GATE A FAILED — the canon and the spine are not in lockstep." From ca1c24e23eee1fa892276d4196e08dea90baf9be Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 17 Sep 2026 17:42:17 +0000 Subject: [PATCH 7/8] fix(gates): --canon was a pathspec prefix, so Gate A's verdict depended on spelling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The real cause of the CI failure on this PR, found by reading the job log rather than by inference. Assertions 1 and 2 were accusing a valid PR of two things it had not done: FAIL constitution constitution/ -> the law changed without re-releasing canon.lock (bump version + rewrite hash) FAIL canon artefacts changed ( gates ) but canon.lock is untouched -> Bump [canon].version and rewrite the hash in the SAME commit. Neither was true. The law had not changed, and canon.lock WAS bumped. Both assertions were reading paths that git never matched. THE BUG Git pathspecs resolve against the REPOSITORY ROOT and know nothing about the --canon prefix. Two places handed git "$CANON/..." instead of a repo-relative path: hash_path() cd "$CANON" && git ls-files -s "$CANON/constitution/" -> matched no path, printed nothing, and sha256sum faithfully hashed the EMPTY stream: e3b0c442…, the digest of nothing. Compared against the real 7fbd1015… -> "the law changed". assertion 2 git -C "$CANON" diff --quiet "$BASE_REF"...HEAD -- "$CANON/canon.lock" -> matched no path, so `git diff --quiet` exited 0 ("nothing changed") and the gate reported the lock as untouched. WHY CI AND MY LOCAL RUN DISAGREED I had only ever invoked it as `--canon .`. With that spelling "./constitution/" and "./canon.lock" happen to resolve, so the pathspec matched by accident and the gate reported "passed 8 failed 0 GATE A PASSED". CI invokes it as `--canon canon`; the same code then reported "passed 6 failed 2 GATE A FAILED". The verdict was a function of how the argument was SPELLED, not of the state of the repository. A gate that can be made green by relabelling its input is not a gate. Reproduced before fixing, byte-for-byte: --canon standards -> passed 6 failed 2 skipped 2 GATE A FAILED (= CI) --canon . -> passed 8 failed 0 skipped 2 GATE A PASSED THE FIX * hash_path() strips the --canon prefix before handing the path to git, and REFUSES to hash an empty listing. The old code's failure mode was invisible because hashing nothing yields a plausible 64-hex digest; a wrong pathspec can no longer be mistaken for a hash. * assertion 1 reports "could not hash" instead of comparing against it. * $LOCK_REL, the repo-relative twin of $LOCK, is used for the `git diff` pathspec in assertion 2. WITNESSES AFTER THE FIX — all three spellings now agree --canon standards -> passed 8 failed 0 skipped 2 GATE A PASSED --canon . -> passed 8 failed 0 skipped 2 GATE A PASSED --canon /home/user/work/standards -> passed 8 failed 0 skipped 2 GATE A PASSED NEGATIVE CONTROL, untracked directory declared in canon.lock: ERROR: git ls-files matched no files for 'zzz/' in . FAIL constitution: could not hash 'zzz/' — see the error above (the old code would have hashed the empty stream and produced a digest) Also carried from the previous commit on this branch: assertions 3/4/5 are informational unless --strict, and skip() is no longer promoted by --strict (an absent tool is not a deviation). With this fix, non-strict on this PR yields 1-2-4-5 pass, 3 informational pending the spine's adoption in hyperpolymath/rsr-template-repo#101, and a loud NOT VERIFIED block. bash -n -> syntax OK. --- scripts/check-canon-lockstep.sh | 36 +++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/scripts/check-canon-lockstep.sh b/scripts/check-canon-lockstep.sh index 84a3438ad..bce8b5d37 100644 --- a/scripts/check-canon-lockstep.sh +++ b/scripts/check-canon-lockstep.sh @@ -104,6 +104,12 @@ softfail() { } LOCK="$CANON/canon.lock" +# Repo-relative twin of $LOCK. Assertion 2 passes the lock to `git diff`, whose +# pathspecs resolve against the repository root, not the filesystem: with +# --canon canon the pathspec "canon/canon.lock" matched nothing, `git diff +# --quiet` therefore reported "no change", and the gate accused a PR that had +# in fact bumped the lock of not bumping it. +LOCK_REL="${LOCK#"$CANON"/}" [ -f "$LOCK" ] || { echo "ERROR: canon.lock not found at $LOCK" >&2; exit 2; } # --------------------------------------------------------------------------- @@ -166,9 +172,28 @@ toml_path() { # sha256 of a path: file -> plain hash; directory -> git-ls-files method, # matching the registry's own source_hash definition. hash_path() { - local p="$1" + # $1 is a FILESYSTEM path, e.g. "$CANON/constitution/". Git pathspecs are + # resolved relative to the repository root and know nothing about the + # --canon prefix, so handing git "$CANON/constitution/" matched NOTHING; + # `git ls-files` printed nothing and sha256sum faithfully hashed the EMPTY + # stream — a plausible-looking digest for a directory that was never read. + # + # That is exactly why this gate reported "passed 8 failed 0 GATE A PASSED" + # locally (invoked as `--canon .`, where "./constitution/" happens to match) + # and "passed 6 failed 2 GATE A FAILED" in CI (invoked as `--canon canon`). + # The verdict depended on the SPELLING of the argument, not on the repository. + # + # Two changes: strip the prefix before handing the path to git, and refuse to + # hash an empty listing, so a wrong pathspec can never again look like a hash. + local p="$1" rel="${1#"$CANON"/}" if [ -d "$p" ]; then - ( cd "$CANON" && git ls-files -s "$p" | sha256sum | cut -d' ' -f1 ) + local listing + listing="$( cd "$CANON" && git ls-files -s -- "$rel" )" + if [ -z "$listing" ]; then + echo " ERROR: git ls-files matched no files for '$rel' in $CANON" >&2 + return 1 + fi + printf '%s\n' "$listing" | sha256sum | cut -d' ' -f1 else sha256sum "$p" | cut -d' ' -f1 fi @@ -190,7 +215,10 @@ for slot in criteria gates applicability lifecycle constitution; do fail "$slot: declared path does not exist: $path" continue fi - got="$(hash_path "$CANON/$path")" + if ! got="$(hash_path "$CANON/$path")"; then + fail "$slot: could not hash '$path' — see the error above" + continue + fi if [ "$want" = "$got" ]; then pass "$slot ${path} $(echo "$got" | cut -c1-12)…" else @@ -216,7 +244,7 @@ if git -C "$CANON" rev-parse --verify --quiet "$BASE_REF" >/dev/null 2>&1; then done if [ -z "$CHANGED" ]; then pass "no canon artefact changed against $BASE_REF" - elif ! git -C "$CANON" diff --quiet "$BASE_REF"...HEAD -- "$LOCK" 2>/dev/null; then + elif ! git -C "$CANON" diff --quiet "$BASE_REF"...HEAD -- "$LOCK_REL" 2>/dev/null; then pass "canon artefacts changed ($CHANGED ) and canon.lock was bumped in the same PR" else fail "canon artefacts changed ($CHANGED ) but canon.lock is untouched From 236fcb8a12e95179529d9b3b958607d06df3c8e4 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 17 Sep 2026 17:45:01 +0000 Subject: [PATCH 8/8] fix(docs): resolve the dangling references and the code-scanning alert on this PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remaining CI failures on this PR, triaged to source rather than to name. "HYPATIA" IS NOT A WORKFLOW Triaged first because it looked like one. No workflow or job in this repository is named `Hypatia`; the check-run comes from the GitHub Advanced Security app, i.e. it is the code-scanning gate, and it failed because the SARIF upload from this PR contained **1 new alert in code changed by this pull request**: .machine_readable/rsr-profile.a2ml warning: doc references `src/interface/` but no such directory exists in the tree (likely surviving a directory rename) A true positive against my own new file, not a false one. The directory really is absent (0 tracked files under src/interface/ in the canon), and the sentence was declaring *that absence* — "no C-ABI seam and no formally specified ABI; src/interface/ absent" — but written so that it read as a live path reference. Reworded to state the same fact without the bare path token. The alert was not dismissed and no scanning config was touched: the text is simply unambiguous now about what it means. TWO MORE DANGLING REFERENCES, FOUND WHILE LOOKING Chasing that alert meant checking every path this PR's files point at, and two pointed at a document that was never committed — `docs/binding/04-EXECUTION-*` does not exist, and neither does `docs/binding/`: .machine_readable/rsr-profile.a2ml:37 "see docs/binding/04-EXECUTION-PLAN.md, ruling R-B" docs/AFFIRMATION.adoc:54 link:../binding/04-EXECUTION-PLAN.md[...] scripts/check-canon-lockstep.sh:98 "# See docs/binding/04-EXECUTION-PLAN.md." These were references to a plan that existed only in conversation. A document that cites a non-existent authority is worse than one that cites none: it reads as though the argument has been made somewhere. Repointed at what actually exists and actually says it: * the dialect note -> docs/AUDIT.adoc finding F3, which records that the law is authored in a superseded dialect while DEED is itself an unratified DRAFT * the ordering rule -> canon.lock [canon.lockstep], which is where "spine-adopts-then-canon-releases" is actually stated * AFFIRMATION's "We intend" list -> docs/AUDIT.adoc, which enumerates the release conditions those actions serve A scan of every cross-reference in every .adoc and .a2ml this PR touches now resolves; earlier it did not. NO LOCK BUMP REQUIRED `.machine_readable/rsr-profile.a2ml` is not named in canon.lock [canon.artifacts] (criteria, gates, applicability, lifecycle, constitution), so a comment edit in it is not a canon change and Gate A assertion 2 stays satisfied. GATES AFTER Gate A, --canon standards -> passed 8 failed 0 skipped 2 GATE A PASSED Gate A, --canon . -> passed 8 failed 0 skipped 2 GATE A PASSED check-rsr-profile.sh . -> OK — scaffold matches declared capabilities check-standards-map.sh -> GATE D PASSED, entry_count = 120 --- .machine_readable/rsr-profile.a2ml | 6 ++++-- docs/AFFIRMATION.adoc | 4 ++-- scripts/check-canon-lockstep.sh | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.machine_readable/rsr-profile.a2ml b/.machine_readable/rsr-profile.a2ml index 65cf04f60..ed97e10cc 100644 --- a/.machine_readable/rsr-profile.a2ml +++ b/.machine_readable/rsr-profile.a2ml @@ -34,7 +34,8 @@ // Dialect note: authored in the a2ml record dialect to match the shape // rsr-template-repo ships, because check-rsr-profile.sh's parser accepts it. // The .a2ml -> .deed conversion is estate task #64 and is deliberately NOT -// attempted here; see docs/binding/04-EXECUTION-PLAN.md, ruling R-B. +// attempted here; see docs/AUDIT.adoc — finding F3 records that the law is +// authored in a superseded dialect while DEED is itself an unratified DRAFT. // --------------------------------------------------------------------------- [rsr-profile] @@ -93,7 +94,8 @@ omitted-rationale = "The canon defines conformance; it is not an instance of it. // — nothing consumes the canon as a library; it is consumed as a DOCUMENT // and as a pin (canon.lock), which is not the `library` capability // ffi / abi -// — no C-ABI seam and no formally specified ABI; src/interface/ absent +// — no C-ABI seam and no formally specified ABI; the interface directory +// is absent from this tree // api-service // — no network daemon; the reusable workflows RUN on GitHub's runners, // they are not served BY this repo diff --git a/docs/AFFIRMATION.adoc b/docs/AFFIRMATION.adoc index 201487570..bb4d4fe72 100644 --- a/docs/AFFIRMATION.adoc +++ b/docs/AFFIRMATION.adoc @@ -50,8 +50,8 @@ Binding now. Each item is checkable against the anchor below. == We intend -Committed next actions. **Not yet true.** Listed in execution order; see -link:../binding/04-EXECUTION-PLAN.md[04-EXECUTION-PLAN.md]. +Committed next actions. **Not yet true.** Listed in execution order below; the +release conditions they serve are enumerated in link:AUDIT.adoc[AUDIT.adoc]. . Resolve ruling *R-A*: `machine-readable/` or `.machine_readable/`. This repo uses the dotted form in 97 files; the spine's `root-allow.txt` and this diff --git a/scripts/check-canon-lockstep.sh b/scripts/check-canon-lockstep.sh index bce8b5d37..82137aab6 100644 --- a/scripts/check-canon-lockstep.sh +++ b/scripts/check-canon-lockstep.sh @@ -95,7 +95,7 @@ skip() { # 1. land the canon change with 3/4/5 informational -> releases canon.lock # 2. land the spine's adoption against the released hashes # 3. turn --strict on, so drift is a hard failure from then on -# See docs/binding/04-EXECUTION-PLAN.md. +# See the [canon.lockstep] section of canon.lock, which states the order. # # softfail() — a REAL deviation. Promoted to a failure by --strict. softfail() {