Skip to main content
InvariantCAD distinguishes expected modeling failures from programmer mistakes. Evaluation, parsing, topology, mass, and BOM operations commonly return CadResult; invalid direct authoring calls can throw TypeError or RangeError immediately.

Diagnostic shape

Branch on code, not message text. Messages are written for people and can be clarified without a protocol change. path uses document-oriented paths when a specific authored field is responsible. Paths from parsing and validateDocument are JSON Pointers: an ID segment that contains ~ or / is escaped as ~0 or ~1, so a node named a/b is reported under /nodes/a~1b. Some diagnostics raised later, during evaluation or feature hashing, still place such IDs unescaped, so prefer IDs without ~ or /. related identifies other document locations involved in the same failure, such as the other end of a conflicting reference.

Complete bounded-parse example

This canonical module configures a document-byte ceiling, deliberately exceeds it, and branches on the structured resource details instead of parsing the human-readable message.
The release gate compiles and executes the source module from examples/docs/diagnostics-and-resource-limits.ts. Document parsing stops measuring as soon as a ceiling is exceeded, so details.actual for maxDocumentBytes, maxStructuralValues, and maxNestingDepth is the size observed at that point: a lower bound on the full input, not its total size.

Common code families

Authoring exceptions

These are programmer mistakes and throw immediately:
  • duplicate IDs
  • cross-design references, including parameters inside expressions
  • wrong-dimensional expressions
  • a face selection passed to a fillet
  • an empty Boolean tool array
  • a configuration with no overrides
  • non-positive or non-finite numeric sketch and path tolerances
  • segments or divisions values outside the document grammar
  • unsupported literal authoring option values
Catch them at application input boundaries if users directly drive a builder. Do not catch and ignore them inside a model definition. Expression values are resolved only during evaluation, so an expression-valued tolerance (shell, offset, or topology predicate), a zero direction vector, or a negative radius is reported as a diagnostic, such as TOPOLOGY_SELECTOR_INVALID, instead of being thrown while authoring. For the public imported-body workflow, invalid caller definitions and unsafe source descriptors return IMPORT_SOURCE_INVALID; invalid evaluation options return IR_INVALID with a path under /options. Resource failures identify the resolution, commitment, or resource-limit phase without exposing bytes. The resolver is not invoked when the selected kernel lacks the strong exact single-solid capability or the operation is already aborted.

Document preflight limits

Default validation bounds include: Parsing captures an untrusted JSON-shaped input into a bounded plain copy while reading each source property at most once, so later reads cannot change what was validated. Cycles, sparse arrays, non-array objects whose prototype is neither Object.prototype nor null, and work beyond the configured bounds fail before schema traversal. For Documents v1–v6 the copy neutralizes rather than rejects the remaining JavaScript-only shapes: an accessor property is read once and its value is captured, only enumerable own string keys are copied, and an instance of an Array subclass is copied into a plain array. Only the staged Document v7 snapshot is stricter. It never invokes a getter and rejects accessor, non-enumerable, and symbol properties, as well as arrays that do not use the intrinsic array prototype.

Why limits are part of correctness

A shape can be mathematically valid but operationally unsafe to materialize. InvariantCAD therefore also bounds topology snapshot normalization, persistent matching, exact native evolution reports, conformance witnesses, and artifact sizes. Raising a limit changes the accepted workload and should be reviewed like a resource-policy change.

Assembly evaluation limits

Public Evaluator.evaluate(...) flattens assemblies into occurrence records. Document preflight bounds the document, not that expanded tree, so evaluation charges three fixed ceilings before allocating records: Crossing one fails with RESOURCE_LIMIT_EXCEEDED at the assembly node and the instance that would exceed it, with details.resource, details.limit, and details.actual. See occurrence ceilings for how records are counted. The ceilings are not configurable on the public evaluator.

Source-only staged external products

The repository-only product evaluator adds a document-scoped resource boundary for direct external-part occurrences and fixed external subassemblies. externalAssembly(resource, output) selects one assembly output from a committed InvariantCAD document and expands its child-local part and assembly tree. Each active occurrence path may cross at most one external-document boundary. This is source-only staged work, not a public 0.2.0 evaluator, resolver, builder, or CLI API. Resolver requests made by this operation include a frozen documentScope. { source: "root" } identifies commitments in the product document. A child resource uses { source: "external", resource: <root resource ID>, digest: <root digest> }. The scope is part of resource identity: the same resourceId in two admitted child documents represents two distinct commitments. Repeating the same (documentScope, resourceId) reuses the verified bytes. Ordinary resolveResourcesV7(...) calls omit documentScope. The product uses one cumulative resource-resolution session across external document JSON and later child geometry: All selected external-document commitments are preflighted and resolved before child assembly expansion or part preparation. Each later resolution phase is also fully preflighted, including its effect on cumulative counts and committed bytes, before a resolver callback for that phase. A later phase that would exceed the shared budget therefore fails before any child geometry callback, even when external document JSON has already been resolved. Suppressed branches are pruned before descendant document admission and do not consume those descendant product-work ceilings. An occurrence-specific external child failure is reported at the parent occurrence’s component pointer. Structured details always add componentResource, the selected output/outputKind, and the full occurrencePath. After the child document is admitted, details also include its digest, byteLength, and admitted sourceVersion. If the child diagnostic already names its own resource, that value is preserved instead of being overwritten. A child-owned output is preserved separately as childOutput, while output continues to name the external component output selected by the parent. The original child location is preserved as childNode and childPath when supplied. A fixed-subassembly leaf also retains its child part node and the child assembly/component location when the failure belongs to that leaf. Internal selectors used to evaluate those leaves are never reported as authored output names. The same top-level provenance applies to deferred failures raised later by an evaluated assembly, such as an occurrence mesh failure, an occurrence mass-property failure, or a BOM warning. For a fixed-subassembly leaf, childAssemblyNode names the child assembly that owns the leaf instance and childPath points at that instance’s component in the child document; a child diagnostic’s own path, when it has one, moves to childDiagnosticPath. A deferred assembly mesh failure names the assembly being meshed as assemblyOutput, because output names the external component. EvaluatedProductDocumentV7.mesh() and export(...) rethrow that structured assembly failure unchanged instead of reducing it to a message string, so applications can identify both the product occurrence and the child-document cause without parsing message text. An aggregate limit, preflight, or shared child-resource failure that cannot be attributed to one leaf remains at the selected external component boundary. A failure shared by several leaves of one fixed subassembly is not attached to an arbitrary first leaf merely to supply a child node or part name. An externalAssembly(...) declaration whose selected child output is not actually an assembly fails after committed child-document admission and before geometry work. A missing child configuration and an unsupported migrated-v6 extrusion-backed part likewise preserve parent/child provenance and fail before child geometry work. Child-local parts and nested local assemblies are admitted, but an active external descendant would cross a second document boundary and is rejected before nested resolution or child geometry/kernel work. A suppressed nested external descendant remains inert. If later child evaluation fails, every earlier acquired or intermediate shape is released exactly once, including work from an earlier child context. Successful product results retain their child shapes until product disposal. The supplied kernel is borrowed in both cases and is never disposed by the staged operation; verified resource bytes are cleared when its operation-scoped session ends.

Debugging workflow

  1. Preserve the full diagnostic object.
  2. Locate path and node in the serialized document.
  3. Check kernel capabilities before changing geometry.
  4. Use topology explanation functions for selector/reference failures.
  5. Reproduce with the smallest parameter/configuration context.
  6. Change a limit only after confirming the input is legitimate and bounded by infrastructure elsewhere.