Appearance
Import PDF/OCR Boundary Agent Tasks
Agent workflow: follow README.md for Audit -> Investigate -> Code -> Test -> Fix; keep this pack's writable scope and verification commands authoritative.
Status: P4-037 direct OCR foundation, opt-in durable Mistral batch hand-off, isolated direct/batch worker lanes, and opt-in usage/wallet accounting.
Current native status: POST /v1/import/ocr/jobs requires owner-scoped PDF, PNG, JPEG, and WebP sourceAssetId references from the non-default Gateway candidate (raw storageKey is rejected), calls Mistral POST /v1/ocr, and stores Markdown plus page/model/usage metadata in the import review state. The companion POST /v1/import/ocr/jobs/{id}/reprocess deliberately retries from the document-service source. Jobs found PROCESSING after restart are marked OCR_RECOVERY_REQUIRES_RETRY instead of automatically charging the provider a second time. ocrMode=mistral-batch is an explicit additive mode: it persists provider source/input/output file IDs, batch ID, lease, attempts, and next poll time. Restart resumes only provider polling after a batch ID is durable; an unfinished Start fails closed. Explicit reprocess reopens the same owner-scoped source asset and preserves the stored direct or batch engine. The DOCX Fast parser proof must not be used as OCR/Mistral batch, Word Equation, gateway, or browser parity evidence.
Dispatch type: orchestrator
Owner: import-service through services/docx-import-service.
Purpose: define the PDF/OCR job slice before implementation so agents do not mix OCR provider work into IAM/Profile, billing, wallet, file storage, or frontend routes.
Must read:
docs/architecture/service-responsibility-matrix.mddocs/agents/service-tasks/import-service.mddocs/agents/service-tasks/file-service.mddocs/agents/service-tasks/usage-service.mddocs/agents/service-tasks/wallet-service.md- Legacy OCR/PDF evidence under:
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/exam-paper-templates/mistral-document-ocr-converter.service.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/exam-paper-templates/mistral-document-ocr-converter.helpers.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/exam-paper-templates/mistral-document-ocr-converter.service.spec.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/ai-import/annotated-docx-import.service.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/ai-import/annotated-docx-import.helpers.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/app-data.import-jobs.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/app-data.import-operations.ts
Legacy source evidence:
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/exam-paper-templates/mistral-document-ocr-converter.service.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/exam-paper-templates/mistral-document-ocr-converter.helpers.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/exam-paper-templates/mistral-document-ocr-converter.service.spec.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/ai-import/annotated-docx-import.service.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/ai-import/annotated-docx-import.helpers.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/app-data.import-jobs.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/app-data.import-operations.ts
Writable files:
services/docx-import-service/**contracts/openapi/services/docx-import-service.yamldocs/api/docx-import-service.mddocs/agents/service-tasks/import-pdf-ocr-boundary.md
Orchestrator-owned files:
- Default gateway route table
deploy/gateway/routes.json. - Non-default import route examples,
scripts/test/import-*.sh, rootMakefile, VitePress config, Compose, K8s, and Helm unless explicitly delegated. - Usage/wallet API contracts unless the orchestrator assigns the cross-service handoff.
Boundary:
- Source kind must be explicit, for example
ocr-documentorpdf-ocr; do not reuse DOCX Fast source kinds for OCR work. - Uploaded binary ownership stays with
file-service/document-service; import-service stores payload refs, derived page metadata, OCR text/layout output, review state, and materialized result refs. - OCR provider settings and secrets are runtime config or secrets, not rows owned by IAM/Profile/Billing.
- When
DOCX_IMPORT_OCR_USAGE_WALLET_ENABLED=true, import-service makes explicit owner-service calls tousage-serviceandwallet-service; it never moves credits locally. It preflights oneocr_pages_monthlyunit and credit cost, then settles exact result pages with job-scoped idempotency keys. Other import workloads remain separate follow-up integrations. - Durable statuses/events must cover queued, running, completed, failed, cancelled, retrying, and review-required states without depending on in-memory-only queues.
- Downloads must serve only materialized refs produced by the job and must not bypass file/document-service ownership for stored bytes.
- Public broad
/api/exam-import*,/api/imports*, and upload routes stay legacy until route guards, live runtime evidence, browser proof, and rollback notes exist; default broad import routes legacy is the required cutover state.
Initial tasks:
- Done: audit the legacy Mistral OCR converter and import job/read model code for request shape, provider timeout, page counting, result text/layout shape, warnings, and retry behavior.
- Done for direct OCR: add service-owned payload, page metadata, provider/model/usage review metadata, status/events, warning, and explicit retry semantics. The source object remains owned by
document-service. - Done: add provider, service, and HTTP tests before any route rehearsal.
make test-import-pdf-ocr-workflowdrives the stored document-service source through the real Mistral-compatible provider adapter, verifies review metadata, and verifies explicit reprocess. The routes stay internal-only. - Done for direct OCR: reprocess always reopens the original document-service object. Automatic restart recovery never retries an in-flight direct provider call.
- Done for opt-in batch parity:
ocrMode=mistral-batchuploads the source and JSONL input, submitsendpoint=/v1/ocr, persists provider file/batch IDs plus lease/attempt/next-run state, polls the provider, and reads the output JSONL. Direct remains the omitted/default mode. Explicit batch reprocess reopens the same owner-scoped source asset, starts/polls a new batch, and emits the batch reprocess event; it never falls through to the direct engine. The public create-job contract does not state an idempotency guarantee, so automatic recovery resumes Poll only after a provider batch id is durable; a crash during Start fails closed for explicit reprocess or operator reconciliation instead of creating another batch. - Done for internal OCR: use versioned usage/wallet owner APIs behind an opt-in adapter. Preflight denial skips the provider; a usable result settles exact pages and persists only accounting metadata. Settlement runs its read-only exact-page quota and wallet-balance checks concurrently, then keeps usage failure/denial precedence before any idempotent debit/consume mutation. Enabled invalid configuration fails the worker closed. Do not apply this accounting keyspace to MathType or other import workloads.
- Done for the candidate source route: upload through document-service returns only an opaque source id and import-service reopens the exact owner-scoped source with its internal token. OCR and MathType create reject raw
storageKey; OCR detail/status/event/review/approve/reprocess and MathType reprocess require the job actor.make test-import-ocr-mathtype-routesvalidates the narrow route table. The isolatedOCR_REVIEW_BROWSER_LOCAL_POSTGRES_CONFIRM=local-postgres make test-ocr-review-browser-local-postgresandOCR_REVIEW_BROWSER_LOCAL_POSTGRES_CONFIRM=local-postgres make test-ocr-batch-review-browser-local-postgresproofs cover direct and explicit batch OCR through Teacher Web, Gateway, local PostgreSQL, and a Mistral-compatible stub. After a Teacher browser save/reload, each harness also approves through candidate Gateway and reads the persisted Question Bank editor projection through candidate Gateway. It asserts the edited stem,SINGLE_CHOICE, answer key/option, solution, score rule, and source import job. They are not public-provider-quality, production, or default-route promotion evidence; default routes stay legacy. - Done for direct OCR latency isolation: direct Mistral requests run in the
docx-import-ocr-directlane, independently bounded byDOCX_IMPORT_OCR_WORKER_CONCURRENCY(default1, maximum10). A blocked direct provider call cannot consume a DOCX worker. This is not provider batch parity and does not authorize automatic replay after a restart. - Done for review approval: the OCR editor retains its
sourceTextandsections[]review shape. Approval converts only an explicitly saved OCR review into canonicalquestions[], retaining the teacher-editedstem_text/stem_html, type, options, reviewed answer labels, score andscoring_rule, solution, source spans, andocr-documentprovenance for the Question Bank owner API. Completed OCR withoutReviewedAtis rejected before any owner write. Direct and batch browser/PostgreSQL runs then prove the owner approval/readback rather than only an in-memory owner contract.
Acceptance:
- PDF/OCR import jobs are service-owned in
docx-import-serviceand do not create a duplicateservices/import-service. - The job lifecycle survives service restart through persistent repository state and payload refs.
- A direct OCR call interrupted by a process restart is not automatically replayed. A batch job resumes only its provider poll after the provider batch ID is durable; a crash during batch Start remains a durable failed job for explicit reprocess or operator reconciliation.
- OCR output can be reviewed and approved through existing question/exam owner APIs, not direct cross-service DB writes. A completed OCR result is never approvable until an explicit review save creates
ReviewedAt. - The reviewed Question Bank readback must preserve the teacher's edited stem, type, answer, solution, and scoring rule in the owner version.
- Provider failures produce durable errors, warnings, status events, and retry-safe state.
- A blocked direct provider call cannot head-of-line block the native DOCX lane; queue snapshots and admin summaries identify direct and batch OCR lanes separately, including batch jobs delayed for their persisted next poll.
- Opt-in OCR usage/wallet calls are explicit and test-covered without local credit movement; the default disabled path does not call either service.
- File bytes and generated downloads stay behind file/document-service owned storage refs.
Verification:
GOTOOLCHAIN=go1.25.11 go test ./services/docx-import-service/...make test-import-pdf-ocr-workflowcovers the Mistral document/image data URL adapter, batch file/JSONL submit/poll/output adapter, durable review metadata, restart-safe poll recovery, document-service source re-open, preflight/settlement owner-service contract, and explicit reprocess. It is hermetic and does not make a billable Mistral call. It also proves a blocked OCR provider does not block a DOCX job, and proves that unreviewed OCR never calls Question Bank while a saved review sends canonical question payload fields through the owner HTTP contract.- OpenAPI mirror check:
diff -u services/docx-import-service/api/openapi.yaml contracts/openapi/services/docx-import-service.yaml - Route proof before public promotion: a non-default OCR/MathType Gateway candidate has static/service checks and isolated direct and batch OCR browser proofs with Question Bank approval/readback. No public PDF/OCR route gate exists yet for default promotion; the isolated proof is a runnable candidate gate, not a public rollout. Keep default broad import routes legacy until a non-default deployed runtime, provider-quality evidence, and rollback proof are approved.
- Shared guards:
make test-service-task-packs,pnpm docs:build, andgit diff --check.
Rollback:
- Remove the non-default OCR route table entries or set them back to
legacy_proxy. - Set
DOCX_IMPORT_OCR_USAGE_WALLET_ENABLED=falsebefore rolling back OCR accounting, then leave existing DOCX Fast routes untouched. - Keep legacy OCR/PDF code read-only; rollback must not edit legacy source.