feat(debt): a Debtfile and a debt runner — measured debt that can only fall - #591
Merged
Conversation
…y fall
A GitHub issue is a boolean. Much of what this estate tracks in issues is an
integer that changes every week. An audit of the 697 open issues on 2026-08-07
found what that costs:
standards#309 claimed ~45 banned .py files measured 176 (~4x wrong, 10 weeks)
standards#308 claimed ~12,000 PMPL headers measured 4,968 (59% paid, unrecorded)
reposystem#138 claimed Nix in 63 repos measured 67 (grew, unnoticed)
The purest case was .git-private-farm, where a bot minted the same measurement
as a NEW ISSUE every run: rustsec 138, 136, 139, 156, 156, 155 — six issues
carrying one number, plus three more for mirror drift.
This adds a register that holds the integer, so the tracker can go back to
holding booleans.
.machine_readable/Debtfile.a2ml the register — same ### block shape as
Mustfile.a2ml, so the parsers agree
scripts/check-debtfile-structure.sh structural half — rejects a HOLLOW ENTRY
(a number with no probe behind it)
scripts/run-debtfile.sh executable half — re-measures every probe
scripts/check-debt-ratchet.sh ceilings may fall, never rise silently
.github/workflows/debt-measure.yml weekly re-measure; opens ONE PR, never an
issue. That is the whole point.
Not a contractile, despite the name fitting the convention exactly. The
contractile CLI does not exist — reposystem/contractiles/cli/ is an empty
directory and five of the six verbs have no runner. Only `must` executes, via
hand-written bash, and that is the pattern copied here.
docs/EXEMPTION-MECHANISMS.adoc already rejects declarations with no reader
("never read by anything. Reject"), which a seventh verb would be.
Not a sixth exemption mechanism either. It absorbs two conventions the estate
already invented: debt.a2ml (4 repos, ADR-002) and Exemption Layer 4b, which
stores debt as a Markdown table inside .claude/CLAUDE.md carrying a count and
an unblock condition, in 19 repos.
Sibling to the exemption ratchet, governing a different population:
check-exemption-ratchet.sh debt you EXCUSED — "did you quietly excuse more?"
check-debt-ratchet.sh debt you MEASURED — "did the number go up?"
Deletion is a violation. An entry leaves by reaching zero, not by being
deleted — the same reasoning that rejects `**` in the migration ledger.
Probe discipline, all of it learned the hard way while writing this:
* NO FALLBACK. A probe that fails is a hard failure. A counter returning 0 on
error is indistinguishable from zero debt, and zero is the state that passes.
* Three candidate probes were written and rejected for manufacturing debt —
two matched COMMENTS (usage examples in codeql-reusable.yml; a comment
explaining why a script does not use python3), one counted the test files
themselves.
* The register must not count itself. The first live run breached on
todo-fixme-markers (278 > 277) and pmpl-licence-headers (590 > 589) purely
because writing the entries down created the strings they search for.
Licensing debt is policy: flag-only. Measured and reported, never acted on,
per the standing owner directive in .claude/CLAUDE.md.
Tests prove each gate CAN fail — 14 + 16 + 12 cases, discovered automatically by
self-test.yml, each mutation-tested. The runner suite was itself found to be a
fake gate during authoring (piping into `expect` ran it in a subshell, so
counters were discarded and a failure would still have exited 0) and fixed.
Wired as job `debt-ratchet` in governance-reusable.yml, so every caller picks it
up with no per-repo PR. Tier 🟡 CHECK — MUST NOT be a required status check.
Adoption is opt-in: a repo with no Debtfile is not in violation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
The Debt ratchet job failed on the very PR that introduced it. Root cause is the bootstrap trap: the job sparse-checks-out hyperpolymath/standards@main to get its scripts, but check-debt-ratchet.sh and check-debtfile-structure.sh do not exist on main yet — they are what this PR adds. The `cp` failed and the step exited non-zero. A gate that fetches its own script from main cannot run on the pull request that introduces that script. Hypatia's self-gating has the same shape. When the repository under test IS standards, run the scripts from the PR's own tree. The guard is on the repository NAME, 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 for the estate's. Also regenerates .machine_readable/REGISTRY.a2ml + TOPOLOGY.md, which went stale when this PR added files (33 specs). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
REGISTRY.a2ml is GENERATED: conflict resolved by regenerating with 'just registry', never by mixing sides. Hand-mixing a generated file is a known 3-day-red failure in this estate.
pmpl-licence-headers 589->584 (consent-aware-http extracted in #593), todo-fixme-markers 277->276. Ceilings follow the measurement down; nothing raises them.
Brings in the CODEOWNERS co-owner fix (#599). REGISTRY.a2ml is GENERATED — if it conflicted it was regenerated with 'just registry', never hand-merged.
…ount pmpl-licence-headers was 'git grep -l PMPL-1.0', which counts every file that MENTIONS the licence: policy docs, manifests listing licences, CITATIONS.adoc, and other debt registers discussing this very debt. It does not count files licensed under it. standards: 587 mentions vs 7 declarations (84x). Estate-wide: 4,952 vs 1,768. The inflated figure is the order of number standards#308 has been quoting for the PMPL->MPL campaign, which is therefore roughly a third the size the issue believes. A licence is a declaration, so match the declaration. Third instance of the same lesson in this PR; now stated explicitly in DEBTFILE-SPEC.adoc.
hyperpolymath
disabled auto-merge
August 7, 2026 14:39
Uh oh!
There was an error while loading. Please reload this page.
hyperpolymath added a commit
that referenced
this pull request
Aug 7, 2026
… audit name (#601) **The rename first, because it was my error.** I added `Debtfile.adoc` at the repo root in #598. `.machine_readable/Debtfile.a2ml` already existed from #591 — a probe-backed, monotone-decreasing register with its own spec, runner, ratchet and 30 tests. Two files a directory apart, both called Debtfile, meaning different things, is exactly the drift this canon exists to prevent. The prose one is an **audit** and now says so: `docs/audits/2026-08-07-estate-cicd-debt.adoc`. **Then the part that matters.** Deno removal is now a *measured* entry, not a sentence. DEBTFILE-SPEC's own opening example is what happens otherwise: `standards#309` claimed ~45 banned `.py` files; measured, **176** — wrong by 4×, unchallenged for ten weeks. | entry | probe | count | |---|---|---:| | `deno-residue` | workflows/scripts that **invoke** deno | 4 | | `deno-artefacts` | files existing only to serve Deno | 6 | ⚠ The owner has ruled Deno removed and Bun permanent **three times**, yet `governance-reusable.yml:264` still runs `denoland/setup-deno` — **installing Deno on every estate repo on every run** — and `docs/migrations/npm-to-deno-template/` is a live recipe pointing repos *at* the retired runtime. That is why the ruling keeps needing repeating: the canon has been propagating the opposite. ⚠ Both probes deliberately **exclude `*/bindings/deno/`** — interop so *other people's* Deno code can call estate libraries, the same shape as the `proven` polyglot exception. "We do not use Deno" and "nobody may call us from Deno" are different statements; only the first has been made. **Verified**: structure OK (7 entries) · both probes HOLDING at 4 and 6 · structure tests 14/14 · runner tests 16/16. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Why
A GitHub issue is a boolean. Much of what this estate tracks in issues is an integer that changes every week.
An audit of the 697 open estate issues on 2026-08-07 found what that costs:
standards#309.pyfilesstandards#308reposystem#138The purest case is
.git-private-farm, where a bot mints the same measurement as a new issue on every run:Six issues carrying one number, plus three more for mirror drift. This PR adds a register that holds the integer, so the tracker can go back to holding booleans.
What
.machine_readable/Debtfile.a2ml### <id>block shape asMustfile.a2mlscripts/check-debtfile-structure.shscripts/run-debtfile.sh--writelowers ceilingsscripts/check-debt-ratchet.sh.github/workflows/debt-measure.ymlWhy not a contractile
The name fits the convention exactly (
Mustfile,Trustfile,Bustfile,Dustfile,Intentfile). The placement does not:reposystem/contractiles/cli/is an empty directory; five of six verbs have no runner.mustexecutes, via hand-written bash — the pattern copied here.EXEMPTION-MECHANISMS.adocalready rejects declarations with no reader (.hypatia-ignore: "never read by anything. Reject."). A seventh verb would be exactly that.bustis fault-injection drills,dustis exnovation,intendis roadmap.If the CLI is ever built, this absorbs as the
debtverb with no rename.Why not a sixth exemption mechanism
It absorbs two conventions the estate already invented:
debt.a2ml(gitbot-fleet,flat-mate,rpa-elysium,chronicles-of-slavia; ADR-002).claude/CLAUDE.md, rows\| glob \| count \| rationale \| unblock-condition \|, regex-parsed, in 19 reposSibling to the exemption ratchet, governing a different population:
check-exemption-ratchet.sh— debt you excused: "did you quietly excuse more?"check-debt-ratchet.sh— debt you measured: "did the number go up?"Probe discipline — all learned the hard way in this PR
codeql-reusable.yml; a comment explaining why a script does not usepython3); one counted the test files themselves.todo-fixme-markers(278 > 277) andpmpl-licence-headers(590 > 589) purely because writing the entries down created the strings they search for.Licensing debt is
policy: flag-only— measured and reported, never acted on, per the standing owner directive in.claude/CLAUDE.md.Verification
Each suite proves its gate can fail and was mutation-tested. The runner suite was itself found to be a fake gate during authoring — piping into
expectran it in a subshell, so counters were discarded and a real failure would still have exited 0 — and fixed.Workflows re-verified as parsing (
debt-measure.ymlinitially did not: a multi-linegit commit -mput continuation lines at column 1 and ended the YAML block scalar — the estate's own broken-workflow class, caught before merge).actions.lockupdated; duplicate-key linter clean across 39 files.Rollout
Wired as job
debt-ratchetingovernance-reusable.yml, so every caller picks it up on next run with no per-repo PR.Tier 🟡 CHECK — MUST NOT be a required status check. Promotion to 🔴 GATE only after two clean weekly measure cycles.
Adoption is opt-in: a repo with no
Debtfile.a2mlis not in violation; a repo that has one must have a well-formed one.🤖 Generated with Claude Code