Skip to content

feat(parser): PDF OCR fallback + font-encoding recovery (#246) - #290

Merged
thewrz merged 4 commits into
mainfrom
feat/issue-246
Jun 26, 2026
Merged

feat(parser): PDF OCR fallback + font-encoding recovery (#246)#290
thewrz merged 4 commits into
mainfrom
feat/issue-246

Conversation

@thewrz

@thewrz thewrz commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Why

Completes PDF spec ingest (#65, slice 3/3): scanned/image PDFs and font-corrupted text layers. After #245
(text-layer PDFs), a scanned PDF dropped into the demo's dropzone only emitted a "needs OCR" deferral — this makes
it actually OCR into the same AST + coordination panels, and repairs mojibake-corrupted text layers.

What

  • OCR fallback (src/parser/pdf/ocr.ts): when feat(parser): PDF text-layer adapter — extract + normalize → text inference (slice 2/3 of #65) #245's detectPdfOcrNeed flags a scanned/mixed PDF,
    rasterize the affected pages (unpdf renderPageAsImage + @napi-rs/canvas) → tesseract.js → splice OCR text
    back per page → existing feat(parser): plaintext spec ingest — hierarchy inference from indent + numbering patterns #64 hierarchy inference. mixed keeps good text-layer pages and OCRs only the empty
    ones. The recognizer + renderer are dependency-injected.
  • Font-encoding recovery (src/parser/pdf/font-encoding.ts): detects mojibake (char-frequency + chardet
    fingerprint), remaps via iconv-lite. Decodes items in coherent adjacent groups and rejects any remap that
    introduces a new
    , so valid non-ASCII (70°F, ±) is never destroyed and sequences split across PDF items
    aren't corrupted. Genuinely unrecoverable corruption is flagged, never silently passed.
  • Warnings: pdf-ocr-applied, pdf-ocr-low-confidence, pdf-ocr-unusable, pdf-font-encoding-remapped,
    pdf-font-encoding-unrecoverable.
  • Graceful degradation: primary → pdfjs fallback → OCR / encoding remap → partial result with warnings[]
    typed hard error. Never a silent empty/garbled parse.
  • Deps per ADR-034 §3: tesseract.js 7 (Apache-2.0, WASM, offline) + @napi-rs/canvas (MIT, prebuilt). pnpm audit clean — 0 new advisories vs main. Model loaded from a gitignored cache (NOT vendored in git);
    tesseract's build script left unapproved.

Testing

  • Build (tsc) clean
  • Unit 1131 (OCR + font-encoding exercised via injected fakes — no model/network needed in CI)
  • Integration 693 (orchestrator-run, isolated Postgres)
  • Real-OCR end-to-end (orchestrator-run, SPECR_OCR_E2E=1): an image-only PDF → genuine tesseract model
    download + recognize → recovers PART 1 - GENERAL + pdf-ocr-applied. CI skips it (no model); skipIf-gated.
  • Lint (eslint + tsc + prettier) + redocly lint openapi.yaml valid (9 pre-existing warnings)
  • Manual: drop a scanned PDF into the demo dropzone → OCR'd into the tree (vs. the prior "needs OCR" deferral)
  • CI green (runs when moved out of draft)

Review notes

  • Implemented autonomously by Codex (gpt-5.5 xhigh) in an isolated worktree (TDD), under Claude Opus 4.8
    orchestration: dependency vetting + install, orchestrator-run integration + the real-OCR e2e, and an adversarial
    Codex review gate that caught 2 P2 font-encoding corruption bugs (valid non-ASCII destroyed; split-item
    mojibake) — both fixed here.
  • The eng.traineddata model downloads on first OCR to a gitignored cache (offline after first fetch). For
    air-gapped deploys, pre-seed the cache or set OCR_LANG_PATH.

🤖 Co-authored by Codex (gpt-5.5 xhigh — implementation) under Claude Opus 4.8 (orchestration + review + deps).
Closes #246.

Summary by CodeRabbit

  • New Features

    • Added improved PDF text extraction with OCR support and font-encoding recovery for harder-to-read documents.
    • Added new OCR-related configuration options to better control confidence thresholds, language files, cache location, and render scale.
  • Bug Fixes

    • Replaced the generic “needs OCR” warning with more specific PDF warning types, including applied OCR, low confidence, unusable OCR output, and font-encoding issues.
    • Updated PDF parsing behavior to better preserve text quality and report more actionable warnings.

thewrz and others added 3 commits June 25, 2026 10:35
…246)

OCR engine (tesseract.js 7.0.0, Apache-2.0, WASM, offline-capable) plus the
rasterizer (@napi-rs/canvas 1.0.1, MIT, prebuilt binary) that unpdf's
renderPageAsImage needs — per ADR-034 §3, isolated to the scanned-PDF branch.
pnpm audit clean: identical 5-advisory baseline as main, zero new advisories.
tesseract's opencollective postinstall left unapproved (funding nag, not
needed). The eng.traineddata model is NOT vendored — loaded from a gitignored
cache; tests inject a fake OCR engine so CI needs no model/network.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@thewrz, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 11 minutes and 31 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e64b9d2f-f878-4e75-a548-f5eca807a665

📥 Commits

Reviewing files that changed from the base of the PR and between adb9ec6 and 4dd3b9e.

📒 Files selected for processing (6)
  • .env.example
  • src/lib/env.ts
  • src/parser/pdf/font-encoding.test.ts
  • src/parser/pdf/font-encoding.ts
  • src/parser/pdf/index.ts
  • src/parser/pdf/ocr.ts
📝 Walkthrough

Walkthrough

The PR adds PDF OCR fallback and font-encoding recovery, expands ParseWarning codes and suggestions, wires new OCR dependencies and config, and updates parser and API tests for the new warning flow.

Changes

PDF OCR fallback and font-encoding recovery

Layer / File(s) Summary
Warning contract updates
openapi.yaml, src/ast/schemas.ts, src/ast/types.ts, src/parser/text/index.ts
The ParseWarning type surfaces and warning suggestion map now include the new PDF OCR and font-encoding codes.
OCR runtime config
package.json, pnpm-workspace.yaml, src/lib/env.ts, src/lib/parse-worker.ts
The runtime adds OCR dependencies, blocks the Tesseract build script, extends OCR environment config, and forwards those settings into the worker parse call.
Parser dispatch and option mapping
src/parser/index.ts
Top-level parse dispatch splits format handling into helpers and maps the OCR-related parse options into PDF parse options.
PDF OCR module
src/parser/pdf/ocr.ts, src/parser/pdf/ocr.integration.test.ts
A PDF OCR module renders selected pages to images, runs Tesseract with configurable worker and render options, and is covered by an image-only PDF integration test.
Font-encoding recovery
src/parser/pdf/font-encoding.ts, src/parser/pdf/font-encoding.test.ts
PDF text items are scored for garbled encodings, remapped when legacy decoding improves the text, and covered by recovery tests.
PDF parse flow and warnings
src/parser/pdf/index.ts, src/parser/pdf/index.test.ts, src/api/parse.integration.test.ts
parsePdf now runs font-encoding recovery before OCR, applies OCR to selected pages, emits applied/low-confidence/unusable OCR warnings, merges them with extraction warnings, and the unit/API tests assert the new warning types and control flow.

Sequence Diagram(s)

sequenceDiagram
  participant parse
  participant parsePdf
  participant recoverPdfFontEncoding
  participant applyOcrIfNeeded
  participant recognizePdfPages
  parse->>parsePdf: parse(buffer, options)
  parsePdf->>recoverPdfFontEncoding: recoverPdfFontEncoding(pages)
  parsePdf->>applyOcrIfNeeded: applyOcrIfNeeded(need, pages, ocr)
  applyOcrIfNeeded->>recognizePdfPages: recognizePdfPages(buffer, pageNumbers, ocr)
  recognizePdfPages-->>applyOcrIfNeeded: OCR text and confidence
  applyOcrIfNeeded-->>parsePdf: updated pages and OCR warnings
  parsePdf-->>parse: ParseResult with merged warnings
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

  • wrzonance/SpecR issue 65: The PR adds the OCR fallback and font-encoding recovery path described in the PDF ingest slice.

Possibly related PRs

  • wrzonance/SpecR#113: Both PRs change the shared ParseWarning enum/type definitions in src/ast/schemas.ts and src/ast/types.ts.
  • wrzonance/SpecR#287: This PR extends the same PDF warning and parsing flow introduced there with OCR fallback and font-encoding recovery.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: PDF OCR fallback plus font-encoding recovery.
Linked Issues check ✅ Passed The changes implement OCR fallback, font-encoding remapping, warnings, config, and tests, matching the linked issue scope.
Out of Scope Changes check ✅ Passed The added dependencies, schema updates, config, and tests all support the OCR and font-recovery feature set; no unrelated changes stand out.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-246

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thewrz
thewrz marked this pull request as ready for review June 26, 2026 03:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/parse.integration.test.ts`:
- Around line 230-243: The regression test for blank PDF handling is currently
gated behind the OCR E2E flag, so the default suite no longer covers the crash
symptom. Move the `completes no-text PDF with an OCR warning instead of
crashing` case in `parse.integration.test.ts` back into the always-on
API-boundary suite, and make it deterministic by stubbing OCR or injecting a
fake recognizer instead of relying on `OCR_E2E_ENABLED`. Keep the real OCR
end-to-end coverage opt-in, but ensure this test remains unskipped in normal CI
and continues asserting the warning-based completion path through `postPdf` and
`blankPdf`.

In `@src/lib/env.ts`:
- Around line 9-12: The OCR_RENDER_SCALE schema in env validation is too
permissive because positive() allows unbounded values, which can later blow up
rasterization. Update the Zod definition in env validation to enforce a sane
maximum for OCR_RENDER_SCALE, keeping the existing default and failing fast on
invalid config so the process exits during boot. Use the OCR_RENDER_SCALE field
in src/lib/env.ts as the place to tighten the bound.

In `@src/parser/pdf/font-encoding.ts`:
- Around line 192-214: The aggregated pdf-font-encoding-remapped warning in
recoverPdfFontEncoding is using a single remapDecision for all recovered pages,
so the hint can become inaccurate when pages recover differently. Update
recoverPdfFontEncoding to keep the decision associated with each remapped page
(or avoid embedding a per-page decision in the aggregated warning), and make
sure the recoveryLineHint call reflects the correct page-specific decision from
remapPage/decideRecovery rather than the last one seen.

In `@src/parser/pdf/index.ts`:
- Around line 152-154: The OCR warning path is exposing raw exception text
through `errorMessage` and `lineHint`, which can leak internal filesystem/config
details to callers. Update the `errorMessage` helper and the related
parse-warning flow so callers only receive a generic `pdf-ocr-unusable` hint,
and make sure the full error is logged server-side via the pino logger from
`src/lib/logger.ts` rather than using any console output. Keep the public
surface in the `src/parser/pdf/index.ts` flow from returning `err.message`
directly, including the later OCR-init handling code referenced by the same
helper.

In `@src/parser/pdf/ocr.ts`:
- Around line 81-143: The OCR module is leaking raw filesystem/render/Tesseract
errors instead of a module-owned typed error. Add a `SpecrError` subclass for
this parser OCR module and catch failures in `createManagedRecognizer`,
`withRecognizer`, and `recognizePage` so you can rethrow with added stage/page
context using `cause`. Keep `recognizePdfPages` as the public surface that only
emits the typed OCR error, preserving the original error details while enriching
the message with the relevant page or OCR step.
- Around line 57-59: The pdfData helper in ocr.ts is unnecessarily cloning the
PDF buffer before OCR, which adds memory overhead on this hot path. Update
pdfData to return the incoming Buffer directly since it already satisfies
Uint8Array, and keep the change localized to the pdfData function used by the
OCR flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5e5f4103-123e-40ef-b9e3-4cc9658a9545

📥 Commits

Reviewing files that changed from the base of the PR and between 67bf1e6 and adb9ec6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • openapi.yaml
  • package.json
  • pnpm-workspace.yaml
  • src/api/parse.integration.test.ts
  • src/ast/schemas.ts
  • src/ast/types.ts
  • src/lib/env.ts
  • src/lib/parse-worker.ts
  • src/parser/index.ts
  • src/parser/pdf/font-encoding.test.ts
  • src/parser/pdf/font-encoding.ts
  • src/parser/pdf/index.test.ts
  • src/parser/pdf/index.ts
  • src/parser/pdf/ocr.integration.test.ts
  • src/parser/pdf/ocr.ts
  • src/parser/text/index.ts

Comment thread src/api/parse.integration.test.ts
Comment thread src/lib/env.ts Outdated
Comment thread src/parser/pdf/font-encoding.ts
Comment thread src/parser/pdf/index.ts Outdated
Comment thread src/parser/pdf/ocr.ts
Comment thread src/parser/pdf/ocr.ts
Address CodeRabbit findings on the PDF OCR fallback + font-encoding PR:

- pdf/index: stop leaking raw OCR exception text (Tesseract cache/lang
  filesystem paths) into the pdf-ocr-unusable warning returned to API
  callers; surface a generic hint instead (stack traces never leave the
  process). The parser stays env/logger-free by design, so the cause is
  preserved via the typed ParserError below rather than the pino logger.
- pdf/ocr: wrap OCR worker init and per-page recognition failures in the
  module's typed ParserError with cause, per the module-boundary error
  convention; return the PDF Buffer directly instead of cloning the whole
  file on the OCR hot path (Buffer already is a Uint8Array).
- font-encoding: report the distinct set of source encodings/fingerprints
  across remapped pages instead of attributing every page to the last
  decision; drop the mutable single-decision accumulator. Pin with a
  multi-page regression test.
- lib/env: cap OCR_RENDER_SCALE at 10 so a bad env value cannot explode
  rasterization size and OOM the worker.
- .env.example: document the OCR knobs, including OCR_LANG_PATH for
  offline/network-restricted deployments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thewrz

thewrz commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Codex (GPT-5.5, xhigh) adversarial review — additional eyes

Ran alongside CodeRabbit as a second reviewer. One confirmed finding:

[P1] OCR worker init can hang parse jobs offline when traineddata is uncached — src/parser/pdf/ocr.ts

Confirmed and reproduced. With OCR_LANG_PATH unset and an empty cache, tesseract.js fetches eng.traineddata from a CDN during createWorker. In a network-restricted env, Codex's run of ocr.integration.test.ts surfaced TypeError: fetch failed from tesseract.js/src/createWorker.js. In the project's offline target, a network stall (vs. fast failure) could leave the first scanned-PDF parse job hung instead of completing with pdf-ocr-unusable.

Decision — partially mitigated here, remainder tracked as a follow-up (#298):

  • The fail-fast path is now handled by this PR's review fixes: createManagedRecognizer wraps init failures in a typed ParserError, which applyOcrIfNeeded catches → clean pdf-ocr-unusable degradation (no hang, no leaked paths).
  • The indefinite-stall path (bounded init timeout) and offline provisioning (vendor/require local eng.traineddata, never depend on a CDN in production) are a deliberate packaging/config decision — binary asset, license, deployment docs — that warrants its own issue + ADR rather than being rushed into this feature PR. Filed as fix(parser): OCR worker init can hang parse jobs offline when traineddata is uncached (#246 follow-up) #298.
  • .env.example now documents OCR_LANG_PATH as the operator-facing interim mitigation for offline deployments.

This isn't a regression: before this PR scanned PDFs weren't OCR'd at all, and the real-OCR path is gated behind SPECR_OCR_E2E (skipped in normal CI). The feature works when traineddata is available; #298 hardens the offline edge.

No other P1/P2 findings from Codex. CodeRabbit's 6 inline findings are all addressed in 4dd3b9e (5 fixed, 1 declined with rationale).

@thewrz
thewrz merged commit e975781 into main Jun 26, 2026
29 checks passed
@thewrz
thewrz deleted the feat/issue-246 branch June 26, 2026 05:07
thewrz added a commit that referenced this pull request Jun 27, 2026
Invariant 2 (a rejecting worker init must keep degrading to a
pdf-ocr-unusable warning, never throw) was only pinned at the
recognizePdfPages surface (-> ParserError). Add the parsePdf-boundary
assertion mirroring the offline `TypeError: fetch failed` case, so all three
#298 invariants are pinned at the parser boundary, not internals.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thewrz added a commit that referenced this pull request Jun 28, 2026
…ever hang (#298) (#311)

* fix(parser): bound OCR worker init so offline scanned PDFs degrade, never hang

When eng.traineddata is uncached AND OCR_LANG_PATH is unset, tesseract.js
fetches the model from a CDN on first use. #290 already handles the fetch
*rejection* (fail-fast -> pdf-ocr-unusable). The unhandled case was a
connection accepted but never answered: createWorker never settles and the
whole parse job hangs.

Race worker init against a configurable timer (OCR_INIT_TIMEOUT_MS, default
30s). On timeout, reject with a typed ParserError -- which the existing
applyOcrIfNeeded catch degrades to a pdf-ocr-unusable warning -- and
terminate any late-resolving worker so no Tesseract process leaks. Wrapping
is centralized at the init boundary, preserving the #290 fail-fast. A
createWorker DI seam makes the stall unit-testable with no network or real
traineddata. ADR-039 records the offline-OCR provisioning strategy: vendored
traineddata via OCR_LANG_PATH, never a production CDN dependency.

Closes #298

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(parser): pin #290 fail-fast degradation at the parsePdf boundary

Invariant 2 (a rejecting worker init must keep degrading to a
pdf-ocr-unusable warning, never throw) was only pinned at the
recognizePdfPages surface (-> ParserError). Add the parsePdf-boundary
assertion mirroring the offline `TypeError: fetch failed` case, so all three
#298 invariants are pinned at the parser boundary, not internals.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(parser): pre-flight local traineddata in strict offline mode to stop OCR worker leak

PR #311's bounded init timeout stops the offline hang but not a worker LEAK
that a Codex (GPT-5.5) adversarial review surfaced: tesseract.js v7 spawns the
worker thread synchronously BEFORE fetching eng.traineddata, so in the
black-hole stall (connection accepted, never answered) createWorker never
settles, terminateLater() never fires, and the already-spawned worker leaks.
The timeout can't terminate a worker it has no handle to. Timeout and
pre-flight are therefore complementary, not substitutes.

Add an opt-in strict mode (OCR_REQUIRE_LOCAL_TRAINEDDATA, default false) that
pre-flights local traineddata BEFORE spawning: if eng.traineddata is absent
(probing langPath + cache for .traineddata and .traineddata.gz, matching
tesseract.js v7's resolution), refuse via ParserError -> pdf-ocr-unusable
WITHOUT invoking the worker factory. Default false preserves the convenient
networked-dev behavior (CDN fetch on first run, bounded by the timeout);
production sets it true and provisions local data. New DI seams
(requireLocalTraineddata, hasLocalTraineddata) keep it fully offline-testable.

Plumbed config -> ParseOptions -> PdfOcrOptions; env via z.stringbool. ADR-039
corrected: pre-flight is adopted as complementary to the timeout, with the
filename contract pinned by a test; credits the PR #311 review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thewrz added a commit that referenced this pull request Jul 1, 2026
#325)

* docs(readme): sync capabilities to last month of merged PRs

Reflect shipped work in the README's "Included Today", "API Surface", and MCP
tool table, validated against the merged diffs and current main:

- PDF ingest (text-layer + OCR + font-encoding recovery) accepted by POST /parse
  (#287, #290, #311)
- coordination / E&O report + submittal register (#241, #269, #277, #282, #283,
  #284) and article-role tagging (#273)
- onboarding pipeline: library import, editability review/override, reclassify,
  finalize/reopen, open-comments (#243, #247, #248, #249, #272)
- spec/project soft-delete + restore (#257, #313), document concurrency (#197),
  revision/addendum manual rendering (#221), numbering profiles (#317, #322)
- add missing MCP tools get_numbering_profile, submittal_register,
  open_comments_report; document GET /docs (Scalar) (#213, #285)
- add Example Client pointer to examples/web_ui_demo (#225)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(roadmap): move shipped work to done; re-date to 2026-07-01

Reconcile the roadmap with merged reality (was stamped 2026-06-17). Moved from
planned/in-progress to Included, each validated against the diff:

- PDF ingest (#287, #290, #311) — remove from "Later"
- deep paragraph nesting pr6/pr7 (#215)
- revision nomenclature (#216) + revision/addendum manual rendering (#221) —
  the two "Near Term" Phase 2e items are done
- coordination / E&O report, required-sections, article-role, submittal register
  (#239, #241, #269, #273, #277, #282, #283, #284) — new "Coordination and
  Semantics" section; removed "coordination report" from planned Phase 4
- onboarding APIs (#243, #247, #248, #249, #272) — API done; UI remains planned
- soft-delete/withdraw (#257, #313), section-number format (#266, #271),
  external-content associations (#242), structural numbering profiles (#317)

Kept as planned (foundation only): header/footer composition (#222, #314) and
keynote surfacing (#315) — DB/AST exist, no resolution/render/export yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(architecture): reflect merged structural changes

Update the architecture spec for shipped work, validated against the diffs and
current schema/routes:

- Tech Stack + Data Flow: Parse — PDF text-layer (unpdf/pdfjs-dist) + OCR
  (tesseract.js/@napi-rs/canvas) path and numberingProfileId override (#287,
  #290, #311, #317; ADR-034, ADR-039)
- DB schema — specs.onboarding_status/withdrawn_at, projects.section_number_format
  /deleted_at/deleted_by, paragraphs.source_facts/classification/
  editability_override; "Additional tables" summary for editing_conventions,
  paragraph_associations, required_sections, keynotes, header_footer_configs,
  numbering_profiles, revision_nomenclature_profiles (foundation-only tables
  flagged) (ADR-021/022/023/028/031/032; #187, #242)
- new Coordination Report / E&O section (finding vocabulary) and Document
  Concurrency section (locks/optimistic/lifecycle) (#197, #241, #269, #277,
  #282, #283, #284; ADR-018, ADR-033/035/036/037)
- AST meta.articleRole (#273, ADR-033); API-surface note pointing at the
  CI-enforced openapi.yaml + GET /docs; refreshed MCP tool list

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(parser): PDF OCR fallback — scanned/image PDFs + font-encoding recovery (slice 3/3 of #65)

1 participant