crates/sysknife-daemon/tests/action_reference_doc.rs:139-144 writes the footer of docs/action-reference.md by adding one to the tabled count and naming ListJobHistory as a literal:
out.push_str(&format!(
"---\n\n_{total} actions have an `ActionSpec` and are tabled above. The \
full catalogue (`KNOWN_ACTION_NAMES`) also includes `ListJobHistory`, \
which the dispatcher handles before the executor, for **{}** total._\n",
total + 1
));
The list that decides which actions bypass the executor is DISPATCHER_INTERNAL_ACTIONS, and the generator never reads it. At 736f9451:
$ grep -rn "DISPATCHER_INTERNAL_ACTIONS" crates/ --include=*.rs
crates/sysknife-daemon/tests/action_consistency.rs:27:const DISPATCHER_INTERNAL_ACTIONS: &[&str] = &["ListJobHistory"];
crates/sysknife-daemon/tests/action_consistency.rs:33: for &name in DISPATCHER_INTERNAL_ACTIONS {
crates/sysknife-daemon/tests/action_consistency.rs:67: let dispatcher_internal: BTreeSet<&str> = DISPATCHER_INTERNAL_ACTIONS.iter().copied().collect();
Add a second dispatcher-internal action and action_consistency.rs forces you to extend its list, while the generated document keeps counting one.
Why it matters
action_reference_doc_is_current compares the committed file against the generator, so both sides carry the same arithmetic and a wrong footer agrees with itself. Nothing compares that total against sysknife_types::KNOWN_ACTION_NAMES, which is the list the sentence names.
I put the arithmetic off by one at 736f9451 and regenerated:
python3 - <<'PY'
import pathlib
p=pathlib.Path('crates/sysknife-daemon/tests/action_reference_doc.rs')
s=p.read_text()
assert " total + 1\n" in s, "anchor missing"
s2=s.replace(" total + 1\n"," total + 2\n",1)
assert s2!=s
p.write_text(s2)
print("mutation applied")
PY
$ UPDATE_ACTION_REFERENCE=1 cargo nextest run -p sysknife-daemon --test action_reference_doc --locked -E 'test(action_reference_doc_is_current)'
PASS [ 0.012s] (1/1) sysknife-daemon::action_reference_doc action_reference_doc_is_current
Summary [ 0.013s] 1 test run: 1 passed, 6 skipped
$ tail -2 docs/action-reference.md
_191 actions have an `ActionSpec` and are tabled above. The full catalogue (`KNOWN_ACTION_NAMES`) also includes `ListJobHistory`, which the dispatcher handles before the executor, for **193** total._
KNOWN_ACTION_NAMES holds 192 names. With the document claiming 193, the Rust suite and the public-claims gate both stay green:
$ out="$(cargo nextest run -p sysknife-daemon -p sysknife-types -p sysknife-brain --locked 2>&1)"; rc=$?; echo "rc=$rc"; printf '%s\n' "$out" | tail -4
rc=0
Summary [ 8.638s] 1451 tests run: 1451 passed, 5 skipped
$ out="$(bash tests/release/public-claims.test.sh 2>&1)"; rc=$?; echo "rc=$rc"; printf '%s\n' "$out" | tail -8
rc=0
Published figures match the evidence artifacts.
Public claims are internally consistent.
Public claims contract passed.
I reverted the mutation and regenerated; git diff --stat is empty at 736f9451.
#452 made scripts/check_evidence_claims.py derive the developer guide's ActionSpec figure from this table, which was the right move. It also means a published claim now rests on a document whose own total nothing checks.
Scope
- Move
DISPATCHER_INTERNAL_ACTIONS somewhere both test binaries can read. Each file under crates/sysknife-daemon/tests/ compiles to its own binary, so a const in action_consistency.rs is invisible to action_reference_doc.rs. Putting it in sysknife-daemon's src/ next to the dispatcher that owns the behaviour is the option I would take; a #[path] include of a shared test module also works and keeps it out of the shipped crate. Either is fine, and the trap is assuming use across test binaries compiles.
- Derive both figures in the footer from that list: the tabled count, and the total. Name the bypassing actions from the list rather than spelling
ListJobHistory into the format string, so a second entry appears in the sentence without anyone remembering to add it.
- Leave
docs/action-reference.md byte-identical. The footer reads the same today; only its derivation changes.
Tests first
The assertion that is missing, in action_reference_doc.rs: the total in the generated footer equals sysknife_types::KNOWN_ACTION_NAMES.len(), and the tabled count equals that minus the number of dispatcher-internal actions. Write it and watch it fail before you touch the generator.
Prove it bites with the mutation above: change total + 1 to total + 2, run with UPDATE_ACTION_REFERENCE=1 so the committed file is regenerated to match, then run the test binary normally. Today that sequence passes. Your assertion has to fail on it, because that is the exact shape of the bug: the generator and the document agreeing with each other while both disagree with the catalogue.
A second worth having, cheaper to write: with DISPATCHER_INTERNAL_ACTIONS extended by one name, the footer sentence names both.
Difficulty
easy. The change is a constant moving and two numbers becoming derived. The one thing that will cost you an hour if nobody says it is the test-binary boundary in the first Scope bullet.
Getting started
CONTRIBUTING.md
has the build and test commands. No CLA and no copyright waiver. The project is MIT.
crates/sysknife-daemon/tests/action_reference_doc.rs:139-144writes the footer ofdocs/action-reference.mdby adding one to the tabled count and namingListJobHistoryas a literal:The list that decides which actions bypass the executor is
DISPATCHER_INTERNAL_ACTIONS, and the generator never reads it. At736f9451:Add a second dispatcher-internal action and
action_consistency.rsforces you to extend its list, while the generated document keeps counting one.Why it matters
action_reference_doc_is_currentcompares the committed file against the generator, so both sides carry the same arithmetic and a wrong footer agrees with itself. Nothing compares that total againstsysknife_types::KNOWN_ACTION_NAMES, which is the list the sentence names.I put the arithmetic off by one at
736f9451and regenerated:KNOWN_ACTION_NAMESholds 192 names. With the document claiming 193, the Rust suite and the public-claims gate both stay green:I reverted the mutation and regenerated;
git diff --statis empty at736f9451.#452 made
scripts/check_evidence_claims.pyderive the developer guide's ActionSpec figure from this table, which was the right move. It also means a published claim now rests on a document whose own total nothing checks.Scope
DISPATCHER_INTERNAL_ACTIONSsomewhere both test binaries can read. Each file undercrates/sysknife-daemon/tests/compiles to its own binary, so aconstinaction_consistency.rsis invisible toaction_reference_doc.rs. Putting it insysknife-daemon'ssrc/next to the dispatcher that owns the behaviour is the option I would take; a#[path]include of a shared test module also works and keeps it out of the shipped crate. Either is fine, and the trap is assuminguseacross test binaries compiles.ListJobHistoryinto the format string, so a second entry appears in the sentence without anyone remembering to add it.docs/action-reference.mdbyte-identical. The footer reads the same today; only its derivation changes.Tests first
The assertion that is missing, in
action_reference_doc.rs: the total in the generated footer equalssysknife_types::KNOWN_ACTION_NAMES.len(), and the tabled count equals that minus the number of dispatcher-internal actions. Write it and watch it fail before you touch the generator.Prove it bites with the mutation above: change
total + 1tototal + 2, run withUPDATE_ACTION_REFERENCE=1so the committed file is regenerated to match, then run the test binary normally. Today that sequence passes. Your assertion has to fail on it, because that is the exact shape of the bug: the generator and the document agreeing with each other while both disagree with the catalogue.A second worth having, cheaper to write: with
DISPATCHER_INTERNAL_ACTIONSextended by one name, the footer sentence names both.Difficulty
easy. The change is a constant moving and two numbers becoming derived. The one thing that will cost you an hour if nobody says it is the test-binary boundary in the first Scope bullet.Getting started
CONTRIBUTING.md
has the build and test commands. No CLA and no copyright waiver. The project is MIT.