Detection run storage¶
Durable detection artifacts live inside the managed notebook project. Top-level paths: project-on-disk.md. Envelope: detection-result.md. Finding shape: detection-finding.md.
Ownership¶
Transcribe persistence owns writes; detection runners produce envelopes + findings.
Authoritative outputs are project-local under
detection/— never a global store.Archive SQLite may index findings later but is never detection authority.
Layout¶
Path |
Role |
|---|---|
|
Durable detection artifacts; optional until first write |
|
Current finding index + envelope metadata |
|
Attempt history |
|
Optional project-local custom definition snapshot |
Workspace custom detector definitions: data/config/detection/custom/<id>.json.
Creating detection/ on first write is not a layout migration.
Project identity binding¶
Every durable artifact must include canonical notebook_id / project_id from project.json, not filesystem path.
Lookup key: (notebook_id, detector_id, cache_identity)
Atomicity and locks¶
Same sequence as analysis-run-storage.md:
Under
mutation_lock(short): allocateattempt_id, persistattempt_state: running, record plannedcache_identityRelease lock; run detection unlocked
Persist terminal attempt atomically
Under
mutation_lock(short): rebuild currentcache_identity; if stale → retain attempt, setstale_at_publish, do not updatepublished.json; else if cacheable → atomically replacepublished.json
Long compute must not hold mutation_lock.
Reopen reconciliation¶
When project is opened and both the OCR job lock and the analysis lock are free: running attempts → interrupted. Reconciliation must not clear valid published.json. Skip reconcile while either long lock is held so a live OCR job or Analyse+detect run is not false-interrupted by ProjectService.load(reconcile=True).
Mid-run project loads during an active detection attempt must use reconcile=False so the in-flight attempt is not marked interrupted before the terminal write. Reopen reconciliation remains the path that cleans orphaned running attempts after process death.
Cache identity¶
cache_identity is hex SHA-256 of canonical JSON (cache_identity_version: 1) with sorted keys. Required fields:
Field |
Source |
|---|---|
|
|
|
|
|
DetectorDefinition |
|
Resolved prompt ( |
|
Threshold, window params, candidate strategy, model_mode |
|
Resolved text or vision model |
|
Sorted target page IDs + per-page input fingerprints |
|
Frozen LLM options |
|
Names detector only: live NER |
Per-page input fingerprint¶
Each page in scope contributes:
page_ideffective_text_sha256(edited text ?? active OCR)active_render_idrendered_image_sha256page_order_index
Invalidation triggers: OCR/edit revision, render change, detector/prompt/model/config change, page reorder.
Cross-window aggregation¶
For page_window / notebook adjacent detection:
Generate sliding windows (default size 3, overlap 1).
Collect raw window detections with continuation flags.
Deterministically merge overlapping/adjacent spans of same
finding_type.Dedupe when Jaccard page-set overlap ≥ 0.5 (keep higher confidence).
Assign new
finding_idper merged span at publish time.
Publish rules¶
Only
attempt_state == succeededwith outcome ∈{success, skipped_not_applicable, unavailable_dependency, insufficient_data}may update published.failed/cancelled/interruptedmust never replace published.Partial runs (
partial: true) may publish successful findings when outcome issuccess.
Stale results policy¶
Results from old detector/prompt versions remain in attempt history with full provenance. Published pointer updates only on successful rerun with matching cache identity. UI marks stale via freshness comparison — never silent upgrade.