Complete InvariantCAD guide
Comprehensive, type-safe CAD-as-code for TypeScript. InvariantCAD represents a design as immutable, versioned JSON and evaluates it through replaceable geometry and sketch-solver backends. The public API never exposes WASM pointers or kernel-specific objects.
Project status: 0.2.0 is the current public foundation release. It includes named definition-scoped configurations and variant-aware BOMs, kernel-independent effective-feature Merkle hashes, a strict kernel-shape artifact/cache protocol foundation with a canonical semantic-observation audit surface, an exact OpenCascade B-Rep backend, analytic sketch-profile transfer, STEP/BREP exchange, bounded ruled solid lofts, explicit 3D polyline, circular-arc, and ordered line/arc composite paths with bounded exact solid sweeps, closed semantic face/edge roles through those bounded sweeps, sketch-boundary provenance, document-owned persistent face/edge/vertex selectors, exact selector-driven fillets and equal-distance chamfers, exact face-selected inward/outward shells, exact whole-solid inward/outward offsets, atomic semantic-face draft, and owned exact multi-input Boolean, fillet/chamfer, and shell/offset evolution when the matched InvariantCAD-owned OCCT facade is loaded. No shipped geometry backend advertises the artifact codec yet, the semantic observer confers no eligibility, and the evaluator does not consume a cross-run cache. Complete topology history outside those owned feature slices is still under active development; see the support matrix and roadmap. Where 0.2.0 behavior differs from 0.1.1, the sections below say so. The general Document v7 grammar and the OCCT artifact candidate are repository-staged and are not exported from any package entry point.
This is the exhaustive single-page reference retained for readers who prefer
one continuous guide. The task-oriented documentation starts at the
documentation home.
Install
occt-wasm 3.8.0; it does not contain
the InvariantCAD-owned facade runtime or its local compliance bundle. No facade
bundle has been published to npm yet, and InvariantCAD never downloads one
implicitly.
Quick start
Exact B-Rep evaluation
Use the OCCT backend when the result must retain exact analytic geometry or be exported through STEP/BREP. The complete example above evaluates one document with both backends, exports a detached STEPUint8Array, disposes each
successful evaluated design, and handles the caller-owned-kernel failure path
before evaluator ownership transfers.
The deterministic STEP contract below was added in 0.2.0. The 0.1.1 package
provided ordinary weak native STEP export.
KERNEL_STEP_EXPORT_PROTOCOL_VERSION === 1 capability makes single-product
AP214IS bytes deterministic for the same backend shape representation, export
options, metadata, implementation, and exact runtime artifact. It is stronger
than the nativeExports availability flag, but it is not geometric
canonicalization or a cross-runtime cache key. The STEP-only
output.export("step", { metadata?, signal?, maxOutputBytes? }) overload uses
document/output/part identity defaults and a fixed
1970-01-01T00:00:00 timestamp. The five resolved metadata strings have a
64 KiB UTF-8 budget. The timestamp retains its exact calendar syntax; the four
identity/description fields support Unicode scalars. Apostrophes use STEP
doubling; literal backslashes and non-ASCII Unicode scalars use Part 21
\X2\...\X0\/\X4\...\X0\ directives. Explicit metadata values that contain
control characters or unpaired surrogates are rejected with
EXPORT_OPTIONS_INVALID. Defaults derived from the document never make a valid
document fail to export: control characters become spaces, unpaired surrogates
become U+FFFD, an empty required default becomes InvariantCAD Shape, and the
longest defaults are truncated at Unicode scalar boundaries to fit the budget,
as described in deterministic single-product STEP.
Metadata is validated and encoded before the native writer starts. Encoding
expansion counts toward the 64 MiB default transformed-output ceiling, and
structural post-write scanning is bounded and cancellable. Exceeding
maxOutputBytes, the default output ceiling, or a fixed input, scan, or entity
ceiling throws CadError with RESOURCE_LIMIT_EXCEEDED and
details.resource, details.limit, and, when known, details.actual.
maxOutputBytes does not bound peak native allocation: the synchronous stock
writer cannot be interrupted or bounded before it materializes its original
full string.
The contract covers one STEP product. The stock writer writes an output that
holds several disjoint solids, such as a union of separated boxes, as several
products. A plain output.export("step") of such an output falls back to the
weak native STEP bytes, while passing any options object requests the strong
contract and throws CadError with EXPORT_UNSUPPORTED and
details.reason === "multiple-products". Material, configuration, arbitrary
document/part metadata, and aggregate assembly/body-set STEP are not mapped.
Explicit wasm, moduleFactory, and attestedRuntime initialization retains
weak raw STEP export and omits this strong envelope until that exact writer
artifact is separately qualified.
The backend is explicitly selected; a design document never contains OCCT handles or backend-specific objects. The default createOcctKernel() loads stock occt-wasm and supports the exact geometry features listed below except draft, but its Boolean, fillet/chamfer, and shell/offset topology history is partial. The stronger capabilities are advertised only when a matched InvariantCAD-owned facade passes the exact facade probe, which recognizes ABI 0.2 through 0.9. Every recognized owned facade advertises draft with its exact evolution; ABI 0.3 and later add the stronger major multi-arc/eccentric-profile composite guarantees; ABI 0.4 adds complete exact multi-input Boolean evolution, ABI 0.5 fillet/chamfer evolution, and ABI 0.6 shell/offset evolution. The repository builds ABI 0.9, which carries all of them. A caller can supply the matched JavaScript/WASM pair directly through moduleFactory plus optional wasm, or preferably verify a reviewed bundle first with the Node/browser attested loader and pass its opaque attestedRuntime. ABI 0.9 retains the complete ABI 0.6 modeling/history surface, ABI 0.7’s bounded artifact transport, and ABI 0.8’s private cumulative native allocation-request budget, then adds exact owned-profile BinTools-v4 structural preflight only for the repository-private candidate; it does not advertise a shape-artifact codec. The repository can turn that local build into a verified, package-neutral bundle, but applications must still acquire and supply its runtime explicitly. See Browser initialization and OCCT runtime attestation.
Since 0.2.0, createOcctKernel() validates modelingTolerance and tessellation before loading WebAssembly. A non-number tolerance or a non-boolean tessellation.relative throws TypeError; a NaN, zero, negative, or infinite tolerance or deflection throws RangeError; every mesh() call checks its tessellation options the same way. 0.1.1 accepted such values. 0.2.0 also stops comparing dimensionless values with the length modelingTolerance: a transform scale is uniform only when its factors agree to floating-point rounding, and exact extrusion rejects any twist other than 0 and any top scale other than [1, 1] with KERNEL_ERROR, where 0.1.1 silently built an untwisted, untapered, or uniformly scaled solid when a raised tolerance covered the difference.
Exact path sweeps
Paths are first-class parameterized nodes rather than closed sketch profiles:1e-8 (COMPOSITE_PATH_MAX_JUNCTION_SINE), so write arc points as exact expressions, as in the Math.sqrt(2) forms above; hand-rounded coordinates fail with non-tangent-junction. Line-line junctions retain right-corner semantics. Since 0.2.0, adjacent polyline or composite line-line segments that fold back within tolerance fail with self-intersection, and right-corner miters are checked against the profile envelope radius r: a corner that turns by θ sets each adjoining straight segment back by r · tan(θ / 2), every straight segment must be longer than the setbacks at its two ends plus the tolerance, and remote clearance is measured against segments extended by that outer miter reach; a violation fails with path-clearance. Minor, major, and certified near-full composite arcs are supported without an artificial endpoint-chord rule. Adjacent segments exclude only their curvature-bounded intrinsic neighborhood, then recursively certify every remote line/arc parameter domain against path tolerance and the full profile diameter; redundant adjacent segments, actual remote returns, and numerical ambiguity fail explicitly. The OCCT adapter realizes the one-edge circular case as an exact revolution about the resolved circle axis. Ordered composites use one exact PipeShell wire. Every composite arc must exceed the profile-envelope radius and pass the 3e-8 three-point conditioning floor. Every PipeShell profile/path edge and all three authored point-pair separations of a composite arc must exceed the native 1e-4 mm transfer floor.
Stock PipeShell does not expose its angular tolerance, so stock OCCT admits a major-arc composite only when it contains one circular-arc segment and the seated profile area centroid is centered within the selected tolerance. InvariantCAD derives that requirement before backend invocation with exact, kernel-neutral line/arc/circle area moments, compensated error bounds, and a strict major threshold of π + 1e-12; an admitted authored profile-origin mismatch is not mistaken for section eccentricity. Those analytic local moments—not OCCT’s world-coordinate integration—also define circular and composite sweep volume. OCCT independently remeasures the constructed profile face and must agree within bounds derived from analytic roundoff, profile perimeter/radius, modeling tolerance, and per-axis coordinate ULPs; disagreement fails before path or result allocation with structured diagnostics. Circular transfer also derives a native-face volume around the actual rounded revolution axis before constructing the solid and requires it to remain inside the same certified representability envelope. Relative profile-centroid and arc-center offsets avoid add-large/subtract-large cancellation at representable world translations. Owned facade ABI 0.3 introduced a corrected-Frenet/right-corner PipeShell with explicit linear, boundary, and 1e-9 angular tolerances, bounded OCCT surface error, and transactional transfer; ABI 0.6 retained that contract, and the current ABI 0.9 retains the same modeling/history surface. It certifies major multi-arc and eccentric-profile composites and advertises those guarantees as versioned refinements. Every composite result is also checked against an exact transported-centroid volume oracle covering lines, arcs, and supported RightCorner miters; ill-conditioned cancellation fails closed. Guided, variable-section, full-circle, Bézier, B-spline, and helix paths remain explicit future contracts.
Semantic topology, Booleans, fillets, chamfers, shells, offsets, and draft
Topology selections describe intent as set queries. They never persist a face index, edge index, OCCT handle, or transient hash. This source-aware selector keeps identifying the same extrusion rim after a 90-degree rotation and across width/height parameter crossovers:edges selects contour seeds rather than hard stopping boundaries. Each seed expands to the maximal connected contour of tangent edges that continues between tangent face chains on both sides. A closed tangent contour expands around the complete loop, and multiple seeds on the same contour apply the operation only once. Selector cardinality constrains the seed set, not the number of input edges ultimately modified. Before native execution, InvariantCAD deduplicates the seed set and orders it by the input snapshot’s edge index. ABI 0.5 echoes that exact canonical seed list, admits the first seed for each not-yet-covered tangent contour, records later overlapping seeds as skipped, and builds all admitted contours in one operation. Expansion stops at sharp, disconnected, degenerate, non-manifold, boundary, or ambiguous junctions. Continuity at a modeling-tolerance boundary can remain kernel-dependent; this mode cannot yet express “stop at this otherwise tangent vertex.”
Shell openings deliberately use different semantics: openings is the exact set of input faces passed to the shell maker’s removal list. Selected faces are not tangent-contour seeds, and the operation never propagates the selection to an adjacent tangent or coplanar face. Selector cardinality therefore describes the actual opening-face set passed to the kernel. Selection does not force a DELETED topology record: the pinned maker may report a selected source face as MODIFIED into the planar opening rim.
Shell thickness is always a positive wall-thickness magnitude. direction: "inward" keeps the unselected input boundary as the exterior skin and offsets the second wall into the solid; direction: "outward" keeps it as the interior skin and builds the second wall outside it. The current document grammar fixes offset-face transitions to round/arc joins; intersection/miter joins are not supported yet. The builder defaults direction to "inward" and reconstruction tolerance to mm(1e-6), then materializes both values in the immutable document so serialization and semantic hashes include them. Thickness and tolerance must be positive, and tolerance must be less than thickness.
The current shell mode accepts exactly one solid with no loose faces, edges, or vertices, at least one selected opening face, and at least one retained face. It produces one valid positive-volume solid or a structured kernel diagnostic; it does not apply independently to disconnected bodies. Closed hollowing without an opening and variable-thickness shells are not supported yet.
Whole-solid offset uses a positive distance magnitude plus an explicit direction. "outward" adds material outside the oriented boundary; "inward" removes material inside it. The builder defaults to "outward" and mm(1e-6) tolerance and materializes both in the document. The current document grammar fixes round/arc joins: an outward box offset therefore contains cylindrical edge transitions and spherical corner transitions rather than an intersection/mitered box. Distance and tolerance must be positive, and tolerance must be less than distance.
Offset accepts exactly one valid positive-volume solid with no loose lower-dimensional topology and must return the same. Invalid, collapsed, disconnected, and direction-inconsistent results fail explicitly. Result orientation differs by path: the stock adapter reverses a negative-volume shell result but rejects a negative-volume offset result rather than repairing it, while owned facade ABI 0.6 and later reverse a negative-volume shell or offset result before validating it. Either way, the reoriented result must still be one valid positive-volume solid that passes the direction checks. It is a 3D body operation; 2D wire/profile offsets will use a separate future contract.
Draft applies the selected input faces atomically: either every face is staged into one native operation or no result is exposed. Its angle is signed and must satisfy 1e-4 < Math.abs(angleRadians) < Math.PI / 2; the pull direction and neutral-plane normal must be nonzero. Pull direction and neutral plane are independent inputs, so neither vector is inferred from or rescaled to the other. The neutral plane is defined by its explicit origin and normal, and its intersection with the drafted faces remains fixed.
The matched owned OCCT facade proves a complete one-to-one face/edge/vertex evolution for every successful draft before transferring the result. That feature-scoped exactIndexedTopologyEvolution v1 guarantee lets later face/edge/vertex queries retain inherited createdBy(...) lineage and identify changed topology with modifiedBy(drafted) without exposing native indices. It does not change the backend’s global topology-provenance declaration, which remains feature because other topology-changing features still have partial history.
Owned facade ABI 0.4 extends that version-1 envelope to every successful union, subtraction, and intersection, including multiple tools. Source shape 0 is the authored target and sources 1..N are the authored tools in order. Union and intersection apply each tool sequentially in that order; subtraction is one cut of the target against the complete authored tool set. The report must prove complete face/edge/vertex coverage for every input and the aggregate result before ownership transfers. PRESERVED and MODIFIED are same-kind identity links, GENERATED is an exact causal link that may change topology kind, and DELETED terminates an input subshape with the NONE/-1 result sentinel only when that source has no final identity successor. Generated links may coexist with identity or deletion records, but do not replace the required identity-successor-or-deletion proof for their source. The facade retains every available native preserved, modified, and generated claim before classifying any residual result topology. OCCT can create higher-order topology through interactions among multiple tools without assigning it to one operand; those otherwise-unclaimed result items use source-less CREATED with the exact -1/NONE/-1 source sentinel. A created result cannot also carry an operand claim.
Owned facade ABI 0.5 retains the ABI 0.4 Boolean contract and applies the same complete version-1 relation graph to constant-radius fillets and equal-distance chamfers. Edge treatments have one input source, but their report still proves every input and result face, edge, and vertex: each source has an identity successor or DELETED, every available GENERATED cause is retained, and only otherwise-unclaimed result topology receives source-less CREATED. A direct solid or a nested one-child compound/compsolid wrapper around exactly one solid is accepted, which lets exact Boolean results feed edge treatments without admitting loose or multiple topology; successful output is normalized to the contained solid. Before constructing the operation, native code makes a deep independent B-Rep working copy of the input, including its curve and surface geometry, and proves one-to-one original/copy topology correspondence. Only that copy enters the fillet or chamfer builder, so the arena-owned input BREP remains byte-stable.
Owned facade ABI 0.6 retains every earlier surface and adds one transactional solid-offset operation for face-selected shell and whole-solid offset. Shell openings are deduplicated and sorted by input face index; native code echoes that canonical selection, while offset accepts no openings. Both modes use positive public magnitudes plus explicit inward/outward direction and tolerance, run one fixed-round-join builder against a deep independent copy of the sole valid input solid, and leave the authored arena BREP byte-stable. Native code reverses a negative-volume result before validation, then the report owns one validated positive-volume single-solid result and a complete version-1 graph over the input and result faces, edges, and vertices until a same-kernel one-shot transfer.
The pinned offset engine can expose a replaced source only through GENERATED while its deletion query remains false. ABI 0.6 reconciles that generated-only replacement from final-result membership: when the source identity is absent and there is no native Modified successor, it records the exact terminal deletion while preserving every generated cause. This makes the identity-successor-or-deletion rule complete without guessing from geometry. Conversely, a selected shell opening may have a real modified identity successor at the planar rim, so selection itself is never treated as proof of deletion.
Public semantic lineage is intentionally stricter than causal history. Only PRESERVED and MODIFIED identity predecessors inherit earlier lineage, roles, and sketch sources; GENERATED never copies the source item’s identity. One narrow edge-treatment rule names the new face class directly from exact evolution: an identity-less result face with at least one exact GENERATED predecessor whose source kind is edge receives fillet.face.blend or chamfer.face.bevel, with no invented sketch source. The rule does not consult surface type, result order, or the authored seed list, so tangent-contour expansion is covered by the graph actually reported by OCCT. Other generated topology and every residual source-less CREATED item remain only createdBy(currentFeature). In particular, no fillet/chamfer edge role is published, and Boolean, shell, and offset generated topology remains unnamed. An identity successor keeps its proven earlier lineage and gains modifiedBy(currentFeature) only when an identity link is modified. These native indices and all public topology keys remain evaluation-scoped plumbing, never persistent document identity.
Exact Boolean evolution is an optional capability. The evaluator validates and uses it when a backend advertises it, but absence does not block the ordinary Boolean feature: stock OCCT and owned ABI 0.2/0.3 remain compatible with partial Boolean history, while Manifold retains Boolean geometry without advertising topology snapshots or history. A malformed advertised envelope or a failed completeness proof is authoritative and fails closed instead of downgrading. ABI 0.4 makes a topology-independent working copy of every operand, shares its immutable geometry, and runs each native builder in non-destructive mode; the Boolean never receives an arena-owned TShape, so every authored target and tool BREP remains byte-stable across the operation. Copy-to-source topology correspondence is proved before history can succeed. The adapter copies, freezes, count-checks, and validates the complete report before its one-shot same-kernel transfer; an adoption failure rolls the transferred result back exactly once. createOcctKernel({ maxExactBooleanHistoryRecords }) sets the caller-controlled record budget, defaults to 1_000_000, passes it into the facade, and rejects an oversized report natively before report record materialization and again from its count before any indexed JavaScript copying. That option bounds history records only; operand working copies and OCCT’s Boolean workspace still scale with the input topology. A legitimate empty Boolean result is representable by zero result counts and follows the normal evaluator EMPTY_RESULT / allowEmpty policy.
Exact fillet/chamfer evolution is independently optional. ABI 0.5 advertises exactIndexedTopologyEvolution v1 for fillet and chamfer; the evaluator validates that metadata before resolving the edge selector, but missing metadata or a well-formed declaration that omits the selected feature continues through the supported partial-history implementation. Stock OCCT and owned ABI 0.2–0.4 therefore retain their exact fillet/chamfer geometry without claiming complete provenance. A malformed advertised capability or exact report fails closed. The ABI 0.5 report owns its result until a READY same-kernel one-shot transfer; TypeScript copies, freezes, count-checks, and validates its diagnostics, canonical selected-edge echo, complete graph, and raw topology counts before transfer, then rolls back a transferred root exactly once if adoption or lineage reduction fails. createOcctKernel({ maxExactEdgeTreatmentHistoryRecords }) provides a separate signed 32-bit record budget with the same 1_000_000 default and native-before-JavaScript enforcement. It does not share or consume the Boolean budget, and it bounds history records rather than the mandatory independent operand copy or OCCT builder workspace.
Exact shell/offset evolution is independently optional as well. ABI 0.6 advertises exactIndexedTopologyEvolution v1 for shell and offset; missing metadata or a well-formed feature omission preserves the stock or legacy owned exact-geometry path with explicit partial history. Malformed metadata fails before selector or kernel execution, while a malformed exact report is authoritative and fails before result exposure. TypeScript validates the operation, direction, amount, tolerance, canonical opening echo, build/status fields, topology counts, full graph, and READY transfer state, then rolls back exactly once if post-transfer validation or lineage reduction fails. createOcctKernel({ maxExactSolidOffsetHistoryRecords }) provides its own signed 32-bit record budget with a 1_000_000 default and native-before-JavaScript enforcement. It is independent of both existing history budgets and does not bound the mandatory deep operand copy or OCCT shell/offset workspace.
The serialized role vocabulary is closed and exported through TOPOLOGY_ROLES and TOPOLOGY_ROLE_RULES:
start and end follow construction parameterization, not current world orientation. For a revolution, the axis is the profile plane’s local v axis through its origin. Since 0.2.0, the profile must lie on one side of that axis; it may touch it, and material within the sketch tolerance of the far side counts as touching. A profile that crosses the axis fails with FEATURE_INVALID at /nodes/<id>/profile and details.reason: "profile-crosses-axis" before any kernel call, and Manifold revolves a profile that lies wholly on the negative side instead of keeping only its positive side. Every boundary curve that produces a face can contribute one revolve.face.swept role carrying that curve’s sketch-entity source. A line contained in the revolution axis collapses and intentionally contributes no swept face. Partial turns expose source-free revolve.face.start-cap and revolve.face.end-cap roles; a full turn has no cap faces or cap roles. Revolution edges, seams, poles, and other generated artifacts have no semantic role. If any constructed revolution-face seed has no unique result-face correspondence, the OCCT snapshot downgrades to partial history instead of attaching a possibly incorrect role. A topology-preserving transform retains the original role/source lineage and adds modified lineage for the transform. Cylinder seams, cone apex artifacts, sphere seams/poles, and other kernel artifacts are likewise deliberately unnamed so a document cannot accidentally depend on their enumeration.
For the current bounded ordered ruled loft, loft.face.start-cap and loft.face.end-cap name the first and last profile faces without a sketch-curve source. Each face ruled between matching curve indices on two adjacent profiles receives loft.face.side; when the resolved curves carry sketch-entity sources, both participating sources are recorded and querying either source can select that face. Every authored section curve contributes a loft.edge.section-rim, carrying a source when its resolved curve has one. Direct kernel calls may supply source-free resolved profiles: those shapes retain roles, but InvariantCAD never invents a sketch source. For each matching non-circular curve, the edge joining its authored starts across one adjacent profile pair receives source-free loft.edge.lateral; a circle has no authored boundary start, so its arbitrary kernel seam remains deliberately unnamed.
The five loft roles apply only to compatible, parallel-plane, hole-free ruled solids whose ordered curves have the same kind, orientation, and authored curve phase across every section. Cyclically rotating an otherwise equivalent loop changes that authored correspondence and is rejected with curve-phase-mismatch instead of allowing OCCT to choose a different pairing. Since 0.2.0, the phase is checked between adjacent sections, the pairing OCCT re-phases, so a gradual twist of square sections at 0, 40, and 80 degrees is accepted while 0, -44, and +44 degrees fails at profiles/2; 0.1.1 compared every section with the first. If construction of any semantic seed fails, a side or lateral correspondence is not unique, a seed cannot be mapped uniquely into the result, or the expected role inventory is incomplete, the snapshot is marked partial rather than treating an incomplete map as authoritative.
The bounded sweep contract has six role literals. Let C be the number of direct-profile boundary curves, S the number of authored path segments, and V the number of authored non-circular profile-curve starts. There is one source-free sweep.face.start-cap and one source-free sweep.face.end-cap; sweep.face.side has cardinality C*S; sweep.edge.start-rim and sweep.edge.end-rim each have cardinality C; and source-free sweep.edge.lateral has cardinality V*S. Sweep start and end follow the authored path direction, not world orientation. Each side, start rim, and end rim carries the optional sketch-curve source from its corresponding direct-profile curve. Caps and laterals carry no source, a direct source-free profile call invents none, and there is deliberately no path-segment source identity. Internal path-joint/right-corner miter fragments and arbitrary circular seams remain unnamed.
OCCT proves that inventory from the result’s face-edge incidence graph: it anchors the authored start section, walks one uniquely corresponding side-face layer per path segment, finds one terminal cap and its rims, and checks curve-local lateral adjacency and complete role counts. A branch, gap, reused or ambiguous candidate, incomplete coverage, unexpected nonlocal mapping, or distant false correspondence downgrades the snapshot to partial history rather than publishing incomplete semantic naming.
The two edge-treatment roles are class-level anchors, not per-edge identities. One treated contour can generate more than one face, and treating multiple contours can produce multiple faces with the same { feature, role } pair. A role selector can intentionally select that full class with an appropriate cardinality, but capturing one member as a persistent reference fails ambiguous when another member has the same authoritative semantic anchor; geometry is not used to break that semantic ambiguity.
Selectors also support curve/surface kind, edge direction, face normal, radius, vertex position, valid face↔edge and edge↔vertex adjacency, and/or/not, and explicit cardinality. Angular predicates default to deg(0.1) and linear ones to mm(1e-6). Face normal is oriented, edge direction ignores sign, and vertex position applies its tolerance to each axis separately; see the topology language for the complete predicate semantics. Zero matches produce TOPOLOGY_SELECTION_MISSING; excess matches produce TOPOLOGY_SELECTION_AMBIGUOUS. Selection counts are positive integers. Since 0.2.0 they must also be safe integers: exactly, atLeast, and between throw RangeError (must be a positive safe integer) for a count above Number.MAX_SAFE_INTEGER, and validation and resolution reject such a selection with TOPOLOGY_SELECTOR_INVALID. The exact backend currently provides complete feature provenance for primitives, extrusions, revolutions, lofts, bounded sweeps whose graph proof succeeds, and topology-preserving transforms, plus the semantic face/edge roles and sketch sources above. Vertices have no semantic role vocabulary. The matched owned ABI 0.9 facade retains the ABI 0.6 exact indexed-evolution surface for draft, Boolean, fillet, chamfer, shell, and offset. Boolean history remains partial on stock/default OCCT and older owned ABIs; fillet/chamfer history remains partial on stock/default OCCT and owned ABIs 0.2–0.4; shell/offset history remains partial on stock/default OCCT and owned ABIs 0.2–0.5. Origin queries against any partial-history result fail with TOPOLOGY_HISTORY_UNAVAILABLE rather than choosing unstable topology, while geometry-only selectors can still inspect it. Manifold exposes none of these topology snapshots or selectors; Manifold and stock/default OCCT both report an explicit capability error for draft.
Explain ordinary topology selections
explainTopologySelection(...) exposes the aggregate result of one ordinary face, edge, or vertex selector pass without turning missing or ambiguous cardinality into a failed operation:
version, topology, currentHistory, candidatesConsidered, candidatesMatched, minimumRequired, and maximumAllowed; an omitted maximum is represented as null. outcome is resolved, missing, or ambiguous. Only resolved adds the sorted current evaluation-scoped keys; missing and ambiguous reports are key-free. Invalid selections, query inputs, snapshots, origin queries against partial history (TOPOLOGY_HISTORY_UNAVAILABLE), exhausted operational limits, and any nested selection failure—including persistent-reference resolution—remain failed CadResults rather than explanation outcomes. A malformed kernel snapshot fails as KERNEL_ERROR with details.protocolViolation: true. Since 0.2.0, a structurally malformed selection, such as a missing or unknown query operator, fails as TOPOLOGY_SELECTOR_INVALID and never as KERNEL_ERROR; 0.1.1 reported it as a KERNEL_ERROR protocol violation.
resolveTopologySelection(...) keeps its legacy fail-closed return shape. Its TOPOLOGY_SELECTION_MISSING and TOPOLOGY_SELECTION_AMBIGUOUS diagnostics now include the same version-1 aggregate under details.explanation. A direct resolveTopologySelection(...) call and a direct explainTopologySelection(...) call are separate normalization and selector passes; there is no shared-session or cross-call cache for ordinary selections.
Persistent topology references across evaluations
Topology-signature protocol v2 extends the bounded persistent-topology layer to exact B-Rep vertices while retaining protocol-v1 face/edge compatibility. A topology-capable evaluated solid exposes a validated, detached, deeply frozen copy of its current face/edge/vertex snapshot throughEvaluatedSolid.topology(), returning a CadResult<KernelTopologySnapshot> rather than exposing the underlying kernel shape or a mutable kernel cache. A kernel advertises its primary persistent-reference profile through KernelTopologyCapabilities.signatures and may advertise exact older profiles through signatureProfiles:
invariantcad-topology-descriptor@6. The OCCT adapter also advertises one exact protocol-v1 compatibility profile: stock and owned ABI 0.2–0.4 retain their former descriptor @4 fingerprint, while owned ABI 0.5+ retains its former descriptor @5 fingerprint. Existing stored v1 variants therefore continue to resolve without recapture, and new primary captures use v2/@6. When a registry contains more than one compatible profile, evaluation prefers the highest supported protocol deterministically. createOcctKernel() publishes these declarations for its known default stock runtime and recognized owned facades. Supplying an explicit wasm or an unknown custom moduleFactory suppresses them unless facade probing recognizes the matched owned runtime.
The executable persistent-topology torture corpus covers protocol-v2 descriptor @6, exact protocol-v1 @4/@5 compatibility profiles, and the owned facade ABI 0.6 exact-evolution matrix. Inherited Boolean, fillet/chamfer, shell, and draft items survive relevant parameter changes with fresh keys. A vertex whose complete incident-edge set carries authoritative anchors can resolve semantically across translation and dimensional change; a vertex with no anchored side uses its point plus edge adjacency. The exact generated fillet/chamfer face resolves semantically through its proved class role across amount changes and drives downstream role and persistent selectors; a two-contour case fails capture as class-level ambiguity. Generated shell/offset faces and unnamed treatment edges remain geometry-only and may fail key-free when their evidence changes. The corpus never promotes surface resemblance or enumeration into persistent identity.
captureTopologyReference(...) receives one snapshot, a face, edge, or vertex key from that snapshot, the advertised signature capability, and explicit linear, angular, and relative match tolerances. Linear tolerance is an absolute error threshold for world-space coordinates, centers, and bounds: it does not grow merely because two compared coordinates are numerically far from the origin. When no authoritative semantic path applies, an actual translation changes those coordinates and can prevent a geometric match. Relative tolerance applies to measures and radii; face area also receives a linear-tolerance term scaled by the face’s characteristic length. It returns a deeply frozen PersistentTopologyReference containing canonical semantic lineage, structured geometry, and structured one-hop adjacency evidence. The returned reference is detached: it contains no kernel key, native index, array ordinal, or enumeration-derived tiebreaker, so it can be stored by application code after the evaluation result is disposed. Call EvaluatedSolid.topology() before disposing its evaluation; calling it afterward throws through the normal evaluated-shape lifetime guard. A snapshot saved before disposal remains readable, but its keys are still scoped to that evaluation. The key-free captured reference is the durable evidence.
Legacy DesignDocumentV1 remains parseable, cloneable, directly evaluable, and hash-stable, but cannot contain persistent selectors. DesignDocumentV2 adds the optional document-owned topologyReferences registry and persistent selector atom while retaining the pre-loft closed role vocabulary. DesignDocumentV3 adds only the five serialized loft roles, DesignDocumentV4 adds only the six bounded-sweep roles, and DesignDocumentV5 adds only fillet.face.blend and chamfer.face.bevel, including those same closed locations in stored lineage and adjacency evidence. DesignDocumentV6 is current: it adds vertex topology references plus vertex position(...) and edge↔vertex adjacency queries without adding any semantic vertex role. Registry entries bind one topology kind and one exact solid-node target to one or more protocol/fingerprint variants. Registry data is semantic: normalized evidence and canonical variant order participate in serialization and hashing. Parsing, cloning, stringifying, hashing, validation, and direct evaluation preserve supplied v1–v5 documents without silently upgrading them. migrateDocument validates and upgrades v1–v5 to v6 and is idempotent for v6. Migration never captures, relabels, protocol-upgrades, or rewrites stored descriptor evidence: protocol-v1 @4/@5 variants retain exactly their original bytes and evidence after document migration.
These are four independent version axes. Document v6 versions the serialized JSON authoring grammar. Topology-signature protocol v2 versions current detached evidence and matching, while protocol v1 remains supported unchanged. OCCT descriptor @6 is the primary v2 semantic declaration, with exact v1 descriptor @4 or @5 exposed as a compatibility profile according to the runtime. The current owned facade ABI is 0.9: it retains the ABI 0.6 modeling/history surface, ABI 0.7’s bounded artifact transport, and ABI 0.8’s private cumulative native allocation-request budget, then adds exact owned-profile BinTools-v4 structural preflight only for the repository-private candidate, while the separate exactIndexedTopologyEvolution protocol remains version 1.
Capture and resolution also accept limits?: Partial<TopologySignatureLimits>. Omitted fields come from the frozen exported DEFAULT_TOPOLOGY_SIGNATURE_LIMITS object:
Each override must be a non-negative safe integer; unknown limit fields and malformed values produce
TOPOLOGY_SIGNATURE_INVALID. Crossing a ceiling stops that call with TOPOLOGY_SIGNATURE_LIMIT_EXCEEDED and reports the resource, configured limit, and observed actual count. Resolution applies the adjacency-link and evidence-record size ceilings independently to the detached reference and to the current snapshot. The stored-variant counter is cumulative across distinct reference IDs in one persistent-selection operation; candidate-pair and matching-step counters are cumulative across those references and every compatible profile. During document evaluation, that operation is one feature resolution. Snapshot and reference arrays are read with fixed captured lengths and metered while they are detached, without invoking caller iteration hooks. These budgets constrain TypeScript signature normalization and matching after a snapshot exists. They do not bound kernel topology extraction, native modeling/history memory, or the separate owned-OCCT history-record budgets.
explainTopologyReference(...) returns a deeply frozen version-1 aggregate report from the same bounded matching pass used by resolveTopologyReference(...). Here ok: true means the analysis completed; outcome is separately resolved, missing, or ambiguous. Every report states the captured/current history modes, unique stored-anchor count, total candidates considered and matched, and per-strategy considered/matched counts for semantic-lineage and geometry-adjacency. Only resolved exposes a current evaluation-scoped key and evidence. Missing and ambiguous reports never expose candidate keys, native indices, ordinals, descriptors, or enumeration-derived samples. Malformed input, fingerprint incompatibility, invalid options, malformed snapshots, and exhausted work limits remain ordinary failed CadResults rather than partial explanations.
The ExplainableTopologyReferenceResolutionSession returned by createTopologyReferenceResolutionSession(...) adds explain(reference) beside resolve(reference). Both project one object-identity-cached analysis, so asking for both does not repeat matching or recharge the session’s cumulative work budget. Missing and ambiguous resolve(...) diagnostics also include the same frozen report in details.explanation, including when a document-owned persistent selector adds its reference ID and node/path context.
persistentReference(...) composes with and, or, not, and adjacentTo like every other topology atom. A stored reference is bound to the consuming feature’s direct solid input; an ancestor, descendant, unrelated node, or reference from another builder is rejected. Fillet, chamfer, shell, and draft consume face/edge selectors today, and vertex atoms can participate in their nested adjacency queries. Before evaluating the input solid, the evaluator requires every topology kind and evidence surface needed by the selected signature profile, then verifies an exact protocol/fingerprint variant. Missing capability, malformed capability metadata, and an unavailable fingerprint fail without invoking input geometry, topology extraction, or the feature.
All persistent atoms in one feature resolution share one normalized snapshot, one cumulative candidate/matching budget across every compatible protocol profile, profile-appropriate compiled evidence, and a per-reference cache. Pass topologySignatureLimits through EvaluationOptions to override those operational limits. A failed reference remains fatal inside every logical operator; or and not never hide an invalid, ambiguous, missing, or incompatible reference.
With complete history on both snapshots, any role or sketch-source creation anchor is authoritative. When either side carries one, resolution compares only those anchors: exact agreement resolves as evidence: "semantic-lineage", any difference is a mismatch, and geometry is never consulted, even when several current items share the anchor. Items that share a source-free class role therefore fail capture or resolution with TOPOLOGY_MATCH_AMBIGUOUS rather than being separated by geometry: extrusion, loft, and sweep lateral edges; fillet.face.blend and chamfer.face.bevel faces; the sides and rims of a source-free multi-curve profile; and the sweep sides that one profile curve produces along several path segments. Select such items through their role with an explicit cardinality instead. A protocol-v2 vertex with no anchor of its own resolves semantically only when every incident edge on both sides is anchored and the complete incident-edge anchor sets agree one-to-one; when only one side is fully anchored, it is a mismatch. Toleranced "geometry-adjacency" evidence applies only when neither side is anchored or either snapshot declares partial history, and partial lineage is never treated as authoritative. Protocol v2 compares face geometry with incident edges, edge geometry with incident faces and vertices, and vertex points with incident edges. Adjacency matching is iterative and non-recursive: neighbor evidence never contains another adjacency layer, and the matcher uses bounded one-to-one bipartite matching rather than recursively traversing the topology graph. Protocol v1 retains exactly its original face↔edge evidence and behavior.
Capture first proves that the detached evidence uniquely identifies the requested item in its own snapshot. Resolution likewise returns a current evaluation-scoped key only for exactly one compatible candidate. No match fails with TOPOLOGY_MATCH_MISSING; multiple matches fail with TOPOLOGY_MATCH_AMBIGUOUS; malformed references, options, tolerances, limits, and signature capabilities fail with TOPOLOGY_SIGNATURE_INVALID; malformed kernel snapshots fail as KERNEL_ERROR; and an exact fingerprint mismatch fails with TOPOLOGY_FINGERPRINT_MISMATCH. Resource normalization can return TOPOLOGY_SIGNATURE_LIMIT_EXCEEDED before deeper validation of an oversized input. Symmetric topology—including distinct coincident B-Rep vertices with indistinguishable evidence—therefore remains explicitly ambiguous; the protocol never invents identity from enumeration order.
Persistent document selectors support exact B-Rep faces, edges, and vertices. Vertices currently have no semantic roles: they can be selected by position, origin lineage where available, persistence, set algebra, and edge adjacency, but never by an invented corner name. The selectors store evidence, never kernel keys or shapes, and therefore do not themselves provide geometric diffing, incremental feature identity, or cross-run shape caching. Feature-hash and artifact-cache protocols are separate layers described below. The published persistent-topology torture suite records the exact stable, missing, ambiguous, cancellation, and ownership boundaries that the current implementation must pass.
What works today
Modeling
- Dimension-safe length, angle, and scalar expression trees
- Parameters with defaults, limits, overrides, dependency resolution, and cycle detection
- Box, cylinder/cone, and sphere primitives
- Sketches on XY, XZ, and YZ planes
- Points, lines, circles, arcs, polylines, rectangles, and regular polygons
- Explicit outer loops and hole loops
- Extrude, symmetric extrude, and revolve on both kernels
- Ordered, ruled, hole-free solid lofts through parallel principal-plane profiles on the exact OCCT backend
- Explicit open 3D polyline, three-point circular-arc, and ordered exact line/arc composite paths with hole-free solid sweeps on the exact OCCT backend
- Twist and top-scale extrusion through the Manifold mesh backend
- Union, subtraction, and intersection on both kernels, with complete exact face/edge/vertex evolution through the owned OCCT facade ABI 0.4 and later
- Semantic face/edge and role-free vertex set selectors with geometry, adjacency, sketch sources where applicable, and explicit cardinality
- Exact constant-radius edge fillets through the OCCT backend, with complete face/edge/vertex evolution through owned facade ABI 0.5 and later
- Exact constant equal-distance edge chamfers through the OCCT backend, with complete face/edge/vertex evolution through owned facade ABI 0.5 and later
- Exact constant-thickness inward/outward shells with semantic face openings through the OCCT backend, with complete face/edge/vertex evolution through owned facade ABI 0.6 and later
- Exact whole-solid inward/outward offsets with fixed round joins through the OCCT backend, with complete face/edge/vertex evolution through owned facade ABI 0.6 and later
- Exact atomic multi-face draft through semantic face selectors when using the matched owned OCCT facade
- Translation, Euler rotation, nonuniform scale, and mirror
- Parts with part number, description, metadata, backward-compatible material labels, and explicit parameterized mass density
- Document-owned material definitions with typed part references and explicit parameterized density
- Fixed-placement and nested assemblies with shared part definitions
- Named configurations with parameter, assembly-instance suppression, and part-material overrides
- Deterministic variant-aware bills of materials with nested quantity and affine mass rollups
Sketch constraints
The permissively licensed reference solver currently supports coincidence, horizontal, vertical, fixed, distance, X/Y distance, line length, parallel, perpendicular, equal length, angle, radius, diameter, equal radius, midpoint, and line-circle tangency. The reference solver derives an arc’s endpoints from its solved center, solved radius, and authored angles, and no constraint joins an arc endpoint to a line endpoint. Aradius, diameter, or equalRadius constraint that changes a loop arc’s radius, or a constraint that moves its center, therefore leaves the adjacent line endpoints in place and opens the loop, which fails with SKETCH_NO_CLOSED_REGION. Since 0.2.0, that diagnostic’s details give the resolved curve indices on either side of the first gap and the gap size. See arcs in constrained loops for safe ways to drive a loop arc.
The solver API is replaceable. The built-in solver is intentionally a v0.1 reference implementation; industrial conflict isolation, redundant-constraint reporting, drag solving, and large sparse systems remain roadmap work.
Evaluation and interchange
- Reliable manifold-mesh CSG through the bundled, pinned Manifold 3.5.1 core WebAssembly runtime
- Exact B-Rep primitives, analytic profile extrusion/revolution, CSG, and transforms through OpenCascade WebAssembly
- Exact face/edge/vertex topology enumeration, geometry/adjacency descriptors, selected-edge fillets/chamfers, face-selected shells, whole-solid offsets, owned-facade atomic draft, and owned-facade exact multi-input Boolean, edge-treatment, and solid-offset evolution through OpenCascade WebAssembly
- Protocol-v2 detached face/edge/vertex capture and fail-closed resolution, exact protocol-v1 face/edge compatibility, and Document-v2–v6 persistent selector atoms with exact target and profile binding
- Native STEP, text BREP, and binary BREP import/export in the exact-kernel protocol
- Opaque one-body documents, added in 0.2.0, that commit caller-resolved STEP or text/binary BREP bytes by SHA-256 and length, apply a closed format/media/unit policy, and require exact single-solid import without weak or mesh fallback
- Volume, surface area, axis-aligned bounds, genus, kernel tolerance, center of mass, centroidal inertia, principal axes/moments, arbitrary-axis inertia, radii of gyration, and density-aware physical mass properties; the capability-correct nullable genus contract was introduced in 0.2.0, while 0.1.1 reported a numeric genus (see Measurements and mass properties)
- Typed-array mesh extraction
- Binary STL, ASCII STL, and OBJ export
- Canonical JSON serialization and structural/semantic validation
- SHA-256 semantic document hashes
- Context-effective, kernel-independent feature Merkle hashes for incremental invalidation
- Versioned artifact key, record-integrity, bounded-store, optional kernel-codec contracts, and canonical finite semantic-observation protocol v1 (0.2.0 adds the nullable-genus v2 observer and codec audit); a repository-private trusted-store experiment covers direct box outputs, but no shipped backend advertises a codec and no public evaluator option enables reuse
- Structured diagnostics instead of opaque geometry exceptions
- Node and browser runtime support with configurable WASM location
Support matrix
Capabilities are negotiated by backends. InvariantCAD will not silently pretend a mesh operation is exact B-Rep or silently downgrade exact geometry. The current loft contract is deliberately bounded to ruled solids through at least two distinct, ordered, hole-free profiles on parallel planes, with matching directed curve signatures and an authored curve phase that, since 0.2.0, is checked between adjacent sections rather than against the first. The current sweep contract is similarly bounded to simple open polyline paths, one exact circular arc, or a certified ordered line/arc composite, with conservative profile clearance and fixed corrected-Frenet/right-corner semantics. Circular-arc and composite sweeping are separate additive capabilities, so an existing polyline-only kernel fails before evaluating unsupported path dependencies. Composite guarantees beyond the base contract use the versioned
compositeSweep refinement envelope; facade ABI 0.3 introduced major-multiple-arcs and major-eccentric-profile, ABI 0.6 retains them, and stock and older runtimes advertise neither. Document evaluation computes the duplicate-free required refinement set from exact path geometry and certified analytic profile moments, then reports a structured missing-capability or malformed-protocol diagnostic before invoking the backend. kernelSupports remains available for discovery and fails closed on malformed metadata; optional refinement metadata is irrelevant when the selected geometry requires no refinement. Direct OCCT calls use the same classifier and requested feature tolerance. Draft requires both the ordinary draft feature and exactIndexedTopologyEvolution v1 scoped to draft. For Boolean, fillet, chamfer, shell, and offset, that exact capability is optional: ABI 0.4 introduced it for Boolean, ABI 0.5 added fillet and chamfer, ABI 0.6 adds shell and offset, malformed metadata fails as a protocol violation, and kernels without the feature-scoped promise continue through their supported partial-history paths.
Measurements and mass properties
measure() returns the kernel-neutral ShapeMeasurements contract. Its centerOfMass is a world-coordinate Vec3, or null for an empty or zero-volume result. Its inertiaTensor is a required readonly [Vec3, Vec3, Vec3]: the three rows of the symmetric centroidal tensor in world axes,
r is measured from the center of mass, for a homogeneous solid with unit volumetric density. Lengths are millimetres, so the tensor is in mm^5. Empty and zero-volume results use a zero tensor.
Since 0.2.0, genus is number | null and never guessed. A number is the exact sum of the connected-component genera in the backend representation; null means unsupported and is never exact zero. Manifold advertises the exact-per-connected-component measurement capability and sums each validated mesh component. Stock and owned OCCT advertise unsupported and return null, and assembly aggregates also return null. Discover support with inspectKernelMeasurementCapabilities(...) or kernelSupports(capabilities, "measurement", "genus"). 0.1.1 typed genus as number and had neither discovery API: OCCT returned a heuristic integer derived from the Euler characteristic, Manifold returned the genus of its whole mesh (negative for disjoint bodies), and assemblies returned 0.
The public analysis functions operate only on copied numeric properties, so they are backend-neutral and remain usable after the evaluated shape is disposed:
principalInertia() uses a deterministic symmetric decomposition. It returns an orthonormal, right-handed world-space frame, ascending moments, per-axis uniqueness, and explicit distinct, minimum-repeated, maximum-repeated, or isotropic degeneracy. Axis directions inside a repeated eigenspace are deterministic but are not physically unique. worldRadiiOfGyration() reports sqrt(Ixx / weight), sqrt(Iyy / weight), and sqrt(Izz / weight); principalRadiiOfGyration() follows ascending principal moments. inertiaTensorAboutPoint(), momentOfInertiaAboutAxis(), and radiusOfGyrationAboutAxis() apply the parallel-axis theorem to arbitrary world-space points and lines. Zero-weight radii are null.
Physical density is explicit authored data and is never inferred from a material name. A document can own reusable material definitions, each with required density, and a part refers to one by stable ID through the typed materialRef authoring option. The legacy part material string remains a backward-compatible descriptive label only: matching it to a material definition’s ID or name does not establish a reference and never supplies density. A part uses either that label or materialRef, never both. part(...) options are limited to partNumber, description, material, materialRef, massDensity, and metadata; since 0.2.0 any other own field, enumerable or not, throws TypeError, even when the options come from a variable that TypeScript’s excess-property check skips.
Documents store density in kg/mm^3; helpers accept the common forms kgPerCubicMillimeter(), kgPerCubicMeter(), and gramsPerCubicCentimeter(). Documents containing a density expression declare units.mass: "kg", physical inertia is in kg*mm^2, and a definition’s density may be parameterized and overridden like any other dimensioned expression:
massDensity is an explicit per-part override; otherwise the effective material definition’s massDensity is used; otherwise density is missing. With a selected configuration, its partMaterial substitution determines that effective material instead of the part’s authored materialRef, but an explicit part massDensity still wins over the substituted material’s density. Neither a legacy material label nor a definition name participates in resolution. Material IDs and references are document-owned, so a reference created by another builder is rejected instead of being silently rebound by text.
EvaluatedPart.physicalMassProperties() and EvaluatedAssembly.physicalMassProperties() return CadResult<PhysicalMassProperties>. An active part without density produces MASS_DENSITY_MISSING; a finite calculation that cannot produce representable, mechanically valid properties produces MASS_PROPERTIES_INVALID. Suppressed occurrences do not require density. A zero, negative, or non-finite resolved density is not reported by these methods; it fails evaluation itself with MASS_DENSITY_INVALID. Density parameters (/parameters/<id>) and material definitions (/materials/<id>/massDensity) are resolved eagerly, so an invalid one fails the whole evaluate(...) call even when only an unrelated output is selected or the only part that uses it is suppressed. A part’s own massDensity expression is resolved when that part is evaluated and fails at /nodes/<id>/massDensity. Assemblies transform each leaf’s volumetric properties through its complete affine placement, multiply by that leaf’s own density, then use mass weighting and the parallel-axis theorem. Repeated definitions count once per occurrence, overlaps remain additive bodies, and an empty assembly returns zero mass, a null center, and a zero tensor. For a raw evaluated solid, call physicalMassProperties(output.measure(), numericDensity) explicitly.
OCCT obtains these properties from its native B-Rep integration with recentered accumulation. Manifold integrates the centered closed polyhedron emitted by its mesh kernel. That representation boundary is intentional: mesh values describe the emitted polyhedral solid, while OCCT values describe the exact B-Rep, so compare cross-kernel results with an appropriate modeling or meshing tolerance rather than expecting identical floating-point values.
Assemblies
Modeling transforms create new geometry. Assembly placements create occurrences and preserve the shared part definition:frame/left-bracket during evaluation while retaining the original part node. Assembly volume, center of mass, and inertia aggregate every placed occurrence under its affine transform and apply the parallel-axis theorem, so shared definitions contribute independently at each placement and overlapping occurrences each count in full. Since 0.2.0, surface area, bounding box, and tolerance also come from the occurrence kernel measurements. Area is the kernel area times the squared scale under rigid, mirrored, and uniformly scaled placements; only a nonuniformly scaled or sheared occurrence uses the area of its placed default tessellation. The bounding box encloses every placed kernel bounding box, so it is exact for axis-aligned placements and may be larger than the tight box for rotations that are not quarter turns. tolerance is the largest occurrence kernel tolerance times its placement’s magnification. 0.1.1 took area and bounds from the merged default tessellation and reported tolerance 0. See assembly aggregation.
Since 0.2.0, public evaluation also charges three fixed ceilings before it allocates occurrence records: maxActiveOccurrences (100,000 records across one evaluation), maxAssemblyDepth (64 nesting levels), and maxOccurrencePathSegments (1,000,000 stored instance-path segments). Suppressed instances are skipped before they are charged. Crossing a ceiling fails with RESOURCE_LIMIT_EXCEEDED at the assembly node and instance that would exceed it, with details.resource, details.limit, and details.actual; see occurrence ceilings. EvaluatedAssembly.instances and every occurrence transform are frozen copies, and outputs that name the same assembly node share one copy.
Parts and assemblies also expose a deterministic bill of materials:
items are grouped by stable part-node ID, not by mutable or potentially duplicate part numbers, descriptions, or material names. Each item reports partNode, nullable partNumber, description, effective materialId and material, quantity, sorted flattened occurrenceIds, resolved massDensity and massDensitySource, base definitionMass, and occurrence-aware totalMass. A directly evaluated part produces definition quantity one and an empty occurrence-path list. Nested assemblies contribute their active leaf occurrences, while authored or configuration-suppressed branches contribute neither quantity nor mass.
BOM mass is physical occurrence mass. Rigid placements and reflections preserve the definition mass; affine scaling changes occurrence mass by its volume scale, so an item’s mass rollup need not equal definitionMass * quantity. Occurrences remain additive even if their geometry overlaps. If any active item lacks density, the BOM still succeeds with warning diagnostics and exact quantities: massComplete is false, knownMass contains the sum of computable occurrence masses, and totalMass is null. An empty assembly has zero quantity, complete zero mass, and no items. The BOM’s configurationId is the selected name or null for the base design, so a stored BOM always identifies its variant. Effectivity and alternate/substitute components remain roadmap work.
Named configurations
A configuration is a document-owned, named set of explicit overrides. It can replace parameter expressions, suppress or re-enable a direct instance in an assembly definition, and substitute a material for a part definition:parameters > selected configuration parameterOverrides > authored parameter defaults. The resolved values in EvaluatedDesign.parameters are therefore the effective values used by geometry, material-density expressions, and placements.
Suppression and material overrides target definitions, not flattened occurrence paths. instanceSuppressed(product, "right") addresses the authored right instance directly inside the product assembly node; if that assembly definition is reused, the override applies to every occurrence of the definition. The third argument defaults to true, so omitting it suppresses the instance; pass false to re-enable an instance authored with suppressed: true. Any override replaces the authored value, which only an instance without an override keeps. partMaterial(part, steel) similarly affects every occurrence of that part definition. A selected material replaces the authored material reference for reporting and density lookup, while an explicit density authored directly on the part remains the highest-priority density source.
In canonical document IR, the optional top-level configurations registry is keyed by stable configuration ID. Each entry contains one or more of parameterOverrides, instanceSuppressions, and partMaterialOverrides, plus optional description and metadata. Suppression is encoded as assembly-node ID -> direct instance ID -> boolean; material substitution is part-node ID -> material ID. Documents without named configurations omit the registry entirely, preserving their existing serialization and semantic hash.
Documents and deterministic builds
build() emits detached, deeply frozen JSON data. Invalid direct calls throw TypeError or RangeError rather than producing a document; a regularPolygon rotation, for example, must be a literal angle. See programmer errors for the complete list. 0.2.0 tightens these checks. It accepts only handles returned by builder methods, so a SolidRef or other handle created with its exported constructor throws even when it names the builder as owner. Assembly, configuration, and sketch builders close when their callbacks return or throw, so a later call through an escaped builder throws, and a nested configuration(...) call with the ID of a configuration whose callback is still running throws. Option records, and the lists and records that reach a document, are read once through their own data properties. A record contributes its own data fields, enumerable or not, and inherited fields are ignored. An accessor among its own fields, a record with more than 1,000,000 own fields, or a record that is not a plain object throws without invoking any property getter. Structural checks on a Proxy can still run its traps. Other new programmer errors cover parameters from another design used inside an expression, non-boolean flags, segments or divisions outside the document grammar’s bounds, sketch and path tolerances that are not positive finite numbers, metadata that is not plain JSON data, part(...) option fields outside its allow-list, and topology selections that are cyclic, have more than the default maxTopologyQueryNodes of 100,000 query nodes or maxStructuralValues of 1,000,000 values, or nest deeper than the default maxNestingDepth of 128.
Authored change impact
analyzeDesignImpact(...) provides a deterministic, kernel-free answer to “what authored definitions can be affected if these IDs change?” Seed one or more existing nodes, parameters, materials, configurations, or persistent topology references:
IR_INVALID, and an unknown seed reports REFERENCE_MISSING, PARAMETER_MISSING, or CONFIGURATION_MISSING at its /changes path. Since 0.2.0, a seed is checked only for membership in the parsed document’s inventory, so any stored node or parameter key, including a key that a frozen document preserves outside the builder ID grammar or an empty key, can be seeded, and an unknown grammar-external seed reports the same missing-item codes. 0.1.1 first checked seeds against the shared ID grammar and rejected a grammar-external seed with IR_INVALID, even when the document stored that key. The report returns branded IDs. The analysis deduplicates and sorts the seeds, follows parameter expressions and bounds, material density, configuration overrides, persistent selectors, and feature-DAG dependencies, and returns deeply frozen sorted inventories for impacted parameters, materials, configurations, nodes, and outputs.
The base design and every named configuration that changes evaluation are propagated independently, then unioned existentially: an entry is reported when it can be affected in at least one current evaluation context. Dependency paths never jump between mutually exclusive configurations. Parameter overrides replace default-expression edges only in their own context while bounds remain active; part-material overrides select the effective material; and suppressed assembly instances contribute neither component nor placement dependencies until a configuration enables them. Every impacted node carries deterministic reasons and a direct flag. direct: true means the node is reached directly in at least one context; downstream-only nodes report direct: false.
AnalyzeDesignImpactOptions.limits uses the same partial DesignDocumentLimits contract as parsing. maxStructuralValues additionally caps context-qualified propagation work and returns a structured IR_INVALID diagnostic instead of allowing a configuration-by-graph product to grow without bound.
This is conservative propagation over the current authored dependency graph, not a comparison between two documents. An ID seed does not identify which field changed, and v1 cannot predict newly added or deleted definitions or dependencies. It does not inspect or compare B-Reps, validate a cached artifact, infer persistent topology identity, or claim that a kernel will produce geometrically different output. The separate feature-identity and artifact protocols below do not change those limits.
Incremental feature identity and shape-artifact foundation
hashDesignFeatures(...) computes feature-hash protocol v1 for one effective evaluation context without invoking a geometry kernel. It applies the evaluator’s configuration and call-time parameter precedence, removes effectively suppressed assembly instances, applies effective part materials, canonicalizes commutative selector logic, includes only consumed persistent-reference evidence, and builds a SHA-256 Merkle hash from each node’s local intent, resolved direct parameter values, ordered direct dependencies, and dependency hashes:
HashDesignFeaturesOptions.signal, and bounded by FeatureHashLimits; DEFAULT_FEATURE_HASH_LIMITS caps feature nodes, direct dependency links, and canonical bytes hashed. A pre-aborted signal returns structured EVALUATION_ABORTED. A configuration name is reported as context but is not itself salted into every node: two contexts with the same effective v1 intent can deliberately produce the same feature hash. An isolated change admitted by protocol v1 changes that feature and its Merkle descendants while unrelated branches retain their hashes.
A tagged FeatureHash proves only equality of the bytes admitted by effective-intent protocol v1. It is not a B-Rep digest, geometric-equivalence proof, topology-identity proof, kernel result attestation, or sufficient cache key. In particular, it does not encode a kernel runtime or the sketch solver that will turn the intent into geometry.
The optional kernel-shape artifact protocol supplies the stronger compatibility envelope required before such an intent hash can participate in a cache key. A supporting GeometryKernel must advertise KernelCapabilities.shapeArtifacts and implement both encodeShapeArtifact(...) and decodeShapeArtifact(...). inspectKernelShapeArtifactSupport(...) distinguishes absent, malformed, and complete declarations. Native STEP/BREP import and export do not imply this capability.
createKernelShapeArtifactCacheKey(...) accepts an ArtifactCacheFeature—the node, outputKind, and hash projection of a DesignFeatureHashEntry, so a full report entry can be passed directly. It requires outputKind: "solid" and a tagged protocol-v1 feature hash, then binds that entry to the artifact-cache and evaluator-semantics versions, kernel ID, backend-owned artifact format/version/compatibility fingerprint, and sketch-solver ID plus artifactCompatibilityFingerprint. A topology-signature descriptor fingerprint is intentionally insufficient and is never substituted for the artifact fingerprint.
Solver artifact compatibility is fail-closed. The optional fingerprint claims exact cross-run numeric semantics across every runtime represented by that value, not merely the same solver class or algorithm name. The built-in reference solver deliberately omits it because its floating-point nonlinear implementation has not established that cross-runtime guarantee; a solver without a fingerprint cannot produce an artifact key.
The record/store layer then provides createArtifactCacheRecord(...), validateArtifactCacheRecord(...), readArtifactCacheRecord(...), writeArtifactCacheRecord(...), deleteArtifactCacheRecord(...), bounded ArtifactCacheLimits, and a copying MemoryArtifactCacheStore. Records carry exact key material, detached payload bytes, byte length, and a SHA-256 integrity digest. The store contract publishes one complete record or nothing; a custom store that knows an entry exceeds context.maxBytes throws ArtifactCacheStoreLimitError before materializing its payload. Integrity detects corruption or misrouting, not geometric correctness or authenticity. Encoding retains ownership of the source shape and returns fresh caller-owned bytes. Decode input is borrowed and cannot be mutated or retained; successful decode returns one new live current-kernel shape owned by the caller. Both codec directions must enforce maxArtifactBytes, observe cancellation, and clean up partial work before failing.
The standalone store helpers apply operation and total-byte ceilings to that one call. createArtifactCacheSession(...) adds concurrency-safe aggregate accounting across calls: it serializes one session’s reads, writes, and deletes, enforces cumulative maxOperations, maxTotalReadBytes, and maxTotalWriteBytes, exposes frozen usage, supports read-only/write-only modes, resolves queued cancellation promptly without allowing later work to overtake the active store call, and isolates synchronous or asynchronous event-listener failures from cache correctness.
Key metadata is itself bounded before canonical hashing. Node IDs admit at most 1,024 canonical UTF-8 bytes, protocol/runtime identities 256, solver and codec compatibility fingerprints 2,048, and the complete canonical key material 16,384; unpaired UTF-16 surrogates are rejected rather than silently replaced. ARTIFACT_CACHE_MAX_KEY_MATERIAL_BYTES and KERNEL_SHAPE_ARTIFACT_MAX_COMPATIBILITY_FINGERPRINT_BYTES expose the aggregate and codec-fingerprint ceilings. Record creation validates and copies a non-SharedArrayBuffer payload before its first await, closing immediate caller-mutation races. The session’s package-private encode/write transaction reserves one serialized operation, passes the codec the exact smaller of the entry and remaining aggregate-write budgets, copies the admitted bytes, charges them conservatively, hashes once, and publishes one record.
The repository also contains an unexported OCCT evaluator binding for one intentionally narrow integration milestone. Repository tests must pass { trust: "trusted" }, a tenant-isolated store, and a sketch solver with an explicit artifact-compatibility fingerprint. The binding is not reachable from any package entry point and does not add cache fields to CreateEvaluatorOptions or EvaluationOptions. The evaluator entry point and session coordinator are ECMAScript # private at runtime, session construction requires an unexported token, and the public session projection is frozen; packed-package checks reject both internal subpaths and require the public prototypes to omit every coordination method. It caches only a requested solid output whose referenced node is directly a box; a transform of a box, any dependency-bearing feature, and every other output follow ordinary evaluation.
Before a cache operation, the private path performs the same box-capability and positive-dimension validation as an uncached evaluation. It snapshots one detached document plus effective configuration, parameters, outputs, signal, empty-result policy, and topology limits before awaiting; uses a fresh cache session per evaluation; and rejects overlapping evaluations or evaluator disposal while active. A miss models, validates, measures, encodes, and writes. A hit decodes a fresh current-kernel shape and runs the same ownership, status, measurement, topology, and empty-result checks. Integrity or decoder rejection in read-write mode emits invalidation, deletes, recomputes, and rewrites. Read-only poison, failed eviction, store, codec, or write failure is strict and cleans every acquired shape. Fixed metadata ineligibility bypasses this private optimization and models normally. This limited boundary avoids suppressing dependency diagnostics; broader transparent caching needs a versioned diagnostic/topology-policy transcript.
The framework-neutral runtime audit is available from the dedicated invariantcad/conformance entry point through frozen-v1 auditKernelShapeArtifactCodec(...). The explicit-v2 auditKernelShapeArtifactCodecV2(...) entry point was added in 0.2.0; the 0.1.1 package exported only the v1 audit. Candidate mode exercises an explicitly supplied, not-yet-advertised codec while requiring the production kernel capability to remain absent; advertised mode audits the codec already exposed by a fresh production kernel and fails on an absent or incomplete declaration. Both modes compare the exact kernel ID, artifact protocol, format, format version, and compatibility fingerprint. Since 0.2.0, both audits also apply the artifact cache’s canonical UTF-8 identity rules to the expected kernel ID, the expected format, and a candidate codec’s declared format: each must be nonempty, well-formed UTF-8 of at most 256 bytes, or the audit fails with ARTIFACT_CACHE_ENTRY_INVALID. The audits then exercise exact semantic witnesses and decode-only golden artifacts before current self-encodes. Each self-round-trip also uses a dedicated fresh producer and fresh consumers for a separately created pre-witness source: the audit encodes it before calling status or witness code, then decodes, observes, and exercises both disposal orders cross-instance. The reviewed source factory must itself avoid observation; the black-box harness cannot enforce its internals or process-global coldness. This pre-witness claim covers the positive full-limit round trip and ownership checks; reduced-limit, cancellation, and malformed-input checks run on the ordinary observed paths. The harness checks live status, ownership and mutation isolation, fresh-instance decoding, empty/truncated input, returned-byte ceilings, and pre-abort behavior. It never wraps or mutates a kernel to manufacture support.
Repository release witnesses can use frozen semantic-observation protocol v1 through observeKernelShapeSemantics(...), encodeKernelShapeSemanticObservation(...), and hashKernelShapeSemanticObservation(...). Explicit protocol v2 through the corresponding ...V2 functions was added in 0.2.0; the 0.1.1 package exported only v1. V1 retains its original field shape, canonical bytes, hash domain, and exact-genus requirement. Because stock and owned OCCT report genus as null since 0.2.0, v1 observation of an OCCT shape fails with KERNEL_CAPABILITY_MISSING; the 0.1.1 observer accepted OCCT’s heuristic numeric genus. V2 adds only the ability to preserve unsupported genus as null, uses a distinct capture brand and witness prefix, and has a separate auditKernelShapeArtifactCodecV2(...) entry point; no v2 witness is admitted by the v1 audit. Each audit admits any digest in its own witness domain and checks only the domain tag and digest shape: it cannot verify how a witness was produced. A v1 witness can come from hashKernelShapeSemanticObservation(...) or from hashKernelShapeArtifactSemanticWitness(...) over caller-canonicalized material; that helper always uses the v1 domain, so a v2 witness comes from hashKernelShapeSemanticObservationV2(...). The helper hashes a string as its exact UTF-8 bytes; since 0.2.0 it rejects a string containing an unpaired UTF-16 surrogate with ARTIFACT_CACHE_ENTRY_INVALID, where 0.1.1 substituted U+FFFD. Both observers record bit-exact normalized IEEE-754 measurements and options, sorted oriented Float32 triangle multisets, and—when supported—an exact bounded canonical topology incidence graph with ephemeral keys removed. A reviewed plan must account for every advertised feature with a downstream probe or a non-applicability reason. Successful native imports and probe results transfer as new observer-owned shapes, are observed and disposed, cannot alias the borrowed source, and are followed by source re-observation. Exact canonical-size preflight, mesh/topology materialization guards, a separate graph-work budget (maxCanonicalWork, which since 0.2.0 also charges the canonical bytes of every topology-labeling search leaf after the first), and one-time snapshots of hostile accessor-backed inputs strengthen the resource and validation boundary. Asynchronous probes are abort-raced: same-realm built-in Promises preserve queued fulfillment-before-cancellation ownership, while custom or cross-realm PromiseLike results transfer only when delivered before the signal is aborted. Synchronous TypeScript and native work does not yield to same-thread timer cancellation and requires ceilings, a worker/process timeout, or backend instrumentation. Trusted probes must honor the derived-shape allowance; it is not a sandbox for arbitrary callback code.
The observation is an exact normalized evaluator-semantic quotient for one protocol and reviewed plan. It intentionally normalizes mesh enumeration, cyclic triangle origin, negative zero, and topology keys while preserving triangle winding/multiplicity, exact numeric bits, history, lineage, sketch sources, and incidence. It is finite corpus evidence—not native representation identity, certification, a cross-process proof, cached-shape validation, or a cache-eligibility token. See Kernel shape-artifact conformance for the API, complete limits, corpus requirements, and stronger backend gates.
The repository contains a private OCCT artifact candidate for conformance development. Format v3 retains binary BREP and bounded canonical sidecar v2, then adds native identity v1 in a separate envelope section. Sidecar v2 keeps key-neutral topology structure and incidence, root/subshape type-orientation evidence, lineage, complete/partial history, and analytic volume overrides. Its fixed 48-byte big-endian header declares exact sidecar length and aggregate face, edge, vertex, adjacency, lineage, UTF-16BE string-byte, and native-orientation totals; the enclosing private envelope caps its compatibility fingerprint at 2,048 UTF-8 bytes. Strings retain arbitrary JavaScript UTF-16 code units; geometry uses finite canonical big-endian binary64; enum tags and presence masks are closed. Encode detaches and canonicalizes once, counts the complete representation before allocating, then writes exact-size sections. Decode preflights every header, aggregate limit, and conservative minimum representation before topology-table allocation, charges nested counts against declared totals, requires canonical sorted unique in-range collections and exact EOF for the envelope, sidecar v2, and identity v1, and never creates the former JSON string/object/re-encoded copy. After the sidecar decode, the topology remap re-enforces only the topology-item, adjacency-link, and lineage-record ceilings and applies no second string budget to topology keys, so an artifact within the declared sidecar ceilings is never rejected for size at that point. Stock occt-wasm can nevertheless accept suffix bytes after a valid native BREP archive, so strict EOF inside the BREP section is an owned-ABI-0.7+ guarantee; a stock decode and canonical re-encode can discard the suffix.
Native identity v1 records the zero-based direct-child path from the serialized root to the first IsSame occurrence of each unique located solid, shell, wire, face, edge, and vertex. It also carries a complete rooted direct-child pre-order stream of every serialized occurrence. Each fixed 12-byte occurrence record contains shape type, composed orientation, direct-child count, and the canonical IsSame class index for those six kinds; compound, compsolid, and generic-shape nodes are structurally recorded but unindexed. Producers sort first paths lexicographically per kind and use the same permutation for all native orientations, face/edge/vertex topology arrays, and occurrence class indices, making the sidecar’s incidence indices and occurrence references canonical as one joint state. Consumers capture the restored runtime’s raw order, map each stored path to its fresh index, exact-compare occurrence count and every record by canonical class path, verify the identified faces, edges, and vertices, and only then restore semantic records onto fresh keys. Incidence, surface and curve kinds, and vertex points must match exactly. Stock binary BREP is not bit-exact for derived values, so derived areas, lengths, centers, bounds, normals, axes, directions, and radii must match within 64 units in the last place of the larger of the native value’s magnitude and the restored item’s own native scale: its largest bounds coordinate for centers and bounds, its largest bounds side for lengths and radii, the product of its two largest bounds sides for areas, and 1 for unit vectors. The decoded shape reports the stored descriptors, not the recomputed ones. Multiplicity, order, orientation, IsSame-class membership, shape type, child count, geometry, incidence, or root-structure substitution fails closed.
The identity codec has a 64-byte header and permits at most 100,000 unique paths, 1,000,000 aggregate first-path components, depth 64, child index 999,999, 100,000 stored occurrences/traversal visits, and 1,000,000 candidate IsSame comparisons. The compatibility fingerprint binds format v3, sidecar v2, nativeIdentity=serialized-first-issame-child-path-v1, nativeOccurrenceManifest=complete-rooted-preorder-type-orientation-child-count-issame-class-v1, nativeOccurrenceRecordBytes=12, nativeIdentityMaxOccurrences=100000, nativeIdentityTraversalOccurrences=100000, every other identity ceiling, the native-structure contract, runtime/options, and materialization profile. Within one artifact this identifies unique located IsSame classes for all six indexed kinds and preserves every occurrence’s rooted structure, order, type, composed orientation, multiplicity, and class membership. Compound/compsolid/generic nodes are structural occurrences rather than public indexed identities. Because stock occt-wasm exposes no IsPartner, v3 cannot attest that distinct-location IsSame classes share an underlying TShape. Its serialized paths are not cross-edit topology IDs or persistent assembly identities.
The deterministic stock-runtime v3 asymmetric-box golden is 13,735 bytes with fixture witness invariantcad:kernel-shape-artifact-fixture:v1:sha256:4279e9f76ab1e41dae47b28aea9c426ffa8b5f329ab624f137c65f6881e23918; its current semantic witness, in the semantic-observation v2 domain, is invariantcad:kernel-shape-semantic:v2:sha256:b99dd9c39b950700dd22c8be6255db6e816e1a51668f415bf84b80c4c200d588. The move from semantic v1 to v2 records OCCT genus as unsupported null; it does not change the v3 artifact bytes or fixture witness. The committed v1 and v2 artifact fixtures are negative-only and must be rejected before native restoration. Run pnpm artifact:fixture:occt -- --check --version v3 to reconstruct and verify the reviewed bytes without writing. Direct state/corruption/ownership tests, a producer/consumer reversed-enumeration case, a duplicate-occurrence substitution regression with transactional cleanup, and the candidate-mode audit remain finite non-certifying evidence. Owned facade ABI 0.8 retains ABI 0.7’s capped chunked BinTools-v4 writer and bounded-input report-owned reader with one-shot same-kernel transfer, then adds a fixed 128 MiB cumulative native allocation-request limit and report telemetry around both calls. ABI 0.9 retains that boundary and adds exact structural parsing of the owned writer’s BinTools-v4 profile before OCCT deserialization under 1,000,000 work units, 64 nesting levels, and location-power magnitude 1,000,000. It validates canonical geometry, count products, representations, locations, and the backward-only TShape hierarchy/root/reachability. Bounded TShape metadata is charged to the native quota, while conservative squared aggregate geometry, representation, expanded-topology, wire, and face validation envelopes are charged to work. Global geometry-work squaring deliberately admits roughly fewer than 1,000 geometry work units under the shared cap, with other charges reducing it; this is a private artifact-compatibility limit, not a core modeling limit. Reports expose the echoed limits, used work, maximum depth/location power, consumed bytes, preflight code/completion, deserializationStarted, and native request/allocation telemetry. ABI 0.8 remains loadable without those fields; ABI 0.7 retains bounded transport without the private request quota; stock and older owned runtimes retain unbounded native materialization, although their v3 envelope uses bounded sidecar v2 and native identity v1. The codec is reachable only through a module-private registry keyed by kernel identity: there is no symbol hook, and nothing on the kernel object or its prototype chain exposes the candidate operations to reflection. It is absent from every exported package entry point and ordinary GeometryKernel surface, and does not advertise shapeArtifacts.
Repository-private isolation gates now reach real evaluator work. Fresh owned-ABI-0.9 Node children run Evaluator.evaluate(...) over a deterministic two-box Boolean union. Their versioned protocol requires exact operation-started then kernel-operation-started events for successful evidence; the second marker proves entry into the evaluator-invoked native Boolean path. The wrapper emits a third exact non-yielding-stall-started marker only after the real Boolean returns and immediately before blocking. Timeout requires that third marker, while abort waits for it before sending SIGKILL; both await child close and recover with identical detached document, measurement, complete-topology, and runtime evidence in a fresh child. The parent rejects incomplete nonempty event prefixes, while a failure before operation start legitimately emits none. Success is emitted only after evaluated-design and evaluator cleanup, and an injected cleanup failure cannot produce successful evidence.
Process protocol v3 separately proves one private direct-box cache handoff between fresh verified owned-ABI-0.9 children. Two producer processes must independently record miss,write, perform one native 2 × 3 × 5 box, observe encode but not decode, and emit byte-identical records plus identical detached evidence. A compatible read-only consumer must record hit, perform zero native box calls, observe decode but not encode, and reproduce complete measurements and topology. A different solver fingerprint must derive a different key, miss, model once, and invoke neither codec direction.
The record crosses processes in an exact binary frame: an 8-byte versioned magic, a little-endian 32-bit header length, a closed-field JSON header written with plain JSON.stringify under a 32 KiB ceiling, and the exact remaining payload. The header’s key order is not checked; integrity comes from artifact-cache record validation and SHA-256. The frame checks run in the consuming child’s decodeCacheRecordTransfer, not in the parent runner: fatal UTF-8, closed header and integrity fields, protocol and digest shape, request-specific byte limits, payload length, and SHA-256, after which the record passes artifact-cache key, metadata, and integrity validation. The parent bounds the record it reads back and matches its byte length and SHA-256 against the child’s evidence. Caller input is snapshotted before child creation; shared and hostile views, payload tamper, forged key/metadata, and post-start abort fail before a later fresh consumer proves recovery. Evidence identifies the boundary as trusted-parent-mediated-record, sets recordIntegrityAuthenticated: false, and retains the compatibility and operational-cancellation non-claims.
The Chromium production-bundle gate runs the real evaluator over a stock-OCCT 2 × 3 × 7 box. In the normal worker, the private binding records miss,write and one native box construction on the cold evaluation, then hit, zero additional box constructions, and exact measurement/topology/diagnostic parity on the warm evaluation. The public kernel still exposes no artifact capability or codec method, and cleanup completes before detached evidence is posted. Its separate unbound stall wrapper completes the native box before emitting kernel-operation-started and blocking. Deadline and post-start abort request Worker.terminate(), and a fresh worker reproduces the successful scalar evidence exactly after normal cleanup. Because browser termination returns void, this proves the request and fresh-worker recovery rather than observed exit. Any killed realm cannot execute language-level cleanup; destroying the one-shot process or worker is the containment boundary.
This is still a repository-only evaluator experiment, not a public operational cache or isolated evaluator API. Neither Manifold nor the stock or owned OCCT adapters currently advertises shapeArtifacts; an evaluator created and used only through exported APIs never reads, decodes, writes, or evicts these records. The low-level functions and conformance audit do not call a codec automatically. In the vendored, digest-pinned manifold-3d 3.5.1 runtime, public Float32 Mesh reconstruction of a 1 × 2 × 3 box translated by 0.1 on X changes its X bounds from [-0.4, 0.6] to [-0.4000000059604645, 0.6000000238418579] and its volume from 6 to 6.000000178813934; restoring the source tolerance with setTolerance(...) does not recover either loss. ABI 0.7 fixes pre-ceiling native output materialization and transactional transfer, ABI 0.8 adds private cumulative native allocation-request denial and telemetry, ABI 0.9 closes the exact owned-profile grammar/count/product preflight gap, sidecar v2 closes intermediate JSON amplification, and candidate v3 removes raw producer/consumer enumeration order while verifying the complete rooted occurrence manifest. The Node/browser attested loaders verify an exact owned JavaScript/WASM pair against an independently pinned canonical release manifest, and that pair identity plus every v3 identity/occurrence/version/limit declaration is bound into the private artifact compatibility fingerprint. The declared-build identity records the manifest but does not authenticate build execution or a publisher. The 128 MiB cumulative-request counter and structural-work envelope are not a live/peak-memory proof, and record SHA-256 is not authenticity against a store allowed to replace both payload and digest. Ordinary public Evaluator.evaluate(...) remains same-thread and observes its signal only at the stage boundaries described under Diagnostics, including the event-loop yields during which evaluator.dispose() throws. These gates do not provide public cache configuration, shapeArtifacts advertising, hard cancellation in a public API, operational-cancellation or compatibility certification, public compound/compsolid identity classes, distinct-location IsPartner/shared-TShape proof, cross-edit or persistent assembly identity, or a reviewed cross-platform matrix. Production still requires a public operational isolation boundary wherever hard cancellation is promised, reviewed owned-runtime cross-process goldens, and expansion from the private direct-box slice to a public diagnostic-preserving evaluator contract.
The current authoring API emits DesignDocumentV6. parseDocument, parseDocumentValue, stringifyDocument, cloneDocument, hashDocument, validation, and evaluation preserve a supplied v1, v2, v3, v4, v5, or v6 document. migrateDocument validates and upgrades v1–v5 to v6 and is idempotent for v6.
Since 0.2.0, these general document APIs reject Document v7, including imported-body JSON. validateDocument, parseDocument, parseDocumentValue, migrateDocument, hashDesignFeatures, analyzeDesignImpact, and Evaluator.evaluate return one IR_INVALID diagnostic at /; stringifyDocument, hashDocument, and cloneDocument throw TypeError. In 0.1.1, validateDocument, stringifyDocument, and hashDocument accepted such JSON. The public imported-body workflow writes a fixed minimal Document v7 subset, frozen by tests, that only parseImportedBodyDocument reads and only Evaluator.evaluateImportedBody evaluates; see imported-body document format. The general Document v7 grammar remains repository-staged and is not exported. V1 cannot contain a topology-reference registry or persistent selector atom; v2 can but retains the pre-loft role vocabulary; v3 adds loft roles but rejects sweep and edge-treatment roles; v4 adds the six sweep roles but rejects fillet.face.blend and chamfer.face.bevel; v5 adds exactly those two face roles while remaining face/edge-only; and v6 adds persistent vertices, vertex position(...), and edge↔vertex adjacency without adding semantic vertex roles. V1–v5 remain frozen and directly evaluable. Migration never rewrites a stored protocol version, descriptor fingerprint, lineage, geometry, or adjacency record. The document schema version is independent of the npm package version, primary OCCT topology descriptor @6, protocol-v1 compatibility descriptor @4/@5, owned facade ABI 0.9, exact-evolution protocol v1, persistent-reference protocol v1/v2, feature-hash protocol v1, artifact-cache protocol v1, kernel-shape-artifact protocol v1, shape-artifact audit protocol v1 (v2 added in 0.2.0), semantic-observation protocol v1 (v2 added in 0.2.0), and artifact evaluator-semantics version. ABI 0.9 retains ABI 0.6’s modeling/history surface, ABI 0.7’s bounded artifact transport, and ABI 0.8’s private cumulative native allocation-request budget, then adds exact owned-profile structural preflight only for the repository-private candidate.
Parsing first captures a bounded, detached snapshot before recursive schema validation and freezing, so accessors and proxies cannot change the value after its limits were checked. For Documents v1–v6 the snapshot reads each accessor once and keeps its value, copies only enumerable own string keys, and copies an Array subclass instance into a plain array; only the repository-staged Document v7 snapshot rejects accessor, non-enumerable, and symbol properties and non-intrinsic arrays. ParseDocumentOptions.limits can override the exported frozen DEFAULT_DESIGN_DOCUMENT_LIMITS ceilings for UTF-8 bytes, structural occurrences (including shared aliases), nesting depth, actual selector-query nodes, registry entries, variants, stored adjacency links, and lineage evidence. parseImportedBodyDocument and the staged Document v7 parsers honor the same raised limits, including while checking the imported-body subset. Sparse arrays, cycles, non-JSON object instances, unknown versioned persistent fields, and malformed or oversized stored evidence fail as structured IR_INVALID diagnostics.
Diagnostics
Evaluation returnsCadResult<T>:
REFERENCE_MISSING, GRAPH_CYCLE, PARAMETER_OUT_OF_RANGE, CONFIGURATION_MISSING, MASS_DENSITY_INVALID, MASS_DENSITY_MISSING, MASS_PROPERTIES_INVALID, SKETCH_OVER_CONSTRAINED, EMPTY_RESULT, KERNEL_CAPABILITY_MISSING, TOPOLOGY_SELECTION_MISSING, TOPOLOGY_SELECTION_AMBIGUOUS, TOPOLOGY_HISTORY_UNAVAILABLE, ARTIFACT_CACHE_ENTRY_INVALID, ARTIFACT_CACHE_LIMIT_EXCEEDED, ARTIFACT_CACHE_OPERATION_FAILED, and EVALUATION_ABORTED.
Evaluator.evaluate(...) runs on the calling thread and observes EvaluationOptions.signal at stage boundaries instead of interrupting work: it checks the signal before each feature node and forwards it to protocols that check it during their own work, such as sketch solving. A single synchronous native WASM call is never interrupted. An aborted evaluation returns EVALUATION_ABORTED and releases every shape it created. Since 0.2.0, evaluate(...) also checks the signal after each node’s own kernel work. When a signal is supplied, it yields to the host event loop before geometry work starts, between selected outputs, and after the last output, so an abort from a timer, UI handler, or worker message can land only at those yields. Such an evaluation first captures its request: it admits the document through the same bounded snapshot as parseDocumentValue(document) and reads each option once as an own data property, so later changes to the caller’s document or options do not affect it. evaluator.dispose() throws while a signal-bearing evaluation is suspended at one of its yields; await the evaluation first. See cancellation. For hard latency isolation, evaluate in a worker or process that the host can terminate.
CLI
The CLI operates on serialized InvariantCAD documents:Inline
--parameter overrides, fail-closed argument parsing, bounded input
files, the --output requirement for multi-output exports, export and write
failures reported as diagnostics, and terminal-safe escaping were added in
0.2.0. The 0.1.1 CLI accepted only --parameters values.json; silently
ignored --parameter, unknown options, and stray arguments; exported only
the first output in the parsed document’s outputs key order, which is
sorted name order only for a canonically serialized document, when
--output was omitted; printed JavaScript stack traces for missing or
unreadable input files, malformed parameter files, and export or write
failures; and wrote document text without escaping. See the
CLI reference.--parameter name=value values use base units: millimetres, radians, kg/mm^3 mass density, and unitless scalars. Repeat --parameter for multiple finite JSON-number overrides. Inline names are exact stored parameter keys; splitting at the final = keeps keys containing = addressable for directly evaluable frozen legacy documents. Use the attached --parameter=-legacy-key=5 form when a stored key begins with -. Choose either inline overrides or one --parameters JSON file; mixing them is a usage error. Runtime overrides take precedence over the selected configuration and document defaults. --configuration <id> selects the same named variant for inspect, bom, and export; validate checks every stored configuration without selecting one.
CLI parsing is fail-closed and command-specific. Unknown or inapplicable options, extra positional arguments, missing values, malformed inline assignments, and duplicate single-value options fail with exit code 2 before document I/O or kernel initialization. Command-scoped help still validates supplied option values, while valid parameter files are loaded before geometry-kernel initialization. Document and parameter files are limited to the default 64 MiB maxDocumentBytes ceiling and decoded as strict UTF-8 without stripping a byte-order mark, and a parameter file that repeats a member name is rejected. These failures, and a missing or unreadable file, print a one-line reason and exit with code 1.
inspect includes geometric centerOfMass, the three-row inertiaTensor, principal inertia, and world/principal radii alongside volume, surfaceArea, boundingBox, genus, tolerance, and triangles. Since 0.2.0, genus is a nonnegative exact integer when supported and JSON null otherwise; stock/owned OCCT and assembly aggregates return null, while Manifold returns the exact sum for its connected mesh components. The 0.1.1 CLI printed the numeric genus described under Measurements and mass properties. Part and assembly reports additionally include analyzed physicalMassProperties; if an active density is missing, that field is null and physicalMassDiagnostics explains why.
bom evaluates the selected part or assembly output and prints the same deterministic item, quantity, mass-completeness, and warning-diagnostic contract exposed by billOfMaterials(), including its configurationId.
Without --format, export selects the format from the destination extension regardless of case: .obj writes OBJ, .step and .stp write STEP, .brep and .brp write text BREP, and any other extension, or none, writes binary STL. Binary BREP requires --format brep-binary. Exact formats select stock OCCT automatically; use --kernel manifold|occt to select a backend explicitly. export requires --output when the document has more than one output and otherwise fails with exit code 2 before kernel initialization. An export failure, such as an exact format for an assembly output or with --kernel manifold, prints EXPORT_UNSUPPORTED diagnostics, and a destination that cannot be written prints a one-line Output file could not be written: ... reason; both exit with code 1. The validate success line and diagnostic paths and messages escape control characters, Unicode line and paragraph separators, and bidirectional controls as \uXXXX, and inspect and bom JSON escape the same characters inside strings, so untrusted document text cannot drive the terminal.
The current CLI does not inject a custom module factory, so document draft evaluation, exact Boolean, fillet/chamfer, and shell/offset topology evolution, and owned-facade-only composite refinements require programmatic initialization with the matched pair. Boolean, fillet/chamfer, and shell/offset geometry itself remains available through supported stock paths with partial history.
Browser initialization
Most Node.js users need no configuration. Modern browser bundlers such as Vite also resolve both kernels’ package-relative WebAssembly assets automatically:import.meta.url. A
deployment pipeline that relocates assets can instead copy that shipped WASM to
a stable public URL and supply it explicitly:
invariantcad; applications do not install
manifold-3d separately.
The Manifold runtime is initialized once per JavaScript realm: each page,
worker, or process. Supply wasmUrl on the first initialization in that realm.
After a successful initialization, an explicit URL that differs from the one
used, including any URL after a default initialization, fails with “already
initialized with a different WASM URL”. Since 0.2.0, a failed
initialization, such as a wrong URL or a network error, is not cached, so a
later createEvaluator() or createManifoldKernel() can retry with the same
or a corrected URL; 0.1.1 cached the failure for the life of the realm.
The exact backend likewise accepts an explicit OCCT WASM location:
occt-wasm@3.8.0 as a direct application dependency when using that asset
subpath under a strict dependency-isolating package manager. The default
createOcctKernel() path does not require an application-level import from
occt-wasm.
That form still pairs the supplied binary with the stock occt-wasm JavaScript glue and therefore does not enable draft or exact Boolean, fillet/chamfer, or shell/offset topology evolution. Because InvariantCAD cannot recognize that overridden binary as the known default stock runtime, it also omits topology.signatures and topology.signatureProfiles; topology inspection still works, but the kernel makes no persistent-reference compatibility promise.
For a reviewed owned-facade bundle in a browser, verify all three acquired files before kernel creation:
ArrayBuffer/Uint8Array views, verifies
canonical metadata/release.json against the independently trusted SHA-256
pin, then verifies both runtime files before importing the JavaScript through a
short-lived Blob URL. attestedRuntime is mutually exclusive with wasm and
moduleFactory, can be reused for fresh kernel instances only through the
evaluated InvariantCAD internal module instance that created it, and requires
blob: in the browser CSP. Cloning its visible report does not reproduce the
executable authority. Node applications use
invariantcad/kernels/occt/node, which uses an isolated, short-lived
node:module.registerHooks() hook on Node 22.15 and newer. Node 22.13 and 22.14
retain the compatible process-wide node:module.register() worker-hook
fallback, for which the Permission Model must allow workers. Neither path
writes a temporary executable file. See
OCCT runtime attestation for complete
acquisition examples and lifecycle/security boundaries.
blob: is not the only browser CSP requirement. Every realm that initializes a
bundled runtime, including module workers, also needs 'wasm-unsafe-eval' and
'unsafe-eval' in script-src. The Manifold runtime and the stock occt-wasm
glue are Emscripten Embind builds that generate their binding invokers with
new Function, and the owned facade is built from the same glue, so expect it
to need the same allowance. The Chromium release gate serves its production
bundle and workers with:
blob: when the application never uses the attested loader. See
Content Security Policy.
An intentionally trusted custom or rebuilt runtime can still use the direct
generated JavaScript factory. A factory may locate its matched sibling WASM
itself; pass wasm when the application or bundler needs an explicit binary
URL:
?url syntax are application/bundler-specific, and this raw
override does not acquire an attested runtime-pair identity. The three
history-record values shown above are independent defaults; callers may lower
one to constrain that operation family or raise it for exceptionally large
exact graphs, up to the signed 32-bit facade ceiling. InvariantCAD passes each
budget to its native operation and validates the returned count before indexed
JavaScript copying. InvariantCAD probes the loaded module before advertising
draft, the ABI 0.3 composite refinements retained by current ABI 0.9, exact
Boolean evolution, exact fillet/chamfer evolution, or exact shell/offset
evolution. A stock module remains usable for its other exact features, including
shell/offset geometry with partial history, while a partial, mismatched, or
unknown owned-facade marker fails closed instead of claiming guarantees it
cannot prove.
The owned facade is not part of the invariantcad npm tarball and no separate
facade package is currently published. This repository can package a local
source build as a versioned, package-neutral directory plus .tar.gz archive:
.artifacts/occt-facade-bundle/invariantcad-occt-facade-0.9.0/runtime/.
Here 0.9.0 is the facade ABI/bundle version, not the npm package version,
document schema version, or product-roadmap milestone.
Its JavaScript and WASM files must be loaded as a matched pair, preferably
through the attested loader with a separately trusted manifest digest or
deliberately through the raw moduleFactory and wasm options shown above for a
custom build. The archive is package-manager neutral: it is not an npm package,
does not install itself, and is never found, downloaded, or extracted by
createOcctKernel.
pnpm test:occt-facade-bundle also packs the npm library, installs that tarball
in a fresh temporary consumer, and checks the owned ABI 0.9 runtime: the ABI 0.6
modeling/history capability surface through direct/document-evaluated draft;
exact Boolean, fillet/chamfer, and shell/offset evolution; major multi-arc and
eccentric-profile composite sweeps; the exact persistent-topology matrix; and
the candidate-only bounded artifact transport and its private cumulative native
allocation-request budget plus structural preflight. It also runs the
one-shot-child evaluator isolation, cleanup-failure, and fresh-recovery matrix
described above. The verified bundle runtime is passed explicitly. The ordinary
pnpm test:package does not require or discover owned-facade artifacts. A
previously built default runtime can run just the persistence matrix with
pnpm test:occt-persistence-public.
The bundle also collects checksums, build provenance, an SBOM, source and
relinking information, and applicable notices for review. Those materials are
engineering compliance inputs, not legal certification. Public distribution
remains pending external legal, release, and security review; until then,
consumers must build the pinned recipe in
native/occt
or obtain an equivalently reviewed matching pair through an explicit channel.
Architecture
Development
.artifacts/.
License
InvariantCAD is Apache-2.0 licensed. The default mesh backend uses the Apache-2.0 licensed Manifold library. The optional-at-runtime OCCT backend depends onocct-wasm and compiled OpenCascade code under LGPL-2.1 with the OCCT exception. Dependency notices, corresponding-source information, and replacement rights must be preserved in distributions.