Wire the complete OCR review and indexing production pipeline: durable OCR result handoff before remote deletion (17a), native-page evidence and quality-gated candidate composition (17b), review images and restart-safe candidate loading (17c), transactional approval and rejection decisions (17d), reviewed-artifact indexing store with exact count verification (17e), and production approve-to-ready wiring with fail-closed OCR_INDEXING_UNAVAILABLE (17f). Activation remains a separately authorized operation; task 7.4 stays pending.
588 lines
60 KiB
Markdown
588 lines
60 KiB
Markdown
# Apply Progress: OCR Ingest Integration
|
||
|
||
## Current State
|
||
|
||
- **Mode:** Strict TDD
|
||
- **Delivery:** Feature-branch-chain; Units 1–13 implemented as bounded slices; production acceptance remains pending
|
||
- **Completed tasks:** 1.1–1.4, 2.1–2.5, 3.1–3.4, 4.1–4.5, 5.1–5.4, 6.1–6.4, 7.1–7.3
|
||
- **Overall task progress:** 29/30 complete
|
||
|
||
## Unit 1: Migration
|
||
|
||
### Implementation Summary
|
||
|
||
- Added `migrations/002_ocr_review.sql` with durable OCR jobs, auditable page records, atomic review correction targets, and a partial unique index for non-terminal OCR identities.
|
||
- Added focused schema contract coverage in `tests/catalog/migration-002.test.ts`.
|
||
- Preserved `migrations/001_knowledge_lifecycle.sql` and all repository logic unchanged.
|
||
|
||
### TDD Cycle Evidence
|
||
|
||
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 1.2 | `tests/catalog/migration-002.test.ts` | Schema contract | N/A (new files) | Valid RED: 4/4 failed with `ENOENT` because migration 002 did not exist. An earlier syntax-invalid run was corrected and is not counted as RED. | 4/4 passed with migration 002 present. | 4 independent schema behaviors cover jobs, pages, corrections, and pending identity uniqueness. | None needed; SQL already matches the minimal authoritative contract. |
|
||
|
||
### Test Summary
|
||
|
||
- **Total tests written:** 4
|
||
- **Total tests passing:** 4
|
||
- **Layers used:** Schema contract (4)
|
||
- **Approval tests:** None — no existing production file was modified
|
||
- **Pure functions created:** 0
|
||
|
||
### Work Unit Evidence
|
||
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused test command and exact result | `npx --no-install tsx --test tests/catalog/migration-002.test.ts` — exit 0; 4 tests, 4 passed, 0 failed. |
|
||
| Runtime harness command/scenario and exact result | N/A — the task forecast defines Unit 1 as schema-only, and the project has no PostgreSQL test harness or database-test dependency. Applying a live migration would require external database access outside this unit. |
|
||
| Rollback boundary | Delete `migrations/002_ocr_review.sql` and `tests/catalog/migration-002.test.ts`, revert task 1.2 to pending, and remove this Unit 1 progress/history entry. No existing migration or repository behavior must be reverted. |
|
||
|
||
### Additional Validation
|
||
|
||
- `npm run check` — exit 0.
|
||
- `git diff --check` — exit 0 after removing one trailing-space warning introduced in the history header.
|
||
|
||
### Deviations
|
||
|
||
None — the migration follows the proposal, specifications, design, and closed OCR persistence contract.
|
||
|
||
### Remaining Persistence Tasks
|
||
|
||
- [x] 1.1 Complete repository-level RED coverage for duplicate pending ingestion, lease recovery, and review transitions in Unit 2.
|
||
- [x] 1.2 Add migration 002 schema and pending OCR identity unique index.
|
||
- [x] 1.3 Add catalog repository candidates and leases in Unit 2.
|
||
- [x] 1.4 Refactor persistence code after Unit 2 reaches green.
|
||
|
||
### Review Boundary
|
||
|
||
- **Start:** Migration 001 already supplies lifecycle states and parent catalog tables.
|
||
- **End:** Migration 002 and its focused schema contract test are green.
|
||
- **Out of scope:** Repository methods, dispatch behavior, runtime PostgreSQL migration, Unit 2, commits, pushes, PRs, deployment, and secrets.
|
||
- **Rollback:** Remove only the two Unit 1 implementation files and their progress metadata.
|
||
- **Authored review budget:** 225 additions/deletions, below the 400-line budget for this autonomous slice.
|
||
|
||
## Unit 2: Repository
|
||
|
||
### Implementation Summary
|
||
|
||
- Added pending OCR identity lookup, atomic queue claiming, expired-lease recovery, and guarded review transitions to `CatalogRepository`.
|
||
- Added a focused fake-pool repository harness that covers duplicate reuse, empty queues, remote identity preservation, and invalid transitions.
|
||
- Corrected the canonical test script so it executes both root-level and nested test suites.
|
||
|
||
### TDD Cycle Evidence
|
||
|
||
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 1.1 | `tests/catalog/repository-ocr.test.ts` | Unit/fake pool | 4/4 existing tests passed | 0/6 with methods absent; then 5/6 while each completion gate was missing | 6/6 passed after task 1.3 | Found/not-found candidates, claimed/empty queue, expired lease, valid/invalid transition | Covered by 1.4 |
|
||
| 1.3 | `tests/catalog/repository-ocr.test.ts` | Unit/fake pool | 4/4 existing tests passed | Tests preceded production code | 6/6 passed | Multiple inputs and state paths exercised | Shared OCR row mapping extracted only after green |
|
||
| 1.4 | `tests/catalog/repository-ocr.test.ts` | Approval refactor | 6/6 before refactor | N/A — behavior-preserving approval baseline | 6/6 after refactor | Existing six cases preserved | Reusable aliased-column projection removed SQL duplication |
|
||
|
||
### Work Unit Evidence
|
||
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused test command and exact result | `NODE_ENV=test npx --no-install tsx --test tests/catalog/repository-ocr.test.ts` — exit 0; 6 tests passed, 0 failed. `NODE_ENV=test npx --no-install tsx --test tests/catalog/*.test.ts` — exit 0; 10 tests passed, 0 failed. |
|
||
| Runtime harness command/scenario and exact result | `NODE_ENV=test npx --no-install tsx --test tests/*.test.ts` — exit 0; 25 root tests passed, 0 failed. Canonical `npm test` — exit 0; 35 total tests passed, 0 failed, proving the 25 root and 10 nested tests execute together. |
|
||
| Rollback boundary | Revert Unit 2 changes in `src/modules/catalog/repository.ts`, delete `tests/catalog/repository-ocr.test.ts`, and revert the `package.json` test script; Unit 1 migration remains intact. |
|
||
|
||
### Validation and Boundary
|
||
|
||
- Corrective rerun for failed evidence revision `sha256:9433c0cd6628f36f4c96a27d41c6d429f4303d038ffe928f297367c274f21c2f`; fresh evidence revision `sha256:a19119f84381447f54a6223e2a203acaf9d3ae08b6ae8f585e3bd52cd510909b`.
|
||
- The failed canonical script expanded only nested tests after nested suites appeared. The minimal correction is `NODE_ENV=test tsx --test tests/*.test.ts tests/**/*.test.ts`.
|
||
- Focused repository suite passed 6/6; all catalog suites passed 10/10; explicit root suites passed 25/25; canonical `npm test` passed 35/35; `npm run check` and `git diff --check` exited 0.
|
||
- Native Unit 2 accounting was 511 changed lines. The maintainer approved `size:exception` exclusively for Unit 2; the corrective attempt has a separate 600-line remediation budget. All later units retain the normal 400-line budget.
|
||
- No design deviations. No Unit 3 work was started.
|
||
|
||
### Corrective TDD Evidence
|
||
|
||
| Correction | Safety Net / RED | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|
|
||
| Canonical test discovery | Failed evidence revision proved canonical `npm test` executed only 10 nested tests while the explicit root command executed 25 tests. | Updated only the package test argument list; canonical `npm test` passed 35/35. | Direct root 25/25 and nested catalog 10/10 results sum to and identify the canonical 35/35 run. | None needed — one package-script argument was added. |
|
||
|
||
## Unit 3: Extraction
|
||
|
||
### Implementation Summary
|
||
|
||
- Added a Node 22 fixture spike proving `pdf-parse` page callbacks backed by bundled PDF.js `2.0.550` preserve ordered pages, including a blank page.
|
||
- Added `parsePdfPages` with one-based page identity, raw native text, stable SHA-256 hashes, and fail-closed complete-page accounting.
|
||
- Preserved textual ingestion as data-only, excluded `CMakeLists.txt`, MDX, and shell lookalikes, and kept task 2.2 pending for Unit 4 detection/composition assertions.
|
||
|
||
### TDD Cycle Evidence
|
||
|
||
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 2.1 | `tests/parsers/pdf-pages.test.ts`; `scripts/spike-pdfjs.ts` | Runtime spike | 20/20 lifecycle service tests passed | Focused suite failed 0/1 because `parsePdfPages` did not exist; the spike and parser implementation were still absent. | Node 22.14.0 spike returned pages 1–3 exactly, including blank page 2. | Three pages prove non-empty, blank, and later-page ordering paths. | Replaced the over-budget direct dependency with the design-approved existing `pdf-parse` pagerender path; spike remained green. |
|
||
| 2.3 | `tests/parsers/pdf-pages.test.ts` | Unit/integration fixture | 20/20 lifecycle service tests passed | Focused suite failed 0/1 on the missing export before production code. | Final focused run passed 5/5 after implementation. | Covered page hashes/blank page, native PDF composition, non-PDF rejection, data-only text parsing, and unsupported lookalikes. | Focused suite remained 5/5 after consolidating on the existing parser dependency. |
|
||
|
||
### Work Unit Evidence
|
||
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused test command and exact result | `NODE_ENV=test npx --no-install tsx --test tests/parsers/pdf-pages.test.ts` — exit 0; 5 passed, 0 failed. |
|
||
| Runtime harness command/scenario and exact result | `NODE_ENV=test npx --no-install tsx scripts/spike-pdfjs.ts` — exit 0 on Node 22.14.0; PDF.js 2.0.550 returned three ordered pages with page 2 blank. |
|
||
| Rollback boundary | Revert `src/modules/parsers/parser-registry.ts`; delete `scripts/spike-pdfjs.ts`, `tests/parsers/pdf-pages.test.ts`, and `tests/fixtures/ocr/native-three-pages.pdf`; revert tasks 2.1/2.3 and this Unit 3 progress/history entry. Units 1–2 remain intact. |
|
||
|
||
### Validation and Boundary
|
||
|
||
- Canonical `npm test` passed 40/40; `npm run check` and `git diff --check` exited 0.
|
||
- Native authored slice before progress/history metadata: 225 additions/deletions; no package or lockfile delta remains.
|
||
- Start: Unit 2 persistence is complete. End: per-page native extraction and parser threat routing are green.
|
||
- Out of scope: detection thresholds, OCR selection, composition, risks, Unit 4 tasks 2.4/2.5, commits, pushes, PRs, deployment, and restricted paths.
|
||
- No specification deviation. The design-authorized `pdf-parse` pagerender fallback was selected only after its Node 22 fixture spike passed.
|
||
|
||
## Unit 4: Detection and Composition
|
||
|
||
### Implementation Summary
|
||
|
||
- Added pure per-page native detection under `pdf-detection-v1`, exact OCR page selection, verified-blank classification, and fail-closed OCR quality gates.
|
||
- Added deterministic candidate composition using exactly one source per page, ordered OCR lines, blank omission, canonical hashes, and unchanged risk tokens prioritized for review.
|
||
- Completed the remaining task 2.2 scenarios by combining the Unit 3 data-only/parser threat coverage with Unit 4 mixed-PDF, detection, blank, hash, and risk RED assertions.
|
||
|
||
### TDD Cycle Evidence
|
||
|
||
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 2.2 | `tests/ocr/detection.test.ts`; `tests/parsers/pdf-pages.test.ts` | Unit/integration fixture | Unit 3 parser suite passed 5/5 before edits. | Focused Unit 4 suite failed before production with `ERR_MODULE_NOT_FOUND` for `src/modules/ocr/detection.js`. | Focused suite passed 5/5 after detection and composition were added. | Real generated mixed PDF selected pages 2 and 3; `.txt`/`.md` stayed data-only and CMake/MDX/shell lookalikes stayed unsupported. | None needed; routing remains separated between parser support and the pure PDF OCR planner. |
|
||
| 2.4 | `tests/ocr/detection.test.ts` | Unit | N/A (new production file) | Threshold, mixed-page, blank, and quality assertions preceded production code. | Exact native, blank, and OCR quality boundaries passed in the 5/5 focused suite. | Every native and OCR threshold has a passing boundary plus a failing neighbor; visible ink with empty OCR is blocked. | Pure functions and named policy version were present at green; final focused suite remained 5/5. |
|
||
| 2.5 | `tests/ocr/detection.test.ts` | Unit | N/A (new production file) | Ordered composition, blank omission, canonical hash, audit-source, and risk assertions preceded production code. | Deterministic composition and risk prioritization passed in the 5/5 focused suite. | Reordered pages, tied OCR boxes, blank middle page, repeated token, and four known ambiguous tokens exercise distinct paths. | Reused shared canonical JSON and SHA-256 utilities; final focused suite and type check remained green. |
|
||
|
||
### Test Summary
|
||
|
||
- **Tests:** 5 written and focused-passing; 45 canonical (unit 4, integration fixture 1)
|
||
- **Approval tests:** None — new production files; **pure functions created:** 6
|
||
|
||
### Work Unit Evidence
|
||
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused test command and exact result | `NODE_ENV=test npx --no-install tsx --test tests/ocr/detection.test.ts` — exit 0; 5 tests passed, 0 failed. |
|
||
| Runtime harness command/scenario and exact result | `NODE_ENV=test npx --no-install tsx --test --test-name-pattern "parsed mixed PDF" tests/ocr/detection.test.ts` — exit 0; 1 test passed, 0 failed. The harness generated a three-page PDF, parsed it through the Node 22 `pdf-parse` page callback, and selected only blank page 2 and insufficient page 3 for OCR. |
|
||
| Rollback boundary | Delete `src/modules/ocr/detection.ts`, `src/modules/ocr/composition.ts`, and `tests/ocr/detection.test.ts`; revert tasks 2.2/2.4/2.5 and this Unit 4 progress/history entry. Units 1–3 and their parser threat coverage remain intact. |
|
||
|
||
### Validation and Boundary
|
||
|
||
- Canonical `npm test` passed 45/45; `npm run check`, tracked `git diff --check`, and no-index whitespace checks for all three new files exited 0.
|
||
- Authored Unit 4 code and tests total 322 additions and 0 deletions, below the 400-line budget with no size exception.
|
||
- Start: Unit 3 page extraction and parser threat routing are complete. End: detection, blank/quality gates, deterministic composition, hashes, and risk tokens are green.
|
||
- Out of scope: OCR service, client, dispatcher, ingest routing, review APIs, commits, pushes, PRs, deployment, and restricted paths.
|
||
- No specification or design deviation.
|
||
|
||
## Unit 5: OCR Service
|
||
|
||
### Implementation and Correction Summary
|
||
|
||
- Added pinned Unit 5 FastAPI test dependencies and retained the ignored `ocr-service/.venv` environment.
|
||
- Added RED-first HTTP tests and a SQLite-backed private API for bearer auth, idempotency, allowlisting, upload/page limits, queue pressure, status, deletion, and health.
|
||
- Preserved failed evidence `sha256:4a5c8ed8d8b302f7ff654d4f40fe0f13049bfdd29db4c76576f3847c0a9be15a`: its harness returned `401/400/400`; inspection showed `&` backgrounded the preceding shell AND-list, so request variables existed only in the child shell and authenticated curls sent an empty `request` form, reproduced as `400 INVALID_REQUEST`.
|
||
- Corrected only the harness command boundary; the 381-line candidate implementation remained unchanged before progress persistence.
|
||
|
||
### TDD Cycle Evidence
|
||
|
||
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 3.1 | `ocr-service/tests/test_api.py` | HTTP integration | N/A (new service) | Collection failed with `ModuleNotFoundError: No module named 'app'`; failed localhost runtime evidence then supplied correction RED. | 8/8 focused tests and corrected curl `401/202/409` passed. | Auth variants, idempotency/conflict, allowlist, limits, pressure, integrity, and real multipart requests exercised distinct paths. | No production refactor: the defect was shell variable scope in the harness. |
|
||
| 3.2 | `ocr-service/tests/test_api.py` | HTTP integration/SQLite | N/A (new service) | Same missing-service RED covered the API; failed harness blocked completion. | 8/8 focused tests and real Uvicorn harness passed. | Ready/not-ready health, queue capacity, status, deletion, and process cleanup exercised distinct paths. | Pure page hashing and centralized terminal errors retained; no correction needed. |
|
||
|
||
### Work Unit Evidence
|
||
|
||
- Focused: `ocr-service/.venv/bin/python -m pytest ocr-service/tests -k auth` — exit 0; 8 passed, 0 failed (one dependency deprecation warning).
|
||
- Runtime: local Uvicorn plus multipart curl — unauthorized `401` with `UNAUTHORIZED`, accepted `202` with queued identity, conflicting same key `409` with `IDEMPOTENCY_CONFLICT`.
|
||
- Gates: `npm test` 45/45, `npm run check`, Python `compileall`, tracked and untracked whitespace checks — all exit 0/equivalent clean.
|
||
- Cleanup: Uvicorn PID 268425 terminated and absent; temporary PDF, SQLite DB, and log removed; `.venv` retained.
|
||
- Rollback: remove `.gitignore` Unit 5 entries and `ocr-service/`, then revert tasks 3.1/3.2 and this Unit 5 progress/history block; Units 1–4 remain intact.
|
||
- Fresh evidence revision: `sha256:0d8b2c57bbab967473eaf99a7ca900168554e64254fa80197b3626b4570b58d0`; corrected final authored change count: 397 additions plus deletions, within 400.
|
||
|
||
## Unit 6: OCR Rendering and Image
|
||
|
||
### Implementation Summary
|
||
|
||
- Added deterministic 200 DPI PDF page rendering with a pre-render 25-megapixel guard, a PaddleOCR adapter, stable line identities, quality metrics, and the exact result schema.
|
||
- Added a deterministic fake-driven test seam and preserved ambiguous OCR tokens without correction.
|
||
- Added the private CPU image with PaddleOCR `3.4.0`, PaddlePaddle CPU `3.2.2`, baked Latin/mobile models, one Uvicorn worker, a non-root user, private job storage, healthcheck, and documented 3 CPU/5 GiB deployment limits.
|
||
- Added a Dockerfile-specific deny-by-default build context so restricted and unrelated repository paths are never sent to the Docker daemon.
|
||
|
||
### TDD Cycle Evidence
|
||
|
||
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 3.3 | `ocr-service/tests/test_render.py` | Unit/integration fixture | Existing OCR API suite passed 8/8 before production edits. | Focused collection failed because `app.engine` did not exist. | Focused render suite passed 5/5; complete OCR suite passed 13/13. | Selected pages 1/3, invalid page and pixel-limit paths, repeatable fake results, and Paddle payload normalization exercise distinct behavior. | Named constants and pure metric/result transforms retained; focused suite remained 5/5. |
|
||
| 3.4 | `ocr-service/tests/test_render.py` | Static/container runtime | Existing OCR API suite passed 8/8 before production edits. | Docker contract was absent; deny-by-default context triangulation then failed until `Dockerfile.dockerignore` existed. | Image built with baked models; offline constrained container rendered and validated PNG/result output. | Static pin/runtime assertions plus real model build and `--network none` execution cover configuration and runtime paths. | Added missing OpenCV runtime libraries after the first build exposed `libGL.so.1`; exact corrected image passed. |
|
||
|
||
### Work Unit Evidence
|
||
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused test command and exact result | `ocr-service/.venv/bin/python -m pytest ocr-service/tests -k render -q` — exit 0; 5 passed, 8 deselected, 1 dependency deprecation warning. |
|
||
| Runtime harness command/scenario and exact result | `docker build --file ocr-service/Dockerfile --tag rag-ocr-service:unit6 .` — corrected exact image exited 0 and baked both models. A `docker run --rm --network none --cpus 3 --memory 5g ...` harness loaded cached PaddleOCR 3.4.0/PaddlePaddle 3.2.2 models, rendered a 1700x2200 PNG (23,309 bytes), returned schema `1`, one page, and one OCR line; host Pillow reopened it as PNG 1700x2200. |
|
||
| Rollback boundary | Remove `ocr-service/{Dockerfile,Dockerfile.dockerignore,app/engine.py,app/models.py,app/render.py,tests/test_render.py}` and revert Unit 6 changes in `app/main.py`, `requirements.txt`, and `README.md`; Units 1–5 remain intact. |
|
||
|
||
### Validation and Boundary
|
||
|
||
- RED was followed by GREEN and post-refactor reruns. One expected GREEN iteration corrected a test-side non-whitespace count from 16 to the mathematically correct 15.
|
||
- The first image build failed on missing `libGL.so.1`; adding the required slim-image runtime libraries produced a successful build and runtime harness. A later optional connectivity-check refactor rebuild exhausted Docker storage; that unvalidated one-line refactor was reverted, the validated Dockerfile bytes were restored, and the image tag was removed during cleanup.
|
||
- `npm test` passed 45/45; `npm run check`, Python `compileall`, tracked/untracked whitespace checks, and the full OCR pytest suite (13/13) passed.
|
||
- Start: Unit 5 private queue/API is complete. End: Unit 6 render/engine/schema/container behavior is complete. Unit 7 client work was not started.
|
||
- Native Unit 6 implementation/tests/docs are 397 authored additions plus deletions, within the 400-line budget; required SDD progress and workspace history metadata are administrative evidence outside that native slice.
|
||
- No specification or design deviation. The Docker image is private by deployment contract; CPU/RAM labels document limits that the platform must enforce.
|
||
|
||
## Unit 7: OCR Client and Durable Artifacts
|
||
|
||
### Implementation Summary
|
||
|
||
- Added a private OCR HTTP client that preserves the exact idempotency key across one initial submission and at most two transient resubmissions, treats `429` as retryable queue pressure without immediate resubmission, and treats deterministic failures as terminal.
|
||
- Added 2–15 second polling backoff plus strict acknowledgement, status, engine, job, document, ordered-page, metrics, line, and schema validation before a result can be persisted.
|
||
- Added durable `0600` original and canonical manifest writes, UUIDv5 document artifact identities, lexical path containment, verifiable hashes, and age-gated orphan sweeping that preserves retained, recent, non-UUID, and symlink entries.
|
||
- Left task 4.1 pending because Unit 7 proves only its retries/integrity clause; Unit 8 still owns flag-off `201`, OCR `202`/status, OCR-down fail-closed, and catalog-down `503` HTTP coverage.
|
||
|
||
### TDD Cycle Evidence
|
||
|
||
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 4.2 | `tests/ocr/client.test.ts` | Unit/runtime fetch stub | Existing OCR detection suite passed 5/5; production file was new. | Focused suite failed with `ERR_MODULE_NOT_FOUND` for `src/modules/ocr/client.js` before production code existed. | Final focused suite passed 6/6; runtime retry scenario passed 1/1. | Network then `503` produced exactly three same-key attempts with 2s/4s backoff; `429` and `422` each stopped after one attempt; malformed acknowledgement/schema/pages failed integrity; polling used 2s/4s. | Adapted multipart bytes to a type-safe `Uint8Array`; focused suite and type check remained green. |
|
||
| 4.3 | `tests/ocr/client.test.ts` | Filesystem integration | Existing OCR detection suite passed 5/5; production file was new. | The same missing-module RED preceded artifact production code. | Private originals, canonical manifest, safe resolution, and sweep passed in the final 6/6 suite. | Two out-of-order documents proved canonical ordering/hashes; traversal was rejected; sweep removed only an old orphan while preserving retained/recent directories and a symlink. | A test-side assertion was corrected to inspect the retained dangling symlink with `lstat`; no production behavior changed, and the suite passed 6/6. |
|
||
|
||
### Test Summary
|
||
|
||
- **Tests written and passing:** 6 focused tests; unit/fetch-stub and filesystem integration layers.
|
||
- **Approval tests:** None — both production modules are new.
|
||
- **RED:** exit 1, module-not-found before either production file existed.
|
||
- **GREEN/REFACTOR:** exit 0, 6 passed, 0 failed.
|
||
|
||
### Work Unit Evidence
|
||
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused test command and exact result | `NODE_ENV=test npx --no-install tsx --test tests/ocr/client.test.ts` — exit 0; 6 passed, 0 failed. |
|
||
| Runtime harness command/scenario and exact result | `NODE_ENV=test npx --no-install tsx --test --test-name-pattern "runtime retry stub" tests/ocr/client.test.ts` — exit 0; 1 passed, 0 failed. Deterministic stub observed network failure, `503`, then `202`: exactly 3 submission attempts, one unchanged idempotency key, 2,000/4,000 ms backoffs, followed by a strictly validated two-page result. |
|
||
| Rollback boundary | Remove `src/modules/ocr/client.ts`, `src/modules/ocr/artifacts.ts`, and `tests/ocr/client.test.ts`; revert tasks 4.2/4.3 and this Unit 7 progress/history metadata. Units 1–6 remain intact. |
|
||
|
||
### Validation and Boundary
|
||
|
||
- `npm test` passed 51/51; `npm run check`, `npm run build`, tracked whitespace, and no-index whitespace checks for all three new files passed.
|
||
- Test-created `rag-ocr-artifacts-*` and `rag-ocr-sweep-*` directories were removed; no server, external OCR call, or persistent process was started; `ocr-service/.venv` was preserved.
|
||
- Start: Unit 6 OCR rendering and container runtime are complete. End: Unit 7 client retries/integrity and durable artifacts/orphan sweep are implemented and green.
|
||
- Out of scope and untouched: dispatcher, reconciler, ingest branch, app upload/status routes, Unit 8 tests, commits, pushes, PRs, native review, deployment, and restricted paths.
|
||
- The implementation/test slice is 506 authored additions (client 174, artifacts 129, tests 203). This exceeds the 400-line default after one honest cohesive assessment; no code-golf or second slicing pass was attempted. The maintainer explicitly approved `size:exception`, including 585 total native-accounting lines with required metadata.
|
||
- No specification or design deviation.
|
||
|
||
### Approved Size-Exception Corrective Context
|
||
|
||
- The native attempt was reset after failed evidence revision `sha256:dd224c3864adc8380dc7d9fd147cb448f5da8138277f3f346a35d7eeb3aca158`; the failure was budget-only, not a functional defect.
|
||
- Corrective work unit `unit-7-size-exception-validation` uses attempt token `sha256:0109cfd8d3b37a5672c65a37700269b89cb80e4e357a39f68a920779a1715c39` and must settle against the failed revision through the parent.
|
||
- Production and test files remain unchanged for the corrective rerun; only approval/reset evidence and fresh validation metadata may change.
|
||
- Task 4.1 remains pending, while tasks 4.2 and 4.3 remain complete. Unit 8 was not started.
|
||
|
||
## Unit 8: Dispatcher and Runtime Orchestration
|
||
- Completed multi-document progress, uniqueness-race reuse, exact lease recovery, persisted-key dispatch, default runtime wiring, authenticated status, and fail-closed retrieval; no activation or embeddings occur on OCR failure.
|
||
|
||
### Evidence Chain and Review Accounting
|
||
- **Complete review slice:** Relative to committed HEAD `ac046e3`, Unit 8 contains 1,045 functional changed lines plus 41 required metadata lines, for 1,086 total changed lines. The complete slice is not within 400 lines, and no `size:exception` was approved or recorded for it.
|
||
- **Interrupted preserved candidate:** Native failed/interrupted revision `sha256:182cc73954a8518d03bd6c3ef5f4141b352aa9b69fca4177447d76deaddad223` contained 815 changed lines.
|
||
- **Auto-chain reset:** The maintainer-selected preflight strategy was `auto-chain` with `feature-branch-chain`. Native reset revision `sha256:c0db82cf3db8ae790d34e4c310b998f97bb36864c97c3aa84e4c87ab77d07c2a` preserved the interrupted candidate and scoped the next bounded recovery objective only to additional changes; it did not approve the complete Unit 8 slice as a size exception.
|
||
- **Recovery delta:** After that reset, the bounded recovery added 350 functional changed lines plus 41 required metadata lines, for 391 total changed lines within the separate 400-line recovery objective.
|
||
- **Passed recovery:** Fresh passed evidence revision `sha256:e424afe7b2f8efd547dcfd0baf64ec1b46ac344f39afd87259560a094d59163f` closes the interrupted → auto-chain reset → passed recovery chain.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 4.1 | `tests/ocr/{dispatcher,client}.test.ts` | HTTP/integration | Recovered baseline 7/7 passed; not labeled RED | New scenarios produced genuine 5/9 RED; catalog `/retrieve` already failed closed | Unit 8 9/9; client 6/6 | 201/202/status, OCR failure, 503 retrieval, retries/integrity | Consolidated persisted-key assertion; 9/9 |
|
||
| 4.4 | `tests/ocr/dispatcher.test.ts` | Unit/integration | Recovered baseline 7/7 | Exact recovery failed by claiming unrelated work | 9/9 | Live/new queue separation plus periodic dispatch | Exact-job dispatch helper; 9/9 |
|
||
| 4.5 | `tests/ocr/dispatcher.test.ts` | HTTP/filesystem | Recovered baseline 7/7 | Runtime wiring RED was 8/10 | 9/9 | Multipart upload, durable reload, duplicate race, mixed documents | Shared queue drain; 9/9 |
|
||
|
||
### Work Unit Evidence
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused test | `NODE_ENV=test npx --no-install tsx --test tests/ocr/dispatcher.test.ts` — 9 passed, 0 failed. |
|
||
| Focused repository OCR test | `NODE_ENV=test npx --no-install tsx --test tests/catalog/repository-ocr.test.ts` — 6 passed, 0 failed. |
|
||
| Canonical Node test | `npm test` — 60 passed, 0 failed. |
|
||
| Runtime harness | Bounded localhost curl — ingest `202/ocr_queued`; authenticated status `200/ocr_queued`; leaked child from initial shell cleanup was detected, terminated, and port/temp cleanup reverified. |
|
||
| Rollback boundary | Revert Unit 8 deltas in `src/{app,config/env}.ts`, catalog/ingest/client modules, `src/modules/ocr/dispatcher.ts`, and its test; preserve Units 1–7. |
|
||
|
||
## Unit 9: Review and Indexing Core
|
||
- Added authenticated review/approval routes with injectable service boundaries, immutable correction preparation, stale/duplicate conflict guards, and new/reusable activation CAS behavior. Rejection transition and playground UI remain Unit 10.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 5.1 | `tests/ocr/review.test.ts` | HTTP/unit | App suites 9/9 and 20/20 | Missing `indexing.js`; exit 1 | 5/5 | Unauthorized/premature, candidate/line/active staleness, duplicate targets, new/reusable races, rejected no-index | Assertions remained 5/5 |
|
||
| 5.2 | `tests/ocr/review.test.ts` | HTTP/unit | 29/29 | Tests preceded `review.ts` | 5/5 | Audit view plus two corrections and four conflict paths | Clone prevents failed commits mutating loaded candidates; 5/5 |
|
||
| 5.3 | `tests/ocr/review.test.ts` | Unit | N/A (new file) | Tests preceded `indexing.ts` | 5/5 | New/reusable true/false, both CAS races, rejected no-index | Named CAS error mapping; 5/5 |
|
||
|
||
### Work Unit Evidence
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused test | `NODE_ENV=test npx --no-install tsx --test tests/ocr/review.test.ts` — exit 0; 5 passed, 0 failed. |
|
||
| Runtime harness | Same runner with `--test-name-pattern "review HTTP rejects"` — exit 0; 1 passed; real localhost Express requests returned authenticated boundary outcomes without external services. |
|
||
| Rollback boundary | Remove `src/modules/ocr/{review,indexing}.ts` and `tests/ocr/review.test.ts`; revert only the Unit 9 route additions in `src/app.ts` and Unit 9 metadata. Preserve Units 1–8. |
|
||
|
||
### Validation and Boundary
|
||
- Canonical `NODE_ENV=test npm test` passed 65/65; `npm run check`, `npm run build`, tracked whitespace, and no-index whitespace checks passed.
|
||
- Unit 9 adds 341 functional changed lines and 38 metadata changed lines, 379 total, within the 400-line budget. No live PostgreSQL, OpenAI, Qdrant, or OCR process was used; no test process remained.
|
||
- Start: Unit 8 dispatch/status is complete. End: review/correction and indexing/CAS ports plus authenticated routes are green. Unit 10 task 5.4 remains unchecked.
|
||
|
||
## Unit 10: Rejection and Review UI
|
||
- Added authenticated rejection with current candidate-hash and state guards, required audit fields, durable store completion before success, and no indexing or activation call.
|
||
- Extended the existing static playground with authenticated candidate loading, protected page images, native/raw/candidate comparisons, line confidence/boxes, corrections, risks, approval, and rejection controls.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 5.4 | `tests/ocr/review.test.ts` | HTTP integration/static HTTP | Existing review suite passed 5/5 before edits. | Genuine RED passed only 5/7: rejection returned 404 instead of authenticated 401 and the playground lacked the review contract. A malformed-body edge then returned 409 instead of 400. | Focused suite passed 7/7 after minimal rejection and UI implementation. | Unauthorized, malformed, stale-hash, successful, repeated-state, durable-status, and static audit-field paths exercise distinct outcomes. | Shared UI decision-state helper extracted; focused suite remained 7/7. |
|
||
|
||
### Work Unit Evidence
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused test command and exact result | `NODE_ENV=test npx --no-install tsx --test tests/ocr/review.test.ts` — exit 0; 7 passed, 0 failed. |
|
||
| Runtime harness command/scenario and exact result | `NODE_ENV=test npx --no-install tsx --test --test-name-pattern "authenticated rejection" tests/ocr/review.test.ts` — exit 0; 1 passed, 0 failed. Real localhost Express requests returned 401/400/409/200, persisted the rejected audit state through the injected store, retrieved rejected status, and made zero indexing calls. |
|
||
| Rollback boundary | Revert Unit 10 deltas in `src/app.ts`, `src/modules/ocr/review.ts`, `tests/ocr/review.test.ts`, and `public/playground/{index.html,app.js,styles.css}` plus Unit 10 metadata; preserve Units 1–9. |
|
||
|
||
### Validation and Boundary
|
||
- Canonical `NODE_ENV=test npm test` passed 67/67; `npm run check` and `npm run build` exited 0.
|
||
- Previously observed tracked `git diff --check` exited 0; no-index whitespace checks for Unit 10's untracked `src/modules/ocr/review.ts` and `tests/ocr/review.test.ts` exited 0.
|
||
- Unit 10 adds 271 functional and 42 metadata changed lines, 313 total, below the 400-line budget. No live database, embedding, vector, OCR, or external service was used; no persistent process was started.
|
||
- Start: Unit 9 review/indexing ports and approval routes are green. End: rejection isolation and the static review UI are green. Retention, deploy, OpenAPI, and E2E tasks remain untouched.
|
||
- No specification or design deviation.
|
||
|
||
## Unit 11: Retention and Flag-Off Isolation
|
||
|
||
### Implementation Summary
|
||
- Added a retention boundary that expires 30-day reviews before deletion, retains failed/rejected artifacts for 7 days, and retains superseded OCR artifacts for 30 days.
|
||
- Added exact state/artifact CAS claims, active-pointer checks, activation exclusion after a deletion claim, and restart-safe `retention_deleting→retention_deleted` completion.
|
||
- Wired retention into the existing exclusive reconciler cycle and made status/review/decision APIs return `404` without store access while OCR is disabled; native synchronous ingestion remains unchanged.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 6.1 | `tests/ocr/retention.test.ts` | Repository/HTTP/filesystem integration | Existing repository, dispatcher, and review suites passed 22/22. | Genuine missing-module RED failed before `retention.ts`; activation-race RED then failed 0/1 with “Missing expected rejection.” | Focused suite passed 6/6. | Exact TTL SQL, active pointer/state guards, flag-off `201/404`, and deletion-claim activation conflict exercise independent paths. | No production refactor was needed; two test-side expectations were corrected during GREEN without weakening behavior. |
|
||
| 6.2 | `tests/ocr/retention.test.ts` | Filesystem/reconciler integration | Same 22/22 safety net. | The missing-module RED preceded all production retention code. | Focused suite passed 6/6; relevant regression set passed 28/28. | Review expiry, terminal deletion, active preservation, simulated restart, repeat no-op, and exclusive reconciler execution cover distinct paths. | Named store boundary keeps filesystem behavior isolated; final focused suite remained 6/6. |
|
||
|
||
### Work Unit Evidence
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused test command and exact result | `NODE_ENV=test npx --no-install tsx --test tests/ocr/retention.test.ts` — exit 0; 6 passed, 0 failed. |
|
||
| Runtime harness command/scenario and exact result | The focused suite used real temporary directories and localhost Express requests: interrupted deletion removed only the claimed directory, resumed to `retention_deleted`, repeated as a no-op, preserved the active directory, returned native `201`, and returned `404` for all disabled OCR candidate APIs without store access. |
|
||
| Rollback boundary | Remove `src/modules/ocr/retention.ts` and `tests/ocr/retention.test.ts`; revert only Unit 11 seams in `src/app.ts`, catalog repository/reconciler, and explicit enabled-state setup in dispatcher/review tests. Preserve Units 1–10. |
|
||
|
||
### Validation and Boundary
|
||
- Relevant regression suites passed 28/28 after explicitly enabling OCR in pre-existing enabled-path tests; the initial 19/22 run exposed only that test-fixture assumption.
|
||
- Canonical `NODE_ENV=test npm test` passed 73/73; `npm run check`, `npm run build`, tracked whitespace, and no-index whitespace checks for all seven intended untracked files passed.
|
||
- Temporary `rag-retention-*` directories were removed and no `tsx --test` or Node test process remained. No live database, OCR, embedding, vector, or external service was used; `ocr-service/.venv` was preserved.
|
||
- Unit 11 adds 277 functional changed lines and 50 metadata changed lines, 327 total, below the 400-line budget.
|
||
- Start: Unit 10 rejection/UI is complete. End: tasks 6.1–6.2 are complete. OpenAPI, deployment/configuration, E2E, production, commit, push, and PR work remain untouched.
|
||
- No specification or design deviation.
|
||
|
||
## Unit 12: Contracts and Deployment Wiring
|
||
|
||
### Implementation Summary
|
||
- Documented OCR `202` ingestion variants and authenticated status, review, correction/approval, and rejection routes with behavior-matched success and error responses.
|
||
- Added progress, review evidence, optimistic correction, decision, and structured error schemas while preserving the legacy-disabled `202` variants.
|
||
- Added private durable artifact defaults and OCR limits/timeouts; the root image now declares `/data/ingestions`, runs as `node`, and applies catalog migrations before startup without embedding secrets.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 6.3 | `tests/ocr/contracts-deploy.test.ts` | OpenAPI contract | Existing lifecycle/dispatcher/review/retention suites passed 42/42. | Genuine initial RED was 0/3: OCR `202` referenced the native schema and OCR routes/schemas were absent. | Focused suite passed 3/3. | A second RED required legacy-disabled `202` variants and accurate help authentication; final suite remained 3/3. | Shared schema references kept nested contracts reviewable; no behavior changed. |
|
||
| 6.4 | `tests/ocr/contracts-deploy.test.ts` | Config/static container | Same 42/42 safety net. | Initial RED found the old relative artifact root, absent limits/timeouts, volume, non-root user, and migration-aware command. | Focused suite passed 3/3; sanitized Docker build and image inspection passed. | A second RED required explicit runtime artifact-root wiring; final suite remained 3/3. | No further refactor needed. |
|
||
|
||
### Work Unit Evidence
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused test command and exact result | `NODE_ENV=test npx --no-install tsx --test tests/ocr/contracts-deploy.test.ts` — exit 0; 3 passed, 0 failed. |
|
||
| Relevant regression command and exact result | Lifecycle, dispatcher, review, retention, and contract suites — exit 0; 45 passed, 0 failed. |
|
||
| Runtime harness | N/A — Unit 12 is static-only by task forecast. A sanitized-context Docker build succeeded and inspection proved user `node`, durable volume, production artifact root, and migration-before-server command. |
|
||
| Rollback boundary | Remove `tests/ocr/contracts-deploy.test.ts` and revert only Unit 12 deltas in `src/api/openapi.ts`, `src/config/env.ts`, and `Dockerfile`; preserve Units 1–11. |
|
||
|
||
### Validation and Boundary
|
||
- Canonical `NODE_ENV=test npm test` passed 76/76; `npm run check`, `npm run build`, tracked whitespace, and all intended-untracked whitespace checks passed.
|
||
- Docker build `rag-service:unit12-contract` succeeded from a sanitized 430.31 kB context; inspection matched the deployment contract. The image and temporary context were removed, no test process remained, and `ocr-service/.venv` was preserved.
|
||
- Unit 12 adds 219 functional lines and 50 metadata lines, 269 total, below the 400-line budget.
|
||
- Start: Unit 11 retention is complete. End: tasks 6.3–6.4 are complete. Unit 13 / all 7.x E2E and production work remains untouched.
|
||
- `docs/CONTRATO_CICLO_VIDA_Y_OCR.md` was read as authoritative and remained unchanged. No specification or design deviation.
|
||
|
||
## Unit 13: Local Deterministic E2E and Complete Gate
|
||
|
||
### Implementation Summary
|
||
- Added a bounded localhost HTTP suite covering native `201`, scanned `202 → review → approve → active`, and exact mixed native/OCR page reporting.
|
||
- Added resend identity reuse, fail-closed OCR exhaustion, and catalog-unavailable `503` coverage without external services.
|
||
- Made no production-code changes; the new scenarios passed immediately as baseline/characterization evidence, so no RED was fabricated.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Test File | Layer | Safety Net | RED / Baseline | GREEN | TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 7.1 | `tests/ocr/e2e.test.ts` | Local HTTP E2E | Existing OCR suites passed 28/28. | Baseline/characterization: the test was written first and passed 2/2 immediately; no implementation gap existed at the local fake boundary. | Focused suite passed 2/2. | Native, scanned, corrected approval, post-activation status, and mixed page methods exercise distinct paths. | Test-only state naming improved; focused suite remained 2/2. |
|
||
| 7.2 | `tests/ocr/e2e.test.ts` | Local HTTP/dispatcher E2E | Same 28/28 safety net. | Baseline/characterization: resend, OCR-down, and catalog-down assertions passed immediately; no production code was written. | Focused suite passed 2/2. | Duplicate resend, terminal dispatch failure, status `503`, and retrieval `503` cover independent outcomes. | None needed beyond the shared test harness. |
|
||
| 7.3 | Gate commands below | Complete gate | Focused E2E passed 2/2. | N/A — verification-only task with no production behavior to implement. | All required gates passed. | Node type/build/test and offline Python tests provide independent stack evidence. | N/A — no production change. |
|
||
|
||
### Work Unit Evidence
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused test | `NODE_ENV=test npx --no-install tsx --test tests/ocr/e2e.test.ts` — exit 0; 2 passed, 0 failed. |
|
||
| Runtime harness | The focused suite started bounded ephemeral localhost Express servers and exercised real HTTP requests through ingest, status, review, approval, and retrieval routes; all servers closed through test cleanup. |
|
||
| Complete gate | `npm run check`, `npm run build`, and `NODE_ENV=test npm test` exited 0; canonical Node passed 78/78. `PYTHONDONTWRITEBYTECODE=1 ocr-service/.venv/bin/python -m pytest ocr-service/tests -q` passed 13/13 offline with one dependency deprecation warning. |
|
||
| Rollback boundary | Remove `tests/ocr/e2e.test.ts` and revert only tasks 7.1–7.3 plus this Unit 13 progress/history metadata; preserve Units 1–12. |
|
||
|
||
### Validation and Boundary
|
||
- Unit 13 adds 183 functional lines. Required tasks/progress/history metadata adds 52 changed lines, for 235 total, below the 400-line budget.
|
||
- Tracked and complete intended-untracked whitespace checks passed. No matching `tsx --test`, E2E Node, or OCR Uvicorn process remained after the corrected self-excluding process check.
|
||
- Intended untracked inventory: `src/modules/ocr/{dispatcher,indexing,retention,review}.ts`; `tests/ocr/{contracts-deploy,dispatcher,e2e,retention,review}.test.ts`.
|
||
- Tasks 7.1–7.3 are complete. Task 7.4 remains unchecked and untouched; no production flag, service, database, vector store, embedding provider, OCR endpoint, commit, push, or PR was used.
|
||
|
||
## Unit 14a: OCR Job Execution and Result
|
||
- Persisted accepted PDF bytes/results in SQLite, resumed queued jobs on idempotent resubmission, executed the existing renderer through an injected engine, recorded terminal status/errors, and added authenticated result retrieval; task 7.4 remains pending.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Test File | Safety Net | RED | GREEN / TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|
|
||
| Unit 14a | `ocr-service/tests/test_api.py` | 13/13 | 8/10 missing execution/result; recovery triangulation 9/10 remained queued | 10/10; success, restart recovery, unauthorized, missing, not-ready | Guarded duplicate workers; 10/10 remained green |
|
||
|
||
### Work Unit Evidence
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused/regression | API 10/10; OCR 15/15; Node 78/78; `npm run check` and whitespace clean. |
|
||
| Runtime harness | Local Uvicorn with deterministic engine returned upload `202`, status `200/succeeded`, result `200`, matching job/document identity; process/temp cleanup verified. |
|
||
| Rollback boundary | Revert `ocr-service/app/main.py`, `ocr-service/tests/test_api.py`, and this Unit 14a/history metadata; Units 1–13 remain intact. |
|
||
|
||
## Unit 14b: OCR Transient Cleanup
|
||
- Added authenticated idempotent client deletion after durable RAG result acceptance and automatic 24-hour OCR row/input/result expiry through the existing 30-second readiness healthcheck; cleanup failures fall back to TTL without failing durable candidates.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Test Files | Safety Net | RED | GREEN / TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|
|
||
| Unit 14b | `ocr-service/tests/test_api.py`; `tests/ocr/{client,dispatcher}.test.ts` | Python 10/10; Node 15/15 | Python 10/11 lacked clock/sweep; Node 15/17 lacked delete client/dispatch | Python 11/11; Node 17/17; pre-persistence failure retained remote state and cleanup failure preserved local success | Reused healthcheck and generic retry/auth boundaries; focused suites remained green |
|
||
|
||
### Work Unit Evidence
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused/regression | Python API 11/11; OCR 16/16; focused Node 17/17; canonical Node 80/80; check/build/whitespace clean. |
|
||
| Runtime harness | Local Uvicorn returned pre-expiry `200`, post-24h `404`, unauthorized delete `401`, idempotent authenticated deletes `204/204`, and post-delete `404`; process/temp cleanup verified. |
|
||
| Rollback boundary | Revert only Unit 14b deltas in OCR API/client/dispatcher/tests and this progress/history metadata; durable RAG artifacts, active corpus, and Unit 14a remain intact. |
|
||
|
||
## Unit 14c: Logical Upload Source Reference
|
||
- Added an optional trimmed multipart `sourceRef` that remains independent from the temporary physical upload path, preserves original-filename fallback, rejects blank values, and documents the contract without activating content.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Test Files | Safety Net | RED | GREEN / TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|
|
||
| Unit 14c | `tests/ocr/{upload-source-ref,contracts-deploy}.test.ts` | Existing upload/contracts 13/13 | 2/4; missing contract and blank reference accepted | 4/4; provided/trimmed reuse, physical-name independence, fallback, blank rejection | Shared one parsed value across file/ZIP branches; focused suite remained green |
|
||
|
||
### Work Unit Evidence
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused/regression | Focused 4/4; multipart runtime 1/1; canonical Node 81/81; check/build/whitespace clean. |
|
||
| Runtime harness | Ephemeral localhost Express multipart uploads reused `Errores Junio 2026 - OCR verificado.md` across two physical PDF names, preserved `fallback.pdf` when omitted, rejected blank input `400`, kept activation false, and removed temporary files. |
|
||
| Rollback boundary | Revert only Unit 14c deltas in `src/{app,api/openapi}.ts`, its two tests, and this progress/history metadata; Units 14a–14b, active content, and corpus remain intact. |
|
||
|
||
## Unit 16: PDF Raster-Aware Routing
|
||
- Added PDF.js painted-area telemetry, `pdf-detection-v2`, a 5% raster threshold, and fingerprint binding; task 7.4 remains pending.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Safety Net | RED | GREEN / TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|
|
||
| Unit 16 | Parser/detection 10/10 | 8/11; v1, missing telemetry, missing routing | 11/11; exact 5% routes and 4.99% logo stays native | Matrix transform readability; 11/11 remained green |
|
||
|
||
### Work Unit Evidence
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused/canonical | Focused 11/11; canonical Node 82/82; check/build/whitespace clean. |
|
||
| Runtime harness | Real 25-page PDF reported raster coverage 1 on every page, selected pages 1–25, and native text still lacked CBG04a/FAT07/DSAU08/NSAV06. |
|
||
| Rollback boundary | Revert Unit 16 parser/detection/ingest/test/contract deltas and this metadata; preserve Units 1–15, candidates, and active content. |
|
||
|
||
## Unit 17a: Durable OCR Result Handoff
|
||
- Persisted each fully validated OCR result as canonical private content before PostgreSQL job completion, review transition, or best-effort remote deletion. The envelope binds version, document, remote job, source hash, engine, pages, full page text, metrics, and line text/confidence/boxes.
|
||
- Added immediate and restart-safe exact readback with schema, content hash, expected identity, permission, and optional artifact-byte hash validation. Immutable retries accept only byte-identical content; corruption and identity mismatches fail closed.
|
||
- Task 7.4 remains pending; no candidate composition, review endpoint restoration, activation, production wiring execution, migration, deployment, commit, or push occurred.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Test Files | Safety Net | RED | GREEN / TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|---|
|
||
| Unit 17a | `tests/ocr/{client,dispatcher}.test.ts` | Existing focused suites 17/17 | 8/11: missing artifact exports and dispatcher skipped the durable-write boundary | Focused 19/19; success ordering, simulated remote deletion/restart, repeat write, corruption, identity mismatch, and write failure | Centralized artifact integrity errors; focused 19/19 remained green |
|
||
|
||
### Work Unit Evidence
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused/regression | Focused client/dispatcher 19/19; client/dispatcher/E2E regression 21/21; canonical Node 84/84; check/build/whitespace clean. |
|
||
| Runtime harness | Real temporary private artifacts survived simulated remote deletion and exact readback after a restart boundary; write failure occurred before job completion/review/deletion and left the active version untouched. |
|
||
| Rollback boundary | Revert Unit 17a deltas in `src/app.ts`, `src/modules/ocr/{artifacts,client,dispatcher}.ts`, `tests/ocr/{client,dispatcher,e2e}.test.ts`, and this progress/history metadata; preserve Units 1–16 and task 7.4. |
|
||
|
||
### Boundary and Accounting
|
||
- Forecast: 250 changed lines against the authorized 360-line cap. Actual: 227 additions plus deletions across source, tests, progress, and history.
|
||
- Unit 17a does not restore the production review endpoint and cannot by itself satisfy the mandatory failed-evidence remediation binding.
|
||
|
||
## Unit 17b: Native Evidence and Candidate Composition
|
||
- Persisted private canonical native-page evidence beside each original and bound it to version, document, original hash, selected OCR pages, page text hashes, and raster metrics.
|
||
- Added OCR `inkCoverage`, exact restart readback of native/OCR evidence, quality classification, existing-domain candidate composition, immutable candidate publication/readback, and lifecycle hash/metrics agreement before `review_required`.
|
||
- Added reconciler recovery for the crash window after all OCR jobs succeed but before candidate finalization. Any missing, corrupt, mismatched, blocked, conflicting, or failed evidence leaves the version fail-closed and the remote OCR result undeleted.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Safety Net | RED | GREEN / TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|
|
||
| Unit 17b | Focused OCR/catalog suites 25/25 | Node failed on missing candidate exports/repository/recovery and skipped finalization; Python failed on missing `inkCoverage` | Focused 36/36 plus runtime 2/2; native/OCR/blank, corruption, missing input, quality block, immutable conflict, readback mismatch, and restart recovery | Low-resolution ink sampling and shared private JSON validation; all gates remained green |
|
||
|
||
### Work Unit Evidence
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused/regression | Focused 36/36; review/E2E/retention 15/15; canonical Node 89/89; offline Python 16/16. |
|
||
| Runtime harness | Bounded filesystem/restart scenarios passed 2/2: mixed native/OCR/blank evidence survived exact readback, and a completed version resumed candidate finalization after restart. |
|
||
| Gates and cleanup | `npm run check`, `npm run build`, tracked/untracked whitespace passed; temporary artifact directories absent; final process check found no test, Uvicorn, or pytest process. |
|
||
| Rollback boundary | Revert only Unit 17b deltas in OCR render/client/artifacts/orchestration/repository/reconciler/ingest code, focused tests, and this metadata; preserve Unit 17a durable results and Units 1–16. |
|
||
|
||
### Boundary and Accounting
|
||
- Forecast: 370 changed lines against the authorized 400-line cap. Actual: 400 additions plus deletions against native begin tree `787b4b55c4d86e401a8b1f195173e3ed0e0e4d94`.
|
||
- Task 7.4 remains pending; no candidate or active version, production service, secret, deployment, commit, or push was touched. Production review construction remains unwired, so Unit 17b does not claim the broader production review `503` is fixed.
|
||
|
||
## Unit 17c: Review Artifacts and Production Candidate Loading
|
||
- Added authenticated OCR-service page-image transfer and immutable private `0600` review-image artifacts with version/document/page/hash metadata and containment-safe resolution.
|
||
- Added a concrete PostgreSQL lifecycle-context read path and restart-safe durable candidate reader that validates state, identity, page hashes, metrics, risks, image permissions, and image hashes before returning review data.
|
||
- Wired and documented production review GET and protected image GET routes while intentionally leaving production approval/rejection/indexing unavailable. Task 7.4 remains pending.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Safety Net | RED | GREEN / TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|
|
||
| Unit 17c | Node OCR/catalog 37/37; Python 16/16 | Node failed on missing image client/artifact/reader exports and absent OpenAPI image path; Python image route returned 404 | Focused 40/40; relevant 48/48; contract 3/3; authenticated image transfer, restart loading, lifecycle mismatch, corruption, traversal-shaped identity, disabled/unauthorized and decision isolation | Consolidated identifier imports and native-document validation; focused suites remained green |
|
||
|
||
### Work Unit Evidence
|
||
| Evidence | Result |
|
||
|---|---|
|
||
| Focused/regression | Focused 40/40; relevant OCR/catalog 48/48; image contract 3/3; canonical Node 92/92; offline Python 16/16. |
|
||
| Runtime harness | Bounded localhost Express dispatch persisted result and all review images before completion, composed the candidate, entered review, restarted the reader, served authenticated review/image bytes, and kept approval/rejection at `503`; FastAPI integration returned authenticated PNG identity/hash headers and rejected unauthorized/invalid pages. |
|
||
| Gates and cleanup | `npm run check`, `npm run build`, canonical tests, offline Python, tracked/untracked whitespace, temporary-artifact cleanup, and final process inspection passed. |
|
||
| Rollback boundary | Revert only Unit 17c deltas in OCR API/client/artifacts/review/catalog/app code, focused tests, and this metadata; preserve Units 17a–17b and task 7.4. |
|
||
|
||
### Boundary and Accounting
|
||
- Unit 17c is 377 additions plus deletions against native begin tree `b64f46c0bb4edd8f9878b3920bf4d3372a29073a`, below the 400-line cap.
|
||
- No approval, rejection, correction, embedding, Qdrant, activation, production, SSH, secret, migration, commit, or push operation occurred.
|
||
|
||
## Unit 17d: Transactional Review Decisions
|
||
- Added a concrete PostgreSQL review store that locks the candidate/source/page lifecycle rows, reloads durable evidence, and revalidates version, active version, candidate, page, line, and correction identities before any decision write.
|
||
- Approval publishes and exactly rereads canonical private immutable `reviewed-pages.json`, records all corrections and reviewed page hashes transactionally, then transitions only `review_required → indexing`. Rejection records its bounded reason transactionally and creates no reviewed artifact.
|
||
- Production approval/rejection routes now use the durable store when PostgreSQL is configured. Approval stops at `indexing`; neither decision route invokes indexing, embeddings, Qdrant, activation, or the active pointer. Task 7.4 remains pending.
|
||
|
||
### TDD Cycle Evidence
|
||
| Task | Safety Net | RED | GREEN / TRIANGULATE | REFACTOR |
|
||
|---|---|---|---|---|
|
||
| Unit 17d | Review/client 20/20 | Missing store/artifact exports failed module loading; route/contract RED failed 2/5 | Focused review/E2E/contracts 16/16; transactional runtime 2/2; approval, rejection, replay, stale state, corrupt path, auth/off gates, and zero-index boundary | Shared correction preparation and immutable publication cleanup; focused suites remained green |
|
||
|
||
### Work Unit Evidence
|
||
- Review/E2E/contracts 16/16; canonical Node 95/95; check/build passed; fake-PostgreSQL transactional runtime 2/2; rejection produced no reviewed artifact; approval stopped at `indexing` with zero indexing calls. Python unchanged.
|
||
- Rollback: revert only Unit 17d deltas in `src/modules/ocr/{artifacts,review}.ts`, `src/{app,api/openapi}.ts`, the three focused tests, and this metadata; preserve Units 17a–17c and task 7.4.
|
||
|
||
### Boundary and Accounting
|
||
- Direct delta against native begin tree `5ab7b1eb5fe07cf2025e36b35fcd132f9428eb01`: within the 400-line cap after metadata condensation. Stale/mismatched/replayed/partial/corrupt decisions fail closed; transaction rollback removes newly published reviewed artifacts on downstream failure.
|
||
- Migrations 001/002 unchanged. No indexing, embedding, Qdrant, activation, active-pointer, production, SSH, secret, re-OCR, deployment, commit, push, or task 7.4 operation occurred.
|
||
|
||
## Unit 17e: Reviewed Indexing Store
|
||
- Added `PostgresOcrIndexingStore` (`indexing.ts`): loads composed candidate + reviewed-pages artifacts with full identity/integrity validation, chunks reviewed text with the canonical `documentalChunkingPolicy`, embeds via the existing provider, writes canonical versioned Qdrant points, and verifies exact point count before `indexing → ready`.
|
||
- Added `OcrReadyIndexingService`: indexes approved reviewed candidates to `ready` only; activation/settlement methods fail closed as reserved for a separately authorized unit. No active-pointer changes.
|
||
- Strict TDD: RED from missing exports (module-not-found); GREEN focused `tests/ocr/indexing-store.test.ts` 2/2 (restart-safe indexing, canonical point IDs, count verification, fail-closed identity/embedding/count/corruption). Canonical Node 97/97, check/build/whitespace green. Python unchanged.
|
||
- Rollback: revert `src/modules/ocr/indexing.ts`, remove `tests/ocr/indexing-store.test.ts`, and this metadata; preserve Units 17a–17d and task 7.4.
|
||
|
||
### Boundary and Accounting
|
||
- Direct delta against begin tree `f351a3d4514d89c9208c72a93be3f9411b41bedd`: within the 400-line cap. No migration, Qdrant production writes, activation, active-pointer, production/SSH/secret access, deploy, commit, or push. Task 7.4 remains pending.
|
||
|
||
## Unit 17f: Production Approval→Indexing Wiring
|
||
- Production `createApp` now constructs `PostgresOcrIndexingStore` + `OcrReadyIndexingService` when PostgreSQL is configured (overridable via `options.indexingService`), and the authenticated approve route invokes indexing after a successful durable approval, returning the resulting `ready` state; without a configured indexing service it fails closed with 503 `OCR_INDEXING_UNAVAILABLE`. Rejection stays decision-only; activation remains reserved.
|
||
- Strict TDD: genuine RED 2/2 (approve returned `indexing` without indexing; missing-service returned 500), GREEN focused wiring 2/2; E2E contract updated to approve→index→ready with exactly one indexing call. Canonical Node 99/99, check/build/whitespace green. Python unchanged.
|
||
- Rollback: revert `src/app.ts` wiring, `tests/ocr/approval-indexing-wiring.test.ts`, E2E delta, and this metadata; preserve Units 17a–17e and task 7.4.
|
||
|
||
### Boundary and Accounting
|
||
- No migration, activation, active-pointer, production/SSH/secret access, deploy, commit, or push. Task 7.4 remains pending.
|