Delete the public /api/config/gemini endpoint and remove client auto-load paths so GEMINI_AI_API_KEY stays server-side only. Add a scan rate-limit class for the upcoming server-side identify route and a CI gate that blocks reintroducing config key leaks or new browser LLM URLs. Co-authored-by: Cursor <cursoragent@cursor.com>
4.9 KiB
4.9 KiB
| name | classification | success_metric | skip | status | created | depends_on | ||
|---|---|---|---|---|---|---|---|---|
| add-real-ocr-layer | feature | ≥70% of legitimate scans resolve at Layer-1 (Tesseract + pg_trgm) with zero Gemini calls; scan_attempts.layer distribution proves it. |
|
open | 2026-05-27 |
|
Convoy: add-real-ocr-layer
Add a cheap local OCR + fuzzy DB match layer so most scans never hit Gemini.
Why
Every scan today (post convoy #2) calls Gemini Flash server-side. That is slow, costs money, and hits rate limits under bulk scanning. The audit proposed a two-layer pipeline: Layer-1 runs Tesseract on the card name strip
pg_trgmsimilarity againstcards.name; only escalates to/api/scan/identify(Gemini) when confidence is low. Target: ≥70% Layer-1 hit rate measured viascan_attempts.layer.
Scope
In scope
- Migration — enable
pg_trgmextension; GIN index oncards (name gin_trgm_ops). pages/api/cards/identify-by-text.js(new):- similarity > 0.85 → single match
- 0.6–0.85 → disambiguation list
- < 0.6 →
{ escalate: true }(client falls back to/api/scan/identify)
tesseract.jsdependency +lib/ocr-worker.js(new) — browser Worker wrapping Tesseract; OCR name strip before API calls.components/CameraScanner.js— integrate Worker: try Layer-1 path first, escalate on low confidence.docs/SCHEMA_MAP.md— index + extension documented..github/workflows/ci.yml— piggyback fix: extendschema-map-freshjobif:condition to includemigrations/**paths (currently only watchesscripts/add-*,scripts/fix-*,setup-neon-db.js, anddocs/SCHEMA_MAP.md— misses post-migration-toolmigrations).
Out of scope
- OpenCV perspective transform / card boundary detection — queue
improve-scan-card-detectionif Layer-1 hit rate stays below 70% after this lands. - Retraining or custom ML models — Tesseract + trigram is sufficient for v1.
- Scanner UX redesign — convoy
redesign-scanner-flow(#4).
Roles invoked
role-ux-reviewer— Layer-1 vs escalation feedback (scanning status).role-architect— similarity thresholds, Worker bundling, 2 briefs.role-implementer— 2 briefs (Brief 2 depends on Brief 1).role-reviewer+role-design-system-auditor+role-a11y-auditor.
Todos
- Architect: ratify similarity thresholds + Worker load strategy
- Brief 1 — migration (pg_trgm) + identify-by-text route + SCHEMA_MAP + CI schema-map-fresh fix
- Brief 2 — Tesseract Worker + CameraScanner integration
- Post-ship: measure
scan_attempts.layerdistribution for 70% target
Operator action required
None. No new secrets. Tesseract runs client-side; pg_trgm is a Postgres extension enabled via migration.
Multitask dispatch
Slice dependencies
slice_dependencies:
- brief: 1
depends_on: []
files:
- migrations/*
- pages/api/cards/identify-by-text.js
- docs/SCHEMA_MAP.md
- .github/workflows/ci.yml
- brief: 2
depends_on: [1]
files:
- components/CameraScanner.js
- lib/ocr-worker.js
- package.json
Serial dispatch: Brief 2 after Brief 1 (Worker calls identify-by-text route).
Post-PR audit:
/multitask role-reviewer + role-design-system-auditor + role-a11y-auditor
Group id: audit-add-real-ocr-layer-<pr>.
CI impact
| Workflow / job | Behavior |
|---|---|
schema-map-fresh |
Modified — if: paths include migrations/**. |
forbidden-client-side-llm-keys |
Unchanged (no new client LLM URLs). |
preview-smoke.yml |
Fires. |
visual-diff.yml |
Fires — components/CameraScanner.js in paths. |
No new grep gate. Total added CI time: ~0 beyond existing workflows.
Decisions to ratify (architect)
- Similarity thresholds — 0.85 / 0.6 defaults from audit; tune with sample set.
- Tesseract language data — bundled vs CDN fetch; impact on first-load latency.
scan_attempts.layervalues — recommend1 | 2(trgm vs gemini).
Known constraints
pg_trgmon Neon — verify extension availability on prod tier.- Worker + Turbopack — confirm
tesseract.jsWorker path works under Next.js 16 default bundler; fallback--webpackonly if architect documents regression. - Layer-1 must not block camera — Worker runs off main thread.
Acceptance criteria
- Migration applies cleanly; GIN index exists on
cards.name. identify-by-textreturns match / disambig / escalate per thresholds.- CameraScanner tries Layer-1 before
/api/scan/identify. scan_attempts.layerpopulated for analytics.schema-map-freshfires when onlymigrations/changes.- Lint + vitest baseline preserved.
Out of scope follow-ups
improve-scan-card-detection— if Layer-1 hit rate < 70%.god-component-split— CameraScanner remains large; split is P2.