GeoSpec
engine 0.4.0

API reference

Every export of geospec and the geospec-engine wheel, with options, defaults, results, errors and constants in TypeScript and Python.

Open Markdown

npm install geospec@beta geospec-engine gives TypeScript the assertions and the engine; pip install geospec-engine gives Python the geospec package with the engine inside. Install covers the engine artifact each host loads.

The examples run beside the demo models plate-with-through-bore.step, a 40 × 40 × 20 mm STEP plate with a 10 mm bore, and involute-gear.glb, a gear mesh in millimetres.

Every option and its default

import { readFile } from 'node:fs/promises';
import { expectGeo } from 'geospec';
import { createModelLoader } from 'geospec/model';
import { Engine } from 'geospec-engine/node';

const loadModel = createModelLoader({
  // required; Engine takes the
  // cache, CPU permits and limits
  engine: new Engine(),
  // required for a path
  readSource: async (path) => new Uint8Array(await readFile(path)),
  // default for every load
  format: 'glb',
});
const plate = await loadModel({
  // a path, or the bytes
  source: 'plate-with-through-bore.step',
  // default 'glb'; also 'stp'
  format: 'step',
  // engine default: 8,000,000
  workUnitLimit: 8_000_000,
});
const gear = await loadModel({
  source: 'involute-gear.glb',
  // default 'm' for a mesh file
  sourceUnit: 'mm',
});
const size = expectGeo(plate, {
  // default: the subject's,
  // then the engine's
  workUnitLimit: 8_000_000,
}).toHaveBoundingBox({
  size: { x: 40, y: 40, z: 20 },
  // default 0.02 mm
  tolerance: 0.02,
});
console.log(size.report.claimId, size.report.status, size.report.claim.workUnitBudget); // geospec-claim-1 passed 8000000
await loadModel.dispose();
from geospec import GeoSpecEngine, expect_geo, load_model

engine = GeoSpecEngine(
    # default None, and file=
    # refuses until it is set
    model_root=".",
    # engine default: 8_000_000
    work_unit_budget=None,
    # up to the host's CPU count
    execution_permits=None,
    # default: the entry profile
    limits=None,
    # set both to cache on disk
    cache_root=None,
    project_root=None,
    # required for
    # load_model(code=...)
    model_loader=None,
)
with engine:
    plate = load_model(
        # under model_root; or
        # source=bytes | path
        file="plate-with-through-bore.step",
        # default "glb"; also
        # "gltf" and "stp"
        format="step",
        # default "auto" for STEP
        # and "m" for a mesh
        source_unit="auto",
        # glTF buffers: dicts with
        # "name" and "source"
        resources=(),
        # for STEP: {"name": ...},
        # which changes the hash
        ingest_options=None,
        # provenance name;
        # default: file
        name="plate.step",
    )
    report = expect_geo(plate, claim_id="size").to_have_bounding_box(size={"x": 40, "y": 40, "z": 20})
    print(report.passed, report.result["status"], report.claim["workUnitBudget"])  # True passed 8000000

Entry points

  • geospec: expectGeo, geoSpecMatcherNames and the types; describe, it and test for spec files.
  • geospec/model: loadModel, createModelLoader, GeoSpecModelLoadError.
  • geospec/assertion-client: createGeoSpecAssertionClient, GeoSpecAssertionError, geoSpecMaximumWorkUnitLimit.
  • geospec/runner/native: createGeoSpecNativeModelLoader.
  • geospec/vitest: setupGeoSpecVitest, installGeoSpecVitest, createGeoSpecVitestAdapter.
  • geospec-engine: Engine, initialize, canonicalize, ProtocolError (WASM).
  • geospec-engine/node: Engine, canonicalize, ProtocolError (native addon).

geospec exports 52 names: the nine documented here, GeoSpecMatchers, GeoSpecLoadModelOptions and the typed dicts behind them; geospec_engine_native is the engine over bytes, and the wheel registers the pytest plugin geospec.

The engine's byte protocol is for embedders and lives in the engine package README.

Load a model

loadModel

Read a model, admit it to the engine and return the subject claims measure.

loadModel(options: LoadModelSourceOptions): Promise<GeoSpecSubject>
  • Parameters: as annotated in the options block, plus resources, named glTF buffers, and name, path and parameters for the load record.
  • Returns: an opaque GeoSpecSubject, alive until loadModel.dispose().
  • Throws: GeoSpecModelLoadError; its first diagnostic's code names the cause: GEOSPEC_NATIVE_MODEL_FORMAT_UNSUPPORTED outside glb, step and stp, GEOSPEC_MODEL_OPTION_UNSUPPORTED for mesh, stepStreaming, the mesh tolerances and sourceUnit on STEP, GEOSPEC_NATIVE_RUNTIME_UNAVAILABLE for { file } without a CAD runtime.
const plate = await loadModel({ source: 'plate-with-through-bore.step', format: 'step' });
const gear = await loadModel({ source: 'involute-gear.glb', sourceUnit: 'mm' });
const size = expectGeo(plate).toHaveBoundingBox({ size: { x: 40, y: 40, z: 20 } });
console.log(
  size.report.status,
  size.report.evidence.measured.size,
  size.report.evidence.subjectContentHash.slice(0, 16),
);
// passed [ 40, 40, 20 ] fb622c854152ce81
load_model(*, file=..., source=..., format="glb", source_unit=..., resources=(), ingest_options=None, name=...) -> GeoSpecSubject
  • Parameters: as annotated in the options block: exactly one of file and source; format; source_unit; resources, each with exactly name and source; ingest_options; name.
  • Returns: a frozen GeoSpecSubject (engine, slot, identity_field, identity, provenance), alive until close() on it or on the engine.
  • Raises: GeoSpecAssertionError with GEOSPEC_MODEL_HOST_UNAVAILABLE, GEOSPEC_MODEL_PATH_OUTSIDE_ROOT, GEOSPEC_MODEL_FORMAT_UNSUPPORTED, GEOSPEC_MODEL_OPTION_UNSUPPORTED or invalid-field; FileNotFoundError for a missing file; TypeError for file with source.
plate = load_model(file="plate-with-through-bore.step", format="step")
gear = load_model(file="involute-gear.glb", source_unit="mm")
print(plate.provenance["primary"]["sha256"][:16], plate.identity_field, gear.identity[:16])  # fb622c854152ce81 subjectHash a9913a341848cb6b

createModelLoader

createModelLoader(defaults: CreateModelLoaderOptions): ManagedGeoSpecModelLoader
  • Parameters: engine; readSource, required for a path source; format and the mesh options as defaults; projectPath, runtime and sourceAdapters for code models.
  • Returns: loadModel with dispose(), which releases every subject it admitted.
  • Throws: nothing on creation; a path load without readSource throws GEOSPEC_NATIVE_SOURCE_READER_UNAVAILABLE.
const plate = await loadModel({ source: 'plate-with-through-bore.step', format: 'step' });
await loadModel.dispose();
expectGeo(plate).toHaveVolume({ value: 30429.2 }); // TypeError GEOSPEC_SUBJECT_INVALID: reload it within its live scope

GeoSpecEngine is the loader: it owns its subjects and runs on the thread that created it.

GeoSpecEngine(*, model_root=None, work_unit_budget=None, execution_permits=None, cache_root=None, project_root=None, model_loader=None)
  • Parameters: as in the options block above; cache_root and project_root go together.
  • Returns: the engine; as a context manager it is load_model's engine and closes on exit, releasing every subject.
  • Raises: ValueError for a budget that is not a positive safe integer, permits outside 1 to the CPU count, or a cache root alone; RuntimeError off the owner thread or after close().
with GeoSpecEngine(model_root=".") as engine:
    print(engine.protocol_version, engine.registry_version, len(engine.capabilities), engine.configuration["defaultWorkUnitBudget"])  # 3 5 32 8000000

State a claim

expectGeo

Start a chain on a subject; each matcher call is one claim, answered in that call. The matcher reference lists the 26 matchers and their fields; a scalar matcher takes { value, tolerance }.

expectGeo(subject: GeoSpecSubject, options?: GeoSpecExpectOptions): GeoSpecMatcher
  • Parameters: the subject; workUnitLimit, this chain's budget.
  • Returns: the matchers and .not, each returning a GeoSpecAssertion with the engine's report; claim ids count geospec-claim-N per process, shared with the assertion client.
  • Throws: GeoSpecAssertionError for a failed or refused claim, carrying the same report; ProtocolError invalid-claim for a plan the engine rejects, such as a negated query; TypeError for a subject outside its live scope or a workUnitLimit outside 1 to the host's maximum.
const noHole = expectGeo(plate).not.toHaveCircularHole({ diameter: 12 });
console.log(noHole.report.claimId, noHole.report.polarity, noHole.report.status); // geospec-claim-1 negative passed
expectGeo(gear, { workUnitLimit: 100 }).toHaveBoundingBox({ size: { z: 5 } }); // GeoSpecAssertionError: refused, MATCHER_TIMEOUT, details.used 3744
expect_geo(subject, *, claim_id=None) -> GeoSpecMatchers
  • Parameters: the subject; claim_id, default geospec-claim-N from one counter per engine.
  • Returns: the matchers and .not_, each returning a GeoSpecAssertionReport; a matcher is the snake_case registry name and takes keyword fields with their camelCase names (toleranceMm=), or one positional dict.
  • Raises: GeoSpecAssertionError for a failed or refused claim, and with code invalid-claim for a plan the engine rejects; AttributeError for a name outside the registry; TypeError for a positional dict with keyword fields.
report = expect_geo(plate).to_have_bounding_box(size={"x": 40, "y": 40, "z": 20})
print(report.claim["claimId"], report.result["status"], report.result["evidence"]["measured"]["size"])  # geospec-claim-1 passed [40, 40, 20]
try:
    expect_geo(plate).to_have_circular_hole(diameter=12)
except GeoSpecAssertionError as error:
    print(error.code, error.result["status"], error)  # GEOSPEC_FEATURE_MISMATCH failed No circular hole matches diameter 12 within 0.02 mm.
expect_geo(gear).not_.to_have_circular_hole(diameter=4)  # GeoSpecAssertionError: refused, GEOSPEC_UNSUPPORTED_EVIDENCE, under either polarity

evaluate_geo(subject, capability, *arguments, claim_id=None, polarity="positive") runs one matcher by registry name and returns the report whatever the status, with passed False for a refusal; a bad payload raises geospec_engine_native.ProtocolError, unwrapped.

Query geometry

The five queries, analyzeMesh, analyzeBrep, inspectGeometry, analyzeMeshOverlap and queryPmi, report what the engine measured, take positive polarity only, and refuse when the evidence they read is missing or cannot decide; a query never fails. Query geometry reads the reports.

createGeoSpecAssertionClient

A client runs chains and queries outside a test runner, on subjects from createGeoSpecNativeModelLoader({ engine, readSource }) in geospec/runner/native: its loadModel returns { subjectHash, load }, releaseAll() releases them, and the root expectGeo rejects them with TypeError GEOSPEC_SUBJECT_INVALID.

createGeoSpecAssertionClient(options: GeoSpecAssertionClientOptions): GeoSpecAssertionClient
  • Parameters: engine; workUnitLimit, every claim's budget unless the chain or the subject sets one; claimId(matcher, sequence); evidenceProfile, complete or bounded; subjectSlot; maximumWorkUnitLimit.
  • Returns: expectGeo(subject, options?), whose matchers return the GeoSpecCanonicalClaimReport directly, and query.
  • Throws: as expectGeo; a subject of another engine, or one released by releaseAll(), throws ProtocolError invalid-claim.
import { createGeoSpecAssertionClient } from 'geospec/assertion-client';
import { createGeoSpecNativeModelLoader } from 'geospec/runner/native';

const loadModel = createGeoSpecNativeModelLoader({ engine, readSource });
const { expectGeo, query } = createGeoSpecAssertionClient({ engine });
const plate = await loadModel({ source: 'plate-with-through-bore.step', format: 'step' });
const volume = expectGeo(plate).toHaveVolume({ value: 30429.2 });
console.log(plate.subjectHash.slice(0, 16), volume.claimId, volume.status, volume.claim.workUnitBudget); // da21b0d6142fc5fb geospec-claim-1 passed 8000000

Python needs no client: expect_geo, evaluate_geo and query_geo use the engine that owns the subject.

query

client.query(options: GeoSpecQueryOptions): Promise<GeoSpecCanonicalClaimReport>
client.query(options: MinimumDistanceQuery): Promise<MinimumDistanceResult>
  • Parameters: capability; subject, from the native loader or a bare { subjectHash }; payload, omitted for analyzeMesh and analyzeBrep; claimId, which leaves the shared counter alone.
  • Returns: the report; minimumDistance returns { status: 'complete', fact } or { status, code, message } (unsupported-evidence without AP242 provenance).
  • Throws: ProtocolError invalid-claim for a plan the engine rejects; TypeError for a subject without exactly one of subjectHash and contentHash.
const brep = await query({ capability: 'analyzeBrep', subject: plate });
console.log(brep.claimId, brep.status, Object.keys(brep.evidence).join(',')); // geospec-claim-2 passed brep,diagnostics,success
await loadModel.releaseAll();
query_geo(subject, capability, payload=None, *, claim_id=None) -> GeoSpecAssertionReport
  • Parameters: the subject; a query name; payload, a mapping whose regular-expression selectors are GeoSpecRegex(pattern, flags=""), since a compiled re.Pattern raises TypeError; claim_id.
  • Returns: the report.
  • Raises: ValueError for a capability that is not a query; geospec_engine_native.ProtocolError for an unknown payload field, unwrapped.
brep = query_geo(plate, "analyzeBrep")
print(brep.result["status"], sorted(brep.result["evidence"]))  # passed ['brep', 'diagnostics', 'success']
print(query_geo(gear, "analyzeBrep").diagnostics[0]["code"])  # GEOSPEC_BREP_EVIDENCE_UNAVAILABLE
query_geo(plate, "toHaveVolume")  # ValueError: GeoSpec capability 'toHaveVolume' is not a query.

Run specs

geospec run, the command in geospec, runs every *.geospec.ts or *.geospec.js file under the directory it is given, with no configuration file: it compiles each spec, supplies the engine and owns the subjects, so a spec has no hooks. A spec imports describe, it and expectGeo from geospec and loadModel from geospec/model, which takes no engine and reads a path relative to that directory; it never imports geospec-engine.

plate.geospec.ts
import { describe, expectGeo, it } from 'geospec';
import { loadModel } from 'geospec/model';

describe('plate', () => {
  it('is 40 x 40 x 20 with a 10 mm bore', async () => {
    const plate = await loadModel({ source: 'plate-with-through-bore.step', format: 'step' });
    expectGeo(plate).toHaveBoundingBox({ size: { x: 40, y: 40, z: 20 } });
    expectGeo(plate).toHaveCircularHole({ diameter: 10 });
  });

  it('refuses a hole claim on the gear mesh', async () => {
    const gear = await loadModel({ source: 'involute-gear.glb', sourceUnit: 'mm' });
    expectGeo(gear).toHaveCircularHole({ diameter: 4 });
  });
});
$ npx geospec run .
FAIL plate.geospec.ts
  fail plate > refuses a hole claim on the gear mesh
    GEOSPEC_UNSUPPORTED_EVIDENCE: expectGeo(...).toHaveCircularHole() needs exact BRep circular-hole evidence, which this subject does not carry.
1 passed, 0 failed, 2 selected

The exit code is the verdict: 0 only when every selected test passed; a failed test, a refused claim, a run-level issue or an empty selection exits 1. A refused claim leaves its test inconclusive, counted in neither number on the last line. --json prints one report instead: success, an accounting block with passed, failed, inconclusive, skipped and notRun, and for each test its status, diagnostics and claim reports.

  • --file <path>, --include <glob> and --exclude <glob> select files, -t <regex> selects suite > test names; a filtered run proves the selected scope only.
  • --workers [n] runs a worker pool, sized to the host's CPUs without n.
  • --test-timeout <ms>, --shard-timeout <ms> and --matcher-wall-backstop <ms> are wall-clock watchdogs, off by default; a timeout is no geometry verdict.
  • --bail stops after the first failing file; --forensic appends timing lines.

Under Vitest, geospec/vitest registers the 26 matchers on expect. setupGeoSpecVitest(options) creates an assertion client from the createGeoSpecAssertionClient options and installs its matchers in the current file; installGeoSpecVitest(client) installs an existing client's, and createGeoSpecVitestAdapter(client) returns { matchers, flush } without installing them. A matcher returns a promise, so await it. A failed or refused claim fails the test with the claim's result as its message, and a refusal fails under .not too.

plate.test.ts
import { readFile } from 'node:fs/promises';
import { afterAll, expect, it } from 'vitest';
import { createModelLoader } from 'geospec/model';
import { setupGeoSpecVitest } from 'geospec/vitest';
import { Engine } from 'geospec-engine/node';

const engine = new Engine();
const loadModel = createModelLoader({
  engine,
  readSource: async (source) => new Uint8Array(await readFile(source as string)),
});
setupGeoSpecVitest({ engine });
afterAll(async () => {
  await loadModel.dispose();
  engine.close();
});

it('refuses a hole claim on the gear mesh', async () => {
  const gear = await loadModel({ source: 'involute-gear.glb', sourceUnit: 'mm' });
  await expect(gear).toHaveCircularHole({ diameter: 4 });
});

npx vitest run reports 1 failed; the message under the test is the result JSON, GEOSPEC_UNSUPPORTED_EVIDENCE first among its diagnostics.

Any other runner works with an explicit engine: a failed or refused claim throws GeoSpecAssertionError, failing the test with the engine's message; release the subjects in an after hook. node --test plate.test.mjs reports fail 1.

plate.test.mjs
import { readFile } from 'node:fs/promises';
import { after, test } from 'node:test';
import { expectGeo } from 'geospec';
import { createModelLoader } from 'geospec/model';
import { Engine } from 'geospec-engine/node';

const engine = new Engine();
const loadModel = createModelLoader({ engine, readSource: async (path) => new Uint8Array(await readFile(path)) });
after(async () => {
  await loadModel.dispose();
  engine.close();
});

test('a hole claim on the gear mesh is refused', async () => {
  const gear = await loadModel({ source: 'involute-gear.glb', sourceUnit: 'mm' });
  expectGeo(gear).toHaveCircularHole({ diameter: 4 });
});

The wheel's pytest plugin gives each test an engine, created on its first load_model and closed at teardown, with model_root at pytest's rootpath; GeoSpecAssertionError extends AssertionError, so a failed or refused claim fails the test.

test_plate.py
import pytest
from geospec import GeoSpecAssertionError, expect_geo, load_model


def test_plate():
    plate = load_model(file="plate-with-through-bore.step", format="step")
    expect_geo(plate).to_have_bounding_box(size={"x": 40, "y": 40, "z": 20})
    with pytest.raises(GeoSpecAssertionError) as raised:
        expect_geo(plate).to_have_circular_hole(diameter=12)
    assert raised.value.code == "GEOSPEC_FEATURE_MISMATCH"
$ python -m pytest test_plate.py -q
.                                                                        [100%]
1 passed in 0.02s

Override the geospec_engine_factory fixture in conftest.py to set every test's model_root, work_unit_budget or limits.

Engines

initialize

Load and compile the WASM module once, before constructing an Engine from geospec-engine; the node entry needs no initialization.

initialize(input?: WasmInput, execution?: WasmExecution): Promise<void>
  • Parameters: input, the module as bytes, a Response, a URL or a string, or omitted for the packaged module; execution, single-threaded by default.
  • Returns: a promise resolved once the module is ready.
  • Throws: ProtocolError invalid-request for module bytes with no initializer.
import { Engine, initialize } from 'geospec-engine';

new Engine(); // ProtocolError invalid-request: Call initialize() before using the GeoSpec WASM engine.
await initialize();
const engine = new Engine();

The wheel needs no initialization: GeoSpecEngine sends the initialize request itself and exposes its answer as protocol_version, registry_version, canonical_profile, capabilities and configuration, which carries defaultWorkUnitBudget and the engine's effective limits.

Engine

One engine owns its admitted subjects. The native addon, the WASM module and the wheel answer a claim with the same bytes: the plate's bounding-box result above is 532 canonical bytes, SHA-256 57e6453a170bada5…, from all three.

  • Parameters: new Engine(cacheOptions?, executionPermits?, limits?) from geospec-engine/node takes an optional filesystem cache, { root, projectRoot }, a caller-inclusive CPU allocation, default one, and the host limits; new Engine(execution?, limits?) from geospec-engine takes the product initialize loaded and the same limits.
  • Limits: maxSubjectBytes (64 MiB), maxResourceBytes (64 MiB) and maxTotalBinaryBytes (128 MiB) bound the bytes one load admits; maxVertices (2,000,000), maxTriangles (4,000,000) and maxOccurrences (65,536) its decoded mesh and occurrence list; maxRetainedSubjects (32) the subjects one engine holds; maxMeshBytes (256 MiB) and maxMeshEntries (256) the bytes and the entries one analysis retains or uses as scratch. maxSolidEntries (256) bounds the CSG source solids the engine keeps for reuse across claims; an operand past it is admitted for its claim alone, never refused. The values in parentheses are the entry profile, geospec-entry-config-v1, which an omitted limit keeps; an engine with other values reports geospec-host-config-v1-<id>.
  • Returns: the engine the loaders and clients take as engine.
  • Throws: ProtocolError invalid-request for a limit that is not a positive integer up to 4,294,967,295, a maxSubjectBytes or maxResourceBytes above maxTotalBinaryBytes, or a name the engine does not know; after close(), every use throws the same code, GeoSpec Node engine is closed. or GeoSpec WASM engine is closed.

A load over an admission limit throws ProtocolError limit-exceeded with capacity, the numbers a refused claim carries in details. geospec@0.1.0-beta.6 reads the default budget from the entry profile only, so a chain, model or client on an engine with its own limits sets workUnitLimit itself.

import { readFile } from 'node:fs/promises';
import { createModelLoader } from 'geospec/model';
import { Engine, ProtocolError } from 'geospec-engine/node';

const engine = new Engine(undefined, undefined, { maxTriangles: 600 });
const loadModel = createModelLoader({
  engine,
  readSource: async (source) => new Uint8Array(await readFile(source as string)),
});
try {
  await loadModel({ source: 'involute-gear.glb', sourceUnit: 'mm' }); // 624 triangles
} catch (error) {
  if (!(error instanceof ProtocolError) || !error.capacity) throw error;
  console.log(error.code, error.capacity.code, error.capacity.limit, error.capacity.required);
  console.log(error.message);
}
engine.close();
limit-exceeded GEOSPEC_RESOURCE_LIMIT 600 624
The decoded mesh has 624 triangles; the configured triangle limit is 600 triangles.

GeoSpecEngine(limits={...}) takes the ten limits in snake_case, max_subject_bytes to max_retained_subjects. An unknown name or a value that is not a positive integer raises ValueError; max_subject_bytes or max_resource_bytes above max_total_binary_bytes is a ProtocolError invalid-request. engine.configuration reports the effective values under binaryAdmissionLimits, analysisRetentionLimits and maxRetainedSubjects, and the profile ids as configurationProfile and binaryAdmissionLimits["profileId"]. A load over an admission limit raises ProtocolError limit-exceeded out of load_model, with capacity_code and capacity_details.

from geospec import GeoSpecEngine, load_model
from geospec_engine_native import ProtocolError

with GeoSpecEngine(model_root=".", limits={"max_triangles": 600}) as engine:
    print(engine.configuration["configurationProfile"])
    try:
        load_model(file="involute-gear.glb", source_unit="mm")  # 624 triangles
    except ProtocolError as error:
        print(error.code, error.capacity_code, error.capacity_details)
        print(error)
geospec-host-config-v1-1649e816d1e3c9a3
limit-exceeded GEOSPEC_RESOURCE_LIMIT {'limitClass': 'admission', 'unit': 'triangles', 'limit': 600, 'required': 624}
The decoded mesh has 624 triangles; the configured triangle limit is 600 triangles.

GeoSpecEngine also admits bytes directly:

  • ingest_subject(primary, *, format, coordinate_system, source_unit, output_unit, ingest_options=None, resources=(), slot="subject") admits STEP, glTF or GLB bytes in a z-up or y-up frame, with (name, bytes) resources and output_unit mm.
  • ingest_mesh(mesh, *, slot="subject") admits mesh-buffer-v1 bytes, named by contentHash.
from pathlib import Path

with GeoSpecEngine(model_root=".") as engine:
    part = engine.ingest_subject(
        Path("plate-with-through-bore.step").read_bytes(),
        format="step", coordinate_system="z-up", source_unit="auto", output_unit="mm",
        ingest_options={"name": "part"}, slot="part",
    )
    print(part.slot, part.identity_field, part.identity[:16])  # part subjectHash a53548710335079b

Options

The type tables are from geospec@0.1.0-beta.6, the release that geospec@beta installs; every TypeScript example on this page ran on it. In Python, load_model takes GeoSpecLoadModelOptions, the snake_case spellings of LoadModelSourceOptions plus file and code.

LoadModelSourceOptions

Prop

Type

CreateModelLoaderOptions

Prop

Type

GeoSpecExpectOptions

Prop

Type

GeoSpecAssertionClientOptions

Prop

Type

GeoSpecQueryOptions

Prop

Type

Results

GeoSpecAssertion

A root expectGeo matcher returns a GeoSpecAssertion: the claim, the engine's report and the load it ran on.

Prop

Type

GeoSpecCanonicalClaimReport

The report is the record both languages keep: the canonical claim, plan and result bytes, the claim with its defaults filled in, and the result with status, diagnostics and evidence. Reproduce a result hashes canonicalResult to compare runs.

Prop

Type

In Python the same record is the frozen GeoSpecAssertionReport, with canonical_*_bytes, claim, result, diagnostics, passed and provenance.

GeometryDiagnostic

A failed claim carries the matcher's mismatch code, such as GEOSPEC_FEATURE_MISMATCH; a refused claim carries the reason the common refusals list, such as GEOSPEC_UNSUPPORTED_EVIDENCE, MATCHER_TIMEOUT with the budget and the units used in details, or GEOSPEC_ANALYSIS_LIMIT with the limit and what the analysis required.

Prop

Type

Errors

GeoSpecAssertionError

Thrown by every matcher for a failed or refused claim, from geospec/assertion-client, with report, claim, result, diagnostics and the three canonical byte fields. It has no code: read report.status for the outcome and diagnostics[0].code for the reason.

Raised by every matcher for a failed or refused claim, and by expect_geo and load_model for a request the engine or host rejects. It extends AssertionError and adds code (the first diagnostic's, or the protocol code), report, result, claim, diagnostics, the canonical_*_bytes and protocol_error, the wrapped native exception.

GeoSpecModelLoadError

Thrown by loadModel, from geospec/model, with diagnostics as GeometryDiagnostic rows; the first one's code names the cause.

Python has no separate load error: load_model raises GeoSpecAssertionError for a request it rejects, and lets the engine's ProtocolError through for a model over an admission limit.

ProtocolError

Thrown by the engine for a request it does not accept, with code and message; a refused claim is a result, not an error. geospec-engine exports the class and the TypeScript assertions let it through; Python's geospec_engine_native.ProtocolError escapes load_model, evaluate_geo and query_geo, and expect_geo wraps it in GeoSpecAssertionError with the same code.

  • invalid-claim: a negated query, an out-of-range budget, or a subject the engine never admitted. A malformed payload refuses its own claim with GEOSPEC_INVALID_EXPECTATION instead.
  • invalid-request: a closed engine, a WASM engine used before initialize(), or a limit the constructor rejects.
  • limit-exceeded: a load over an admission limit, maxTriangles or maxRetainedSubjects among them. capacity (Python capacity_code and capacity_details) carries the refusal code, GEOSPEC_RESOURCE_LIMIT or GEOSPEC_ANALYSIS_LIMIT, with limitClass, unit, limit and required.

Constants

  • geoSpecMatcherNames, from geospec: the 26 matcher names in registry order, toHaveBoundingBox first and toSatisfyParallelPlaneDistance last.
  • geoSpecMaximumWorkUnitLimit, from geospec/assertion-client: 512,000,000, the largest workUnitLimit unless a host raises maximumWorkUnitLimit.
  • Defaults: 8,000,000 work units (configuration["defaultWorkUnitBudget"] in Python), a tolerance of 0.02 mm and claim ids geospec-claim-N.
  • Protocol version 3, registry version 5, canonical profile geospec-jcs-v1 and 32 capabilities: the 26 matchers, the five queries and minimumDistance.
  • Profiles: geospec-entry-config-v1 and geospec-entry-binary-v1 name the entry limits; an engine constructed with other limits reports geospec-host-config-v1-<id> and geospec-host-binary-v1-<id>, ids digested from the values.

On this page