Freshness and adoption#
What makes a review stale#
A review is current only while its recorded subject still matches what the rule asks you to review. It becomes stale when:
- the file's bytes change;
- the rule's definition changes, including its level, description, review guidance or declared context;
- a file in the declared context is edited, added or removed, or the context patterns change.
Reverting a file to the exact bytes it had at review restores that part of the subject. Rewording a rule invalidates claims for that rule; TOML formatting and comments do not. Context covers only the supporting files you declare. snob does no import or dependency analysis, so choose context the review actually depends on rather than making unrelated changes reopen the review. See policy files.
See what needs reviewing again#
Ask for the worklist around the change:
$ snob pending src/routes/admin.ts
Each result shows the rule's description, review guidance and expected subject. A stale result lists the differences from the recorded subject: for example, changed file content, a changed rule definition or changed context. Use those reasons to find the relevant source changes, then review them against the requirement.
After the review, record a fresh claim for the subject pending shows
and update the review reference. snob cannot create claims yet; the
local claim format explains how to write one.
Pointing a review entry at a claim for another file, rule or older content
does not rebind it.
Incremental adoption#
Choose an adoption commit to start checking an existing codebase without first reviewing every unchanged file:
# snob.toml
[ratchet]
since = "0123456789abcdef0123456789abcdef01234567" # full commit id
snob works out the obligations as they were at since, from that commit's
own policies, review-file context lists, files and snob.toml layout.
A current obligation without a satisfied claim is exempt only if its
expected subject is identical to the one at since. Everything else needs a
claim:
- new files, and files newly matched because a
matchlist grew; - edited files, and files whose context files changed;
- new policies and rules, and any change to a rule's description, review text, level or context globs.
Exempt obligations pass, are counted in the summary, and are listed by
pending as EXEMPT. A satisfied claim still wins, so the debt can be paid
off one review at a time. --strict ignores [ratchet], and without a
[ratchet] section every obligation needs a claim.
Choices to know about:
- Exemption depends only on equality with the snapshot at
since. Reverting a file to exactly its content atsincerestores its exemption. - Claims that existed at
sinceare not reconstructed, only obligations. - History is read with
git rev-parse,ls-treeandcat-file, which run no hooks or filters. The oldsnob.tomlis read for layout only; its provider commands are never run. - The snapshot uses the files tracked at
since. Committed blobs are hashed as stored, so line-ending conversion can prevent an exemption, never create one.
An unknown or unfetched commit, a project outside git, or malformed files at
since stop the check with exit 2; snob never falls back to strict or lenient
checking on its own. since must be a full 40- or 64-character commit id.
CI and the trust boundary#
A change under review must not be able to weaken the rules it is judged by,
move since, or make files disappear from the check. In CI, judge the
checkout against the target branch:
$ git fetch origin main "$ADOPTION_SHA" # shallow clones need both
$ snob check --trusted-rev origin/main
With --trusted-rev, everything trusted is read from git objects at that
revision, and nothing from it is executed except the provider commands its
own snob.toml configures:
- The base's
snob.tomlis the whole configuration:[paths],ignore,[ratchet]and providers. A change cannot ignore its own files, movesinceor swap in a provider. A base withoutsnob.tomlmeans defaults, which means strict checking. - The base's policies keep applying alongside the checkout's.
A rule the change removes or weakens still applies in its base form. A
rule both define identically is one obligation; a rule id the change
redefines is two, both of which need a satisfied claim. A rule the change
adds applies before merge, so policy edits are reviewed under the rules
they create. Reports mark each rule's
origin:both,trusted(only the base has it) orcheckout(proposed by the change). - A file's context patterns are those of its base review file plus its checkout review file, so editing or deleting a review file drops claims but never context the base requires.
- A file that was a candidate at the base and no longer exists is reported
as deleted, with one obligation per base rule that
matched it. Policy files and
snob.tomlare candidates too, so removing them is covered. A rename is a deletion plus an addition.
A file the change merely stops tracking or starts ignoring, but that is still there, is not deleted: it stays a candidate. Only absence counts.
An unknown or unfetched revision, a repository snob cannot read, or a
malformed snob.toml, policy or review file at the base stops the check
with exit 2. snob never falls back to the checkout's own configuration.
--config PATH reads snob.toml from a file outside the checkout but still
takes policies from the checkout; it protects the configuration only.
Subjects#
Every rule on every file has an expected subject, shown by snob pending
and in JSON reports:
{
"path": "src/routes/admin.ts",
"content_hash": "blake3:…",
"rule": "routes/no-privilege-escalation",
"rule_fingerprint": "blake3:…",
"context": { "patterns": ["src/auth/**"], "digest": "blake3:…" }
}
content_hashis a BLAKE3 hash of the file's bytes. Identity is content, never modification time, so reverting an edit makes an old claim fresh again. Line-ending conversion changes the hash.rule_fingerprintcovers the rule definition (configuration.md).contextappears when the rule, its policy or the file's review file declares context globs.digestcovers the sorted(path, content_hash)pairs of the candidate files they match, excluding the file itself.deleted: trueappears only on the subject of a deleted file's obligation (below).
A satisfied claim counts only when its subject equals the expected subject
field for field; otherwise it is stale, with a reason for each
difference. A claim without a subject is stale. Pointing a review entry at
an existing claim for another file, rule or older content does not rebind
it: the claim keeps the subject its reviewer recorded.
Reviewing a deletion#
A deletion obligation's subject has "deleted": true. Its content_hash is
the hash of the file as committed at the base. Its context patterns are
the base rule's plus the review files'. Its digest covers the matching
files as they are after the change, so deleted context files simply drop
out of it. A claim on that subject reviews the removal in the resulting
tree: what the rule required of the deleted file, and how the rest of the
tree stands without it. A claim about the file's content never approves its
deletion, and the reverse holds too. The claim goes stale if the base
content, the rule or the remaining context changes.
The deleted file's review file lists the deletion claims. After merge, the base no longer has the file and the obligations end. Mark the review file as a record, or delete it:
# snob/reviews/src/routes/users.ts.review.toml
deleted = true
[claims]
"routes/no-privilege-escalation" = "local:users-esc-deleted"
deleted = true stops a review file without a source file from being an
error, and nothing more:
- its claims are not looked up, so a provider that has since gone away does not break later checks;
- it satisfies no obligation, so a deletion still under review needs its claims, as above;
- it is an error while the file exists, including after recreating the path, and on a file that is ignored but present;
- a review file without the marker and without a source file, such as a typo, is an error that suggests the marker. A marker on a misspelt path hides only that review file; the real file's obligations are unaffected.