feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
# Pre-push procedure
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
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).
2026-10-31 20:08:26 +01:00
this file. The mechanics: freeze → hash → gates → the owner; five lenses,
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
the functional one as Gate 2 (Jorijn, 2026-10-15), the security lens beside
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).
2026-10-31 20:08:26 +01:00
the bug-prover in Gate 3 (2026-10-22), the commit-truth lens as Gate 4
2026-10-31 20:09:07 +01:00
(2026-10-31), every one of them off until the repository's config turns it
on (2026-10-31).
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
Nothing is pushed without the owner's explicit approval of the exact frozen
hash. Local commits parked unpushed are fine and normal.
Terms: * the pipeline * is crafter-review, the Flatpak
`net.catcrafts.CrafterReview` ; every command below is spelled
`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
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).
2026-10-31 20:08:26 +01:00
hash at Gate 5.
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
## Inputs — what the lenses see, and nothing else
- The frozen artifact: `.review-gates/push/<slug>/*.patch` — `git
format-patch --base=<base> <base>..HEAD` of the repository, cut by
`freeze` , inlined into every prompt.
- The repository at the range HEAD: the checkout itself (`--trees .` ; the
driver verifies HEAD and a clean tree before every launch) or a detached
worktree under `.review-gates/trees/<repo>-<hash8>/` (`freeze --worktree` ;
feat(run): the repository under review is the freeze record's, not the working directory's
A directory can review repositories other than itself: a workbench whose
config, env file and records serve several, freezing each with --repo and
--out. The run took the working directory for the repository under review:
the prompt named it, the calibration was kept under it, and --trees fell
back to it. The run now reads the repository from the artifact's
freeze.json - the prompt names it and the calibration is kept under it -
and --trees defaults to what the record says: the worktree, else the
repository when it is not the working directory, else `.`, the same rule
that builds the printed run line. The working directory keeps its roles:
the config, the env file, the slots, the lens processes' cwd and every
logged path. A damaged record stops the run at preflight.
2026-10-31 21:31:48 +01:00
gitignored; `--trees` defaults to what the freeze record says, so a
workbench that froze another repository with `--repo` reads its worktree)
— content, history and blame; the state before the range via
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
`git show <base>:<path>` .
- `--context-paths` for the evidence dirs the commit messages' claims rest on.
feat(config): lens sections - a repository's own text appended to a shipped charter
A lens has one charter, the one this pipeline ships; what a repository
knows that is not general to the lens - its platform and layers, the
record it keeps its decisions in, what its users already have, the
calibration entries that are its own - goes in a file the config names
for the lens (lenses.<name>.section), and Charter appends it after one
blank line. The charter sha in every reuse key covers the combined text.
The config refuses a path outside the repository - absolute, or
climbing out with `..` - by the check slots.dir had, now one both
settings share; a file missing or one the run could not use (a dangling
link, unreadable, not UTF-8) is a problem too, checked through the
reader Charter uses (Prompt::ReadSection), so a section is never
dropped in silence, and a name that is no lens is the unknown key it
always was. Nothing lists a directory, so what lies beside the files is
no concern of the config's: a directory listed would have to tolerate or
refuse every foreign entry. The run logs which charters got one.
The better-solution charter gains a consumer's general entry
(2026-10-31: a mechanism reworked a second time in one submission is the
signal to derive the fix at every layer the data crosses). The procedure
names the section wherever it names the charter: among the config's keys,
in the lens inputs, in the reuse rule (an edit to either re-runs the lens)
and in the feedback loop, which routes a repository's own lesson to its
section and a general one to the shipped charter.
2026-11-01 01:30:22 +01:00
- The lens charter (`process/lenses/repo-*.md` , with the repository's own
section, `lenses.<name>.section` , appended; system text
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
`_common-repo.md` ) and the dispositions in force.
- A lens with the `browser` or `browser+supabase` environment (the
functional lens by default; `lenses.<name>.environment` in the config):
the running application (`.review-gates/config.json` `functional.*` : its
URL, the local origins, the login fixture, the owner's notes), through the
Playwright MCP server the driver starts for each of its passes, and the
exact one-time-code command the prompt names (`totp` ).
- A lens with the `supabase` or `browser+supabase` environment: the local
stack's database (`supabase.dbUrl` , this machine only), through the exact
command the prompt names (`sql` ) — every call one transaction rolled back
at its end.
The helper hands the SQL to the driver's broker, which runs it: the URL,
its password included, stays in the driver, and the pass's process gets
only the broker's address and its stack's token.
Never: `.review-gates/push/` (dumps, manifests, gate-state, freeze.json), the
pipeline's own files, the network beyond the application, a build tool, a test
runner, a package manager, a stack command or a database client of its own
(`psql` is denied: it commits to the database every pass shares) — the
bug-prover and the security lens prove statically unless their environment
gives them the database or the application, where a proof may also be run;
the functional lens observes, it does not build.
### How the passes share the stacks
A stack is an application with its API, its database and a login. It is
either the one the config names (`functional.*` , `supabase.dbUrl` , the first
login fixture) or one of several **slots ** the operator brings up side by
side (`slots.*` ). Each instrument is shared in its own way:
- **The application** (the browser environments): a pass holds a stack of
its own from its launch to its end, and no other pass with the browser
gets that stack meanwhile. So as many such passes run at once as there
are stacks up — one on the config's single stack — and a sign-out, a
setting or a row of one pass reaches no pass running beside it. A stack
is not reset between passes: what keeps a pass apart from the ones before
and after it is the rule in the APPLICATION UNDER TEST block. Create only
what you need, name it with your unit id, never change what you did not
create, and leave the account as you found it. The application's writes
are real; they last until the stack is reset. With slots, the pass's own
stack — its URL, origins and login — is named in its user message, so
the system prompt every pass shares stays the same.
- **The database** (the supabase environments): every SQL call is its own
transaction and is rolled back, so a pass never sees another pass's
writes. The application never sees them either, and nothing outlives the
call. That is why a pass with the database alone needs no stack of its
own: it uses the first. A lock a call takes (DDL, an UPDATE) holds until
that call ends; other passes wait for it, up to the `lock_timeout`
(`PgWire::LockTimeoutS` , 3 s). The guard is against interference and
leftovers, not against a lens set on escaping: a second connection opened
from inside the database (dblink) commits on its own, and a lens may open
a connection of its own with the local stack's well-known credentials.
- **Memory:** there is no per-lens number. Before a gate, and before every
launch, the driver reads the machine's free memory (`MemAvailable` ). It
launches a pass only while the pass fits: free memory at the gate's start
less the reserve (`REVIEW_GATES_MEM_RESERVE_MB` , by default 10 % of the
machine), less what the passes in flight were admitted at, with the
pass's cost still free right now. A pass costs its environment's measured
peak: the driver samples every pass's own process tree (claude, the
Playwright server, Chromium), keeps the recent peaks per repository and
environment (`~/.cache/review-gates/memory.json` ), and admits at the
highest. Before any is recorded it uses the defaults measured on
2026-10-22. With nothing in flight a pass runs whatever the memory says.
- **Other drivers on this machine** (2026-10-31): every driver registers
its passes in flight on one board, `~/.cache/review-gates/seat-board.json`
(`SeatPool::Board` ), each
with the account it runs on, the memory it was admitted at and the stack
it holds. A launch is decided and registered in one locked section, so:
a stack another driver's pass holds is not free here (a stack is its
application's address, `Pool::StackKey` , so two configs that name one
URL share it); the other drivers' passes count against the memory budget
at what they were admitted at, and the budget's free memory is the
reading from before they ran (`Board::MemoryBase` ) — "nothing in flight"
means nothing on the machine; their passes count in each seat's
2026-10-31 17:33:11 +01:00
projection, in an account's 128 and in an API key's slots; and
`--concurrency` , when given, is the machine's width, every driver's
passes counted. A driver waiting on
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
what another holds re-checks every 30 s. A driver that died is swept by
its lifetime lock under the board's `drivers/` , a pass by its deadline. A
driver takes its passes with it however it ends: each pass runs under a
supervisor that kills the pass's whole tree — the Playwright server and
Chromium included — when the driver goes, SIGKILL included.
- **Stopping a run:** `flatpak run net.catcrafts.CrafterReview stop <its scratch dir>` , or
`--hash <hash8>` for every live run of that hash (the scratch dir is the
last word of the run's gate-0 log). It works from any sandbox: the driver
looks for the stop file every 2 s and handles it as SIGTERM — its running
passes killed, no later gate started, the dump written. From outside the
sandbox a SIGTERM reaches the driver only through the process group it was
launched in (`kill -TERM -<pgid>` of the `setsid` launch); a SIGTERM to the
`flatpak run` pid alone does not. `flatpak kill net.catcrafts.CrafterReview`
is the hard stop: it ends every run of the app at once, without a dump.
## The consumer's configuration
`.review-gates/config.json` at the repository root, committed; the template
is what `init` writes (`flatpak run net.catcrafts.CrafterReview doc config` prints it) and
`flatpak run net.catcrafts.CrafterReview config` prints the merged result with its problems. What it holds: `base` (the integration
branch a range is measured against) and `rangeFrom` (`upstream` : the push
delta `@{upstream}..HEAD` , the rule for a repository that pushes to its
own trunk; `base` : always the merge-base with `base` , the whole pull request
for a repository whose branches become PRs — their commits must be linear,
rebased, never a merge of the integration branch), `authors` (the emails the
freeze accepts; null = `git config user.email` ), `aiTrailers` (`forbid` or
`allow` ), `requireSigned` , `allowDirty` (tracked
files the local stack rewrites in place while the lenses run), `gate0` (the
feat(config): modes - a mode is an artifact kind, the lenses it runs and its effort
The config defines the modes (modes.<name>): the kind of artifact the mode
reviews - reply, series or commits, the three shipped partitions; a kind
is a mode of its own name - the lenses it runs, in its kind's gate order,
and the effort it runs at, else the kind's default (max for a reply or a
series, xhigh for a commit range). `run --mode` names any of them, the
dump records mode and kind, and everything that decided by the mode's
name - the partition and hash, the prompts, the schemas, the disposition
and reuse-key rules, what the CLI requires - decides by the kind.
Every lens stays off until a mode lists it; lenses.<name>.run is retired
with a warning that says so, and a lens of another kind, a name that is
no lens, a lens listed twice and an unknown effort are config problems.
The defaults run no lens; the template ships the three kinds as modes
with every lens of their kind at the kind's effort, to trim (the owner's
ruling of 2026-11-01), and init says so as it writes it - a kept config
is the repository's own, and only one that lists no lens gets a step.
The readers of the retired key follow: the CI smoke config, the shipped
skill, the procedure (its description of the config as well as its
mechanics), the manifest template, the run's messages and the comments
that named the three modes. The run lock, the run numbers and the dump
names are the kind's, so one full run per hash holds across the modes
of a kind and their rounds count on together; the dump says which mode
ran. The database broker starts for a lens the mode runs, as every other
reader of the lenses off reads them. A named mode runs through the CLI
in the kernel-modes test.
2026-11-01 01:31:40 +01:00
commands Gate 0 runs by hand, for the skill), `modes` (`modes.<name>` : the
kind of artifact the mode reviews — `reply` , `series` or `commits` , the
three shipped partitions, a kind being a mode of its own name — the
`lenses` it runs, in its kind's gate order (the table below for
`commits` , the README's kernel modes for a `series` or a `reply` ), and
its `effort` , else the kind's default; every lens is off until a mode
lists it (opt-in only,
2026-10-31; `lenses.<name>.run` is retired, 2026-11-01); a lens no mode
lists is out of every run of this repository: its passes leave its gate,
which disappears when none of its lenses runs; in a commit range the units
only it reads are listed as not reviewed, never as holes, and the run stays
a full run (the ruling of record for a repository with nothing to
exercise, where `--without` is a per-run ruling that leaves a hole), while
the `reply` and `series` kinds excuse no unit),
`lenses` (per lens, by its name: `environment` , what its processes get
beyond the read-only tools: `none` ; `browser` = a Playwright MCP server per pass,
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
the browser tools and `curl` , the APPLICATION UNDER TEST block, the probe
before its gate, a stack of its own per pass and the fail-closed server
check; `supabase` = the database under test: the `sql` helper as its
exact command, the DATABASE UNDER TEST block, the probe before its gate
(the database answers and its applied migrations are the range HEAD's —
by version, and by content for the migrations the range adds or changes)
and the fail-closed check that a pass whose SQL calls never reached the
database is no evidence; `browser+supabase` = both. The functional lens
is empirical and needs one with the browser while it runs; the static
lenses default to `none` and may take any. A verdict bought in one
environment is never reused, resumed or salvaged in another, and a defect
found in one drops the clean verdicts the lens holds for the same code in
feat(config): lens sections - a repository's own text appended to a shipped charter
A lens has one charter, the one this pipeline ships; what a repository
knows that is not general to the lens - its platform and layers, the
record it keeps its decisions in, what its users already have, the
calibration entries that are its own - goes in a file the config names
for the lens (lenses.<name>.section), and Charter appends it after one
blank line. The charter sha in every reuse key covers the combined text.
The config refuses a path outside the repository - absolute, or
climbing out with `..` - by the check slots.dir had, now one both
settings share; a file missing or one the run could not use (a dangling
link, unreadable, not UTF-8) is a problem too, checked through the
reader Charter uses (Prompt::ReadSection), so a section is never
dropped in silence, and a name that is no lens is the unknown key it
always was. Nothing lists a directory, so what lies beside the files is
no concern of the config's: a directory listed would have to tolerate or
refuse every foreign entry. The run logs which charters got one.
The better-solution charter gains a consumer's general entry
(2026-10-31: a mechanism reworked a second time in one submission is the
signal to derive the fix at every layer the data crosses). The procedure
names the section wherever it names the charter: among the config's keys,
in the lens inputs, in the reuse rule (an edit to either re-runs the lens)
and in the feedback loop, which routes a repository's own lesson to its
section and a general one to the shipped charter.
2026-11-01 01:30:22 +01:00
the others; `section` , a file of the repository's own — a relative path
inside it — appended to that lens's shipped charter after one blank line:
the calibration entries and conventions that are this repository's rather
than the lens's, which has the shipped charter for what is general to it;
the reuse keys carry the combined text's hash; a path outside the
repository and a file missing, unreadable or not UTF-8 (a dangling link
included) are config problems, a name that is no lens an unknown key),
`supabase` , the database under test for every lens with
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
`supabase` or `browser+supabase` : `dbUrl` (the local stack's Postgres —
this machine only; masked in the dump and the log), `start` / `stop` (how
the operator brings the stack up when no lens drives the application;
`functional.start` covers it when one does), `notes` (the schemas that
matter, the seeded users, what to leave alone), `slots` , stacks side by
side: `dir` (where the bring-up writes `<n>/slot.json` once slot n is up —
`{appUrl, apiUrls, dbUrl, login}` ; with it set, the driver uses the slots
that are up and refuses a gate with none), `start` / `stop` (the commands
for slot `{n}` ; the skill runs them), `memMb` (one slot's measured memory
while a pass uses it: its containers' and processes' own, never the
machine's — `slots` sizes the slots by it), and
`functional` , the application under test for every lens with the
`browser` or `browser+supabase` environment when no slots are configured:
`appUrl` and `readyPath` (what the driver probes before that lens's gate),
`apiUrls` , `start` /
`stop` (how the operator brings the application up from the frozen tree — the
skill runs them, the driver only probes), `login.fixtures` (JSON files with
`email` , `password` , `totpSecret` ; the first that exists is the stack's
login; a slot names its own in `slot.json` ), `exclude` (path globs whose
hunks carry no runtime behaviour: tests, CI, docs — mechanical n/a for the
functional lens; it holds with slots too),
`extraTools` (`Bash(<prefix>:*)` or `mcp__<server>[__<tool>]` entries a lens
with the browser may also use; an entry the deny list (`Tools::Deny` ) overrides — `psql` , a
package manager, a stack command — is a config problem, never a silent
no-op), `mcp` (the Playwright MCP command; null args = the pipeline's pinned
default), `notes` .
## Gate 0 — mechanical preflight (no agents)
1. **Build and tests green on the exact HEAD ** , the repository's own way (the
`gate0` commands of the config, run by hand from the checkout). Keep the
output where a commit message's "tested" claim can point at it
(`--context-paths` ).
2. **Freeze ** — `freeze` checks, all of them, before it
exports anything: author AND committer are in `authors` ; AI attribution
trailers (`Assisted-by:` , `Co-Authored-By:` ) by the `aiTrailers` rule; the
tracked tree is clean (the `allowDirty` paths aside, warned); no merge
commit in the range (`git format-patch` drops them silently, so the
artifact would not be the push); the range is a fast-forward of
`@{upstream}` when there is one; an unsigned commit warns
(`requireSigned` makes it fail). The base: `--base` , else by `rangeFrom` :
`@{upstream}` (the push delta) or the merge-base of HEAD and the
configured `base` branch (the whole pull request). Then the
export, the hash, an empty `gate-state/dispositions.json` when none
exists, `freeze.json` , `run-line.txt` , and with `--worktree` the detached
checkout.
```
flatpak run net.catcrafts.CrafterReview freeze --slug <branch-or-release> [--worktree] [--base <ref>]
```
The hash is `cat $(ls *.patch | sort) | sha256sum` over the export; every
pass binds to it. An amend or a rebase changes the `From <sha>` lines and
therefore the hash: re-freeze with `--replace` into the same dir
(`gate-state/` survives, so clean unit results are reused from
`unit-state.json` — what a re-freeze re-runs is under Gates 1– 3).
3. **The application and the database under test ** (before a gate whose
lens has an environment with either can run — Gate 2 by default; nothing
to start when no lens that runs has one): start them from the tree the
lenses read, at the frozen HEAD — the checkout itself when `--trees .` ,
the worktree when `--worktree` — with the config's `functional.start`
(or `supabase.start` when no lens drives the application). With
`slots.*` , bring up slot 1 first, then run
`flatpak run net.catcrafts.CrafterReview slots --artifact <dir>` : it prints
how many slots fit in free memory — no more than the range's browser
passes can use at once — and the `slots.start` commands for the missing
ones, which may run side by side. The driver refuses a gate while a
stack's application or database does not answer, while its applied
migrations are not HEAD's (a migration of HEAD not applied, one applied
that HEAD lacks, or one of the range applied with other content — an
amend without a reset), or while its login is not in its database; and
it aborts the run if the tree moves or gets dirty beyond `allowDirty`
while it runs. After `freeze --replace` , bring every stack up again from
the new HEAD before the rerun. A fix during a run happens in another
worktree, never in the tree the lenses read.
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).
2026-10-31 20:08:26 +01:00
## Gates 1– 4 (the driver) and Gate 5 (the owner)
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
| Gate | Lens | Question |
|---|---|---|
| 1 Approach | `repo-better-solution` (whole pass gating code blocks) | an existing mechanism? the right layer? the right commit shape? compatible with what users have? |
| 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 |
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).
2026-10-31 20:08:26 +01:00
| 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 |
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
feat(config): modes - a mode is an artifact kind, the lenses it runs and its effort
The config defines the modes (modes.<name>): the kind of artifact the mode
reviews - reply, series or commits, the three shipped partitions; a kind
is a mode of its own name - the lenses it runs, in its kind's gate order,
and the effort it runs at, else the kind's default (max for a reply or a
series, xhigh for a commit range). `run --mode` names any of them, the
dump records mode and kind, and everything that decided by the mode's
name - the partition and hash, the prompts, the schemas, the disposition
and reuse-key rules, what the CLI requires - decides by the kind.
Every lens stays off until a mode lists it; lenses.<name>.run is retired
with a warning that says so, and a lens of another kind, a name that is
no lens, a lens listed twice and an unknown effort are config problems.
The defaults run no lens; the template ships the three kinds as modes
with every lens of their kind at the kind's effort, to trim (the owner's
ruling of 2026-11-01), and init says so as it writes it - a kept config
is the repository's own, and only one that lists no lens gets a step.
The readers of the retired key follow: the CI smoke config, the shipped
skill, the procedure (its description of the config as well as its
mechanics), the manifest template, the run's messages and the comments
that named the three modes. The run lock, the run numbers and the dump
names are the kind's, so one full run per hash holds across the modes
of a kind and their rounds count on together; the dump says which mode
ran. The database broker starts for a lens the mode runs, as every other
reader of the lenses off reads them. A named mode runs through the CLI
in the kernel-modes test.
2026-11-01 01:31:40 +01:00
Every lens runs on Opus 5.5 at the mode's effort (`modes.<name>.effort` in
the config, else `xhigh` for a commit range); `--effort` overrides it for one
run.
2026-10-31 17:33:11 +01:00
There is no width of the machine by default (2026-10-31, Jorijn: "there is
no need to do a global cap anymore, the cap should be 128 per available
account"): every OAuth account takes at most 128 passes at once, every
driver's counted (`REVIEW_GATES_OAUTH_CONCURRENCY` ), an API seat its
`REVIEW_GATES_API_CONCURRENCY` , and free memory the rest; `--concurrency N`
still caps the machine when given (it was 128 by default from 2026-10-15,
the width two drivers running at once shared). One full run
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
per hash: a second on the same hash is refused while the first is live
(`State::RunLock` ; a test run takes a run number of its own), and no two
drivers share a scratch dir. `flatpak run net.catcrafts.CrafterReview seats` shows the
drivers running and what they hold. The seat pool still places
every process by its seat's projected 5-hour headroom
feat(seats): accounts without a 5-hour limit
Anthropic is removing the 5-hour limit in stages (from 2026-10-31), and
an account without it reports the weekly window alone. Until now the pool
took such an account for an unprobed seat: always eligible, ranked after
every other, never held to the weekly cutoff, with no cap on its passes.
- The burst window is the 5-hour one, else the weekly one
(Seats::BurstWindow). An account whose burst window is the weekly one is
ranked by it and admitted under the 95 % cutoff with the passes in
flight projected on it, at the measured share per pass (0.04 at xhigh,
0.125 otherwise; REVIEW_GATES_LENS_COST_7D_PCT), and waits for the weekly
reset when over it.
- Every OAuth account and the local login takes at most 128 passes at once,
every driver's counted (REVIEW_GATES_OAUTH_CONCURRENCY): every run
measured with 149 or more in flight on one account drew usage-limit
rejections, none of the 119 with 139 or fewer.
- A 'concurrents' rejection pauses the seat for 20 s or its retry-after,
not until a window's reset (a seat sat two hours at 10 % on one).
- Board entries, the folded foreign record and the board's view carry
cost7d, so the drivers see each other's weekly load.
- `seats` and the log table say `no limit` in such an account's 5-hour
column; the board's others-line names its weekly projection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:07:17 +01:00
(`REVIEW_GATES_LENS_COST_PCT` , `REVIEW_GATES_SEAT_MAX_UTIL` ) — or, on an
account whose 5-hour limit Anthropic has removed, by its weekly window
2026-10-31 21:13:55 +01:00
under the 99 % cutoff — at most 128 passes per account, every driver's
feat(seats): accounts without a 5-hour limit
Anthropic is removing the 5-hour limit in stages (from 2026-10-31), and
an account without it reports the weekly window alone. Until now the pool
took such an account for an unprobed seat: always eligible, ranked after
every other, never held to the weekly cutoff, with no cap on its passes.
- The burst window is the 5-hour one, else the weekly one
(Seats::BurstWindow). An account whose burst window is the weekly one is
ranked by it and admitted under the 95 % cutoff with the passes in
flight projected on it, at the measured share per pass (0.04 at xhigh,
0.125 otherwise; REVIEW_GATES_LENS_COST_7D_PCT), and waits for the weekly
reset when over it.
- Every OAuth account and the local login takes at most 128 passes at once,
every driver's counted (REVIEW_GATES_OAUTH_CONCURRENCY): every run
measured with 149 or more in flight on one account drew usage-limit
rejections, none of the 119 with 139 or fewer.
- A 'concurrents' rejection pauses the seat for 20 s or its retry-after,
not until a window's reset (a seat sat two hours at 10 % on one).
- Board entries, the folded foreign record and the board's view carry
cost7d, so the drivers see each other's weekly load.
- `seats` and the log table say `no limit` in such an account's 5-hour
column; the board's others-line names its weekly projection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:07:17 +01:00
counted (`REVIEW_GATES_OAUTH_CONCURRENCY` ), and a pass
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
launches only when its environment's instrument is free and it fits in
free memory (How the passes share the stacks): the driver logs per gate
how many passes of each environment memory and the stacks allow. Run
from the repository root in a detached background shell (`setsid nohup … &` —
a launch bound to a tool call's timeout dies with it) and poll
`~/.cache/review-gates/<hash8>-run-<n>/progress.json` ; `freeze` prints the
exact line and writes it to `run-line.txt` — launch from that file verbatim,
never retyped (2026-09-29: a hand-copied base cut at 120 columns did not
resolve and nobody noticed for a round).
```
flatpak run net.catcrafts.CrafterReview run --artifact .review-gates/push/<slug>/ --hash <sha256> \
--base <base sha> --trees . --dispositions .review-gates/push/<slug>/gate-state/dispositions.json \
[--context-paths <dirs>] --date <YYYY-MM-DD>
```
feat(seats): the env file's settings count, and a machine's own seats file
ApiConfig and PoolConfig read the seat order, the budgets and the
concurrency from the process environment alone, so a `.env` that said
REVIEW_GATES_SEAT_ORDER=oauth-first ordered nothing: the Python driver
reads those lines from the file under the process environment
(config_env, 2026-10-17: a setting lives beside the seats it orders).
SeatPool::SettingsOf does the same - the file's placement settings
under the process environment, in a map handed to the placement readers
alone (ApiConfig, PoolConfig, Seats::ConfigFromEnv) - and leaves the
process environment untouched, so no other reader sees a line of the
file: a path's or a binary's line reaches nothing, and two drivers
reading one .env keep one board and one ledger. The placement settings
are one list, SeatPool::PlacementSettings - the fifteen names those
readers read - for the map, the run's log and what makes a working
directory's .env a seats file; a REVIEW_GATES_* line of any other name
stays out of the map and is named, by the run's log and on `seats`'s
stderr. One dotenv grammar serves the tokens and the settings
(Seats::DotenvLines).
`run` and `seats` resolve the file from their own --env-file
(SeatPool::ResolveSeatsFile): the --env-file, else $SEATS_ENV_FILE, else
the working directory's .env - the operator's, as the Python driver and
`seats` read it, not the repository under review's - when it is a file
holding a seat or a placement setting, each with a value, or one that
cannot be read as UTF-8 text (its error is then the command's) - a
readable application .env, its Vite or Supabase lines, one of blank
placeholder lines or one that is no file is passed over, and the run
says so - else the machine's own
$XDG_CONFIG_HOME/crafter-review/env, else ~/.config/crafter-review/env:
one file for every repository on a machine, a repository's seats .env
winning over it. Each file is read once and the settings and the tokens
are taken from that read (SeatsFile::Text), so a file that can be read
only once - a pipe, `--env-file <(gpg -d seats.env.gpg)`, a FIFO - gives
its settings and its seats alike, and a failed read raises its own error
where the tokens are laid over the environment: SeatPool::WithFileTokens,
for `run` and `seats` alike, and LoadSeats takes the merged environment
and no longer reads a file by path. An empty `seats --env-file` means no file
now, as it did for `run`, and the `seats` report names the side that
runs first (it said "api seats run first" whatever the order).
Runs, Commands and KernelModes, the tests that run `run` and `seats`,
point XDG_CONFIG_HOME, and HOST_XDG_CONFIG_HOME for the Flatpak, at a
path of their own that nothing creates, so a global file stays out of
them; the grammar, the settings and their precedence, the lookup and the
one read have their tests, a run with `--env` (a prefix) and with a
repeated --env-file, and a `seats` with `--env`, take tokens and
settings from the one file, an application's .env and a venv are passed
over, a FIFO is read once, no line of the file reaches `stop`, every
listed placement setting moves a reader, another REVIEW_GATES_* line is
named and a .env of blank lines passed over, and the
README, the environment table and the procedure say the new rule.
2026-11-01 01:51:48 +01:00
The lens processes run on the seats of a dotenv file: `--env-file` , else
`$SEATS_ENV_FILE` , else the working directory's `.env` when it is a file
holding a seat or a placement setting, or one that cannot be read as
UTF-8 text (the run then stops at preflight with the read error; a
readable application `.env` is passed over), else this machine's own
`~/.config/crafter-review/env` (`$XDG_CONFIG_HOME/crafter-review/env` ).
Its seat lines go over the process environment's; with no seat in either,
this machine's own login. The seats are `CLAUDE_CODE_OAUTH_TOKEN1..6` Max
plans, placed by 5-hour headroom (weekly headroom where Anthropic has
removed the 5-hour limit), and `ANTHROPIC_API_KEY1..6` API-credit
organisations, up to `REVIEW_GATES_API_BUDGET_USD` per seat per cycle; API
seats go first unless `REVIEW_GATES_SEAT_ORDER=oauth-first` . The placement settings among the file's
lines (the seat order, the budgets, the concurrency, the utilisation
ceilings) count where the process environment has none; any other
`REVIEW_GATES_*` line - a path, a binary - is not taken, and the run and
`seats` name it.
`flatpak run net.catcrafts.CrafterReview seats` reports them. An API seat is safe only
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
when its organisation holds nothing but the plan's monthly credits — no
payment method, no purchased credits, auto-reload off — so that "credit
balance is too low" is a hard stop. `--seats local|api|oauth|<list>` ,
`--no-probe` , `--api-budget` override. OpenRouter seats and the opencode
harness are carried for the hours when every seat is out (`--seats
openrouter --harness opencode`); a lens whose environment has the browser
or the database cannot run there (no MCP server; the database check reads
the claude stream) — `--without` it, and the round is PARTIAL in spirit
even though the driver calls it full: say so in the manifest.
Exit 0 SEND-READY = **push-ready ** ; 1 FIX FIRST; 2 INCOMPLETE / PARTIAL /
CONFIRMING (gates clean, code units still owe clean reads — run the same line
again, see "Three clean reads"); 3 preflight. A whole pass gates its block
passes; coverage is fail-closed per inventory unit (`inventory commits` :
`pK:subject` , `pK:msg` , `pK:diffstat` , `pK:<file>#hdr` , `pK:<file>#H` ,
`pK:outside` , `pK:absent` , `whole:order` , `whole:absent` ,
`whole:behaviour` ); per-unit reuse across hashes, salvage of finished
outputs after a kill, budgets, `--only` / `--units` test-only runs (PARTIAL,
never a pass), the dump at `gate-state/commits-<hash8>-run-<n>.json` .
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).
2026-10-31 20:08:26 +01:00
**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
feat(config): modes - a mode is an artifact kind, the lenses it runs and its effort
The config defines the modes (modes.<name>): the kind of artifact the mode
reviews - reply, series or commits, the three shipped partitions; a kind
is a mode of its own name - the lenses it runs, in its kind's gate order,
and the effort it runs at, else the kind's default (max for a reply or a
series, xhigh for a commit range). `run --mode` names any of them, the
dump records mode and kind, and everything that decided by the mode's
name - the partition and hash, the prompts, the schemas, the disposition
and reuse-key rules, what the CLI requires - decides by the kind.
Every lens stays off until a mode lists it; lenses.<name>.run is retired
with a warning that says so, and a lens of another kind, a name that is
no lens, a lens listed twice and an unknown effort are config problems.
The defaults run no lens; the template ships the three kinds as modes
with every lens of their kind at the kind's effort, to trim (the owner's
ruling of 2026-11-01), and init says so as it writes it - a kept config
is the repository's own, and only one that lists no lens gets a step.
The readers of the retired key follow: the CI smoke config, the shipped
skill, the procedure (its description of the config as well as its
mechanics), the manifest template, the run's messages and the comments
that named the three modes. The run lock, the run numbers and the dump
names are the kind's, so one full run per hash holds across the modes
of a kind and their rounds count on together; the dump says which mode
ran. The database broker starts for a lens the mode runs, as every other
reader of the lenses off reads them. A named mode runs through the CLI
in the kernel-modes test.
2026-11-01 01:31:40 +01:00
run's mode does not list (in `modes.<mode>.lenses` of the mode `--mode`
names: every lens is off until it does, and another mode's list turns
nothing on in this run) reads
2026-10-31 20:09:07 +01:00
nothing: the units only it would have read (its whole slot —
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).
2026-10-31 20:08:26 +01:00
`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
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
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.
**What a re-freeze re-runs** (the rules of 2026-10-06, with one addition for
the functional lens):
- a hunk or a file header, for the static lenses (the approach lens, the
bug-prover, the security lens): its file and its own
text, with the blob ids of the `index` line, the line numbers of the `@@`
header and format-patch's tail taken out. Not in the key: the commit id,
the message, the unit's number, the rest of its commit. A reworded message,
a new set of commit ids, a hunk or a commit added before it re-run nothing;
a changed line in the hunk, context lines included, re-runs that hunk;
- a hunk, for the functional lens: the same, plus the code of the whole
range — what the application shows for one hunk depends on every other
hunk (a later commit changes the screen it renders), so any code change in
the range exercises every functional unit again. The static lenses' blocks
stay self-contained by the owner's ruling (2026-10-06: "blocks fully
own the change that results from their block even if its in other
blocks") — the browser cannot;
- `pK:outside` and `pK:absent` (static lenses) have no text and stand for
one commit in the repository: they are asked again when any code of the
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;
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).
2026-10-31 20:08:26 +01:00
- 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;
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
- 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
never stored, so it runs on the next hash whatever changed;
- every code unit and both whole passes: the range base (a rebase re-runs
all of them);
feat(config): lens sections - a repository's own text appended to a shipped charter
A lens has one charter, the one this pipeline ships; what a repository
knows that is not general to the lens - its platform and layers, the
record it keeps its decisions in, what its users already have, the
calibration entries that are its own - goes in a file the config names
for the lens (lenses.<name>.section), and Charter appends it after one
blank line. The charter sha in every reuse key covers the combined text.
The config refuses a path outside the repository - absolute, or
climbing out with `..` - by the check slots.dir had, now one both
settings share; a file missing or one the run could not use (a dangling
link, unreadable, not UTF-8) is a problem too, checked through the
reader Charter uses (Prompt::ReadSection), so a section is never
dropped in silence, and a name that is no lens is the unknown key it
always was. Nothing lists a directory, so what lies beside the files is
no concern of the config's: a directory listed would have to tolerate or
refuse every foreign entry. The run logs which charters got one.
The better-solution charter gains a consumer's general entry
(2026-10-31: a mechanism reworked a second time in one submission is the
signal to derive the fix at every layer the data crosses). The procedure
names the section wherever it names the charter: among the config's keys,
in the lens inputs, in the reuse rule (an edit to either re-runs the lens)
and in the feedback loop, which routes a repository's own lesson to its
section and a general one to the shipped charter.
2026-11-01 01:30:22 +01:00
- as always: the lens charter (the shipped text with the repository's
section), the prompt version, the model, the dispositions in force. An
edit to a `repo-*.md` charter, or to the repository's section of it,
re-runs that lens on the next hash; a re-run on the SAME hash resumes and salvages by hash and
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
model alone (use `--no-reuse --no-salvage` there).
What is stored as reusable: a pass that itself returned no undischarged
finding, on a unit that is clean in the merged result; and where several
units share a key (the same file header in two commits), only when all of
them are. A reused whole pass gates its block passes like a fresh one and
hands them the summary it stored. `--no-reuse` buys the full round.
**Three clean reads (2026-10-06.5).** A static lens is not
deterministic: over one release's fifty rounds, of 58 defect verdicts on
unchanged code at most 14 were in the block when a first read called it clean. The owner's
rule: "per round each block gets run once, then in future rounds the blocks
from the previous rounds get run again until they reach x"; x is 3
(`--reads` ).
- It applies to the code units of Gates 1 and 3: hunks, file headers and the
per-commit slots. Both whole passes need one read; the functional lens's
units need one read (their key already holds the whole range's code, so
every code change exercises them again; flip `reads` in `:Plan` 's lens table to make
them three — three browser reads per hunk per round).
- A round reads every such unit below three clean reads ONCE. A clean read
counts; a finding drops the count (and its fix changes the key anyway); a
changed key starts at zero. A unit at three is reused.
- A round is the runs on one hash up to the first that finished. The
relaunch of a killed or paused run continues its round; the same line run
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
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).
2026-10-31 20:08:26 +01:00
SEND-READY. Gate 5 is asked for a hash only then.
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
- Every finding in the dump carries `read` , the number of the read that
found it.
## Verdicts, findings, dispositions
A lens reports **findings ** (defects) and **notes ** (everything else worth
the record). It never grades a finding. What makes something a defect is the
lens's charter question — objective wrongness: a better mechanism that
exists, a demonstrated bug, a behaviour the application does not deliver, a
protection the code lacks. "No reviewer would notice" is no defence. Every finding blocks the push until it
is fixed or discharged by a disposition in force. Notes never affect the
verdict and are never waived. Note kinds: `reserve-answer` , `invariant` ,
`pre-existing` , `residual-risk` , `hold` , `info` , `immaterial` ,
`send-time-check` , `refutation-verified` , `unverified` .
**verdict** — FIX FIRST iff any finding is undischarged. The driver derives
it: SEND-READY declared over an undischarged finding fails closed; FIX FIRST
declared with everything discharged and no contest is a silent veto — the
derived pass stands and the violation is logged.
**Dispositions** discharge ONE named finding each and are never the lens's.
Two kinds: `waiver-owner` (the owner's ruling — every owner ask of a gate
stop batched into one message: finding, evidence, proposed grounds, cost of
fixing instead; the round blocks until they rule) and `refuted` (a FACT that
falsifies the finding's premise; a competing preference is an owner ask).
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
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).
2026-10-31 20:08:26 +01:00
manifest's rounds table names each discharge with its grounds for Gate 5.
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
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
times, lapsed otherwise — a lapsed entry discharges nothing and the finding
re-blocks. The lens receives the entries in force, verifies the grounds of
any it discharges, still reports the finding marked `dischargedBy` , and may
contest one only with evidence the grounds do not survive; a contest is
itself a claim and is verified before it reaches the owner; a reaffirmed or
rejected contest is final for that binding. Every undischarged finding is
fixed (amend → `freeze --replace` → rerun), refuted, or an owner ask. Notes
never block: an `invariant` is recorded in the manifest, a `send-time-check` is a
push-time check, an `unverified` names what a manual check must cover before
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.
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).
2026-10-31 20:08:26 +01:00
## Gate 5 — the owner, then the push
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
Present the frozen patches themselves (never summaries), the rounds table,
every discharged finding with its grounds, every contest, every `unverified`
note and every other note — then STOP. On their approval of the hash:
1. `flatpak run net.catcrafts.CrafterReview freeze --slug <slug> --verify` must
print MATCH (HEAD, a clean tree and a fresh export still equal
`freeze.json` ); a MISMATCH means the range moved and the review is void.
2. `git push` (and open the pull request, where the repository's flow is a
PR into its integration branch).
3. `git worktree remove .review-gates/trees/<repo>-<hash8>` when one was
used; stop the application (`functional.stop` ).
4. Record in `<out>/manifest.md` (`flatpak run net.catcrafts.CrafterReview doc manifest` prints the template): the
rounds, `approved: <date> @ <hash>` , `pushed: <date> <remote>/<branch> @
<sha>`. ` .review-gates/push/` is gitignored; paste the rounds table into
the pull request when the repository keeps its record there.
## Driver changes and test-only runs
`--only` / `--units` runs are PARTIAL and never a pass. After any driver
change: its suite passes (`crafter-build test` in a checkout of
Crafter.Review), a `--dry-run` prompt comparison before and after on a frozen
fixture shows exactly the intended differences, and a run proves each gate
can still reject (a fixture repository with one planted defect per gate;
`AGENTS.md` of Crafter.Review says how).
## Feedback loop
A defect that reaches a pushed release, or a review comment on the
repository nobody's lens predicted, is a process bug: post-mortem it and
feat(config): lens sections - a repository's own text appended to a shipped charter
A lens has one charter, the one this pipeline ships; what a repository
knows that is not general to the lens - its platform and layers, the
record it keeps its decisions in, what its users already have, the
calibration entries that are its own - goes in a file the config names
for the lens (lenses.<name>.section), and Charter appends it after one
blank line. The charter sha in every reuse key covers the combined text.
The config refuses a path outside the repository - absolute, or
climbing out with `..` - by the check slots.dir had, now one both
settings share; a file missing or one the run could not use (a dangling
link, unreadable, not UTF-8) is a problem too, checked through the
reader Charter uses (Prompt::ReadSection), so a section is never
dropped in silence, and a name that is no lens is the unknown key it
always was. Nothing lists a directory, so what lies beside the files is
no concern of the config's: a directory listed would have to tolerate or
refuse every foreign entry. The run logs which charters got one.
The better-solution charter gains a consumer's general entry
(2026-10-31: a mechanism reworked a second time in one submission is the
signal to derive the fix at every layer the data crosses). The procedure
names the section wherever it names the charter: among the config's keys,
in the lens inputs, in the reuse rule (an edit to either re-runs the lens)
and in the feedback loop, which routes a repository's own lesson to its
section and a general one to the shipped charter.
2026-11-01 01:30:22 +01:00
append a dated calibration entry where the lesson belongs: a lesson of
this repository's in its own section of the lens that should have caught
it (`lenses.<name>.section` ), one general to the lens in the shipped `repo-*.md`
charter, one of the mechanics here. The entries dated before 2026-10-15 were carried from the
feat(core): the data home, the lens texts, and --version naming both
process/ holds the procedure, the five lens texts, the templates and the
skill under process/skill/prepush/. A lens text's sha256 is part of every
reuse key and the texts are part of every prompt, so they carry no SPDX
header; --version prints the charter hashes: 1728bd4c, 148d5c4c,
c7ffef44 and 2b9a5f83 for the four charters.
Home() finds them: CRAFTER_REVIEW_HOME, natively only (a host setting
must not swap the Flatpak's texts for a checkout's), then
<exe>/../share/crafter-review, then the checkout the binary was built in.
Main() hands `__supervise` to the supervisor before anything else, then
decides the sandbox once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:51:37 +01:00
pipeline's earlier home because they are rules of the mechanics, not of one
repository.
## Calibration (append dated entries at the bottom)
- 2026-09-25 — the pipeline's first form (Jorijn: "a slim version with better solution,
bug prover and facts"). Decided: the bug-prover stays static; the author
reads the frozen patches at Gate 4, there is no author sign-off gate before
the lenses.
- 2026-09-29 (round 13 of a release) — **launch from the freeze's `run` line
verbatim, never retyped.** A round ran with a base hand-copied from a
display cut at 120 columns; it did not resolve and the lenses fell back to
the artifact's base-commit line. Copy from `run-line.txt` or build the
command from `freeze.json` .
- 2026-10-01 (round 15 of a release) — **a scripted rebase must never commit
at a stop it did not verify.** One stop per command; before any `--amend`
at a stop assert the subject of HEAD and `git status` free of `UU` entries.
A chain rebuild runs as a `sh -e` script file, never typed into the tool
shell (2026-10-14).
- 2026-10-06 — **a review pass is paid for again only when what it
judged changed; measure what a round re-runs before the tenth round, not
after the fiftieth.** At every gate stop read the dump's passes run /
reused per lens beside the findings, and say so when a round re-ran code
that had not changed; a fix pass that touches only messages should cost
nothing in Gates 1 and 3.
- 2026-10-06 — **a reuse rule is reviewed by someone who did not write
it, and its STORE side is tested as hard as its key side.** For every new
key ask "two units, one key, different verdicts" and "a FIX FIRST round,
then a text-only re-freeze". A prompt for a reviewer names the files and
what was already verified, never the conclusion.
- 2026-10-06 — **a fix pass is where the next round's findings come
from: delete, use the finding's own words, and sweep the fix's
neighbours.** A sentence added to pre-empt a finding is new surface; take
the finding's own minimal proposedAction.
- 2026-10-06 — **agree the exit with the owner early. ** Once the code
gates are SEND-READY and what remains is sentence-level or taste, put the
stopping rule to the owner at THAT gate stop, with the trend; `waiver-owner`
is the designed exit. Before offering "a re-run on the same hash, minutes":
the driver RESUMES a finished lens result with its undischarged findings and
SALVAGES old outputs by job label — re-running only the waived lens needs
the checkpoint entry removed plus `--no-salvage` ; the documented alternative
is `--no-reuse --no-salvage` , a full round.
- 2026-10-14 (a CI run of an image build) — **when a push depends on an external
mutable resource (a registry, a mirror, a tag, a hosted environment), the
send-time check names every reader of it in the run and verifies the
resource at the level those readers see; a precondition of step N is never
repaired by step N+k of the same run.**
- 2026-10-15 — created as the submodule pipeline for the owner's repositories
(Jorijn: "a repo that can be included as a git submodule and carries the
prepush review pipeline … remove the facts lens and add a
functional lens that is whole and block scoped that has a playwright mcp;
this should be gate 2 and the bug prover should be gate 3"). First
consumer: an application repository. Decided here, open to the owner's
ruling: the functional lens's blocks are keyed on the whole range's code
(above) and read once; its passes run four at a time by default on one
shared application and database; the text and documentation units are
listed as not reviewed rather than read by a lens the owner removed. The
consumer's `.env` already carries six OAuth seats; the driver reads it from
the consumer's root. Proofs at creation: the
tests, a `--dry-run` on the pipeline's fixture range and on a real
consumer range, and firing proofs.
- 2026-10-15 — **a lens that says it had no tools is not a pass; the driver
must know what a pass had, not only what it returned.** The first firing
run of the functional lens (the planted greeter range, `--only
repo-functional.md`) came back SEND-READY: all four hunks ` n/a` with
`unverified` notes saying "no mcp__playwright__* tools in this process" and
`hold` notes saying "this SEND-READY is a formality". The charter's
fail-closed wording worked — the lens refused to call anything clear — but
the verdict rule (FIX FIRST iff a finding) still read it as a pass. Cause:
the CLI's `--safe-mode` , which the driver passed to every lens process, drops
every MCP server (`mcp_servers: []` with it, connected without; CLI
2.1.300). Fixed in the driver: a functional pass runs without
`--safe-mode` , and a pass whose init line shows no connected Playwright
server is an error (retried once, then NOT RETURNED). Rule for any lens
that depends on a capability the driver provides: assert the capability
from the process's own init line, never from the lens's report of it.
- 2026-10-22 — **the database under test: two environments, `supabase` and
`browser+supabase` ** (Jorijn: "can you make a browser+supabase envoirement
and a supabase only envoirement? … can we give each agent its own
supabase, or make them share one correctly?"). Decided here: the passes
share the one stack the operator brings up. The database's sharing is
made correct by the helper rather than by instruction: every call is a
transaction it rolls back. A statement that would end the transaction is
refused before anything connects, every statement goes to the server as
its own Parse (two commands in one are refused), and a transaction found
closed after a statement stops the call. Not chosen: Supabase's own MCP
server (`/mcp` is served by Studio, which the throwaway bring-up leaves
out, and its `execute_sql` commits), and one stack per pass. A stack per
pass is possible on a machine that runs several side by side (a minimal
scratch stack started in 33 s), but every stack also needs its own build
of an application that bakes the API URL in, and its own seed. Also
decided: raw `psql` is denied, the database URL never reaches a prompt,
the preflight compares the applied migrations with HEAD's, and a defect
found in one environment drops the clean verdicts the other environments
hold for the same code. The firing proofs are in this pipeline's
`AGENTS.md` .
- 2026-10-22 — **a stack per pass; the limit from free memory, by
environment** (Jorijn: "ideally i would like to remove the cap … by not
making the database shared"; "make the concurrency limit from free
memory, instead of just guessing … the concurrency limit should be on
the environment"). `functional.concurrency` (4, never measured) and the
per-lens cap are gone. A pass with the browser holds a stack of its own;
every pass is admitted against free memory at its environment's
measured cost. Costs are measured from a pass's or a stack's own
processes, not from the machine's free memory, which other work moves —
that number is the budget only. Not reset between passes: a slot keeps
what earlier passes created, so Method 5's rule stays. The driver still
never starts a stack: `slots` sizes them, the skill runs the commands.
Numbers and proofs in this pipeline's `AGENTS.md` .
- 2026-10-22 — **the security lens, beside the bug-prover in Gate 3 **
(Jorijn: "please write the security lens", and written so that no
process reads it as a request to do harm — a charter that reads so is
declined, and a declined pass is NOT RETURNED). Decided here: the
bug-prover's shape (static code blocks keyed on the code, three clean
reads, `none` by default, the database or the browser when the config
gives them), in the bug-prover's gate rather than one of its own — one
gate stop for both proofs, and the owner stays Gate 4. The charter is the
owner's own defensive review: the promise of the repository first, every
decision point the hunk touches, the classes settled one by one, a gap
shown in the smallest step on the throwaway local copy and never a step
past it, refutations by the control that closes the class. A charter
edit re-keys the lens alone. Its firing proof is in this pipeline's
`AGENTS.md` .
- 2026-10-31 — **the driver as a Flatpak ** (Jorijn: "… make it an portable
flatpak executable like Crafter.Build"). The helpers are subcommands
(`sql` , `totp` ). The submodule goes: `crafter-review init` sets a
repository up and ships the skill, `doc` the texts, `browser install` the
Chromium, `stop` ends a run from any sandbox. A driver takes its passes
with it however it ends.
feat(seats): accounts without a 5-hour limit
Anthropic is removing the 5-hour limit in stages (from 2026-10-31), and
an account without it reports the weekly window alone. Until now the pool
took such an account for an unprobed seat: always eligible, ranked after
every other, never held to the weekly cutoff, with no cap on its passes.
- The burst window is the 5-hour one, else the weekly one
(Seats::BurstWindow). An account whose burst window is the weekly one is
ranked by it and admitted under the 95 % cutoff with the passes in
flight projected on it, at the measured share per pass (0.04 at xhigh,
0.125 otherwise; REVIEW_GATES_LENS_COST_7D_PCT), and waits for the weekly
reset when over it.
- Every OAuth account and the local login takes at most 128 passes at once,
every driver's counted (REVIEW_GATES_OAUTH_CONCURRENCY): every run
measured with 149 or more in flight on one account drew usage-limit
rejections, none of the 119 with 139 or fewer.
- A 'concurrents' rejection pauses the seat for 20 s or its retry-after,
not until a window's reset (a seat sat two hours at 10 % on one).
- Board entries, the folded foreign record and the board's view carry
cost7d, so the drivers see each other's weekly load.
- `seats` and the log table say `no limit` in such an account's 5-hour
column; the board's others-line names its weekly projection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:07:17 +01:00
- 2026-10-31 — **accounts without a 5-hour limit ** (Jorijn: "antrophic is
gradually removing the 5h limits"). Anthropic removes the 5-hour limit in
stages, and an account without it reports the weekly window alone. Such
an account is ranked and admitted by its weekly window with the passes in
flight projected on it, never taken for an unprobed seat; every OAuth
account takes at most 128 passes at once (every run measured with 149 or
more in flight on one account drew usage-limit rejections); a 'concurrents' rejection backs the seat off for seconds,
not until a window's reset; `seats` shows `no limit` in such an account's
2026-10-31 17:33:11 +01:00
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 20:09:07 +01:00
- 2026-10-31 — **every lens off by default, opt-in only; the commit-truth
lens as Gate 4** (Jorijn: "make it so all lenses are off by default and
it's opt in only"; and of the commit-truth lens, "fix that in
Crafter.Review"). A config that
turns on no lens is a problem `config` names and `freeze` and `run` stop
on; `init` says to turn lenses on; the owner's gate is 5. The commit-truth
lens's firing proof in this pipeline is owed (the owner's go).
feat(config): modes - a mode is an artifact kind, the lenses it runs and its effort
The config defines the modes (modes.<name>): the kind of artifact the mode
reviews - reply, series or commits, the three shipped partitions; a kind
is a mode of its own name - the lenses it runs, in its kind's gate order,
and the effort it runs at, else the kind's default (max for a reply or a
series, xhigh for a commit range). `run --mode` names any of them, the
dump records mode and kind, and everything that decided by the mode's
name - the partition and hash, the prompts, the schemas, the disposition
and reuse-key rules, what the CLI requires - decides by the kind.
Every lens stays off until a mode lists it; lenses.<name>.run is retired
with a warning that says so, and a lens of another kind, a name that is
no lens, a lens listed twice and an unknown effort are config problems.
The defaults run no lens; the template ships the three kinds as modes
with every lens of their kind at the kind's effort, to trim (the owner's
ruling of 2026-11-01), and init says so as it writes it - a kept config
is the repository's own, and only one that lists no lens gets a step.
The readers of the retired key follow: the CI smoke config, the shipped
skill, the procedure (its description of the config as well as its
mechanics), the manifest template, the run's messages and the comments
that named the three modes. The run lock, the run numbers and the dump
names are the kind's, so one full run per hash holds across the modes
of a kind and their rounds count on together; the dump says which mode
ran. The database broker starts for a lens the mode runs, as every other
reader of the lenses off reads them. A named mode runs through the CLI
in the kernel-modes test.
2026-11-01 01:31:40 +01:00
- 2026-11-01 — **a mode is an artifact kind plus the lenses it runs and
its effort** (Jorijn: "instead of defining lenses in the config you
define the mode and each mode enables the lenses"; "make effort
configurable per mode"): `modes.<name>` in the config, the three kinds as
modes of their own name, `lenses.<name>.run` retired with a warning,
`--mode` naming any configured mode. `Config::Defaults()` runs no lens;
the template lists every lens of each kind at the kind's effort, to trim
(Jorijn, 2026-11-01, on the populated template as the example: "looks
good"; `init` says to trim). The first self-review - this
pipeline on its own range - found the retirement not carried to the CI
smoke config, this file, the shipped skill and the manifest template:
every reader of a setting must agree (2026-10-12), and the run lock still
keyed on the mode's name where the gate-state is the kind's.
fix(init): the skill it writes ignores itself, as a generated directory
A repository that ignores .claude wholesale, as Crafter.Build does,
never sees in git the prepush skill init writes, and the question was
whether it should: it should not. The skill is generated
from the shipped text with init's marker and refreshed by every init, a
tracked copy lags the installed binary whenever the shipped skill
changes, and every refresh would be a diff in the consumer's own ranges.
Jorijn's ruling: every consumer leaves it untracked, one init per
checkout.
When init owns the skill - wrote or refreshed it - it writes a
.gitignore of `*` beside it, as Python's venv and pytest's cache do for
theirs: the directory ignores itself, that file included, on every
checkout that runs init, whatever the repository's own rules or one
machine's say, and nothing of the repository's is edited for it. A copy
init wrote that the repository tracks gets the step that untracks it, in
the output and in the README's migration. A skill the repository wrote
itself is kept and not ignored - the ignore file an earlier init wrote
beside it goes - and an ignore file init did not write is kept unless
--force; a link there, dangling or not, is refused like the skill's
other links unless --force, which replaces it (the old setup's exemption
- a link that resolves into the review-gates submodule at the toplevel,
not one that merely names it - is the skill directory's and SKILL.md's
alone), and nothing is written through it: the links go before anything
is written, and one init cannot remove refuses. The procedure's log
records the ruling.
2026-11-01 17:24:03 +01:00
- 2026-11-01 — **the generated skill stays untracked ** (Jorijn, as
Crafter.Build was set up: a consumer that ignored `.claude` wholesale
never saw the skill in git, and whether it should was the question):
every consumer leaves `.claude/skills/prepush/SKILL.md` out of git, one
`init` per checkout. `init` writes a `.gitignore` of `*` beside the
skill, as a generated directory does, and edits nothing of the
repository's for it: the first self-review's gate on the repository's
own ignore rules was reworked twice in one range (a tracked path, one
machine's rule) before the gate itself went (2026-10-31's rule).