Required commands — what a reviewer must be able to run to measure (SPEC-192 FR-1)¶
The single reference for which commands /vt-d-4-review's reviewers depend on when they
measure rather than reason. Reviewers report against this list, which is what makes their
execution_available answer meaningful — a self-report against an undeclared set says nothing.
This file is the declaration only. It grants nothing, and SPEC-192 widens no path — every rule
it leaves behind pins one exact tracked file (NFR "No new grant surface"). It adds exactly ONE
allow rule, for the mandatory validator call, by owner decision (Decision 9, c86b47a02).
⚠ This sentence read "adds no allow rule" until pass 12, while the table at the bottom of this
same file said "granted — 1 of 105". A file that contradicts itself about a grant is worse than
one that is merely stale, because each half looks authoritative on its own. The Allow rule column records what .claude/settings.json
says today; where a rule is absent the column names the rule that would grant the command,
so a reader can act on the gap without inventing a pattern — or, where no rule is permissible,
says so and names the remedy instead.
Modelled on persona-catalog.md: one authoritative statement that other
surfaces point at instead of repeating. Do not restate this table in a SKILL.md.
Scope — this repository only¶
Every command in the table below is a path in V025-claude-toolkit, and Granted? measures
this repository's .claude/settings.json. /vt-d-4-review also runs in other projects, and there
this table does not apply. Read literally it would: EC-6 turns a declared command that does not
exist into unknown, so every review outside this repository would come out INCOMPLETE and
/vt-d-5-finalize would block it for a reason that has nothing to do with the review.
In any other project the required set is that project's own test command — the exact
command the test-runner ran in Step 1.5, recorded verbatim as test_command in .test-gate.md.
(test_framework only names the framework, such as jest; it is not something a reviewer can
run.) A reviewer that needed it and was refused reports false and names it; degraded_reason
names the allow rule that would grant it in that project's .claude/settings.json. When code
is not a deliverable type the test gate is NOT_RUN, and the set is empty (EC-5). A gate that
records no test_command declares nothing either: the set is empty, and a reviewer does not
report unknown for a command nobody declared.
test_command: not relayed (failed the Step 1.5 shape check) is the one case where the set is
non-empty and unrunnable (SPEC-192 pass 13 security H-4). The project does declare a test
command; the gate file's value failed Step 1.5 item 1's untrusted-input check, so the dispatcher
withheld it. The required set is therefore that one withheld command, the reviewer reports
false and names it, and the pass is INCOMPLETE by the ordinary rule. That cost is deliberate
and it is the cheap side of the trade: a project whose real command needs shell composition
(cd web && npm test) must record a single-command wrapper instead, which is a visible,
one-line fix — whereas treating the withheld value as "nothing declared" would hand back a full
verdict from reviewers that ran nothing, which is the exact failure this spec exists to prevent.
The set¶
Measured against .claude/settings.json on 2026-10-13 — 120 allow rules, all exact paths, zero
globs (zero asterisks other than each rule's trailing :*).
⚠ These numbers rot on any change to .claude/settings.json, and they have now rotted eight
times (the EIGHTH on 2026-10-09, merging main into SPEC-164: main read 118/99/99 and that branch had no copy of this file, so the merge took main's figures clean; the merged tree holds 119/100/100 — that branch's one test file, test-lane-detect.sh, and its allow rule on top of main's; the SEVENTH on 2026-10-09, merging main again: main removed one rule and read 117,
that branch read 119, the merged tree holds 118 — neither side right; the SIXTH on 2026-10-08, merging main into SPEC-205: 118 against 119 and
98 against 99 twice — that branch's one extra test file, test-spec205-measurement-tools.sh) — twice on 2026-10-07 alone, on two merges of origin/main eight
hours apart. The FIFTH was the second of those (80 commits): 117 against 118, and 97 against 98
twice, and .design-state.yaml's dependency claim moved in the SAME merge for an unrelated reason,
so three mirrors in two files rotted together. The fourth was the first merge that day (162
commits): 107 against 117, and 88 against 97 twice. Nothing in the merge conflicted on them — only one side
ever edits this file — so all three were wrong on a clean merge, and test-output-contract.sh
caught them only because it re-derives each one from disk. Claude also could not verify them by
hand here: reading .claude/settings.json is permission-denied, so the derivation was the only
available measurement. The third was the 2026-10-06 merge of origin/main, which added
plugins/vt-base/tests/test-registry-sweep.sh and its allow rule: test-output-contract.sh went
to 187/4 with all three figures wrong at once (106 against 107, 87 against 88 twice). The two
earlier instances: 67 → 68 was corrected in review pass 1, then the origin/main merge brought a
69th rule (b0719c5d, SPEC-181's test-status-vocab.sh) and the table was not re-measured, so
pass 5 found 68 against 69 and 50 against 51 at the same time. Not every change is rot: on 2026-10-08 118 → 117 was a deliberate removal, the
ci-test-gate.py rule (SPEC-221 finding SEC-P6-3). Nothing binds any figure here to
settings.json. Re-measure before trusting them:
jq '.permissions.allow | length' .claude/settings.json # 120
jq -r '.permissions.allow[]' .claude/settings.json | grep -c 'plugins/.*/tests/test-.*\.sh' # 101
ls plugins/*/tests/test-*.sh | wc -l # 101
jq -r '.permissions.allow[]' .claude/settings.json | grep -c 'python3' # 13
⚠ This table is shrink-protected (SPEC-192 pass 5, SEC-4). test-review-gates.sh check DOC5
floors it at four well-formed rows. The reason is not tidiness: execution_available is only
meaningful against a declared set, and a reviewer needing none of the declared commands reports
true trivially (EC-5) — so quietly shrinking this table narrows what "unmeasured" can ever mean
and switches the control off with every suite still green. Adding rows is free; removing one is a
reviewable act that must be justified in the commit that does it. Measured when the floor was
added: emptying the whole table was already caught (DOC3 reads the python3 row), but deleting the
other three rows produced zero violations.
| Command | What it is for | Allow rule | Granted? |
|---|---|---|---|
bash scripts/tests/setup/run-all.sh |
The guard suite. A reviewer claiming a guard passes, fails, or is vacuous has measured nothing without it. | Bash(bash scripts/run-tests.sh:*) — reach it as bash scripts/run-tests.sh --groups=3. Group 3 is the guard suite (run-tests.sh:25), and the granted wildcard covers the flag. Invoking run-all.sh directly has no rule and needs none. |
yes, via group 3 |
bash scripts/run-tests.sh |
The full suite, including the 101 plugin suites that run only from here. | Bash(bash scripts/run-tests.sh:*) |
yes |
python3 (ad-hoc, e.g. python3 -c …) |
Counting, parsing YAML/JSON, and differential sweeps — the arithmetic behind most measured findings. | No permissible rule. The settings guard (test-output-contract.sh, _wildcard_offenders) accepts a Bash rule only when it pins one tracked file, so no rule can grant a bare or -c invocation; 13 rules exist, each pinning one exact script. Remedy: move the computation into a tracked script and grant that exact path. That is the only remedy that clears the gate. Accepting the finding as an unverified lead is not a second remedy — it is the decision to leave the pass INCOMPLETE, and an INCOMPLETE pass is never finalizable. Measured: a gate listing the reviewer under reviewers_unmeasured validates finalizable=no, and forcing completeness: COMPLETE to escape is refused ("reviewers are not covered"). Re-running the review reproduces the same gate, so the loop does not terminate on its own — which is why /vt-d-4-review Step 5's INCOMPLETE branch now carries a STOP IF UNCHANGED bound (SPEC-192 pass 5, PERF-1): an unchanged degraded_reason means the re-run cannot help, and a third pass costs ~1.17M input tokens to learn nothing. The bound stops the looping; it does not clear the gate. The remedy above is still the only thing that does. |
no |
bash "$CLAUDE_PLUGIN_ROOT/../vt-base/bin/validate-review-gate.sh" .review-gate.md (the only spelling any call site invokes) / bash plugins/vt-base/bin/validate-review-gate.sh (granted, invoked nowhere) |
The MANDATORY ledger check that promote, workflow-5-finalize and finalization-orchestrator each STOP on. It is the only mechanical evaluation of R0-R7; reading the gate instead is what failed on 2026-08-13. |
Bash(bash plugins/vt-base/bin/validate-review-gate.sh:*) — owner decision 2026-09-27 (SPEC-192 H-9): grant the repo-relative path. ⚠ RETRACTED by BUG-340 two days later, and the grant is consequently inert. That decision was reversed before it was ever implemented: on 2026-09-29 BUG-340 MEASURED the repo-relative spelling as rc=127 from a real consumer project, where it reads to an operator as "the toolkit has no validator". Pass 12 therefore left all three call sites on the $CLAUDE_PLUGIN_ROOT/../vt-base/ hop and retracted H-6. Measured again 2026-10-03: the repo-relative form appears inside a code fence at NONE of the three sites, and not one of them carries an [ -x ... ] test — this cell asserted both for six days, and a reader who "fixed" the discrepancy by adding the test would have re-created the inert-grant defect H-6 was retracted for, since a compound if [ -x … ] cannot match a PREFIX allow rule. Resolving beats being granted. The rule below is therefore granted and matched by nothing; withdrawing it is an owner decision and needs no test edit, because the H-9 and H-3 rows derive the grant state from the settings file in both directions. Why this way round: the settings guard (_wildcard_offenders) accepts a Bash rule only when it pins one tracked file, so the hop can never be granted and the repo-relative path can. |
granted — 1 of 120, added by the owner on 2026-09-27 (SPEC-192 H-6). It was ungranted for the whole of passes 1-11: the MANDATORY step failed closed, and because workflow-5-finalize is context: fork it could not be satisfied there at all. The grant had to land together with the two counts above. MEASURED both ways: the rule alone moves the derived allow-rule count and takes test-output-contract.sh from 165/2 to 164/3, AND reddens this file's own H-9 row in test-review-gates.sh, the row that checks this cell stops claiming a zero grant count once the rule exists — two suites, not one. ⚠ That row greps this cell for the zero-claim substring, so do NOT quote its failure message here: doing so re-introduces the string and reddens the row from inside the fix (measured). A grant committed by itself trades one red for two. |
bash plugins/*/tests/test-*.sh |
Running one plugin suite directly, the usual way a reviewer checks a single claim. | (absent as a class) — 101 rules exist, each naming one exact test file, and they correspond 1:1 with the 101 suites on disk (measured: comm -3 of the two sets is empty); a new file needs its own Bash(bash plugins/<plugin>/tests/test-<name>.sh:*) |
per file |
What this table does NOT see¶
Granted? measures the tracked .claude/settings.json. An untracked
.claude/settings.local.json loads at higher precedence, so a command this table calls
absent can be in force locally and a reviewer can measure something the table says it cannot.
The reverse is equally possible.
That is not a flaw in the declaration — it is why the reviewer reports the commands it actually ran rather than what any table predicts. The table tells a reader which grants are reviewed; the report tells them what happened. When the two disagree, the report wins and the gap belongs in the tracked file.
plugins/vt-base/tests/test-output-contract.sh check P11-6 fails when a local file carries such
an escalation, so this does not rot silently.
Reading the last column¶
Three values, and the third is not hedging:
- yes — a rule matches the command as written.
- no — no rule matches. A reviewer that needs it must report
execution_available: falseand name it. - per file — the class has no rule, but individual members do. A reviewer may be able to run
test-foo.shand nottest-bar.shin the same pass. It must report against the files it actually needed, not against the class.
The python3 and plugins/*/tests/ rows are the reason execution_available is per-reviewer
(FR-7) rather than per-pass: two reviewers in one roster can legitimately give different answers.
How a reviewer uses this¶
Report the commands it actually ran, never the ones it planned to run. That is the only
mitigation against a reviewer that reports true while having been refused (EC-8) — a plan is
not evidence, and the orchestrator cannot tell the difference from the outside.
A command that ran and failed is a measurement, not a refusal (EC-3). A non-zero exit, a failing
test or a crash after launch belongs under commands_run with its result, and the answer stays
true. commands_refused is only for commands that were never allowed to start. Filing a failing
suite as refused would turn a measured FAIL into an INCOMPLETE pass.
A reviewer that needs none of these commands — a document or presentation reviewer with nothing
to execute — reports true trivially against an empty required set (EC-5). That is not a
degraded pass and must not be recorded as one.
Keeping it honest¶
Granted? is a measurement of .claude/settings.json, not a wish. When a rule is added or
removed, this table is wrong until it is re-measured — and a stale row degrades loudly, not
silently: a reviewer that cannot find a declared command reports unknown for it and names it
(EC-6), which is itself enough to make the pass INCOMPLETE.