Summary
Give verify-no-work-records (and every future governance checker in this repository) a distinct "could not run" exit code (2), a --self-test that plants one defect per rule, and a green result that prints how much it checked. Today a listing failure reports clean.
Current behaviour
.github/workflows/verify-no-work-records.yml:
- Sets
set -euo pipefail (line 33) but feeds each rule from a process substitution: done < <(git ls-files -- '.ai-work/') (line 39), done < <(git ls-files -- '*.md' | grep -E '…' || true) (lines 52 and 61). The shell never checks a process substitution's exit status, and the || true explicitly swallows any pipeline failure, so if git ls-files fails (not a checkout, shallow-clone surprise, wrong working-directory) each loop sees empty input, finds zero violations, and the job prints No AI work records tracked. and exits 0 (line 71).
- The only non-zero exit is
exit 1 on violations (line 69). There is no exit 2, no count of files scanned in the green message, and no --self-test (neither string appears in the file).
- The logic is inline YAML, so it cannot be unit-tested the way
.github/scripts/update-ai-profile-subscription.mjs is (verify-ai-profile-subscription-update.yml runs node --check and node --test on it).
Desired behaviour
- Extract the checker to
.github/scripts/verify-no-work-records.sh (or .mjs), called by the workflow with the same inputs.
- Exit contract:
0 ran and found nothing, printing scanned: <n> tracked files, rules: 3, violations: 0; 1 ran and found violations (one line per file with the rule id); 2 could not run (git ls-files non-zero, zero files listed in a repository that has tracked files, missing prerequisite), printing the reason. A "pass" over zero listed files is 2, not 0.
--self-test: builds a temporary git repository with one planted violation per rule plus a clean control, asserts exactly those violations and exit 1, asserts the control exits 0 with counts, and asserts a broken listing (for example running outside a git checkout) exits 2. A red control (VERIFY_SELF_TEST_BREAK=1) must make the self-test itself fail.
- A CI job runs
--self-test on every change to the script (same pattern as verify-ai-profile-subscription-update.yml).
Acceptance
Done when:
- The script exists with the exit contract above and the workflow calls it.
--self-test passes in CI and fails with VERIFY_SELF_TEST_BREAK=1.
- Running the checker in an empty directory (not a git checkout) exits
2 and prints the reason.
Verify by:
.github/scripts/verify-no-work-records.sh --self-test # exit 0, prints "self-test: 3 planted, 3 red, control 0, broken-listing 2"
VERIFY_SELF_TEST_BREAK=1 .github/scripts/verify-no-work-records.sh --self-test; test $? -ne 0
cd "$(mktemp -d)" && "$OLDPWD/.github/scripts/verify-no-work-records.sh"; test $? -eq 2
Mutation proof: replace git ls-files with false in a scratch copy; the checker exits 2, not 0.
Out of scope
What the checker allows (separate allowlist issue); the future verify-decisions checker (it adopts the same contract from day one).
Dependencies
Source: AI-Learning F-95. Depends on: nothing. Related: the allowlist amendment and the verify-decisions issue in this repository.
Summary
Give
verify-no-work-records(and every future governance checker in this repository) a distinct "could not run" exit code (2), a--self-testthat plants one defect per rule, and a green result that prints how much it checked. Today a listing failure reports clean.Current behaviour
.github/workflows/verify-no-work-records.yml:set -euo pipefail(line 33) but feeds each rule from a process substitution:done < <(git ls-files -- '.ai-work/')(line 39),done < <(git ls-files -- '*.md' | grep -E '…' || true)(lines 52 and 61). The shell never checks a process substitution's exit status, and the|| trueexplicitly swallows any pipeline failure, so ifgit ls-filesfails (not a checkout, shallow-clone surprise, wrongworking-directory) each loop sees empty input, finds zero violations, and the job printsNo AI work records tracked.and exits0(line 71).exit 1on violations (line 69). There is no exit2, no count of files scanned in the green message, and no--self-test(neither string appears in the file)..github/scripts/update-ai-profile-subscription.mjsis (verify-ai-profile-subscription-update.ymlrunsnode --checkandnode --teston it).Desired behaviour
.github/scripts/verify-no-work-records.sh(or.mjs), called by the workflow with the same inputs.0ran and found nothing, printingscanned: <n> tracked files, rules: 3, violations: 0;1ran and found violations (one line per file with the rule id);2could not run (git ls-filesnon-zero, zero files listed in a repository that has tracked files, missing prerequisite), printing the reason. A "pass" over zero listed files is2, not0.--self-test: builds a temporary git repository with one planted violation per rule plus a clean control, asserts exactly those violations and exit1, asserts the control exits0with counts, and asserts a broken listing (for example running outside a git checkout) exits2. A red control (VERIFY_SELF_TEST_BREAK=1) must make the self-test itself fail.--self-teston every change to the script (same pattern asverify-ai-profile-subscription-update.yml).Acceptance
Done when:
--self-testpasses in CI and fails withVERIFY_SELF_TEST_BREAK=1.2and prints the reason.Verify by:
Mutation proof: replace
git ls-fileswithfalsein a scratch copy; the checker exits2, not0.Out of scope
What the checker allows (separate allowlist issue); the future
verify-decisionschecker (it adopts the same contract from day one).Dependencies
Source: AI-Learning F-95. Depends on: nothing. Related: the allowlist amendment and the
verify-decisionsissue in this repository.