• #295 — ask a Graph which engine executed it.

    Parameters

    • graph: Graph

      Any Graph, wasm-constructed or not, live or disposed.

    Returns CauslEngineIdentity

    The frozen CauslEngineIdentity for that graph.

    TOTAL OVER Graph, AND ONLY OVER Graph. Every Graph has an answer, including one this module never touched; there is no undefined return to branch on and no "ask again later" state. Anything that is not a Graph is REFUSED with CauslNotAGraphError rather than answered, because totality over Graph silently becoming totality over unknown is how a forgotten await gets attributed to the TS floor.

    The totality rests on an ordering fact rather than on a default: constructWasmGraphSync is the only codepath in this package that produces a Graph whose commits execute in Rust, and it stamps the identity before it returns. It is the single construct shared by createCauslWasm, createCauslWasmSync and the registry FLIP a preloaded createCausl() routes through. So a graph carrying no stamp provably did not come from the Rust engine, and reporting the TS floor for it is a conclusion, not a guess.

    That claim is load-bearing, so the two routes that look like counterexamples are discharged by name here, and pinned by test in test/graph-engine-identity-295.test.ts so the argument is executed rather than merely asserted:

    • createCausl({ backend: 'auto' }) — public (../src/types.ts:762). Pre-#122 this dispatched into ../src/auto-adapt-wrapper.ts, whose performSwap replaces inner with a different Graph mid-life; a graph that became something else after construction would defeat a stamp applied at construction. #122 retired that dispatch — ../src/graph.ts:1109 now only warns and falls through to the ordinary FLIP — and createAutoAdaptGraph has no callers left in the package. 'auto' therefore reaches the Rust engine only via constructWasmGraphSync, and is stamped like any other FLIP graph.
    • loadWasmBackend().__graph(): this bullet described the Phase-1 WasmBackend, which published a __graph() accessor onto a wrapped pure-TypeScript engine, so an adopter could get a Graph out of the loader without passing through constructWasmGraphSync and causlEngineOf correctly reported 'typescript' for it. #279 slice S12a deleted the wrap and the accessor with it, so the route no longer exists. It is recorded because the 'typescript' value it justified is retained on the union.

    GRAPH-SCOPED. Contrast isCauslWasmPreloaded, which reads a process-wide slot: after one preloadCauslWasm() that predicate answers true for every graph in the process, including the TS-floor graphs the pre-0.5.0 engine / fallbackToTs opts returned. This function distinguishes them.

    NARROW, DON'T ASSERT. The return is a discriminated union (CauslEngineIdentity); if (id.engine === 'rust-wasm') gives you a non-optional bridge, and if (id.mode === 'ts-floor') gives you a tsFloorReason. Neither needs a !.

    STABLE. The returned record is frozen, and repeated calls for one graph return the identical object, so it is safe to use as a memoisation key (Object.is-comparable) alongside stats().nodeVersion.

    NOT A LIVENESS CHECK. This reports what DID execute the graph, and keeps reporting it after disposal. __wasmBackendForTests answers the other question — whether a backend is still LIVE for the graph — and goes to undefined at teardown. The two diverging after dispose() is the intended behaviour of both, not a disagreement between them.

    ONE CAVEAT, stated because it is the only way this can mislead: the stamp lives in a module-scoped WeakMap, so it is per COPY of this module. A host that loads @causl/causl-wasm-ts/wasm twice — a duplicated node_modules entry, or the ESM and CJS builds side by side — will have one copy report the TS floor for the other copy's graphs. That hazard is not new here; the preload cache, the registry FLIP slot and the #111 teardown map all share it, and a single resolved copy is a precondition for the package working at all.

    Importing this subpath solely to call this function does NOT change which engine createCausl() selects. The import registers the sync constructor into the main-bundle registry slot, but that slot is only consulted when a module is preloaded for the default bridge (isPreloadedForDefaultBridge: () => isCauslWasmPreloaded() at the registerWasmSyncEngine call below), and the import performs no preload. It does cost the wasm chunk.

    CauslNotAGraphError when graph is not a Graph.

    import { causlEngineOf } from '@causl/causl-wasm-ts/wasm'

    // Narrowing, not asserting: `bridge` is a BridgeId inside this branch.
    const id = causlEngineOf(graph)
    if (id.engine !== 'rust-wasm') {
    throw new Error(`refusing to publish a benchmark run from ${id.mode}`)
    }
    report.attribution = { engine: id.engine, bridge: id.bridge }
    const id = causlEngineOf(graph)
    if (id.mode === 'ts-floor' &&
    id.tsFloorReason === 'wasm-fallback') {
    // Someone asked for wasm on this graph and did not get it.
    log.warn('running on the TS floor after a wasm soft degrade')
    }

    #296 — the build identifier (WHICH build of that engine), which lands as a tail field on the record this returns.