Fixes reported from APK field testing: DO/Product scan mode was inconsistent between the camera drawer and documents screen (now one shared provider, with an orange/green color cue); unconfirmed scans leaked into history with placeholder data before the user tapped confirm (backend now gates GET /documents on a new `confirmed` column, flipped only by PUT); and Product Scan ran the GPU classifier twice, once at upload and again on review (now a single pass at upload, persisted and read directly by the editor). Also removes the unused "Hubungkan ke PO" field and fabricated PO/SO/DO placeholder values from the Product Scan flow, closes out the per-document-polling and save-recovery tasks (6.1/6.3), and splits several touched files to stay under the repo's 256-line guideline. Full detail in docs/iteration-log.md and backend/docs/iteration-log.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
26 KiB
Next Enhancements (backend)
Working backlog driven by the e/enhance and n/next triggers defined in
AGENTS.md Part B. Split out 2026-07-08 from root
plans/next-enhancements.md sections 5-8, renumbered 1-4 — this file now owns all
backend enhancement tracking going forward; the root file only tracks Flutter
sections from this point on. Statuses below were re-verified against the live code
at split time (not copied blind):
- 1.1
file_hashdedup — confirmed present,v1/documents/upload/route.ts:59,98. - 1.2
AbortSignal.timeouton both hops — confirmed present inparse/route.tsandv1/documents/upload/route.ts. - 1.3 classic routes skipping auth — confirmed still true at split time; later cancelled as out-of-scope (dev-only web UI, no production benefit) — see §1 below, 1.4 shipped instead.
- 3.1
withTransaction— confirmed present and used indb/index.ts,api/parse/route.ts,api/v1/documents/[id]/route.ts. - 2.1
pfm-web-app/public/produk-pfm/models/— corrected 2026-07-08 (was checked against the wrong path,backend/models, in an earlier pass this same day): the directory does exist. Later the same day, task 2.1 itself was completed — see §2 below — so the artifacts now exist too. - 2.2
m-scan-pfm/page.tsx— confirmed still missing; desktopscan-pfm/page.tsxconfirmed present, and now confirmed full feature parity (confidence bar, top-5 candidate list, expiry-date crop preview all present, 1169 lines) — see §2 below, migrated fromnext-implementation.md(deleted 2026-07-08, content now lives here). - 3.2 password hashing — confirmed plaintext at split time (2026-07-08 morning); fixed later the same day, see §3 below.
- 3.3 unique index on
documents.file_hash— confirmed still absent (onlyfilenamehas a UNIQUE constraint indb/init.ts). - 4.2 healthcheck for
pipeline-api/vLLM — confirmed nohealthcheckblock in rootdocker-compose.yml.
Format
Tasks are grouped under a numbered section per backend module. Each section gets exactly 3 tasks:
## 1. <Section / Module Name>
- **1.1** [TODO] <clear, specific description of the functional change>
- **1.2** [TODO] <...>
- **1.3** [TODO] <...>
When a task is picked up via n/next, its clarified acceptance criteria (from
AGENTS.md Part B §B2a) are appended directly under it. When complete, the status
flips to [DONE] and the feature is logged in
docs/feature-list.md (this dir).
1. Backend — Next.js API Gateway
pfm-web-app/src/app/api/
- 1.1 [DONE] File hash dedup already implemented. (See docs/feature-list.md)
- 1.2 [DONE] AbortSignal timeout already implemented. (See docs/feature-list.md)
- 1.3 [CANCELLED 2026-07-08] Wire JWT auth to classic routes (not needed for dev UI). (See docs/feature-list.md)
- 1.5 [DONE 2026-07-08] Per-account data scoping on
/api/v1/documents/*. (See docs/feature-list.md) - 1.6 [DONE 2026-07-08] Lightweight
GET /api/v1/healthendpoint. (See docs/feature-list.md) - 1.4 [DONE 2026-07-08] Enforced real 401 auth on
/api/v1/documents/*. (See docs/feature-list.md)
2. Backend — OCR Pipeline & Accuracy
config/, pfm-web-app/src/utils/parser.ts, accuracy regression harness (see CLAUDE.md)
Product/SKU scan sub-feature — decisions & state (migrated 2026-07-08 from
next-implementation.md, which is now deleted; this section is the sole source of
truth for it going forward):
-
Decisions made: new pages are standalone routes (
scan-pfm— done), following the same self-contained pattern asmanual-label/page.tsx— own header/theme, no shared chrome with the root DO-PFM page. Build to full feature parity with the oldai-ocr-pfm-2026pages (confidence bars, top-5 SKU candidates, visual OCR overlay, expiry-date crop preview), not a lean MVP. -
Scope correction 2026-07-08 (later):
m-scan-pfm/page.tsx(mobile web page) is not needed — user clarified the webscan-pfmpage is desktop-only, used for testing the pipeline, not a production mobile surface. Real mobile scanning is already handled by the Flutter app instead. Focus for this sub-feature going forward is backend services (model artifacts, pipeline correctness), not any additional frontend. See 2.2 below — cancelled, not reassigned. -
Already shipped, re-verified 2026-07-08 (all confirmed present, not assumed):
config/classify_ocr_server.py(DINOv2 + YOLO classify, SKU/expiry/name OCR),pfm-web-app/public/produk-pfm/index_dinov2.py+train_classifier.py,api/scan-pfm/route.ts(Levenshtein match againstsku_master),api/produk-pfm/route.ts, DB schema (sku_master/store_master/arena_runsindb/init.ts),scripts/serve-pipeline.shwiring,Dockerfile'sultralyticsinstall in the pipeline-api stage,nginx.confroutes for/scan-pfm,/m-scan-pfm,/produk-pfm. -
Stale claims corrected 2026-07-08 (next-implementation.md said these didn't exist; they now do):
pfm-web-app/public/produk-pfm/foto-kemasan-v2/dataset — 16 SKU subfolders now present (was "❌ does not exist");scan-pfm/page.tsx— now exists at full feature parity, 1169 lines (was "❌ does not exist"). -
Newly observed, not in original doc's scope:
/produk-pfmhas an nginx proxy block and an API route (api/produk-pfm/route.ts) but no matching frontend page (src/app/produk-pfm/page.tsxdoesn't exist) — dead route, same class of issue as scan-pfm/m-scan-pfm were. Not turned into a task below since it wasn't part of the original decision record; flagging for a futuree/enhancepass to pick up.- Correction (2026-07-08 audit): only half dead.
api/produk-pfm/route.tsis live —scan-pfm/page.tsx:159fetches it for the sample-product gallery. Only the nginxlocation /produk-pfmpage proxy block points at a nonexistent page. Don't remove the API route; see task 4.4.
- Correction (2026-07-08 audit): only half dead.
-
2.1 [DONE 2026-07-08] Built the initial model artifacts (DINOv2 + YOLO) and fixed volume mount issue. (See docs/feature-list.md)
-
2.2 [CANCELLED 2026-07-08] Port
m-scan-pfm/page.tsx(mobile) fromai-ocr-pfm-2026into v2 — not needed, Flutter app handles mobile scanning. -
2.3 [DONE 2026-07-08] Ran accuracy regression harness, target 95% already met. Fixed one parser logic bug. (See docs/feature-list.md)
-
2.4 [TODO] Human review of
do-008.jpg's ground truth insources/manual_labels.json— flagged in task 2.3 as a suspected labeling error (itsnoPO/noSO/noDOOCR cleanly but are entirely different digit sequences from the labels, not plausible misreads) but never turned into a task. Verify against the source photo with the client/labeler; if the labels are wrong, fix them and re-run the harness (aggregate accuracy should tick up).
Not in scope for either 2.1/2.2 (per next-implementation.md's own note, still true
2026-07-08): batch/lot number extraction doesn't exist in classify_ocr_server.py
in either project (only SKU, product name, expiry date are extracted) — if
requested later, follow the same OCR-regex-cascade pattern already used for
expiry-date extraction.
3. Backend — Postgres Data Layer
pfm-web-app/src/db/
- 3.1 [DONE] Wrapped ocr_items updates in DB transactions. (See docs/feature-list.md)
- 3.2 [DONE 2026-07-08] Hashed accounts.password with bcryptjs. (See docs/feature-list.md)
- 3.3 [DONE 2026-07-08] Added an index on
documents.file_hashto speed up the dedup lookup, and scoped the dedup logic itself bykode_tokoso stores can't leak duplicates to each other. (See docs/feature-list.md)
4. DevOps — Docker & Dev Tunnel
../docker-compose.yml, ../docker-compose.demo.yml, ../start-dev-tunnel.ps1
- 4.1 [DONE 2026-07-08] Formalized the Docker Compose usage policy in
README.mdandCLAUDE.md, making it explicit that the production override must be used for field testing. (See docs/feature-list.md) - 4.2 [DONE 2026-07-08] Add a startup healthcheck/readiness gate for
pipeline-api/vLLM indocker-compose.ymlso the Next.js gateway doesn't accept uploads before the GPU pipeline is actually ready to serve them. (See docs/feature-list.md) - 4.3 [DONE 2026-07-08] Extend
start-dev-tunnel.ps1to verify the ngrok tunnel/LAN IP is actually reachable (not just started) before reporting success, since a stale tunnel is the documented first failure point for login/upload from the Flutter app. (See docs/feature-list.md) - 4.4 [DONE 2026-07-08] Prune/annotate the dead
nginx.conflocation blocks:/do-pfmand/m-do-pfm(dead since v2 consolidated the DO-PFM UI into the root page — already documented as dead in rootCLAUDE.md), the/produk-pfmpage proxy (nosrc/app/produk-pfm/page.tsxexists — but keepapi/produk-pfm/route.ts, it's live, used byscan-pfm/page.tsx:159; see the corrected §2 note), and/m-scan-pfm(page intentionally unbuilt per cancelled task 2.2 — removing the proxy block doesn't resurrect that task, it just stops nginx advertising a 404). (See docs/feature-list.md) - 4.5 [DONE 2026-07-08] Lock down the publicly tunneled surface.
start-dev-tunnel.ps1tunnels all oflocalhost:8000(the whole nginx gateway) to a stable, reserved ngrok domain — which makes the deliberately unauthenticated classic routes (/api/upload,/api/parse,/api/history, ...) and the dev pages (root DO-PFM,/scan-pfm,/manual-label) internet-reachable. Task 1.3's "classic routes never get auth" decision was made in an on-prem/LAN context and stands — so restrict at the edge instead of adding auth: either (a) tunnel a separate nginx server block/port that proxies only/api/v1/*(the authenticated production surface the Flutter app actually uses — seeapp_config.dart, both endpoints end in/api/v1), or (b) an ngrok traffic policy (IP allowlist / basic auth) covering everything except/api/v1/*. Decide (a) vs (b) with the user at pickup; (a) is self-contained innginx.conf+ the script and doesn't depend on ngrok plan features. NoteGET /api/v1/health(task 1.6) must remain reachable through whichever restriction ships — it's the fallback probe target. (See docs/feature-list.md)
5. Docs & Workflow Integrity
CLAUDE.md, SKILLS.md, AGENTS.md, this file — added 2026-07-08 after an audit
cross-checking the kit docs' claims against live code. All code-level claims in
this file's §1-4 re-verified accurate (auth guards, bcrypt, withTransaction,
parser-fallback fix, dedup, timeouts, absent index/healthcheck all match the code);
the drift found is in the guidance docs the SKILLS.md roles rely on.
- 5.1 [DONE 2026-07-08] Fixed stale doc claims in SKILLS.md and CLAUDE.md. (See docs/feature-list.md)
- 5.2 [DONE 2026-07-08] Shrunk [DONE] tasks in plans/next-enhancements.md. (See docs/feature-list.md)
- 5.3 [DONE 2026-07-08] Amended AGENTS.md completion checklist with doc-sync step. (See docs/feature-list.md)
6. Product Scan — Ground Truth Annotation & Accuracy
scan-pfm/page.tsx, api/manual-label-scan/route.ts, sources/product_manual_labels.json
Added 2026-07-08 via a user-directed e run: build an editable ground-truth
surface for product scans, mirroring what the DO flow already has in
manual-label/page.tsx + api/manual-label/route.ts + sources/manual_labels.json.
Current-state audit (verified against code, not assumed):
-
What exists:
api/manual-label-scan/route.ts(GET by exact filename / POST upsert, schema{filename, no_sku, nama_item, expiry_date, top1_confidence, notes, saved_at}, ownnormalizeDateString), a "Save Ground Truth" button inscan-pfm/page.tsx(handleSaveGroundTruth, line 333), and the./backend/sources:/sourcesmount that makes saves host-visible (fixed in 2.1). -
Gap (a) — prediction saved as truth: in the scan-pfm save, only
no_skuis correctable (the top-5 "Use" override →editedSku);nama_itemis hardcoded to the classifier'stop1_nameandexpiry_dateto the OCR output,notesalways"". Ground truth that is the model's own prediction makes any future accuracy measurement against it trivially inflated — this is the core defect the user's request fixes. -
Gap (b) — phantom image keys: uploaded (non-gallery) photos are keyed
uploaded-${Date.now()}.jpgand the image bytes are never persisted anywhere (data-URL in the browser only) — those label entries reference files that don't exist on disk, unusable for re-evaluation. -
Gap (c) — no browse/edit: GET requires an exact filename; there's no list endpoint, no page to review/correct/delete existing entries (the DO side has a full annotation page for this).
-
Gap (d) — no consumer: nothing plays the role
accuracy-check.mtsplays for the DO parser; the labels currently gate nothing. -
6.1 [DONE 2026-07-08] Built standalone annotation page manual-label-scan/page.tsx. (See docs/feature-list.md)
8. Master Data Management
api/v1/master/stores/route.ts, api/v1/master/skus/route.ts, admin/master-data/page.tsx
- 8.1 [DONE 2026-07-08] Build full CRUD API routes for
store_masterandsku_masterto allow dynamic updating of reference data. (See docs/feature-list.md) - 8.2 [DONE 2026-07-08] Implement auto-provisioning of login accounts with securely hashed passwords whenever a new store is created via the API. (See docs/feature-list.md)
- 8.3 [DONE 2026-07-08] Build an
/admin/master-dataweb UI to visually manage both SKUs and Stores. (See docs/feature-list.md) - 6.2 [DONE 2026-07-08] API + storage groundwork for scan annotation. (See docs/feature-list.md)
- 6.3 [DONE 2026-07-08] Product-scan accuracy harness created. (See docs/feature-list.md)
Suggested order: 6.2 → 6.1 → 6.3 (storage/API first, page on top, harness once labels exist in volume).
7. Auth — Store Accounts & Profile-Sourced Metadata
pfm-web-app/src/db/init.ts, api/v1/auth/*, api/parse/route.ts, sources/toko_aktif.json
Added 2026-07-08 via a user-directed e run: one login account per store
(username = kode_toko, default password "password"), each carrying its store
profile (nama toko, kode toko, alamat), with the profile's store name + address
used as the parse response's values instead of OCR.
Current-state audit (verified against code and the live DB, not assumed):
-
The "don't OCR store/alamat" half is already live.
v1/documents/uploadpassesaccount?.kodeTokointo/api/parse(uploadroute.ts:125), and parse stampsmetadata.orderUntuk/alamatstraight fromstore_masterwhen akodeTokois present — OCR-based store matching (resolveStoreFromText) is only the no-account fallback (parse/route.ts:392-410). The Flutter app reads these same metadata fields fromGET /documents, so no response-shape or Flutter change is needed for that half. -
What's missing is the accounts: the live DB has 779
store_masterrows but exactly 1 account (admin), so every real upload today authenticates asadmin/WH_JOFFICEand the per-store path never fires for actual stores. -
Bootstrap gap: no code anywhere populates
store_master— the 779 rows exist only in the live DB volume (imported out-of-band).sources/toko_aktif.json({namaToko, kodeToko, alamat}, same 3 fields) is the obvious source. On a truly fresh DB,init.tswould even fail its ownadminseed — theaccounts.kode_toko → store_master(kode_toko)FK can't resolveWH_JOFFICEwhenstore_masteris empty. -
7.1 [DONE 2026-07-08] Seeded one account per store in
db/init.ts. (See docs/feature-list.md) -
7.2 [DONE 2026-07-08] Returned the store profile at login and added
/meendpoint. (See docs/feature-list.md) -
7.3 [DONE 2026-07-08] Built reproducible
store_masterbootstrap indb/init.ts. (See docs/feature-list.md)
Suggested order: 7.3 → 7.1 → 7.2 (bootstrap first — account seeding FK-depends on it; login profile last, it's additive).
9. Flutter Client Contract — v1 Surface Completion
api/v1/documents/, api/v1/master/skus/, api/parse/route.ts, db/init.ts
Added 2026-07-10 via a user-directed, explicitly backend-scoped e run auditing
the full Flutter↔backend request/response contract. Context doc:
../../docs/api-contract-map.md (repo-root
docs/) — endpoint inventory, envelopes, lifecycle, and gap IDs (G1-G10) cited
below. These are the server halves; the Flutter halves are root
plans/next-enhancements.md §6-7 and consume these, so this section ships first.
Keep the v1 envelope ({status, data} / api-error.ts) on everything new.
- 9.1 [DONE 2026-07-10]
GET /api/v1/documents/:idwithparseStatus/docType,scan_modepersistence, dedup-stub fix. (See docs/feature-list.md) - 9.2 [DONE 2026-07-10] Relaxed
GET /api/v1/master/skusto any authenticated account (writes stay admin-only); no response-shape change. Picked up via explicitn{9.2}request; user chose "relax existing endpoint" over "add a new one" when asked. (See docs/feature-list.md) - 9.3 [DONE 2026-07-10] Authenticated
POST /api/v1/scan-product, shared classify+match util. (See docs/feature-list.md)
Section 9 is now fully [DONE]. With 9.1-9.3 all shipped, every backend
blocker behind Flutter root plans/next-enhancements.md §7.1 (moving the
product editor onto the v1 surface) is cleared. §7.2 (eliminating the
duplicate classification pass) is unblocked in principle but still needs its
own grill-me decision on the client side about which single pass to keep.
10. Backend — Document Confirmation Gate & Data Hygiene
pfm-web-app/src/db/init.ts, app/api/v1/documents/, app/api/parse/route.ts, utils/document-mapper.ts
Added 2026-07-10 from user testing feedback on the release APK
(twinkly-riding-mitten.md).
Backend counterpart to Flutter root plans/next-enhancements.md §8.
Root-cause documentation in ../../docs/api-contract-map.md
G11 (no draft/confirmed distinction) and G12 (fabricated PO/SO/DO).
Ship §10 before Flutter §8.2 — Flutter's model change consumes the new
confirmed field this section adds. Keep the v1 envelope ({status, data} /
api-error.ts) on everything new.
-
10.1 [DONE 2026-07-10] Confirmation-gated document list visibility (Task B backend half). Three coordinated changes, one migration:
db/init.ts—ALTER TABLE documents ADD COLUMN IF NOT EXISTS confirmed BOOLEAN NOT NULL DEFAULT true;(DEFAULT truegrandfathers every pre-existing row — today's history stays visible after migration).v1/documents/upload/route.ts— addconfirmed = falseto the INSERT column list for every new upload (dedup-hit branch unchanged — reflects whateverconfirmedstate the original row already has).utils/document-mapper.ts— addconfirmed: booleantoDocumentRowinterface; returnconfirmed: doc.confirmedfrommapDocumentRow(); update the three SELECT statements that build aDocumentRow(list route,[id]GET, upload dedup-hit SELECT) to include theconfirmedcolumn.v1/documents/route.ts(list) — addAND confirmed = trueto WHERE clause, unconditionally for every account including admin (per clarified answer — existingkode_tokoscoping for non-admins is untouched).v1/documents/[id]/route.ts— PUT handler: addconfirmed = trueto the UPDATE SET. GET handler: no filter change (poller must keep seeing pending/unconfirmed docs); just receivesconfirmedvia mapper update.
- Note on
parse/route.ts's own INSERT...ON CONFLICT statements (both DO and Product branches): deliberately left untouched forconfirmed— in the real mobile flow,upload/route.ts's INSERT always runs first (explicitconfirmed = false), soparse/route.ts's upsert always hits theON CONFLICT DO UPDATEbranch; since that branch'sSETclause doesn't mentionconfirmed, Postgres leaves the existing value untouched — exactly the desired behavior (never regress an already-confirmed document, never reset the pending flag mid-parse). Omission was verified to be correct, not an oversight. - Live verification (all 5 steps passed against the running Docker stack):
(a) uploaded a real DO photo as store
WH_JCIBBR1, did not PUT — absent from that store'sGET /documents(count stayed at 3, new id 3400 not present) whileGET /documents/3400still returnedparseStatus: "done",confirmed: false; (b) PUT (confirm) — doc count became 4, id 3400 present with the real submittednamaPenerima, DB row'sconfirmedflipped totrue; (c) covered by (a)'s single-doc check; (d)admin'sGET /documentsalso excluded the unconfirmed doc (13, unchanged) before the PUT; (e) all 13 pre-existing rows carriedconfirmed = trueafter the migration ran (ALTER TABLEexecuted on container restart, verified via\d documents+ acount(*)query — 13 confirmed, 0 unconfirmed pre-restart).
-
10.2 [DONE 2026-07-10] Remove fabricated Product Scan PO/SO/DO placeholders (Task C, bundled with 10.1 — same file
parse/route.ts). Replaced hardcodednoPO: "PO-PRODUCT-001",noSO: "1002003004",noDO: "DO-PRODUCT-999"— both the flat keys and the mirroredheader.no_po/no_so/no_dosub-object — with empty strings"". Scope stayed narrow to exactly these three fields;nama_driver: "PRODUCT SCAN"andnama_penerima: "STORE STAFF"were left untouched (deliberate fixed convention, not a fabricated document number that could mislead someone reading raw data — different failure mode from G7's fake SKU/date data). Not user-facing: verifiedpdf_service.dartandproduct_editor_submit_logic.dartneither reads nor displays these values.- Verification: uploaded a fresh Product Scan as
WH_JCIBBR1(doc id 3402), read the raw unconfirmedGET /documents/3402response —header.no_po/no_so/no_doall returned"", not the old fabricated strings.
- Verification: uploaded a fresh Product Scan as
11. Backend — Single-Pass Product Classification
app/api/parse/route.ts, utils/product-scan.ts, utils/document-mapper.ts
Added 2026-07-10 from user feedback ("kenapa harus dilakukan dua kali... GPU
tidak 2x kerja") after noticing Product Scan's editor took visibly longer to
open than DO Scan's. Root-caused as gap G3 in
../../docs/api-contract-map.md (deferred
there pending exactly this client-side decision). Backend counterpart to
Flutter root plans/next-enhancements.md §7.2.
- 11.1 [DONE 2026-07-10] Run the classify+match pipeline once per
photo, and persist the full result.
api/parse/route.ts's Product branch had its own separate, poorer inlinefetchto the classifier that only kepttop1_name/extracted_sku— the Flutter editor then had to re-run the entire pipeline a second time viaPOST /api/v1/scan-product(task 9.3) just to get the top-5 candidate list and OCR-extracted expiry date. Replaced the inline fetch with a call to the same sharedclassifyAndMatchProduct()(utils/product-scan.ts) that route already uses — one GPU call, richer result,b64reused from the DO path's own computation (not recomputed). The richer result is persisted under a newmetadata.productScanJSONB key (possibleMatches+extractedExpiryDate— no schema migration needed, same pattern asheader/shipmentcoexisting in that column) and surfaced bydocument-mapper.tsas a top-levelproductScanfield on every GET response (list, by-id, upload dedup).ocr_itemsstill stores only the single best-match row, unchanged.- Regression caught and fixed during implementation: delegating to
classifyAndMatchProduct()silently dropped the 90sAbortSignal.timeoutthe old inline fetch had (a wedged GPU container would otherwise hang past the intended fail-fast bound). Added the samePIPELINE_TIMEOUT_MS = 90_000bound directly insideclassifyAndMatchProduct()itself, so both callers (parse/route.tsand the livePOST /api/v1/scan-productroute, which never had this bound either) are protected, not just the one this task touched. - Verification: uploaded a genuinely fresh image/store combination
(
do-015.jpgasWH_JAFATAH, never uploaded before, so this is provably a real classify pass and not a dedup hit) — took 9s (one GPU pass), response was"Document uploaded successfully"(not the dedup branch). ImmediateGET /documents/:idreturnedproductScanwith 5 realpossibleMatches(real SKUs/names/scores fromsku_master) and the OCR-extracted expiry date — before any editor interaction. See rootdocs/iteration-log.mdfor the Flutter-side verification that the editor renders this without a second network call.
- Regression caught and fixed during implementation: delegating to
Sections 1-4 migrated 2026-07-08 from root plans/next-enhancements.md sections
5-8 (originally populated the same day via a backend-scoped e/enhance run,
grounded in a prior end-to-end reliability audit and, for §2's Product/SKU scan
sub-feature, next-implementation.md's status table). This file is the sole home
for backend enhancement tasks — the root copy's sections 5-8 were removed outright
from plans/next-enhancements.md (not just frozen) per root AGENTS.md's "Scope:
excludes backend/" note; the already-shipped 3.1/7.1 DB-transaction fix stays
recorded in root docs/feature-list.md regardless, since that's a shipped-feature
log, not a backlog.
next-implementation.md itself was deleted 2026-07-08 once its full content
(decisions, verified state, action steps, verification checklist) was folded into
§2 above with corrected statuses — it's no longer a separate source of truth.