Skip to content

ltm: PREVIOUS-captured array-valued non-reducing builtin (RANK) scalarizes into ill-typed stubbed helpers, corrupting link scores #742

Description

@bpowers

Summary

When LTM scores a link whose target equation contains an array-valued, NON-reducing builtin subtree (RANK demonstrated; VECTOR SORT ORDER / VECTOR ELM MAP / ALLOCATE are plausibly the same class), the ceteris-paribus partial wraps that subtree atomically in PREVIOUS(...), builtins_visitor::make_temp_arg captures the complex PREVIOUS argument into per-element scalar helper auxes, the helper equation is ill-typed in scalar context, the helper fragments fail to compile and are silently stubbed to constant 0, and the consuming link score is corrupted -- with no diagnostic anywhere.

Demonstrated repro

With arrayed pop[region] and a scored feedback-loop edge scale -> grow:

grow[region] = scale[region] * RANK(pop, 1)
  • The ceteris-paribus partial for the scale -> grow link embeds PREVIOUS(rank(pop, 1)) (the RANK subtree contains no live scale reference, so wrap_non_matching_in_previous wraps it atomically).
  • builtins_visitor::make_temp_arg captures the complex PREVIOUS arg into per-element scalar helper auxes (...⁚arg0⁚north / ...⁚arg0⁚south) whose equation is rank(pop, 1).
  • That equation is ill-typed in a scalar helper: RANK is array-valued and non-reducing, Pass 1 of the builtins visitor never decomposes RANK args, and the dimension-bounds machinery cannot rescue it (verified identical behavior at multiple commits).
  • The helper fragments fail to compile; assemble_module silently drops them (the silent drop itself is ltm: failed implicit-helper fragment compile emits no diagnostic (model_ltm_fragment_diagnostics skips model_ltm_implicit_var_info) #741); the helpers read constant 0 at runtime.
  • The scale -> grow link score reads a corrupted -1000 instead of the true value. No Warning fires.

Root cause

Two interacting pieces:

  1. src/simlin-engine/src/ltm_augment.rs -- is_array_reducer_name (~line 299) is a thin reader of ltm_agg::reducer_kind_from_name, which includes RANK. The wrap_non_matching_in_previous call site (~lines 639-652) uses it to decide "this subtree reduces to a scalar, so wrapping it atomically in PREVIOUS(...) is safe." That is true for SUM/MEAN/STDDEV/SIZE/MIN/MAX, but RANK does not reduce -- RANK(arr, n) is array-valued -- so the resulting PREVIOUS(<array-valued expr>) has no scalar meaning.
  2. src/simlin-engine/src/builtins_visitor.rs -- make_temp_arg (~line 694) synthesizes the captured PREVIOUS argument as scalar helper auxes. For an array-valued non-reducing builtin subtree there is no per-element scalar projection it can emit (unlike the bare-arrayed-name case engine: bare arrayed name inside nested PREVIOUS() in an apply-to-all equation fails to compile #541 fixed via arrayed helper synthesis), so the helper lands ill-typed.

The failure then disappears into the model_ltm_implicit_var_info silent-stub path tracked separately as #741.

Relationship to existing issues

Why it matters

Silent wrong link scores (and therefore loop scores / dominance attribution) for any model where an array-valued non-reducing builtin appears inside a scored equation on a loop edge. Severity low-medium: the shape is uncommon (no current corpus model hits it), but when hit the corruption is invisible to the user.

Components affected

  • src/simlin-engine/src/ltm_augment.rs -- is_array_reducer_name (~299) and its wrap_non_matching_in_previous call site (~639-652): the atomic-PREVIOUS wrap is only sound for genuinely reducing builtins
  • src/simlin-engine/src/builtins_visitor.rs -- make_temp_arg (~694): scalar capture of array-shaped PREVIOUS args
  • src/simlin-engine/src/db/ltm/compile.rs -- the fragment compile path where the helpers fail

Possible approaches

Discovery context

Identified during adversarial review on branch ltm-core-batch; pre-existing on main, NOT a regression of that branch's work (verified byte-identical at bd4376e and 9187da4). Part of LTM tracking epic #488.

Activity

  1. added
    ltmLoops that Matter (LTM) analysis subsystem
    on Jun 10, 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

    ltmLoops that Matter (LTM) analysis subsystem

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions