Skip to content

engine: assembly-stage CompilationDiagnostics are silently discarded (IN_TRACKED_CONTEXT never set true) #581

Description

@bpowers

Problem

In src/simlin-engine/src/db.rs, the thread-local flag IN_TRACKED_CONTEXT (declared around db.rs:104) is only ever read, never set to true. The guarded wrapper try_accumulate_diagnostic (db.rs:115) short-circuits to a permanent no-op whenever the flag is false:

fn try_accumulate_diagnostic(db: &dyn Db, diag: Diagnostic) {
    let in_context = IN_TRACKED_CONTEXT.with(|flag| flag.get());
    if in_context {
        CompilationDiagnostic(diag).accumulate(db);
    }
    // Outside a tracked context the diagnostic is silently discarded.
}

The wrapper exists because a bare .accumulate(db) panics with "cannot accumulate values outside of an active tracked function" when called outside a #[salsa::tracked] frame (and catch_unwind is ineffective under WASM's panic = "abort"). But the entire assemble_* chain runs outside any tracked frame:

compile_project_incremental -> assemble_simulation -> assemble_module
  -> compile_implicit_var_fragment / lower_implicit_var

So try_accumulate_diagnostic is a no-op for every call site on that chain. The doc comment at db.rs:111-114 already states this plainly: "Scaffolding: the flag is never set to true today, so all calls are no-ops -- assembly errors are instead returned via Result::Err..."

Consequence

Structured per-variable CompilationDiagnostics produced during assembly never reach collect_all_diagnostics, so MCP / FFI / simlin_project_get_errors consumers never see them as structured per-variable diagnostics. Two concrete instances on the dead path today:

  1. The pre-existing assemble_module aggregate-Err diagnostic (db.rs:5067, the "failed to compile fragments for variables: ..." path).
  2. The per-helper DimensionInScalarContext diagnostic in lower_implicit_var (db.rs:4013-4026), added during element-cycle-resolution Phase 6 Task 4 (commit b25dc06d, branch clearn-hero-model) precisely to make a lowering-stage residual legible per-helper rather than only in the opaque aggregate missing_vars string.

Both are only legible today via the compile_project_incremental Err return string, not as structured diagnostics. The real-variable path emits a structured diagnostic via accumulate_var_compile_error (which runs inside a tracked frame); the assembly path cannot reach that surface.

Why it matters

Observability / diagnostic-surfacing gap. The error is not lost — the Err is returned and the helper/variable names appear in its message, so this is not a silent miscompile. But an AI agent or CLI caller reaching for the structured per-variable error list (the canonical "what's wrong with my model?" surface) gets nothing for assembly-stage failures; they only get a single opaque aggregate string at the top level. Degrades the developer/agent debugging experience for exactly the failure modes (DimensionInScalarContext residuals, fragment-compile failures) most likely to need per-variable attribution.

Components affected

  • src/simlin-engine/src/db.rs: IN_TRACKED_CONTEXT (~db.rs:104), try_accumulate_diagnostic (~db.rs:115), assemble_module (~db.rs:5067), lower_implicit_var (~db.rs:4013-4026).

Possible approaches

Either:

  1. Wire IN_TRACKED_CONTEXT to true around the assemble_* chain — but with care: the chain is not itself a #[salsa::tracked] query, so this needs thought about salsa accumulator semantics (which tracked frame would the accumulated values attach to, and would collect_all_diagnostics actually re-run that query and observe them?). A flag set true without a live tracked frame underneath would just re-introduce the panic the wrapper was built to dodge.
  2. Relocate the per-variable diagnostic emission into the tracked compile_var_fragment-equivalent path that does run inside a tracked frame, so the structured diagnostic is produced where accumulate is legal and collect_all_diagnostics can see it.

Approach 2 is likely the cleaner fix: it keeps the no-op scaffolding honest (delete IN_TRACKED_CONTEXT and the wrapper once nothing depends on it) and emits diagnostics only where salsa supports it.

Context

Identified during element-cycle-resolution Phase 6 Task 4 (commit b25dc06d, branch clearn-hero-model). This is not a blocker for the element-cycle-resolution work, which uses the compile_project_incremental Err return directly.

Same area and same investigation as #580 (the assembly-stage fragment-compile failure itself) but a distinct, pre-existing issue: #580 is the bug; this is the structured-diagnostic-surfacing gap around it. Conceptually adjacent to #466 (LTM auto-flip warning unreachable through simlin_project_get_errors) — both are "diagnostic invisible to get_errors" — but a different mechanism (#466 is an ltm_enabled=false FFI-layer reset; this is the IN_TRACKED_CONTEXT-never-true engine scaffolding no-op).

Activity

  1. added
    bugSomething isn't working
    engineIssues with the rust-based simulation engine
    on May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingengineIssues with the rust-based simulation engine

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions