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

@ -46,6 +46,31 @@ def _dump(payload: dict[str, Any]) -> str:
return json.dumps(payload, sort_keys=True, indent=2) + "\n"
def _artefact_path(directory: Path, name: str) -> Path:
"""Compose ONE artefact path, and refuse one that leaves the directory — the last link.
Every writer below builds ``<directory>/<name>``, where ``name`` interpolates caller-supplied
values. The doors (``toolbox._checked_name``) reject a value that carries a path before it
ever gets here, and that string rule is the first link. This is the last one, and it is not
the same check twice: it looks at the FINISHED path, so it holds for the NEXT value someone
interpolates into a file name without remembering the door — and for the callers that never
pass a door at all. ``run.py`` hands its ``--run-id`` straight to these writers, so the
property is reachable here in a way it was not inside the door, where mutating it away in
session 146 left all 36 arms green and it was removed as dead code.
Checked BEFORE the directory is created, so a refusal leaves nothing behind — not the
artefact, and not a folder the caller would have to clean up."""
path = directory / name
root = directory.resolve()
if root not in path.resolve().parents:
raise ValueError(
f"{name!r} resolves outside {directory}: an artefact belongs in the output directory "
"the caller named — utkatalogen — and this path leaves it."
)
directory.mkdir(parents=True, exist_ok=True)
return path
def write_outbox(
outbox_dir: str,
run_id: str,
@ -70,14 +95,16 @@ def write_outbox(
rather than written as null, since these artefacts are byte-deterministic by contract and a run
nobody commissioned has no approach to name."""
directory = Path(outbox_dir)
directory.mkdir(parents=True, exist_ok=True)
stem = run_id if approach_id is None else f"{run_id}-{approach_id}"
keys: dict[str, Any] = {"run_id": run_id}
if approach_id is not None:
keys["approach_id"] = approach_id
proposal_path = directory / f"{stem}-proposal.json"
# Both paths BEFORE either write: a pair where the second escapes must not leave the first
# on disk, which is what composing them one at a time would do.
proposal_path = _artefact_path(directory, f"{stem}-proposal.json")
outcome_path = _artefact_path(directory, f"{stem}-outcome.json")
proposal_path.write_text(
_dump(
{
@ -89,7 +116,6 @@ def write_outbox(
encoding="utf-8",
)
outcome_path = directory / f"{stem}-outcome.json"
outcome_path.write_text(
_dump(
{
@ -170,8 +196,7 @@ def write_parse_failures(
consistency of reading, not to pin bytes. The caller writes it only when there is at least one
failure, so the file's PRESENCE is itself the signal that something did not parse."""
directory = Path(outbox_dir)
directory.mkdir(parents=True, exist_ok=True)
path = directory / f"{run_id}-parse-failures.json"
path = _artefact_path(directory, f"{run_id}-parse-failures.json")
path.write_text(
_dump({"run_id": run_id, "parse_failures": [dict(f) for f in failures]}),
encoding="utf-8",
@ -197,8 +222,7 @@ def write_exploration(
written even when the exploration RAISED — the caller writes it from a ``finally``, because a
capped exploration is precisely the one whose per-round ledger a reader needs."""
directory = Path(outbox_dir)
directory.mkdir(parents=True, exist_ok=True)
path = directory / f"{run_id}-exploration.json"
path = _artefact_path(directory, f"{run_id}-exploration.json")
path.write_text(_dump({"run_id": run_id, **dict(payload)}), encoding="utf-8")
return path
@ -226,8 +250,7 @@ def write_debate_tools(
Takes already-rendered plain mappings (``explore.tool_call_payload``) so the RAW output layer
stays MAF-free — ``write_exploration``'s own rule, same reason."""
directory = Path(outbox_dir)
directory.mkdir(parents=True, exist_ok=True)
path = directory / f"{run_id}-debate.json"
path = _artefact_path(directory, f"{run_id}-debate.json")
path.write_text(
_dump(
{
@ -270,8 +293,7 @@ def write_multibase(
Plain data only, so the RAW output layer stays MAF-free (``write_debate_tools``' own rule)."""
directory = Path(outbox_dir)
directory.mkdir(parents=True, exist_ok=True)
path = directory / f"{run_id}-multibase.json"
path = _artefact_path(directory, f"{run_id}-multibase.json")
path.write_text(
_dump(
{
@ -312,8 +334,7 @@ def write_prepass(
Takes an already-rendered plain mapping (``prepass.declaration_payload``) so the RAW output
layer stays framework-free — ``write_exploration``'s rule, same reason."""
directory = Path(outbox_dir)
directory.mkdir(parents=True, exist_ok=True)
path = directory / f"{run_id}-prepass.json"
path = _artefact_path(directory, f"{run_id}-prepass.json")
path.write_text(_dump({"run_id": run_id, "prepass": dict(declaration)}), encoding="utf-8")
return path
@ -341,8 +362,7 @@ def write_plan_review(
the loop has already moved past. Staleness is caught anyway — the answer names the
``request_id`` it answers — but the file should not invite it."""
directory = Path(outbox_dir)
directory.mkdir(parents=True, exist_ok=True)
path = directory / f"{run_id}-plan-review.json"
path = _artefact_path(directory, f"{run_id}-plan-review.json")
path.write_text(_dump({"run_id": run_id, **dict(payload)}), encoding="utf-8")
return path
@ -369,8 +389,7 @@ def write_proposal_reviews(
validated — is a fact this artefact must be able to STATE, not something an operator has to
infer from a missing file. "Iff a reviewer was given" is the only rule that keeps both."""
directory = Path(outbox_dir)
directory.mkdir(parents=True, exist_ok=True)
path = directory / f"{run_id}-proposal-reviews.json"
path = _artefact_path(directory, f"{run_id}-proposal-reviews.json")
path.write_text(_dump({"run_id": run_id, **dict(payload)}), encoding="utf-8")
return path
@ -397,8 +416,7 @@ def write_coverage(
Byte-deterministic and wall-clock-free, mirroring ``write_run_config``; plain data only, so the
RAW output layer stays MAF-free."""
directory = Path(outbox_dir)
directory.mkdir(parents=True, exist_ok=True)
path = directory / f"{run_id}-coverage.json"
path = _artefact_path(directory, f"{run_id}-coverage.json")
path.write_text(
_dump(
{
@ -429,8 +447,7 @@ def write_run_config(
NO wall-clock / date (that lives in the S11 report envelope, not the deterministic artefact), so
two runs with identical config produce byte-identical files (mirrors ``write_outbox``)."""
directory = Path(config_dir)
directory.mkdir(parents=True, exist_ok=True)
path = directory / f"{run_id}-runconfig.json"
path = _artefact_path(directory, f"{run_id}-runconfig.json")
path.write_text(
_dump(
{