[object Object]

← back to Dw Photo Capture

test: lock candidate-order contract scanResolve relies on (cycle 17 audit)

d9df0cb85cd0d80d06972c10cb6c76a9bc146836 · 2026-06-26 07:33:06 -0700 · Steve Abrams

Audited resolution idea #3 (multi-candidate → different products). Found scanResolve's
first-hit-wins is ALREADY correct because analyzeOcr orders candidates strongest-first:
barcode > brand-prefixed code > larger-font > generic. Verified empirically (WDW2310
beats XQ5599 at equal font height; a barcode beats a 200-height OCR code; CD2222 beats
AB1111 on font size). No code change — locked the contract with 3 regression tests so a
future analyzeOcr change can't silently break scanResolve's reliance on the ordering.

Deferred known-minor (logged): scanResolve runs all candidates through the TWIL catalog
before any through Shopify, so a weak TWIL hit can beat a barcode that only resolves in
Shopify — narrow edge, deliberate source preference, revisit only if seen in real use.
48→51 tests, lint clean. $0 (local).

Files touched

Diff

commit d9df0cb85cd0d80d06972c10cb6c76a9bc146836
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jun 26 07:33:06 2026 -0700

    test: lock candidate-order contract scanResolve relies on (cycle 17 audit)
    
    Audited resolution idea #3 (multi-candidate → different products). Found scanResolve's
    first-hit-wins is ALREADY correct because analyzeOcr orders candidates strongest-first:
    barcode > brand-prefixed code > larger-font > generic. Verified empirically (WDW2310
    beats XQ5599 at equal font height; a barcode beats a 200-height OCR code; CD2222 beats
    AB1111 on font size). No code change — locked the contract with 3 regression tests so a
    future analyzeOcr change can't silently break scanResolve's reliance on the ordering.
    
    Deferred known-minor (logged): scanResolve runs all candidates through the TWIL catalog
    before any through Shopify, so a weak TWIL hit can beat a barcode that only resolves in
    Shopify — narrow edge, deliberate source preference, revisit only if seen in real use.
    48→51 tests, lint clean. $0 (local).
---
 OVERNIGHT_LEDGER.md |  4 ++++
 test-analyze-ocr.js | 15 +++++++++++++++
 2 files changed, 19 insertions(+)

diff --git a/OVERNIGHT_LEDGER.md b/OVERNIGHT_LEDGER.md
index 6816ed4..0afc9d2 100644
--- a/OVERNIGHT_LEDGER.md
+++ b/OVERNIGHT_LEDGER.md
@@ -59,3 +59,7 @@ the single highest-value safe one, the officer executes it, and the loop resched
 | 14 | brainstorm #1 (learning-loop) | profileToLex prefix support gate (>=2 scans) — one bad scan can no longer inject a wrong prefix into the ranking lexicon; catalog prefixes (builder-vetted n>=3) unaffected; +5 profileToLex tests | df 27G healthy; real-data impact = 1 unconfirmed prefix gated (WC test artifact), 0 vendors lose matching; 41→46 tests; lint clean; restart 200 | $0 |
 | 15 | brainstorm #4 (SECURITY) | found+fixed a real DoS: vendor="__proto__" → VENDOR_PROFILES["__proto__"] returns Object.prototype → prof.prefixes deref CRASHED the process. Guard rejects __proto__/constructor/prototype with 400 | df 31G; exploit reproduced (HTTP 000 + crash, launchd auto-restarted); post-fix all 3 → 400, server stays up, normal learn 200 | $0 |
 | 16 | sweep (defer-w-evidence) + fuzzy ambiguity guard | (a) proto-key sweep: only 2 OBJ[key]= sites, vendor-key fixed cycle15, prefix-key proven safe live (CONSTRUCTOR/PROTOTYPE skus→200). (b) fuzzy-collision audit found REAL collisions (Alan~Nina Campbell 0.33, Retro~Rebel Walls 0.30) → added ambiguity guard: 2 different vendors within margin → null (no wrong-vendor binding) | df 29G; 46→48 tests; lint clean; CAMPBELL→null, NINA CAMPBELL→Nina, BRUNSWIG→Brunschwig preserved; live identify no-regression | $0 |
+| 17 | audit idea #3 → defer-w-evidence + lock contract | scanResolve first-hit-wins is ALREADY correct: analyzeOcr orders candidates barcode > brand-prefixed > larger-font > generic (verified: WDW2310>XQ5599 at equal height, barcode>huge-font, CD2222>AB1111). No change to scanResolve; locked the order with 3 regression tests. Residual: TWIL-pass beats Shopify-pass regardless of candidate strength (narrow, deliberate source-preference) — deferred known-minor | df 21G; 48→51 tests; lint clean | $0 |
+
+### Deferred known-minor (cycle 17, low value)
+- scanResolve runs ALL candidates through TWIL (pass 1) before ANY through Shopify (pass 2), so a weak candidate that hits TWIL beats a barcode that only resolves in Shopify. Narrow edge (barcode present + fails TWIL + weak code hits TWIL); the TWIL-first source preference is deliberate. Revisit only if it surfaces in real use.
diff --git a/test-analyze-ocr.js b/test-analyze-ocr.js
index 7672f70..7955329 100644
--- a/test-analyze-ocr.js
+++ b/test-analyze-ocr.js
@@ -164,6 +164,21 @@ const bc = (payload, sym = 'Code128') => `BC\t${payload}\t${sym}`;
   ok(twoBc.topStrong === true, 'edge: a barcode forces topStrong even with two present');
 }
 
+// ── candidate ORDER contract that scanResolve depends on (cycle 17 audit) ──
+// scanResolve loops candidates first-hit-wins, so the strongest must come first:
+// barcode > brand-prefixed code > larger-font > generic. Lock that order.
+{
+  // brand-prefixed code outranks a generic code at the SAME font height (vendor on swatch)
+  const bp = analyzeOcr(parseOcrRows([line(40, 'WINFIELD THYBONY'), line(100, 'WDW2310'), line(100, 'XQ5599')].join('\n')));
+  eq(bp.candidates[0], 'WDW2310', 'order: brand-prefixed code beats generic at equal font height');
+  // a barcode beats even a much larger-font OCR code
+  const bcWin = analyzeOcr(parseOcrRows([line(200, 'BIGCODE99'), bc('GRS-7777')].join('\n')));
+  eq(bcWin.candidates[0], 'GRS-7777', 'order: barcode beats a huge-font OCR code');
+  // with no barcode/brand, the larger-font generic code leads
+  const fontWin = analyzeOcr(parseOcrRows([line(60, 'AB1111'), line(140, 'CD2222')].join('\n')));
+  eq(fontWin.candidates[0], 'CD2222', 'order: larger font wins among generic codes');
+}
+
 // ── fingerprintVendor edge cases (cycle 12 brainstorm: harden the tiebreaker contract) ──
 {
   eq(fingerprintVendor('', 'serif'), null, 'fp edge: empty read → null');

← 5980db1 auto-save: 2026-06-26T07:13:19 (2 files) — data/build.json d  ·  back to Dw Photo Capture  ·  robustness: atomic saveJSON so a crash/ENOSPC can't corrupt 4d8fe6d →