docs(invarianter): the run id that carried a path, and the rule that had no guard [skip-docs]

Two rows and the matching CHANGELOG entries. The first records the measurement rather than
the decision: `--outbox-dir <d>/inni --run-id ../../ESCAPE` answered 0 and wrote two levels
above the directory the caller named, on a door whose directory argument was already
guarded. It also records the check that did NOT survive -- the containment half was
unreachable after the string rule and is written down as dead code removed, not as a second
layer of defence.

The second row is the smaller and older failure: a rule called load-bearing in prose, with
no arm that would notice if it were deleted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-20 11:49:35 +02:00
commit 705bcfed6f
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
2 changed files with 38 additions and 0 deletions

View file

@ -31,6 +31,19 @@ at all, and this release gives each of them a command of its own.
data that came back from it. Fifteen of seventeen steps at this release; the round binder and its
report share one module entry point without a subcommand that tells them apart.
### Security
- A `--run-id` that carries a path is refused by all seven outbox doors before anything is written.
Each writer composes `<out_dir>/<run_id>-<artefact>.json`; the directory was guarded and the run
id was not, so `--outbox-dir <d>/inni --run-id ../../X` wrote two levels above the directory the
caller named and answered `0`. Separators, a bare `.` or `..`, an absolute prefix, an embedded
NUL and the empty string are now refused with exit `3`, the reason named, and no directory left
behind. Fixed as a class, because the composition is identical in all seven.
### Fixed
- `--stop-reason` being required is now guarded by a test rather than only by prose. The rule is
load-bearing - "the run finished" and "we never found out" must not be the same value - but
swapping `required=True` for `default=""` passed the entire suite before this release.
### Changed
- Console entry points are pinned against the **installed** distribution's metadata rather than
against `pyproject.toml`: a `[project.scripts]` line that has never been installed is a claim,

View file

@ -3576,3 +3576,28 @@
vei til å produsere rapporten — ikke en dør over et eksisterende steg. Å gi proben et navn i
argv ved å døpe en katalog «rapport» ville vært å tøye proben for å få tallet. Rad 1 er derfor
**15 av 17**, uttalt, ikke 17.
- **`--run-id` navngir en kjøring, aldri en sti (20.09):** de sju utboks-dørene bygger filnavnet
som `<utkatalog>/<run_id>-<artefakt>.json`. Utkatalogen var voktet (den er alltid kallerens å
navngi); run-id-en gikk urørt inn i den samme sammensetningen. MÅLT på `write-prepass`:
`--outbox-dir <kat>/inni --run-id ../../ESCAPE` ga **exit 0** og la artefaktet TO nivåer OVER
katalogen kalleren navnga. Vakten sto på den ene halvdelen av stien og manglet på den andre.
`_checked_run_id` avviser nå en run-id som BÆRER en sti — skilletegn (`/`, `\`, `os.sep`,
`os.altsep`), bar `.` eller `..`, absolutt prefiks, innleiret NUL, tom streng — FØR noen dør
rører filsystemet: exit **3** med grunnen navngitt og ingen katalog lagt igjen. Rettet som
KLASSE i alle sju, fordi sammensetningen er den samme i alle sju.
**ÉN sjekk, ikke to, og det er en MÅLING.** Første utkast krevde i tillegg at den sammensatte
stien resolverte INNI den resolverte utkatalogen. Den mutanten overlevde alle 36 armer: etter
strengregelen finnes det ingen sammensetning som KAN forlate katalogen, så inneslutnings-
sjekken var uoppnåelig og lignet bare på forsvar. Den er borte, og egenskapen den påsto
asserteres der den ER oppnåelig — i probens aksepterende arm.
**Load-bearing test:** `tests/test_toolbox_run_id_containment.py` — sju dører × fire stibærende
former, hver arm asserterer ikke-null exit OG at filtreet under `tmp_path` er byte-uendret
etterpå (den første alene ville vært tilfreds med en dør som avviste ETTER å ha skrevet), med
en rc-0-kontroll per dør. Mutanter: vakten fjernet fra én dør → 4 armer faller; hele vakten av
→ alle 28 rømningsarmer faller.
- **`--stop-reason` er PÅKREVD, og regelen har nå en vakt (20.09):** «kjøringen ble ferdig» og «vi
fikk aldri vite» må ikke være samme verdi. Koden var riktig, men `required=True` byttet til
`default=""` passerte HELE suiten — regelen var bærende i prosa og uvoktet i test.
`test_write_coverage_refuses_to_invent_a_stop_reason` feller mutanten: uten flagget er kallet en
usage-feil (exit 2, flagget navngitt i stderr, ingen katalog lagt igjen), mens den tomme
strengen fortsatt er en verdi kalleren kan OPPGI med vilje.