fix(tools): do not blame the plan for a verdict okf_adjudicate built
run() parses the plan before building anything and parses its own
verdict after. Only the first failure is the operator's file.
The second is reachable: an empty --adjudicator produces a verdict the
grammar refuses ("adjudication field 'adjudicated_by' must be a
non-empty string"). The plan parsed fine; the fault is in what this
command stamped onto it.
Catching SegmentationError at the top of main() -- the previous commit
-- caught both raise sites and printed "malformed plan" for each. On
this path that is a clean, confident, WRONG diagnosis: it sends the
operator to fix the one artifact that was fine. Worse than the traceback
it replaced, because a traceback at least does not claim to know.
The verdict parse now raises AdjudicationError, which is what "this
command failed" already means in this file and already returns 2. Exit
code unchanged either way, nothing written either way; only the message
changes.
Suite 1073 -> 1074 passed (pytest exit 0, measured without a pipe);
ruff and mypy --strict clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
a72053f66f
commit
798f64ad6b
3 changed files with 55 additions and 1 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -275,6 +275,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
belonged to. Pre-existing since the empty-verdict branch landed, and pinned
|
||||
until now by a test that recorded it as a finding rather than fixing it.
|
||||
|
||||
- **A verdict `okf_adjudicate` cannot read back is no longer reported as a
|
||||
malformed plan.** The command parses the plan before building anything and
|
||||
parses its own verdict after, so only the first failure is the operator's
|
||||
file. The second is reachable -- an empty `--adjudicator` produces a verdict
|
||||
the grammar refuses -- and before the change above it surfaced as a
|
||||
traceback. Catching the grammar error at the top would have turned it into a
|
||||
clean, confident, wrong diagnosis naming the plan file, which is worse than
|
||||
the traceback: it sends the operator to fix the one artifact that was fine.
|
||||
The verdict parse now raises `AdjudicationError`, so the message names what
|
||||
actually failed. Exit 2 either way; nothing is written either way.
|
||||
|
||||
## [0.5.0a2] — 2026-07-31
|
||||
|
||||
**This is the pre-release the pilots pin. `v0.5.0a1` was tagged and abandoned
|
||||
|
|
|
|||
|
|
@ -347,6 +347,40 @@ def test_an_entries_value_that_is_not_a_list_is_still_refused(
|
|||
assert not (tmp_path / "verdict.json").exists()
|
||||
|
||||
|
||||
def test_a_verdict_this_tool_cannot_read_back_is_not_blamed_on_the_plan(
|
||||
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""The plan is parsed BEFORE the verdict is built, and the verdict is parsed
|
||||
after. Only the first failure is the plan's.
|
||||
|
||||
A verdict that will not parse back is THIS command failing on what it was
|
||||
told to stamp -- here an empty `--adjudicator`. Reporting that as a
|
||||
malformed plan sends the operator to fix the one artifact that was fine,
|
||||
which is worse than the traceback it replaced: a clean, confident, wrong
|
||||
diagnosis."""
|
||||
plan_path = proposal(tmp_path)
|
||||
capsys.readouterr() # the proposer's own report is not what is under test
|
||||
|
||||
code = okf_adjudicate.main(
|
||||
[
|
||||
"--plan",
|
||||
str(plan_path),
|
||||
"--out",
|
||||
str(tmp_path / "verdict.json"),
|
||||
"--adjudicator",
|
||||
"",
|
||||
"--adjudicated-at",
|
||||
AT,
|
||||
]
|
||||
)
|
||||
|
||||
assert code == 2
|
||||
stderr = capsys.readouterr().err
|
||||
assert "malformed plan" not in stderr
|
||||
assert "verdict" in stderr
|
||||
assert not (tmp_path / "verdict.json").exists()
|
||||
|
||||
|
||||
def test_a_malformed_non_empty_plan_exits_two_with_one_stderr_line(
|
||||
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
|
|
|
|||
|
|
@ -213,7 +213,16 @@ def run(
|
|||
verdict = build_verdict(
|
||||
payload, adjudicator=adjudicator, adjudicated_at=adjudicated_at, dwell_s=dwell_s
|
||||
)
|
||||
parse_segmentation_plan(verdict)
|
||||
# The plan already parsed above, so a failure HERE is this command's
|
||||
# own output, not the operator's file. Raised as an AdjudicationError
|
||||
# so it is not reported as a malformed plan: that would send the
|
||||
# operator to fix the one artifact that was fine.
|
||||
try:
|
||||
parse_segmentation_plan(verdict)
|
||||
except SegmentationError as exc:
|
||||
raise AdjudicationError(
|
||||
f"the verdict this command built does not parse back [{exc.code}]: {exc}"
|
||||
) from exc
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
out_path.write_text(
|
||||
json.dumps(verdict, indent=2, ensure_ascii=False) + "\n", encoding="utf-8", newline=""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue