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:
parent
8a6a09df05
commit
cec9b8fe50
5 changed files with 116 additions and 68 deletions
|
|
@ -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(
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue