Analysis project format v1
August 13, 2026 ยท View on GitHub
Status: normative alpha contract for persisted PureJsImage analysis projects. This format is opt-in and does not affect the ordinary image pipeline.
An analysis project records a reusable recipe, semantic bindings, source rebinding information, and optional presentation state. It never embeds executable providers, open handles, tiles, or large quantitative result payloads.
Document shape
interface AnalysisProjectV1 {
readonly schemaVersion: 1
readonly graph: AnalysisGraph
readonly roiSet: RoiSet
readonly bindings: readonly PersistedInputBinding[]
readonly sourceReferences: readonly PersistedSourceReference[]
readonly providerPolicy?: PersistedProviderPolicy
readonly display?: ApplicationDisplayState
readonly createdWith: {
readonly packageVersion: string
readonly buildFingerprint: string
}
readonly hashes: {
readonly graph: string
readonly bindings: string
readonly invocation: string
}
}
interface PersistedInputBinding {
readonly input: string
readonly valueType: { readonly id: string; readonly version: number }
readonly identity: AnalysisSemanticIdentity
readonly value:
| { readonly kind: 'source'; readonly sourceReference: string }
| { readonly kind: 'roi'; readonly roiId: string }
| { readonly kind: 'roi-set'; readonly roiIds?: readonly string[] }
| { readonly kind: 'inline-json'; readonly value: OperationJsonValue }
}
interface PersistedSourceReference {
readonly id: string
readonly identity: AnalysisSemanticIdentity
readonly locatorHint?:
| { readonly kind: 'local-file'; readonly name: string; readonly size: number }
| { readonly kind: 'remote'; readonly url: string }
}
The discriminated value resolves a source, one project ROI, all or a selected subset of project
ROIs, or bounded inline JSON without duplicating ROI geometry. A source reference ID is unique and
must resolve to exactly one entry. Its identity is a plain SourceIdentity for a single source or a
ScientificDatasetIdentity for a reader-opened dataset; the latter carries every resource in a
multi-resource format such as ENVI. ApplicationDisplayState is bounded JSON owned by the
application; PureJsImage does not interpret its keys.
PersistedProviderPolicy mirrors the public reference-only, automatic, and exact pinned
policies. An absent or automatic policy is advisory: replay may choose another semantically
conforming provider and records the actual choice. A pinned policy is mandatory; replay fails if
the exact provider version and optional build fingerprint are unavailable or decline the plan.
Semantic and presentation fields
The graph hash covers the graph's computation semantics and excludes graph/input/node/output labels. The binding hash covers every binding's input name, exact value type, and semantic identity. ROI identities use quantitative ROI semantics: geometry, axes, fixed indices, coordinate space, units, schema version, and stable ROI ID. ROI names and presentation metadata are excluded.
display, graph labels, ROI names/presentation, createdWith, locator hints, and source-reference
IDs are presentation or provenance fields. They are not included in the three semantic hashes.
Changing a source identity, inline semantic value, quantitative ROI, value type, or graph recipe
requires recomputing the hashes.
The hashes use the library's published domains:
- graph:
purejsimage.analysis-graph.canonical-json.v2; - bindings:
purejsimage.analysis-bindings.v1; - invocation:
purejsimage.analysis-invocation.v1, over{ graphHash, bindingHash }.
The stored values must exactly equal a newly planned AnalysisInvocationManifest. A loader must
reject a mismatch; it must never repair hashes silently.
Source rebinding
A persisted local-file locator is a hint, not authority. Browsers ask the user for a File; Node
applications resolve paths through their own policy. The loader obtains the candidate's current
SourceIdentity and compares it with the persisted identity before planning.
- A matching strong content or versioned remote identity may be rebound automatically.
- A matching weak local metadata identity may be offered as a best-effort candidate, but the UI or calling script must disclose that equality is not proven.
- A session identity is not durable and always requires explicit rebinding.
- If a remote ETag, version ID,
Last-Modified, size, or other validator changes, the source is a different binding. Replay stops until the user accepts a rebind, after which binding and invocation hashes are recomputed and the event is auditable.
Hashing a multi-gigabyte source is never required before first display. Applications may later upgrade a weak identity to a content identity through an explicit bounded, cancellable hash pass; that produces a new binding and invocation identity.
Validation and migration
The validator boundary is intentionally explicit:
interface AnalysisProjectValidation {
readonly valid: boolean
readonly issues: readonly AnalysisIssue[]
readonly project?: AnalysisProjectV1
}
validateAnalysisProjectV1(
value: unknown,
options: {
readonly operations: OperationRegistry
readonly valueTypes: ValueTypeRegistry
readonly roi: {
readonly descriptor: NormalizedScientificDatasetDescriptor
readonly limits?: RoiLimits
}
readonly analysisLimits?: AnalysisLimits
readonly maxDocumentBytes?: number
readonly maxSourceReferences?: number
readonly maxBindings?: number
readonly maxDisplayBytes?: number
},
): Promise<AnalysisProjectValidation>
validateAnalysisProjectV1(), normalizeAnalysisProjectV1(), and
computeAnalysisProjectHashes() are exported from purejsimage/analysis. Validation treats parsed
data as unknown, rejects unknown fields, accessors, cycles, non-finite numbers, duplicate IDs,
unresolved references, mismatched value types/hashes, and configured limits. It does not open
sources, migrate, prepare providers, or execute a graph.
Project migrations and graph/operation migrations are separate explicit steps. A project loader first validates the envelope version, presents a registered project migration plan, applies it only after caller approval, then runs graph migration inspection and validation. No loader may silently rewrite operation semantics or preserve an old hash across a semantic migration.
Default limits are 16 MiB of UTF-8 project JSON, 1,024 bindings/source references, and 1 MiB of display JSON, in addition to the public graph and ROI limits. Applications may lower these limits.
Results
Version 1 persists no full quantitative results. Results may contain typed arrays and explicit
ownership that are not JSON-safe. An application may persist bounded summarizeResult() output as
display cache data outside this document, but it is not authoritative and must be keyed by the
invocation plus execution record. A future binary result format requires its own version and
integrity contract.
Complete example
{
"schemaVersion": 1,
"graph": {
"schemaVersion": 1,
"inputs": [{ "name": "surface", "valueType": { "id": "purejsimage.scientific.dataset", "version": 1 } }],
"nodes": [],
"outputs": []
},
"roiSet": { "schemaVersion": 1, "rois": [] },
"bindings": [{
"input": "surface",
"valueType": { "id": "purejsimage.scientific.dataset", "version": 1 },
"identity": {
"kind": "scientific-dataset",
"reader": { "id": "purejsimage/gsf", "version": "1.0.0" },
"datasetId": "surface",
"resources": [{ "id": "primary", "identity": { "kind": "local-file", "strength": "weak", "stability": "metadata", "nameOrPath": "sample.gsf", "size": 8192, "lastModified": 1722470400000 } }]
},
"value": { "kind": "source", "sourceReference": "primary" }
}],
"sourceReferences": [{
"id": "primary",
"identity": {
"kind": "scientific-dataset",
"reader": { "id": "purejsimage/gsf", "version": "1.0.0" },
"datasetId": "surface",
"resources": [{ "id": "primary", "identity": { "kind": "local-file", "strength": "weak", "stability": "metadata", "nameOrPath": "sample.gsf", "size": 8192, "lastModified": 1722470400000 } }]
},
"locatorHint": { "kind": "local-file", "name": "sample.gsf", "size": 8192 }
}],
"providerPolicy": { "mode": "reference-only" },
"display": { "dataset": "surface", "axes": ["x", "y"] },
"createdWith": { "packageVersion": "0.10.0", "buildFingerprint": "example-build" },
"hashes": { "graph": "<64 lowercase hex>", "bindings": "<64 lowercase hex>", "invocation": "<64 lowercase hex>" }
}