> ## Documentation Index
> Fetch the complete documentation index at: https://invariant-cad.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Complete guide

> The exhaustive published 0.1 foundation guide with clearly marked unreleased 0.2 contracts.

# 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.1.1` 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](#support-matrix) and [roadmap](/roadmap).

<Note>
  This is the exhaustive single-page reference retained for readers who prefer
  one continuous guide. The task-oriented documentation starts at the
  [documentation home](/).
</Note>

## Install

```bash theme={"system"}
pnpm add invariantcad
```

Node.js 22.13 or newer is required. The core API is ESM and also targets modern
browsers. Release CI executes a production Vite bundle in Chromium and loads
both the Manifold and stock OpenCascade WebAssembly backends through their
public package entry points.

The npm package contains the TypeScript API, a pinned standalone Manifold core
runtime, and stock `occt-wasm`; 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

```ts theme={"system"}
import {
  EvaluatedSolid,
  createEvaluator,
  design,
  mm,
  plane,
  vec2,
} from "invariantcad";
import { createOcctKernel } from "invariantcad/kernels/occt";

const cad = design("mounting-plate", {
  metadata: { description: "Parameterized plate with one mounting hole" },
});

const width = cad.parameter.length("width", mm(80), {
  min: mm(20),
  max: mm(200),
  description: "Overall plate width",
});
const height = cad.parameter.length("height", mm(50), { min: mm(10) });
const thickness = cad.parameter.length("thickness", mm(6), { min: mm(1) });
const holeRadius = cad.parameter.length("holeRadius", mm(4), { min: mm(1) });

const profile = cad.sketch("plate-profile", plane.xy(), (sketch) => {
  const outline = sketch.rectangle("outline", { width, height });
  const hole = sketch.circle("hole", {
    center: vec2(width.mul(0.25), mm(0)),
    radius: holeRadius,
  });
  return sketch.profile(outline, { holes: [hole.loop()] });
});

const solid = cad.extrude("plate-solid", profile, {
  distance: thickness,
  symmetric: true,
});
const part = cad.part("plate", solid, {
  partNumber: "PLATE-001",
  description: "Machined mounting plate",
});
cad.output("plate", part);

const document = cad.build();
const parameters = {
  width: 100,
  holeRadius: 5,
};

async function evaluateDefaultMesh() {
  const evaluator = await createEvaluator();
  try {
    const result = await evaluator.evaluate(document, {
      parameters,
      outputs: ["plate"],
    });
    if (!result.ok) {
      throw new Error(
        result.diagnostics.map((item) => item.message).join("\n"),
      );
    }

    try {
      const plate = result.value.output("plate");
      if (!(plate instanceof EvaluatedSolid)) {
        throw new Error("Expected the 'plate' output to be a solid part");
      }
      return {
        volume: plate.measure().volume,
        stl: plate.export("stl"),
      };
    } finally {
      result.value.dispose();
    }
  } finally {
    evaluator.dispose();
  }
}

async function exportExactStep() {
  const kernel = await createOcctKernel();
  let evaluatorOwnsKernel = false;
  try {
    const evaluator = await createEvaluator({ kernel });
    evaluatorOwnsKernel = true;
    try {
      const result = await evaluator.evaluate(document, {
        parameters,
        outputs: ["plate"],
      });
      if (!result.ok) {
        throw new Error(
          result.diagnostics.map((item) => item.message).join("\n"),
        );
      }

      try {
        const plate = result.value.output("plate");
        if (!(plate instanceof EvaluatedSolid)) {
          throw new Error("Expected the 'plate' output to be a solid part");
        }
        return {
          volume: plate.measure().volume,
          step: plate.export("step"),
        };
      } finally {
        result.value.dispose();
      }
    } finally {
      evaluator.dispose();
    }
  } finally {
    // A rejected caller-supplied kernel remains caller-owned.
    if (!evaluatorOwnsKernel) kernel.dispose();
  }
}

const defaultMesh = await evaluateDefaultMesh();
const exact = await exportExactStep();

export const mountingPlateSummary = {
  defaultVolume: defaultMesh.volume,
  defaultStlBytes: defaultMesh.stl.byteLength,
  exactVolume: exact.volume,
  stepBytes: exact.step.byteLength,
  stepHeader: new TextDecoder().decode(exact.step.subarray(0, 32)),
};
console.log(mountingPlateSummary);
```

Every feature, entity, constraint, parameter, instance, output, and stored topology reference has an explicit stable ID. Those IDs are the basis for reproducible diffs, diagnostics, and durable design intent.

### 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 STEP `Uint8Array`, disposes each
successful evaluated design, and handles the caller-owned-kernel failure path
before evaluator ownership transfers.

<Note>
  The deterministic STEP contract below is implemented for the unreleased 0.2
  line. The current 0.1.1 package provides ordinary weak native STEP export.
</Note>

The bundled zero-override stock runtime's optional
`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; control characters and unpaired
surrogates are rejected. 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. `maxOutputBytes` does not bound peak native
allocation: the synchronous stock writer cannot be interrupted or bounded
before it materializes its original full string. 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. Draft, complete exact multi-input Boolean, fillet/chamfer, and shell/offset evolution, plus the stronger major multi-arc/eccentric-profile composite guarantees, are advertised only when the matched InvariantCAD-owned facade ABI 0.9 build passes the exact facade probe. A caller can supply that 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](#browser-initialization) and [OCCT runtime attestation](/evaluation/occt-runtime-attestation).

### Exact path sweeps

Paths are first-class parameterized nodes rather than closed sketch profiles:

```ts theme={"system"}
const section = cad.sketch("section", plane.yz(), (sketch) =>
  sketch.profile(
    sketch.rectangle("outline", { width: mm(2), height: mm(2) }),
  ),
);
const spine = cad.polylinePath("spine", [
  vec3(mm(0), mm(0), mm(0)),
  vec3(mm(5), mm(0), mm(0)),
  vec3(mm(5), mm(5), mm(0)),
]);
const swept = cad.sweep("swept", section, spine);
```

An exact circular bend uses a point on the desired arc rather than an ambiguous control point:

```ts theme={"system"}
const bend = cad.circularArcPath("bend", {
  start: vec3(mm(0), mm(0), mm(0)),
  through: vec3(
    mm(10 / Math.sqrt(2)),
    mm(10 - 10 / Math.sqrt(2)),
    mm(0),
  ),
  end: vec3(mm(10), mm(10), mm(0)),
});
const curved = cad.sweep("curved", section, bend);
```

An ordered exact route mixes lines and circular arcs without repeating joint coordinates:

```ts theme={"system"}
const route = cad.compositePath("route", {
  start: vec3(mm(0), mm(0), mm(0)),
  segments: [
    { kind: "line", end: vec3(mm(5), mm(0), mm(0)) },
    {
      kind: "circularArc",
      through: vec3(mm(5 + 5 / Math.sqrt(2)), mm(5 - 5 / Math.sqrt(2)), mm(0)),
      end: vec3(mm(10), mm(5), mm(0)),
    },
    { kind: "line", end: vec3(mm(10), mm(10), mm(0)) },
  ],
});
const routed = cad.sweep("routed", section, route);
```

The current document grammar requires an open, simple sweep path; a closed, hole-free profile seated at its start; an initial tangent parallel to the profile-plane normal in either direction; corrected-Frenet transport; and conservative profile clearance. Polyline paths reject repeated or redundant collinear vertices and use right-corner intersections. Circular-arc paths are one exact three-point arc below a full turn and require their circumradius to exceed the complete profile envelope. Composite paths contain at least two structurally connected segments and at least one arc. Junctions touching an arc must be forward G1 tangent, while line-line junctions retain right-corner semantics. 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:

```ts theme={"system"}
import {
  angleVec3,
  deg,
  design,
  explainTopologySelection,
  mm,
  plane,
  scalarVec3,
  tf,
  topology,
  vec3,
} from "invariantcad";

const cad = design("source-stable-fillet");
const width = cad.parameter.length("width", mm(40));
const height = cad.parameter.length("height", mm(20));
const profile = cad.sketch("profile", plane.xy(), (sketch) =>
  sketch.profile(
    sketch.rectangle("outline", { width, height }),
  ),
);
const extrusion = cad.extrude("extrusion", profile, {
  distance: mm(10),
});
const moved = cad.transform("moved", extrusion, [
  tf.rotate(angleVec3(deg(0), deg(0), deg(90))),
  tf.translate(vec3(mm(100), mm(5), mm(7))),
]);

const rightEndRim = topology.edges
  .createdBy(extrusion, {
    role: "extrude.edge.end-rim",
    source: { sketch: profile, entity: "outline.e1" },
  })
  .and(topology.edges.modifiedBy(moved))
  .select(); // exactly one edge

const rounded = cad.fillet("rounded", moved, {
  edges: rightEndRim,
  radius: mm(2),
});
cad.output("rounded", rounded);

const beveled = cad.chamfer("beveled", moved, {
  edges: rightEndRim,
  distance: mm(2),
});
cad.output("beveled", beveled);

const openEnd = topology.faces
  .createdBy(extrusion, { role: "extrude.face.end-cap" })
  .and(topology.faces.modifiedBy(moved))
  .select();

const hollow = cad.shell("hollow", moved, {
  openings: openEnd,
  thickness: mm(2),
  direction: "inward",
  tolerance: mm(1e-6),
});
cad.output("hollow", hollow);

const expanded = cad.offset("expanded", moved, {
  distance: mm(1),
  direction: "outward",
  tolerance: mm(1e-6),
});
cad.output("expanded", expanded);

const draftedSide = topology.faces
  .createdBy(extrusion, {
    role: "extrude.face.side",
    source: { sketch: profile, entity: "outline.e1" },
  })
  .and(topology.faces.modifiedBy(moved))
  .select();

const drafted = cad.draft("drafted", moved, {
  faces: draftedSide,
  angle: deg(3),
  pullDirection: scalarVec3(0, 0, 1),
  neutralPlane: {
    origin: vec3(mm(100), mm(5), mm(7)),
    normal: scalarVec3(0, 0, 1),
  },
});
cad.output("drafted", drafted);
```

The current chamfer mode applies one constant, equal setback distance on both incident faces. That produces a 45-degree bevel where the faces are orthogonal. Distance-angle, asymmetric, and variable chamfers are not supported yet.

For both fillets and chamfers, `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. 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. 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`:

| Producer                     | Stable face roles                                                                             | Stable edge roles                                                                                                                                                   |
| ---------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Box                          | signed local faces such as `box.face.x-min`                                                   | unique face intersections such as `box.edge.x-min-y-min`                                                                                                            |
| Cylinder or cone             | start/end caps and side                                                                       | start/end rims                                                                                                                                                      |
| Sphere                       | `sphere.face.surface`                                                                         | none; seam and pole artifacts stay unnamed                                                                                                                          |
| Extrusion                    | start/end caps and source-aware sides                                                         | source-aware start/end rims and unsourced lateral edges                                                                                                             |
| Revolution                   | source-aware swept faces; start/end caps for partial turns                                    | none; every revolution edge and kernel artifact stays unnamed                                                                                                       |
| Bounded ruled loft           | source-free start/end caps and two-source ruled sides                                         | source-aware section rims and source-free lateral edges for curves with authored endpoints; circular seams stay unnamed                                             |
| Bounded sweep                | source-free start/end caps and source-aware sides per profile curve and authored path segment | source-aware start/end rims and source-free laterals per authored non-circular profile start and path segment; path-joint fragments and circular seams stay unnamed |
| Exact constant-radius fillet | `fillet.face.blend` for identity-less faces proved by exact generated edge→face evolution     | none; generated and residual-created edges stay unnamed                                                                                                             |
| Exact equal-distance chamfer | `chamfer.face.bevel` for identity-less faces proved by exact generated edge→face evolution    | none; generated and residual-created edges stay unnamed                                                                                                             |

`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. 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 instead of allowing OCCT to choose a different pairing. 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. Zero matches produce `TOPOLOGY_SELECTION_MISSING`; excess matches produce `TOPOLOGY_SELECTION_AMBIGUOUS`. 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:

```ts theme={"system"}
// Given a live evaluated solid from a topology-capable kernel:
const current = evaluatedSolid.topology();
if (!current.ok) throw new Error(current.diagnostics[0]?.message);

const selection = topology.faces.all().exactly(1);
const explainedSelection = explainTopologySelection(
  selection.ir,
  current.value,
  {
    // This `all` query contains no expressions, so evaluation is never called.
    evaluate: () => {
      throw new Error("Unexpected selector expression");
    },
  },
);
if (!explainedSelection.ok) {
  throw new Error(explainedSelection.diagnostics[0]?.message);
}

switch (explainedSelection.value.outcome) {
  case "resolved":
    console.log(explainedSelection.value.keys);
    break;
  case "missing":
  case "ambiguous":
    console.log(explainedSelection.value.candidatesMatched);
    break;
}
```

Topology-selection explanation version 1 is a deeply frozen discriminated union. Every completed report contains `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, and any nested selection failure—including persistent-reference resolution—remain failed `CadResult`s rather than explanation outcomes.

`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 through `EvaluatedSolid.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`:

```ts theme={"system"}
{
  protocolVersion: 2,
  fingerprint: "kernel-specific descriptor compatibility declaration",
}
```

The fingerprint is a semantic compatibility declaration for that kernel's topology descriptors, including any runtime or modeling-tolerance choices the kernel considers material. It is not a hash or cryptographic attestation of the native runtime bytes. Capture and resolution require an exact protocol/fingerprint match before considering a candidate. Protocol v2 adds vertex point evidence and edge↔vertex adjacency: face evidence has incident edges, edge evidence has incident faces and vertices, and vertex evidence has incident edges. Protocol v1 remains frozen to its original face/edge wire shape and its original face↔edge evidence; v1 construction and matching deliberately ignore the new vertex arrays.

The known stock runtime and every recognized owned facade now use protocol v2 with primary `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](/persistent-topology-torture) 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; otherwise vertex matching 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:

| Limit                  |      Default | Input or work bounded during capture or resolution                                                                                                                |
| ---------------------- | -----------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `maxTopologyItems`     |    `100_000` | Total faces plus edges plus vertices in the current snapshot                                                                                                      |
| `maxAdjacencyLinks`    |  `1_000_000` | Snapshot descriptor-array entries, or adjacency entries in a detached reference; each reciprocal face↔edge or edge↔vertex incidence occupies two snapshot entries |
| `maxEvidenceRecords`   |  `1_000_000` | Lineage records in the snapshot or detached reference                                                                                                             |
| `maxReferenceVariants` |     `20_000` | Stored variants inspected cumulatively across one persistent-selection operation                                                                                  |
| `maxCandidatePairs`    |  `1_000_000` | Topology and neighbor compatibility pairs considered while matching                                                                                               |
| `maxMatchingSteps`     | `10_000_000` | Lineage comparisons plus iterative one-to-one adjacency search and update steps                                                                                   |

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.

```ts theme={"system"}
const signatures = kernel.capabilities.topology?.signatures;
const signatureLimits = { maxTopologyItems: 50_000 };
const firstTopology = firstOutput.topology();
if (signatures === undefined || !firstTopology.ok) {
  throw new Error("This evaluation cannot capture persistent topology");
}

const face = firstTopology.value.faces.find((item) =>
  item.lineage.some((entry) => entry.role === "box.face.x-min"),
);
if (face === undefined) throw new Error("Expected box face was not present");

const reference = captureTopologyReference(
  firstTopology.value,
  "face",
  face.key,
  {
    capabilities: signatures,
    tolerance: { linear: 1e-6, angular: 1e-9, relative: 1e-9 },
    limits: signatureLimits,
  },
);
if (!reference.ok) throw new Error(reference.diagnostics[0]?.message);

// Re-author the stable target node and store the captured design intent.
const nextCad = design("persistent-shell");
const nextBox = nextCad.box("box", {
  size: vec3(mm(12), mm(20), mm(30)),
});
const openingFace = nextCad.topologyReference("opening-face", nextBox, {
  topology: "face",
  variants: [reference.value],
});
const hollow = nextCad.shell("hollow", nextBox, {
  openings: topology.faces.persistentReference(openingFace).select(),
  thickness: mm(1),
});
nextCad.output("hollow", hollow);

// Or resolve the detached evidence directly against a later snapshot:
const nextTopology = nextOutput.topology();
// If nextOutput came from another kernel instance, read that instance instead.
const nextSignatures = kernel.capabilities.topology?.signatures;
if (!nextTopology.ok || nextSignatures === undefined) {
  throw new Error("The later evaluation cannot resolve persistent topology");
}
const resolved = resolveTopologyReference(reference.value, nextTopology.value, {
  // Always use the declaration from the kernel that produced nextTopology.
  capabilities: nextSignatures,
  limits: signatureLimits,
});

const explained = explainTopologyReference(
  reference.value,
  nextTopology.value,
  { capabilities: nextSignatures, limits: signatureLimits },
);
if (!explained.ok) throw new Error(explained.diagnostics[0]?.message);
if (explained.value.outcome === "resolved") {
  console.log(explained.value.key, explained.value.evidence);
} else {
  console.log(explained.value.outcome, explained.value.candidatesMatched);
}
```

`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 `CadResult`s 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, a unique stable role or sketch-source anchor resolves as `evidence: "semantic-lineage"`; that design evidence is authoritative rather than being overridden by a coincidental geometric match. A v2 vertex can also resolve semantically when every incident edge on both sides has an authoritative anchor and the complete edge-anchor sets agree one-to-one. When neither path applies, or either snapshot declares partial history, resolution falls back to toleranced `"geometry-adjacency"` evidence and does not treat partial lineage 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](/persistent-topology-torture) 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 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
* Unreleased opaque one-body documents 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, capability-correct nullable genus, kernel tolerance, center of mass, centroidal inertia, principal axes/moments, arbitrary-axis inertia, radii of gyration, and density-aware physical 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; 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

| Capability                                         |                                                                                                                                                                                                                                                                                                Current main |                                                               Intended stable system |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -----------------------------------------------------------------------------------: |
| Versioned, kernel-neutral design IR                |                                                                                                                                                                                                                                                                                                         Yes |                                                                                  Yes |
| Parametric feature graph                           |                                                                                                                                                                                                                                                                                                         Yes |                                                                                  Yes |
| Watertight mesh solids and CSG                     |                                                                                                                                                                                                                                                                                                         Yes |                                                                                  Yes |
| Sketch constraints                                 |                                                                                                                                                                                                                                                                                            Reference solver |                                                         Pluggable industrial solvers |
| Parts and fixed-placement assemblies               |                                                                                                                                                                                                                                                                                                         Yes |                                                                                  Yes |
| Assembly mates and joints                          |                                                                                                                                                                                                                                                                                                          No |                                                                                  Yes |
| Center of mass and centroidal inertia tensor       |                                                                                                                                                                                                                                                                                 Both kernels and assemblies |                                                                                  Yes |
| Principal/axis inertia and radii of gyration       |                                                                                                                                                                                                                                                                              Kernel-neutral public analysis |                                                                                  Yes |
| Density-aware part and heterogeneous-assembly mass |                                                                                                                                                                                                                                                                                   Explicit authored density |                                                                                  Yes |
| Named configurations                               |                                                                                                                                                                                                                                       Parameter, definition-scoped suppression, and part-material overrides |                                                 Effectivity and rule-driven variants |
| Deterministic bill of materials                    |                                                                                                                                                                                                                                              Fixed and nested assemblies, including selected configurations |                                      Effectivity and alternate/substitute components |
| Incremental feature identity                       |                                                                                                                                                                                                                            Feature-hash protocol v1 over one effective base/configuration/call-time context |                      Field-aware and geometric comparison where explicitly supported |
| Cross-run kernel-shape cache                       |                                                                                                                             Strict public foundation plus a repository-private trusted-store direct-box evaluator slice; no shipped backend advertises a codec and no public evaluator option enables reuse | Public diagnostic-preserving evaluator integration and backend-owned complete codecs |
| Exact B-Rep primitives and core features           |                                                                                                                                                                                                                                                                                                OCCT backend |                                                                                  Yes |
| STEP and BREP import/export                        |                                                                                                                                                                                                                                                                                                OCCT backend |                                                                                  Yes |
| Verified public one-body import                    |                                                                                                                                                     Unreleased opaque document/resolver facade over strong stock-OCCT import; no healing, multibody, assembly, location I/O, or broad Document v7 promotion |                     Broader repair and product import only behind explicit contracts |
| IGES import/export                                 |                                                                                                                                                                                                                                                                                                          No |                                                                        Exact backend |
| Fillet                                             |                                                                                                                                                                                                                                                                   OCCT backend with semantic edge selectors |                                                                                  Yes |
| Chamfer                                            |                                                                                                                                                                                                                                                       OCCT equal-distance mode with semantic edge selectors |                                                                                  Yes |
| Shell                                              |                                                                                                                                                                                                                                     OCCT inward/outward constant-thickness mode with semantic face openings |                                                                                  Yes |
| Whole-solid offset                                 |                                                                                                                                                                                                                                                             OCCT inward/outward mode with fixed round joins |                                                                                  Yes |
| Draft                                              |                                                                                                                                                                                                                                  Explicitly supplied matched owned OCCT facade with semantic face selectors |                                                                                  Yes |
| Boolean topology evolution                         |                                                                                                                                                         Complete face/edge/vertex graph with explicitly supplied owned OCCT facade ABI 0.4 and later; partial on stock/legacy OCCT; unavailable on Manifold |                                                   Persistent cross-evaluation naming |
| Fillet/chamfer topology evolution                  |                                                                                                                                                                        Complete face/edge/vertex graph with explicitly supplied owned OCCT facade ABI 0.5 and later; partial on stock and owned ABI 0.2–0.4 |                                                   Persistent cross-evaluation naming |
| Shell/whole-solid offset topology evolution        |                                                                                                                                                                        Complete face/edge/vertex graph with explicitly supplied owned OCCT facade ABI 0.6 and later; partial on stock and owned ABI 0.2–0.5 |                                                   Persistent cross-evaluation naming |
| Persistent face/edge/vertex references             |                 Protocol-v2 capture/resolution plus exact protocol-v1 face/edge compatibility, versioned aggregate explanations, and Document-v2–v6 registries/selector atoms, with exact target/profile binding, a published torture corpus, and no invented identity for symmetric or coincident topology |                                               Broader naming across feature families |
| Loft                                               |                                                                                                                                                                                                 OCCT ordered ruled-solid mode with matched hole-free sections and five fail-closed semantic face/edge roles |                                                       Smooth, guided, and open modes |
| Sweep                                              |            OCCT open-polyline, one-edge circular-arc, and certified ordered line/arc composite solid modes with corrected-Frenet transport and six fail-closed semantic face/edge roles; owned facade ABI 0.6 retains the certified major multi-arc and eccentric-profile refinements introduced by ABI 0.3 |                          Bézier, B-spline, helix, guided, and variable-section modes |
| Semantic topology selectors                        | Face/edge origin/geometry plus role-free vertex position, valid adjacency, and Document-v2–v6 persistent-reference queries; v3 adds bounded ruled-loft roles, v4 adds bounded-sweep roles, v5 adds exact generated fillet/chamfer face roles, and v6 adds vertices while every earlier grammar stays frozen |                        Remaining feature-family roles and an expanded torture corpus |
| Drawings, GD\&T, PMI                               |                                                                                                                                                                                                                                                                                                          No |                                                                                  Yes |
| Sheet metal                                        |                                                                                                                                                                                                                                                                                                          No |                                                                                  Yes |
| CAM and CAE adapters                               |                                                                                                                                                                                                                                                                                                          No |                                                                                  Yes |
| STL and OBJ export                                 |                                                                                                                                                                                                                                                                                                         Yes |                                                                                  Yes |

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. 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,

```text theme={"system"}
integral(((r dot r) I - r r^T) dV)
```

where `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.

The public analysis functions operate only on copied numeric properties, so they are backend-neutral and remain usable after the evaluated shape is disposed:

```ts theme={"system"}
import {
  momentOfInertiaAboutAxis,
  principalInertia,
  principalRadiiOfGyration,
  worldRadiiOfGyration,
} from "invariantcad";

const measured = output.measure();
const principal = principalInertia(measured.inertiaTensor);
// principal.moments is ascending; principal.axes[i] matches moments[i].
console.log(principal.degeneracy, principal.moments, principal.axes);
console.log(worldRadiiOfGyration(measured));
console.log(principalRadiiOfGyration(measured));
console.log(
  momentOfInertiaAboutAxis(measured, {
    point: [0, 0, 0],
    direction: [0, 0, 1],
  }),
);
```

`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.

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:

```ts theme={"system"}
import { EvaluatedPart, kgPerCubicMeter } from "invariantcad";

const density = cad.parameter.massDensity(
  "density",
  kgPerCubicMeter(2700),
);
const aluminum = cad.material("aluminum-6061-t6", {
  name: "6061-T6 Aluminum",
  massDensity: density,
});
const part = cad.part("bracket", solid, {
  partNumber: "BRACKET-001",
  materialRef: aluminum,
});
cad.output("bracket", part);

const evaluated = await evaluator.evaluate(cad.build(), {
  // Parameter overrides use the document base unit, here kg/mm^3.
  parameters: { density: 7.85e-6 },
});
if (evaluated.ok) {
  try {
    const output = evaluated.value.output("bracket");
    if (output instanceof EvaluatedPart) {
      const properties = output.physicalMassProperties();
      if (properties.ok) console.log(properties.value.mass);
    }
  } finally {
    evaluated.value.dispose();
  }
}
```

Density resolution has one deterministic precedence rule: a part's own `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`; zero, negative, or non-finite resolved density produces `MASS_DENSITY_INVALID`; a finite calculation that cannot produce representable, mechanically valid properties produces `MASS_PROPERTIES_INVALID`. Suppressed occurrences do not require density. 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:

```ts theme={"system"}
import { tf, vec3, mm } from "invariantcad";

const product = cad.assembly("product", (assembly) => {
  assembly.instance("left", part);
  assembly.instance("right", part, {
    placement: [tf.translate(vec3(mm(100), mm(0), mm(0)))],
  });
});

cad.output("product", product);
```

Nested assemblies are flattened into occurrence paths such as `frame/left-bracket` during evaluation while retaining the original part node. Assembly measurements aggregate every placed occurrence under its affine transform and apply the parallel-axis theorem, so shared definitions contribute independently at each placement.

Parts and assemblies also expose a deterministic bill of materials:

```ts theme={"system"}
import { EvaluatedAssembly } from "invariantcad";

const evaluated = await evaluator.evaluate(cad.build(), {
  outputs: ["product"],
});
if (evaluated.ok) {
  try {
    const output = evaluated.value.output("product");
    if (output instanceof EvaluatedAssembly) {
      const bom = output.billOfMaterials();
      if (!bom.ok) {
        console.error(bom.diagnostics);
      } else {
        console.table(bom.value.items);
        console.log({
          quantity: bom.value.totalQuantity,
          massComplete: bom.value.massComplete,
          knownMass: bom.value.knownMass,
          totalMass: bom.value.totalMass,
        });
        // Successful partial BOMs can still carry warning diagnostics.
        console.warn(bom.diagnostics);
      }
    }
  } finally {
    evaluated.value.dispose();
  }
}
```

`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:

```ts theme={"system"}
const compactSingle = cad.configuration(
  "compact-single",
  (configuration) => {
    configuration.parameter(width, mm(60));
    configuration.instanceSuppressed(product, "right");
    configuration.partMaterial(part, steel);
  },
  { description: "One compact bracket in steel" },
);

const evaluated = await evaluator.evaluate(cad.build(), {
  configuration: compactSingle,
  parameters: {
    width: 72, // Call-time values override the selected configuration.
  },
});

if (evaluated.ok) {
  try {
    console.log(evaluated.value.configurationId); // "compact-single"
    const output = evaluated.value.output("product");
    if (output instanceof EvaluatedAssembly) {
      const bom = output.billOfMaterials();
      if (bom.ok) console.log(bom.value.configurationId);
    }
  } finally {
    evaluated.value.dispose();
  }
}
```

Parameter precedence is exact: call-time `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. Pass `false` as the third argument to re-enable an instance authored with `suppressed: true`. `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

```ts theme={"system"}
import {
  hashDocument,
  parseDocument,
  stringifyDocument,
} from "invariantcad";

const json = stringifyDocument(document, { pretty: true });
const parsed = parseDocument(json);
const semanticHash = await hashDocument(document);
```

Canonical serialization sorts record keys, normalizes negative zero, rejects non-finite numbers, and produces identical bytes regardless of feature construction order. Top-level document metadata is excluded from semantic hashes unless requested; metadata attached to parameters, materials, nodes, and configurations remains part of their authored document semantics. Persistent topology registry data is always semantic.

### 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:

```ts theme={"system"}
import {
  DESIGN_IMPACT_REPORT_VERSION,
  analyzeDesignImpact,
} from "invariantcad";

const impact = analyzeDesignImpact(document, {
  parameters: [width.id],
});

if (impact.ok) {
  console.log(impact.value.version === DESIGN_IMPACT_REPORT_VERSION); // true
  console.log(impact.value.nodes);
  console.log(impact.value.outputs);
}
```

Authored-impact report version 1 detaches and validates the document before traversing it. Seed arrays accept ordinary string IDs, then validate them against the shared ID grammar and the document inventory before returning 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:

```ts theme={"system"}
import {
  FEATURE_HASH_PROTOCOL_VERSION,
  hashDesignFeatures,
} from "invariantcad";

const hashes = await hashDesignFeatures(document, {
  configuration: "compact-single",
  parameters: { width: 72 },
});

if (hashes.ok) {
  console.log(hashes.value.hashProtocolVersion === FEATURE_HASH_PROTOCOL_VERSION);
  console.log(hashes.value.nodes);
  console.log(hashes.value.outputs);
}
```

The report is deterministic, sorted, deeply frozen, cancellable through `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(...)` and explicit-v2 `auditKernelShapeArtifactCodecV2(...)`. 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, 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(...)`, or explicit protocol v2 through the corresponding `...V2` functions. V1 retains its original field shape, canonical bytes, hash domain, and exact-genus requirement. 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. 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, 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](/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. 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 exact identified face/edge/vertex geometry and incidence, and only then restore semantic records onto fresh keys. 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 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 package-private symbol hook, 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 parent transports that record through an exact binary frame with an 8-byte versioned magic, little-endian 32-bit canonical-JSON header length, 32 KiB header ceiling, and exact remaining payload. It enforces fatal UTF-8, closed fields, protocol/key/metadata/integrity validation, request-specific byte limits, SHA-256, payload length, and EOF. 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 lockfile-tested Manifold 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 cooperatively cancellable. 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. 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, semantic-observation protocol v1/v2, 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. `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. 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 returns `CadResult<T>`:

```ts theme={"system"}
if (!result.ok) {
  for (const issue of result.diagnostics) {
    console.error(issue.code, issue.path, issue.message);
  }
}
```

Stable codes include `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`.

## CLI

The CLI operates on serialized InvariantCAD documents:

```bash theme={"system"}
invariantcad validate design.invariantcad.json
invariantcad inspect design.invariantcad.json
invariantcad inspect design.invariantcad.json --parameters dimensions.json
invariantcad inspect design.invariantcad.json --parameter width=120 --parameter=height=80
invariantcad inspect design.invariantcad.json --configuration compact-single
invariantcad inspect design.invariantcad.json --output plate
invariantcad bom design.invariantcad.json --output product
invariantcad bom design.invariantcad.json --output product --configuration compact-single
invariantcad export design.invariantcad.json --output plate --to plate.stl
invariantcad export design.invariantcad.json --output product --configuration compact-single --to product.stl
invariantcad export design.invariantcad.json --output plate --format obj --to plate.obj
invariantcad export design.invariantcad.json --output plate --to plate.step
invariantcad inspect design.invariantcad.json --kernel occt
```

Parameter JSON and inline `--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.
`inspect` includes geometric `centerOfMass`, the three-row `inertiaTensor`, principal inertia, and world/principal radii alongside `volume`, `surfaceArea`, `boundingBox`, `genus`, `tolerance`, and `triangles`. Genus is a nonnegative exact integer when supported and JSON `null` otherwise; stock/owned OCCT and assembly aggregates currently return `null`, while Manifold returns the exact sum for its connected mesh components. 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`.
The CLI selects stock OCCT automatically for `.step` and `.brep` destinations. Use `--kernel manifold|occt` to select a backend explicitly. 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:

```ts theme={"system"}
import { createEvaluator } from "invariantcad";
import { createOcctKernel } from "invariantcad/kernels/occt";

const manifoldEvaluator = await createEvaluator();
const occtKernel = await createOcctKernel();
```

InvariantCAD's release gate executes that exact default initialization from a
production Vite bundle in Chromium. The package stages its pinned Manifold core
runtime beside the library and resolves the WASM through `import.meta.url`. A
deployment pipeline that relocates assets can instead copy that shipped WASM to
a stable public URL and supply it explicitly:

```ts theme={"system"}
import { createEvaluator } from "invariantcad";

const evaluator = await createEvaluator({
  manifold: { wasmUrl: "/wasm/manifold.wasm" },
});
```

InvariantCAD accepts a normal URL and does not couple its API to Vite. The
standalone runtime is part of `invariantcad`; applications do not install
`manifold-3d` separately.

The exact backend likewise accepts an explicit OCCT WASM location:

```ts theme={"system"}
import occtWasmUrl from "occt-wasm/dist/occt-wasm.wasm?url";
import { createOcctKernel } from "invariantcad/kernels/occt";

const kernel = await createOcctKernel({ wasm: occtWasmUrl });
```

Add `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:

```ts theme={"system"}
import { createOcctKernel } from "invariantcad/kernels/occt";
import {
  INVARIANTCAD_OCCT_FACADE_0_9_0_RELEASE_MANIFEST_SHA256,
  loadAttestedOcctRuntime,
} from "invariantcad/kernels/occt/browser";

const attestedRuntime = await loadAttestedOcctRuntime({
  releaseManifest,
  expectedReleaseManifestSha256:
    INVARIANTCAD_OCCT_FACADE_0_9_0_RELEASE_MANIFEST_SHA256,
  javascript,
  webassembly,
});
const kernel = await createOcctKernel({ attestedRuntime });
```

The loader snapshots the exact `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](/evaluation/occt-runtime-attestation) for complete
acquisition examples and lifecycle/security boundaries.

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:

```ts theme={"system"}
import ownedOcctModuleFactory from "./occt-facade/occt-wasm.js";
import ownedOcctWasmUrl from "./occt-facade/occt-wasm.wasm?url";
import { createOcctKernel } from "invariantcad/kernels/occt";

const kernel = await createOcctKernel({
  moduleFactory: ownedOcctModuleFactory,
  wasm: ownedOcctWasmUrl,
  maxExactBooleanHistoryRecords: 1_000_000,
  maxExactEdgeTreatmentHistoryRecords: 1_000_000,
  maxExactSolidOffsetHistoryRecords: 1_000_000,
});
```

The paths and `?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:

```bash theme={"system"}
pnpm build:occt-facade
pnpm bundle:occt-facade
pnpm verify:occt-facade-bundle
pnpm test:occt-facade-bundle
```

The unpacked runtime is under
`.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](https://github.com/shlokjain42/invariantCAD/tree/v0.1.1/native/occt)
or obtain an equivalently reviewed matching pair through an explicit channel.

## Architecture

```text theme={"system"}
TypeScript builders
        │
        ▼
immutable DesignDocument v6 ──► validation / canonical JSON / hashing
  (v1/v2/v3/v4/v5 stay frozen, readable, directly evaluable, and migratable)
        │
        ├──► sketch-solver protocol ──► reference solver (v0.1)
        │
        └──► geometry-kernel protocol ──► Manifold mesh kernel
                                      └─► exact OCCT B-Rep kernel
        │
        ▼
evaluated parts / assemblies ──► measurement / mesh / STL / OBJ / STEP / BREP
```

See [Architecture](/architecture) for the invariants and backend contracts.

## Development

```bash theme={"system"}
pnpm install
pnpm check
pnpm test
pnpm build
pnpm example:bracket
pnpm verify

# Heavyweight owned-facade release checks (after building the facade)
pnpm test:occt-persistence-public
pnpm test:occt-facade-bundle
```

The bracket example writes its document and STL to `.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 on `occt-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.
