Skip to content

engine: LTM-off Sim.getRun() now returns Run.links=[] instead of populated-scoreless (AC1.3 deviation) #626

@bpowers

Description

@bpowers

Summary

Phase 2 Task 5 of the @simlin/engine wasm backend work gated Sim.getRun()'s link-fetching on ltmEnabled && _engine !== 'wasm' (src/engine/src/sim.ts, ~line 234), replacing what had been an unconditional getLinks() call. As a result, a default (LTM-off) VM Sim.getRun() now returns Run.links = [], whereas before this change it returned populated-but-scoreless links.

This is a deviation from acceptance-criterion AC1.3's literal "default unchanged" wording for the Run.links field. It is presently consumer-invisible and matches the Phase 2 plan's prescribed code and design intent (LTM-off runs carry empty links). This issue exists to document the contract (LTM-off Run.links is [] by design) so a future consumer relying on populated default-path links is not surprised -- or to restore populated-scoreless links for the LTM-off VM path if that contract is preferred.

Why the plan's premise was off

The Phase 2 plan's Background assumed "LTM-off VM runs already carry links: []." That is not how the underlying FFI behaves: simlin_analyze_get_links (src/libsimlin/src/analysis.rs:227-258) returns causal links from model_causal_edges regardless of LTM -- enable_ltm only gates the link score field. So:

  • Before this change: a default (LTM-off) VM Sim.getRun() returned populated links (the causal edges) with no/zero scores.
  • After this change: it returns [].

The change is correct relative to the Phase 2 plan's prescribed code and its design intent (LTM-off runs should carry empty links), but it does change the observable default-path Run.links value, which is what AC1.3 nominally pinned as "unchanged."

Why the practical impact is currently zero (verified)

A full repo consumer audit (src/diagram, src/app, src/core, src/server, src/engine) confirmed that no consumer currently reads a Run's .links for an LTM-off run. The only Model.run caller outside the engine package -- src/diagram/Editor.tsx:487/:493 -- reads only varNames and getSeries, never .links. So the change is invisible to every present consumer and does not regress any current behavior.

Why it matters

It is a latent contract change. The guarantee a future consumer might reasonably assume ("a default run's .links carries the model's causal edges, just without scores") no longer holds: a default run's .links is now empty. With no documented contract, a future feature that wants the causal edge list from a cheap LTM-off run would silently get [] and might conclude the model has no causal structure.

Component(s) affected

  • src/engine/src/sim.ts (~line 234, the ltmEnabled && _engine !== 'wasm' gate on getLinks())
  • src/libsimlin/src/analysis.rs:227-258 (simlin_analyze_get_links -- returns causal edges regardless of LTM; enable_ltm gates only the score)
  • Consumers of Run.links (audited: only src/diagram/Editor.tsx calls Model.run, and it does not read .links)

Possible approaches for resolution

Pick one:

  1. Document the contract. State (in the @simlin/engine CLAUDE.md and/or a doc comment on Sim.getRun() / the Run.links field) that an LTM-off Run.links is [] by design -- causal links and their scores are produced only when LTM is enabled -- so a future consumer does not assume populated default-path links.
  2. Restore populated-scoreless links for the LTM-off VM path if the "default .links carries causal edges without scores" contract is the one we want to keep. Note simlin_analyze_get_links already returns these regardless of LTM, so this is a matter of relaxing the ltmEnabled gate for the VM path (the _engine !== 'wasm' portion would still need its own resolution -- see below).

Note the gate has two conditions; the _engine !== 'wasm' half (wasm runs never fetch links) is a separate question about whether the wasm backend will ever surface links and is out of scope for this item, which is specifically about the LTM-off VM default-path .links value vs AC1.3.

How it was discovered

Identified during the Phase 2 code review of the @simlin/engine wasm simulation backend integration (docs/implementation-plans/2026-05-22-engine-wasm-sim/, branch engine-wasm-sim). The reviewer traced the actual FFI behavior in analysis.rs, confirmed the plan's "LTM-off runs already carry links: []" Background premise was incorrect, performed the cross-package consumer audit confirming no current consumer reads LTM-off .links, and concluded the change matches the plan's prescribed code and design intent but deviates from AC1.3's literal "default unchanged" wording. Filed as the tracking item for that contract deviation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions