feat(lenses): the commit-truth lens, repo-facts, as Gate 4

Every claim in every commit message, and in the documentation the range
adds or changes, proven or gone. The lens's block kinds are text, doc and
slot: a message unit is keyed on its text, the base and the code up to its
commit (MessageDeps); a documentation unit on its file, its normalised
text, the base and the code of the whole range (DocKeyParts); a diffstat on
its text alone; whole:order and whole:absent are asked in slot passes of
its own, never stored. A documentation file's diff header and a part the
commit writes nothing in are a mechanical n/a. Budgets 60/40 for a text or
doc block, 50/35 for a slot; one read. Gate 4, after the proofs, so the
messages and documents are read once the code they describe has settled;
the owner's gate is 5.

The goldens that follow from the lens table are derived by
tests/Goldens/derive.py, which asserts that the recorded table still
reproduces the recorded entries before it writes.

Proven, with the opt-in commit after this one: the suite (26 passed); a dry
run of a scratch range (2 commits, a README with sections) by the previous
binary and this one with the three static lenses on and the rest off, 25
prompts and 4 system files byte for byte; with repo-facts on as well,
those files unchanged and 10 passes and 1 system file added.
The lens's firing proof in this pipeline is owed (the owner's go).
This commit is contained in:
Jorijn van der Graaf 2026-10-31 20:08:26 +01:00
commit febbfc3fcc
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
19 changed files with 2564 additions and 646 deletions

View file

@ -0,0 +1,151 @@
# Lens: repo-facts
You are an adversarial fact-checker. Your job is to DISPROVE every claim made
in the commit messages of the range under review, and in the documentation
the range adds or changes (README, AGENTS.md: the units of a `.md` file). A
claim you cannot verify against evidence is treated as false until proven.
These commits go into a public repository's permanent history and into a
release users install; its documentation is what they read.
Inputs: the frozen range, the repository at the range HEAD and at the base,
its history (`git -C <tree> log`), and the evidence paths INPUTS names (the
proof artifacts for anything tested on a device or seen on a network).
## Method
1. **Enumerate every factual claim** in each commit message — behavior
claims ("X now does Y", "fixes Z"), claims about the previous behavior,
claims about users, devices, carriers, networks or field observations,
"tested" claims, version and changelog statements, references to specs,
tickets, other projects or other people's work. For each: find the proof
in the diff, the tree, the history, or a capture — or flag it.
2. **Code claims are verified against the diff and the tree at HEAD** —
what the message says the change does must be what the change does, and
what it says about the state before must hold at the base
(`git -C <tree> show <base>:<path>`). A message that describes the
intended behavior rather than the implemented one is a finding.
3. **"Tested" claims trace to a capture** naming what ran, where, and on
which build. When INPUTS names no evidence path, a tested claim that the
diff and tree cannot substantiate is unverifiable — a finding whose cure
is the capture or the deletion of the claim.
4. **Overclaims are cured by TESTING, not rewording.** If a claim is
stronger than the evidence, the verdict is "test it or delete it" — never
"soften the wording".
5. **"Doesn't affect X" / "no user-visible change" requires 100% hard
proof** — enumerate every user of the touched path in the repository and
show each is unaffected, or the claim goes.
6. **Negative existence claims** ("nothing else uses this", "no caller
passes NULL", "there is no test for this", "the first X to …") are
auditable only against a search you ran: cite the grep and its result in
the basis.
7. **Evidence quality rules**: exact-event match, never substring
pattern-match; levels-vs-baseline, never mere presence; an unexplained
nonzero baseline is evidence, not noise — attribute it before relying on
any "nothing happens" claim.
8. **A test of a tie-break, fallback, or "prefer X" branch must run in a
state that FORCES that branch to decide the outcome.** "The test passed"
and "the new code ran" are different claims. Name the state that forces
it, and show the evidence came from that state.
9. **References** to a spec, an RFC, a ticket, an upstream commit or another
project: verify that what is on disk (the repository, its installed or
vendored dependencies, the evidence paths) says what the message says it
says; a document not on disk makes the claim unverifiable — a note naming
what would settle it, never an assumed pass.
10. **History claims** ("since 0.3.4", "introduced by <sha>", "reverts",
"the previous commit"): archaeology in the repository's own history,
cited by sha; a message in commit K that describes what commit K+1 does
is a finding on both.
11. **Documentation the range adds or changes** — a unit of a `.md` file: a
diff hunk, or one heading-delimited part of it — is read like a message,
by the rules above, with three differences. (a) The claims are the lines
the commit adds. A context line is context and a removed line claims
nothing — except a context sentence that this commit's own change makes
false, which is a finding. (b) A sentence must hold for the repository
as ITS commit leaves it (`git -C <tree> show <sha>:<path>`, the sha on
that patch's `From` line): a later commit that makes it true does not
cure it. Where the document at HEAD still carries the sentence it must
hold at HEAD too: a later commit of the range that makes it false
without updating it is a finding on the unit that wrote it. (c) An
instruction — a command, a path, a variable, an option, a reference to a
section — is a claim that it exists and works as written: check the
file, the flag, the unit, the target section in the tree at that commit;
what only a run on a device or a network can settle traces to a capture,
or is unverifiable. This lens checks what a document says, not what it
could also say: an omission is a finding only where the text claims
completeness ("every", "all", "only", "never", a list given as the
list). Documentation the range did not touch and its code makes false is
the `whole:absent` slot's.
## Verdict
Per `prepush.md` §Verdicts — objective lens: no materiality defence. Any
unproven or disproven claim is FIX FIRST whoever would notice — and the cure
is evidence (a test, a capture, a read of the referenced code) or deletion of
the claim, never a hedge word. Nothing below FIX FIRST is a finding;
record-worthy observations go in notes. Never grade a finding.
## Calibration (append dated entries at the bottom)
- Seeded 2026-09-25 from the earlier facts lens: the 2026-07-20
overclaim rule (Method 4), the 2026-07-24 forced-branch rule (Method 8),
the 2026-08-30 captured-search rule for negative existence claims (Method
6), and the 2026-09-16 coverage contract: when the prompt carries an
INVENTORY (a mechanical partition: every non-blank line in exactly one
unit, fixed `whole:*`/`pK:outside`/`pK:absent` slots for what is absent),
return one `{item, verdict, basis}` per unit AFTER the review: `clear`
only when every claim in the unit was checked at its source (basis = the
tree ref, the command run, the capture path) — a plausible reading is not
a check; `defect` with a finding carrying `items:[id]`; `immaterial` with
a note carrying `items:[id]`; `n/a` with the reason (never a paragraph of
claims). A `defect` without a finding is turned into an undischarged
finding; a missing unit makes the run INCOMPLETE.
- 2026-09-25 (firing proof at creation): a fixture repository with a
planted range - commit 1 adds `median()` whose message claims an
empty-list guard and the mean of the two middle values that the diff does
not implement, commit 2 "fix the median crash on an empty list" adds the
guard - and a corrected single-commit range. Single-unit `--only`/`--units`
runs: block pass on `p1:msg` -> FIX FIRST with 3 findings: the even-count
claim disproven against the diff, the guard claim disproven against
`git show <sha>:stats.py` of that commit (and the next commit's message
conceding it - cited on both units), the "as the other helpers document"
reference unsupported (no docstring, README silent); the corrected message
-> SEND-READY, `clear`, with an `info` note that no tested/field claim
existed to trace. 7 / 7 turns, ~$0.45 / $0.25. No denied call.
- 2026-10-06 (documentation is this lens's since driver 2026-10-06.3 -
Method 11; firing proof with this charter): a fixture repository, two
commits. Planted: commit 1's README says "`spread(xs)` returns the largest
value minus the smallest" while spread() arrives in commit 2; commit 2's
README says the three functions raise `TypeError` (the code raises
ValueError); commit 2's message says "the test suite covers an unsorted
sequence" (it does not). Single-unit `--only`/`--units` run on
`p1:README.md#1.2`, `p2:README.md#1.2`, `p2:msg` -> FIX FIRST, three
findings: the first disproven at commit 1's own sha (`git show
<sha>:stats.py` has no spread; `git grep spread <sha>` hits only the
README line) and cited on `whole:order` too, with an `info` note that the
sentence is accurate at HEAD and only its placement is wrong; the second
against `_require` and the tests of the same commit; the third against
the two spread tests. The corrected range, units `p2:README.md#1.1`,
`#1.2`, `p2:msg` -> SEND-READY, `clear` x3; that the README's list leaves
out `is_finite()` came back as a `pre-existing` note, not a finding (the
list does not claim to be complete). Opus 5.5 xhigh, 10 / 17 turns, $0.49
/ $0.64, under a minute each. No denied call.
- 2026-10-06 (Method 11(b) reworded and the `whole:absent` duty widened
after the soundness review of the same day - driver 2026-10-06.4;
firing proof): the same fixture repository; commit 1's README says
median() returns "the mean of the two middle values for an even count"
(true there), commit 2 makes median() return the lower middle value and
touches no documentation. Run on `p1:README.md#1` and `whole:absent` ->
FIX FIRST: the sentence flagged on the unit that wrote it ("true at the
commit that adds it but false at HEAD ... does not update README.md"),
the slot flagged the stale README and that spread(), which the range
adds, is missing from the list the range itself keeps. With the sentence
updated in commit 2, `p1:README.md#1`, `p2:README.md#1`, `whole:absent`
-> SEND-READY: "replaced the sentence, so it does not carry to HEAD"; the
list's old gap (is_finite) again a `pre-existing` note. Opus 5.5 xhigh,
13 / 14 turns, $0.51 / $0.72. No denied call.
- 2026-10-31 — carried into this pipeline with the runtime examples made
generic (the evidence paths INPUTS names stand for a journal's ledger and
captures; a device for a phone); the rules are the same. Here it is Gate 4, after the
proofs, so the messages and documents are read once the code they
describe has settled, and it is off until a repository's config turns it
on. Its firing proof in this pipeline is still owed (the owner's go).

View file

@ -3,9 +3,10 @@
Scope: every push of a commit range from a repository set up for this
pipeline (`crafter-review init`) — a release channel with real users, where a push (or
the pull request it opens) is the next release. The `/prepush` skill drives
this file. The mechanics: freeze → hash → gates → the owner; four lenses,
this file. The mechanics: freeze → hash → gates → the owner; five lenses,
the functional one as Gate 2 (Jorijn, 2026-10-15), the security lens beside
the bug-prover in Gate 3 (2026-10-22).
the bug-prover in Gate 3 (2026-10-22), the commit-truth lens as Gate 4
(2026-10-31).
Nothing is pushed without the owner's explicit approval of the exact frozen
hash. Local commits parked unpushed are fine and normal.
@ -15,7 +16,7 @@ Terms: *the pipeline* is crafter-review, the Flatpak
`flatpak run net.catcrafts.CrafterReview <command>`, because the Bash tool sees no
alias (a native build is run by its path instead). *The repository* is the
consumer checkout the driver runs in; *the owner* is whoever approves the
hash at Gate 4.
hash at Gate 5.
## Inputs — what the lenses see, and nothing else
@ -231,7 +232,7 @@ therefore the hash: re-freeze with `--replace` into the same dir
the new HEAD before the rerun. A fix during a run happens in another
worktree, never in the tree the lenses read.
## Gates 1–3 (the driver) and Gate 4 (the owner)
## Gates 1–4 (the driver) and Gate 5 (the owner)
| Gate | Lens | Question |
|---|---|---|
@ -239,7 +240,8 @@ therefore the hash: re-freeze with `--replace` into the same dir
| 2 Functional | `repo-functional` (whole pass gating code blocks; the `browser` or `browser+supabase` environment; absent when the config turns it off) | in the running application, does the range do what its code and messages say, and does it leave the flows next to it working? |
| 3 Proof | `repo-bug-prover` (code blocks, static; with the `supabase` environment a proof may also be run in the database) | a demonstrated functional defect in the range's code |
| 3 Proof | `repo-security` (code blocks, static, beside the bug-prover; with the `supabase` or a browser environment a demonstration may also be run in the database or the application) | a demonstrated gap in a protection the repository promises: a check, policy or grant the range's code lacks, weakens or removes |
| 4 | the owner | approves this exact hash, then the push happens |
| 4 Commit truth | `repo-facts` (message blocks, documentation blocks and the `whole:order` / `whole:absent` slots, static) | every claim in every commit message, and in the documentation the range adds or changes, proven or gone |
| 5 | the owner | approves this exact hash, then the push happens |
Every lens runs on Opus 5.5 at effort `xhigh` (`--effort` overrides).
There is no width of the machine by default (2026-10-31, Jorijn: "there is
@ -299,15 +301,17 @@ passes; coverage is fail-closed per inventory unit (`inventory commits`:
outputs after a kill, budgets, `--only` / `--units` test-only runs (PARTIAL,
never a pass), the dump at `gate-state/commits-<hash8>-run-<n>.json`.
**Who reads what.** The four lenses read code: hunks, file headers and (the
static lenses) the per-commit slots `pK:outside` and `pK:absent`. The
text units — subjects, messages, diffstats — and the units of documentation
files (`*.md`) are read by no lens of this pipeline (the facts lens was
removed, 2026-10-15); the dump lists them under `coverage.notReviewed` and
the summary says so. A lens the config turns off (`lenses.<name>.run =
false`) reads nothing: the units only it would have read (its whole slot —
`whole:behaviour` for the functional lens) are listed there too, never as
holes. The functional lens plans no pass on a diff header, on a
**Who reads what.** The four code lenses read code: hunks, file headers and
(the static lenses) the per-commit slots `pK:outside` and `pK:absent`. The
commit-truth lens reads the prose: the text units — subjects, messages,
diffstats — and the units of documentation files (`*.md`), each
heading-delimited part of a hunk on its own, and it asks `whole:order` and
`whole:absent` in slot passes of its own; a documentation file's diff header
and a part the commit writes nothing in are a mechanical `n/a`. A lens the
config turns off (`lenses.<name>.run = false`) reads nothing: the units only it would have read (its whole slot —
`whole:behaviour` for the functional lens; the prose for the commit-truth
lens) are listed under `coverage.notReviewed`, never as holes, and the
summary says so. The functional lens plans no pass on a diff header, on a
unit of a file matching `functional.exclude`, or on the per-commit slots (its
whole pass judges adjacent flows and missing deliveries range-wide): each is
a mechanical `n/a` with its basis.
@ -334,6 +338,16 @@ the functional lens):
range changed, or when a documentation file of their commit is added,
deleted or renamed (`absent` also when the commit gains or loses its body).
The commit's position is in the key;
- a message (`pK:subject`, `pK:msg`), for the commit-truth lens: its own
text, the base and the code of the range up to and including its commit —
a fix to the code a message describes asks the message again; a diffstat
its own text alone;
- a unit of a documentation file, for the commit-truth lens: its file and
its own text (normalised like a hunk), the base and the code of the whole
range — a sentence must hold for the code as its commit leaves it and as
the range leaves it;
- the commit-truth lens's slot passes (`whole:order`, `whole:absent`): never
stored, bought every run;
- both whole passes: the code of every commit of the range in order, which
commits have a body and which documentation files each touches. No message
text and no documentation text. A whole pass that returned a finding is
@ -371,7 +385,7 @@ from the previous rounds get run again until they reach x"; x is 3
again after a finished run is the next round.
- All gates clean with reads still owed is `CONFIRMING — NOT SEND-READY`
(exit 2); run the same line again, on the same hash, until it says
SEND-READY. Gate 4 is asked for a hash only then.
SEND-READY. Gate 5 is asked for a hash only then.
- Every finding in the dump carries `read`, the number of the read that
found it.
@ -401,7 +415,7 @@ Each entry is dated, names what it `supersedes`, binds to a hash, records
grounds, and lists the artifact `anchors` its grounds depend on with the
count of occurrences at binding. The register is
`<out>/gate-state/dispositions.json` — the authority and the record; the
manifest's rounds table names each discharge with its grounds for Gate 4.
manifest's rounds table names each discharge with its grounds for Gate 5.
Carry across a hash change
is mechanical: an entry is in force when bound to the current hash, carried
when `carry` is true and every anchor still occurs its recorded count of
@ -419,7 +433,7 @@ the push, the rest are banked per kind.
A new disposition in force re-keys every unit, so a waiver costs every static
code unit its three reads again.
## Gate 4 — the owner, then the push
## Gate 5 — the owner, then the push
Present the frozen patches themselves (never summaries), the rounds table,
every discharged finding with its grounds, every contest, every `unverified`
@ -592,3 +606,6 @@ repository.
5-hour column. Then (Jorijn: "there is no need to do a global cap anymore,
the cap should be 128 per available account") the machine-wide width
went: `--concurrency` caps only when given.
- 2026-10-31 — **the commit-truth lens as Gate 4** (Jorijn: "fix that in
Crafter.Review"). The owner's gate is 5. Its firing proof in this pipeline
is owed (the owner's go).

View file

@ -1,6 +1,6 @@
---
name: prepush
description: Pre-push gates for a repository set up for crafter-review (`crafter-review init`). Runs the pipeline's procedure end to end — Gate 0 build/tests + freeze (identity, trailers by the config's rule, clean tree, no merges, fast-forward), the application and the database under test brought up from the frozen tree, the driver run of the lenses the config enables (repo-better-solution → repo-functional with the Playwright MCP → repo-bug-prover and repo-security side by side) via crafter-review, the fix/rerun loop, the owner's approval of the hash, verify, push. Nothing is pushed unreviewed or unapproved.
description: Pre-push gates for a repository set up for crafter-review (`crafter-review init`). Runs the pipeline's procedure end to end — Gate 0 build/tests + freeze (identity, trailers by the config's rule, clean tree, no merges, fast-forward), the application and the database under test brought up from the frozen tree, the driver run of the lenses the config enables (repo-better-solution → repo-functional with the Playwright MCP → repo-bug-prover and repo-security side by side → repo-facts, the commit truth) via crafter-review, the fix/rerun loop, the owner's approval of the hash, verify, push. Nothing is pushed unreviewed or unapproved.
---
Follow the pipeline's procedure, `prepush.md`, EXACTLY — read it now, in
@ -46,7 +46,7 @@ of running it:
`supabase.dbUrl` does not answer or its applied migrations are not
HEAD's: reset the stack from the frozen tree, after every
`freeze --replace` too. Do not edit that tree while the run is on.
4. **Gates 1–3**: run the printed command from the repository root in a
4. **Gates 1–4**: run the printed command from the repository root in a
detached background shell (`setsid nohup … > <log> 2>&1 < /dev/null &`);
poll `~/.cache/review-gates/<hash8>-run-<n>/progress.json` and `log.txt`.
Add `--context-paths` for the evidence dirs the messages' claims rest on.
@ -68,11 +68,11 @@ of running it:
`grounds`, `carry`). A `contests` entry is a lens claim: re-check its cited
evidence first; a surviving contest on an owner entry goes to the owner.
Notes never block — bank each per its kind; an `unverified` note names a
manual check the owner sees at Gate 4. INCOMPLETE is never a pass: rerun.
manual check the owner sees at Gate 5. INCOMPLETE is never a pass: rerun.
6. **Rounds** go in `.review-gates/push/<slug>/manifest.md`
(`flatpak run net.catcrafts.CrafterReview doc manifest` prints the
template; test runs with `--only`/`--units` never).
7. **Gate 4 — the owner**: present the frozen patches themselves, the rounds
7. **Gate 5 — the owner**: present the frozen patches themselves, the rounds
table, the discharged findings with grounds, every contest, all notes —
then STOP. Nothing is pushed until they explicitly approve that exact hash.
8. **Push**: `flatpak run net.catcrafts.CrafterReview freeze --slug <slug>

View file

@ -26,6 +26,10 @@
"repo-security": {
"run": true,
"environment": "none"
},
"repo-facts": {
"run": true,
"environment": "none"
}
},
"supabase": {

View file

@ -25,5 +25,5 @@ repository's rule).
-
approved: <date> @ <hash> (the owner, Gate 4 — saw the frozen patches, the discharged-findings list, contests and notes)
approved: <date> @ <hash> (the owner, Gate 5 — saw the frozen patches, the discharged-findings list, contests and notes)
pushed: <date> <remote>/<branch> @ <sha> (PR: <url>)