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 match list 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 at since restores its exemption.
  • Claims that existed at since are not reconstructed, only obligations.
  • History is read with git rev-parse, ls-tree and cat-file, which run no hooks or filters. The old snob.toml is 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.toml is the whole configuration: [paths], ignore, [ratchet] and providers. A change cannot ignore its own files, move since or swap in a provider. A base without snob.toml means 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) or checkout (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.toml are 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_hash is 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_fingerprint covers the rule definition (configuration.md).
  • context appears when the rule, its policy or the file's review file declares context globs. digest covers the sorted (path, content_hash) pairs of the candidate files they match, excluding the file itself.
  • deleted: true appears 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.