Skip to main content

Complete executable lifecycle

This canonical module migrates a frozen v1 document, serializes it canonically, reparses the bytes through validation, proves the round trip is stable, and computes its semantic SHA-256.
The release gate compiles and executes the source module from examples/docs/document-canonicalization-and-migration.ts.

Serialize deterministically

Serialization canonicalizes topology query ordering and persistent-reference variants. Equivalent commutative topology expressions therefore produce the same document representation. The writer counts the canonical UTF-8 bytes, compact or pretty, before it builds the canonical tree or string, and topology normalization stops as soon as one query operand alone exceeds the ceiling. Output larger than maxDocumentBytes (64 MiB by default) throws TypeError instead of being materialized. Pass { limits: { maxDocumentBytes } } to choose another ceiling; malformed limits also throw TypeError. Output within the ceiling is byte-for-byte unchanged, including the frozen writer’s omission of an own __proto__ key. The writer does not otherwise validate its input, so the ceiling assumes plain data whose property reads are stable, such as builder output or a parsed document. It does reject values JSON cannot preserve: non-finite numbers, undefined array items, functions, symbols, and BigInts, and any object that is not an array, a plain object (from any realm), or a null-prototype object, such as a Map, Set, Date, typed array, or class instance, throw TypeError instead of being written as {}. hashDocument and cloneDocument share this writer.

Parse untrusted text

parseDocument requires a primitive string and measures its UTF-8 size before JSON.parse, stopping at maxDocumentBytes without encoding the text. It then captures a bounded plain snapshot and audits the raw text: an object that repeats a member name, including an escape-equivalent spelling such as "name" and "\u006eame", is rejected with IR_INVALID and details.reason: "duplicate-json-member" instead of silently keeping the last value. The raw structural and nesting ceilings also charge subtrees that last-key-wins parsing would have erased. Finally it validates the matching frozen version schema and graph semantics, and deep-freezes the result. Use parseDocumentValue(value) when another parser already produced an unknown JavaScript value. It still performs the bounded snapshot and schema checks, but it cannot detect member names that the upstream parser already collapsed, and neither can migrateDocument(value). Pass the original text to parseDocument at boundaries that must reject ambiguous JSON.

Do not cast

Type assertions provide no runtime safety and can feed cycles, wrong versions, invalid expressions, missing references, or resource bombs into code that expects a validated document.

Clone

Cloning serializes and reparses through the public protocol, applying options.limits to both steps. It is appropriate when a detached frozen copy is needed; it is not a mutable editing API.

Migrate

Migration preserves admitted authored data from versions 1 through 6 and moves it to the current schema. It does not:
  • invent information a prior version could not express
  • recapture topology evidence
  • upgrade a stored persistent-reference protocol
  • rewrite kernel fingerprints
  • execute geometry
Keep the original input when auditability requires proving exactly what was received before migration.

Hash a document

By default, root metadata is excluded so descriptive root changes do not alter the semantic document hash. Node metadata and every modeled field follow the protocol’s canonical representation. Pass { includeMetadata: true } when the root metadata is intentionally part of the hash. Like stringifyDocument, hashDocument rejects canonical bytes above limits.maxDocumentBytes before materializing them; the returned promise rejects with TypeError.

Schema identifiers

The schema URI and integer version are protocol identifiers. Parsers do not dereference the URI over the network. Treat both as exact values, not as a URL from which runtime code should be downloaded.

Imported-body documents are not v1–v6 documents

The functions on this page read, write, hash, migrate, and evaluate Documents v1 through v6. The public imported-body workflow writes a fixed, minimal Document v7 subset instead (see Imported-body document format), and those bytes have their own entry points: parseImportedBodyDocument, stringifyImportedBodyDocument, and Evaluator.evaluateImportedBody. Passing Document v7 JSON, including a parsed imported-body document, to parseDocument, parseDocumentValue, validateDocument, migrateDocument, or Evaluator.evaluate returns one IR_INVALID diagnostic at / instead of reinterpreting it. stringifyDocument, hashDocument, and cloneDocument throw a TypeError with the same message. migrateDocument never produces v7.

Editing strategy

InvariantCAD 0.2 exposes a builder for authoring and parsers for validated documents. It does not expose an in-place mutable document editor. To revise a stored document programmatically, use a reviewed transformation that produces a new value, then pass it through parseDocumentValue before persistence or evaluation.