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 @@ -20,6 +20,8 @@ workflows:
'.github/workflows/codeql-reusable.yml':
- 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1'
- 'github/codeql-action@f205ea1c3313d32999d8d6a48b4f6530d4437b38'
'.github/workflows/debt-measure.yml':
- 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1'
'.github/workflows/deno-ci-reusable.yml':
- 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1'
- 'denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed'
Expand Down
108 changes: 108 additions & 0 deletions .github/workflows/debt-measure.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# SPDX-License-Identifier: MPL-2.0
name: "📅 Debt measure"

# Re-measure every Debtfile probe and record the result as ONE pull request.
#
# This workflow exists to replace a specific bad habit. In `.git-private-farm`,
# a bot minted the same measurement as a NEW ISSUE on every run:
#
# #104 rustsec: 138 open advisories (34 high/critical)
# #105 rustsec: 136 open advisories (34 high/critical)
# #107 rustsec: 139 open advisories (34 high/critical)
# #117 rustsec: 156 open advisories (54 high/critical)
# #119 rustsec: 156 open advisories (54 high/critical)
# #120 rustsec: 155 open advisories (53 high/critical)
#
# Six issues carrying one number. This workflow opens at most one PR and
# NEVER opens an issue. That is the whole point; do not add issue creation.
#
# 📅 PERIODIC per docs/CICD-SIGNAL-DISCIPLINE.adoc — MUST NOT be a required
# status check.

on:
schedule:
# Mondays 06:17 UTC. Off the hour on purpose: the estate's crons cluster on
# :00 and saturate the Actions queue.
- cron: '17 6 * * 1'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: debt-measure-${{ github.ref }}
cancel-in-progress: false

jobs:
measure:
name: Measure and record
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Re-measure the Debtfile
id: measure
run: |
set -uo pipefail
if [ ! -f .machine_readable/Debtfile.a2ml ]; then
echo "No Debtfile — nothing to measure."
echo "changed=no" >> "$GITHUB_OUTPUT"
exit 0
fi

# --write updates `count` and lowers any `ceiling` that has been paid
# down. It NEVER raises one. If debt grew past its ceiling the run
# exits 1 and the resulting file has count > ceiling — which the
# structure check rejects, so the PR opens RED. That is deliberate:
# growth is a human decision (pay it down, or raise the ceiling with a
# `Debt-exception:` trailer), never something a cron decides.
rc=0
bash scripts/run-debtfile.sh --write | tee "$RUNNER_TEMP/report.txt" || rc=$?
echo "rc=$rc" >> "$GITHUB_OUTPUT"

if git diff --quiet -- .machine_readable/Debtfile.a2ml; then
echo "No change in measured debt."
echo "changed=no" >> "$GITHUB_OUTPUT"
else
echo "changed=yes" >> "$GITHUB_OUTPUT"
fi

- name: Open (or update) the single recording PR
if: steps.measure.outputs.changed == 'yes'
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
BRANCH="chore/debt-measure"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

git checkout -B "$BRANCH"
git add .machine_readable/Debtfile.a2ml
# One -m per paragraph. A multi-line quoted string here would put its
# continuation lines at column 1, which ends the YAML block scalar and
# leaves the workflow unparseable — the estate already has a large
# population of workflows broken exactly this way.
git commit -m "chore(debt): re-measure Debtfile probes" \
-m "Machine-written by .github/workflows/debt-measure.yml. Counts follow the probes; ceilings only ever fall. A ceiling that needs to RISE is not done here — raise it in a human PR with a Debt-exception: trailer."
git push -f origin "$BRANCH"

BODY="$(printf 'Automated re-measurement of `.machine_readable/Debtfile.a2ml`.\n\n```\n%s\n```\n\nCeilings only fall here. If an entry breached its ceiling this PR is red on\npurpose — pay the debt down, or raise the ceiling in a human commit carrying\na `Debt-exception:` trailer naming the entry.\n\nSee `docs/DEBTFILE-SPEC.adoc`.\n' "$(cat "$RUNNER_TEMP/report.txt")")"

if gh pr view "$BRANCH" --json number >/dev/null 2>&1; then
gh pr edit "$BRANCH" --body "$BODY"
else
gh pr create --head "$BRANCH" --base "${{ github.ref_name }}" \
--title "chore(debt): re-measure Debtfile probes" --body "$BODY"
fi

- name: Surface a breach without minting an issue
if: steps.measure.outputs.rc != '0'
run: |
echo "::error::Debtfile run reported a breach, a broken probe, or an expired acceptance."
echo "See the PR opened by this run. No issue has been created, by design."
exit 1
68 changes: 68 additions & 0 deletions .github/workflows/governance-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1257,3 +1257,71 @@ jobs:
rm -rf .standards-checkout
bash "$RUNNER_TEMP/check-exemption-ratchet.sh" \
"${{ github.event.pull_request.base.sha }}"

debt-ratchet:
name: Debt ratchet
runs-on: ${{ inputs.runs-on }}
timeout-minutes: 5
permissions:
contents: read
# Sibling to exemption-ratchet, and pull-request-only for the same reason:
# the check compares ceilings against a BASE. The two govern different
# populations — the exemption ratchet watches debt you have EXCUSED, this
# one watches debt you have MEASURED.
if: ${{ github.event_name == 'pull_request' }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ github.repository }}
ref: ${{ github.sha }}
# Full history: the ratchet reads the Debtfile at the base ref and
# scans commit messages between base and HEAD for a declared
# exception. A shallow clone has neither.
fetch-depth: 0

- name: Check out standards repo for shared scripts
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: hyperpolymath/standards
ref: main
path: .standards-checkout
sparse-checkout: |
scripts
sparse-checkout-cone-mode: false

- name: Debt ratchet
run: |
set -euo pipefail
# Which copy of the scripts do we run?
#
# ⚠ BOOTSTRAP TRAP. A gate that fetches its own script from `main`
# cannot run on the pull request that INTRODUCES that script — the
# sparse checkout of main has no such file and the `cp` fails. This
# job failed exactly that way on the PR that added it, and the same
# shape has bitten hypatia's self-gating before.
#
# So when the repository under test IS standards, run the scripts
# from the PR's own tree. The guard is on the repository name and
# not on file existence: a consumer repo that happened to contain a
# file at scripts/check-debt-ratchet.sh must NOT be able to
# substitute its own gate.
if [ "${{ github.repository }}" = "hyperpolymath/standards" ]; then
cp scripts/check-debt-ratchet.sh \
scripts/check-debtfile-structure.sh "$RUNNER_TEMP/"
else
cp .standards-checkout/scripts/check-debt-ratchet.sh \
.standards-checkout/scripts/check-debtfile-structure.sh "$RUNNER_TEMP/"
fi
# Stage the scripts OUT of the scanned tree and delete the checkout,
# so the ratchet only ever reads the CALLER's Debtfile — standards
# has one of its own and it is not this repository's.
rm -rf .standards-checkout

# A repo with no Debtfile is not in violation — adoption is opt-in.
# But a repo that HAS one must have a well-formed one, or the ratchet
# would be comparing ceilings it could not parse.
if [ -f .machine_readable/Debtfile.a2ml ]; then
bash "$RUNNER_TEMP/check-debtfile-structure.sh"
fi
bash "$RUNNER_TEMP/check-debt-ratchet.sh" \
"${{ github.event.pull_request.base.sha }}"
88 changes: 88 additions & 0 deletions .machine_readable/Debtfile.a2ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# SPDX-License-Identifier: MPL-2.0
# Debtfile — measured, tolerated shortfalls for this repository.
# Author: Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
#
# A Mustfile asserts a BOOLEAN: the invariant holds, or the build fails.
# A Debtfile asserts a MONOTONE-DECREASING INTEGER: this much debt is
# tolerated, and it may never grow without saying so out loud.
#
# Run with: just debt-measure (scripts/run-debtfile.sh)
# Ratchet: scripts/check-debt-ratchet.sh <base-ref> [CI, on pull_request]
# Structure: scripts/check-debtfile-structure.sh
# Spec: docs/DEBTFILE-SPEC.adoc
#
# `count` and `ceiling` are MACHINE-WRITTEN by the weekly measure run.
# Do not hand-edit them to make a gate pass; that is the one thing this
# file exists to prevent.

@abstract:
Measured technical debt for the hyperpolymath `standards` canon. Every entry
carries a probe that returns an integer, so the claim can be re-checked
instead of believed. Entries leave this file by reaching zero, not by being
forgotten.
@end

## Documentation

### docs-md-not-adoc
- description: Docs under docs/ still in Markdown; estate policy is AsciiDoc (.adoc) except the four GitHub-required .md files
- probe: git ls-files 'docs/**/*.md' 'docs/*.md' | grep -vEi '(SECURITY|CONTRIBUTING|CODE_OF_CONDUCT|CHANGELOG)\.md$' | wc -l
- count: 309
- ceiling: 309
- severity: low
- policy: remediable
- tri: substitute
- accepted-until: 2027-02-01

## Test coverage of the gates

### gate-scripts-without-tests
- description: Scripts under scripts/ with no matching scripts/tests/<name>-test.sh — a gate with no test has never been shown able to fail
- probe: n=0; for f in $(git ls-files 'scripts/*.sh'); do b=$(basename "$f" .sh); case "$b" in *-test) continue;; esac; if [ ! -f "scripts/tests/${b}-test.sh" ] && [ ! -f "scripts/tests/${b#check-}-test.sh" ] && [ ! -f "scripts/tests/${b#run-}-test.sh" ]; then n=$((n+1)); fi; done; echo "$n"
- count: 31
- ceiling: 31
- severity: high
- policy: remediable
- tri: eliminate
- accepted-until: 2026-12-01

## Hygiene

### shell-scripts-missing-spdx
- description: Shell scripts under scripts/ without an SPDX-License-Identifier in the first three lines
- probe: n=0; for f in $(git ls-files 'scripts/*.sh'); do head -3 "$f" | grep -q 'SPDX-License-Identifier' || n=$((n+1)); done; echo "$n"
- count: 2
- ceiling: 2
- severity: medium
- policy: remediable
- tri: eliminate
- accepted-until: 2026-10-01

### todo-fixme-markers
- description: Files carrying a TODO or FIXME marker. Advisory — a marker is a note to self, not a defect
- probe: git grep -I -l -E '\b(TODO|FIXME)\b' -- . ':(exclude).machine_readable/Debtfile.a2ml' ':(exclude)docs/DEBTFILE-SPEC.adoc' | wc -l
- count: 276
- ceiling: 276
- severity: low
- policy: remediable
- tri: control
- accepted-until: 2027-06-01

## Licensing — FLAG ONLY

### pmpl-licence-headers
- description: Files whose SPDX identifier IS PMPL-1.0. Correct for palimpsest-license, palimpsest-plasma and consent-aware-http; wrong elsewhere — but which is which is an owner ruling, never an automated sweep
# ⚠ MATCH THE DECLARATION, NOT THE STRING. The first version of this probe was
# `git grep -l 'PMPL-1\.0'`, which counted every file that MENTIONS the licence
# — policy documents, manifests listing licences, citations, and other debt
# registers discussing this very debt. It reported 587 where the true figure is
# 7: an 84x over-count. Estate-wide the same error inflates 1,768 to 4,952, and
# it is the number standards#308 has been quoting.
- probe: git grep -l -E '^[^a-zA-Z0-9]*SPDX-License-Identifier:.*PMPL-1\.0' -- . | wc -l
- count: 7
- ceiling: 7
- severity: medium
- policy: flag-only
- tri: control
- tracking: hyperpolymath/standards#308
- accepted-until: 2027-01-01
13 changes: 13 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ mustfile-check path=".machine_readable/contractiles/must/Mustfile.a2ml":
must-check path=".machine_readable/contractiles/must/Mustfile.a2ml":
@bash scripts/run-mustfile.sh "{{path}}"

# Structural validation of the Debtfile (probe + ceiling + policy + expiry per entry)
debtfile-check path=".machine_readable/Debtfile.a2ml":
@bash scripts/check-debtfile-structure.sh "{{path}}"

# Re-measure every Debtfile probe and compare against its ceiling (read-only)
debt-measure path=".machine_readable/Debtfile.a2ml":
@bash scripts/run-debtfile.sh "{{path}}"

# Re-measure and WRITE BACK: updates count, lowers ceilings that were paid down.
# Never raises a ceiling — that needs a Debt-exception in the commit message.
debt-ratchet-down path=".machine_readable/Debtfile.a2ml":
@bash scripts/run-debtfile.sh --write "{{path}}"

# Install this repo's git hooks into .git/hooks/ (pre-commit guards)
hooks-install:
@bash hooks/install.sh
Expand Down
Loading
Loading