Consistency audit phase 4: review scope and coverage¶
Master plan: PLAN-consistency-audit.md
Planning effort: high. Two of the three judgements in this phase are permanent: which file types are subject to human review for the life of the repository, and what to do about the fact that no review mark this repository has ever recorded was signed. The third, the order in which 112 files get read, decides whether the first sessions find anything.
Scope¶
In scope:
- Issue #227 (
review-coverage): 77 in-scope files need review against a threshold of fewer than 5. - The
review-scope-completenesscheck, which fails today with 44 orphaned files and has no issue open against kerbside yet. It is folded in here rather than given a phase of its own; see decision 1. - Configuring commit signing in this clone, and deciding what to do about the unsigned marks already in history.
- An ordered work queue and a per-session recipe, so the grind can be picked up by any session without re-deriving the order.
Out of scope:
- Re-reviewing the 115 files that already carry a mark. Their marks are unattested (survey finding 4) and that is worth knowing, but re-attesting without re-reading would be a signature over a claim nobody checked.
- Teaching the
review-coverageaudit to care about signatures. That is an upstream change toshakenfist/developmentand is recorded as future work, not done here. See decision 4. - Issues #370 and #381, which are phase 5.
- Any change to the files being reviewed. A review session produces marks and, where it finds something, issues. It does not produce fixes; a fix found this way becomes its own change with its own review.
What the survey found¶
The master plan's phase 4 sketch is stale in three of its
five factual claims, and the tree has grown a new failing
check since it was written. All corrections below are also made
at source, in the master plan's phase 4 sketch and in the
docs/plans/index.md row, as part of this planning commit.
1. The backlog is 77 files, not 70. tools/review-tracking.sh
status today: 115 of 192 in-scope files carry a valid review at
HEAD; 77 need review. The sketch's 70 was measured on
2026-08-29, before phases 2 and 3 and the renovate merges landed
and before prune-reviews ran against them.
2. Issue #227's body is badly stale, and worse than #370's.
It reads "0 of 152 in-scope files reviewed at HEAD; 152 need
review" and lists files that no longer exist -- .github/
workflows/pr-address-comments.yml, deleted in phase 2, and
alembic/env.py, which moved under kerbside/migrations/ before
that. It also names .claude/skills/add-database-migration.md,
which is now a directory with a SKILL.md inside. The audit
files an issue and never refreshes its body, so an open
consistency issue's body is a historical record of the day it was
filed. This is the second instance of the same pathology; #370
has it too. Read the numbers from tools/review-tracking.sh
status, never from the issue.
3. "The remaining bulk is protocol documentation under
docs/spice/" is wrong. docs/spice/ is 9 of the 77. The
actual distribution is kerbside/ 25 (15 of them unit tests),
tools/ 17, docs/ 17 (9 under spice/), 7 repository-root
files including AGENTS.md, ARCHITECTURE.md, README.md,
PLAN-TEMPLATE.md and PUSH-AUDIT.md, and the remainder spread
across .claude/, .github/workflows/, demo/,
tempest-plugin/, loadtests/ and tests/. The sketch's
conclusion -- that the bulk is low-yield reading -- does not
follow from a corrected distribution.
4. No review mark this repository has ever recorded was
signed. git log --format='%h %G? %s' -- REVIEWS.md returns
N for every commit, including the four that added marks:
742dd06 reviews: CI workflows and tools, 08422d6 review:
test tooling, 7b67f12 review: tempest plugin, bc30959
review: rust proxy churn. N is "no signature", not "cannot
verify" -- the merge commits alongside them show E for
GitHub's key, which is the "cannot verify" case, so the two are
distinguishable and this is the former.
The cause is that nothing is configured: git config --get
gpg.format, gpg.x509.program and commit.gpgsign are all
empty in both local and global scope, though gitsign is
installed at /usr/bin/gitsign. .claude/CLAUDE.md and
AGENTS.md (line 191) both document the configuration as a
prerequisite; nobody ran it. The sketch called signing "a
prerequisite the phase must check before starting", which is
right, but it assumed the check would pass. The consequence is
larger than a config step: the attestation the whole scheme
rests on is absent for the 115 files that already count as
reviewed.
5. A new audit check fails, with no issue filed.
review-scope-completeness landed upstream in d3244d6 on
2026-08-30 19:23 UTC and reports 44 tracked file(s) are out of
review scope only because no include pattern in
.vscode/review-scope.toml names them. No Consistency: Human
review scope completeness issue exists against kerbside, open or
closed; the daily run at 06:00 UTC on 2026-08-31 either predated
the check reaching the audit's repository list or has not yet
filed. Either way the failure is real and reproducible locally,
and waiting for the issue to appear before acting on it would be
superstition.
6. The audit is now 45 checks, and kerbside fails four. Run
locally against develop at bee839c: mermaid-lint-ci and
push-audit (both phase 5), review-coverage and
review-scope-completeness (both this phase). llm-context-lint-ci
passes, confirming phase 3 landed; llm-context-lint reports
not_applicable because skillsaw is not installed in the audit
environment, which is an environment property and not a kerbside
one.
7. The four files the sketch nominates for front-loading all
exist and are all genuinely unreviewed -- kerbside/api.py
(875 lines), kerbside/proxy_supervisor.py, kerbside/sf_token.py
and kerbside/sources/ovirt.py. That claim survived. So did the
sketch's central framing: this is a human grind, not an
implementation step.
The size of the grind, measured rather than estimated. The 77 unreviewed files total 19,948 lines. The 35 files decision 1 brings into scope add roughly 3,000 more, most of it small configuration and eight Jinja templates. Call it 23,000 lines of whole-file reading to get from 112 files needing review to fewer than 5.
Decisions¶
1. Settle the scope before starting the grind, in this phase.
review-scope-completeness gets folded into phase 4 rather than
becoming a phase of its own. The two checks are coupled by
design -- the upstream spec says so directly: "narrowing
include is the cheapest way to make a review-coverage issue
close, and until this check existed nothing noticed a repository
that reached full coverage by shrinking what counted." Doing the
grind first would mean reading 77 files, closing #227, then
adding 35 files to scope and reopening it. Doing scope first
costs one session and fixes the order.
This is the decision most likely to be argued with, because it widens the phase past the issue named in the master plan and makes the coverage number worse before it gets better: 77 files needing review becomes 112, and 192 in scope becomes 227. Both numbers are the honest ones. The alternative is a smaller number that means less.
2. Enumerate the file types; do not set include = []. The
spec offers include = [] -- every tracked file, lean entirely
on exclude -- and calls it reasonable for a small repository.
Kerbside is not one: 316 tracked files, a vendored sfui tree,
generated protobuf stubs, exported GitHub configuration, two
qcow2 fixtures and a Cargo.lock. The exclude list needed to
make include = [] behave would be longer than the include list
it replaced, and it would give up the property the new check
exists to provide -- that an unfamiliar file type fails loudly
rather than joining the queue in silence.
The candidate configuration, verified against the tree before
this plan was written, adds *.html, *.toml, *.ini, *.json,
*.conf, *.cfg, *.mako, *.svg, *.txt, *Dockerfile,
Makefile and */Makefile to include, names the three
extensionless files (etc/kerbside.conf.example,
demo/kerbside-demo-env, tools/run-tempest-tests) and the
ignore files, and adds five exclude entries with reasons:
.github/exported-config/* (written by a workflow),
rust/kerbside-proxy/Cargo.lock (generated), docs/schema.html
(generated), tests/fixtures/*.qcow2 (binary), and AUTHORS and
LICENSE (not authored here). That takes orphans to 0,
in-scope from 192 to 227, and drops nothing that is in
scope today.
The tests/fixtures/*.qcow2 spelling is deliberate. The obvious
tests/fixtures/* also swallows tests/fixtures/README.md,
which is prose somebody wrote and should be read.
3. The Jinja templates are in scope, and they are not
low-value filler. Eight files under kerbside/api/templates/
enter scope under *.html. Four of the repository's open
security issues concern the web surface those templates render:
319 (CSRF on a GET that mints a token), #132 (cleartext backend¶
credentials disclosed by an endpoint), #134 (/console/direct
leaking a hypervisor ticket) and #131 (forgeable JWT). Autoescape
behaviour and any |safe filter live in these files and nowhere
else. They are read in tranche 2, not left to the end.
4. Configure signing now; do not retroactively sign the
existing marks. Every mark from this phase forward is signed,
which is a git config away and is done in step 4b. The 115
existing marks stay as they are. Re-signing them would produce a
signature attesting that somebody read a file at a content
version, when what actually happened is that somebody read it and
the attestation was never captured -- and there is no way now to
tell the two apart from the outside. Re-reading 115 files to
attest them properly is not worth it against re-reading them on
their next natural staleness.
The gap is worth recording upstream rather than only here: the
review-coverage audit runs review-tracking.py status, which
reads the sidecar and never looks at whether the commit carrying
a mark was signed. A repository can therefore pass the audit
with no attestation at all, which is what kerbside has been
doing. Raising that with shakenfist/development is future
work, noted in the master plan, not this phase's job.
6. Signing configuration belongs in the review account's clone, not in a development clone. Review sessions run under a separate account, so that turning on commit signing does not mean signing every ordinary development commit. That makes the split structural rather than a matter of remembering, and it matches the upstream workflow's note that the dedicated review account's clones never carry development edits.
This phase got it wrong on the way through. Step 4b originally set
gpg.format, gpg.x509.program, commit.gpgsign and tag.gpgsign
in this clone, on the reading that .claude/CLAUDE.md's "signing is
per-clone config, so a fresh clone needs..." applied to whichever
clone was to hand. It applies to the clone that makes review marks.
The settings were reverted; a development clone should have none of
them.
The survey's finding stands unchanged and is worth restating so it is
not read as a consequence of this mix-up: every mark-adding commit on
develop is unsigned (742dd06, 08422d6, 7b67f12, bc30959), so
the 115 files that already count as reviewed carry no attestation.
That is a fact about the commits on the branch, whatever any clone's
configuration says, and it is why the Definition of done asks for
confirmation against a real commit rather than against a config file.
5. No agent pre-reads a file and hands the human a summary to mark against. The review mark asserts that a person read the file. An agent-written briefing note, however good, becomes the thing that gets read, and the mark then attests to the summary. The upstream workflow's line about "Claude Code in the integrated terminal for questions" is the supported use and the boundary: answering a question about a file the human is reading is fine; producing the reading is not.
This is the second decision a reader might argue with, since it declines the one available speed-up on a 23,000-line grind. The answer is that the speed-up would empty the marks of meaning, and the marks are the entire product.
Step plan¶
The agent-executable work is steps 4a to 4c. The reviewing itself is not a sub-agent step -- marks come from the weAudit VSCode extension, driven by a person -- so it is sequenced below the table under The review sessions.
| Step | Effort | Model | Isolation | Brief for sub-agent |
|---|---|---|---|---|
| 4a | high | opus | none | Rewrite .vscode/review-scope.toml in the phase 4 worktree so ./tools/review-tracking.sh scope-orphans reports zero orphans, and stop before committing -- this needs Michael's ratification (gate 1). The 44 orphans are listed by that command; run it first. Apply decision 2's candidate list: add *.html, *.toml, *.ini, *.json, *.conf, *.cfg, *.mako, *.svg, *.txt, *Dockerfile, Makefile, */Makefile, .dockerignore, .gitignore, *.gitignore to include, plus the three extensionless paths etc/kerbside.conf.example, demo/kerbside-demo-env and tools/run-tempest-tests. Add to exclude, each with a one-line reason in the comment block above: .github/exported-config/*, rust/kerbside-proxy/Cargo.lock, docs/schema.html, tests/fixtures/*.qcow2, AUTHORS, LICENSE. Use tests/fixtures/*.qcow2 and not tests/fixtures/*, which would also drop tests/fixtures/README.md. Follow the file's existing style: a prose comment block explaining the reasoning, then the two lists. Verify with scope-orphans (expect the "every tracked file is either in scope or explicitly excluded" line) and with status, and report both numbers: expect 227 in scope, 112 needing review. If either number differs, report it rather than adjusting patterns until it matches. Outcome: 227 in scope as predicted, but 104 needing review rather than 112 -- see the note below the tranche table. |
| 4b | low | sonnet | none | Nothing to do in this clone. See decision 6: review sessions run under a separate account in a separate clone, and the signing configuration belongs there. Do not set commit.gpgsign in a development clone. The step remains in the table because the phase must still confirm, before tranche 1, that the review clone signs -- git log --format='%h %G? %s' -1 after the first mark-adding commit, where N means it landed with no attestation. gitsign authenticates through an interactive Sigstore OIDC browser flow, so run gitsign-credential-cache & once in that clone rather than authenticating per commit. If the flow cannot complete, stop -- do not disable signing to get a mark through, because an unsigned mark is indistinguishable later from the 115 already in history. |
| 4c | medium | sonnet | none | Add the per-session recipe to docs/development.md, in the review tracking section that already documents tools/review-tracking.sh. It must cover: pull on a clean tree, then ./tools/review-tracking.sh prune; pick files from the current tranche rather than from next (which chooses at random and would scatter the order this plan sets); read and mark in weAudit; then ./tools/review-tracking.sh stamp, git add .vscode/*.weaudit* REVIEWS.md, and a signed commit. Give the one-liner that lists a tranche's outstanding files, so a session does not re-derive it: ./tools/review-tracking.sh status \| grep 'never reviewed' \| sed 's/.*: //' \| grep '^kerbside/' with the prefix swapped per tranche. State that the commit must be signed and how to check (git log --format='%h %G? %s' -1; N means it landed unsigned). Cross-link the upstream workflow doc rather than restating it -- docs/code-review-tracking.md in shakenfist/development is the authority. Keep to the repository's 80-column wrap. |
Each step is its own commit:
- 4a:
Name every tracked file in review scope. - 4b: no commit, and no change in this clone
- 4c:
Document the review session recipe.
The review sessions¶
Six tranches, ordered so the sessions most likely to find
something come first. Counts are from the survey and assume 4a
has landed; each session re-derives its own list with the
one-liner from step 4c, because the numbers move as marks land
and as prune-reviews runs.
| # | Tranche | Files | Why here |
|---|---|---|---|
| 1 | Application code: kerbside/*.py, kerbside/sources/, kerbside/rpc/ |
7 | api.py (875 lines), main.py (530), proxy_supervisor.py, sf_token.py, sources/ovirt.py, sources/static.py, rpc/contract.py. Every open security issue against this repository points somewhere in here. Highest yield per line, and the sketch already nominated four of the seven. |
| 2 | The web surface: kerbside/api/templates/, kerbside/rpc/kerbside.proto |
12 | New to scope under decision 3. Autoescape and \|safe live here; #319, #132 and #134 are all rendered by these files. Read immediately after the code that renders them, while it is fresh. |
| 3 | Configuration that is executable in practice | ~25 | The rest of what 4a brings in, plus .pre-commit-config.yaml and the two remaining workflows. .gitleaks.toml first: it decides what the credential scan catches, so an over-broad allow rule there is a hole that hides holes. |
| 4 | tools/ |
17 | Shell and Python that CI executes, several with repository write access. gitleaks-scan.sh, flake8wrap.sh and shellcheck-wrap.sh gate other checks; a bug in one of those is a silently disabled check, which is exactly what phase 2 found in flake8wrap.sh. |
| 5 | Prose: repository root, docs/, .claude/ |
~27 | AGENTS.md, ARCHITECTURE.md, README.md, PLAN-TEMPLATE.md, PUSH-AUDIT.md, RELEASE-SETUP.md, the docs/ guides and the skills. Drift here is real but it is not exploitable, and reading it after the code means the reader can tell when a document has gone stale. |
| 6 | docs/spice/ and the unit tests |
24 | 9 protocol documents (channel-protocols.md alone is 952 lines) and 15 test modules. The protocol documents have never been read end to end and that is worth fixing, but they describe an external protocol rather than this repository's decisions. The tests are read constantly in the course of other work. Last, honestly. |
The backlog came out at 104, not the 112 this plan predicted, and
the eight are already read. The projection assumed every file
entering scope was unreviewed. Eight were not: demo/Dockerfile,
demo/kerbside.ini, demo/kerbside-demo-env,
demo/spice-target/Dockerfile, etc/kerbside.conf.example,
loadtests/latency/Dockerfile, rust/kerbside-proxy/Dockerfile and
tools/run-tempest-tests all carry stamps dated 2026-08-06, 08-08 or
08-18. They were read and marked in weAudit during earlier sessions
while out of scope, the stamps persisted in the sidecar, and
widening include made those reviews count. Tranche 3 is smaller by
seven and tranche 4 by one as a result.
This is worth knowing beyond the arithmetic: weAudit will mark any file, in scope or not, and the sidecar keeps the stamp. So a reviewer who reads something the scope config does not cover is not wasting the effort -- it banks against the day the file comes into scope.
The backlog is still above the threshold at the end of tranche 5, with the 24 files of tranche 6 outstanding, so tranche 6 is what closes #227 -- there is no short cut that skips it. At a realistic 800 to 1,200 lines of genuine whole-file reading per hour, 23,000 lines is eight to twelve two-hour sessions. That is the number to plan against; a phase that pretends it is three will stall and look stuck.
Risks and mitigations¶
| Risk | Mitigation |
|---|---|
Scope is widened to satisfy review-scope-completeness and the review-coverage number gets worse, so the next reader thinks phase 4 went backwards. |
Stated in decision 1 and carried into the master plan and the index row as part of the planning commit, with both before and after numbers. The Definition of done names 227 in scope and 112 needing review as the expected post-4a state, so hitting it reads as success rather than regression. |
The grind stalls after two sessions and the phase sits In progress for months with no way to tell progress from abandonment. |
The tranche table is the progress meter: status filtered by tranche prefix says exactly where the phase is, and step 4c writes that one-liner into docs/development.md so any session can answer the question in one command. A stalled phase is a legitimate outcome to report; an unmeasurable one is not. |
| A session marks files reviewed on a tree with uncommitted edits, so the mark attests to content that was never committed. | The upstream workflow's first rule, restated in 4c: mark only on a clean tree. prune catches it after the fact -- the blob SHA will not match at HEAD -- but after the fact means the reading has to be redone. |
| gitsign cannot complete its browser flow in a headless or remote session, and signing gets quietly turned off to unblock a commit. | 4b makes this a stop-and-report condition rather than a judgement call, but only for the commits it applies to. An unsigned mark is worse than a delayed one: it is indistinguishable, later, from the 115 already in history. |
Signing configuration is set in a development clone, where commit.gpgsign true makes git attempt to sign every ordinary commit and ordinary work blocks on a Sigstore login for no benefit. |
Decision 6. The configuration belongs in the review account's clone only. This phase set it here during implementation and reverted it; the four settings are unset in both local and global scope, which is the state a development clone should be in. |
| An agent is asked to "help with" a review session and ends up producing the reading the mark attests to. | Decision 5, and the step table stops at 4c for exactly this reason. There is no sub-agent step in this phase that reads a file under review. |
review-scope-completeness files an issue against kerbside between now and 4a landing, and it looks like the phase missed it. |
Expected, not a problem. The check fails today and the issue is overdue; when it appears it is the same finding this phase is already fixing, and it closes on the audit run after 4a merges. |
Definition of done¶
-
./tools/review-tracking.sh scope-orphansprints every tracked file is either in scope or explicitly excluded and exits zero. -
./tools/review-tracking.sh statusreports 227 in-scope files immediately after 4a. The backlog came out at 104, not the predicted 112, because eight of the files entering scope already carried stamps from earlier sessions; the discrepancy is explained under the tranche table rather than tuned away. - Every
excludeentry added by 4a has a reason in the comment block that says what the file is, not merely that it is excluded. -
tests/fixtures/README.mdis still in scope after 4a. - The review account's clone signs, confirmed against the first mark-adding commit rather than against its config. No signing configuration is set in a development clone; the 4a and 4c commits are correctly unsigned, adding no review mark.
-
docs/development.mdgives a session recipe that a reader can follow without opening the upstream document, and the tranche one-liner in it runs and produces file paths. -
pre-commit run --all-filesis clean. - The audit's
review-scope-completenesscheck passes for kerbside. -
review-coveragereports fewer than 5 files needing review and issue #227 closes from a passing audit run.
The last two are on different clocks. review-scope-completeness
passes as soon as 4a merges. review-coverage needs the tranches
done, so this phase stays In progress across many sessions by
design -- which is what the phase is, not a sign it is stuck.
Back brief¶
The thing to notice about this phase is that its measured numbers get worse first. 4a takes the backlog from 77 to 112 and that is the correct outcome; a phase report that leads with "112 files need review" without leading with why is going to read as a failure.
Two gates:
Gate 1, before 4a is committed. The scope config decides what
is subject to human review for the life of the repository, and
the argument for including a file type is easier to make now than
to revisit later. Show the proposed include and exclude lists
with the reason for each addition and the resulting numbers, and
wait. In particular the three that are genuine judgement calls
rather than bookkeeping: the Jinja templates and SVG icons
(in, decision 3), the ignore files (.dockerignore decides what
ships in an image, so in), and AUTHORS/LICENSE (out, not
authored here).
Gate 2, after the first review session. One session against tranche 1 is enough to calibrate the 800-1,200 lines per hour estimate the tranche table is built on. If the real rate is half that, the plan is a twenty-session plan and the tranche boundaries should move before another ten sessions are spent against the wrong shape. Report the actual rate, not a reassurance.