Story 2482: one extraction job
π°οΈ Why this exists
A retry must not pay Textract twice, and a crash after Textract accepts the job must not lose the job identity.
The durable baton is the claim ticket: one firm, document, and extraction version can own only one provider job.
βοΈ The fixture-only path
direct fixture invoke
β reserve unique DB baton
β StartDocumentAnalysis(LAYOUT, TABLES, SIGNATURES)
β SNS β completion SQS
β paginated GetDocumentAnalysis
β one exact JSON object per response page in derived/extraction-record/
β baton = succeeded
normal upload β existing detect_document_text serving path (unchanged)
The trap avoided. The baton is committed before the provider call. Its stable request token makes
retries converge on the same Textract JobId; the database unique key makes concurrent callers converge on the same
baton. Completion writes are conditional and deterministic; a conflict is accepted only after the existing page's
provider-content digest matches. The baton checkpoints the next token and page number, so a retry resumes instead of
replaying an entire large document.
π¬ What is durable
| Surface | Why |
ops.document_extraction_jobs | Firm-scoped RLS baton with a unique document/version start identity, durable pagination cursor, and honest terminal status. |
derived/extraction-record/<version>/page-N.json | Every paginated provider mapping is stored without flattening, merging, or projecting its blocks. |
| Existing extraction Lambda | Infra IAM restricts Start/Get to this function; a second function would be denied. |
| No billing event | RFD I-10 bills only full INTAKE-package success, never extraction persistence. |
Proof
- Unit tests pin the exact feature set, retry reuse, seven-day stale guard, notification validation, resumable pagination, partial-result vaulting, and conflict integrity.
- A real-Postgres integration test races two starts and proves one RLS-isolated baton.
- The slow matter-16 scaffold compares all Textract WORD blocks with the PDF native text layer.
- The deploy canary resolves the Terraform-owned topic, queue, and role config and reaches StartDocumentAnalysis with a fresh nonexistent S3 key; only the expected no-object error is green. The infra fleet audit separately proves Get/SQS authority and the event-source mapping.
- Direct fixture starts are disabled by default and restricted to an explicit document-id allowlist.
Known live boundary gap. Infra PR #153 created and applied the topic, queue, role, IAM, and outputs,
but not the queue-to-Lambda event-source mapping. The live fleet audit is red on βenqueue-to-nowhere.β Functions
deliberately does not create that Terraform-owned trigger; the infra twin must land before this rail is live.
π― Recall
One durable claim ticket makes every retry and concurrent caller converge on one paid Textract job, while the old serving path stays untouched.