You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
engine: freshly imported/created period-named variables may fail to simulate at HEAD (XMILE-name + JSON ident boundaries skip name-context canonicalization, unlike the #690 serde fix) #702
The fix for #690 adds a protobuf-deserialize ident migration so a 2022-era stored blob carrying a raw-. canonical ident (e.g. goal_1.5_for_temperature) is repaired at load time. During design work for that fix, a code-trace of HEAD surfaced a broader, apparently pre-existing problem that #690's protobuf-only fix deliberately does not cover: HEAD's other ident-entry boundaries appear to mishandle freshly created / imported period-named variables today, independent of any 2022 blob.
This is code-traced, not yet empirically reproduced. It needs a reproduction before it is treated as confirmed (see the recipe below). It is filed honestly as a suspected defect with a concrete trace.
The conceptual root cause
canonicalize (src/simlin-engine/src/common.rs) applies equation-reference semantics to name/ident-field contexts. In an equation, an unquoted. means a module path, so common.rs:690 rewrites it to · (U+00B7 MIDDLE DOT, the module separator); a . inside a quoted identifier is a literal, so common.rs:681 rewrites it to ‥ (U+2024 ONE DOT LEADER, the LITERAL_PERIOD_SENTINEL, the #559 path). But a variable's name attribute / ident field is a context where a . can only ever be literal -- there is no module-path meaning. Feeding an unquoted display-name ident through the equation-context canonicalize therefore maps its literal . to the · module separator, and downstream module-splitting logic (src/simlin-engine/src/model.rs:396-403) then tears the ident into a phantom submodule path.
Entry path 1: XMILE import (fresh model)
src/simlin-engine/src/xmile/variables.rs:671 (Aux) and :289 (Stock) store ident: aux.name.clone() / ident: stock.name.clone() -- the raw unquoted display name from the XMILE name attribute (e.g. Goal 1.5 for Temperature).
At the salsa boundary (src/simlin-engine/src/db/sync.rs, ~:288 / ~:616) this stored ident is canonicalized. Because it is unquoted, common.rs:690 maps the . to ·, so the variable's canonical key becomes the phantom-module form goal_1·5_for_temperature.
goal_1·5_for_temperature (U+00B7, variable's own key) ≠ goal_1‥5_for_temperature (U+2024, referrer's resolved target) ⇒ DoesNotExist, and the variable's own ·-bearing ident triggers the module split at model.rs:396-403 (phantom goal_1 / 5_for_temperature submodule lookup, which misses).
XMILE import: construct an XMILE model with a quoted-period-named aux (name attribute Goal 1.5 for Temperature) and a second variable whose equation references it via the quoted form "Goal 1.5 for Temperature". open_xmile it, run compile_project_incremental, and check for DoesNotExist on the referrer plus a module-split on the period-named variable. Add a no-period control with the same structure that should compile and run.
Editor / JSON: create (or rename a variable to) a period-named variable in the live editor (or drive the equivalent JSON datamodel through json.rs), add a referrer, and check the same simulate-time failure.
Per the TDD / 95%-coverage discipline, land the failing fixtures first.
Suggested direction (do NOT implement without design discussion)
Apply name-context canonicalization -- treat a . in an ident/name field as a literal (the U+2024 LITERAL_PERIOD_SENTINEL), never as the · module separator -- at all ident-entry boundaries, consistent with the protobuf-deserialize migration being added for #690:
The XMILE reader (xmile/variables.rs) should canonicalize the name attribute in name-context (literal-period sentinel) rather than storing the raw display name to be re-canonicalized in equation-context.
The JSON reader (json.rs) should do the same for incoming idents.
Surfaced during design work for the #690 fix (a protobuf-deserialize ident migration in src/simlin-engine/src/serde.rs). A codebase investigation of HEAD code-traced (but did not empirically reproduce) these two additional ident-entry boundaries that #690's protobuf-only fix does not cover. File:line anchors validated against HEAD: xmile/variables.rs:671/:289 (ident: name.clone()), common.rs:681 (U+2024 quoted) / :690 (U+00B7 unquoted), model.rs:396-403 (phantom submodule split). The not-yet-reproduced caveat is intentional; the first task is to confirm via the recipe above.
Problem (needs empirical reproduction)
The fix for #690 adds a protobuf-deserialize ident migration so a 2022-era stored blob carrying a raw-
.canonical ident (e.g.goal_1.5_for_temperature) is repaired at load time. During design work for that fix, a code-trace of HEAD surfaced a broader, apparently pre-existing problem that #690's protobuf-only fix deliberately does not cover: HEAD's other ident-entry boundaries appear to mishandle freshly created / imported period-named variables today, independent of any 2022 blob.This is code-traced, not yet empirically reproduced. It needs a reproduction before it is treated as confirmed (see the recipe below). It is filed honestly as a suspected defect with a concrete trace.
The conceptual root cause
canonicalize(src/simlin-engine/src/common.rs) applies equation-reference semantics to name/ident-field contexts. In an equation, an unquoted.means a module path, socommon.rs:690rewrites it to·(U+00B7 MIDDLE DOT, the module separator); a.inside a quoted identifier is a literal, socommon.rs:681rewrites it to‥(U+2024 ONE DOT LEADER, theLITERAL_PERIOD_SENTINEL, the #559 path). But a variable's name attribute / ident field is a context where a.can only ever be literal -- there is no module-path meaning. Feeding an unquoted display-name ident through the equation-contextcanonicalizetherefore maps its literal.to the·module separator, and downstream module-splitting logic (src/simlin-engine/src/model.rs:396-403) then tears the ident into a phantom submodule path.Entry path 1: XMILE import (fresh model)
src/simlin-engine/src/xmile/variables.rs:671(Aux) and:289(Stock) storeident: aux.name.clone()/ident: stock.name.clone()-- the raw unquoted display name from the XMILEnameattribute (e.g.Goal 1.5 for Temperature).src/simlin-engine/src/db/sync.rs, ~:288/ ~:616) this stored ident is canonicalized. Because it is unquoted,common.rs:690maps the.to·, so the variable's canonical key becomes the phantom-module formgoal_1·5_for_temperature."Goal 1.5 for Temperature", which canonicalizes via the quoted branch (common.rs:681, the engine: C-LEARN full simulation blocked by non-macro issues (circular dep, dimension mismatches, unknown dep) -- the macro epic's deferred 'tracked separately' follow-up #559 sentinel path) togoal_1‥5_for_temperature(U+2024).goal_1·5_for_temperature(U+00B7, variable's own key) ≠goal_1‥5_for_temperature(U+2024, referrer's resolved target) ⇒DoesNotExist, and the variable's own·-bearing ident triggers the module split atmodel.rs:396-403(phantomgoal_1/5_for_temperaturesubmodule lookup, which misses).Entry path 2: TS editor (create / rename in the live app)
canonicalizein@simlin/core(see ts: two divergent canonicalize implementations (@simlin/core incomplete vs @simlin/engine Rust-faithful) #624 -- two divergent TS canonicalize implementations) reportedly produces raw-dot canonical idents (e.g.goal_1.5_for_temperature) for period-named variables.src/simlin-engine/src/json.rs(e.g.:791,ident: stock.name) carries it through unchanged..→·re-canonicalization at the salsa boundary, reproducing the path-1 mismatch.Mismatch mechanism (the load-bearing detail)
Three visually-near-identical characters are in play (cf. #565):
·.(common.rs:690) -- the path the name attribute / JSON ident wrongly takes‥.(common.rs:681, #559) -- the path a quoted equation referrer takes.@simlin/coreident (#624) and 2022 stored blobs (#690)The variable's own key lands on U+00B7 (and gets module-split), while a quoted referrer resolves to U+2024 -- so they never match.
Why it matters
Goal 1.5 for Temperature,Fig. 3) silently fails to simulate. Like engine: 2022-era models with quoted literal-period idents silently fail to simulate (no load-time canonical-data migration) #690, the model opens, renders, and looks fine -- the failure is silent and only appears at simulate time asDoesNotExist/NotSimulatable.Components affected
src/simlin-engine/src/xmile/variables.rs:671(Aux) and:289(Stock) -- stores the raw unquoted display name as the ident.src/simlin-engine/src/json.rs(~:791) -- carries the TS-produced raw-dot ident through the JSON datamodel path.src/simlin-engine/src/db/sync.rs(~:288, ~:616) -- the salsa-boundary canonicalize that re-maps the unquoted.to·.src/simlin-engine/src/common.rs:681/:690-- the quoted (‥, U+2024) vs unquoted (·, U+00B7).mapping; the source of the context-blindness.src/simlin-engine/src/model.rs:396-403-- the·-split that turns the corrupted ident into a phantom submodule path.@simlin/coresrc/core/canonicalize.ts-- emits raw-dot idents (the path-2 trigger; tracked structurally by ts: two divergent canonicalize implementations (@simlin/core incomplete vs @simlin/engine Rust-faithful) #624).Reproduction recipe (do this first to confirm)
Goal 1.5 for Temperature) and a second variable whose equation references it via the quoted form"Goal 1.5 for Temperature".open_xmileit, runcompile_project_incremental, and check forDoesNotExiston the referrer plus a module-split on the period-named variable. Add a no-period control with the same structure that should compile and run.json.rs), add a referrer, and check the same simulate-time failure.Per the TDD / 95%-coverage discipline, land the failing fixtures first.
Suggested direction (do NOT implement without design discussion)
Apply name-context canonicalization -- treat a
.in an ident/name field as a literal (the U+2024LITERAL_PERIOD_SENTINEL), never as the·module separator -- at all ident-entry boundaries, consistent with the protobuf-deserialize migration being added for #690:xmile/variables.rs) should canonicalize thenameattribute in name-context (literal-period sentinel) rather than storing the raw display name to be re-canonicalized in equation-context.json.rs) should do the same for incoming idents.canonicalizelikely needs context awareness (a name-context vs equation-context mode), so a single function does not apply module-path semantics to a field where a.can only be literal. The engine: 2022-era models with quoted literal-period idents silently fail to simulate (no load-time canonical-data migration) #690 serde migration, this XMILE-name boundary, and the JSON boundary should all share that name-context treatment so the three entry points agree.Relationship to existing tracked items (all DISTINCT)
.stored canonical idents) and its proposed fixes (load-time ident migration recognizing a raw., one-shot blob migration, production scan) target the deserialize/sync path only -- they would not touch the XMILEname-attribute path or the JSON ident path, which carry an unquoted display name with a literal., not a stored raw-.canonical ident. engine: 2022-era models with quoted literal-period idents silently fail to simulate (no load-time canonical-data migration) #690's fix does not cover this..but the canonical separator is U+00B7): a dead/incorrect code branch at specificmodel.rslines, distinct from this ident-entry-boundary canonicalization mismatch across the xmile/json readers (the relevant split here is the·-split atmodel.rs:396-403).Context
Surfaced during design work for the #690 fix (a protobuf-deserialize ident migration in
src/simlin-engine/src/serde.rs). A codebase investigation of HEAD code-traced (but did not empirically reproduce) these two additional ident-entry boundaries that #690's protobuf-only fix does not cover. File:line anchors validated against HEAD:xmile/variables.rs:671/:289(ident: name.clone()),common.rs:681(U+2024 quoted) /:690(U+00B7 unquoted),model.rs:396-403(phantom submodule split). The not-yet-reproduced caveat is intentional; the first task is to confirm via the recipe above.