Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/actions.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
194 changes: 194 additions & 0 deletions .github/workflows/canon-spine-lockstep.yml
Original file line number Diff line number Diff line change
@@ -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

Check warning on line 60 in .github/workflows/canon-spine-lockstep.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move this read permission from workflow level to job level.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCwcFQelNfxZaDogd2s&open=AaCwcFQelNfxZaDogd2s&pullRequest=811
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
126 changes: 126 additions & 0 deletions .machine_readable/rsr-profile.a2ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
//
// 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)
Loading
Loading