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.
This commit is contained in:
Jorijn van der Graaf 2026-11-01 01:51:48 +01:00
commit e215ad986e
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
16 changed files with 483 additions and 91 deletions

View file

@ -9,7 +9,7 @@ This file is for changing crafter-review itself. `README.md` says what it is and
- Each lens is one `claude --print` process per unit (block passes). Where the lens has one, a whole pass gates the blocks and hands them its summary.
- A pass returns structured output: a verdict, findings, notes, and one coverage entry per unit. `:Normalize` derives the verdict from the findings and fails closed on a missing or blocking entry.
- Clean results are stored per unit under a content key and reused across hashes (`:Plan`). The static lenses need three clean reads of a unit before it is reused; the functional lens needs one.
- Processes run on the seats of the consumer's `.env` (`:SeatPool`). A lens's processes get the environment the config names: `none`; `browser`, a Playwright MCP server per pass (`:Mcp`, `:Pool`); `supabase`, the local stack's database through the `sql` helper (`:PgWire`); or `browser+supabase`.
- Processes run on the seats of the seats file (`SeatPool::ResolveSeatsFile`: `--env-file`, `$SEATS_ENV_FILE`, the consumer's `.env`, else the machine's own) and of the process environment, the file's lines over the environment's; with no seat in either, on this machine's login. A lens's processes get the environment the config names: `none`; `browser`, a Playwright MCP server per pass (`:Mcp`, `:Pool`); `supabase`, the local stack's database through the `sql` helper (`:PgWire`); or `browser+supabase`.
- A browser pass holds a stack of its own (`Pool::Stacks`), and a pass launches only when it fits in free memory at its environment's measured cost (`Pool::Admission`, `:Memory`).
- Drivers on one machine share one board, `~/.cache/review-gates/seat-board.json` (`SeatPool::Board`). `State::RunLock` allows one full run per hash.
- The kernel modes (2026-10-31): `--mode series` and `--mode reply` run the pre-send pipeline of a kernel contributor's workbench with the same machinery. Their own pieces: the inventory of a cover letter, a mail draft and the camera review's shape (`:Inventory` PartitionReply, PartitionMail), the ten kernel lenses with their gates (`Plan::Lenses` rows with `kernel`, `Plan::Gates(kind)`), text units keyed on what they cite and the anchor (`Plan::TextDeps`) and a series' code units on the code like the push gates, the persona gate - one `maintainer-persona` instance per `--reviewers` entry, its whole pass pruning the not-mine units - with the persona vocabulary in `:Schema`, `:Normalize` and the matrix, the references prefetched through `utilities/lore.py` and the persona corpora (`:Refs`), the kernel texts in `:Prompt`, the `GATES CLEAN` verdict, `personaPass` in unit-state, and the subsystem's journal in the run layer and the dispositions block. Their prompts, keys and plans were checked byte for byte against the pipeline they replace, on its own artifacts.
@ -111,7 +111,7 @@ Conventions:
No file the drivers write holds these in practice, and a file that fails to parse is treated as a damaged one.
- **HTTP.** `ClientHTTP1::Fetch` ignores `*_proxy` variables. The OpenRouter requests send `User-Agent: crafter-review`; `app_ready` sends `review-gates`. Reason phrases are the standard ones, not the server's.
- **Seat placement** (SeatPool, Seats, Drivers):
- **Seat placement** (SeatPool, Seats, Drivers): The settings beside the seats count too. `run` (Gate 0) and `seats` each resolve the seats file from their own `--env-file` with `SeatPool::ResolveSeatsFile` (`--env-file`, `$SEATS_ENV_FILE`, `<cwd>/.env` when it is a file holding a seat or a placement setting, each with a value (a blank line is neither), or one that cannot be read as UTF-8 text (its error is then the command's) - a readable application `.env`, or one that is no file, is passed over, recorded as `passedOver` and logged by the run - the machine's own), reading each file once (`SeatsFile::Text()`: a pipe or a FIFO `--env-file` 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` - the one place, so `LoadSeats` takes the merged environment and reads no file). `SeatPool::SettingsOf` lays the file's placement settings (`Seats::SettingsFromEnvFile`, one dotenv grammar with the tokens', `DotenvLines`; `SeatPool::PlacementSettings`, the fifteen names the readers read, also deciding what makes `<cwd>/.env` a seats file - a test pins that each moves a reader) under the process environment into a map handed to the placement readers alone - `ApiConfig`, `PoolConfig`, `Seats::ConfigFromEnv` - names its other `REVIEW_GATES_*` lines (the run logs them, `seats` prints them on stderr), and the process environment is not touched, 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 Python driver's `config_env` of 2026-10-17; the first self-review's rounds 3 to 10, 2026-11-01, reworked it as an application to the process environment until round 11 found the map the readers already took; the per-seat `ANTHROPIC_API_KEY<n>_GRANT_DAY` cycle days are still unported: one `REVIEW_GATES_API_CYCLE_DAY` for every API seat) (SeatPool).
- An account whose 5-hour limit Anthropic has removed reports the weekly window alone. Its burst window (`Seats::BurstWindow`) is the weekly one: it is ranked and admitted by the weekly window with the passes in flight projected on it (0.04 points per pass at xhigh, 0.125 otherwise), under the 99 % cutoff.
- A pass's projected share of the windows follows the run's effort: 0.5 / 0.125 points of the 5-hour window and 0.125 / 0.04 of the weekly one at max / xhigh (`SeatPool::LensCostPct`, `LensCost7dPct`), the pooled means of 5438 passes on 141 seat-runs (2026-10-12..15) rounded up. The weekly cutoff is 99 %: the running passes are projected against it, so the last percent is spent a pass at a time.
- Every OAuth account and the local login takes at most 128 passes at once, every driver's counted.
@ -181,9 +181,10 @@ What the binary reads at run time, and how. A variable the driver only sets or s
| `CLAUDE_CODE_OAUTH_TOKEN1`..`6` (+ legacy `CLAUDE_CODE_OAUTH_TOKEN`) | `:Seats` SeatTokensFromEnv, via `:SeatPool` LoadSeats (run) and `seats` | none | The fixed names 1..6 only; `TOKEN7` does nothing. Stripped; an empty value is no seat. The bare name is seat 1 when `TOKEN1` is empty. The env file's lines override the process environment. Stripped from every child's environment. | OAuth seats `1`..`6`; a token reaches claude only as its seat's `CLAUDE_CONFIG_DIR/.credentials.json` |
| `ANTHROPIC_API_KEY1`..`6` | `:Seats` ApiKeysFromEnv (run, seats) | none | The fixed names 1..6, stripped; empty is no seat. The bare `ANTHROPIC_API_KEY` is never a seat (the env-file parser wants the digit too) and is stripped from every child. | API-credit seats `api1`..`api6`, each key in its own process's `ANTHROPIC_API_KEY` only |
| `OPENROUTER_KEY`, `OPENROUTER_KEY1`..`6` | `:Seats` OpenRouterKeysFromEnv, via LoadSeats (run; `seats` ignores them) | none | As the OAuth tokens; the bare name is `or1` when `KEY1` is empty. Never in `auto`: `--seats openrouter` or `orN` ids; mixed with other seats it is a preflight error (exit 3). | OpenRouter seats `or1`..`or6` |
| `SEATS_ENV_FILE` | `:Run` Gate0 (run), `seats` | run: `<repo>/.env` if it exists; `seats`: `./.env` if it exists | `--env-file` wins (in run even `--env-file ""`, meaning no file); empty counts as unset. Only seat lines are taken (`[export ]NAME=value`, one pair of matching quotes stripped), overriding the process environment; no knob below is ever read from the file. A file that cannot be read, or is not UTF-8: run stops at preflight (exit 3), `seats` exits 1 with the read error. | The dotenv file the seat secrets come from |
| `REVIEW_GATES_SEATS` | `:Driver` RunCommand → LoadSeats | `auto` | Only without `--seats`. Stripped, case-sensitive; unset or empty is `auto` (every api and oauth seat, else `local`); `local`, `api`, `oauth`, `openrouter` select that kind; anything else is a comma list of ids (`apiN`, `N`, `orN`, `local`; blank items skipped). An id not configured, or OpenRouter mixed with others, is a preflight error (exit 3). | The seats a run places lens processes on |
| `REVIEW_GATES_SEAT_MAX_UTIL` | `:Seats` ConfigFromEnv (`seats`; run through `:SeatPool` PoolConfig) | `85` | A finite number >= 0, in decimal or exponent notation; surrounding whitespace, `_` between digits and Unicode digits are taken. Empty, garbage, negative, `inf`, `nan` keep the default; a whole value is stored as an int. Process environment only. | Ceiling (%) of an OAuth seat's projected 5-hour utilisation |
| `SEATS_ENV_FILE` | `:SeatPool` ResolveSeatsFile (run's Gate 0, `seats`) | `<cwd>/.env` if it is a file holding a seat or a placement setting, each with a value (or no UTF-8 text: its error is the command's), else the machine's `$XDG_CONFIG_HOME/crafter-review/env` (`~/.config/crafter-review/env`) | `--env-file` wins (in run and `seats` even `--env-file ""`, meaning no file); empty counts as unset. Seat lines are taken (`[export ]NAME=value`, one pair of matching quotes stripped), overriding the process environment; the file's placement settings (`SeatPool::PlacementSettings`) go, under the process environment, into the map the placement readers take (`SeatPool::SettingsOf`, from the one read the tokens come from too), so every placement knob below may come from the file, the process environment winning; no other reader sees a line of it, and its other `REVIEW_GATES_*` lines are named (the run's log, `seats` on stderr). A file that cannot be read, or is not UTF-8: run stops at preflight (exit 3), `seats` exits 1 with the read error. | The dotenv file the seat secrets come from |
| `XDG_CONFIG_HOME` | `:SeatPool` GlobalEnvFile | `~/.config` | The directory whose `crafter-review/env` is the machine's own seats file, the last place a run or `seats` looks (2026-11-01). Runs, Commands and KernelModes, the tests that run `run` and `seats`, point it (and `HOST_XDG_CONFIG_HOME`) at a path nothing creates, so a global file on the machine stays out of them. |
| `REVIEW_GATES_SEATS` | `:Driver` RunCommand → LoadSeats | `auto` | Only without `--seats`; the process environment's alone, never from the seats file. Stripped, case-sensitive; unset or empty is `auto` (every api and oauth seat, else `local`); `local`, `api`, `oauth`, `openrouter` select that kind; anything else is a comma list of ids (`apiN`, `N`, `orN`, `local`; blank items skipped). An id not configured, or OpenRouter mixed with others, is a preflight error (exit 3). | The seats a run places lens processes on |
| `REVIEW_GATES_SEAT_MAX_UTIL` | `:Seats` ConfigFromEnv (`seats`; run through `:SeatPool` PoolConfig) | `85` | A finite number >= 0, in decimal or exponent notation; surrounding whitespace, `_` between digits and Unicode digits are taken. Empty, garbage, negative, `inf`, `nan` keep the default; a whole value is stored as an int. The process environment, else the seats file (`SeatPool::SettingsOf`). | Ceiling (%) of an OAuth seat's projected 5-hour utilisation |
| `REVIEW_GATES_LENS_COST_PCT` | `:Seats` ConfigFromEnv; `:SeatPool` PoolConfig | run: `0.5` at max and any other effort, `0.125` at xhigh; `seats`: `10` | As MAX_UTIL. PoolConfig puts in the effort's figure only when it is unset or blank, so a set but invalid value gives 10, in run too. | Percentage points of the 5-hour window a running process is projected to add (its cost on the board too) |
| `REVIEW_GATES_SEAT_POLICY` | `:Seats` ConfigFromEnv | `spread` | Exactly `pack` (case-sensitive, untrimmed) is pack; anything else spread. | pack: the first eligible OAuth seat by id; spread: the lowest projected one |
| `REVIEW_GATES_SEAT_WAIT_MIN` | `:Seats` ConfigFromEnv | `15` | As MAX_UTIL | Shown by `seats --json` as `waitMaxMin`; nothing else reads it |
@ -198,21 +199,21 @@ What the binary reads at run time, and how. A variable the driver only sets or s
| `REVIEW_GATES_OPENROUTER_BUDGET_USD` | `:SeatPool` ApiConfig | `100.0` | As API_BUDGET_USD | USD per cycle per OpenRouter key |
| `REVIEW_GATES_OPENROUTER_LENS_COST_USD` | `:SeatPool` ApiConfig | `0.1` | As API_BUDGET_USD | Reserve per process on an OpenRouter seat |
| `REVIEW_GATES_OPENROUTER_CONCURRENCY` | `:SeatPool` ApiConfig | `16` | As API_CONCURRENCY | Processes at once on one OpenRouter seat |
| `REVIEW_GATES_SCRATCH` | `:Run` ScratchRoot (run, slots); `:SeatPool` LedgerPath (run, seats) | `~/.cache/review-gates` | `--scratch` wins; empty counts as unset; used verbatim. | Where the `<hash8>-run-<n>` dirs, `memory.json` and `seat-ledger.json` live; not the board |
| `REVIEW_GATES_BOARD` | `:SeatPool` BoardPath (run, seats, stop) | `~/.cache/review-gates/seat-board.json` | Non-empty: the board file, verbatim; its directory also holds `drivers/`, the lifetime locks. | The one board per machine every driver shares; set it for tests and hand-made boards |
| `REVIEW_GATES_MEM_RESERVE_MB` | `:Memory` Reserve (run's admission, slots) | `max(2048, MemTotal / 10)` MB | Unset or empty: the default. Otherwise a number in MAX_UTIL's syntax, truncated toward zero; a negative value enlarges the budget. Garbage and `nan` exit 1 with a ValueError, `inf` with an OverflowError; a value past int64 saturates. | MB of available memory never given to passes or slots |
| `REVIEW_GATES_SCRATCH` | `:Run` ScratchRoot (run, slots); `:SeatPool` LedgerPath (run, seats) | `~/.cache/review-gates` | `--scratch` wins; empty counts as unset; used verbatim; never from the seats file. | Where the `<hash8>-run-<n>` dirs, `memory.json` and `seat-ledger.json` live; not the board |
| `REVIEW_GATES_BOARD` | `:SeatPool` BoardPath (run, seats, stop) | `~/.cache/review-gates/seat-board.json` | Non-empty: the board file, verbatim; its directory also holds `drivers/`, the lifetime locks; never from the seats file. | The one board per machine every driver shares; set it for tests and hand-made boards |
| `REVIEW_GATES_MEM_RESERVE_MB` | `:Memory` Reserve (run's admission, slots) | `max(2048, MemTotal / 10)` MB | The process environment's alone, never from the seats file. Unset or empty: the default. Otherwise a number in MAX_UTIL's syntax, truncated toward zero; a negative value enlarges the budget. Garbage and `nan` exit 1 with a ValueError, `inf` with an OverflowError; a value past int64 saturates. | MB of available memory never given to passes or slots |
| `CRAFTER_REVIEW_HOME` | `:Core` Home | `<exe>/../share/crafter-review` (the Flatpak's `/app/share/crafter-review`), else the checkout when the exe runs from `<checkout>/bin/crafter.review-exe-*/` | Natively only; ignored in the Flatpak. Empty counts as unset. A set value without `process/lenses` is an error, with no fallback. | Where `process/` (the lenses, the templates, the skill) is read from |
| `REVIEW_GATES_CLAUDE_BIN` | `:Pool` Runner (lens processes, run's probes); `:Seats` FindClaudeBin (second) | FindClaudeBin's search | Non-empty: used verbatim, unchecked. In run: `--claude-bin`, then this, then FindClaudeBin. | The claude binary |
| `REVIEW_GATES_CLAUDE_BIN` | `:Pool` Runner (lens processes, run's probes); `:Seats` FindClaudeBin (second) | FindClaudeBin's search | Non-empty: used verbatim, unchecked; never from the seats file. In run: `--claude-bin`, then this, then FindClaudeBin. | The claude binary |
| `CLAUDE_BIN` | `:Seats` FindClaudeBin (`seats`, run's last fallback) | after it: `claude` on PATH, the editor-extension bundles (`~/.vscode-oss`, `.vscode`, `.vscode-insiders`, `.cursor`; newest first), `~/.claude/local/claude`, `~/.local/bin/claude`, then the bare `claude` | Non-empty: verbatim. FindClaudeBin checks it before `REVIEW_GATES_CLAUDE_BIN`, so it wins in `seats` and loses in run. | The claude binary |
| `REVIEW_GATES_OPENCODE_BIN` | `:OpenCode` DefaultBin (`--harness opencode`) | `opencode` on PATH | `--opencode-bin` wins; empty counts as unset. | The opencode binary |
| `REVIEW_GATES_OPENCODE_BIN` | `:OpenCode` DefaultBin (`--harness opencode`) | `opencode` on PATH | `--opencode-bin` wins; empty counts as unset; never from the seats file. | The opencode binary |
| `REVIEW_GATES_DB` | `:PgWire` SqlMain → CallBroker (`crafter-review sql`, in a pass) | none: NO DATABASE | `<host>:<port>#<token>`, split at the last `#`, then at the last `:`. Unset, empty or no `#`: NO DATABASE, exit 2; a port that is no integer: NO DATABASE with the parse error, exit 2; a port outside 0..65535: exit 1 with an OverflowError. The driver sets it per pass (`""` without a database) and strips an inherited one. | The driver's SQL broker and this pass's database token |
| `REVIEW_GATES_STRUCTURED_OUT` | the opencode tool `:OpenCode` ToolSource generates (reads `process.env`) | none | A file path; unset or empty: the tool writes nothing and still reports success. Set per process by `:OpenCode` ChildEnv. | Where an opencode pass's structured result goes |
| `REVIEW_GATES_SIMULATE_FAIL` | `:Pool` Runner (`--dry-run` only) | none | Split on commas, items untrimmed, empty ones dropped: a unit id fails that unit's block pass, `whole` every whole pass. Ignored without `--dry-run`. | A test hook: "simulated failure (REVIEW_GATES_SIMULATE_FAIL)" |
| `REVIEW_GATES_SIMULATE_FAIL` | `:Pool` Runner (`--dry-run` only) | none | The process environment's alone, never from the seats file. Split on commas, items untrimmed, empty ones dropped: a unit id fails that unit's block pass, `whole` every whole pass. Ignored without `--dry-run`. | A test hook: "simulated failure (REVIEW_GATES_SIMULATE_FAIL)" |
| `PATH` | `:Process` spawn and the supervisor; `:Seats` FindClaudeBin, ProbeSeat | unset: `/usr/local/bin:/usr/bin:/bin` (spawn), `/usr/bin:/bin` (supervisor); the Flatpak sets `/app/bin:/usr/bin` | Colon-separated; an empty entry is the cwd; a name with `/` is used as is. | Where child programs (claude, git, npx …) are found |
| `HOME` | `:Platform` Home (every `~` path); `:Seats` ProbeEnv | unset or empty: the password database's, else `/` | Verbatim. | `~/.cache/review-gates`, the XDG defaults, claude's install locations, `~/.cache/ms-playwright`, init's home-directory check |
| `TZ` | `:Seats` LocalZone (the `seats` report); the C library behind `:Platform` LocalTime | the zone `/etc/localtime` points to, else the C library's `%Z` | A leading `:` stripped. `UTC`, or a name with a file under `/usr/share/zoneinfo`, is the zone (the C library converts with TZ set to it: libc++ has no tzdb); any other name only labels the report. | The zone of the `seats` report and of every local timestamp (the default `--date`, the ledger's cycle) |
| `TMPDIR` | libc++'s `temp_directory_path`, from `:Seats` ProbeSeat and `:Freeze` | `/tmp` | The library's lookup: TMPDIR, TMP, TEMP, TEMPDIR. | Where the probes' config dirs (`rg-seat-*`, removed after) and freeze's temp dirs go |
| `HOST_XDG_{CONFIG,CACHE,DATA,STATE}_HOME` | `:Platform` RestoreHostXdg (Init, the sandbox only) | `$HOME/.config`, `.cache`, `.local/share`, `.local/state` | Non-empty: copied verbatim into `XDG_<KIND>_HOME`, which is always overwritten. Never read natively. | The host's XDG dirs for git, claude, npx and Playwright, instead of `~/.var/app/…` |
| `HOST_XDG_{CONFIG,CACHE,DATA,STATE}_HOME` | `:Platform` RestoreHostXdg (Init, the sandbox only) | `$HOME/.config`, `.cache`, `.local/share`, `.local/state` | Non-empty: copied verbatim into `XDG_<KIND>_HOME`, which is always overwritten. Never read natively. | The host's XDG dirs for git, claude, npx and Playwright, and for the machine's own seats file (`$XDG_CONFIG_HOME/crafter-review/env`, `SeatPool::GlobalEnvFile`), instead of `~/.var/app/…` |
| `SHELL` | `:Platform` RestoreShell (Init, the sandbox only) | — | Kept when it ends in `/bash` or `/zsh` and is executable; else `/bin/bash` when that is executable. Never read natively. | The shell claude's Bash tool runs commands with |
| `XDG_RUNTIME_DIR` | `:Platform` Paths (at Init, the sandbox only) | `/run/user/<uid>` | Empty counts as unset; verbatim. | Where `.flatpak/<instance>/bwrapinfo.json` gives the driver its host-pid identity |
| `XDG_CACHE_HOME` | `:Driver` PlaywrightBrowsers (`browser install`) | `~/.cache` | Empty counts as unset; in the Flatpak it already holds the host's. | The parent of `ms-playwright` when PLAYWRIGHT_BROWSERS_PATH is unset |
@ -232,7 +233,7 @@ In the tests only:
| `PATH` | LensTools | `""` | Colon-separated | Every program a lens's allowlist names, bash, node and npx must be on it: a skip natively, a FAIL inside the Flatpak |
| `FAKE_*` | the claude stand-ins of Runs and Scheduler | set by the test | — | Plumbing between a test and its stand-in |
Runs and Commands drop every `REVIEW_GATES_*`, `CRAFTER_REVIEW_*`, `SEATS_ENV_FILE`, seat secret and `FAKE_*` (Commands also `CLAUDE_BIN`) from the environment they run the binary with, then set their own `CRAFTER_REVIEW_HOME` and `REVIEW_GATES_BOARD` (Commands also `REVIEW_GATES_SCRATCH` and `TZ=UTC`): a developer's own settings never reach them.
Runs and Commands drop every `REVIEW_GATES_*`, `CRAFTER_REVIEW_*`, `SEATS_ENV_FILE` and seat secret (Runs also `FAKE_*`, Commands also `CLAUDE_BIN`) from the environment they run the binary with, then set their own `CRAFTER_REVIEW_HOME` and `REVIEW_GATES_BOARD` (Commands also `REVIEW_GATES_SCRATCH` and `TZ=UTC`), and Runs, Commands and KernelModes pin `XDG_CONFIG_HOME` and `HOST_XDG_CONFIG_HOME` (the Flatpak's `Platform::Init` copies the latter over the former) to `no-config` in their own temporary directory, a path nothing creates, so a developer's own `~/.config/crafter-review/env` stays out in both modes: a developer's own settings never reach them.
## CI and publishing