Corrected 2026-09-03 at 60af0ad. This issue was filed on 2026-08-17. PR #242 landed the next day and fixed two of the three claims below. One is still wrong, and the guard the issue asks for still does not exist. The original text is kept underneath so the history reads straight.
CONTRIBUTING.md publishes story-coverage figures that nothing derives from the story files.
What is still wrong at 60af0ad
CONTRIBUTING.md:50 says:
The suite is 133 stories: 54 atomic + 79 Ubuntu. Every Debian-only action now has one.
CONTRIBUTING.md:47, four lines above it in the same table, says the opposite:
Remaining: story coverage for the cross-family actions, and five Debian-only ones that still have none: the four fail2ban actions and GrubSetKargs.
Line 47 is the true one. Recounted at 60af0ad:
$ for a in Fail2banStatus Fail2banBanIp Fail2banUnbanIp ConfigureFail2banJail GrubSetKargs; do
echo "$a: $(grep -rl "\"$a\"" tests/e2e/stories/ | wc -l)"
done
Fail2banStatus: 0
Fail2banBanIp: 0
Fail2banUnbanIp: 0
ConfigureFail2banJail: 0
GrubSetKargs: 0
The story totals and the uncovered counts are correct today. Both recounted at 60af0ad:
$ ls tests/e2e/stories/story-*.sh | wc -l
133
$ # family from each story's own header line
ubuntu=79 atomic=54
and the derivation below now returns {'All': 59, 'Fedora': 10, 'Ubuntu': 5}, which is what CONTRIBUTING.md:50 prints.
So the numbers are right and nothing keeps them right. They were wrong for a day in August, somebody fixed them by hand, and the next story added or retired puts them back out.
The guard, which is the part worth doing
scripts/check_evidence_claims.py has no rule that derives an uncovered-action count. Its checks at 60af0ad:
$ grep -nE '^def check_' scripts/check_evidence_claims.py
124:def check_figure
149:def check_bare_test_totals
194:def check_story_claims
261:def check_bare_story_counts
306:def check_action_figures
382:def check_validated_tiers
check_bare_story_counts matches \b([0-9]{2,})[-\s]stor(?:y|ies)\b, so it catches "133 stories" and misses "59 are still untouched by any story" and "54 atomic". The guard is sound; the claim shape slips past it.
Scope
- Fix the contradiction:
CONTRIBUTING.md:50 still claims every Debian-only action has a story.
- Add a
check_evidence_claims.py rule that derives the uncovered counts and the family split from the tree, so neither can drift again. The derivation in the original text below is the whole implementation.
Tests first
Feed the checker a CONTRIBUTING.md fixture carrying a wrong count and assert it fails naming both figures, the published one and the derived one. A test that only exercises the agreeing case proves nothing, since that is the state the file is in today. Prove the rule bites by changing the published number and watching the checker go red, and again by adding a story file and watching it go red the other way.
Difficulty
medium. The documentation half is a two-line edit; the guard rule is the work.
Original text, filed 2026-08-17, before #242
CONTRIBUTING.md publishes two coverage figures and one prose claim about story coverage. All three are wrong.
The prose claim
Remaining: story coverage for the cross-family actions. The Debian-only ones are all covered now.
Five Ubuntu-family actions have no story: Fail2banStatus, Fail2banBanIp, Fail2banUnbanIp, ConfigureFail2banJail and GrubSetKargs. The fail2ban four are the subject of #219, which exists because of this gap.
The figures
of the action names available on both families, 57 are still untouched by any story, plus 8 Fedora-only ones.
Derived from the catalogue and the story files:
| family |
uncovered |
CONTRIBUTING says |
All (both families) |
59 |
57 |
Fedora only |
10 |
8 |
Ubuntu only |
5 |
not mentioned |
| total |
74 |
65 |
Derivation, from a clean checkout:
python3 - <<'PY'
import re, glob
rows = re.findall(r'^\| `([A-Za-z0-9_]+)` \|.*?\| (All|Ubuntu|Fedora) \|',
open('docs/action-reference.md').read(), re.M)
named = set()
for f in glob.glob('tests/e2e/stories/*.sh'):
named |= set(re.findall(r'"([A-Za-z0-9_]+)"', open(f, errors='replace').read()))
from collections import Counter
print(Counter(d for n, d in rows if n not in named))
PY
Two caveats a fixer should keep: this counts an action as covered if its name appears as a quoted literal anywhere in a story, which over-counts, because ConfigureWifi, GrubSetKargs and SetLocale appear only in "must not confuse with" comments. And covered-by-any-story is not the same as exercised-on-Ubuntu: 98 of the 164 Ubuntu-and-All actions are named by no ubuntu-family story.
Why the guard missed it
CONTRIBUTING.md is in CLAIM_FILES (scripts/check_evidence_claims.py), so this is not an unguarded file. check_bare_story_counts matches \b([0-9]{2,})[-\s]stor(?:y|ies)\b, and neither "57 are still untouched by any story" nor "all covered now" presents a number next to the word it looks for. The guard is sound and the claim shape slipped past it.
Scope
- Correct the three claims in
CONTRIBUTING.md.
- Consider deriving them instead of writing them, the way
docs/action-reference.md is generated. A check_evidence_claims.py rule for "N ... untouched by any story" would keep the number honest, and the derivation above is already the whole implementation.
Difficulty
easy as a documentation fix. medium if you also add the guard rule, which is the version worth doing.
Getting started
CONTRIBUTING.md has the build and test commands. No CLA and no copyright waiver. The project is MIT.
CONTRIBUTING.mdpublishes story-coverage figures that nothing derives from the story files.What is still wrong at
60af0adCONTRIBUTING.md:50says:CONTRIBUTING.md:47, four lines above it in the same table, says the opposite:Line 47 is the true one. Recounted at
60af0ad:The story totals and the uncovered counts are correct today. Both recounted at
60af0ad:and the derivation below now returns
{'All': 59, 'Fedora': 10, 'Ubuntu': 5}, which is whatCONTRIBUTING.md:50prints.So the numbers are right and nothing keeps them right. They were wrong for a day in August, somebody fixed them by hand, and the next story added or retired puts them back out.
The guard, which is the part worth doing
scripts/check_evidence_claims.pyhas no rule that derives an uncovered-action count. Its checks at60af0ad:check_bare_story_countsmatches\b([0-9]{2,})[-\s]stor(?:y|ies)\b, so it catches "133 stories" and misses "59 are still untouched by any story" and "54 atomic". The guard is sound; the claim shape slips past it.Scope
CONTRIBUTING.md:50still claims every Debian-only action has a story.check_evidence_claims.pyrule that derives the uncovered counts and the family split from the tree, so neither can drift again. The derivation in the original text below is the whole implementation.Tests first
Feed the checker a
CONTRIBUTING.mdfixture carrying a wrong count and assert it fails naming both figures, the published one and the derived one. A test that only exercises the agreeing case proves nothing, since that is the state the file is in today. Prove the rule bites by changing the published number and watching the checker go red, and again by adding a story file and watching it go red the other way.Difficulty
medium. The documentation half is a two-line edit; the guard rule is the work.Original text, filed 2026-08-17, before #242
CONTRIBUTING.mdpublishes two coverage figures and one prose claim about story coverage. All three are wrong.The prose claim
Five Ubuntu-family actions have no story:
Fail2banStatus,Fail2banBanIp,Fail2banUnbanIp,ConfigureFail2banJailandGrubSetKargs. The fail2ban four are the subject of #219, which exists because of this gap.The figures
Derived from the catalogue and the story files:
All(both families)FedoraonlyUbuntuonlyDerivation, from a clean checkout:
Two caveats a fixer should keep: this counts an action as covered if its name appears as a quoted literal anywhere in a story, which over-counts, because
ConfigureWifi,GrubSetKargsandSetLocaleappear only in "must not confuse with" comments. And covered-by-any-story is not the same as exercised-on-Ubuntu: 98 of the 164 Ubuntu-and-All actions are named by no ubuntu-family story.Why the guard missed it
CONTRIBUTING.mdis inCLAIM_FILES(scripts/check_evidence_claims.py), so this is not an unguarded file.check_bare_story_countsmatches\b([0-9]{2,})[-\s]stor(?:y|ies)\b, and neither "57 are still untouched by any story" nor "all covered now" presents a number next to the word it looks for. The guard is sound and the claim shape slipped past it.Scope
CONTRIBUTING.md.docs/action-reference.mdis generated. Acheck_evidence_claims.pyrule for "N ... untouched by any story" would keep the number honest, and the derivation above is already the whole implementation.Difficulty
easyas a documentation fix.mediumif you also add the guard rule, which is the version worth doing.Getting started
CONTRIBUTING.md has the build and test commands. No CLA and no copyright waiver. The project is MIT.