Skip to content

fix(sourcehunt): anchor rejecting verdicts to a source location - #203

Open
tsunoda-kazuya wants to merge 2 commits into
Lazarus-AI:mainfrom
tsunoda-kazuya:fix/sourcehunt-verdict-location
Open

fix(sourcehunt): anchor rejecting verdicts to a source location#203
tsunoda-kazuya wants to merge 2 commits into
Lazarus-AI:mainfrom
tsunoda-kazuya:fix/sourcehunt-verdict-location

Conversation

@tsunoda-kazuya

Copy link
Copy Markdown
Contributor

Summary

  • Extend the validator verdict with tie_breaker_file: str | None and tie_breaker_line: int | None (ge=1).
  • Require both fields whenever advance is false, enforced by a Pydantic model_validator.
  • Ask the 4-axis prompt to set the two fields on rejection; the response will be rejected otherwise.
  • Legacy verdicts without the fields still load (the fields default to None on the persisted dataclass).

Problem

tie_breaker is free text. A rejection can cite evidence that no reader can locate, so false-reject audits fall back to reading prose. There is no mechanical way to point at "the single line the rejection rests on".

Fix

  • _VerdictSchema: add the two fields plus a @model_validator(mode="after") that raises ValueError when advance is False and either field is missing.
  • ValidatorVerdict (state.py): mirror the two optional fields; carried through from schema in to_verdict.
  • Prompt: one added sentence in the 4-axis instructions and two new keys in the JSON template. The 2-axis quick pass is unchanged.
  • apply_validator_verdict: write to finding["verifier_tie_breaker_file"] and finding["verifier_tie_breaker_line"].
  • _error_verdict bypasses the wire schema by constructing ValidatorVerdict directly, so internal error paths do not need to synthesise dummy locations.

Validation

  • uv run --frozen --extra dev pytest -q tests/test_sourcehunt_validator.py — 45 passed; new regressions: reject without file / without line raises ValidationError; advance=true accepts missing fields; tie_breaker_line=0 rejected; legacy verdicts without the fields still load; apply_validator_verdict writes both keys.
  • uv run --frozen --extra dev ruff check clearwing/sourcehunt/validator.py clearwing/sourcehunt/state.py tests/test_sourcehunt_validator.py
  • git diff --check

Backward compatibility

Persisted verdicts without the new fields remain readable (the dataclass defaults to None). Only newly produced rejections at the wire boundary must carry a location. Producers that emit valid JSON without the fields on advance=false will see a ValidationError — this is the intended contract change, exposing silent omissions that previously slipped through as unlocatable rejections.

Independent of the other 4 PRs in this batch; trivial rebase if any lands first.

Add optional tie_breaker_file and tie_breaker_line to the validator verdict,
threaded from the wire schema into the domain verdict and the persisted
finding. The 4-axis prompt asks for them on rejection so a rejection can be
located. The fields default to None, so verdicts that omit them are unchanged.
…line

Spec 009 §3.3: a validator verdict with advance=False must anchor its
rejection in the source so mechanised false-reject audits can locate it.
Enforce this at the wire schema with a model_validator that raises when
either tie_breaker_file or tie_breaker_line is missing on rejection, and
add Field(ge=1) on tie_breaker_line to stop 0 or negatives from being
persisted. The prompt is tightened to say the response will be rejected
otherwise. Advancing verdicts remain unconstrained.

Legacy verdicts read back via ValidatorVerdict (dataclass) bypass the
wire-schema validator and still load; the internal _error_verdict path
constructs ValidatorVerdict directly, so it also bypasses the check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant