Skip to content

fix(resolver): refuse ambiguous OCR targets#226

Merged
abrichr merged 3 commits into
mainfrom
fix/ocr-ambiguity-local-first-20260721
Jul 21, 2026
Merged

fix(resolver): refuse ambiguous OCR targets#226
abrichr merged 3 commits into
mainfrom
fix/ocr-ambiguity-local-first-20260721

Conversation

@abrichr

@abrichr abrichr commented Jul 21, 2026

Copy link
Copy Markdown
Member

What changed

  • searches the recorded padded OCR region before considering the full frame
  • requires unique OCR targeting evidence and converts duplicate target or landmark candidates into a typed resolution refusal
  • prevents OCR ambiguity from being misread as absence and falling through to geometry or model grounding
  • preserves successful resolution for one local or globally moved unique target
  • preserves the historical best-match behavior for non-targeting readiness/presence consumers
  • refuses OCR candidates contradicted by recorded landmark position evidence
  • adds deterministic synthetic mechanism tests with no customer, deployment, tuned-threshold, or real-application data
  • updates the resolver/vision contract and all in-repository scripted vision providers

Why

PR #167 is a historical adversarial battery explicitly marked DO NOT MERGE. Its useful unresolved class is repeated OCR labels on pixel-only surfaces. This replacement turns that class into a permanent fail-closed implementation and deterministic regression suite rather than merging historical xfail evidence.

The initial local-first patch represented both “not found” and “multiple candidates” as None. That allowed ambiguity to fall through to weaker geometry or grounding. The final patch adds a typed ambiguity signal consumed by the resolver, so only a true miss may continue down the ladder.

Validation

  • 230 passed, 3 deselected across resolver, OCR, replay, healing, backend-factory, and structural-unit suites (the deselected tests launch Playwright locally; protected CI runs them)
  • strict safety-path mypy: 21 source files, zero errors
  • Ruff source lint and source/tests format checks: pass
  • docs/version/path consistency and claims gate: pass
  • release/source guard: pass
  • release-contract and benchmark-boundary tests: 61 pass (the one loopback fixture was rerun outside the socket sandbox)
  • wheel + sdist boundary validation: pass; no openimis, AGPL, private corpus, identity ROC, or real-EMR payloads
  • wheel SHA-256: 240924ff7c2027afd7cbcaa93282072545764844ef01102f6d31e1cc43bbcc02
  • sdist SHA-256: e14b141de46cda49afb8fe6c3168fa228070b6dc105018a9057b3b4623ee2205

Disposition

After this PR passes protected CI and independent review, merge this replacement. Then close #167 unmerged with a pointer here; retain its branch/history as the original design record.

@abrichr

abrichr commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

Patched the protected e2e-browser regression at exact head a689272.

Root cause: the first safety pass flattened all repeated OCR labels into an immediate refusal and treated stale fixed-offset landmarks as a veto, which over-halted four valid drift runs. The repair enumerates candidates and selects only when independent retained evidence uniquely establishes one (recorded target region and/or landmark relations). It never selects by OCR order or fuzzy-score arg-max. A sole target label survives legitimate layout reflow. Conflicting observed candidates and unresolved repeats remain typed terminal halts.

Also incorporated the cross-run MoveDrift evidence: incompatible geometry estimates are never averaged. A unique in-recorded-region estimate can win; otherwise only a unique coherent cluster can win, with tied/unresolved conflicts halting.

Local evidence on this head:

  • exact four failed browser contracts: 4 passed
  • repeated-row wrong-record adversaries: 3 passed
  • focused resolver/replayer/vision/structural unit set: 225 passed (plus 2 headless structural probes passed separately)
  • expanded focused set after geometry repair: 136 passed
  • exact fix(mockmed): stabilize textarea render metrics #227 MoveDrift step_008 artifact resolves to (344,296), not the synthetic averaged (690,296)
  • final MoveDrift record/compile/replay: passed
  • strict mypy + Ruff: passed
  • actual wheel/sdist release boundary: passed; no AGPL/openIMIS/private corpus material

PR remains draft and unmerged while protected CI runs.

@abrichr
abrichr force-pushed the fix/ocr-ambiguity-local-first-20260721 branch from a689272 to 429824f Compare July 21, 2026 17:24
@abrichr

abrichr commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

Independent follow-up review incorporated at 429824f:

  • Geometry now computes coherent clusters before granting recorded-region locality precedence.
  • If a stale in-region singleton disagrees with a unique >=2-landmark cluster at a moved target, the conflict typed-halts; it never silently chooses the singleton or averages the coordinates.
  • Added order-independent 3-estimate regressions for that exact case.
  • Preserved the fix(mockmed): stabilize textarea render metrics #227 two-landmark MoveDrift repair: one valid in-region estimate plus one stale outlier still resolves exactly to (344,296).
  • Narrowed DESIGN wording for local template matching to its literal behavior: repeated widgets are refused when no candidate is near the recorded position.

Focused rerun: 138 passed; new geometry subset 12 passed; Ruff, formatting, strict mypy, and artifact replay check passed. CI restarted on this exact head.

@abrichr
abrichr force-pushed the fix/ocr-ambiguity-local-first-20260721 branch from 429824f to 6d0151d Compare July 21, 2026 17:29
@abrichr

abrichr commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

Stale merge base corrected at exact head 6d0151dc4423aa223a85514ba22199f718d9e3af.

Verified:

  • origin/main: bd2a27caf300e58d2b19dcee3b1493c5ab6fa813
  • merge base: exact same bd2a27c
  • git range-diff marks all three resolver commits patch-equivalent to the prior series
  • diff is limited to 17 intended resolver/OCR/replayer/validation/test/DESIGN paths: 1,287 insertions, 60 deletions
  • no lending benchmark, MockLoan, paper, public-artifact, or release-script reversions
  • focused rebased tests: 138 passed; Ruff/format and strict resolver/OCR mypy passed
  • rebased wheel/sdist boundary passed against current main

PR remains draft and unmerged. Protected CI restarted on this exact head.

@abrichr

abrichr commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

Cross-PR integration evidence for the current exact heads:

The combined tree passed the focused headless browser suite: 18 passed in 187.84s (three baseline replays, MoveDrift plus healed replay, and all cosmetic-drift tests).

The formerly failing moved-textarea path resolved step_008 through geometry at the correct (344, 296) point; step_009 then verified the typed input without a retry. All three baseline replays were 8/8 template with zero heals, and the healed replay returned to 8/8 template with zero heals.

Recommended order: land #226 after its own required CI is green, then rebase #227 and rerun its required/full macOS CI before landing #227.

@abrichr
abrichr marked this pull request as ready for review July 21, 2026 17:38
@abrichr
abrichr merged commit af143f2 into main Jul 21, 2026
17 checks passed
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