flowchart TD
J["Document job worker"] --> R["Synthetic HTTP request actor context + durable flag"]
R --> H["Scan Invoice continuation inside HTTP handler"]
H --> E["OCR · config · draft · quota"]
H --> W["ResponseWriter + JSON"]
W --> J
classDef leak fill:#fff6f2,stroke:#b34b3d,color:#8e382e;
class R,W leak;
linkStyle 0,1,3,4 stroke:#b34b3d,stroke-width:1.7px;
Both owners use this detour. Pharmacy preserves an invoice conflict through a private ResponseWriter field.
After · processing stays inside Document jobs
flowchart TD
H["HTTP adapter: submit choice / wait"] --> J["Document job scheduler + worker"]
J -. existing processor seam .-> C["Deep Scan Invoice continuation module replay · OCR · config · draft · quota"]
C --> A["Provider and persistence adapters"]
classDef deep fill:#24302d,stroke:#175b44,stroke-width:4px,color:#ffffff;
class C deep;
Deepen each owner's continuation implementation; retain its specific Distributor or Supplier rules.
Problem
The worker must understand an HTTP request, middleware context, response bytes and status codes to run an in-process continuation.
Solution
Concentrate continuation behavior behind the existing processor seam and keep transport encoding in its adapter.
Deletion test
Delete the synthetic request and ResponseWriter modules; their transport complexity disappears once continuation returns its result directly.
Seam and adapters
Keep the Document job interface. Existing production and fake OCR adapters justify the internal seam; PostgreSQL tests exercise durable effects.
The interface is the test surface
Retain replay, quota, actor-scope and duplicate-invoice outcomes at the processor interface; keep HTTP submission and waiting checks at the HTTP adapter.Existing evidence: scan_resume_handler_test.go:364,376,416 · pharmacy_document_job_processor_test.go:54 · distributor replay recovery at scan_resume_handler.go:471.
Preserves ADR 0023. HTTP continues the same durable job; the deep module does not introduce a second scheduler or direct HTTP processing path.
Product-photo processing repeats routing and acceptance setup too, but has different input and result needs.
After · one module owns the shared sequence
Pharmacy adapter
Distributor adapter
scan-processing seam
Trusted classification orchestration module
Line order and per-product sequencing Lease-fenced fact acceptance Specialist route precedence and result mapping
Catalog, Medicine, Pharmacy FMCG, Device and Surgical modules retain their current responsibilities.
Problem
Application setup repeats rules for route precedence, worker-context lifetime, acceptance callbacks and queue scheduling across the two owners.
Solution
Move the shared sequence into one module; keep owner-specific identity access in adapters and review product-photo reuse separately.
Deletion test
Moving callbacks into separate files only moves complexity. Delete the repeated orchestration once one implementation handles both owner adapters.
Seam and adapters
Two real owner adapters justify the seam. The catalog interface still owns classification receipts and atomic acceptance; specialist modules own their facts.
The interface is the test surface
Move the repeated-product, bounded-concurrency and failure-recovery scenarios to the module interface; retain a small application-wiring check and the catalog acceptance tests.Existing evidence: internal/app/scan_specialist_budget_test.go:22,83,171 creates a full Application; scan_classification_test.go:16 checks both owners; modules/catalog/classification_test.go:112 checks replay.
Preserves ADRs 0021, 0023 and 0034. Keep ProductKind storage ownership, Document job lease fencing, and classification-plus-master acceptance in one transaction.
The shallow modules expose query-module types and add no distinct purchase behavior.
After · callers enter a validated read module
HTTP adapter · parse and render
existing purchase-read seam
Deep Purchase read module
Validate every caller's query Tenant scope · period rules · composition PostgreSQL implementation
Pharmacy and distributor keep separate reads and Purchase origin semantics.
Problem
Each middle module repeats normalization already performed by its caller and callee, then forwards the same repository query type.
Solution
Remove the two pass-through modules and redundant business validation calls; keep validation mandatory at each Purchase read entry point.
Deletion test
Deleting these modules removes constructors and repeated calls; no distinct purchase rule needs to move elsewhere.
Seam and adapters
Reuse the existing read seam. Run behavior tests against real test PostgreSQL; keep a fake adapter only for HTTP parsing and status checks.
The interface is the test surface
Keep tenant-isolation, mixed-purchase membership, period and intake tests through the read interface; delete duplicate pass-through tests only after their assertions survive there.Existing evidence: pharmacy and distributor handlers/purchases_report_integration_test.go cover membership, payment modes, counterparties and intake; both middle modules have validation and empty-period tests.
Preserves ADRs 0008 and 0021 and the existing cross-spine read allow-list. Keep validation for non-HTTP callers. The benefit is modest; do this when these reads next change.
Depth in the existing read module
Locality for query validation
Leverage from retained behavior tests
Review evidence: current source and existing test code, relevant domain definitions and ADRs, plus read-only staging aggregates. Staging has classification rows for both owner kinds and no current Document job rows. The recommendations identify architectural friction; they do not claim a reproduced runtime failure. Backend tests were inspected, not run. No interface design or backend edits are included.
Top recommendation
Start with Scan Invoice continuation.
It removes a concrete transport detour from both worker paths, with existing replay and quota tests to protect the behavior and no change to Document job ownership.