← 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
M OVERNIGHT_LEDGER.mdM test-analyze-ocr.js
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 →