fix(outbox,toolbox): the class was every caller-supplied name, and containment was not unreachable

Session 146 fixed `--run-id` across the seven doors and called it a class. It was not: the class
is every caller-supplied value that reaches a file name, and `write_outbox` composes
`{run_id}-{approach_id}`. Measured 20.09 -- `--approach-id a/../../../ESCAPE` answered 0 and put
the artefacts three levels above the directory the caller named. Counted rather than assumed: 11
path compositions in `outbox.py`, 2 such values, both now through one `_checked_name`.

The ledger sentence that said containment was UNREACHABLE after the string rule was untrue, and
the approach-id escape is the disproof -- the removed check would have caught it. It is back, but
in `outbox._artefact_path`, where the composition is, not in the door. That is the difference
that makes it reachable: the string rule lives in the door, while `run.py` hands its own
`--run-id` straight to the writers and goes past it. Checked before the directory is created, so
a refusal leaves nothing behind, and it covers the next flag someone interpolates into a name.

The judge's exact call now answers 3 with 0 files outside. Suite 2291/0/5/5 (746 s), ruff clean,
mypy 0. Both gates re-run after `git add`: v1 exit 1 (0/3, 0/3, 3/8, no report, 3/8, NOT
MEASURED, 1/20), B exit 1 (15/17, 0/2, 15/15 over 516 files, 0/3, 4/5, NOT MEASURED) -- no row
moved, and row 3's denominator held because the probe grew in place rather than as a new file.

Also: the presentation deck said 1.1.0 was the current version in two places. 1.2.0 now stands in
every tracked place that claims the repo's version. No bump, no tag, no new capability.

[skip-docs]

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-20 13:12:15 +02:00
commit cec9b8fe50
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
5 changed files with 116 additions and 68 deletions

View file

@ -3576,25 +3576,37 @@
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`:
- **Et kaller-oppgitt NAVN bærer aldri en sti inn i et filnavn (20.09):** de sju utboks-dørene
bygger filnavnet som `<utkatalog>/<navn>-<artefakt>.json`. Utkatalogen var voktet (den er alltid
kallerens å navngi); navnene 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.
katalogen kalleren navnga. `_checked_name` avviser nå et navn 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.
**KLASSEN ble først satt for smalt, og det er rundens funn.** Første rettelse kalte klassen
«run-id-en i sju dører» og var da fortsatt usann: `write_outbox` bygger stammen
`{run_id}-{approach_id}`, så den ANDRE halvdelen av samme sammensetning sto åpen. MÅLT 20.09:
`--approach-id a/../../../ESCAPE` (med `<ut>/<run_id>-a` på plass) ga **exit 0** og la
artefaktene TRE nivåer over kallerens katalog. Klassen er hver kaller-oppgitt verdi som når et
filnavn — TELT i `outbox.py`: **11 stisammensetninger, 2 slike verdier**, begge gjennom samme
regel.
**Inneslutningen STÅR, som siste ledd, og prosaen om at den var uoppnåelig var USANN.** Den ble
fjernet i første rettelse fordi mutanten overlevde alle 36 armer, og begrunnelsen var at ingen
sammensetning KAN forlate katalogen etter strengregelen. Approach-id-rømningen er motbeviset:
den ville tatt akkurat dette. Sjekken bor nå der sammensetningen skjer — `outbox._artefact_path`,
som alle 11 går gjennom — og ikke i døren. Dét er forskjellen som gjør den oppnåelig: strengregelen
bor i døren, mens `run.py` sender sin egen `--run-id` rett inn i skriverne og går utenom den. Den
verner også mot det NESTE flagget noen legger inn i et filnavn uten å huske døren. Sjekket FØR
katalogen opprettes, så en nekt ikke legger igjen noe.
**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.
run-id-former, `--approach-id` × fire former mot den ene døren den når, og seks skrivere kalt
DIREKTE for inneslutningen. Hver arm asserterer ikke-null exit (eller `ValueError`) 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 og per skriver. To av
approach-id-armene var grønne ved HEAD på en manglende mellomkatalog før hver form fikk sin
opprettet — en grunn som ikke er vakten. 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.