test(toolbox): approach-id is the other half of the same composition -- and containment is reachable

`--run-id` was guarded as a class; `--approach-id` was not. `write_outbox` composes
`{run_id}-{approach_id}`, so the same escape ran through the unguarded half: 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`, two caller-supplied values in
them. Four forms x the one door approach-id reaches, each with the intermediate directory
created first -- without it two of the four were green at HEAD on a missing directory, which is
a reason that is not the guard.

The containment check is asserted where it IS reachable: the writers themselves, called
directly. That is the run path's own way in -- `run.py` hands its `--run-id` straight to
`outbox.write_*`, past the string rule that lives in the door -- so the arms are not vacuous.

10 arms red on ASSERT about behaviour: 4 approach-id (exit 0), 6 writer-level (DID NOT RAISE).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-20 12:52:26 +02:00
commit 8a6a09df05
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q

View file

@ -1,4 +1,4 @@
"""`--run-id` navngir en kjøring, ikke en sti — og de sju utboks-dørene må håndheve det.
"""Ingen kaller-oppgitt NAVN bærer en sti inn i et filnavn — og de sju utboks-dørene håndhever det.
Hver skriver bygger filnavnet sitt som ``<utkatalog>/<run_id>-<artefakt>.json``. Utkatalogen er
kallerens å navngi (det er en egen regel), men run-id-en gikk urørt inn i den sammensetningen:
@ -13,6 +13,18 @@ lagt igjen fila ville passert på den første alene.
rc-0-kontrollen er obligatorisk: samme argv med en ren run-id må svare 0 og skrive. Uten den
måler armene ingenting — en dør som avviste ALT ville vært like grønn som den riktige.
`--run-id` var bare den ene halvdelen av SAMME sammensetning. `write_outbox` bygger
``{run_id}-{approach_id}`` når en tilnærming er navngitt, og `--approach-id` gikk urørt inn:
``--approach-id a/../../../ESCAPE`` (etter at ``<ut>/<run_id>-a`` finnes) svarte 0 og la
artefaktene TRE nivåer over katalogen kalleren navnga. Telt som klasse: 11 stisammensetninger i
`outbox.py`, to kaller-oppgitte verdier i dem — run-id og approach-id. Begge voktes her.
Og inneslutningen står igjen som SISTE ledd, men der den er OPPNÅELIG: i `outbox.py`, der stien
faktisk settes sammen. Strengregelen bor i døren, så kjørestiens egne kall på `outbox.write_*`
(og `run.py`s eget `--run-id`) går UTENOM den — inneslutnings-armene under kaller skriverne
direkte og er derfor ikke vakuøse. Den verner også mot det NESTE flagget noen legger inn i et
filnavn: sammensetningen er ett sted, og sjekken sitter i den.
"""
from __future__ import annotations
@ -26,6 +38,8 @@ from typing import Any
import pytest
from portfolio_optimiser import outbox
_REPO = Path(__file__).resolve().parents[1]
_IR = _REPO / "shared" / "examples" / "bygg-energi-mikro" / "validator-input.json"
@ -216,3 +230,124 @@ def test_write_coverage_refuses_to_invent_a_stop_reason(tmp_path: Path) -> None:
assert med.returncode == 0, med.stdout + med.stderr
paa_disk = json.loads(Path(json.loads(med.stdout)["path"]).read_text(encoding="utf-8"))
assert paa_disk["stop_reason"] == ""
#: De fire formene, som approach-id-en de settes inn som. `write_outbox` bygger stammen
#: ``{run_id}-{approach_id}``, så approach-id-en er et SUFFIKS i sammensetningen og ikke et eget
#: ledd: `../../ESCAPE` alene ville truffet et ikke-eksisterende `<run_id>-..` og gitt en
#: FileNotFoundError — altså grønt av feil grunn. Derfor går den relative formen gjennom et ledd
#: som FINNES (`a/../../../`), som er nøyaktig formen som ble målt 20.09.
_TILNAERMING_FORMER: dict[str, str] = {
"relativ-opp-gjennom-eksisterende": "a/../../../ESCAPE",
"med-skilletegn": "under/ESCAPE",
"bare-punktum": "..",
"absolutt": "/ABSOLUTT",
}
@pytest.mark.parametrize("form", sorted(_TILNAERMING_FORMER))
def test_an_approach_id_that_carries_a_path_is_refused_and_writes_nothing(
tmp_path: Path, form: str
) -> None:
"""`--approach-id` × den ene døren den når. Samme sammensetning, samme krav.
Mellomkatalogen den sammensatte stien peker gjennom opprettes FØRST, og det er ikke pynt:
uten den feiler skrivingen på en manglende katalog, og armen er grønn ved HEAD av en grunn
som ikke er vakten. Målt 20.09 — to av de fire formene var nettopp dét. Med katalogen på
plass svarer ALLE fire 0 og skriver ved HEAD, så hver av dem skiller de to
implementasjonene."""
fikstur = tmp_path / "fikstur"
ut = tmp_path / "inni" / "dypere"
verdi = _TILNAERMING_FORMER[form]
(ut / f"{_REN}-{verdi}-proposal.json").parent.mkdir(parents=True, exist_ok=True)
_, oevrige = _DOERER["write-outbox"]
argv = oevrige(fikstur)
foer = _tree(tmp_path)
proc = _toolbox(
"write-outbox",
"--outbox-dir",
str(ut),
"--run-id",
_REN,
"--approach-id",
verdi,
*argv,
)
assert proc.returncode != 0, f"write-outbox godtok en stibærende approach-id: {proc.stdout}"
assert _tree(tmp_path) == foer, f"write-outbox la igjen en fil for {form}"
def test_the_same_call_with_a_clean_approach_id_writes(tmp_path: Path) -> None:
"""rc-0-kontrollen for approach-id-en. Samme argv, eneste forskjell er verdien — og
artefaktene bærer den utvidede stammen inne i katalogen kalleren navnga."""
fikstur = tmp_path / "fikstur"
ut = tmp_path / "inni" / "dypere"
_, oevrige = _DOERER["write-outbox"]
proc = _toolbox(
"write-outbox",
"--outbox-dir",
str(ut),
"--run-id",
_REN,
"--approach-id",
"a",
*oevrige(fikstur),
)
assert proc.returncode == 0, proc.stdout + proc.stderr
skrevet = json.loads(proc.stdout)
sti = Path(skrevet["proposal_path"])
assert sti.is_file()
assert sti.name == f"{_REN}-a-proposal.json"
assert sti.resolve().parent == ut.resolve(), "artefaktet havnet utenfor utkatalogen"
#: Skriverne bak de sju dørene, hver kalt DIREKTE — utenom strengregelen, som bor i døren.
#: Det er kjørestiens egen vei (`run.py` sender sin `--run-id` rett inn her), så inneslutningen
#: er oppnåelig her og armene er ikke vakuøse.
_SKRIVERE: dict[str, Callable[[str, str], Path]] = {
"write_run_config": lambda d, r: outbox.write_run_config(
d,
r,
profile="local",
resolved_models={"proposer": "gpt-x"},
max_rounds=5,
max_tokens=600000,
top_k=3,
),
"write_coverage": lambda d, r: outbox.write_coverage(d, r, rows=[], stop_reason="tokens"),
"write_prepass": lambda d, r: outbox.write_prepass(d, r, declaration={"bundle_id": "x"}),
"write_parse_failures": lambda d, r: outbox.write_parse_failures(d, r, failures=[]),
"write_proposal_reviews": lambda d, r: outbox.write_proposal_reviews(
d, r, payload={"reviews": []}
),
"write_debate_tools": lambda d, r: outbox.write_debate_tools(d, r, tool_calls=[]),
}
@pytest.mark.parametrize("writer", sorted(_SKRIVERE))
def test_the_writer_itself_refuses_a_path_that_leaves_the_directory(
tmp_path: Path, writer: str
) -> None:
"""Inneslutningen som SISTE ledd, der sammensetningen skjer. En ferdig sti som ikke resolverer
UNDER utkatalogen er en feil, ikke en fil — og ingenting skal være skrevet når den felles."""
ut = tmp_path / "inni" / "dypere"
foer = _tree(tmp_path)
with pytest.raises(ValueError, match="utkatalogen|outside|outenfor|leaves"):
_SKRIVERE[writer](str(ut), "../../ESCAPE")
assert _tree(tmp_path) == foer, f"{writer} la igjen en fil for en rømmende sti"
@pytest.mark.parametrize("writer", sorted(_SKRIVERE))
def test_the_writer_itself_still_writes_a_contained_path(tmp_path: Path, writer: str) -> None:
"""rc-0-kontrollen for inneslutningen: samme kall med et rent navn skriver, inne i katalogen.
Uten den ville en skriver som nektet ALT vært like grønn som den riktige."""
ut = tmp_path / "inni" / "dypere"
sti = _SKRIVERE[writer](str(ut), _REN)
assert sti.is_file()
assert sti.resolve().parent == ut.resolve()