Analysis run storage¶
Durable analysis artifacts live inside the managed notebook project. Top-level paths are owned solely by project-on-disk.md. Result envelope semantics: analysis-result.md. Input fingerprint: analysis-document.md. Eligibility contribution: notebook-eligibility.md.
Ownership¶
Transcribe persistence owns writes; analysis cores are pure over canonical I/O
Authoritative outputs are project-local under
analysis/— never a global per-module analysis treeWorkspace
archive.sqlite(and any discovery index) may point at projects but is never analysis authority
Layout (reference only)¶
Per project-on-disk.md:
Path |
Role |
|---|---|
|
Durable analysis artifacts; optional until first write |
|
Disposable acceleration only |
Exact filenames, staging directories, and payload file formats under analysis/ are implementation-defined but must obey the publish, identity, and atomicity rules below. Illustrative pattern:
analysis/
runs/<run_id>.json # batch AnalysisRunPlan + progress (optional)
<module_id>/
published.json # current published reusable artifact (if any)
attempts/<attempt_id>.json
Creating analysis/ on first write is not a project-layout migration; projects without it remain valid. The runs/ directory is reserved and is never treated as a module id.
Project identity binding¶
Every durable artifact and every cache lookup must include the canonical project_id from project.json, not merely the filesystem path.
Lookup key: (project_id, module_id, cache_identity)
Moved projects remain valid when
project_idis unchangedCross-project reuse is impossible: path coincidence must not produce a cache hit
Atomicity and locks¶
Do not hold mutation_lock while running analysis cores. Long compute must not block unrelated project writes.
Required sequence:
Under
mutation_lock(short): allocateattempt_id, persistattempt_state: runningattempt artifact, record plannedcache_identity/ content fingerprintRelease lock; run the module unlocked
Persist the terminal attempt artifact atomically (immutable after leaving
running)Under
mutation_lock(short): rebuild current document fingerprint /cache_identity; if stale vs the attempt’s planned identity → retain attempt, do not update published; else if outcome is cacheable → atomically replacepublished.jsonRelease lock
Attempt file writes and published-pointer swaps use write_json_atomic. Partial writes must not become published.
Layout¶
analysis/
runs/<run_id>.json
<module_id>/
published.json
attempts/<attempt_id>.json
Creating analysis/ on first write is not a layout migration. Skip the reserved runs/ name when scanning module directories.
Analysis batch lock¶
At most one analysis batch run per project across processes, held via .transcribe.analysis.lock (see project-on-disk.md). Long module compute holds this lock for the run lifetime and must not hold mutation_lock.
Frozen AnalysisRunPlan¶
Batch Analyse launches freeze an immutable AnalysisRunPlan before any module runs: ordered module ids, optional detector ids, optional question text, EffectiveConfig snapshot + config fingerprint, text-model identity when LLM modules or detectors are included, and preset identity (preset_key, preset_content_version, preset_policy_fingerprint). Workers consume the plan (bound config + frozen model identity), not live UI/settings. Mid-run settings / text-model / module-list changes apply to the next run only. Notebook content edits mid-run still use publish revalidation (stale_at_publish) — text is not frozen as execution authority.
plan_hash (preflight bind)¶
plan_hash is the hex SHA-256 of a canonical JSON object over execution-significant fields. Exclude ephemeral run_id, created_at, and plan_hash itself. Required body fields:
Field |
Notes |
|---|---|
|
from |
|
ordered freeze list |
|
ordered detectors (may be empty); run after modules via DetectionService |
|
or null |
|
full EffectiveConfig snapshot |
|
frozen model object or null |
|
plan config fingerprint |
|
|
|
integer content generation (Custom may use |
|
SHA-256 of policy body (or Custom module/detector-list fingerprint) |
Preflight bind rule: the UI freezes the plan at launch confirm and stashes {plan, plan_hash}. Start must deserialize that plan and refuse when recomputed plan_hash ≠ stored hash. Start must not re-snapshot live settings/config. Coordinator start also refuses a tampered or empty plan_hash.
Durable run records (format: transcribe.analysis-run) live under analysis/runs/<run_id>.json. They are history/progress only and never replace module publish authority. Run records include plan_hash and preset identity fields alongside the embedded plan.
Reopen reconciliation¶
When the project is opened/loaded and the analysis lock is free: every attempt still running becomes interrupted, and every non-terminal run record becomes interrupted. Reconciliation must not clear or rewrite a valid published.json. Do not gate analysis reconcile on the OCR job lock. Process death does not auto-resume a batch; the user re-launches and published cache hits skip completed modules.
Cache-hit validation¶
Matching cache_identity is necessary but not sufficient. Refuse reuse when the published envelope fails schema validation, module_version disagrees with the requested module pin, the artifact is unparsable/corrupt, or required fields are missing.
Attempt history vs published result¶
Store |
Role |
|---|---|
Attempt history |
Retained execution records ( |
Published result |
Single reusable artifact per |
Publish rules (normative detail in analysis-result.md):
Only
attempt_state == succeededwith outcome ∈{success, skipped_not_applicable, unavailable_dependency, insufficient_data}may update the published resultrunning/failed/cancelled/interruptedmust never replace the last published reusable artifactReconciliation (
running→interruptedwhen lock free) updates history only
A cache hit that skips rerun requires: published artifact present and schema-valid, project_id match, cache_identity match, module_version match, and outcome in the cacheable terminal set above.
Forbidden: cache keys of only (project_id, module_id), filesystem path, or raw text hash without the full cache_identity object.
Cache identity¶
cache_identity is the hex SHA-256 of a canonical JSON object (cache_identity_version: 1) with sorted keys and compact UTF-8 JSON (same serialization discipline as the analysis-document content fingerprint). Required fields:
Field |
Source |
|---|---|
|
|
|
|
|
module |
|
analysis-document.md algorithm |
|
|
|
Transcribe module version / code fingerprint |
|
adapter |
|
document |
|
document |
|
relevant module configuration (canonical subset) |
|
e.g. |
|
when used; else null |
|
hash of eligibility output when policy used; else null |
|
sorted array of |
|
package/lexicon/model identities affecting reproducibility |
|
when applicable; else null |
|
when applicable (below); else null |
Identity therefore varies with text/order/included pages and adapter granularity/splitting, module configuration, dependency outputs, algorithm/schema version, lexicon/model version or digest, and relevant prompt/runtime parameters — not content alone.
LLM llm object (when module uses a generative model)¶
Field |
Required |
|---|---|
|
yes |
|
yes (canonical subset: temperature, max tokens, etc.) |
|
yes |
|
yes |
|
yes ( |
|
yes ( |
|
yes for |
|
yes when model resolved; else null (preflight → unavailable_model) |
|
yes (excerpts actually supplied / reduction fingerprint — not whole-document dump) |
Frozen LLM policy ids (must match analysis-result.md):
Field |
Allowed LLM policy values |
|---|---|
|
|
|
|
|
|
|
|
Dependency compatibility (sole normative hard DAG)¶
PRODUCT docs may list human-readable relationships including soft enrichments. This contract owns hard-parent compatibility. Consumers must not silently reuse stale or differently configured parents.
Required (hard) parents (core set)¶
Consumer |
Required parents |
Acceptable parent outcomes |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
For each required parent, the consumer must verify:
Published parent exists for the same
project_idParent
outcomeis in the acceptable setParent
cache_identityequals the identity recorded in the consumer’s plannedparentslist (content/config/eligibility alignment)
On failure → commit reusable terminal with outcome: unavailable_dependency when attempt completes cleanly; never run the consumer core against mismatched parents.
Optional enrichments (non-blocking)¶
These parents enrich payloads when present and compatible; absence must not fail the consumer. When consumed, their {module_id, cache_identity, outcome} enter parents and thus cache identity. When absent, the consumer runs its documented baseline path (never an ad-hoc stub).
Optional-parent resolution must precede cache_identity construction and cache lookup. Only parents actually consumed enter parents. Baseline wordclouds mode locks wordclouds to enrichment_mode: "baseline": keyphrases is never consumed even when a compatible published success exists (absent / incompatible / failed / non-success / success → all ignored). Enrichment requires a deliberate later mode/module_version transition so enabling enrichment cannot silently change baseline identity or outputs.
Consumer |
Optional parent |
Baseline when absent |
|---|---|---|
|
|
Token/frequency cloud from document text only ( |
|
|
Model without keyphrase prior/seed enrichment |
|
|
BERTopic without keyphrase enrichment |
|
neighbouring-unit window only |
N/A (no parent module; window is config) |
|
|
Notebook salience features still computed; missing soft signals → lower feature set + warning, not hard fail |
|
— |
Hard parents only; no TX |
|
deterministic |
|
Fallbacks (core set)¶
Situation |
Behaviour |
|---|---|
TX |
Not ported. Sole substitute: |
TX |
Not ported. |
BERTopic extra missing |
|
Transformer emotion missing |
lexicon/ |
Ollama missing |
LLM modules → |
Modules not listed under Required (hard) parents have no hard parent dependencies under this contract version.
Non-goals¶
Redefining top-level project layout (owned by project-on-disk)
Global authoritative analysis storage
Cache hits keyed only by filesystem path