Source assets¶
Activation gate¶
Same gate as notebook-corpus.md — satisfied; this contract is runtime-normative. Existing SourceDocument fields in transcribe.project v1 remain the shipped source model; optional linkage fields are additive. This contract defines the durable semantics bulk import obeys and the integrity invariants notebook validation enforces (see Migration).
Purpose¶
Every imported image/PDF is a managed immutable original plus provenance. Runtime authority after commit is the managed copy and its content hash—not the external path that happened to be scanned.
Managed originals¶
Import copies source bytes into the notebook’s managed
sources/tree.External path/filename are non-authoritative provenance only. They may move or disappear between scan and resume; idempotency must not key off them.
After commit, missing managed bytes (or fingerprint mismatch) is an integrity error, not a soft warning to re-read
/Volumes/....
Identity vs fingerprint¶
Concept |
Role |
|---|---|
|
Immutable identity of the SourceAsset record |
|
SHA-256 of the imported source bytes (JPEG/PNG/PDF as stored). Integrity + duplicate classification |
External path/filename |
Provenance display / audit only |
Field semantics (aligned to current SourceDocument)¶
Field |
Status |
Semantics |
|---|---|---|
|
Required |
UUID hex |
|
Required |
Content hash of managed source bytes |
|
Required |
e.g. |
|
Required today |
Basename/provenance label from import |
|
Required |
Managed path relative to notebook root |
|
Required |
Exact size of the represented within-source page set (see below) |
|
Required |
Import timestamp |
|
Required |
DPI used when renders were produced for this source |
|
Optional / future |
Full external path provenance; absence is valid |
|
Optional / future |
Byte length at import; absence is valid |
|
Optional / nullable |
Set when created by an ImportRun; must be null/absent for legacy sources predating bulk-import generation |
bytes_present¶
Computed integrity state, not persisted authoritative metadata. Doctor/deep validation reports whether the managed file exists and matches sha256. Do not store a stale bytes_present: true flag on the entity.
PDF and page linkage¶
Do not put
pdf_page_indexon the SourceAsset. One PDF SourceAsset owns N pages.Each page carries
source_id+ within-sourcepage_index.pdf_page_indexlives on render provenance (and may be mirrored in staging journals). Where a render was produced from a PDF page, invariant:render.pdf_page_index == page.page_indexfor that page’s active render lineage when the sourcemedia_typeisapplication/pdf.For single-image sources,
page_indexis0andpdf_page_indexis null/absent on renders.
page_count and within-source indices¶
For each source_id:
Let
Pbe the set of pages with thatsource_id.page_countmust equal|P|.The set of
page_indexvalues inPmust be exactly{0, 1, …, page_count - 1}(contiguous, starting at 0, no duplicates, no gaps).(source_id, page_index)is unique across the notebook.
Current shipped validation enforces contiguous unique indices via validate_project (see corpus-integrity.md notebook invariants).
Page ↔ render integrity invariants¶
Within a notebook (normative; doctor/validate_project must enforce):
Unique
(source_id, page_index)among pages.Active render belongs to the page’s source: the active render’s provenance must reference the same
source_idas the page (via render bookkeeping already stored: source hash linkage and path layoutpages/<source_id>/<page_index>/…). Explicitly:page.active_render_idresolves; that render’ssource_sha256equals the page’s SourceAssetsha256; render path containment uses the page’ssource_idandpage_index.render.source_sha256 == SourceAsset.sha256for every render retained for pages of that source.PDF index coherence: for PDF sources, every page’s
page_indexequals the corresponding render’spdf_page_indexwhen present;page_countmatches PDF page set represented.Dimension coherence:
page.width/page.heightequal the active render’swidth/height.No unreferenced authoritative renders/sources unless explicitly permitted as a documented soft state. Default: every
sources[]entry is referenced by ≥1 page; everyrendersmap entry is the active render of exactly one page or is retained under an explicit future multi-render policy. Until multi-render history is a product feature, unreferenced renders are doctor errors (or warnings only if a migration note marks them transitional—default error for bulk-import generation).
Visual declutter provenance (additive)¶
Import may run visual declutter on the staged page PNG before it becomes the active render (workspace ingest.visual_declutter_enabled, default on). This is not OCR preprocess.
When present on a render, declutter fields are authoritative for that render’s pixels:
Field group |
Role |
|---|---|
|
One of |
|
Frozen declutter identity (enabled flag + version + ordered ops + detection params) |
Crop geometry |
|
|
Bounded diagnostic (required non-empty for noop/error) |
Absence of these fields means a pre-declutter-era render. Changing declutter config/version alone does not rewrite existing notebooks. New imports use the current identity. Explicit re-apply (Settings → Configuration → Re-apply visual declutter, Review → Cleanup, or ProjectService.reapply_visual_declutter) re-runs declutter on active renders (optionally a page subset): new render_id when pixels change; provenance-only update when bytes are unchanged. Re-apply does not restore already-cropped margins when enabled is false (records disabled provenance on current pixels only). Refuses while an OCR job lock is held. Does not re-run OCR. Ingest journals freeze declutter_identity_sha256 with page sha/geometry; crash recovery must not promote a journal whose identity no longer matches the effective setting.
Duplicate taxonomy¶
Classify before applying policy—never silently merge:
Class |
Meaning |
|---|---|
|
SHA-256 already present as a SourceAsset in the target notebook |
|
SHA-256 present in a different notebook’s sources |
|
Provenance name collides; hashes differ |
|
Prior path/fingerprint pair no longer matches bytes (detected; not auto-applied) |
|
Plan would create a second page for an already-represented |
Duplicate policies (first bulk-import release)¶
Allowed policies (named on the ImportPlan / import_policy_id):
Policy |
Behaviour |
|---|---|
|
See precise definition below |
|
Always create new |
replace_source is out of policy for the first bulk-import release. It must not be selectable; implementations must not offer silent replace.
skip_existing (precise)¶
May skip only when the candidate source SHA-256 already exists in the target notebook specified by the plan item (
same_bytes_same_notebook).Must not silently skip because the same bytes exist in another notebook.
Corpus-wide deduplication is allowed only when the plan explicitly sets a policy/flag requesting it (separate from default
skip_existing). Default plans without that flag treatsame_bytes_other_notebookas a distinct classification: eithercreate_duplicateper policy or a validation finding—never an implicit skip.Skips are recorded on the ImportRun item outcome with the existing
source_id/ page IDs that caused the skip.
Idempotency drivers¶
Idempotency keys for commit/resume are:
Immutable planned IDs (
plan_id,item_id, preallocatednotebook_id/source_id/page_id/render_id)Source content
sha256import_policy_id+ plan fingerprint equality (import-run.md)
External paths and filenames must not drive idempotency.
Migration rules (before writing new linkage fields)¶
Before any writer persists import_run_id, original_path, or source_size_bytes into transcribe.project:
Decision (locked) |
Rule |
|---|---|
Additive optional fields on schema_version 1 |
|
No silent requiredness |
Bulk-import writers may set them; OCR/review paths must not require them. |
Schema bump reserved |
Making any of these required, renaming |
Until migration writers ship, doctors must accept legacy sources with only today’s required fields.
Non-goals¶
Content-defined merging of near-duplicate images
Deduplicating bytes into a global content-addressed blob store in v1 (notebook-local managed copies remain the unit)
Using filename lexicography as identity