Skip to content

The action-reference footer writes the catalogue total as total + 1, and nothing checks it against KNOWN_ACTION_NAMES #455

Description

@vladimirrott

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingeasyDifficulty: self-contained, no deep context neededgood first issueGood for newcomershelp wantedExtra attention is needed

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions