feat(pre-push-safety-net): scope comparison completeness and ambiguity (M1b 10.2, 10.3)

Two vetoes M1a recorded were global where the evidence is local.

Comparison required every selected base question to complete, so one unsupported
family anywhere prevented an otherwise complete target comparison. Completeness
is now demanded over the required scope: the counterpart question plus the
dependencies the change context actually recorded for it. Every other gap stays
in aggregate coverage and is still reported, never discharged. An incomplete
counterpart, an incomplete recorded dependency, an unknown-ownership boundary
and a truncated base scan all still yield unknown.

Correspondence ambiguity was recorded file-wide and inherited by every question,
so repeated unchanged lines in one file invalidated every question in the
repository and blocked the whole transaction comparison. It is now decided per
operation by the correspondence classifier: an operation needing an ambiguous
anchor stays uncomparable, while a uniquely mapped or newly introduced operation
in the same file is unaffected. The mapping is still withheld and still reported.

On the NodeGoat case the ambiguity is on the target's own file, so file-level
scoping would not have been enough; only per-operation resolution is.

1,347 tests passed, nine skipped. No capability enabled; rollout stays NO-GO.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-09-16 15:45:57 +02:00
co-authored by Claude Opus 5
parent 7e0d01d9e8
commit 8ff1d9a638
5 changed files with 182 additions and 23 deletions
+9 -1
View File
@@ -179,6 +179,9 @@ def _families(language: str, *, has_handler: bool) -> tuple[str, ...]:
return tuple(sorted(families))
AMBIGUOUS_CORRESPONDENCE = "ambiguous_line_correspondence"
def affected_context(
context: ChangeContext,
questions: Sequence[QuestionIdentity],
@@ -202,7 +205,12 @@ def affected_context(
gaps.append("configuration_dependency_projection_unavailable:contributor-scan")
if context.base_revision is None:
gaps.append("comparison_base_unavailable")
inherited_gap = bool(gaps)
# Line-correspondence ambiguity is not a context gap: it says which base lines a head
# line could map to, which only the later per-operation comparison consumes. Inheriting
# it here made one file with repeated unchanged lines invalidate every question in the
# repository. The mapping itself is still withheld, so an operation that needs an
# ambiguous anchor remains uncomparable.
inherited_gap = any(not gap.endswith(":" + AMBIGUOUS_CORRESPONDENCE) for gap in gaps)
renamed = {context.decode_path(r.base_path): context.decode_path(r.head_path) for r in context.renames}
for identity in questions:
if execution_budget is not None and time.monotonic() >= execution_budget.deadline_monotonic: