geospec 0.1.0-beta.2 → 0.1.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/agent/doctrine.md +8 -0
  2. package/agent/geospec-authoring/SKILL.md +19 -20
  3. package/agent/geospec-authoring/api-constants.md +11 -1
  4. package/agent/geospec-authoring/api-functions.md +9 -1
  5. package/agent/geospec-authoring/api-index.md +22 -7
  6. package/agent/geospec-authoring/api-types.md +91 -3
  7. package/agent/geospec-authoring/public-api-constants.md +33 -1
  8. package/agent/geospec-authoring/public-api-functions.md +21 -2
  9. package/agent/geospec-authoring/public-api-index.md +57 -20
  10. package/agent/geospec-authoring/public-api-types-2.md +267 -220
  11. package/agent/geospec-authoring/public-api-types-3.md +217 -0
  12. package/agent/geospec-authoring/public-api-types.md +37 -137
  13. package/agent/resources.js +14 -13
  14. package/agent/skills.json +3 -2
  15. package/dist/assertion-client/client.d.mts +12 -2
  16. package/dist/assertion-client/client.mjs +2 -2
  17. package/dist/assertion-client/index.d.mts +2 -2
  18. package/dist/assertion-client/index.mjs +1 -1
  19. package/dist/config/tau-project-artifact.mjs +1 -1
  20. package/dist/create-geospec.d.mts +3 -2
  21. package/dist/create-geospec.mjs +1 -1
  22. package/dist/engine/client.d.mts +21 -1
  23. package/dist/engine/client.mjs +1 -1
  24. package/dist/index.d.mts +2 -2
  25. package/dist/model/export-intent.d.mts +17 -1
  26. package/dist/model/export-intent.mjs +1 -1
  27. package/dist/model/index.d.mts +4 -3
  28. package/dist/model/index.mjs +1 -1
  29. package/dist/model/native-model-loader.d.mts +30 -7
  30. package/dist/model/native-model-loader.mjs +1 -1
  31. package/dist/model/region-scope.d.mts +23 -0
  32. package/dist/model/region-scope.mjs +1 -0
  33. package/dist/model/subject-assertions.mjs +1 -1
  34. package/dist/model/types.d.mts +50 -3
  35. package/dist/runner/collector.d.mts +4 -2
  36. package/dist/runner/collector.mjs +2 -2
  37. package/dist/runner/index.d.mts +3 -2
  38. package/dist/runner/index.mjs +1 -1
  39. package/dist/runner/native/native-serial-runner.mjs +1 -1
  40. package/dist/runner/run-geospec-module.mjs +2 -2
  41. package/dist/runner/timeout-codes.d.mts +45 -0
  42. package/dist/runner/timeout-codes.mjs +1 -0
  43. package/dist/runner/types.d.mts +34 -1
  44. package/dist/runner/worker/runner-types.d.mts +24 -3
  45. package/dist/runner/worker/serial-runner.mjs +1 -1
  46. package/package.json +4 -4
package/agent/skills.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "slug": "geospec-authoring",
5
5
  "name": "GeoSpec authoring",
6
6
  "description": "Guides canonical GeoSpec geometry tests in TypeScript or JavaScript for every Tau kernel. Use for *.geospec.ts and *.geospec.js tests.",
7
- "version": "0.1.0-beta.2",
7
+ "version": "0.1.0-beta.4",
8
8
  "whenToUse": "Use before creating or editing *.geospec.ts or *.geospec.js tests, for a model in any kernel.",
9
9
  "directory": "geospec-authoring",
10
10
  "files": [
@@ -19,9 +19,10 @@
19
19
  "public-api-constants.md",
20
20
  "public-api-types.md",
21
21
  "public-api-types-2.md",
22
+ "public-api-types-3.md",
22
23
  "public-api-classes.md"
23
24
  ],
24
- "body": "---\nname: geospec-authoring\ndescription: Guides canonical GeoSpec geometry tests in TypeScript or JavaScript for every Tau kernel. Use for *.geospec.ts and *.geospec.js tests.\n---\n\n# GeoSpec authoring\n\nWrite tests before implementation; assert deterministic, measurable properties.\n\n## Test shape\n\n- Use `*.geospec.ts` or `*.geospec.js`. Import `describe`, `it` and `expectGeo` from `geospec`, and `loadModel` from `geospec/model`.\n- Await `loadModel`, then assert synchronously with `expectGeo(model)`. The host supplies the compiled binding; authoring does not select an engine.\n- Await or return the complete model-load chain from the test callback; detached future loads are not part of a completed test.\n- Omit `parameters` for model-code defaults; use `{ file, parameters }` for variants. Loaded subjects are opaque.\n\nCanonical TypeScript authoring:\n\n```ts\nimport { describe, expectGeo, it } from 'geospec';\nimport { loadModel } from 'geospec/model';\n\ndescribe('main geometry', () => {\n it('has the intended envelope', async () => {\n const model = await loadModel({ file: 'main.ts', parameters: { width: 120 } });\n expectGeo(model).toHaveBoundingBox({ size: { x: 120 }, tolerance: 1 });\n });\n});\n```\n\nSpecs are always TypeScript or JavaScript, whatever the model's kernel: `loadModel({ file })` evaluates `main.ts`, `main.tsx`, `main.py`, `main.scad`, `main.kcl` or `main.cs` through Tau's runtime. `test_model` discovers `*.geospec.ts` and `*.geospec.js` only.\n\nEvery matcher takes one expectation object:\n\n- Wrong: `expectGeo(model).toHaveVolume(5000, { tolerance: 5 })`. Correct: `expectGeo(model).toHaveVolume({ value: 5000, tolerance: 5 })`.\n- Wrong: `await expectGeo(model)…`. Correct: await `loadModel`, then assert synchronously.\n- Wrong: exact-feature matchers on a mesh kernel (Manifold, JSCAD, OpenSCAD, PicoGK, PicoVoxel). Correct: `toBeValidBrep`, `toHaveTopologyCounts`, the face, hole, pattern, chamfer, fillet and wall-thickness matchers, `toHaveStepUnits`, `toHaveProductStructure` and `toHaveVoidContinuity` need STEP BRep evidence and report unsupported on meshes; assert meshes with bounds, volume, surface area, connected components and `toBeWatertight`.\n\n## Coverage\n\nBounds and physical properties are insufficient. Cover major components, named features, variants, dimensions/positions, disjoint counts, closure, interference and supported exact features. Explicitly report unsupported requirements; proxies cannot fulfill them.\n\n- `toHaveBoundingBox`: size/position.\n- `toHaveConnectedComponents`: qualified material connectivity; visual touching cannot justify tolerance changes.\n- `toBeWatertight`: closed edge incidence, not manifold validity, fusion or material connectivity. Bounds prove none of these. Missing evidence is unsupported.\n- `toHaveSurfaceArea`, `toHaveVolume`, `toHaveCenterOfMass`, `toHaveMass`: physical measurements.\n- `toHaveNoComponentInterference`: overlap; allow deliberate press fits.\n- `loadModel({ file, format: 'step' })`: use BRep evidence for supported topology and feature assertions. Missing occurrence, interference, clearance, containment, lumen, wall, section or motion evidence is unsupported, not a pass or an envelope substitute.\n\nTest assemblies and independent units; preserve sibling coverage when adding files. Non-top-level geometry needs kernel export/invocation, not test removal.\n\nFix geometry at its root; never weaken tolerances, delete assertions, or reduce detail to pass.\n\n`public-api-index.md` covers the host and framework API; authoring needs only the Core API below and `api-*` files.\n\n## Core API\n\nThe symbols real Tau models use most, as verbatim declarations. Anything not shown is in the API reference below.\n\n### Functions\n\n```ts\n// Start a geometry assertion chain\nexport declare function expectGeo(subject: GeoSpecSubject): GeoSpecMatcher;\n\n// A model admitted by one live GeoSpec host scope\nGeoSpecSubject: {\n readonly [subjectBrand]: true\n}\n\n// Load a CAD model into GeoSpec evidence\nexport declare function loadModel<Code extends Record<string, string> = Record<string, string>>(_options: LoadModelOptions<Code>): Promise<GeoSpecSubject>;\n\n// Options accepted by {@link import ('./load-model.js').loadModel}\nLoadModelOptions: LoadModelSourceOptions | LoadModelCodeOptions<Code> | LoadModelFileOptions\n // Geometry format to export\n format?: GeoSpecModelFormat\n // Explicit parameters passed to the runtime\n parameters?: Record<string, unknown>\n // STEP reader strategy used for STEP exports\n stepStreaming?: StepStreamingMode\n // Whether STEP loading should also produce mesh evidence\n mesh?: boolean\n // Linear tolerance used while meshing exact BRep evidence\n meshLinearTolerance?: number\n // Angular tolerance in degrees used while meshing exact BRep evidence\n meshAngularToleranceDegrees?: number\n\n// Geometry formats accepted by {@link import ('./load-model.js').loadModel}\nGeoSpecModelFormat: MeshFileFormat | 'step' | 'stp'\n```\n\n### Types\n\n```ts\n// Assertion chain returned by `expectGeo(subject)`\nGeoSpecMatcher: {\n // Core-owned negation\n readonly not: Omit<GeoSpecMatcher, 'not'>\n // Assert the fixed rational plate contract\n toSatisfyRationalPlate(): GeoSpecAssertion;\n // Assert the fixed parallel-plane distance contract\n toSatisfyParallelPlaneDistance(): GeoSpecAssertion;\n // Assert axis-aligned bounds, size, or center for a loaded geometry subject\n toHaveBoundingBox(first: Vec3 | GeoSpecBoundingBoxExpectation, second?: Vec3): GeoSpecAssertion;\n // Assert how many spatially disjoint chunks the mesh contains\n toHaveConnectedComponents(expected: GeoSpecConnectedComponentsExpectation): GeoSpecAssertion;\n // Assert closed mesh edge incidence\n toBeWatertight(): GeoSpecAssertion;\n // Assert that separate assembly components do not occupy the same solid volume\n toHaveNoComponentInterference(expected?: GeoSpecComponentInterferenceExpectation): GeoSpecAssertion;\n // Assert that named assembly occurrences exist with expected counts and bounds\n toHaveAssemblyOccurrences(expected: GeoSpecAssemblyOccurrencesExpectation): GeoSpecAssertion;\n // Assert that selected entities satisfy declared spatial relationships\n toHaveSpatialRelationships(expected: GeoSpecSpatialRelationshipsExpectation): GeoSpecAssertion;\n // Assert rendered mesh evidence is internally trustworthy for downstream checks\n toHaveMeshIntegrity(expected: GeoSpecMeshIntegrityExpectation): GeoSpecAssertion;\n // Assert that the subject carries no diagnostics at the rejected severities\n toHaveNoDiagnostics(expected?: GeoSpecNoDiagnosticsExpectation): GeoSpecAssertion;\n // Assert total surface area, preferring exact BRep mass properties when available\n toHaveSurfaceArea(expected: GeoSpecSurfaceAreaExpectation): GeoSpecAssertion;\n // Assert enclosed volume, preferring exact BRep mass properties when available\n toHaveVolume(expected: GeoSpecVolumeExpectation): GeoSpecAssertion;\n // Assert mass derived from exact mass properties or volume times density\n toHaveMass(expected: GeoSpecMassExpectation): GeoSpecAssertion;\n // Assert the center of mass or mesh-derived centroid for a closed subject\n toHaveCenterOfMass(expected: GeoSpecCenterOfMassExpectation): GeoSpecAssertion;\n // Assert that exact BRep evidence reports a valid shape\n toBeValidBrep(expected?: GeoSpecValidBrepExpectation): GeoSpecAssertion;\n // Assert exact BRep topology counts\n toHaveTopologyCounts(expected: GeoSpecTopologyCountsExpectation): GeoSpecAssertion;\n // Assert the STEP unit evidence\n toHaveStepUnits(expected: GeoSpecStepUnitsExpectation): GeoSpecAssertion;\n // Assert STEP product-structure evidence\n toHaveProductStructure(expected: GeoSpecProductStructureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a planar face matching the requested constraints\n toHavePlanarFace(expected: GeoSpecPlanarFaceExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a cylindrical face with the requested radius and axis\n toHaveCylindricalFace(expected: GeoSpecCylindricalFaceExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a circular hole matching diameter, center, and axis\n toHaveCircularHole(expected: GeoSpecCircularHoleExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a repeated circular-hole pattern\n toHaveCircularHolePattern(expected: GeoSpecCircularHolePatternExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a chamfer feature with the requested distance\n toHaveChamferFeature(expected: GeoSpecChamferFeatureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a fillet feature with the requested radius\n toHaveFilletFeature(expected: GeoSpecFilletFeatureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence reports a minimum wall thickness satisfying the expectation\n toHaveMinimumWallThickness(expected: GeoSpecMinimumWallThicknessExpectation): GeoSpecAssertion;\n // Assert that the declared waypoints share one connected void, stay isolated from the declared…\n toHaveVoidContinuity(expected: GeoSpecVoidContinuityExpectation): GeoSpecAssertion;\n}\n\n// Numeric 3D vector\nVec3: readonly [number, number, number]\n\n// Bounding-box expectation accepted by `expectGeo(...).toHaveBoundingBox(...)`\nGeoSpecBoundingBoxExpectation: {\n min?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n max?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n size?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n center?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n tolerance?: number\n}\n\n// Connected-components expectation accepted by `expectGeo(...).toHaveConnectedComponents(...)`\nGeoSpecConnectedComponentsExpectation: {\n count: number\n tolerance?: number\n toleranceMm?: number\n}\n\n// Component-interference expectation accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferenceExpectation: {\n tolerance?: number\n pairs?: GeoSpecComponentInterferencePairExpectation[]\n allowances?: GeoSpecComponentInterferenceAllowance[]\n}\n\n// Assembly occurrence expectation accepted by `expectGeo(...).toHaveAssemblyOccurrences(...)`\nGeoSpecAssemblyOccurrencesExpectation: {\n occurrences: GeoSpecAssemblyOccurrenceExpectation[]\n uniqueNames?: boolean\n}\n\n// Spatial relationship expectation accepted by `expectGeo(...).toHaveSpatialRelationships(...)`\nGeoSpecSpatialRelationshipsExpectation: {\n relationships: GeoSpecSpatialRelationshipExpectation[]\n}\n\n// Mesh integrity expectation accepted by `expectGeo(...).toHaveMeshIntegrity(...)`\nGeoSpecMeshIntegrityExpectation: {\n finitePositions?: boolean\n degenerateTriangles?: {\n count?: number;\n maxCount?: number;\n areaTolerance?: number;\n }\n duplicateFaces?: {\n count?: number;\n maxCount?: number;\n }\n watertight?: boolean\n triangleCount?: GeoSpecNumericExpectation\n}\n\n// Diagnostic severities rejected by `expectGeo(...).toHaveNoDiagnostics(...)`\nGeoSpecNoDiagnosticsExpectation: {\n severities?: Array<GeometryDiagnostic['severity']>\n}\n\n// Surface-area expectation accepted by `expectGeo(...).toHaveSurfaceArea(...)`\nGeoSpecSurfaceAreaExpectation: {\n value: number | GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Volume expectation accepted by `expectGeo(...).toHaveVolume(...)`\nGeoSpecVolumeExpectation: {\n value: number | GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Mass expectation accepted by `expectGeo(...).toHaveMass(...)`\nGeoSpecMassExpectation: {\n value: number | GeoSpecNumericExpectation\n density?: number\n tolerance?: number\n}\n\n// Center-of-mass expectation accepted by `expectGeo(...).toHaveCenterOfMass(...)`\nGeoSpecCenterOfMassExpectation: {\n point: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Exact BRep validity expectation accepted by `expectGeo(...).toBeValidBrep(...)`\nGeoSpecValidBrepExpectation: {\n maxTolerance?: number\n freeBounds?: {\n count?: GeoSpecNumericExpectation;\n }\n minEdgeLength?: number\n sameParameter?: boolean\n closedShells?: boolean\n closedWires?: boolean\n}\n\n// Topology-count expectation accepted by `expectGeo(...).toHaveTopologyCounts(...)`\nGeoSpecTopologyCountsExpectation: {\n vertices?: GeoSpecNumericExpectation\n edges?: GeoSpecNumericExpectation\n wires?: GeoSpecNumericExpectation\n faces?: GeoSpecNumericExpectation\n shells?: GeoSpecNumericExpectation\n solids?: GeoSpecNumericExpectation\n compounds?: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// STEP unit expectation accepted by `expectGeo(...).toHaveStepUnits(...)`\nGeoSpecStepUnitsExpectation: {\n unit: string\n}\n\n// Product-structure expectation accepted by `expectGeo(...).toHaveProductStructure(...)`\nGeoSpecProductStructureExpectation: {\n names?: string[]\n count?: GeoSpecNumericExpectation\n}\n\n// Planar-face expectation accepted by `expectGeo(...).toHavePlanarFace(...)`\nGeoSpecPlanarFaceExpectation: {\n normal: GeoSpecPointExpectation\n offset: number\n area?: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Cylindrical-face expectation accepted by `expectGeo(...).toHaveCylindricalFace(...)`\nGeoSpecCylindricalFaceExpectation: {\n radius: number\n axis: 'x' | 'y' | 'z'\n tolerance?: number\n}\n\n// Circular-hole expectation accepted by `expectGeo(...).toHaveCircularHole(...)`\nGeoSpecCircularHoleExpectation: {\n diameter: number\n through?: boolean\n axis?: 'x' | 'y' | 'z'\n center?: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Circular-hole-pattern expectation accepted by `expectGeo(...).toHaveCircularHolePattern(...)`\nGeoSpecCircularHolePatternExpectation: {\n count: number\n holeDiameter: number\n boltCircleDiameter?: number\n axis?: 'x' | 'y' | 'z'\n center?: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Chamfer-feature expectation accepted by `expectGeo(...).toHaveChamferFeature(...)`\nGeoSpecChamferFeatureExpectation: {\n distance: number\n selection?: string\n tolerance?: number\n}\n\n// Fillet-feature expectation accepted by `expectGeo(...).toHaveFilletFeature(...)`\nGeoSpecFilletFeatureExpectation: {\n radius: number\n selection?: string\n tolerance?: number\n}\n\n// Minimum-wall-thickness expectation accepted by `expectGeo(...).toHaveMinimumWallThickness(...)`\nGeoSpecMinimumWallThicknessExpectation: {\n value: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Void-continuity expectation accepted by `expectGeo(...).toHaveVoidContinuity(...)`\nGeoSpecVoidContinuityExpectation: {\n // Ordered waypoints (>= 1) known to lie in the void being proven\n path: GeoSpecVoidWaypoint[]\n // Occurrence names whose solids bound the void\n material?: string[]\n // Minimum required bottleneck cross-section (mm²), sampled\n minCrossSection?: number\n // Points that must NOT be reachable from the path void (isolation claim)\n isolatedFrom?: Vec3[]\n // Region bounded for the proof (subject frame)\n bounds?: {\n min: Vec3;\n max: Vec3;\n }\n}\n\n// Axis-keyed numeric expectation used by high-level geometry matchers\nGeoSpecAxisExpectation: {\n x?: number\n y?: number\n z?: number\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// A pair-specific component-interference check accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferencePairExpectation: {\n left: GeoSpecComponentSelector\n right: GeoSpecComponentSelector\n}\n\n// Intentional component interference allowance accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferenceAllowance: {\n kind?: 'intentionalInterference'\n left: GeoSpecComponentSelector\n right: GeoSpecComponentSelector\n maxVolume?: number\n reason: string\n}\n\n// Assembly occurrence rule accepted by `expectGeo(...).toHaveAssemblyOccurrences(...)`\nGeoSpecAssemblyOccurrenceExpectation: {\n name: GeoSpecComponentSelector\n count?: GeoSpecNumericExpectation\n bounds?: {\n within?: GeoSpecComponentSelector;\n min?: Vec3 | GeoSpecAxisExpectation;\n max?: Vec3 | GeoSpecAxisExpectation;\n center?: GeoSpecPointExpectation;\n tolerance?: number;\n }\n}\n\n// One spatial relationship accepted by `expectGeo(...).toHaveSpatialRelationships(...)`\nGeoSpecSpatialRelationshipExpectation: {\n id?: string\n kind: 'contact' | 'clearance' | 'coaxial' | 'concentric' | 'coplanar' | 'parallel' | 'perpendicular' | 'angle' | 'containment' | 'insertion' | 'interference'\n subject: GeoSpecGeometrySelector\n target: GeoSpecGeometrySelector\n tolerance?: number\n angularToleranceDegrees?: number\n // Expected angle in degrees for `kind\n angleDegrees?: number\n // Declared insertion axis (subject-frame direction) for `kind\n axis?: Vec3\n min?: number\n max?: number\n minVolume?: number\n maxVolume?: number\n reason?: string\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// Diagnostic emitted by GeoSpec loaders, analyzers, and matchers\nGeometryDiagnostic: {\n code: KernelIssueCode | (string & {})\n severity: 'error' | 'warning' | 'info'\n message: string\n suggestion?: string\n spatial?: {\n min?: Vec3;\n max?: Vec3;\n center?: Vec3;\n }\n details?: unknown\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// Point expectation accepted by center and feature matchers\nGeoSpecPointExpectation: Vec3 | GeoSpecAxisExpectation\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n```\n\n## API reference\n\nTo read any other signature, grep the skill directory for the name followed by `(` (or the bare type name): each hit is the declaration line and names its file; read a few lines around it for overloads and parameter notes. `api-index.md` lists all 345 symbols by file.\n\n- `api-functions.md` — Functions\n- `api-constants.md` — Constants\n- `api-types.md` — Types\n- `api-classes.md` — Classes\n\nRead ranges, not whole files. Never copy a reference into a source file.\n"
25
+ "body": "---\nname: geospec-authoring\ndescription: Guides canonical GeoSpec geometry tests in TypeScript or JavaScript for every Tau kernel. Use for *.geospec.ts and *.geospec.js tests.\n---\n\n# GeoSpec authoring\n\nWrite tests before implementation; assert deterministic, measurable properties.\n\n## Test shape\n\n- Use `*.geospec.ts` or `*.geospec.js`. Import `describe`, `it` and `expectGeo` from `geospec`, and `loadModel` from `geospec/model`.\n- Await `loadModel`, then assert synchronously with `expectGeo(model)`. The host supplies the compiled binding; authoring does not select an engine.\n- Await or return the complete model-load chain from the test callback; detached future loads are not part of a completed test.\n- Omit `parameters` for model-code defaults; use `{ file, parameters }` for variants. Loaded subjects are opaque.\n\nCanonical TypeScript authoring:\n\n```ts\nimport { describe, expectGeo, it } from 'geospec';\nimport { loadModel } from 'geospec/model';\n\ndescribe('main geometry', () => {\n it('has the intended envelope', async () => {\n const model = await loadModel({ file: 'main.ts', parameters: { width: 120 } });\n expectGeo(model).toHaveBoundingBox({ size: { x: 120 }, tolerance: 1 });\n });\n});\n```\n\nSpecs are always TypeScript or JavaScript, whatever the model's kernel: `loadModel({ file })` evaluates `main.ts`, `main.tsx`, `main.py`, `main.scad`, `main.kcl` or `main.cs` through Tau's runtime. `test_model` discovers `*.geospec.ts` and `*.geospec.js` only.\n\nEvery matcher takes one expectation object:\n\n- Wrong: `expectGeo(model).toHaveVolume(5000, { tolerance: 5 })`. Correct: `expectGeo(model).toHaveVolume({ value: 5000, tolerance: 5 })`.\n- Wrong: `await expectGeo(model)…`. Correct: await `loadModel`, then assert synchronously.\n- Wrong: exact-feature matchers on a mesh kernel (Manifold, JSCAD, OpenSCAD, PicoGK, PicoVoxel). Correct: `toBeValidBrep`, `toHaveTopologyCounts`, the face, hole, pattern, chamfer, fillet and wall-thickness matchers, `toHaveStepUnits`, `toHaveProductStructure` and `toHaveVoidContinuity` need STEP BRep evidence and report unsupported on meshes; assert meshes with bounds, volume, surface area, connected components and `toBeWatertight`.\n\n## Loads and budgets\n\n- GeoSpec has no `beforeAll`. Write each `loadModel` inside its own `it`: within one run, identical loads (same file or code, parameters, format, tolerances and region) share one export and one admission. Each load keeps its own `loadId` and records `reused: true`, and a file is reused only while its source closure is unchanged.\n- `loadModel({ file, workUnitLimit })` sets the default claim budget for that subject, and `expectGeo(model, { workUnitLimit })` overrides it for one chain. The budget is a positive integer no greater than 512000000. A `MATCHER_TIMEOUT` refusal reports `details.limit` and, when measured, `details.used`; raise the budget within the bound or simplify the model, and never treat the refusal as a pass.\n- `loadModel({ file, region: { min: [x, y, z], max: [x, y, z] } })` makes a separate claim about one millimetre box, never a stand-in for a whole-model claim. The kernel crops before export (PicoVoxel trims Voxels parts), and other kernels, STEP loads and unconfirmed crops refuse with a typed diagnostic. A cropped subject answers only bounding box, volume, surface area, mass and center of mass of its region, and its assertions record the region. Every other matcher on it refuses with `GEOSPEC_REGION_WHOLE_MODEL_CLAIM`. Name the region in the test, and keep whole-model claims on whole-model loads.\n- A host or engine limit leaves a claim undecided, never false. Keep the model as designed: never coarsen, shrink or crop it to fit. Raise `workUnitLimit` or `testTimeout` within the host's maximum, which the host can raise, or report the claim as not decided at this capacity.\n- `toHaveCenterOfMass({ point, tolerance })` takes an exact point. Per-axis ranges such as `{ y: { greaterThan: 1 } }` are refused.\n\n## Coverage\n\nBounds and physical properties are insufficient. Cover major components, named features, variants, dimensions/positions, disjoint counts, closure, interference and supported exact features. Explicitly report unsupported requirements; proxies cannot fulfill them.\n\n- `toHaveBoundingBox`: size/position.\n- `toHaveConnectedComponents`: qualified material connectivity; visual touching cannot justify tolerance changes.\n- `toBeWatertight`: closed edge incidence, not manifold validity, fusion or material connectivity. Bounds prove none of these. Missing evidence is unsupported.\n- `toHaveSurfaceArea`, `toHaveVolume`, `toHaveCenterOfMass`, `toHaveMass`: physical measurements.\n- `toHaveNoComponentInterference`: overlap; allow deliberate press fits.\n- `loadModel({ file, format: 'step' })`: use BRep evidence for supported topology and feature assertions. Missing occurrence, interference, clearance, containment, lumen, wall, section or motion evidence is unsupported, not a pass or an envelope substitute.\n\nTest assemblies and independent units; preserve sibling coverage when adding files. Non-top-level geometry needs kernel export/invocation, not test removal.\n\nFix geometry at its root; never weaken tolerances, delete assertions, or reduce detail to pass.\n\n`public-api-index.md` covers the host and framework API; authoring needs only the Core API below and `api-*` files.\n\n## Core API\n\nThe symbols real Tau models use most, as verbatim declarations. Anything not shown is in the API reference below.\n\n### Functions\n\n```ts\n// Start a geometry assertion chain\nexport declare function expectGeo(subject: GeoSpecSubject, options?: GeoSpecExpectOptions): GeoSpecMatcher;\n\n// A model admitted by one live GeoSpec host scope\nGeoSpecSubject: {\n readonly [subjectBrand]: true\n}\n\n// Per-chain options accepted by `expectGeo(subject, options)`\nGeoSpecExpectOptions: {\n // Deterministic work-unit budget for every claim in this chain\n readonly workUnitLimit?: number\n}\n\n// Load a CAD model into GeoSpec evidence\nexport declare function loadModel<Code extends Record<string, string> = Record<string, string>>(_options: LoadModelOptions<Code>): Promise<GeoSpecSubject>;\n\n// Options accepted by {@link import ('./load-model.js').loadModel}\nLoadModelOptions: LoadModelSourceOptions | LoadModelCodeOptions<Code> | LoadModelFileOptions\n // Geometry format to export\n format?: GeoSpecModelFormat\n // Explicit parameters passed to the runtime\n parameters?: Record<string, unknown>\n // STEP reader strategy used for STEP exports\n stepStreaming?: StepStreamingMode\n // Whether STEP loading should also produce mesh evidence\n mesh?: boolean\n // Linear tolerance used while meshing exact BRep evidence\n meshLinearTolerance?: number\n // Angular tolerance in degrees used while meshing exact BRep evidence\n meshAngularToleranceDegrees?: number\n // Default work-unit budget for every claim on this subject\n workUnitLimit?: number\n\n// Geometry formats accepted by {@link import ('./load-model.js').loadModel}\nGeoSpecModelFormat: MeshFileFormat | 'step' | 'stp'\n```\n\n### Types\n\n```ts\n// Assertion chain returned by `expectGeo(subject)`\nGeoSpecMatcher: {\n // Core-owned negation\n readonly not: Omit<GeoSpecMatcher, 'not'>\n // Assert the fixed rational plate contract\n toSatisfyRationalPlate(): GeoSpecAssertion;\n // Assert the fixed parallel-plane distance contract\n toSatisfyParallelPlaneDistance(): GeoSpecAssertion;\n // Assert axis-aligned bounds, size, or center for a loaded geometry subject\n toHaveBoundingBox(first: Vec3 | GeoSpecBoundingBoxExpectation, second?: Vec3): GeoSpecAssertion;\n // Assert how many spatially disjoint chunks the mesh contains\n toHaveConnectedComponents(expected: GeoSpecConnectedComponentsExpectation): GeoSpecAssertion;\n // Assert closed mesh edge incidence\n toBeWatertight(): GeoSpecAssertion;\n // Assert that separate assembly components do not occupy the same solid volume\n toHaveNoComponentInterference(expected?: GeoSpecComponentInterferenceExpectation): GeoSpecAssertion;\n // Assert that named assembly occurrences exist with expected counts and bounds\n toHaveAssemblyOccurrences(expected: GeoSpecAssemblyOccurrencesExpectation): GeoSpecAssertion;\n // Assert that selected entities satisfy declared spatial relationships\n toHaveSpatialRelationships(expected: GeoSpecSpatialRelationshipsExpectation): GeoSpecAssertion;\n // Assert rendered mesh evidence is internally trustworthy for downstream checks\n toHaveMeshIntegrity(expected: GeoSpecMeshIntegrityExpectation): GeoSpecAssertion;\n // Assert that the subject carries no diagnostics at the rejected severities\n toHaveNoDiagnostics(expected?: GeoSpecNoDiagnosticsExpectation): GeoSpecAssertion;\n // Assert total surface area, preferring exact BRep mass properties when available\n toHaveSurfaceArea(expected: GeoSpecSurfaceAreaExpectation): GeoSpecAssertion;\n // Assert enclosed volume, preferring exact BRep mass properties when available\n toHaveVolume(expected: GeoSpecVolumeExpectation): GeoSpecAssertion;\n // Assert mass derived from exact mass properties or volume times density\n toHaveMass(expected: GeoSpecMassExpectation): GeoSpecAssertion;\n // Assert the center of mass or mesh-derived centroid for a closed subject\n toHaveCenterOfMass(expected: GeoSpecCenterOfMassExpectation): GeoSpecAssertion;\n // Assert that exact BRep evidence reports a valid shape\n toBeValidBrep(expected?: GeoSpecValidBrepExpectation): GeoSpecAssertion;\n // Assert exact BRep topology counts\n toHaveTopologyCounts(expected: GeoSpecTopologyCountsExpectation): GeoSpecAssertion;\n // Assert the STEP unit evidence\n toHaveStepUnits(expected: GeoSpecStepUnitsExpectation): GeoSpecAssertion;\n // Assert STEP product-structure evidence\n toHaveProductStructure(expected: GeoSpecProductStructureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a planar face matching the requested constraints\n toHavePlanarFace(expected: GeoSpecPlanarFaceExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a cylindrical face with the requested radius and axis\n toHaveCylindricalFace(expected: GeoSpecCylindricalFaceExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a circular hole matching diameter, center, and axis\n toHaveCircularHole(expected: GeoSpecCircularHoleExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a repeated circular-hole pattern\n toHaveCircularHolePattern(expected: GeoSpecCircularHolePatternExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a chamfer feature with the requested distance\n toHaveChamferFeature(expected: GeoSpecChamferFeatureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence contains a fillet feature with the requested radius\n toHaveFilletFeature(expected: GeoSpecFilletFeatureExpectation): GeoSpecAssertion;\n // Assert that BRep evidence reports a minimum wall thickness satisfying the expectation\n toHaveMinimumWallThickness(expected: GeoSpecMinimumWallThicknessExpectation): GeoSpecAssertion;\n // Assert that the declared waypoints share one connected void, stay isolated from the declared…\n toHaveVoidContinuity(expected: GeoSpecVoidContinuityExpectation): GeoSpecAssertion;\n}\n\n// Numeric 3D vector\nVec3: readonly [number, number, number]\n\n// Bounding-box expectation accepted by `expectGeo(...).toHaveBoundingBox(...)`\nGeoSpecBoundingBoxExpectation: {\n min?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n max?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n size?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n center?: Vec3 | Partial<Record<keyof GeoSpecAxisExpectation, GeoSpecNumericExpectation>>\n tolerance?: number\n}\n\n// Connected-components expectation accepted by `expectGeo(...).toHaveConnectedComponents(...)`\nGeoSpecConnectedComponentsExpectation: {\n count: number\n tolerance?: number\n toleranceMm?: number\n}\n\n// Component-interference expectation accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferenceExpectation: {\n tolerance?: number\n pairs?: GeoSpecComponentInterferencePairExpectation[]\n allowances?: GeoSpecComponentInterferenceAllowance[]\n}\n\n// Assembly occurrence expectation accepted by `expectGeo(...).toHaveAssemblyOccurrences(...)`\nGeoSpecAssemblyOccurrencesExpectation: {\n occurrences: GeoSpecAssemblyOccurrenceExpectation[]\n uniqueNames?: boolean\n}\n\n// Spatial relationship expectation accepted by `expectGeo(...).toHaveSpatialRelationships(...)`\nGeoSpecSpatialRelationshipsExpectation: {\n relationships: GeoSpecSpatialRelationshipExpectation[]\n}\n\n// Mesh integrity expectation accepted by `expectGeo(...).toHaveMeshIntegrity(...)`\nGeoSpecMeshIntegrityExpectation: {\n finitePositions?: boolean\n degenerateTriangles?: {\n count?: number;\n maxCount?: number;\n areaTolerance?: number;\n }\n duplicateFaces?: {\n count?: number;\n maxCount?: number;\n }\n watertight?: boolean\n triangleCount?: GeoSpecNumericExpectation\n}\n\n// Diagnostic severities rejected by `expectGeo(...).toHaveNoDiagnostics(...)`\nGeoSpecNoDiagnosticsExpectation: {\n severities?: Array<GeometryDiagnostic['severity']>\n}\n\n// Surface-area expectation accepted by `expectGeo(...).toHaveSurfaceArea(...)`\nGeoSpecSurfaceAreaExpectation: {\n value: number | GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Volume expectation accepted by `expectGeo(...).toHaveVolume(...)`\nGeoSpecVolumeExpectation: {\n value: number | GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Mass expectation accepted by `expectGeo(...).toHaveMass(...)`\nGeoSpecMassExpectation: {\n value: number | GeoSpecNumericExpectation\n density?: number\n tolerance?: number\n}\n\n// Center-of-mass expectation accepted by `expectGeo(...).toHaveCenterOfMass(...)`\nGeoSpecCenterOfMassExpectation: {\n // Exact expected point in millimetres\n point: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Exact BRep validity expectation accepted by `expectGeo(...).toBeValidBrep(...)`\nGeoSpecValidBrepExpectation: {\n maxTolerance?: number\n freeBounds?: {\n count?: GeoSpecNumericExpectation;\n }\n minEdgeLength?: number\n sameParameter?: boolean\n closedShells?: boolean\n closedWires?: boolean\n}\n\n// Topology-count expectation accepted by `expectGeo(...).toHaveTopologyCounts(...)`\nGeoSpecTopologyCountsExpectation: {\n vertices?: GeoSpecNumericExpectation\n edges?: GeoSpecNumericExpectation\n wires?: GeoSpecNumericExpectation\n faces?: GeoSpecNumericExpectation\n shells?: GeoSpecNumericExpectation\n solids?: GeoSpecNumericExpectation\n compounds?: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// STEP unit expectation accepted by `expectGeo(...).toHaveStepUnits(...)`\nGeoSpecStepUnitsExpectation: {\n unit: string\n}\n\n// Product-structure expectation accepted by `expectGeo(...).toHaveProductStructure(...)`\nGeoSpecProductStructureExpectation: {\n names?: string[]\n count?: GeoSpecNumericExpectation\n}\n\n// Planar-face expectation accepted by `expectGeo(...).toHavePlanarFace(...)`\nGeoSpecPlanarFaceExpectation: {\n normal: GeoSpecPointExpectation\n offset: number\n area?: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Cylindrical-face expectation accepted by `expectGeo(...).toHaveCylindricalFace(...)`\nGeoSpecCylindricalFaceExpectation: {\n radius: number\n axis: 'x' | 'y' | 'z'\n tolerance?: number\n}\n\n// Circular-hole expectation accepted by `expectGeo(...).toHaveCircularHole(...)`\nGeoSpecCircularHoleExpectation: {\n diameter: number\n through?: boolean\n axis?: 'x' | 'y' | 'z'\n center?: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Circular-hole-pattern expectation accepted by `expectGeo(...).toHaveCircularHolePattern(...)`\nGeoSpecCircularHolePatternExpectation: {\n count: number\n holeDiameter: number\n boltCircleDiameter?: number\n axis?: 'x' | 'y' | 'z'\n center?: GeoSpecPointExpectation\n tolerance?: number\n}\n\n// Chamfer-feature expectation accepted by `expectGeo(...).toHaveChamferFeature(...)`\nGeoSpecChamferFeatureExpectation: {\n distance: number\n selection?: string\n tolerance?: number\n}\n\n// Fillet-feature expectation accepted by `expectGeo(...).toHaveFilletFeature(...)`\nGeoSpecFilletFeatureExpectation: {\n radius: number\n selection?: string\n tolerance?: number\n}\n\n// Minimum-wall-thickness expectation accepted by `expectGeo(...).toHaveMinimumWallThickness(...)`\nGeoSpecMinimumWallThicknessExpectation: {\n value: GeoSpecNumericExpectation\n tolerance?: number\n}\n\n// Void-continuity expectation accepted by `expectGeo(...).toHaveVoidContinuity(...)`\nGeoSpecVoidContinuityExpectation: {\n // Ordered waypoints (>= 1) known to lie in the void being proven\n path: GeoSpecVoidWaypoint[]\n // Occurrence names whose solids bound the void\n material?: string[]\n // Minimum required bottleneck cross-section (mm²), sampled\n minCrossSection?: number\n // Points that must NOT be reachable from the path void (isolation claim)\n isolatedFrom?: Vec3[]\n // Region bounded for the proof (subject frame)\n bounds?: {\n min: Vec3;\n max: Vec3;\n }\n}\n\n// Axis-keyed numeric expectation used by high-level geometry matchers\nGeoSpecAxisExpectation: {\n x?: number\n y?: number\n z?: number\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// A pair-specific component-interference check accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferencePairExpectation: {\n left: GeoSpecComponentSelector\n right: GeoSpecComponentSelector\n}\n\n// Intentional component interference allowance accepted by `expectGeo(...).toHaveNoComponentInterference(...)`\nGeoSpecComponentInterferenceAllowance: {\n kind?: 'intentionalInterference'\n left: GeoSpecComponentSelector\n right: GeoSpecComponentSelector\n maxVolume?: number\n reason: string\n}\n\n// Assembly occurrence rule accepted by `expectGeo(...).toHaveAssemblyOccurrences(...)`\nGeoSpecAssemblyOccurrenceExpectation: {\n name: GeoSpecComponentSelector\n count?: GeoSpecNumericExpectation\n bounds?: {\n within?: GeoSpecComponentSelector;\n min?: Vec3 | GeoSpecAxisExpectation;\n max?: Vec3 | GeoSpecAxisExpectation;\n center?: GeoSpecPointExpectation;\n tolerance?: number;\n }\n}\n\n// One spatial relationship accepted by `expectGeo(...).toHaveSpatialRelationships(...)`\nGeoSpecSpatialRelationshipExpectation: {\n id?: string\n kind: 'contact' | 'clearance' | 'coaxial' | 'concentric' | 'coplanar' | 'parallel' | 'perpendicular' | 'angle' | 'containment' | 'insertion' | 'interference'\n subject: GeoSpecGeometrySelector\n target: GeoSpecGeometrySelector\n tolerance?: number\n angularToleranceDegrees?: number\n // Expected angle in degrees for `kind\n angleDegrees?: number\n // Declared insertion axis (subject-frame direction) for `kind\n axis?: Vec3\n min?: number\n max?: number\n minVolume?: number\n maxVolume?: number\n reason?: string\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// Diagnostic emitted by GeoSpec loaders, analyzers, and matchers\nGeometryDiagnostic: {\n code: KernelIssueCode | (string & {})\n severity: 'error' | 'warning' | 'info'\n message: string\n suggestion?: string\n spatial?: {\n min?: Vec3;\n max?: Vec3;\n center?: Vec3;\n }\n details?: unknown\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n\n// Point expectation accepted by center and feature matchers\nGeoSpecPointExpectation: Vec3 | GeoSpecAxisExpectation\n\n// Shared scalar expectation used by geometry measurements\nGeoSpecNumericExpectation: number | {\n value?: number;\n greaterThan?: number;\n greaterThanOrEqual?: number;\n lessThan?: number;\n lessThanOrEqual?: number;\n}\n```\n\n## API reference\n\nTo read any other signature, grep the skill directory for the name followed by `(` (or the bare type name): each hit is the declaration line and names its file; read a few lines around it for overloads and parameter notes. `api-index.md` lists all 360 symbols by file.\n\n- `api-functions.md` — Functions\n- `api-constants.md` — Constants\n- `api-types.md` — Types\n- `api-classes.md` — Classes\n\nRead ranges, not whole files. Never copy a reference into a source file.\n"
25
26
  }
26
27
  ]
27
28
  }
@@ -1,5 +1,5 @@
1
1
  import { GeoSpecSubject } from "../model/subject.mjs";
2
- import { GeoSpecAssertion, GeoSpecMatcher } from "../runner/types.mjs";
2
+ import { GeoSpecAssertion, GeoSpecExpectOptions, GeoSpecMatcher } from "../runner/types.mjs";
3
3
  import { GeoSpecMatcherName } from "../engine/matchers.mjs";
4
4
  import { GeoSpecCanonicalClaimReport, GeoSpecClaimPolarity, GeoSpecNativeEngine, GeoSpecNativeEvidenceProfile, GeoSpecNativeSubject, GeoSpecQueryCapability } from "../engine/client.mjs";
5
5
  import { JSONValue } from "@taucad/runtime/types";
@@ -21,7 +21,11 @@ type GeoSpecAssertionMatchers = GeoSpecMatcherMethods<GeoSpecCanonicalClaimRepor
21
21
  };
22
22
  /** Runner-independent native assertion client. @public */
23
23
  type GeoSpecAssertionClient = {
24
- expectGeo(subject: GeoSpecNativeSubject | GeoSpecSubject): GeoSpecAssertionMatchers;
24
+ /**
25
+ * Start a chain; `options.workUnitLimit` overrides the client budget for its claims.
26
+ * Without one, a loaded subject's `loadModel` budget applies, then the client's.
27
+ */
28
+ expectGeo(subject: GeoSpecNativeSubject | GeoSpecSubject, options?: GeoSpecExpectOptions): GeoSpecAssertionMatchers;
25
29
  query(options: MinimumDistanceQuery): Promise<MinimumDistanceResult>;
26
30
  query(options: GeoSpecQueryOptions): Promise<GeoSpecCanonicalClaimReport>;
27
31
  };
@@ -88,6 +92,12 @@ type GeoSpecAssertionClientOptions = {
88
92
  readonly evidenceProfile?: GeoSpecNativeEvidenceProfile;
89
93
  readonly subjectSlot?: string;
90
94
  readonly workUnitLimit?: number;
95
+ /**
96
+ * The largest `workUnitLimit` a spec may author in `loadModel` or `expectGeo` options. Host
97
+ * policy: omitted uses `geoSpecMaximumWorkUnitLimit`, and a host provisioned for larger claims
98
+ * raises it. Must be a positive safe integer.
99
+ */
100
+ readonly maximumWorkUnitLimit?: number;
91
101
  };
92
102
  /** A non-passed core assertion report surfaced by standalone `expectGeo`. @public */
93
103
  declare class GeoSpecAssertionError extends Error {
@@ -1,2 +1,2 @@
1
- import{geoSpecMatcherDescriptors as e,normalizeGeoSpecExpected as t}from"../engine/matchers.mjs";import{evaluateGeoSpecNativeClaim as n,evaluateGeoSpecNativeQuery as r,resolveGeoSpecNativeWorkUnitLimit as i,supportsGeoSpecNativeMinimumDistance as a}from"../engine/client.mjs";import{resolveGeoSpecSubject as o}from"../model/subject.mjs";const s=(e,t)=>{if(typeof e!=`object`||!e||Array.isArray(e))return;let{fact:n}=e;if(typeof n!=`object`||!n||Array.isArray(n))return;let{distance:r,points:i,occurrences:a,subjectHash:o}=n,s=e=>Array.isArray(e)&&e.length===3&&e.every(e=>typeof e==`number`&&Number.isFinite(e));if(!(e.profile!==`geospec-minimum-distance-v1`||n.source!==`ap242`||n.assurance!==`exact-brep`||n.unit!==`mm`||n.coordinateSystem!==`z-up`||n.algorithmProfile!==`geospec-minimum-distance-v1`||typeof o!=`string`||!/^[0-9a-f]{64}$/u.test(o)||o!==(t.subject.subjectHash??t.subject.contentHash)||typeof r!=`number`||!Number.isFinite(r)||r<0||!Array.isArray(a)||a.length!==2||a[0]!==t.payload.pair[0].occurrencePath||a[1]!==t.payload.pair[1].occurrencePath||!Array.isArray(i)||i.length!==2||!s(i[0])||!s(i[1])))return{source:`ap242`,assurance:`exact-brep`,unit:`mm`,coordinateSystem:`z-up`,subjectHash:o,algorithmProfile:`geospec-minimum-distance-v1`,occurrences:[a[0],a[1]],distance:r,points:[i[0],i[1]]}},c=(e,t)=>{if(e.status===`passed`){let n=s(e.evidence,t);return n===void 0?{status:`interrupted`,code:`engine-error`,message:`Native minimum returned malformed evidence.`}:{status:`complete`,fact:n}}let n=e.diagnostics.find(e=>typeof e==`object`&&!!e&&!Array.isArray(e)),r=typeof n==`object`&&n&&!Array.isArray(n)?n.code:void 0,i=typeof n==`object`&&n&&!Array.isArray(n)&&typeof n.message==`string`?n.message:`Native minimum is unavailable.`;return r===`GEOSPEC_INVALID_SELECTION`?{status:`refused`,code:`invalid-selection`,message:i}:r===`MATCHER_TIMEOUT`?{status:`refused`,code:`work-limit`,message:i}:r===`GEOSPEC_UNSUPPORTED_EVIDENCE`||r===`GEOSPEC_EVIDENCE_UNSUPPORTED`?{status:`refused`,code:`unsupported-evidence`,message:i}:{status:`interrupted`,code:`engine-error`,message:i}};var l=class extends Error{canonicalClaim;canonicalPlan;canonicalResult;claim;diagnostics;report;result;constructor(e){let t=e.diagnostics.flatMap(e=>{if(typeof e!=`object`||!e||Array.isArray(e))return[];let{message:t}=e;return typeof t==`string`?[t]:[]});super(t.join(`
2
- `)||`GeoSpec assertion '${e.claimId}' ended with status '${e.status}'.`),this.name=`GeoSpecAssertionError`,this.report=e,this.diagnostics=e.diagnostics,this.claim=e.claim,this.result=e.result,this.canonicalClaim=e.canonicalClaim,this.canonicalPlan=e.canonicalPlan,this.canonicalResult=e.canonicalResult}};const u=n=>{let r={};for(let i of Object.keys(e)){let a=e[i];r[i]=(...e)=>{if(a.expected===`true`&&e.length>0)throw TypeError(`GeoSpec matcher ${i} does not accept arguments.`);return n.invoke({arguments:e,expected:t(a.expected,e),kind:a.kind,matcher:i,polarity:n.polarity,subject:n.subject})}}return r},d=e=>{let t=0,s=e.subjectSlot??`subject`,d,f=()=>(d??=i(e.engine,e.workUnitLimit),d),p=(r,i,a)=>u({subject:r,polarity:i,invoke:o=>{a(),t+=1;let c={claimId:e.claimId?.(o.matcher,t)??`geospec-claim-${t}`,engine:e.engine,...e.evidenceProfile===void 0?{}:{evidenceProfile:e.evidenceProfile},polarity:i,subject:r,subjectSlot:s,workUnitLimit:f()},u=n({...c,arguments:o.arguments,capability:o.matcher,kind:o.kind});if(u.status!==`passed`)throw new l(u);return u}});async function m(n){let i=`claimId`in n?n.claimId??`geospec-claim-${++t}`:`geospec-claim-${++t}`;if(n.capability===`minimumDistance`){let t=n.payload?.pair,r=e=>{if(!(typeof e!=`object`||!e||!(`occurrencePath`in e)))return typeof e.occurrencePath==`string`&&e.occurrencePath.length>0?e.occurrencePath:void 0};if(!Array.isArray(t)||t.length!==2||r(t[0])===void 0||r(t[1])===void 0||r(t[0])===r(t[1]))return{status:`refused`,code:`invalid-selection`,message:`Select two distinct resolved AP242 occurrence paths.`};try{if(!a(e.engine))return{status:`refused`,code:`unsupported-evidence`,message:`This native engine does not advertise the complete minimum-distance profile.`}}catch{return{status:`refused`,code:`unsupported-evidence`,message:`This native engine cannot negotiate the complete minimum-distance profile.`}}}try{let t=r({capability:n.capability,claimId:i,engine:e.engine,...e.evidenceProfile===void 0?{}:{evidenceProfile:e.evidenceProfile},payload:n.payload,subject:n.subject,subjectSlot:s,workUnitLimit:f()});return n.capability===`minimumDistance`?c(t,n):t}catch(e){if(n.capability!==`minimumDistance`)throw e;return{status:`interrupted`,code:`engine-error`,message:e instanceof Error?e.message:`Native minimum query failed.`}}}return{query:m,expectGeo(t){let n=`subjectHash`in t||`contentHash`in t,r=n?t:o(t,e.engine).identity,i=()=>{n||o(t,e.engine)},a=p(r,`positive`,i);return Object.assign(a,{not:p(r,`negative`,i)})}}};export{l as GeoSpecAssertionError,d as createGeoSpecAssertionClient,u as createGeoSpecMatcherMethods};
1
+ import{geoSpecMatcherDescriptors as e,normalizeGeoSpecExpected as t}from"../engine/matchers.mjs";import{assertGeoSpecAuthoredWorkUnitLimit as n,evaluateGeoSpecNativeClaim as r,evaluateGeoSpecNativeQuery as i,resolveGeoSpecMaximumWorkUnitLimit as a,resolveGeoSpecNativeWorkUnitLimit as o,supportsGeoSpecNativeMinimumDistance as s}from"../engine/client.mjs";import{resolveGeoSpecSubject as c}from"../model/subject.mjs";const l=(e,t)=>{if(typeof e!=`object`||!e||Array.isArray(e))return;let{fact:n}=e;if(typeof n!=`object`||!n||Array.isArray(n))return;let{distance:r,points:i,occurrences:a,subjectHash:o}=n,s=e=>Array.isArray(e)&&e.length===3&&e.every(e=>typeof e==`number`&&Number.isFinite(e));if(!(e.profile!==`geospec-minimum-distance-v1`||n.source!==`ap242`||n.assurance!==`exact-brep`||n.unit!==`mm`||n.coordinateSystem!==`z-up`||n.algorithmProfile!==`geospec-minimum-distance-v1`||typeof o!=`string`||!/^[0-9a-f]{64}$/u.test(o)||o!==(t.subject.subjectHash??t.subject.contentHash)||typeof r!=`number`||!Number.isFinite(r)||r<0||!Array.isArray(a)||a.length!==2||a[0]!==t.payload.pair[0].occurrencePath||a[1]!==t.payload.pair[1].occurrencePath||!Array.isArray(i)||i.length!==2||!s(i[0])||!s(i[1])))return{source:`ap242`,assurance:`exact-brep`,unit:`mm`,coordinateSystem:`z-up`,subjectHash:o,algorithmProfile:`geospec-minimum-distance-v1`,occurrences:[a[0],a[1]],distance:r,points:[i[0],i[1]]}},u=(e,t)=>{if(e.status===`passed`){let n=l(e.evidence,t);return n===void 0?{status:`interrupted`,code:`engine-error`,message:`Native minimum returned malformed evidence.`}:{status:`complete`,fact:n}}let n=e.diagnostics.find(e=>typeof e==`object`&&!!e&&!Array.isArray(e)),r=typeof n==`object`&&n&&!Array.isArray(n)?n.code:void 0,i=typeof n==`object`&&n&&!Array.isArray(n)&&typeof n.message==`string`?n.message:`Native minimum is unavailable.`;return r===`GEOSPEC_INVALID_SELECTION`?{status:`refused`,code:`invalid-selection`,message:i}:r===`MATCHER_TIMEOUT`?{status:`refused`,code:`work-limit`,message:i}:r===`GEOSPEC_UNSUPPORTED_EVIDENCE`||r===`GEOSPEC_EVIDENCE_UNSUPPORTED`?{status:`refused`,code:`unsupported-evidence`,message:i}:{status:`interrupted`,code:`engine-error`,message:i}};var d=class extends Error{canonicalClaim;canonicalPlan;canonicalResult;claim;diagnostics;report;result;constructor(e){let t=e.diagnostics.flatMap(e=>{if(typeof e!=`object`||!e||Array.isArray(e))return[];let{message:t}=e;return typeof t==`string`?[t]:[]});super(t.join(`
2
+ `)||`GeoSpec assertion '${e.claimId}' ended with status '${e.status}'.`),this.name=`GeoSpecAssertionError`,this.report=e,this.diagnostics=e.diagnostics,this.claim=e.claim,this.result=e.result,this.canonicalClaim=e.canonicalClaim,this.canonicalPlan=e.canonicalPlan,this.canonicalResult=e.canonicalResult}};const f=[`x`,`y`,`z`],p=e=>{if(typeof e!=`object`||!e||!(`point`in e))return;let{point:t}=e,n=Array.isArray(t)?t.map((e,t)=>[f[t]??String(t),e]):typeof t==`object`&&t?Object.entries(t):[];if(n.length===0&&!(typeof t==`object`&&t))throw TypeError(`GeoSpec matcher toHaveCenterOfMass needs point as [x, y, z] or { x, y, z } in millimetres, with an optional tolerance.`);for(let[e,t]of n)if(!(typeof t==`number`&&Number.isFinite(t)))throw TypeError(typeof t==`object`&&t&&!Array.isArray(t)?`GeoSpec matcher toHaveCenterOfMass point.${e} must be a finite number in millimetres: per-axis ranges such as { greaterThan } are not supported for center of mass. Assert the expected point with a tolerance instead.`:`GeoSpec matcher toHaveCenterOfMass point.${e} must be a finite number in millimetres.`)},m=n=>{let r={};for(let i of Object.keys(e)){let a=e[i];r[i]=(...e)=>{if(a.expected===`true`&&e.length>0)throw TypeError(`GeoSpec matcher ${i} does not accept arguments.`);return i===`toHaveCenterOfMass`&&p(e[0]),n.invoke({arguments:e,expected:t(a.expected,e),kind:a.kind,matcher:i,polarity:n.polarity,subject:n.subject})}}return r},h=e=>{let t=0,l=e.subjectSlot??`subject`,f,p=()=>(f??=a(e.maximumWorkUnitLimit),f),h,g=()=>(h??=o(e.engine,e.workUnitLimit),h),_=(n,i,a)=>m({subject:n,polarity:i,invoke:o=>{a.validate(),t+=1;let s={claimId:e.claimId?.(o.matcher,t)??`geospec-claim-${t}`,engine:e.engine,...e.evidenceProfile===void 0?{}:{evidenceProfile:e.evidenceProfile},polarity:i,subject:n,subjectSlot:l,workUnitLimit:a.workUnitLimit??g(),maximumWorkUnitLimit:p()},c=r({...s,arguments:o.arguments,capability:o.matcher,kind:o.kind});if(c.status!==`passed`)throw new d(c);return c}});async function v(n){let r=`claimId`in n?n.claimId??`geospec-claim-${++t}`:`geospec-claim-${++t}`;if(n.capability===`minimumDistance`){let t=n.payload?.pair,r=e=>{if(!(typeof e!=`object`||!e||!(`occurrencePath`in e)))return typeof e.occurrencePath==`string`&&e.occurrencePath.length>0?e.occurrencePath:void 0};if(!Array.isArray(t)||t.length!==2||r(t[0])===void 0||r(t[1])===void 0||r(t[0])===r(t[1]))return{status:`refused`,code:`invalid-selection`,message:`Select two distinct resolved AP242 occurrence paths.`};try{if(!s(e.engine))return{status:`refused`,code:`unsupported-evidence`,message:`This native engine does not advertise the complete minimum-distance profile.`}}catch{return{status:`refused`,code:`unsupported-evidence`,message:`This native engine cannot negotiate the complete minimum-distance profile.`}}}try{let t=i({capability:n.capability,claimId:r,engine:e.engine,...e.evidenceProfile===void 0?{}:{evidenceProfile:e.evidenceProfile},payload:n.payload,subject:n.subject,subjectSlot:l,workUnitLimit:g()});return n.capability===`minimumDistance`?u(t,n):t}catch(e){if(n.capability!==`minimumDistance`)throw e;return{status:`interrupted`,code:`engine-error`,message:e instanceof Error?e.message:`Native minimum query failed.`}}}return{query:v,expectGeo(t,r){n(r?.workUnitLimit,`expectGeo workUnitLimit`,p());let i=`subjectHash`in t||`contentHash`in t,a=i?void 0:c(t,e.engine),o=i?t:a.identity,s={validate:()=>{i||c(t,e.engine)},workUnitLimit:r?.workUnitLimit??a?.load?.workUnitLimit},l=_(o,`positive`,s);return Object.assign(l,{not:_(o,`negative`,s)})}}};export{d as GeoSpecAssertionError,h as createGeoSpecAssertionClient,m as createGeoSpecMatcherMethods};
@@ -1,5 +1,5 @@
1
1
  import { GeoSpecFixedMatcherDescriptor, GeoSpecMatcherName, geoSpecMatcherDescriptors } from "../engine/matchers.mjs";
2
- import { GeoSpecCanonicalClaimReport, GeoSpecNativeClaimEvaluation, GeoSpecNativeEngine, GeoSpecNativeEvidenceProfile, GeoSpecNativeQueryOptions, GeoSpecNativeSubject, GeoSpecQueryCapability, evaluateGeoSpecNativeQuery } from "../engine/client.mjs";
2
+ import { GeoSpecCanonicalClaimReport, GeoSpecNativeClaimEvaluation, GeoSpecNativeEngine, GeoSpecNativeEvidenceProfile, GeoSpecNativeQueryOptions, GeoSpecNativeSubject, GeoSpecQueryCapability, evaluateGeoSpecNativeQuery, geoSpecMaximumWorkUnitLimit, resolveGeoSpecMaximumWorkUnitLimit } from "../engine/client.mjs";
3
3
  import { GeoSpecAssertionClient, GeoSpecAssertionClientOptions, GeoSpecAssertionError, GeoSpecAssertionMatchers, GeoSpecAuthoringInvocation, GeoSpecMatcherMethods, GeoSpecQueryOptions, MinimumDistanceFact, MinimumDistanceQuery, MinimumDistanceResult, createGeoSpecAssertionClient, createGeoSpecMatcherMethods } from "./client.mjs";
4
4
  import { GeoSpecPmiFaceAssociation, GeoSpecPmiField, GeoSpecPmiInventory, GeoSpecPmiLimits, GeoSpecPmiNumber, GeoSpecPmiQueryPayload, GeoSpecPmiQueryValue, GeoSpecPmiRawEntity, GeoSpecPmiRecord, GeoSpecPmiShapeReference } from "../engine/pmi.types.mjs";
5
- export { type GeoSpecAssertionClient, type GeoSpecAssertionClientOptions, GeoSpecAssertionError, type GeoSpecAssertionMatchers, type GeoSpecAuthoringInvocation, type GeoSpecCanonicalClaimReport, type GeoSpecFixedMatcherDescriptor, type GeoSpecMatcherMethods, type GeoSpecMatcherName, type GeoSpecNativeClaimEvaluation, type GeoSpecNativeEngine, type GeoSpecNativeEvidenceProfile, type GeoSpecNativeQueryOptions, type GeoSpecNativeSubject, type GeoSpecPmiFaceAssociation, type GeoSpecPmiField, type GeoSpecPmiInventory, type GeoSpecPmiLimits, type GeoSpecPmiNumber, type GeoSpecPmiQueryPayload, type GeoSpecPmiQueryValue, type GeoSpecPmiRawEntity, type GeoSpecPmiRecord, type GeoSpecPmiShapeReference, type GeoSpecQueryCapability, type GeoSpecQueryOptions, type MinimumDistanceFact, type MinimumDistanceQuery, type MinimumDistanceResult, createGeoSpecAssertionClient, createGeoSpecMatcherMethods, evaluateGeoSpecNativeQuery, geoSpecMatcherDescriptors };
5
+ export { type GeoSpecAssertionClient, type GeoSpecAssertionClientOptions, GeoSpecAssertionError, type GeoSpecAssertionMatchers, type GeoSpecAuthoringInvocation, type GeoSpecCanonicalClaimReport, type GeoSpecFixedMatcherDescriptor, type GeoSpecMatcherMethods, type GeoSpecMatcherName, type GeoSpecNativeClaimEvaluation, type GeoSpecNativeEngine, type GeoSpecNativeEvidenceProfile, type GeoSpecNativeQueryOptions, type GeoSpecNativeSubject, type GeoSpecPmiFaceAssociation, type GeoSpecPmiField, type GeoSpecPmiInventory, type GeoSpecPmiLimits, type GeoSpecPmiNumber, type GeoSpecPmiQueryPayload, type GeoSpecPmiQueryValue, type GeoSpecPmiRawEntity, type GeoSpecPmiRecord, type GeoSpecPmiShapeReference, type GeoSpecQueryCapability, type GeoSpecQueryOptions, type MinimumDistanceFact, type MinimumDistanceQuery, type MinimumDistanceResult, createGeoSpecAssertionClient, createGeoSpecMatcherMethods, evaluateGeoSpecNativeQuery, geoSpecMatcherDescriptors, geoSpecMaximumWorkUnitLimit, resolveGeoSpecMaximumWorkUnitLimit };
@@ -1 +1 @@
1
- import{geoSpecMatcherDescriptors as e}from"../engine/matchers.mjs";import{evaluateGeoSpecNativeQuery as t}from"../engine/client.mjs";import{GeoSpecAssertionError as n,createGeoSpecAssertionClient as r,createGeoSpecMatcherMethods as i}from"./client.mjs";export{n as GeoSpecAssertionError,r as createGeoSpecAssertionClient,i as createGeoSpecMatcherMethods,t as evaluateGeoSpecNativeQuery,e as geoSpecMatcherDescriptors};
1
+ import{geoSpecMatcherDescriptors as e}from"../engine/matchers.mjs";import{evaluateGeoSpecNativeQuery as t,geoSpecMaximumWorkUnitLimit as n,resolveGeoSpecMaximumWorkUnitLimit as r}from"../engine/client.mjs";import{GeoSpecAssertionError as i,createGeoSpecAssertionClient as a,createGeoSpecMatcherMethods as o}from"./client.mjs";export{i as GeoSpecAssertionError,a as createGeoSpecAssertionClient,o as createGeoSpecMatcherMethods,t as evaluateGeoSpecNativeQuery,e as geoSpecMatcherDescriptors,n as geoSpecMaximumWorkUnitLimit,r as resolveGeoSpecMaximumWorkUnitLimit};
@@ -1,3 +1,3 @@
1
- import{resolveRuntimeExportIntent as e}from"../model/export-intent.mjs";import{resolveGeoSpecConfig as t}from"./validate.mjs";import{parseProjectManifestBytes as n,projectManifestSchema as r,projectRelativePathSchema as i,serializeProjectManifest as a}from"@taucad/project-core";const o=(e,t,n={})=>({success:!1,issues:[...n.priorIssues??[],{code:e,message:t,severity:`error`,type:`runtime`,...n.details===void 0?{}:{details:n.details}}]}),s=(e,t)=>e.byteLength===t.byteLength&&e.every((e,n)=>e===t[n]),c=(e,t)=>{let n=e.lastIndexOf(`/`);return i.parse(n===-1?t:`${e.slice(0,n)}/${t}`)},l=(e,t)=>t.bytes.byteLength===0?!1:e===`step`?t.mimeType===`application/step`:t.mimeType===`model/gltf-binary`,u=async i=>{let u=t({subjects:{selected:i.descriptor}}).subjects?.selected;if(!u)throw TypeError(`GeoSpec Tau descriptor is required.`);let d=structuredClone(u),f=r.safeParse(d.manifest);if(!f.success)throw TypeError(`GeoSpec Tau descriptor manifest must match the current Tau project schema.`);let p=c(d.manifestPath,f.data.assets.main.entryPath),m=await i.createRuntime(),h,g;try{await m.connect(),h=await m.snapshotSource({source:{path:p},additionalPaths:[{path:d.manifestPath,required:!0}],...i.signal===void 0?{}:{signal:i.signal}}),h.success&&(g=structuredClone(e({runtime:m,format:d.format})))}finally{await m.shutdown()}if(!h.success)return h;if(!g)throw TypeError(`Runtime export intent was not resolved from the successful source snapshot.`);if(`success`in g)return o(`KERNEL_CAPABILITY_MISSING`,g.diagnostics.map(({message:e})=>e).join(`
1
+ import{resolveRuntimeExportIntent as e}from"../model/export-intent.mjs";import{resolveGeoSpecConfig as t}from"./validate.mjs";import{parseProjectManifestBytes as n,projectManifestSchema as r,projectRelativePathSchema as i,serializeProjectManifest as a}from"@taucad/project-core";const o=(e,t,n={})=>({success:!1,issues:[...n.priorIssues??[],{code:e,message:t,severity:`error`,type:`runtime`,...n.details===void 0?{}:{details:n.details}}]}),s=(e,t)=>e.byteLength===t.byteLength&&e.every((e,n)=>e===t[n]),c=(e,t)=>{let n=e.lastIndexOf(`/`);return i.parse(n===-1?t:`${e.slice(0,n)}/${t}`)},l=(e,t)=>t.bytes.byteLength===0?!1:e===`step`?t.mimeType===`application/step`:t.mimeType===`model/gltf-binary`,u=async i=>{let u=t({subjects:{selected:i.descriptor}}).subjects?.selected;if(!u)throw TypeError(`GeoSpec Tau descriptor is required.`);let d=structuredClone(u),f=r.safeParse(d.manifest);if(!f.success)throw TypeError(`GeoSpec Tau descriptor manifest must match the current Tau project schema.`);let p=c(d.manifestPath,f.data.assets.main.entryPath),m=await i.createRuntime(),h,g;try{await m.connect(),h=await m.snapshotSource({source:{path:p},additionalPaths:[{path:d.manifestPath,required:!0}],...i.signal===void 0?{}:{signal:i.signal}}),h.success&&(g=structuredClone(e({runtime:m,format:d.format,kernelId:h.data.kernelId})))}finally{await m.shutdown()}if(!h.success)return h;if(!g)throw TypeError(`Runtime export intent was not resolved from the successful source snapshot.`);if(`success`in g)return o(`KERNEL_CAPABILITY_MISSING`,g.diagnostics.map(({message:e})=>e).join(`
2
2
  `),{details:{diagnostics:g.diagnostics},priorIssues:h.issues});let _=g.provenance.route,v=g.provenance.honored;if(_?.kernelId!==h.data.kernelId||_.targetFormat!==d.format||v?.coordinateSystem!==`z-up`||v.sourceUnit!==`mm`||d.format===`glb`&&v.unit?.length!==`millimeter`)return o(`KERNEL_CAPABILITY_MISSING`,`Runtime route did not honor the snapshotted provider and Z-up/mm export.`,{details:{snapshotKernelId:h.data.kernelId,route:_,honored:v},priorIssues:h.issues});if(h.data.entryPath!==p||h.data.unresolvedPaths.length>0)return o(`SOURCE_SNAPSHOT_INVALID`,`Runtime did not return the requested complete Tau source closure.`,{details:{expectedEntryPath:p,actualEntryPath:h.data.entryPath,unresolvedPaths:h.data.unresolvedPaths},priorIssues:h.issues});let y=h.data.files.filter(({path:e})=>e===d.manifestPath);if(y.length!==1)return o(`SOURCE_SNAPSHOT_INVALID`,`Runtime source closure must contain exactly one requested Tau manifest.`,{details:{manifestPath:d.manifestPath,actualCount:y.length},priorIssues:h.issues});let b=y[0],x=n(b.content);if(!x.success)return o(`SOURCE_SNAPSHOT_INVALID`,`Runtime source closure contains an invalid Tau manifest.`,{details:x.issue,priorIssues:h.issues});if(!s(a(f.data),a(x.data)))return o(`SOURCE_SNAPSHOT_CHANGED`,`Imported Tau descriptor does not match the snapshotted manifest bytes.`,{details:{manifestPath:d.manifestPath},priorIssues:h.issues});if(c(d.manifestPath,x.data.assets.main.entryPath)!==h.data.entryPath)return o(`SOURCE_SNAPSHOT_CHANGED`,`Snapshotted Tau manifest selects a different main asset.`,{details:{manifestPath:d.manifestPath},priorIssues:h.issues});let S={};for(let e of h.data.files){if(S[e.path]!==void 0)return o(`SOURCE_SNAPSHOT_INVALID`,`Runtime source closure contains duplicate paths.`,{details:{path:e.path},priorIssues:h.issues});S[e.path]=e.content}let C=await i.createRuntime();if(C===m)throw TypeError(`createRuntime must return a fresh runtime for source replay.`);try{await C.connect();let t=C.open({source:{files:S,entry:h.data.entryPath},parameters:d.parameters??{},watch:!1,...i.signal===void 0?{}:{signal:i.signal}}),n=await(async()=>{try{return await t.export(d.format,{options:g.options,...i.signal===void 0?{}:{signal:i.signal}})}finally{t.close()}})();if(!n.success)return{success:!1,issues:[...h.issues,...n.issues]};let r=e({runtime:C,format:d.format});if(`success`in r)return o(`KERNEL_CAPABILITY_MISSING`,r.diagnostics.map(({message:e})=>e).join(`
3
3
  `),{details:{diagnostics:r.diagnostics},priorIssues:[...h.issues,...n.issues]});let a=r.provenance.route,s=r.provenance.honored;if(a?.kernelId!==h.data.kernelId||a.targetFormat!==d.format||s?.coordinateSystem!==`z-up`||s.sourceUnit!==`mm`||d.format===`glb`&&s.unit?.length!==`millimeter`||JSON.stringify(a)!==JSON.stringify(_)||JSON.stringify(r.options)!==JSON.stringify(g.options))return o(`KERNEL_CAPABILITY_MISSING`,`Export runtime did not retain the requested source-bound route and Z-up/mm frame.`,{details:{snapshotKernelId:h.data.kernelId,requested:g,actual:r},priorIssues:[...h.issues,...n.issues]});let c=[...n.files];if(c.length!==1)return o(`EXPORT_ARTIFACT_SET_INVALID`,`Tau project export must produce exactly one usable artifact.`,{details:{actualCount:c.length},priorIssues:[...h.issues,...n.issues]});let[u]=c;if(!u||!l(d.format,u))return o(`EXPORT_ARTIFACT_SET_INVALID`,`Tau project export did not produce the requested usable artifact.`,{details:{format:d.format,artifact:u&&{name:u.name,mimeType:u.mimeType,byteLength:u.bytes.byteLength}},priorIssues:[...h.issues,...n.issues]});let{bytes:f,mimeType:p,name:m}=u;return{success:!0,data:{format:d.format,name:m,mimeType:p,bytes:f,frame:{coordinateSystem:`z-up`,lengthUnit:`millimeter`,sourceUnit:`mm`},source:{manifestPath:d.manifestPath,manifestBytes:b.content,manifest:x.data,projectEntryPath:x.data.assets.main.entryPath,entryPath:h.data.entryPath,kernelId:h.data.kernelId,files:h.data.files},export:{options:g.options,route:a}},issues:[...h.issues,...n.issues]}}finally{await C.shutdown()}};export{u as exportTauProjectArtifact};
@@ -1,5 +1,5 @@
1
1
  import { GeoSpecSubject } from "./model/subject.mjs";
2
- import { GeoSpecMatcher } from "./runner/types.mjs";
2
+ import { GeoSpecExpectOptions, GeoSpecMatcher } from "./runner/types.mjs";
3
3
  //#region src/create-geospec.d.ts
4
4
  type GeoSpecTestCallback = () => unknown | PromiseLike<unknown>;
5
5
  type SuiteFunction = {
@@ -32,10 +32,11 @@ declare const test: TestFunction;
32
32
  * Start a geometry assertion chain.
33
33
  *
34
34
  * @param subject - geometry subject under test.
35
+ * @param options - Optional chain options, such as a larger `workUnitLimit` for a claim that refused with `MATCHER_TIMEOUT`.
35
36
  * @returns GeoSpec geometry matchers.
36
37
  * @public
37
38
  */
38
- declare function expectGeo(subject: GeoSpecSubject): GeoSpecMatcher;
39
+ declare function expectGeo(subject: GeoSpecSubject, options?: GeoSpecExpectOptions): GeoSpecMatcher;
39
40
  /**
40
41
  * Every matcher name exposed by {@link expectGeo}, derived from the live
41
42
  * matcher surface so it can never drift from the real implementation. Use it to
@@ -1 +1 @@
1
- import{geoSpecMatcherDescriptors as e}from"./engine/matchers.mjs";import{getCollector as t}from"./runner/collector.mjs";import{expectGeoSubject as n}from"./model/subject-assertions.mjs";const r=Object.assign((e,n)=>{t().describe(e,n)},{skip(e,n){t().describeSkip(e,n)}}),i=Object.assign((e,n)=>{t().it(e,n)},{skip(e,n){t().itSkip(e,n)}}),a=i;function o(e){return n(e)}const s=Object.freeze(Object.keys(e));export{r as describe,o as expectGeo,s as geoSpecMatcherNames,i as it,a as test};
1
+ import{geoSpecMatcherDescriptors as e}from"./engine/matchers.mjs";import{getCollector as t}from"./runner/collector.mjs";import{expectGeoSubject as n}from"./model/subject-assertions.mjs";const r=Object.assign((e,n)=>{t().describe(e,n)},{skip(e,n){t().describeSkip(e,n)}}),i=Object.assign((e,n)=>{t().it(e,n)},{skip(e,n){t().itSkip(e,n)}}),a=i;function o(e,t){return n(e,t)}const s=Object.freeze(Object.keys(e));export{r as describe,o as expectGeo,s as geoSpecMatcherNames,i as it,a as test};
@@ -1,6 +1,23 @@
1
1
  import { GeoSpecFixedMatcherName } from "./matchers.mjs";
2
2
  import { JSONValue } from "@taucad/runtime/types";
3
3
  //#region src/engine/client.d.ts
4
+ /**
5
+ * Default largest work-unit budget a spec may author through `loadModel` or `expectGeo` options:
6
+ * 64 times the engine's current 8 000 000-unit default. It is host policy, not a ceiling: a host
7
+ * raises it with `maximumWorkUnitLimit` on its assertion client, runner or model loader. The
8
+ * host's own `workUnitLimit` keeps the full safe-integer range.
9
+ * @public
10
+ */
11
+ declare const geoSpecMaximumWorkUnitLimit = 512000000;
12
+ /**
13
+ * Resolve a host's largest authored work-unit budget.
14
+ *
15
+ * @param maximum - The host's configured maximum, when it sets one.
16
+ * @returns The maximum, defaulting to {@link geoSpecMaximumWorkUnitLimit}.
17
+ * @throws TypeError when the configured maximum is not a positive safe integer.
18
+ * @public
19
+ */
20
+ declare const resolveGeoSpecMaximumWorkUnitLimit: (maximum?: number) => number;
4
21
  /**
5
22
  * Exact core bytes of one claim evaluated in one engine call: the canonical plan, its one
6
23
  * canonical claim and the canonical result, as fresh bytes the client retains without copying.
@@ -32,6 +49,7 @@ type GeoSpecCanonicalClaimReport = {
32
49
  readonly canonicalResult: Uint8Array<ArrayBuffer>;
33
50
  readonly claim: Readonly<Record<string, JSONValue>>;
34
51
  readonly claimId: string;
52
+ /** Engine diagnostics; a `MATCHER_TIMEOUT` row also carries `details.limit`, `details.used` and `details.maximumLimit`. */
35
53
  readonly diagnostics: readonly JSONValue[];
36
54
  readonly evidence?: JSONValue;
37
55
  readonly polarity: GeoSpecClaimPolarity;
@@ -55,6 +73,8 @@ type GeoSpecNativeClaimContext = {
55
73
  readonly subject: GeoSpecNativeSubject;
56
74
  readonly subjectSlot: string;
57
75
  readonly workUnitLimit: number;
76
+ /** The host's largest authored budget, reported as `details.maximumLimit` on `MATCHER_TIMEOUT`. */
77
+ readonly maximumWorkUnitLimit?: number;
58
78
  };
59
79
  /** One authored matcher call lowered and evaluated by Rust in one engine call. @public */
60
80
  type GeoSpecNativeClaimOptions = GeoSpecNativeClaimContext & ({
@@ -90,4 +110,4 @@ type GeoSpecNativeQueryOptions = Omit<GeoSpecNativeClaimOptions, 'arguments' | '
90
110
  */
91
111
  declare const evaluateGeoSpecNativeQuery: (options: GeoSpecNativeQueryOptions) => GeoSpecCanonicalClaimReport;
92
112
  //#endregion
93
- export { GeoSpecCanonicalClaimReport, GeoSpecCanonicalClaimStatus, GeoSpecClaimPolarity, GeoSpecNativeClaimEvaluation, GeoSpecNativeClaimOptions, GeoSpecNativeEngine, GeoSpecNativeEvidenceProfile, GeoSpecNativeQueryOptions, GeoSpecNativeSubject, GeoSpecQueryCapability, evaluateGeoSpecNativeQuery };
113
+ export { GeoSpecCanonicalClaimReport, GeoSpecCanonicalClaimStatus, GeoSpecClaimPolarity, GeoSpecNativeClaimEvaluation, GeoSpecNativeClaimOptions, GeoSpecNativeEngine, GeoSpecNativeEvidenceProfile, GeoSpecNativeQueryOptions, GeoSpecNativeSubject, GeoSpecQueryCapability, evaluateGeoSpecNativeQuery, geoSpecMaximumWorkUnitLimit, resolveGeoSpecMaximumWorkUnitLimit };
@@ -1 +1 @@
1
- import{assertGeoSpecJsonValue as e,toGeoSpecProtocolJson as t}from"./protocol.mjs";import{geoSpecMatcherDescriptors as n}from"./matchers.mjs";const r=`geospec-jcs-v1`,i=[`analyzeMesh`,`analyzeBrep`,`inspectGeometry`,`analyzeMeshOverlap`,`queryPmi`,`minimumDistance`],a=t=>{let n=JSON.parse(new TextDecoder(`utf-8`,{fatal:!0}).decode(t));return e(n),n},o=new TextEncoder().encode(`{"canonicalProfile":"geospec-jcs-v1","method":"initialize","protocolVersion":3,"registryVersion":5,"requestId":"configuration"}`),s=new WeakMap,c=new WeakMap,l=(e,t)=>{if(typeof e!=`object`||!e||Array.isArray(e))throw TypeError(`GeoSpec engine returned a malformed ${t}.`);return e},u=(e,t)=>{let{contentHash:n,subjectHash:r}=e;if(n===void 0==(r===void 0))throw TypeError(`A native GeoSpec subject requires exactly one of contentHash or subjectHash.`);let i=n??r;if(!/^[0-9a-f]{64}$/u.test(i))throw TypeError(`A native GeoSpec subject hash must contain 64 lowercase hexadecimal characters.`);return n===void 0?{slot:t,subjectHash:i}:{slot:t,contentHash:i}},d=e=>{if(!Number.isSafeInteger(e)||e<=0||e>9007199254740991)throw TypeError(`GeoSpec workUnitLimit must be a positive exact safe integer.`)},f=e=>{if(e.claimId.length===0||e.subjectSlot.length===0)throw TypeError(`GeoSpec claim IDs and subject slots must be non-empty strings.`);d(e.workUnitLimit)},p=(e,t)=>{if(t!==void 0)return d(t),t;let n=s.get(e);if(n!==void 0)return n;let i=l(a(Uint8Array.from(e.processRequest(Uint8Array.from(o)))),`initialize response envelope`);if(i.requestId!==`configuration`)throw TypeError(`GeoSpec engine returned an initialize response with the wrong requestId.`);let u=l(i.result,`initialize result`);if(u.canonicalProfile!==r||u.protocolVersion!==3||u.registryVersion!==5)throw TypeError(`GeoSpec engine returned an initialize response for an incompatible protocol profile.`);let f=l(u.configuration,`initialize configuration`),{capabilities:p}=u,m=Array.isArray(p)?p.filter(e=>typeof e==`object`&&!!e&&!Array.isArray(e)&&e.name===`minimumDistance`):[];if(c.set(e,m.length===1&&m.every(e=>e.profile===`geospec-minimum-distance-v1`&&e.implementation===`implemented`&&e.registryVersion===5)),f.configurationProfile!==`geospec-entry-config-v1`)throw TypeError(`GeoSpec engine returned an unsupported configuration profile.`);let h=f.defaultWorkUnitBudget;if(typeof h!=`number`)throw TypeError(`GeoSpec engine initialize configuration omitted defaultWorkUnitBudget.`);return d(h),s.set(e,h),h},m=e=>(p(e),c.get(e)===!0),h=e=>{if(e.capability===`toSatisfyRationalPlate`||e.capability===`toSatisfyParallelPlaneDistance`){if(e.arguments.length>0)throw TypeError(`GeoSpec matcher ${e.capability} does not accept arguments.`);return _({...e,payload:{contract:n[e.capability].contract}})}if(!(`kind`in e))throw TypeError(`This GeoSpec matcher requires its canonical kind.`);return _({...e,payload:{kind:e.kind,arguments:e.arguments.map(e=>t(e))}})},g=e=>{if(!i.includes(e.capability))throw TypeError(`GeoSpec query capability must name an ancillary operation.`);return _({...e,payload:t(e.payload),polarity:`positive`})},_=e=>{f(e);let t={method:`submitClaims`,requestId:e.claimId,protocolVersion:3,registryVersion:5,canonicalProfile:r,plan:{subjects:[u(e.subject,e.subjectSlot)],claims:[{claimId:e.claimId,capability:e.capability,subjectSlots:[e.subjectSlot],payload:e.payload,polarity:e.polarity,workUnitBudget:e.workUnitLimit}],...e.evidenceProfile===void 0?{}:{evidenceProfile:e.evidenceProfile}}},{canonicalClaim:n,canonicalPlan:i,canonicalResult:o}=e.engine.evaluateClaim(new TextEncoder().encode(JSON.stringify(t))),s=l(a(n),`canonical claim`);if(s.claimId!==e.claimId)throw TypeError(`GeoSpec engine returned a malformed canonical claim for '${e.claimId}'.`);let{results:c}=l(a(o),`canonical result envelope`);if(!Array.isArray(c)||c.length!==1)throw TypeError(`GeoSpec engine returned a canonical result envelope without exactly one result.`);let[d]=c,p=l(d,`canonical claim result`),{claimId:m,diagnostics:h,status:g}=p;if(m!==e.claimId||typeof g!=`string`||!Array.isArray(h))throw TypeError(`GeoSpec engine returned a malformed result for claim '${e.claimId}'.`);return{canonicalClaim:n,canonicalPlan:i,canonicalResult:o,claim:s,claimId:m,diagnostics:h,...p.evidence===void 0?{}:{evidence:p.evidence},polarity:e.polarity,result:p,status:g}};export{h as evaluateGeoSpecNativeClaim,g as evaluateGeoSpecNativeQuery,p as resolveGeoSpecNativeWorkUnitLimit,m as supportsGeoSpecNativeMinimumDistance};
1
+ import{assertGeoSpecJsonValue as e,toGeoSpecProtocolJson as t}from"./protocol.mjs";import{geoSpecMatcherDescriptors as n}from"./matchers.mjs";const r=`geospec-jcs-v1`,i=512e6,a=e=>{if(e===void 0)return i;if(!Number.isSafeInteger(e)||e<=0)throw TypeError(`GeoSpec maximumWorkUnitLimit must be a positive safe integer; received ${String(e)}.`);return e},o=[`analyzeMesh`,`analyzeBrep`,`inspectGeometry`,`analyzeMeshOverlap`,`queryPmi`,`minimumDistance`],s=t=>{let n=JSON.parse(new TextDecoder(`utf-8`,{fatal:!0}).decode(t));return e(n),n},c=new TextEncoder().encode(`{"canonicalProfile":"geospec-jcs-v1","method":"initialize","protocolVersion":3,"registryVersion":5,"requestId":"configuration"}`),l=new WeakMap,u=new WeakMap,d=(e,t)=>{if(typeof e!=`object`||!e||Array.isArray(e))throw TypeError(`GeoSpec engine returned a malformed ${t}.`);return e},f=(e,t)=>{let{contentHash:n,subjectHash:r}=e;if(n===void 0==(r===void 0))throw TypeError(`A native GeoSpec subject requires exactly one of contentHash or subjectHash.`);let i=n??r;if(!/^[0-9a-f]{64}$/u.test(i))throw TypeError(`A native GeoSpec subject hash must contain 64 lowercase hexadecimal characters.`);return n===void 0?{slot:t,subjectHash:i}:{slot:t,contentHash:i}},p=e=>{if(!Number.isSafeInteger(e)||e<=0||e>9007199254740991)throw TypeError(`GeoSpec workUnitLimit must be a positive exact safe integer.`)},m=e=>{if(e.claimId.length===0||e.subjectSlot.length===0)throw TypeError(`GeoSpec claim IDs and subject slots must be non-empty strings.`);p(e.workUnitLimit)},h=(e,t,n=i)=>e===void 0||typeof e==`number`&&Number.isSafeInteger(e)&&e>0&&e<=n?void 0:`GeoSpec ${t} must be a positive integer no greater than this host's maximum of ${n} (the host can raise it with maximumWorkUnitLimit); received ${typeof e==`number`?String(e):typeof e}.`,g=(e,t,n)=>{let r=h(e,t,n);if(r!==void 0)throw TypeError(r)},_=(e,t)=>{if(t!==void 0)return p(t),t;let n=l.get(e);if(n!==void 0)return n;let i=d(s(Uint8Array.from(e.processRequest(Uint8Array.from(c)))),`initialize response envelope`);if(i.requestId!==`configuration`)throw TypeError(`GeoSpec engine returned an initialize response with the wrong requestId.`);let a=d(i.result,`initialize result`);if(a.canonicalProfile!==r||a.protocolVersion!==3||a.registryVersion!==5)throw TypeError(`GeoSpec engine returned an initialize response for an incompatible protocol profile.`);let o=d(a.configuration,`initialize configuration`),{capabilities:f}=a,m=Array.isArray(f)?f.filter(e=>typeof e==`object`&&!!e&&!Array.isArray(e)&&e.name===`minimumDistance`):[];if(u.set(e,m.length===1&&m.every(e=>e.profile===`geospec-minimum-distance-v1`&&e.implementation===`implemented`&&e.registryVersion===5)),o.configurationProfile!==`geospec-entry-config-v1`)throw TypeError(`GeoSpec engine returned an unsupported configuration profile.`);let h=o.defaultWorkUnitBudget;if(typeof h!=`number`)throw TypeError(`GeoSpec engine initialize configuration omitted defaultWorkUnitBudget.`);return p(h),l.set(e,h),h},v=e=>(_(e),u.get(e)===!0),y=e=>{if(e.capability===`toSatisfyRationalPlate`||e.capability===`toSatisfyParallelPlaneDistance`){if(e.arguments.length>0)throw TypeError(`GeoSpec matcher ${e.capability} does not accept arguments.`);return w({...e,payload:{contract:n[e.capability].contract}})}if(!(`kind`in e))throw TypeError(`This GeoSpec matcher requires its canonical kind.`);return w({...e,payload:{kind:e.kind,arguments:e.arguments.map(e=>t(e))}})},b=e=>{if(!o.includes(e.capability))throw TypeError(`GeoSpec query capability must name an ancillary operation.`);return w({...e,payload:t(e.payload),polarity:`positive`})},x=e=>typeof e==`number`&&Number.isSafeInteger(e)&&e>=0?e:void 0,S=e=>typeof e==`object`&&!!e&&!Array.isArray(e)&&e.code===`MATCHER_TIMEOUT`,C=(e,t,n)=>{if(!S(e))return e;let{details:r}=e,i=typeof r==`object`&&r&&!Array.isArray(r)?r:{},a=x(i.budget)??x(i.workUnitBudget)??t,o=x(i.unitsUsed)??x(i.requiredWorkUnits);return{...e,details:{...i,limit:a,...o===void 0?{}:{used:o},maximumLimit:n}}},w=e=>{m(e);let t={method:`submitClaims`,requestId:e.claimId,protocolVersion:3,registryVersion:5,canonicalProfile:r,plan:{subjects:[f(e.subject,e.subjectSlot)],claims:[{claimId:e.claimId,capability:e.capability,subjectSlots:[e.subjectSlot],payload:e.payload,polarity:e.polarity,workUnitBudget:e.workUnitLimit}],...e.evidenceProfile===void 0?{}:{evidenceProfile:e.evidenceProfile}}},{canonicalClaim:n,canonicalPlan:i,canonicalResult:a}=e.engine.evaluateClaim(new TextEncoder().encode(JSON.stringify(t))),o=d(s(n),`canonical claim`);if(o.claimId!==e.claimId)throw TypeError(`GeoSpec engine returned a malformed canonical claim for '${e.claimId}'.`);let{results:c}=d(s(a),`canonical result envelope`);if(!Array.isArray(c)||c.length!==1)throw TypeError(`GeoSpec engine returned a canonical result envelope without exactly one result.`);let[l]=c,u=d(l,`canonical claim result`),{claimId:p,diagnostics:h,status:g}=u;if(p!==e.claimId||typeof g!=`string`||!Array.isArray(h))throw TypeError(`GeoSpec engine returned a malformed result for claim '${e.claimId}'.`);return{canonicalClaim:n,canonicalPlan:i,canonicalResult:a,claim:o,claimId:p,diagnostics:h.some(e=>S(e))?h.map(t=>C(t,e.workUnitLimit,e.maximumWorkUnitLimit??512e6)):h,...u.evidence===void 0?{}:{evidence:u.evidence},polarity:e.polarity,result:u,status:g}};export{g as assertGeoSpecAuthoredWorkUnitLimit,y as evaluateGeoSpecNativeClaim,b as evaluateGeoSpecNativeQuery,h as geoSpecAuthoredWorkUnitLimitProblem,i as geoSpecMaximumWorkUnitLimit,a as resolveGeoSpecMaximumWorkUnitLimit,_ as resolveGeoSpecNativeWorkUnitLimit,v as supportsGeoSpecNativeMinimumDistance};
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { GeoSpecUnit } from "./geometry-unit.mjs";
2
2
  import { BrepEvidence, GeometryCapability, GeometryDiagnostic, GeometryFileFormat, GeometryProvenance, GeometrySource, GeometrySubject, GeometrySubjectMeshEvidence, MeshEvidence, MeshFileFormat, MeshQualityStats, MeshTriangle, StepEvidence, Vec3 } from "./mesh/types.mjs";
3
3
  import { GeoSpecSubject } from "./model/subject.mjs";
4
- import { GeoSpecAssemblyOccurrenceExpectation, GeoSpecAssemblyOccurrencesExpectation, GeoSpecAssertion, GeoSpecAxisExpectation, GeoSpecBoundingBoxExpectation, GeoSpecCenterOfMassExpectation, GeoSpecChamferFeatureExpectation, GeoSpecCircularHoleExpectation, GeoSpecCircularHolePatternExpectation, GeoSpecComponentInterferenceAllowance, GeoSpecComponentInterferenceExpectation, GeoSpecComponentInterferencePairExpectation, GeoSpecConnectedComponentsExpectation, GeoSpecCylindricalFaceExpectation, GeoSpecFilletFeatureExpectation, GeoSpecGeometrySelector, GeoSpecMassExpectation, GeoSpecMatcher, GeoSpecMeshIntegrityExpectation, GeoSpecMinimumWallThicknessExpectation, GeoSpecNoDiagnosticsExpectation, GeoSpecNumericExpectation, GeoSpecPlanarFaceExpectation, GeoSpecPointExpectation, GeoSpecProductStructureExpectation, GeoSpecSpatialRelationshipExpectation, GeoSpecSpatialRelationshipsExpectation, GeoSpecStepUnitsExpectation, GeoSpecSurfaceAreaExpectation, GeoSpecTopologyCountsExpectation, GeoSpecValidBrepExpectation, GeoSpecVoidContinuityExpectation, GeoSpecVoidWaypoint, GeoSpecVolumeExpectation } from "./runner/types.mjs";
4
+ import { GeoSpecAssemblyOccurrenceExpectation, GeoSpecAssemblyOccurrencesExpectation, GeoSpecAssertion, GeoSpecAxisExpectation, GeoSpecBoundingBoxExpectation, GeoSpecCenterOfMassExpectation, GeoSpecChamferFeatureExpectation, GeoSpecCircularHoleExpectation, GeoSpecCircularHolePatternExpectation, GeoSpecComponentInterferenceAllowance, GeoSpecComponentInterferenceExpectation, GeoSpecComponentInterferencePairExpectation, GeoSpecConnectedComponentsExpectation, GeoSpecCylindricalFaceExpectation, GeoSpecExpectOptions, GeoSpecFilletFeatureExpectation, GeoSpecGeometrySelector, GeoSpecMassExpectation, GeoSpecMatcher, GeoSpecMeshIntegrityExpectation, GeoSpecMinimumWallThicknessExpectation, GeoSpecNoDiagnosticsExpectation, GeoSpecNumericExpectation, GeoSpecPlanarFaceExpectation, GeoSpecPointExpectation, GeoSpecProductStructureExpectation, GeoSpecSpatialRelationshipExpectation, GeoSpecSpatialRelationshipsExpectation, GeoSpecStepUnitsExpectation, GeoSpecSurfaceAreaExpectation, GeoSpecTopologyCountsExpectation, GeoSpecValidBrepExpectation, GeoSpecVoidContinuityExpectation, GeoSpecVoidWaypoint, GeoSpecVolumeExpectation } from "./runner/types.mjs";
5
5
  import { describe, expectGeo, geoSpecMatcherNames, it, test } from "./create-geospec.mjs";
6
- export { type BrepEvidence, type GeoSpecAssemblyOccurrenceExpectation, type GeoSpecAssemblyOccurrencesExpectation, type GeoSpecAssertion, type GeoSpecAxisExpectation, type GeoSpecBoundingBoxExpectation, type GeoSpecCenterOfMassExpectation, type GeoSpecChamferFeatureExpectation, type GeoSpecCircularHoleExpectation, type GeoSpecCircularHolePatternExpectation, type GeoSpecComponentInterferenceAllowance, type GeoSpecComponentInterferenceExpectation, type GeoSpecComponentInterferencePairExpectation, type GeoSpecConnectedComponentsExpectation, type GeoSpecCylindricalFaceExpectation, type GeoSpecFilletFeatureExpectation, type GeoSpecGeometrySelector, type GeoSpecMassExpectation, type GeoSpecMatcher, type GeoSpecMeshIntegrityExpectation, type GeoSpecMinimumWallThicknessExpectation, type GeoSpecNoDiagnosticsExpectation, type GeoSpecNumericExpectation, type GeoSpecPlanarFaceExpectation, type GeoSpecPointExpectation, type GeoSpecProductStructureExpectation, type GeoSpecSpatialRelationshipExpectation, type GeoSpecSpatialRelationshipsExpectation, type GeoSpecStepUnitsExpectation, type GeoSpecSubject, type GeoSpecSurfaceAreaExpectation, type GeoSpecTopologyCountsExpectation, type GeoSpecUnit, type GeoSpecValidBrepExpectation, type Vec3 as GeoSpecVec3, type GeoSpecVoidContinuityExpectation, type GeoSpecVoidWaypoint, type GeoSpecVolumeExpectation, type GeometryCapability, type GeometryDiagnostic, type GeometryFileFormat, type GeometryProvenance, type GeometrySource, type GeometrySubject, type GeometrySubjectMeshEvidence, type MeshEvidence, type MeshFileFormat, type MeshQualityStats, type MeshTriangle, type StepEvidence, type Vec3, describe, expectGeo, geoSpecMatcherNames, it, test };
6
+ export { type BrepEvidence, type GeoSpecAssemblyOccurrenceExpectation, type GeoSpecAssemblyOccurrencesExpectation, type GeoSpecAssertion, type GeoSpecAxisExpectation, type GeoSpecBoundingBoxExpectation, type GeoSpecCenterOfMassExpectation, type GeoSpecChamferFeatureExpectation, type GeoSpecCircularHoleExpectation, type GeoSpecCircularHolePatternExpectation, type GeoSpecComponentInterferenceAllowance, type GeoSpecComponentInterferenceExpectation, type GeoSpecComponentInterferencePairExpectation, type GeoSpecConnectedComponentsExpectation, type GeoSpecCylindricalFaceExpectation, type GeoSpecExpectOptions, type GeoSpecFilletFeatureExpectation, type GeoSpecGeometrySelector, type GeoSpecMassExpectation, type GeoSpecMatcher, type GeoSpecMeshIntegrityExpectation, type GeoSpecMinimumWallThicknessExpectation, type GeoSpecNoDiagnosticsExpectation, type GeoSpecNumericExpectation, type GeoSpecPlanarFaceExpectation, type GeoSpecPointExpectation, type GeoSpecProductStructureExpectation, type GeoSpecSpatialRelationshipExpectation, type GeoSpecSpatialRelationshipsExpectation, type GeoSpecStepUnitsExpectation, type GeoSpecSubject, type GeoSpecSurfaceAreaExpectation, type GeoSpecTopologyCountsExpectation, type GeoSpecUnit, type GeoSpecValidBrepExpectation, type Vec3 as GeoSpecVec3, type GeoSpecVoidContinuityExpectation, type GeoSpecVoidWaypoint, type GeoSpecVolumeExpectation, type GeometryCapability, type GeometryDiagnostic, type GeometryFileFormat, type GeometryProvenance, type GeometrySource, type GeometrySubject, type GeometrySubjectMeshEvidence, type MeshEvidence, type MeshFileFormat, type MeshQualityStats, type MeshTriangle, type StepEvidence, type Vec3, describe, expectGeo, geoSpecMatcherNames, it, test };
@@ -52,12 +52,28 @@ type RuntimeExportIntentFailure = {
52
52
  success: false;
53
53
  diagnostics: GeometryDiagnostic[];
54
54
  };
55
+ /**
56
+ * The matchers mesh evidence answers, as the GeoSpec authoring skill lists
57
+ * them (`packages/geospec/agent/geospec-authoring/SKILL.md`): bounds, volume,
58
+ * surface area, connected components and watertightness. Every other
59
+ * exact-feature matcher needs STEP BRep evidence.
60
+ *
61
+ * @public
62
+ */
63
+ declare const geoSpecMeshEvidenceMatchers: readonly ["toHaveBoundingBox", "toHaveVolume", "toHaveSurfaceArea", "toHaveConnectedComponents", "toBeWatertight"];
55
64
  /** @public */
56
65
  declare const resolveRuntimeExportIntent: (options: {
57
66
  runtime: GeoSpecRuntimeClient;
58
67
  format: RuntimeBackedModelFormat;
59
68
  meshLinearTolerance?: number;
60
69
  meshAngularToleranceDegrees?: number;
70
+ /**
71
+ * The kernel that evaluates this source, when the host knows it. Routes are
72
+ * then chosen among that kernel's own, so a mesh-only kernel is told its
73
+ * BRep matchers are unsupported instead of being sent to another kernel's
74
+ * STEP route.
75
+ */
76
+ kernelId?: string;
61
77
  }) => RuntimeExportIntent | RuntimeExportIntentFailure;
62
78
  //#endregion
63
- export { GeoSpecExportRoute, RuntimeBackedModelFormat, RuntimeClientWithRoutes, RuntimeExportIntent, RuntimeExportIntentFailure, resolveRuntimeExportIntent };
79
+ export { GeoSpecExportRoute, RuntimeBackedModelFormat, RuntimeClientWithRoutes, RuntimeExportIntent, RuntimeExportIntentFailure, geoSpecMeshEvidenceMatchers, resolveRuntimeExportIntent };
@@ -1 +1 @@
1
- const e=new Set([`step`,`stp`]),t=e=>typeof e.bestRouteFor==`function`,n=(e,t)=>{let{capabilities:n}=e;if((n?.routes?.length??0)>0)return!0;let{routesFor:r}=e;return typeof r==`function`&&r(t).length>0},r=(e,t)=>t in(e?.exportOptions?.schema.properties??{}),i=e=>e.transcoderId===void 0,a=(e,t)=>({format:e,coordinateSystem:`z-up`,unit:{length:`millimeter`},...t===void 0?{}:{tessellation:t}}),o=e=>e===void 0?{}:{tessellation:{...e.linearTolerance===void 0?{}:{linearTolerance:e.linearTolerance},...e.angularToleranceDegrees===void 0?{}:{angularTolerance:e.angularToleranceDegrees}}},s=e=>{let t=a(e.format,e.tessellation);return{options:e.exportOptions,sourceUnit:`mm`,provenance:{requested:t,honored:{format:t.format,coordinateSystem:`z-up`,unit:{length:`millimeter`},sourceUnit:`mm`,...e.tessellation===void 0?{}:{tessellation:e.tessellation}}}}},c=e=>e?{...e.kernelId===void 0?{}:{kernelId:e.kernelId},...e.sourceFormat===void 0?{}:{sourceFormat:e.sourceFormat},...e.targetFormat===void 0?{}:{targetFormat:e.targetFormat},...e.transcoderId===void 0?{}:{transcoderId:e.transcoderId},...e.fidelity===void 0?{}:{fidelity:e.fidelity},direct:i(e)}:void 0,l=e=>({success:!1,diagnostics:[{code:`GEOSPEC_CANONICAL_EXPORT_UNSUPPORTED`,severity:`error`,message:`GeoSpec cannot request canonical Z-up millimeter ${e.format.toUpperCase()} evidence from this runtime route.`,suggestion:`Use a runtime route whose export schema exposes coordinateSystem and unit.length, or pass a custom runtime that returns canonical millimeter geometry bytes.`,details:{format:e.format,missing:e.missing,route:c(e.route)}}]}),u=u=>{let{runtime:d,format:f}=u,p=u.meshLinearTolerance===void 0&&u.meshAngularToleranceDegrees===void 0?void 0:{...u.meshLinearTolerance===void 0?{}:{linearTolerance:u.meshLinearTolerance},...u.meshAngularToleranceDegrees===void 0?{}:{angularToleranceDegrees:u.meshAngularToleranceDegrees}},m=t(d),h=m?d.bestRouteFor(f):void 0;if(e.has(f)){if(h&&(!i(h)||h.fidelity!==`brep`))return{success:!1,diagnostics:[{code:`GEOSPEC_DIRECT_STEP_ROUTE_REQUIRED`,severity:`error`,message:`GeoSpec exact BRep evidence requires a direct STEP/BRep runtime export route.`,suggestion:`Request a runtime export route that preserves exact STEP/BRep evidence, or load mesh evidence with GLB/glTF when exact BRep assertions are not required.`,details:{route:c(h)}}]};let e={},t={format:f,sourceUnit:`mm`};return r(h,`coordinateSystem`)&&(e.coordinateSystem=`z-up`,t.coordinateSystem=`z-up`),{options:e,sourceUnit:`mm`,provenance:{requested:{format:f},honored:t,route:c(h)}}}let g=a(f,p);if(!m)return s({format:f,exportOptions:{coordinateSystem:`z-up`,unit:{length:`millimeter`},...o(p)},...p===void 0?{}:{tessellation:p}});if(!h)return n(d,f)?l({format:f,missing:[`route`]}):s({format:f,exportOptions:{coordinateSystem:`z-up`,unit:{length:`millimeter`},...o(p)},...p===void 0?{}:{tessellation:p}});let _=[`coordinateSystem`,`unit`,...p===void 0?[]:[`tessellation`]].filter(e=>!r(h,e));return _.length>0?l({format:f,route:h,missing:_}):{options:{coordinateSystem:`z-up`,unit:{length:`millimeter`},...o(p)},sourceUnit:`mm`,provenance:{requested:g,honored:{format:g.format,coordinateSystem:`z-up`,unit:{length:`millimeter`},sourceUnit:`mm`,...p===void 0?{}:{tessellation:p}},route:c(h)}}};export{u as resolveRuntimeExportIntent};
1
+ const e=new Set([`step`,`stp`]),t=[`toHaveBoundingBox`,`toHaveVolume`,`toHaveSurfaceArea`,`toHaveConnectedComponents`,`toBeWatertight`],n=(e,t)=>({format:e,coordinateSystem:`z-up`,unit:{length:`millimeter`},...t===void 0?{}:{tessellation:t}}),r=e=>e===void 0?{}:{tessellation:{...e.linearTolerance===void 0?{}:{linearTolerance:e.linearTolerance},...e.angularToleranceDegrees===void 0?{}:{angularTolerance:e.angularToleranceDegrees}}},i=e=>{let t=n(e.format,e.tessellation);return{options:e.exportOptions,sourceUnit:`mm`,provenance:{requested:t,honored:{format:t.format,coordinateSystem:`z-up`,unit:{length:`millimeter`},sourceUnit:`mm`,...e.tessellation===void 0?{}:{tessellation:e.tessellation}}}}},a=e=>e?{...e.kernelId===void 0?{}:{kernelId:e.kernelId},...e.sourceFormat===void 0?{}:{sourceFormat:e.sourceFormat},...e.targetFormat===void 0?{}:{targetFormat:e.targetFormat},...e.transcoderId===void 0?{}:{transcoderId:e.transcoderId},...e.fidelity===void 0?{}:{fidelity:e.fidelity},direct:d(e)}:void 0,o=`loadModel({ file, format: 'glb' }) with mesh-supported matchers (${t.join(`, `)})`,s=e=>({success:!1,diagnostics:[{code:`GEOSPEC_KERNEL_BREP_UNSUPPORTED`,severity:`error`,message:`${e.kernelId===void 0?`No kernel in this runtime`:`Kernel '${e.kernelId}'`} has no direct STEP/BRep export route, so exact BRep evidence is unavailable and BRep matchers are unsupported on ${e.kernelId===void 0?`it`:`this kernel`}.`,suggestion:`Keep the requirement and report it as unsupported on this kernel. Load mesh evidence with ${o} for the requirements a mesh can answer.`,details:{format:`step`,...e.kernelId===void 0?{}:{kernelId:e.kernelId},...e.route===void 0?{}:{route:a(e.route)},meshAlternative:{format:`glb`,matchers:[...t]}}}]}),c=e=>typeof e.bestRouteFor==`function`,l=(e,t)=>{let{capabilities:n}=e;if((n?.routes?.length??0)>0)return!0;let{routesFor:r}=e;return typeof r==`function`&&r(t).length>0},u=(e,t)=>t in(e?.exportOptions?.schema.properties??{}),d=e=>e.transcoderId===void 0,f=e=>({success:!1,diagnostics:[{code:`GEOSPEC_CANONICAL_EXPORT_UNSUPPORTED`,severity:`error`,message:`GeoSpec cannot request canonical Z-up millimeter ${e.format.toUpperCase()} evidence from this runtime route.`,suggestion:`Use a runtime route whose export schema exposes coordinateSystem and unit.length, or pass a custom runtime that returns canonical millimeter geometry bytes.`,details:{format:e.format,missing:e.missing,route:a(e.route)}}]}),p=p=>{let{runtime:m,format:h}=p,g=p.meshLinearTolerance===void 0&&p.meshAngularToleranceDegrees===void 0?void 0:{...p.meshLinearTolerance===void 0?{}:{linearTolerance:p.meshLinearTolerance},...p.meshAngularToleranceDegrees===void 0?{}:{angularToleranceDegrees:p.meshAngularToleranceDegrees}},_=c(m),{kernelId:v}=p,y=_?v===void 0?m.bestRouteFor(h):m.bestRouteFor(h,{kernelId:v}):void 0;if(e.has(h)){let e=y!==void 0&&d(y)&&y.fidelity===`brep`;if(_&&!e&&v!==void 0&&l(m,h))return s({kernelId:v,route:y});if(y&&!e)return{success:!1,diagnostics:[{code:`GEOSPEC_DIRECT_STEP_ROUTE_REQUIRED`,severity:`error`,message:`GeoSpec exact BRep evidence requires a direct STEP/BRep runtime export route.`,suggestion:`No kernel in this runtime offers a direct STEP/BRep route, so keep the requirement and report it as unsupported here. Load mesh evidence with ${o} for the requirements a mesh can answer.`,details:{route:a(y),meshAlternative:{format:`glb`,matchers:[...t]}}}]};let n={},r={format:h,sourceUnit:`mm`};return u(y,`coordinateSystem`)&&(n.coordinateSystem=`z-up`,r.coordinateSystem=`z-up`),{options:n,sourceUnit:`mm`,provenance:{requested:{format:h},honored:r,route:a(y)}}}let b=n(h,g);if(!_)return i({format:h,exportOptions:{coordinateSystem:`z-up`,unit:{length:`millimeter`},...r(g)},...g===void 0?{}:{tessellation:g}});if(!y)return l(m,h)?f({format:h,missing:[`route`]}):i({format:h,exportOptions:{coordinateSystem:`z-up`,unit:{length:`millimeter`},...r(g)},...g===void 0?{}:{tessellation:g}});let x=[`coordinateSystem`,`unit`,...g===void 0?[]:[`tessellation`]].filter(e=>!u(y,e));return x.length>0?f({format:h,route:y,missing:x}):{options:{coordinateSystem:`z-up`,unit:{length:`millimeter`},...r(g)},sourceUnit:`mm`,provenance:{requested:b,honored:{format:b.format,coordinateSystem:`z-up`,unit:{length:`millimeter`},sourceUnit:`mm`,...g===void 0?{}:{tessellation:g}},route:a(y)}}};export{t as geoSpecMeshEvidenceMatchers,p as resolveRuntimeExportIntent};
@@ -1,6 +1,7 @@
1
1
  import { GeoSpecSubject } from "./subject.mjs";
2
- import { CreateModelLoaderOptions, GeoSpecModelFormat, GeoSpecModelLoader, GeoSpecRuntimeClient, GeoSpecRuntimeClientFactory, GeoSpecRuntimeSourceAdapter, LoadModelCodeOptions, LoadModelFileOptions, LoadModelOptions, LoadModelSourceOptions, ManagedGeoSpecModelLoader } from "./types.mjs";
3
- import { GeoSpecExportRoute, RuntimeBackedModelFormat, RuntimeClientWithRoutes, RuntimeExportIntent, RuntimeExportIntentFailure, resolveRuntimeExportIntent } from "./export-intent.mjs";
2
+ import { CreateModelLoaderOptions, GeoSpecLoadRegion, GeoSpecModelFormat, GeoSpecModelLoader, GeoSpecRuntimeClient, GeoSpecRuntimeClientFactory, GeoSpecRuntimeSourceAdapter, LoadModelCodeOptions, LoadModelFileOptions, LoadModelOptions, LoadModelSourceOptions, ManagedGeoSpecModelLoader } from "./types.mjs";
3
+ import { GeoSpecExportRoute, RuntimeBackedModelFormat, RuntimeClientWithRoutes, RuntimeExportIntent, RuntimeExportIntentFailure, geoSpecMeshEvidenceMatchers, resolveRuntimeExportIntent } from "./export-intent.mjs";
4
4
  import { GeoSpecModelLoadError } from "./errors.mjs";
5
5
  import { createModelLoader, loadModel } from "./load-model.mjs";
6
- export { type CreateModelLoaderOptions, type GeoSpecExportRoute, type GeoSpecModelFormat, GeoSpecModelLoadError, type GeoSpecModelLoader, type GeoSpecRuntimeClient, type GeoSpecRuntimeClientFactory, type GeoSpecRuntimeSourceAdapter, type GeoSpecSubject, type LoadModelCodeOptions, type LoadModelFileOptions, type LoadModelOptions, type LoadModelSourceOptions, type ManagedGeoSpecModelLoader, type RuntimeBackedModelFormat, type RuntimeClientWithRoutes, type RuntimeExportIntent, type RuntimeExportIntentFailure, createModelLoader, loadModel, resolveRuntimeExportIntent };
6
+ import { geoSpecRegionScopedKinds, geoSpecRegionWholeModelClaimCode } from "./region-scope.mjs";
7
+ export { type CreateModelLoaderOptions, type GeoSpecExportRoute, type GeoSpecLoadRegion, type GeoSpecModelFormat, GeoSpecModelLoadError, type GeoSpecModelLoader, type GeoSpecRuntimeClient, type GeoSpecRuntimeClientFactory, type GeoSpecRuntimeSourceAdapter, type GeoSpecSubject, type LoadModelCodeOptions, type LoadModelFileOptions, type LoadModelOptions, type LoadModelSourceOptions, type ManagedGeoSpecModelLoader, type RuntimeBackedModelFormat, type RuntimeClientWithRoutes, type RuntimeExportIntent, type RuntimeExportIntentFailure, createModelLoader, geoSpecMeshEvidenceMatchers, geoSpecRegionScopedKinds, geoSpecRegionWholeModelClaimCode, loadModel, resolveRuntimeExportIntent };
@@ -1 +1 @@
1
- import{GeoSpecModelLoadError as e}from"./errors.mjs";import{resolveRuntimeExportIntent as t}from"./export-intent.mjs";import{createModelLoader as n,loadModel as r}from"./load-model.mjs";export{e as GeoSpecModelLoadError,n as createModelLoader,r as loadModel,t as resolveRuntimeExportIntent};
1
+ import{GeoSpecModelLoadError as e}from"./errors.mjs";import{geoSpecRegionScopedKinds as t,geoSpecRegionWholeModelClaimCode as n}from"./region-scope.mjs";import{geoSpecMeshEvidenceMatchers as r,resolveRuntimeExportIntent as i}from"./export-intent.mjs";import{createModelLoader as a,loadModel as o}from"./load-model.mjs";export{e as GeoSpecModelLoadError,a as createModelLoader,r as geoSpecMeshEvidenceMatchers,t as geoSpecRegionScopedKinds,n as geoSpecRegionWholeModelClaimCode,o as loadModel,i as resolveRuntimeExportIntent};
@@ -1,4 +1,4 @@
1
- import { GeoSpecModelFormat, GeoSpecRuntimeClient, GeoSpecRuntimeClientFactory, GeoSpecRuntimeSourceAdapter, LoadModelOptions, LoadModelSourceOptions } from "./types.mjs";
1
+ import { GeoSpecLoadRegion, GeoSpecModelFormat, GeoSpecRuntimeClient, GeoSpecRuntimeClientFactory, GeoSpecRuntimeSourceAdapter, LoadModelOptions, LoadModelSourceOptions } from "./types.mjs";
2
2
  import { GeoSpecNativeEngine, GeoSpecNativeSubject } from "../engine/client.mjs";
3
3
  import "../assertion-client/index.mjs";
4
4
  import { SourceRevision } from "@taucad/runtime/types";
@@ -38,6 +38,20 @@ type GeoSpecModelLoadEvidence = {
38
38
  readonly sourceRevision?: SourceRevision;
39
39
  /** Actual direct-source locator, when the load read one; absent for anonymous in-memory bytes. */
40
40
  readonly sourcePath?: string;
41
+ /** Authored default work-unit budget for claims on this subject. */
42
+ readonly workUnitLimit?: number;
43
+ /**
44
+ * The crop box the kernel applied before export. Present only on a cropped load: its subject is
45
+ * the region, never the whole model.
46
+ */
47
+ readonly region?: GeoSpecLoadRegion;
48
+ /**
49
+ * True when this load reused the run's earlier admission of identical inputs: the same file,
50
+ * code, parameters, format, tolerances, region and ingest options, with an unchanged source
51
+ * revision. No export or ingest ran for it; its artifacts and source revision are the earlier
52
+ * load's, and lineage still records it under its own load id.
53
+ */
54
+ readonly reused?: true;
41
55
  readonly artifacts: ReadonlyArray<{
42
56
  readonly name: string;
43
57
  readonly sha256: string;
@@ -72,16 +86,25 @@ type CreateGeoSpecNativeModelLoaderOptions = {
72
86
  * scopes one at a time: a scope's release touches only subjects whose scope has settled.
73
87
  */
74
88
  readonly carried?: Map<string, unknown>;
89
+ /**
90
+ * The largest `loadModel` `workUnitLimit` a spec may author. Host policy: omitted uses
91
+ * `geoSpecMaximumWorkUnitLimit`, and a host provisioned for larger claims raises it.
92
+ */
93
+ readonly maximumWorkUnitLimit?: number;
75
94
  };
76
95
  /**
77
96
  * Model loader injected into native VM runs.
78
97
  *
79
- * The freshness unit is one load: every call reads its source again (the
80
- * source reader or a Runtime export) and the engine digests those exact bytes.
81
- * Nothing is deduplicated per run or per scope; bytes equal to an admitted
82
- * subject's reuse that subject by digest, length and descriptor, and edited
83
- * bytes admit a new subject. Only concurrent identical inline-code Runtime
84
- * loads share one in-flight export.
98
+ * Direct `source` loads read their source again on every call and the engine
99
+ * digests those exact bytes; bytes equal to an admitted subject's reuse that
100
+ * subject by digest, length and descriptor, and edited bytes admit a new
101
+ * subject. Runtime loads are coalesced per release scope (one run): a load
102
+ * whose file, inline code, project path, parameters, format, tolerances,
103
+ * region and ingest options equal an earlier successful load's reuses that
104
+ * admission without exporting or ingesting again. A file load is reused only
105
+ * while a Runtime source snapshot still matches the earlier load's complete
106
+ * source revision; otherwise, or when every owner of the earlier admission has
107
+ * released it, the load exports afresh. Failures are never reused.
85
108
  *
86
109
  * @public
87
110
  */
@@ -1 +1 @@
1
- import{bindRawSubjectResidency as e}from"./subject.mjs";import{GeoSpecModelLoadError as t}from"./errors.mjs";import{resolveRuntimeExportIntent as n}from"./export-intent.mjs";import{sha256Bytes as r}from"@taucad/runtime/kernel";const i={canonicalProfile:`geospec-jcs-v1`,protocolVersion:3,registryVersion:5},a=new TextEncoder,o=new TextDecoder(`utf-8`,{fatal:!0}),s=new Set([`glb`,`step`,`stp`]),c=new WeakMap,l=16777216,u=e=>e instanceof Error&&`code`in e&&e.code===`limit-exceeded`&&e.message===`Engine exceeds the configured retained subject count.`,d=e=>{let t=c.get(e);if(t!==void 0)return t;let n=_(e.processRequest(h({...i,method:`initialize`,requestId:`configuration`})),`initialize response`),r=g(n.result,`initialize result`);if(n.requestId!==`configuration`||r.protocolVersion!==3||r.registryVersion!==5||r.canonicalProfile!==`geospec-jcs-v1`)throw TypeError(`Native GeoSpec residency requires a compatible initialize profile.`);let a=g(r.configuration,`initialize configuration`),o=g(a.binaryAdmissionLimits,`binary admission limits`),s=[o.maxSubjectBytes,o.maxTotalBinaryBytes].map(e=>{if(typeof e!=`number`||!Number.isSafeInteger(e)||e<=0)throw TypeError(`Native GeoSpec residency requires safe positive binary admission limits.`);return e}),l={inputs:new Map,resident:new Map,binaryLimit:Math.max(...s),binaryBytes:0,descriptorBytes:0};return c.set(e,l),l},f=e=>new t(e),p=e=>({code:e.code,severity:`error`,message:e.message,suggestion:e.suggestion,...e.details===void 0?{}:{details:e.details}}),m=e=>({code:e.code,severity:e.severity,message:e.message,suggestion:`Fix the model source or its Runtime export route, then retry the native run.`}),h=e=>a.encode(JSON.stringify(e)),g=(e,t)=>{if(typeof e!=`object`||!e||Array.isArray(e))throw TypeError(`Native GeoSpec returned a non-object ${t}.`);return e},_=(e,t)=>g(JSON.parse(o.decode(e)),t),v=async(e,t)=>{if(e instanceof Uint8Array)return Uint8Array.from(e);if(e instanceof ArrayBuffer)return Uint8Array.from(new Uint8Array(e));if(typeof Blob<`u`&&e instanceof Blob)return new Uint8Array(await e.arrayBuffer());if(t!==void 0)try{return await t(e)}catch(t){throw f([p({code:`GEOSPEC_NATIVE_SOURCE_READ_FAILED`,message:`The host source reader could not read ${typeof e==`string`?e:`this source`}: ${t instanceof Error?t.message:String(t)}`,suggestion:`Check that the source exists at a project-rooted path and is readable, then retry the load.`,...typeof e==`string`?{details:{source:e}}:{}})])}throw f([p({code:`GEOSPEC_NATIVE_SOURCE_READER_UNAVAILABLE`,message:`The native model loader cannot read this source form without a host source reader.`,suggestion:`Pass in-memory bytes or configure readSource on createGeoSpecNativeModelLoader().`})])},y=e=>`code`in e?{files:e.code,entry:e.file}:{path:e.file},b=(e,t)=>{let n=e.format??t??`glb`;if(!s.has(n))throw f([p({code:`GEOSPEC_NATIVE_MODEL_FORMAT_UNSUPPORTED`,message:`The native model host does not admit '${n}' models.`,suggestion:`Use 'step', 'stp', or 'glb' for native model assertions.`,details:{format:n}})]);return n},x=t=>{let a=0,o=new Map,s=new Map,x=0,S=new Set,C=new Map,w=new Set,T=new Map,E=e=>(a+=1,`geospec-native-model:${e}:${a}`),D=e=>(e.sourceAdapters??t.sourceAdapters)?.find(t=>t.extensions.some(t=>e.file.endsWith(t))),O=async e=>{let n=e.runtime??t.runtime;if(typeof n==`function`){let e=T.get(n);e===void 0&&(e=n(),T.set(n,e));let t=await e;return w.add(t),t}if(n!==void 0)return n;let r=D(e);if(r!==void 0){let n=await r.createRuntime({file:e.file,...(e.projectPath??t.projectPath)===void 0?{}:{projectPath:e.projectPath??t.projectPath}});return w.add(n),n}throw f([p({code:`GEOSPEC_NATIVE_RUNTIME_UNAVAILABLE`,message:`No Tau Runtime is configured to export '${e.file}' for native GeoSpec.`,suggestion:`Pass a Runtime client/factory or a matching source adapter to the native runner.`,details:{file:e.file}})])},k=e=>{t.engine.releaseSubject(h({...i,method:`releaseSubject`,requestId:E(`release`),subjectHandle:e}))},A=(e,n)=>{let r=c.get(t.engine),i=r?.inputs.get(e);if(r!==void 0&&i!==void 0&&(i.cleanupPending?.(),delete i.cleanupPending,i.owners.has(n))){if(i.owners.size>1){i.owners.delete(n);return}i.handle!==void 0&&(k(i.handle),delete i.handle),i.owners.delete(n),r.resident.delete(e),r.inputs.delete(e),r.binaryBytes-=i.resources.reduce((e,t)=>e+t.byteLength,i.primary.byteLength),r.descriptorBytes-=i.request.byteLength}},j=e=>{for(let[t,n]of e.inputs)n.cleanupPending!==void 0&&(n.cleanupPending(),delete n.cleanupPending,delete n.handle,e.resident.delete(t))},M=()=>[...t.carried??[]].filter(([e])=>!o.has(e)),N=(e,n)=>{let r=e.format===`stp`?`step`:e.format,a=d(t.engine);j(a);let c=h({...i,method:`ingestSubject`,requestId:E(`ingest`),format:r,frame:{coordinateSystem:`z-up`,sourceUnit:r===`step`?`auto`:e.sourceUnit??`m`,outputUnit:`mm`},ingestOptions:e.ingestOptions??{},...e.diagnostics===void 0||e.diagnostics.length===0?{}:{diagnostics:e.diagnostics},primaryByteLength:e.primary.byteLength,resources:e.resources.map(({name:e,bytes:t})=>({name:e,byteLength:t.byteLength}))}),f=e.resources.reduce((e,{bytes:t})=>e+t.byteLength,e.primary.byteLength);if(!Number.isSafeInteger(f)||f>a.binaryLimit||c.byteLength>l)throw Object.assign(RangeError(`GeoSpec retained admission input exceeds its byte limit.`),{code:`limit-exceeded`});let p=(e,t)=>e.length===t.length&&e.every((e,n)=>e===t[n]),m={..._(c,`admission request`),requestId:``},v=[...a.inputs.values()].find(t=>{let n={..._(t.request,`retained request`),requestId:``};return JSON.stringify(n)===JSON.stringify(m)&&p(t.primary,e.primary)&&t.resources.length===e.resources.length&&t.resources.every((t,n)=>p(t,e.resources[n].bytes))});if(v===void 0&&(f>a.binaryLimit-a.binaryBytes||c.byteLength>l-a.descriptorBytes))throw Object.assign(RangeError(`GeoSpec live admission snapshots exceed their retained input byte limit.`),{code:`limit-exceeded`});let y=v??{request:Uint8Array.from(c),primary:Uint8Array.from(e.primary),resources:e.resources.map(({bytes:e})=>Uint8Array.from(e)),owners:new Set},b=()=>t.engine.ingestSubject(c,e.primary,e.resources.map(({bytes:e})=>e)),x;try{x=b()}catch(e){let n=M();if(!u(e))throw e;let r=!1;for(let[e,i]of n){let n=a.inputs.get(e);if(n===void 0)k(i),r=!0;else{let i=n.owners.size===1&&n.handle!==void 0;A(e,t.carried),r||=i}t.carried?.delete(e)}if(!r){let t=a.resident.entries().next().value;if(t===void 0)throw e;k(t[1].handle),delete t[1].handle,a.resident.delete(t[0])}x=b()}let S=_(x,`admission response`),C=g(S.result,`admission result`),{subjectHash:w}=g(C.subject,`admitted subject`);if(typeof w!=`string`||!/^[0-9a-f]{64}$/u.test(w))throw TypeError(`Native GeoSpec admission did not return a valid subjectHash.`);let T=_(t.engine.subjectHandle(h({...i,method:`subjectHandle`,requestId:E(`handle`),subjectHash:w})),`subject-handle response`),D=g(T.result,`subject-handle result`).subjectHandle;if(D===void 0)throw TypeError(`Native GeoSpec did not return a subject handle.`);let O=a.inputs.get(w),N=O??y;O===void 0&&(a.inputs.set(w,N),a.binaryBytes+=f,a.descriptorBytes+=N.request.byteLength);for(let e of n)N.owners.add(e),s.set(e,w);return N.handle=D,a.resident.delete(w),a.resident.set(w,N),o.set(w,N),{subjectHash:w}},P=e=>{let n=o.get(e);if(n===void 0)throw TypeError(`This subject is not admitted by the active GeoSpec host.`);let r=d(t.engine);if(j(r),n.handle===void 0){let a=()=>t.engine.ingestSubject(n.request,n.primary,n.resources),o;try{o=a()}catch(e){let t=r.resident.entries().next().value;if(!u(e)||t===void 0)throw e;k(t[1].handle),delete t[1].handle,r.resident.delete(t[0]),o=a()}let s=()=>{let n=g(_(t.engine.subjectHandle(h({...i,method:`subjectHandle`,requestId:E(`restore-handle`),subjectHash:e})),`restored handle`).result,`restored handle result`),r=g(n.subjectHandle,`restored subject handle`);if(r.subjectHash!==e)throw TypeError(`Native GeoSpec restored handle changed its subject identity.`);return r};try{if(g(g(_(o,`restored admission`).result,`restored result`).subject,`restored subject`).subjectHash!==e)throw TypeError(`Native GeoSpec restored admission changed its subject identity.`);n.handle=s()}catch(e){throw n.cleanupPending=()=>{try{k(s())}catch(t){throw AggregateError([e,t],`Native GeoSpec restored admission failed and cleanup remains pending.`,{cause:e})}},n.cleanupPending(),delete n.cleanupPending,e}}r.resident.delete(e),r.resident.set(e,n)},F=async(e,n)=>{let i=b(e,t.format),a=await v(e.source,t.readSource),o=await Promise.all((e.resources??[]).map(async({name:e,source:n})=>({name:e,bytes:await v(n,t.readSource),...typeof n==`string`?{sourcePath:n}:{}}))),s=await Promise.all([{name:e.path??e.name??`primary`,bytes:a,...typeof e.source==`string`?{sourcePath:e.source}:{}},...o].map(async({name:e,bytes:t,sourcePath:n})=>({name:e,sha256:await r(t),byteLength:t.byteLength,...n===void 0?{}:{sourcePath:n}})));return{...N({format:i,primary:a,resources:o,...e.ingestOptions===void 0?{}:{ingestOptions:e.ingestOptions},...e.sourceUnit===void 0?{}:{sourceUnit:e.sourceUnit}},n),load:{loadId:``,status:`complete`,format:i,parameters:e.parameters??{},ingestOptions:e.ingestOptions??{},...typeof e.source==`string`?{sourcePath:e.source}:{},artifacts:s}}},I=async(e,i)=>{for(let t of[`sourceUnit`,`unit`,`scale`,`coordinateSystem`])if(t in e)throw f([p({code:`GEOSPEC_NATIVE_RUNTIME_FRAME_OVERRIDE`,message:`Native Runtime model loading rejects '${t}': the Runtime export intent owns the frame.`,suggestion:`Remove the frame override and let GeoSpec request canonical Z-up millimetre evidence.`,details:{key:t}})]);let a=b(e,t.format),o=a===`stp`?`step`:a,s=await O(e);await s.connect();let c={runtime:s,format:o,...e.meshLinearTolerance===void 0?{}:{meshLinearTolerance:e.meshLinearTolerance},...e.meshAngularToleranceDegrees===void 0?{}:{meshAngularToleranceDegrees:e.meshAngularToleranceDegrees}},l=n(c);if(`success`in l)throw f(l.diagnostics);let u=structuredClone(l.options),d=s.open({source:y(e),...e.parameters===void 0?{}:{parameters:e.parameters},watch:!1}),h=await(async()=>{try{return await d.export(o,{options:structuredClone(u)})}finally{d.close()}})();if(!h.success)throw f(h.issues.map(m));let g=[...h.files],[_,...v]=g;if(_===void 0)throw f([p({code:`GEOSPEC_NATIVE_MODEL_EXPORT_EMPTY`,message:`The Tau Runtime exported no ${o.toUpperCase()} artifact for '${e.file}'.`,suggestion:`Check that the selected source exports geometry in the requested format.`,details:{file:e.file,format:o}})]);let x=n(c);if(`success`in x)throw f(x.diagnostics);let S=await Promise.all(g.map(async({name:e,bytes:t})=>({name:e,sha256:await r(t),byteLength:t.byteLength}))),C=h.sourceRevision===void 0?void 0:structuredClone(h.sourceRevision),w=C!==void 0&&/^sha256:[0-9a-f]{64}$/u.test(C.files[C.entry]??``)&&Object.values(C.files).every(e=>e===`missing`||/^sha256:[0-9a-f]{64}$/u.test(e));return{...N({format:o,primary:_.bytes,resources:v,sourceUnit:x.sourceUnit,diagnostics:h.issues.map(m),...e.ingestOptions===void 0?{}:{ingestOptions:e.ingestOptions}},i),load:{loadId:``,status:w?`complete`:`unavailable`,format:o,parameters:e.parameters??{},exportOptions:u,ingestOptions:e.ingestOptions??{},...C===void 0?{}:{sourceRevision:C},artifacts:S}}},L=async(e,n)=>{let r=e.runtime??t.runtime;if(!(`code`in e)||r===void 0||e.parameters!==void 0||e.ingestOptions!==void 0||[`sourceUnit`,`unit`,`scale`,`coordinateSystem`].some(t=>t in e))return I(e,new Set([n]));let i=Object.getPrototypeOf(e.code);if(i!==Object.prototype&&i!==null||Object.values(Object.getOwnPropertyDescriptors(e.code)).some(e=>!(`value`in e)||typeof e.value!=`string`))return I(e,new Set([n]));let a=Object.entries(e.code),o=Object.fromEntries(a),s=JSON.stringify({file:e.file,files:a,format:e.format??t.format??`glb`,meshLinearTolerance:e.meshLinearTolerance,meshAngularToleranceDegrees:e.meshAngularToleranceDegrees}),c=C.get(r);c===void 0&&(c=new Map,C.set(r,c));let l=c.get(s);if(l!==void 0)return l.owners.add(n),l.pending;let u=new Set([n]),d=I({...e,code:o},u);c.set(s,{pending:d,owners:u});let f=()=>{c.delete(s),c.size===0&&C.delete(r)};return d.then(f,f),d};return Object.assign(n=>{let r={},i=E(`load`),a={...n,...`code`in n?{code:Object.fromEntries(Object.entries(n.code))}:{},...n.parameters===void 0?{}:{parameters:structuredClone(n.parameters)},...n.ingestOptions===void 0?{}:{ingestOptions:structuredClone(n.ingestOptions)}},c=n.format??t.format??`glb`,l=[`stepStreaming`,...n.mesh===!1&&(c===`step`||c===`stp`)?[]:[`mesh`],...`source`in n?[`meshLinearTolerance`,`meshAngularToleranceDegrees`,...c===`step`||c===`stp`?[`sourceUnit`]:[]]:[]].find(e=>e in n);if(l!==void 0)return Promise.reject(f([p({code:`GEOSPEC_MODEL_OPTION_UNSUPPORTED`,message:`This GeoSpec host cannot honor model option '${l}'.`,suggestion:`Remove this option or configure a host that supports its declared behavior.`,details:{option:l}})]));let u=`source`in n?F(a,new Set([r])):L(a,r),d=(async()=>{let t=await u,n={...t,...t.load===void 0?{}:{load:{...t.load,loadId:i}}},a=x,c=!1;return e(n,{ensureResident(){if(a!==x||!s.has(r))throw TypeError(`This subject is not admitted by the active GeoSpec host.`);P(t.subjectHash)},dispose(){if(!(c||!s.has(r))){c=!0;try{A(t.subjectHash,r),s.delete(r),[...s.values()].includes(t.subjectHash)||o.delete(t.subjectHash)}finally{c=!1}}}}),n})();return S.add(d),d},{async releaseAll(){do{let e=[...S];await Promise.allSettled(e);for(let t of e)S.delete(t)}while(S.size>0);let e=[],{carried:n}=t;if(x+=1,n!==void 0){for(let[r,i]of M())try{c.get(t.engine)?.inputs.has(r)?A(r,n):k(i),n.delete(r)}catch(t){e.push(t)}for(let[e,t]of o)t.handle===void 0?(t.owners.delete(n),n.delete(e)):(t.owners.add(n),n.set(e,t.handle))}for(let[t,n]of[...s].reverse())try{A(n,t),s.delete(t),[...s.values()].includes(n)||o.delete(n)}catch(t){e.push(t)}for(let t of w)try{t.shutdown?await t.shutdown():t.terminate()}catch(t){e.push(t)}if(w.clear(),T.clear(),e.length>0)throw AggregateError(e,`Native GeoSpec model cleanup failed.`)}})};export{x as createGeoSpecNativeModelLoader};
1
+ import{geoSpecAuthoredWorkUnitLimitProblem as e,resolveGeoSpecMaximumWorkUnitLimit as t}from"../engine/client.mjs";import{bindRawSubjectResidency as n}from"./subject.mjs";import{GeoSpecModelLoadError as r}from"./errors.mjs";import{resolveRuntimeExportIntent as i}from"./export-intent.mjs";import{sha256Bytes as a}from"@taucad/runtime/kernel";const o={canonicalProfile:`geospec-jcs-v1`,protocolVersion:3,registryVersion:5},s=new TextEncoder,c=new TextDecoder(`utf-8`,{fatal:!0}),l=new Set([`glb`,`step`,`stp`]),u=new WeakMap,d=16777216,f=e=>e instanceof Error&&`code`in e&&e.code===`limit-exceeded`&&e.message===`Engine exceeds the configured retained subject count.`,p=e=>{let t=u.get(e);if(t!==void 0)return t;let n=y(e.processRequest(_({...o,method:`initialize`,requestId:`configuration`})),`initialize response`),r=v(n.result,`initialize result`);if(n.requestId!==`configuration`||r.protocolVersion!==3||r.registryVersion!==5||r.canonicalProfile!==`geospec-jcs-v1`)throw TypeError(`Native GeoSpec residency requires a compatible initialize profile.`);let i=v(r.configuration,`initialize configuration`),a=v(i.binaryAdmissionLimits,`binary admission limits`),s=[a.maxSubjectBytes,a.maxTotalBinaryBytes].map(e=>{if(typeof e!=`number`||!Number.isSafeInteger(e)||e<=0)throw TypeError(`Native GeoSpec residency requires safe positive binary admission limits.`);return e}),c={inputs:new Map,resident:new Map,binaryLimit:Math.max(...s),binaryBytes:0,descriptorBytes:0};return u.set(e,c),c},m=e=>new r(e),h=e=>({code:e.code,severity:`error`,message:e.message,suggestion:e.suggestion,...e.details===void 0?{}:{details:e.details}}),g=e=>({code:e.code,severity:e.severity,message:e.message,suggestion:`Fix the model source or its Runtime export route, then retry the native run.`}),_=e=>s.encode(JSON.stringify(e)),v=(e,t)=>{if(typeof e!=`object`||!e||Array.isArray(e))throw TypeError(`Native GeoSpec returned a non-object ${t}.`);return e},y=(e,t)=>v(JSON.parse(c.decode(e)),t),b=async(e,t)=>{if(e instanceof Uint8Array)return Uint8Array.from(e);if(e instanceof ArrayBuffer)return Uint8Array.from(new Uint8Array(e));if(typeof Blob<`u`&&e instanceof Blob)return new Uint8Array(await e.arrayBuffer());if(t!==void 0)try{return await t(e)}catch(t){throw m([h({code:`GEOSPEC_NATIVE_SOURCE_READ_FAILED`,message:`The host source reader could not read ${typeof e==`string`?e:`this source`}: ${t instanceof Error?t.message:String(t)}`,suggestion:`Check that the source exists at a project-rooted path and is readable, then retry the load.`,...typeof e==`string`?{details:{source:e}}:{}})])}throw m([h({code:`GEOSPEC_NATIVE_SOURCE_READER_UNAVAILABLE`,message:`The native model loader cannot read this source form without a host source reader.`,suggestion:`Pass in-memory bytes or configure readSource on createGeoSpecNativeModelLoader().`})])},x=e=>`code`in e?{files:e.code,entry:e.file}:{path:e.file},S=(e,t)=>{let n=e.format??t??`glb`;if(!l.has(n))throw m([h({code:`GEOSPEC_NATIVE_MODEL_FORMAT_UNSUPPORTED`,message:`The native model host does not admit '${n}' models.`,suggestion:`Use 'step', 'stp', or 'glb' for native model assertions.`,details:{format:n}})]);return n},C=(e,t)=>{let{routesFor:n,bestRouteFor:r}=e;if(typeof n==`function`)return n.call(e,t);let i=typeof r==`function`?r.call(e,t):void 0;return i===void 0?[]:[i]},w=async(e,t)=>{if(typeof e.describe==`function`)try{let{kernelId:n}=await e.describe({source:x(t)});return typeof n==`string`&&n.length>0?n:void 0}catch{return}},T=e=>{if(e===null||typeof e==`boolean`||typeof e==`string`)return JSON.stringify(e);if(typeof e==`number`)return Number.isFinite(e)?JSON.stringify(e):void 0;if(Array.isArray(e)){let t=e.map(e=>T(e));return t.includes(void 0)?void 0:`[${t.join(`,`)}]`}if(typeof e==`object`&&Object.getPrototypeOf(e)===Object.prototype){let t=Object.entries(e).filter(([,e])=>e!==void 0).sort(([e],[t])=>e<t?-1:1).map(([e,t])=>[e,T(t)]);return t.some(([,e])=>e===void 0)?void 0:`{${t.map(([e,t])=>`${JSON.stringify(e)}:${t}`).join(`,`)}}`}},E=e=>Array.isArray(e)&&e.length===3&&e.every(e=>typeof e==`number`&&Number.isFinite(e)),D=e=>{if(e===void 0)return;let t=typeof e==`object`&&e?e:{},{min:n,max:r}=t;return E(n)&&E(r)&&Object.keys(t).every(e=>e===`min`||e===`max`)&&n.every((e,t)=>e<r[t])?void 0:m([h({code:`GEOSPEC_MODEL_OPTION_INVALID`,message:`loadModel region must be { min: [x, y, z], max: [x, y, z] } in millimetres with every min axis below its max axis.`,suggestion:`Pass finite millimetre corners of the box to check, for example { min: [0, 0, 0], max: [40, 40, 60] }.`,details:{option:`region`,region:structuredClone(e)}})])},O=({min:e,max:t})=>({min:[e[0],e[1],e[2]],max:[t[0],t[1],t[2]]}),k=(e,t)=>{let n=t.sourceRevision?.files??Object.fromEntries(t.data.files.map(({path:e,sha256:t})=>[e,`sha256:${t}`]));return(t.sourceRevision?.entry??t.data.entryPath)===e.entry&&Object.entries(n).every(([t,n])=>n===`missing`||e.files[t]===n)&&Object.entries(e.files).every(([e,t])=>t===`missing`?n[e]===void 0||n[e]===`missing`:n[e]===t)},A=r=>{let s=0,c=t(r.maximumWorkUnitLimit),l=new Map,E=new Map,A=0,j=new Set,M=new Map,N=new Set,P=new Map,F=e=>(s+=1,`geospec-native-model:${e}:${s}`),I=e=>(e.sourceAdapters??r.sourceAdapters)?.find(t=>t.extensions.some(t=>e.file.endsWith(t))),L=async e=>{let t=e.runtime??r.runtime;if(typeof t==`function`){let e=P.get(t);e===void 0&&(e=t(),P.set(t,e));let n=await e;return N.add(n),n}if(t!==void 0)return t;let n=I(e);if(n!==void 0){let t=await n.createRuntime({file:e.file,...(e.projectPath??r.projectPath)===void 0?{}:{projectPath:e.projectPath??r.projectPath}});return N.add(t),t}throw m([h({code:`GEOSPEC_NATIVE_RUNTIME_UNAVAILABLE`,message:`No Tau Runtime is configured to export '${e.file}' for native GeoSpec.`,suggestion:`Pass a Runtime client/factory or a matching source adapter to the native runner.`,details:{file:e.file}})])},R=e=>{r.engine.releaseSubject(_({...o,method:`releaseSubject`,requestId:F(`release`),subjectHandle:e}))},z=(e,t)=>{let n=u.get(r.engine),i=n?.inputs.get(e);if(n!==void 0&&i!==void 0&&(i.cleanupPending?.(),delete i.cleanupPending,i.owners.has(t))){if(i.owners.size>1){i.owners.delete(t);return}i.handle!==void 0&&(R(i.handle),delete i.handle),i.owners.delete(t),n.resident.delete(e),n.inputs.delete(e),n.binaryBytes-=i.resources.reduce((e,t)=>e+t.byteLength,i.primary.byteLength),n.descriptorBytes-=i.request.byteLength}},B=e=>{for(let[t,n]of e.inputs)n.cleanupPending!==void 0&&(n.cleanupPending(),delete n.cleanupPending,delete n.handle,e.resident.delete(t))},V=()=>[...r.carried??[]].filter(([e])=>!l.has(e)),H=(e,t)=>{let n=e.format===`stp`?`step`:e.format,i=p(r.engine);B(i);let a=_({...o,method:`ingestSubject`,requestId:F(`ingest`),format:n,frame:{coordinateSystem:`z-up`,sourceUnit:n===`step`?`auto`:e.sourceUnit??`m`,outputUnit:`mm`},ingestOptions:e.ingestOptions??{},...e.diagnostics===void 0||e.diagnostics.length===0?{}:{diagnostics:e.diagnostics},primaryByteLength:e.primary.byteLength,resources:e.resources.map(({name:e,bytes:t})=>({name:e,byteLength:t.byteLength}))}),s=e.resources.reduce((e,{bytes:t})=>e+t.byteLength,e.primary.byteLength);if(!Number.isSafeInteger(s)||s>i.binaryLimit||a.byteLength>d)throw Object.assign(RangeError(`GeoSpec retained admission input exceeds its byte limit.`),{code:`limit-exceeded`});let c=(e,t)=>e.length===t.length&&e.every((e,n)=>e===t[n]),u={...y(a,`admission request`),requestId:``},m=[...i.inputs.values()].find(t=>{let n={...y(t.request,`retained request`),requestId:``};return JSON.stringify(n)===JSON.stringify(u)&&c(t.primary,e.primary)&&t.resources.length===e.resources.length&&t.resources.every((t,n)=>c(t,e.resources[n].bytes))});if(m===void 0&&(s>i.binaryLimit-i.binaryBytes||a.byteLength>d-i.descriptorBytes))throw Object.assign(RangeError(`GeoSpec live admission snapshots exceed their retained input byte limit.`),{code:`limit-exceeded`});let h=m??{request:Uint8Array.from(a),primary:Uint8Array.from(e.primary),resources:e.resources.map(({bytes:e})=>Uint8Array.from(e)),owners:new Set},g=()=>r.engine.ingestSubject(a,e.primary,e.resources.map(({bytes:e})=>e)),b;try{b=g()}catch(e){let t=V();if(!f(e))throw e;let n=!1;for(let[e,a]of t){let t=i.inputs.get(e);if(t===void 0)R(a),n=!0;else{let i=t.owners.size===1&&t.handle!==void 0;z(e,r.carried),n||=i}r.carried?.delete(e)}if(!n){let t=i.resident.entries().next().value;if(t===void 0)throw e;R(t[1].handle),delete t[1].handle,i.resident.delete(t[0])}b=g()}let x=y(b,`admission response`),S=v(x.result,`admission result`),{subjectHash:C}=v(S.subject,`admitted subject`);if(typeof C!=`string`||!/^[0-9a-f]{64}$/u.test(C))throw TypeError(`Native GeoSpec admission did not return a valid subjectHash.`);let w=y(r.engine.subjectHandle(_({...o,method:`subjectHandle`,requestId:F(`handle`),subjectHash:C})),`subject-handle response`),T=v(w.result,`subject-handle result`).subjectHandle;if(T===void 0)throw TypeError(`Native GeoSpec did not return a subject handle.`);let D=i.inputs.get(C),O=D??h;D===void 0&&(i.inputs.set(C,O),i.binaryBytes+=s,i.descriptorBytes+=O.request.byteLength);for(let e of t)O.owners.add(e),E.set(e,C);return O.handle=T,i.resident.delete(C),i.resident.set(C,O),l.set(C,O),{subjectHash:C}},U=e=>{let t=l.get(e);if(t===void 0)throw TypeError(`This subject is not admitted by the active GeoSpec host.`);let n=p(r.engine);if(B(n),t.handle===void 0){let i=()=>r.engine.ingestSubject(t.request,t.primary,t.resources),a;try{a=i()}catch(e){let t=n.resident.entries().next().value;if(!f(e)||t===void 0)throw e;R(t[1].handle),delete t[1].handle,n.resident.delete(t[0]),a=i()}let s=()=>{let t=v(y(r.engine.subjectHandle(_({...o,method:`subjectHandle`,requestId:F(`restore-handle`),subjectHash:e})),`restored handle`).result,`restored handle result`),n=v(t.subjectHandle,`restored subject handle`);if(n.subjectHash!==e)throw TypeError(`Native GeoSpec restored handle changed its subject identity.`);return n};try{if(v(v(y(a,`restored admission`).result,`restored result`).subject,`restored subject`).subjectHash!==e)throw TypeError(`Native GeoSpec restored admission changed its subject identity.`);t.handle=s()}catch(e){throw t.cleanupPending=()=>{try{R(s())}catch(t){throw AggregateError([e,t],`Native GeoSpec restored admission failed and cleanup remains pending.`,{cause:e})}},t.cleanupPending(),delete t.cleanupPending,e}}n.resident.delete(e),n.resident.set(e,t)},W=async(e,t)=>{let n=S(e,r.format),i=await b(e.source,r.readSource),o=await Promise.all((e.resources??[]).map(async({name:e,source:t})=>({name:e,bytes:await b(t,r.readSource),...typeof t==`string`?{sourcePath:t}:{}}))),s=await Promise.all([{name:e.path??e.name??`primary`,bytes:i,...typeof e.source==`string`?{sourcePath:e.source}:{}},...o].map(async({name:e,bytes:t,sourcePath:n})=>({name:e,sha256:await a(t),byteLength:t.byteLength,...n===void 0?{}:{sourcePath:n}})));return{...H({format:n,primary:i,resources:o,...e.ingestOptions===void 0?{}:{ingestOptions:e.ingestOptions},...e.sourceUnit===void 0?{}:{sourceUnit:e.sourceUnit}},t),load:{loadId:``,status:`complete`,format:n,parameters:e.parameters??{},ingestOptions:e.ingestOptions??{},...typeof e.source==`string`?{sourcePath:e.source}:{},...e.workUnitLimit===void 0?{}:{workUnitLimit:e.workUnitLimit},artifacts:s}}},G=async(e,t)=>{for(let t of[`sourceUnit`,`unit`,`scale`,`coordinateSystem`])if(t in e)throw m([h({code:`GEOSPEC_NATIVE_RUNTIME_FRAME_OVERRIDE`,message:`Native Runtime model loading rejects '${t}': the Runtime export intent owns the frame.`,suggestion:`Remove the frame override and let GeoSpec request canonical Z-up millimetre evidence.`,details:{key:t}})]);let n=S(e,r.format),o=n===`stp`?`step`:n,s=await L(e);await s.connect();let c=await w(s,e),l={runtime:s,format:o,...c===void 0?{}:{kernelId:c},...e.meshLinearTolerance===void 0?{}:{meshLinearTolerance:e.meshLinearTolerance},...e.meshAngularToleranceDegrees===void 0?{}:{meshAngularToleranceDegrees:e.meshAngularToleranceDegrees}},u=i(l);if(`success`in u)throw m(u.diagnostics);let d=e.region===void 0?void 0:O(e.region);if(d!==void 0){let t=C(s,o).filter(e=>c===void 0||e.kernelId===c);if(!t.some(e=>e.transcoderId===void 0&&`region`in(e.exportOptions?.schema.properties??{}))){let n=[...new Set(t.flatMap(e=>e.kernelId===void 0?[]:[e.kernelId]))];throw m([h({code:`GEOSPEC_MODEL_REGION_UNSUPPORTED`,message:`No direct ${o.toUpperCase()} export of this Runtime${n.length===0?``:` (${n.join(`, `)})`} declares a region option, so GeoSpec cannot crop '${e.file}' before admission.`,suggestion:`Load the whole model; if it exceeds a host limit, provision more resources and report the claim as not decided at this capacity. A region load needs a kernel whose GLB export crops, such as PicoVoxel, and only scopes a separate claim to that region.`,details:{file:e.file,format:o,region:d,kernelIds:n}})])}}let f={...structuredClone(u.options),...d===void 0?{}:{region:{min:[...d.min],max:[...d.max]}}},p=s.open({source:x(e),...e.parameters===void 0?{}:{parameters:e.parameters},watch:!1}),_=await(async()=>{try{return await p.export(o,{options:structuredClone(f)})}finally{p.close()}})();if(!_.success)throw m(_.issues.map(g));if(d!==void 0&&!_.issues.some(e=>{let{details:t}=e,n=typeof t==`object`&&t&&`region`in t?t.region:void 0;return typeof n==`object`&&!!n&&T(n)===T({min:[...d.min],max:[...d.max]})}))throw m([h({code:`GEOSPEC_MODEL_REGION_NOT_HONORED`,message:`The Runtime export of '${e.file}' did not confirm the requested region crop, so its bytes may be the whole model.`,suggestion:`Load the whole model, or update the kernel so its export reports the region it cropped.`,details:{file:e.file,format:o,region:d}})]);let v=[..._.files],[y,...b]=v;if(y===void 0)throw m([h({code:`GEOSPEC_NATIVE_MODEL_EXPORT_EMPTY`,message:`The Tau Runtime exported no ${o.toUpperCase()} artifact for '${e.file}'.`,suggestion:`Check that the selected source exports geometry in the requested format.`,details:{file:e.file,format:o}})]);let E=i(l);if(`success`in E)throw m(E.diagnostics);let D=await Promise.all(v.map(async({name:e,bytes:t})=>({name:e,sha256:await a(t),byteLength:t.byteLength}))),k=_.sourceRevision===void 0?void 0:structuredClone(_.sourceRevision),A=k!==void 0&&/^sha256:[0-9a-f]{64}$/u.test(k.files[k.entry]??``)&&Object.values(k.files).every(e=>e===`missing`||/^sha256:[0-9a-f]{64}$/u.test(e));return{...H({format:o,primary:y.bytes,resources:b,sourceUnit:E.sourceUnit,diagnostics:_.issues.map(g),...e.ingestOptions===void 0?{}:{ingestOptions:e.ingestOptions}},t),load:{loadId:``,status:A?`complete`:`unavailable`,format:o,parameters:e.parameters??{},exportOptions:f,ingestOptions:e.ingestOptions??{},...k===void 0?{}:{sourceRevision:k},...e.workUnitLimit===void 0?{}:{workUnitLimit:e.workUnitLimit},...d===void 0?{}:{region:d},artifacts:D}}},K=(e,t)=>{let n=u.get(r.engine)?.inputs.get(e);return n===void 0||n.owners.size===0?!1:(n.owners.add(t),E.set(t,e),l.set(e,n),!0)},q=async(e,t)=>{let n=t.status===`complete`?t.sourceRevision:void 0;if(n===void 0)return!1;let r=await L(e);if(r.snapshotSource===void 0)return!1;try{let t=await r.snapshotSource({source:{path:e.file}});return t.success&&k(n,t)}catch{return!1}},J=async(e,t)=>{let n=e.runtime??r.runtime,i=`code`in e,a=i&&Object.values(e.code).every(e=>typeof e==`string`)?e.code:void 0,o=n===void 0||i&&a===void 0||[`sourceUnit`,`unit`,`scale`,`coordinateSystem`].some(t=>t in e)?void 0:T({file:e.file,code:a,projectPath:e.projectPath??r.projectPath,format:e.format??r.format??`glb`,meshLinearTolerance:e.meshLinearTolerance,meshAngularToleranceDegrees:e.meshAngularToleranceDegrees,parameters:e.parameters,ingestOptions:e.ingestOptions,region:e.region});if(n===void 0||o===void 0)return G(e,new Set([t]));let s=M.get(n);s===void 0&&(s=new Map,M.set(n,s));let c=s.get(o);if(c!==void 0){c.owners?.add(t);let n=await c.pending;if((c.owners!==void 0||await q(e,n.load))&&K(n.subjectHash,t)){let{workUnitLimit:t,...r}=n.load;return{subjectHash:n.subjectHash,load:{...r,...e.workUnitLimit===void 0?{}:{workUnitLimit:e.workUnitLimit},reused:!0}}}}let l=new Set([t]),u=G(e,l),d={pending:u,...a===void 0?{}:{owners:l}};return s.set(o,d),u.then(void 0,()=>{s.delete(o)}),u};return Object.assign(t=>{let i={},a=F(`load`),o={...t,...`code`in t?{code:Object.fromEntries(Object.entries(t.code))}:{},...t.parameters===void 0?{}:{parameters:structuredClone(t.parameters)},...t.ingestOptions===void 0?{}:{ingestOptions:structuredClone(t.ingestOptions)},...`region`in t&&t.region!==void 0?{region:structuredClone(t.region)}:{}},s=t.format??r.format??`glb`,u=[`stepStreaming`,...t.mesh===!1&&(s===`step`||s===`stp`)?[]:[`mesh`],...`source`in t?[`meshLinearTolerance`,`meshAngularToleranceDegrees`,`region`,...s===`step`||s===`stp`?[`sourceUnit`]:[]]:[],...s===`step`||s===`stp`?[`region`]:[]].find(e=>e in t&&(e!==`region`||t[e]!==void 0));if(u!==void 0)return Promise.reject(m([h({code:`GEOSPEC_MODEL_OPTION_UNSUPPORTED`,message:`This GeoSpec host cannot honor model option '${u}'.`,suggestion:`Remove this option or configure a host that supports its declared behavior.`,details:{option:u}})]));let d=e(t.workUnitLimit,`loadModel workUnitLimit`,c);if(d!==void 0)return Promise.reject(m([h({code:`GEOSPEC_MODEL_OPTION_INVALID`,message:d,suggestion:`Pass a positive integer workUnitLimit within the stated bound, or omit it for the engine default.`,details:{option:`workUnitLimit`}})]));let f=`source`in t?void 0:D(t.region);if(f!==void 0)return Promise.reject(f);let p=`source`in t?W(o,new Set([i])):J(o,i),g=(async()=>{let e=await p,t={...e,...e.load===void 0?{}:{load:{...e.load,loadId:a}}},r=A,o=!1;return n(t,{ensureResident(){if(r!==A||!E.has(i))throw TypeError(`This subject is not admitted by the active GeoSpec host.`);U(e.subjectHash)},dispose(){if(!(o||!E.has(i))){o=!0;try{z(e.subjectHash,i),E.delete(i),[...E.values()].includes(e.subjectHash)||l.delete(e.subjectHash)}finally{o=!1}}}}),t})();return j.add(g),g},{async releaseAll(){do{let e=[...j];await Promise.allSettled(e);for(let t of e)j.delete(t)}while(j.size>0);let e=[],{carried:t}=r;if(A+=1,M.clear(),t!==void 0){for(let[n,i]of V())try{u.get(r.engine)?.inputs.has(n)?z(n,t):R(i),t.delete(n)}catch(t){e.push(t)}for(let[e,n]of l)n.handle===void 0?(n.owners.delete(t),t.delete(e)):(n.owners.add(t),t.set(e,n.handle))}for(let[t,n]of[...E].reverse())try{z(n,t),E.delete(t),[...E.values()].includes(n)||l.delete(n)}catch(t){e.push(t)}for(let t of N)try{t.shutdown?await t.shutdown():t.terminate()}catch(t){e.push(t)}if(N.clear(),P.clear(),e.length>0)throw AggregateError(e,`Native GeoSpec model cleanup failed.`)}})};export{A as createGeoSpecNativeModelLoader};