ADR-0007: Book Ingestion Pipeline — two-file contract, importer, and provenance¶
Status¶
Accepted — implements the Content Pipeline (CSL-22) referenced by
ADR-0006. Scoped to the first book
ingestion (Mere Christianity); revisit when the ADR-0006 review-state fields
and the unified Chapter kind land.
Date¶
2026-07-09
Context¶
A book is processed off-repo into passages, each with an AI-written interpretation (deterministic extraction → editorial selection → per-passage AI pass). The result must land in the Wagtail CMS as reviewable content without (a) altering Lewis's words or (b) letting AI-generated text reach the app unreviewed — the constraints ADR-0006 records.
We need a stable, committed contract between that off-repo processing and the
CMS, plus an importer, ahead of the review-state schema and the unified readable
Chapter model (both proposed in ADR-0006 but not yet built).
Decision Drivers¶
- Must: canon passages stored verbatim; nothing AI-written served unreviewed.
- Must: import is idempotent and never clobbers editorial work on re-run.
- Should: full provenance preserved (source lines, model, confidence, taxonomy suggestions) so any quote is auditable and re-runnable.
- Should: human-reviewable input; work metadata not repeated per excerpt.
- Nice-to-have: no new model surface beyond what's strictly needed now.
Considered Options¶
Contract format¶
- A. One JSONL per work (record = excerpt, work repeated per line). Machine- friendly, but repeats work metadata 264× and reads poorly for editorial QA.
- B. Two YAML files —
book.yaml+excerpts.yaml. Work described once; excerpts reference it by slug; YAML block scalars keep prose readable.
Chosen: B. JSONL remains the intermediate assembly form upstream; the committed contract is the YAML pair.
Where provenance lives¶
- A. Drop it on import (lossy — a beautiful interpretation of a misattributed quote is worse than none; loses the audit trail).
- B. Dedicated columns for each field (large surface; premature before the ADR-0006 review model settles).
- C. One
ingest_metaJSONField onPassageholding the full source record.
Chosen: C. Preserves everything for audit/editor review in one low-risk
field; internal (not in api_fields).
Withholding unreviewed AI content¶
- A. Wait for the ADR-0006 per-field review flag before importing anything.
- B. Import as Wagtail draft (
live=False) now; the API serves only live content, so drafts are withheld. Adopt the per-field flag when it lands.
Chosen: B as the interim gate; the intended review state is carried in
ingest_meta meanwhile.
Decision¶
- Contract:
content/ingestion/<work-slug>/{book.yaml, excerpts.yaml}, grouped by trust (canon/interpretation/taxonomy/locator/review). Joins on slug +chapter_id. Documented in the book-processing guide. import_bookmanagement command (interfaces/cli) validates the pair, creates theBook(published) and one draftPassagesnippet per passage, matched by a deterministic slug (<chapter_id>-NN). Idempotent: existing excerpts are skipped unless--update-existing.--dry-runvalidates only. (Snippets, not pages, since ADR-0008.)Passage.ingest_meta(JSONField, migration 0004) stores the full source record — provenance, AI-generation metadata, and taxonomy suggestions.- Canon verbatim:
canon.passage→bodywrapped in paragraphs only, no rewording. - Themes are auto-applied from
taxonomy.suggested_themesonto the draft revision (an editor confirms before publish) — but link-only against a global controlled vocabulary created once bysync_themes. The importer never creates aTheme; a suggestion outside the vocabulary is skipped and flagged. Tags stay an editorial act. Every suggestion is preserved iningest_metaregardless. - The off-repo processing working area (raw text, segmentation, per-passage AI output) is archived separately, not committed; the repo owns the stage-④ artifact and the importer.
Consequences¶
Positive¶
- Clean, auditable boundary; any imported quote traces back to exact source lines.
- Re-import is safe and predictable; editors' work is never overwritten by default.
- No dependence on the (still-proposed) review-state schema to start ingesting.
- Minimal new model surface: one JSONField.
Negative¶
interpretation.title/opening/bodycompose into one flatinterpretationRichText (the model isn't structured yet) — lossy for structure, though the originals stay iningest_meta.- Draft/live is coarser than the intended per-field gate: a whole Passage is withheld until published, so canon can't yet publish independently of an unreviewed interpretation.
- Provenance in a JSON blob isn't queryable like columns.
Risks & Mitigations¶
- Risk: re-run clobbers editorial edits. → Skip existing by default; overwrite
only behind
--update-existing. - Risk:
import_bookdiverges from the real data schema. → A test imports the committed Mere Christianity files via--dry-runas a schema-drift guard. - Risk:
ingest_metaleaks to the API. → Excluded fromapi_fields; it is internal provenance only.