@earthsciml/ast 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@
4
4
  * and run json-schema-to-typescript to regenerate this file.
5
5
  */
6
6
  /**
7
- * EarthSciML Serialization Format (v1.0.0) — a language-agnostic JSON format for Earth system model components, their composition, and runtime configuration. v1.0.0 is a clean break with no deprecation path and no compatibility shims (docs/content/rfcs/unified-variable-model.md): the five declared variable types collapse to TWO. `unknown` is a quantity the solver solves for — its behavior is given by EQUATIONS and never by a field on the variable — and `parameter` is a quantity supplied to the solver, carrying a number or a `distribution` plus an optional `update` block saying when and from what it refreshes. Removed: `state` and `observed` (with the `expression` field — an observed variable becomes an unknown plus a bare-variable-LHS equation); `brownian` (with `noise_kind` / `correlation_group` — a noise source is a parameter with a `distribution` and `update.kind: "wiener"`, correlated noise being ONE vector-valued parameter whose distribution carries a `cov` matrix, which closes the gap the 0.x schema explicitly deferred); `discrete` (with `refresh` and the `RefreshTrigger` def — a refreshing input is a parameter with a `schedule` / `data` / `remesh` update); the `discrete_parameters` event lists and the event `functional_affect` (parameter mutation moves onto the parameter itself as a `condition` / `crossing` update carrying an `expression`, a `from` binding, or a `handler`; events now affect UNKNOWNS only); and the `data_loaders` COMPONENT kind together with `DataLoader.variables` (top-level `data_sources` is now a pure registry of ingest configuration — source, temporal, select, record_filter, extent, reader_options, determinism — that parameters draw from via `update.from`; a data source is no longer a coupling endpoint, a subsystem, or a scoped-name path root). Everything the solver needs is DERIVED rather than declared — which unknowns are ODE states, observed, or algebraic, and which parameters are Brownian, discrete, sampled, or constant — and every binding exposes the same classification functions to derive it (esm-spec §6.3.1). v0.9.0 changes no on-disk shape: it replaces the Option A (always-expanded) template round-trip with Option B (reference-preserving; esm-spec §9.6.4, docs/content/rfcs/out-of-line-expression-templates.md). Template references survive load and parse-then-emit; emit materializes each component's referenced templates into its `expression_templates` registry (authored entries first in authored order, materialized entries after in lexicographic UTF-8 name order; registry keys may be dotted post-rename names); eager references (target-bearing, esm-spec §9.6.4 rule 3) still expand at load, so no rewrite-target op survives inside a reference; `emit ∘ load` is a byte-wise fixed point; emitted documents carrying surviving references or materialized registries declare `esm: 0.9.0` or later. v0.8.0 is a clean break that removes all bespoke spatial-grid machinery in favor of expressing grid geometry directly with the unified Functional Aggregate Query (`aggregate`) IR (RFC semiring-faq-unified-ir), and retains no backward-compatibility shims. Removed: the top-level `grids`, `staggering_rules`, and `discretizations` blocks; the `Grid` / `GridExtent` / `GridMetricArray` / `GridMetricGenerator` / `GridConnectivity` / `GridCRS` defs; the entire stencil-template discretization rule grammar (`Discretization`, `DiscretizationVariant`, `MultiOutputStencilRule`, `DiscretizationRef`, `GridDiscretizationDescriptor`, and the `Rule` / `PatternNode` / `NeighborSelector` / `StencilEntry` / `RuleBinding` / `BoundaryPolicy` / `BoundaryPolicySpec` / `GhostWidth` / `GhostVarDecl` / `RuleRegion` / `RuleGuard` machinery, including `Equation.region`); all regridding configuration (`Model.regrid`, `RegridSpec`, `Interface.regridding`); the `Domain.spatial` / `SpatialDimension` / `CoordinateTransform` geometry block and the deprecated domain-level `boundary_conditions`; the `DataLoader.grid` / `DataLoader.mesh` descriptors and the `DataLoaderMesh` def; and the `grid_discretization_descriptor` document kind with its `grid_refs` test/example fields. Grid geometry — coordinates, extents, spacing, CRS parameters, connectivity, and metric arrays — is now ordinary data: loaded from a `data_loaders` primitive or declared as variables/parameters, with topology and metrics constructed declaratively as `aggregate` FAQs (the `intersect_polygon` kernel leaf remains for polygon clipping). Iteration domains are declared once in the document-scoped `index_sets` registry. Regridding is expressed as an ordinary coupling expression between two variables. Earlier additive features are retained: the `integral` AST op (PIDEs), array-or-single `plots.y`, sampled `function_tables` + `table_lookup`, in-file `expression_templates` + `apply_expression_template`, and the closed `enums` + `fn` / `enum` ops. The template-library RFC (docs/content/rfcs/template-library-imports.md; esm-spec §9.7) adds cross-file template sharing at esm 0.8.0: template-library files (top-level `expression_templates`), ordered `expression_template_imports` with §4.7 reference semantics, and load-time integer `metaparameters` admissible in `index_sets` sizes, `aggregate` dense ranges, and `makearray` regions — all resolved and folded at load, before validation and before the §9.6.3 rewrite fixpoint.
7
+ * EarthSciML Abstract Syntax Tree Format — v1.2.0 adds an OPTIONAL declared `units` string on a `const` expression node (esm-spec §4.8.5): the unit the constant's value is in, so a conversion constant such as `{"op": "const", "args": [], "value": 0.44704, "units": "m*h/(mi*s)"}` makes its equation dimensionally checkable. Purely additive: `units` is legal on no other node, and a document declaring esm below 1.2.0 that carries it is rejected with `const_units_version_too_old`. v1.1.0 renames the unified query node from `aggregate` to `faq`, the Functional Aggregate Query it has always been (docs/content/rfcs/faq-node-rename.md). `aggregate` names only one of the node's specializations: it also joins, filters, and — under `bool_and_or` with `distinct` — produces an index set rather than an array, reducing nothing. `faq` is the canonical tag; `aggregate` is accepted as a DEPRECATED ALIAS, normalized to `faq` at load with the warning `deprecated_op_alias`, and REMOVED at esm 2.0.0. The text surface is unchanged: the head keyword there is the ⊕-word (`sum`/`prod`/`max`/`min`/`any`), not the node name. The `arrayop` alias — nominally removed at 0.8.0 but still live in all five bindings — is removed for real. Also at v1.1.0: the top-level `solver` block. (v1.0.0) — a `faq`'s `join` clause carries the optional `syms`, naming the two RANGE SYMBOLS its `on` pairs are read at, so a relation can be joined to ITSELF: two ranges over one index set, whose key columns' shared axis cannot say which side is which (CONFORMANCE_SPEC §5.5.8, docs/content/rfcs/self-join-two-ranges-over-one-index-set.md). Purely additive — every document written before it stays valid and unchanged, which is why it lands without a version bump; see that note's §4. v1.0.0 — a language-agnostic JSON format for Earth system model components, their composition, and runtime configuration. v1.0.0 is a clean break with no deprecation path and no compatibility shims (docs/content/rfcs/unified-variable-model.md): the five declared variable types collapse to TWO. `unknown` is a quantity the solver solves for — its behavior is given by EQUATIONS and never by a field on the variable — and `parameter` is a quantity supplied to the solver, carrying a number or a `distribution` plus an optional `update` block saying when and from what it refreshes. Removed: `state` and `observed` (with the `expression` field — an observed variable becomes an unknown plus a bare-variable-LHS equation); `brownian` (with `noise_kind` / `correlation_group` — a noise source is a parameter with a `distribution` and `update.kind: "wiener"`, correlated noise being ONE vector-valued parameter whose distribution carries a `cov` matrix, which closes the gap the 0.x schema explicitly deferred); `discrete` (with `refresh` and the `RefreshTrigger` def — a refreshing input is a parameter with a `schedule` / `data` / `remesh` update); the `discrete_parameters` event lists and the event `functional_affect` (parameter mutation moves onto the parameter itself as a `condition` / `crossing` update carrying an `expression`, a `from` binding, or a `handler`; events now affect UNKNOWNS only); and the `data_loaders` COMPONENT kind together with `DataLoader.variables` (top-level `data_sources` is now a pure registry of ingest configuration — source, temporal, select, record_filter, extent, reader_options, determinism — that parameters draw from via `update.from`; a data source is no longer a coupling endpoint, a subsystem, or a scoped-name path root). Everything the solver needs is DERIVED rather than declared — which unknowns are ODE states, observed, or algebraic, and which parameters are Brownian, discrete, sampled, or constant — and every binding exposes the same classification functions to derive it (esm-spec §6.3.1). v0.9.0 changes no on-disk shape: it replaces the Option A (always-expanded) template round-trip with Option B (reference-preserving; esm-spec §9.6.4, docs/content/rfcs/out-of-line-expression-templates.md). Template references survive load and parse-then-emit; emit materializes each component's referenced templates into its `expression_templates` registry (authored entries first in authored order, materialized entries after in lexicographic UTF-8 name order; registry keys may be dotted post-rename names); eager references (target-bearing, esm-spec §9.6.4 rule 3) still expand at load, so no rewrite-target op survives inside a reference; `emit ∘ load` is a byte-wise fixed point; emitted documents carrying surviving references or materialized registries declare `esm: 0.9.0` or later. v0.8.0 is a clean break that removes all bespoke spatial-grid machinery in favor of expressing grid geometry directly with the unified Functional Aggregate Query (`faq`) IR (RFC semiring-faq-unified-ir), and retains no backward-compatibility shims. Removed: the top-level `grids`, `staggering_rules`, and `discretizations` blocks; the `Grid` / `GridExtent` / `GridMetricArray` / `GridMetricGenerator` / `GridConnectivity` / `GridCRS` defs; the entire stencil-template discretization rule grammar (`Discretization`, `DiscretizationVariant`, `MultiOutputStencilRule`, `DiscretizationRef`, `GridDiscretizationDescriptor`, and the `Rule` / `PatternNode` / `NeighborSelector` / `StencilEntry` / `RuleBinding` / `BoundaryPolicy` / `BoundaryPolicySpec` / `GhostWidth` / `GhostVarDecl` / `RuleRegion` / `RuleGuard` machinery, including `Equation.region`); all regridding configuration (`Model.regrid`, `RegridSpec`, `Interface.regridding`); the `Domain.spatial` / `SpatialDimension` / `CoordinateTransform` geometry block and the deprecated domain-level `boundary_conditions`; the `DataLoader.grid` / `DataLoader.mesh` descriptors and the `DataLoaderMesh` def; and the `grid_discretization_descriptor` document kind with its `grid_refs` test/example fields. Grid geometry — coordinates, extents, spacing, CRS parameters, connectivity, and metric arrays — is now ordinary data: loaded from a `data_loaders` primitive or declared as variables/parameters, with topology and metrics constructed declaratively as `faq` nodes (the `intersect_polygon` kernel leaf remains for polygon clipping). Iteration domains are declared once in the document-scoped `index_sets` registry. Regridding is expressed as an ordinary coupling expression between two variables. Earlier additive features are retained: the `integral` AST op (PIDEs), array-or-single `plots.y`, sampled `function_tables` + `table_lookup`, in-file `expression_templates` + `apply_expression_template`, and the closed `enums` + `fn` / `enum` ops. The template-library RFC (docs/content/rfcs/template-library-imports.md; esm-spec §9.7) adds cross-file template sharing at esm 0.8.0: template-library files (top-level `expression_templates`), ordered `expression_template_imports` with §4.7 reference semantics, and load-time integer `metaparameters` admissible in `index_sets` sizes, `faq` dense ranges, and `makearray` regions — all resolved and folded at load, before validation and before the §9.6.3 rewrite fixpoint.
8
8
  */
9
9
  type ESMFormat = ESMFormat1 & ESMFormat2;
10
10
  type ESMFormat1 = {
@@ -20,13 +20,17 @@ type ModelVariable$1 = ModelVariable1 & {
20
20
  type: "unknown" | "parameter";
21
21
  units?: string;
22
22
  /**
23
- * For an unknown, its initial value at t=0. For a parameter, its constant value — mutually exclusive with `distribution`, which draws the value instead.
23
+ * For an unknown, its initial value at t=0. For a parameter, its constant value — mutually exclusive with `distribution`, which draws the value instead. A SHAPED variable (non-empty `shape`) may instead carry a row-major nested JSON array whose nesting matches the declared shape after metaparameter folding; a mismatch is a load-time error. A scalar on a shaped variable keeps its broadcast meaning — the one value applies to every element (esm-spec §6.3).
24
24
  */
25
- default?: number;
25
+ default?: number | NumericArrayLiteral;
26
26
  /**
27
27
  * Units of the default value, if different from the declared units field. When present, validators flag a unit_inconsistency error if these do not match the declared units (including dimensionally incompatible cases like K vs kg, and same-dimension mismatches like K vs degC). Default is the same as `units`.
28
28
  */
29
29
  default_units?: string;
30
+ /**
31
+ * Floating-point precision of THIS variable and of the arithmetic over it, overriding the document-wide `domain.element_type` (esm-spec §11.3.1). It exists for the key/quantity split one document-wide precision cannot express: a relational model whose floating-point QUANTITIES are binary32 still has join keys and integer identifiers binary32 cannot represent (a ten-digit category code is ~2.26e9, 135x binary32's exact-integer limit of 2^24), so the keys declare `Float64` while the quantities follow the document. Precision propagates from the leaves: an operator evaluates at its operands' precision, a numeric literal adopts its context's, and an operator whose operands carry DIFFERENT precisions is an error naming both variables — never a silent widening. Omitted means the document's `domain.element_type`.
32
+ */
33
+ element_type?: "Float32" | "Float64";
30
34
  description?: string;
31
35
  /**
32
36
  * Arrayed-variable shape: ordered list of index-set names drawn from the document-scoped `index_sets` registry. Omitted or null indicates a scalar. REQUIRED for a parameter whose `update` is `schedule`, `data`, or `remesh` — such a parameter is a buffer whose extent is fixed at setup and refilled on a discrete cadence, so its extent must be known ahead of time (RFC semiring-faq-unified-ir §6.1); an empty array is a valid (scalar) shape.
@@ -64,6 +68,10 @@ type ModelVariable$1 = ModelVariable1 & {
64
68
  type ModelVariable1 = {
65
69
  [k: string]: unknown;
66
70
  };
71
+ /**
72
+ * A row-major nested JSON array of numbers carrying a SHAPED variable's inline data (esm-spec §6.3, §6.6.2). Every axis is a JSON array and every leaf a number; the nesting depth and the per-axis lengths MUST match the variable's declared `shape` after metaparameter folding, and a mismatch is a load-time error — the same convention `from_file` data already follows (§6.6.5 convention 3). A SCALAR in the same position keeps its broadcast meaning (one value applied to every element).
73
+ */
74
+ type NumericArrayLiteral = (number | NumericArrayLiteral)[];
67
75
  /**
68
76
  * Declares WHEN a parameter refreshes and WHAT it refreshes from. A parameter with no `update` is a constant (or, with a `distribution`, is sampled once at setup). Six kinds. `wiener` is a driving stochastic process and takes no value form — it resamples the parameter's own `distribution`. The other five each take EXACTLY ONE value form: an `expression`, a `from` binding to a data source, or a registered `handler`. Together they subsume three constructs the 0.x format kept apart: the `brownian` variable type (now `wiener`), the `discrete` type with its `RefreshTrigger` (now `schedule` / `data` / `remesh`), and the `discrete_parameters` event lists with their `functional_affect` (now `condition` / `crossing`). This is also the sole seed of the DISCRETE cadence class in the dependency-partition pass (RFC semiring-faq-unified-ir §6.1): without it such inputs would be mis-seeded as CONST (never refresh) or CONTINUOUS (recompute every step).
69
77
  */
@@ -81,11 +89,11 @@ type Expression = number | string | ExpressionNode;
81
89
  */
82
90
  type ExpressionNode = ExpressionNode1 & {
83
91
  /**
84
- * Operator name. TWO TIERS (esm-spec §4.2). (1) The CLOSED evaluable-core set — every binding's evaluator implements each one directly: `+ - * / ^`, the comparisons/booleans (`> < >= <= == != and or not`), the elementary functions (`exp log log10 sqrt abs sign sin cos tan asin acos atan atan2 sinh cosh tanh asinh acosh atanh min max floor ceil`), `ifelse`, the calculus form-ops `D` and `ic` (structural / equation-LHS use only), `Pre`, `true`, and the array/query ops (`aggregate makearray index broadcast reshape transpose concat skolem rank argmin argmax intersect_polygon polygon_intersection_area fn enum const table_lookup apply_expression_template`). (2) The OPEN rewrite-target tier — ANY other identifier: a spatial `D` on a right-hand side, the optional sugar ops `grad`/`div`/`laplacian`/`integral`, or a user op such as `godunov_hamiltonian`. A rewrite-target op carries NO evaluator implementation and MUST be eliminated by a rewrite rule (§9.6) before evaluation. Loading is permissive — a file MAY load with rewrite-target ops still present (e.g. a coupling/scoping example that never simulates) — but one reaching evaluation/compilation without being lowered is rejected with `unlowered_operator`. The `pattern` only rejects malformed strings — it does NOT enforce membership in the core set, so the open tier stays expressible. The evaluable-core set is the ONLY set the format privileges.
92
+ * Operator name. TWO TIERS (esm-spec §4.2). (1) The CLOSED evaluable-core set — every binding's evaluator implements each one directly: `+ - * / ^` and `pow` (the word spelling of `^`), the comparisons/booleans (`> < >= <= == != and or not`), the elementary functions (`exp log log10 sqrt abs sign sin cos tan asin acos atan atan2 sinh cosh tanh asinh acosh atanh min max floor ceil`), `ifelse`, the calculus form-ops `D` and `ic` (structural / equation-LHS use only), `Pre`, the boolean literals `true` and `false`, and the array/query ops (`faq makearray index broadcast reshape transpose concat skolem rank argmin argmax intersect_polygon polygon_intersection_area fn enum const table_lookup apply_expression_template`). (2) The OPEN rewrite-target tier — ANY other identifier: a spatial `D` on a right-hand side, the optional sugar ops `grad`/`div`/`laplacian`/`integral`, or a user op such as `godunov_hamiltonian`. A rewrite-target op carries NO evaluator implementation and MUST be eliminated by a rewrite rule (§9.6) before evaluation. Loading is permissive — a file MAY load with rewrite-target ops still present (e.g. a coupling/scoping example that never simulates) — but one reaching evaluation/compilation without being lowered is rejected with `unlowered_operator`. The `pattern` only rejects malformed strings — it does NOT enforce membership in the core set, so the open tier stays expressible. The evaluable-core set is the ONLY set the format privileges. The array/query node `faq` is a FUNCTIONAL AGGREGATE QUERY (docs/content/rfcs/semiring-faq-unified-ir.md): a semiring reduction of a body over named index sets, carrying joins, filters and set semantics, so under `bool_and_or` + `distinct` it produces an INDEX SET rather than an array and reduces nothing. DEPRECATED ALIAS: `aggregate` is the pre-1.1.0 spelling of `faq`. A loader MUST accept it, MUST normalize the node to `faq` before validation so that nothing downstream of the loader sees the alias, and MUST report the warning diagnostic `deprecated_op_alias` once per document per alias. It is REMOVED at esm 2.0.0 (docs/content/rfcs/faq-node-rename.md). The older `arrayop` spelling is NOT accepted. It was removed at 0.8.0 and is rejected BY NAME with the hard error `removed_op` — NOT left to the open tier: `arrayop` is a well-formed identifier, so the open rewrite-target tier would otherwise admit it and the document would load silently, failing much later as `unlowered_operator`, or never. A loader MUST also reject a document that spells `faq` while declaring esm < 1.1.0 (`faq_version_too_old`), the same version gate the top-level `solver` block follows; the gate reads the AUTHORED form, so a pre-1.1.0 document carrying the `aggregate` alias stays legal and is normalized with its declared version raised to the 1.1.0 floor.
85
93
  */
86
94
  op: string;
87
95
  /**
88
- * Stable node identity (RFC semiring-faq-unified-ir §6.1): an optional, author-assigned identifier that makes this expression node addressable as a vertex in the inter-node dependency DAG the partition pass walks. Its primary use is to be the referent of a derived index set: an `index_sets` entry of kind "derived" names, via `from_faq`, the index-set-producing `aggregate` node that materializes it — and it names it by this id. When present it MUST be unique among the expression nodes of its model; the build-time reference-resolution pass errors on a duplicate id and on a `from_faq` that names no node. Absent ⇒ the node is addressed only by its structural path and cannot be the target of a `from_faq`. Purely additive: a file using no `id` validates and resolves exactly as before.
96
+ * Stable node identity (RFC semiring-faq-unified-ir §6.1): an optional, author-assigned identifier that makes this expression node addressable as a vertex in the inter-node dependency DAG the partition pass walks. Its primary use is to be the referent of a derived index set: an `index_sets` entry of kind "derived" names, via `from_faq`, the index-set-producing `faq` node that materializes it — and it names it by this id. When present it MUST be unique among the expression nodes of its DOCUMENT — `index_sets` is a document-scoped registry, so a `from_faq` in it resolves against every model's nodes, and per-model uniqueness would leave a cross-model reference ambiguous. The build-time reference-resolution pass errors on a duplicate id and on a `from_faq` that names no node in the document. Absent ⇒ the node is addressed only by its structural path and cannot be the target of a `from_faq`. Purely additive: a file using no `id` validates and resolves exactly as before.
89
97
  */
90
98
  id?: string;
91
99
  /**
@@ -93,7 +101,7 @@ type ExpressionNode = ExpressionNode1 & {
93
101
  */
94
102
  expect_cadence?: "const" | "discrete" | "continuous";
95
103
  /**
96
- * Operand list. For most ops these are sub-expressions. Array ops use args for the input array operands (aggregate, broadcast, index, reshape, transpose, concat). makearray has no natural args and uses an empty array. For `D` (esm-spec §4.2 "Arity of `D`"): `args[0]` is always the differentiated operand; a STRUCTURAL `D` (`wrt` "t", or `wrt` absent) is strictly unary, while a REWRITE-TARGET `D` (spatial `wrt`) MAY carry an unbounded number of TRAILING AUXILIARY OPERANDS after `args[0]` — the per-face boundary/halo data a discretization rule binds and consumes (§9.6.8). Trailing operands carry NO evaluator semantics and no per-position meaning assigned by this format; they are matched positionally by the consuming rule.
104
+ * Operand list. For most ops these are sub-expressions. Array ops use args for the input array operands (faq, broadcast, index, reshape, transpose, concat). makearray has no natural args and uses an empty array. For `D` (esm-spec §4.2 "Arity of `D`"): `args[0]` is always the differentiated operand; a STRUCTURAL `D` (`wrt` "t", or `wrt` absent) is strictly unary, while a REWRITE-TARGET `D` (spatial `wrt`) MAY carry an unbounded number of TRAILING AUXILIARY OPERANDS after `args[0]` — the per-face boundary/halo data a discretization rule binds and consumes (§9.6.8). Trailing operands carry NO evaluator semantics and no per-position meaning assigned by this format; they are matched positionally by the consuming rule.
97
105
  *
98
106
  * @minItems 0
99
107
  */
@@ -125,7 +133,7 @@ type ExpressionNode = ExpressionNode1 & {
125
133
  */
126
134
  upper?: Expression;
127
135
  /**
128
- * For aggregate: the result's index signature. Each entry is either a string (a symbolic index variable like "i", "j") or the integer 1 (a literal singleton dimension for reshape/broadcast). Mirrors SymbolicUtils.ArrayOp.output_idx.
136
+ * For faq: the result's index signature. Each entry is either a string (a symbolic index variable like "i", "j") or the integer 1 (a literal singleton dimension for reshape/broadcast). Mirrors SymbolicUtils.ArrayOp.output_idx.
129
137
  */
130
138
  output_idx?: (string | 1)[];
131
139
  /**
@@ -133,15 +141,15 @@ type ExpressionNode = ExpressionNode1 & {
133
141
  */
134
142
  expr?: Expression;
135
143
  /**
136
- * For aggregate: the reduction operator applied to any index symbol that appears in expr but not in output_idx. Default is "+". This names only the ⊕ operator; it is a shorthand retained for files that omit "semiring" (⊕ = reduce, ⊗ = "*"). When "semiring" is present it supersedes "reduce".
144
+ * For faq: the reduction operator applied to any index symbol that appears in expr but not in output_idx. Default is "+". This names only the ⊕ operator; it is a shorthand retained for files that omit "semiring" (⊕ = reduce, ⊗ = "*"). When "semiring" is present it supersedes "reduce".
137
145
  */
138
146
  reduce?: "+" | "*" | "max" | "min";
139
147
  /**
140
- * For aggregate: the named semiring (⊕, ⊗) that parameterizes the reduction (RFC semiring-faq-unified-ir §5.1). A closed, exhaustive registry — adding a semiring is a spec change, not a per-file extension. Absent ⇒ "sum_product", which reproduces today's einsum / sum-of-products semantics exactly (the strict-superset promise). The ⊕ operator, the ⊗ operator, and BOTH identity elements (the value of an empty ⊕-reduction and an empty ⊗-product) are fixed by the registry table and MUST NOT be written into the file: sum_product (+,0;×,1), max_product (max,-∞;×,1), min_sum (min,+∞;+,0), max_sum (max,-∞;+,0), bool_and_or (∨,false;∧,true).
148
+ * For faq: the named semiring (⊕, ⊗) that parameterizes the reduction (RFC semiring-faq-unified-ir §5.1). A closed, exhaustive registry — adding a semiring is a spec change, not a per-file extension. Absent ⇒ "sum_product", which reproduces today's einsum / sum-of-products semantics exactly (the strict-superset promise). The ⊕ operator, the ⊗ operator, and BOTH identity elements (the value of an empty ⊕-reduction and an empty ⊗-product) are fixed by the registry table and MUST NOT be written into the file: sum_product (+,0;×,1), max_product (max,-∞;×,1), min_sum (min,+∞;+,0), max_sum (max,-∞;+,0), bool_and_or (∨,false;∧,true).
141
149
  */
142
150
  semiring?: "sum_product" | "max_product" | "min_sum" | "max_sum" | "bool_and_or";
143
151
  /**
144
- * For aggregate: optional map from index symbol name to the range it iterates over. Each value is EITHER (a) a dense integer tuple — a 2-element array [start, stop] (unit step) or a 3-element array [start, step, stop], as today, mirroring SymbolicUtils.ArrayOp.ranges — entries MAY be metaparameter expressions folded to concrete integers at load (esm-spec §9.7.6); OR (b) a reference to a declared index set (RFC semiring-faq-unified-ir §5.2): an object { "from": <index_sets key> }, optionally with "of": [parent index names] for a ragged / dependent inner set (e.g. the edges of cell "i"). Indices not present are inferred from the domain / operand shapes at runtime.
152
+ * For faq: optional map from index symbol name to the range it iterates over. Each value is EITHER (a) a dense integer tuple — a 2-element array [start, stop] (unit step) or a 3-element array [start, step, stop], as today, mirroring SymbolicUtils.ArrayOp.ranges — entries MAY be metaparameter expressions folded to concrete integers at load (esm-spec §9.7.6); OR (b) a reference to a declared index set (RFC semiring-faq-unified-ir §5.2): an object { "from": <index_sets key> }, optionally with "of": [parent index names] for a ragged / dependent inner set (e.g. the edges of cell "i"). Indices not present are inferred from the domain / operand shapes at runtime.
145
153
  */
146
154
  ranges?: {
147
155
  [k: string]: [MetaparameterExpression, MetaparameterExpression] | [MetaparameterExpression, MetaparameterExpression, MetaparameterExpression] | {
@@ -150,13 +158,13 @@ type ExpressionNode = ExpressionNode1 & {
150
158
  */
151
159
  from: string;
152
160
  /**
153
- * Parent index name(s) for a ragged / dependent inner set: the members enumerated depend on these outer indices (e.g. { "from": "edges_of_cell", "of": ["i"] } iterates the edges of cell i).
161
+ * Parent index name(s) for a ragged / dependent inner set: the enumeration depends on these outer indices (e.g. { "from": "edges_of_cell", "of": ["i"] } iterates the POSITIONS k in 1…offsets[i] of cell i's edges, and the body gathers each edge as index(values, i, k); esm-spec §4.3.1 "Ragged ranges").
154
162
  */
155
163
  of?: string[];
156
164
  };
157
165
  };
158
166
  /**
159
- * For aggregate: optional value-equality combination of factors by key columns — an inner equi-join (RFC semiring-faq-unified-ir §5.3), subsuming ESI join and making connectivity gathers first-class. Each entry is a join clause whose "on" lists one or more key-column pairs [left, right]; a ⊗-product term is contributed only for index combinations whose key columns are equal on EVERY listed pair, and an unmatched row contributes the additive identity (the empty ⊕-reduction value), adding nothing under any semiring. Inner-only — there is no outer/left variant; many-to-many is defined, not an error (m·n product terms, each a ⊗-term into the enclosing ⊕-reduction); key columns must be exact-equality types (integer IDs or categorical members compared by Unicode code point) — floating-point join keys are forbidden. Absent ⇒ factors combine only by shared index name (positional einsum), exactly as today.
167
+ * For faq: optional value-equality combination of factors by key columns — an inner equi-join (RFC semiring-faq-unified-ir §5.3), subsuming ESI join and making connectivity gathers first-class. Each entry is a join clause whose "on" lists one or more key-column pairs [left, right]; a ⊗-product term is contributed only for index combinations whose key columns are equal on EVERY listed pair, and an unmatched row contributes the additive identity (the empty ⊕-reduction value), adding nothing under any semiring. Inner-only — there is no outer/left variant; many-to-many is defined, not an error (m·n product terms, each a ⊗-term into the enclosing ⊕-reduction); key columns must be exact-equality types (integer IDs or categorical members compared by Unicode code point) — floating-point join keys are forbidden. Absent ⇒ factors combine only by shared index name (positional einsum), exactly as today. A key column may name a loop symbol, an index set, or a genuine 1-D DATA COLUMN, and the resolved match set DRIVES enumeration rather than merely testing it (CONFORMANCE_SPEC §5.5.8).
160
168
  */
161
169
  join?: ({
162
170
  [k: string]: unknown;
@@ -168,7 +176,7 @@ type ExpressionNode = ExpressionNode1 & {
168
176
  */
169
177
  filter?: Expression;
170
178
  /**
171
- * For aggregate: when true, the node has set semantics (dedup) and is index-set-producing — it materializes a data-derived index set rather than an array (RFC semiring-faq-unified-ir §5.5). Only meaningful under the `bool_and_or` semiring (the relational specialization, §5.1); combined with `key`, it enumerates the unique Skolem terms (e.g. the unique edges discovered from a face→vertex relation) and exposes the result as a `kind:"derived"` index set (§5.2) that a downstream geometric aggregate consumes. The output order is fixed by the normative determinism rules (§5.7): sort by the total tuple order, then drop adjacent duplicates — never first-seen / insertion order. Absent ⇒ false (ordinary array-producing reduction), exactly as today.
179
+ * For faq: when true, the node has set semantics (dedup) and is index-set-producing — it materializes a data-derived index set rather than an array (RFC semiring-faq-unified-ir §5.5). Only meaningful under the `bool_and_or` semiring (the relational specialization, §5.1); combined with `key`, it enumerates the unique Skolem terms (e.g. the unique edges discovered from a face→vertex relation) and exposes the result as a `kind:"derived"` index set (§5.2) that a downstream geometric `faq` consumes. The output order is fixed by the normative determinism rules (§5.7): sort by the total tuple order, then drop adjacent duplicates — never first-seen / insertion order. Absent ⇒ false (ordinary array-producing reduction), exactly as today.
172
180
  */
173
181
  distinct?: boolean;
174
182
  /**
@@ -216,7 +224,7 @@ type ExpressionNode = ExpressionNode1 & {
216
224
  */
217
225
  axis?: number;
218
226
  /**
219
- * For broadcast: the name of the scalar operator to apply element-wise to the operands in args (esm-spec.md §4.3.4). `{op: 'broadcast', fn: F, args: A}` means exactly what `{op: F, args: A}` means, applied element-wise — so a ONE-operand broadcast applies `fn` UNARILY (`broadcast(fn:'-',[x])` is `-x`), and MUST NOT be treated as the identity on its operand. The value MUST be an ExpressionNode op name drawn from the closed SCALAR subset — the pointwise ops: arithmetic (`+ - * / ^ neg`), elementary functions (`exp log ln log10 sqrt abs sign floor ceil sin cos tan asin acos atan sinh cosh tanh asinh acosh atanh atan2 min max`), comparisons (`== != < <= > >=`), logical connectives (`and or not`), and `ifelse`. It may NOT name a non-pointwise op (`aggregate makearray index broadcast reshape transpose concat fn D ic Pre const true enum table_lookup apply_expression_template skolem rank distinct argmin argmax intersect_polygon polygon_intersection_area`), and `args.length` MUST satisfy the named operator's §4.2 arity. Bindings MUST reject a violation — including an absent `fn`, for which there is no default — with diagnostic 'invalid_broadcast_fn'; loading MUST fail. This is a VALUE constraint the schema deliberately does not enum-enforce, for the same reason `op` and the closed-registry `name` are not enum-enforced: the admissible set is fixed by the file's declared `esm` version, and the check must also cover arity, which a schema `enum` cannot express.
227
+ * For broadcast: the name of the scalar operator to apply element-wise to the operands in args (esm-spec.md §4.3.4). `{op: 'broadcast', fn: F, args: A}` means exactly what `{op: F, args: A}` means, applied element-wise — so a ONE-operand broadcast applies `fn` UNARILY (`broadcast(fn:'-',[x])` is `-x`), and MUST NOT be treated as the identity on its operand. The value MUST be an ExpressionNode op name drawn from the closed SCALAR subset — the pointwise ops: arithmetic (`+ - * / ^ neg`), elementary functions (`exp log ln log10 sqrt abs sign floor ceil sin cos tan asin acos atan sinh cosh tanh asinh acosh atanh atan2 min max`), comparisons (`== != < <= > >=`), logical connectives (`and or not`), and `ifelse`. It may NOT name a non-pointwise op (`faq makearray index broadcast reshape transpose concat fn D ic Pre const true enum table_lookup apply_expression_template skolem rank distinct argmin argmax intersect_polygon polygon_intersection_area`), and `args.length` MUST satisfy the named operator's §4.2 arity. Bindings MUST reject a violation — including an absent `fn`, for which there is no default — with diagnostic 'invalid_broadcast_fn'; loading MUST fail. This is a VALUE constraint the schema deliberately does not enum-enforce, for the same reason `op` and the closed-registry `name` are not enum-enforced: the admissible set is fixed by the file's declared `esm` version, and the check must also cover arity, which a schema `enum` cannot express.
220
228
  */
221
229
  fn?: string;
222
230
  /**
@@ -229,6 +237,10 @@ type ExpressionNode = ExpressionNode1 & {
229
237
  value?: {
230
238
  [k: string]: unknown;
231
239
  };
240
+ /**
241
+ * For const only: the declared units of the constant's `value`, resolved against the esm-spec §4.8.1 registry with the §4.8.2 grammar exactly as a variable's `units` are. A unit-bearing constant has that dimension and exact scale in dimensional analysis (esm-spec §4.8.5); a `const` without `units`, like a bare numeric literal, stays undeterminable. The numeric value is never checked against the units. Legal on no other op. Arrives at esm 1.2.0 (`const_units_version_too_old`).
242
+ */
243
+ units?: string;
232
244
  /**
233
245
  * For table_lookup: the id of the function_tables entry to evaluate. Bindings MUST reject references to undeclared tables at file-load time with diagnostic 'table_lookup_unknown_table'.
234
246
  */
@@ -254,7 +266,7 @@ type ExpressionNode1 = {
254
266
  [k: string]: unknown;
255
267
  };
256
268
  /**
257
- * A load-time integer expression over metaparameters (esm-spec §9.7.6): an integer literal, a declared metaparameter name, or `{op: +|-|*|/, args: [...]}` over metaparameter expressions (unary `-` allowed). Folded to a concrete integer at load with exact 64-bit arithmetic; `/` MUST divide exactly and overflow is an error (`metaparameter_type_error`). Admissible in structural integer sites (`index_sets` interval `size`, `aggregate` dense `ranges` tuple entries, `makearray` `regions` bound pairs) AND as an import-edge / subsystem-edge binding VALUE, whose free names resolve in the importing document's metaparameter scope.
269
+ * A load-time integer expression over metaparameters (esm-spec §9.7.6): an integer literal, a declared metaparameter name, or `{op: +|-|*|/, args: [...]}` over metaparameter expressions (unary `-` allowed). Folded to a concrete integer at load with exact 64-bit arithmetic; `/` MUST divide exactly and overflow is an error (`metaparameter_type_error`). Admissible in structural integer sites (`index_sets` interval `size`, `faq` dense `ranges` tuple entries, `makearray` `regions` bound pairs) AND as an import-edge / subsystem-edge binding VALUE, whose free names resolve in the importing document's metaparameter scope.
258
270
  */
259
271
  type MetaparameterExpression = number | string | {
260
272
  op: "+" | "-" | "*" | "/";
@@ -289,36 +301,7 @@ type DiscreteEventTrigger = {
289
301
  times: [number, ...number[]];
290
302
  };
291
303
  /**
292
- * One native axis of a `DataLoaderSelect`. The vocabulary is shared with the projection-pushdown gate template (CONFORMANCE_SPEC §5.5): "all" keeps the axis whole; `fixed` takes one index and DROPS the axis; `range` takes a half-open strided window and keeps it; `gated_by` names a `derived` index set whose value-invention members become the axis, which DEFERS the fetch until those members exist.
293
- */
294
- type DataSourceSelectAxis = "all" | {
295
- /**
296
- * A single 0-based native index.
297
- */
298
- fixed: number | [number];
299
- } | {
300
- range: {
301
- /**
302
- * Inclusive first index (default 0). A string names a `metaparameters` entry and resolves to its default.
303
- */
304
- start?: number | string;
305
- /**
306
- * Exclusive last index. A string names a `metaparameters` entry and resolves to its default, so a prefix is declared in the model's own terms (`W[0:N_SRC]`) rather than as a repeated literal that can drift from the index set sized by the same metaparameter.
307
- */
308
- stop: number | string;
309
- /**
310
- * Stride (default 1; MUST be >= 1).
311
- */
312
- step?: number | string;
313
- };
314
- } | {
315
- /**
316
- * Name of a `kind: "derived"` index set. The axis becomes that set's materialised members in canonical (sorted) member order (CONFORMANCE_SPEC §5.5, Hook 2), so only the rows the model can reach are ever read.
317
- */
318
- gated_by: string;
319
- };
320
- /**
321
- * A single scalar check against a model variable at a specific (variable, time) point. PDE-aware variants pin a spatial point via `coords`, or reduce the field to a scalar via `reduce` (domain-integral, mean, max, min, or error-norm). `coords` and `reduce` are mutually exclusive; if neither is given the assertion is pointwise and only valid on a 0-D component. Error-norm reductions (L2_error, Linf_error) require `reference`.
304
+ * A single scalar check against a model variable at a specific (variable, time) point. PDE-aware variants pin a spatial point via `coords`, or reduce the field to a scalar via `reduce` (domain-integral, mean, max, min, or error-norm). `coords` and `reduce` are mutually exclusive; if neither is given the assertion is pointwise and is valid only on a variable declared without a `shape` or on an element name such as `u[1]` (esm-spec §6.6.5). Error-norm reductions (L2_error, Linf_error) require `reference`.
322
305
  */
323
306
  type Assertion = Assertion1 & {
324
307
  /**
@@ -468,6 +451,35 @@ type Parameter = Parameter1 & {
468
451
  type Parameter1 = {
469
452
  [k: string]: unknown;
470
453
  };
454
+ /**
455
+ * One native axis of a `DataLoaderSelect`. The vocabulary is shared with the projection-pushdown gate template (CONFORMANCE_SPEC §5.5): "all" keeps the axis whole; `fixed` takes one index and DROPS the axis; `range` takes a half-open strided window and keeps it; `gated_by` names a `derived` index set whose value-invention members become the axis, which DEFERS the fetch until those members exist.
456
+ */
457
+ type DataSourceSelectAxis = "all" | {
458
+ /**
459
+ * A single 0-based native index.
460
+ */
461
+ fixed: number | [number];
462
+ } | {
463
+ range: {
464
+ /**
465
+ * Inclusive first index (default 0). A string names a `metaparameters` entry and resolves to its closed value (esm-spec §9.7.6).
466
+ */
467
+ start?: number | string;
468
+ /**
469
+ * Exclusive last index. A string names a `metaparameters` entry and resolves to its closed value (esm-spec §9.7.6), so a prefix is declared in the model's own terms (`W[0:N_SRC]`) rather than as a repeated literal that can drift from the index set sized by the same metaparameter.
470
+ */
471
+ stop: number | string;
472
+ /**
473
+ * Stride (default 1; MUST be >= 1).
474
+ */
475
+ step?: number | string;
476
+ };
477
+ } | {
478
+ /**
479
+ * Name of a `kind: "derived"` index set. The axis becomes that set's materialised members in canonical (sorted) member order (CONFORMANCE_SPEC §5.5, Hook 2), so only the rows the model can reach are ever read.
480
+ */
481
+ gated_by: string;
482
+ };
471
483
  /**
472
484
  * A single coupling rule connecting models or reaction systems. A `data_sources` entry is not a component and cannot appear as a coupling endpoint; external data enters through a parameter's `update`.
473
485
  */
@@ -558,11 +570,11 @@ type CouplingEvent = CouplingEvent1 & {
558
570
  times: [number, ...number[]];
559
571
  };
560
572
  /**
561
- * Affect equations. Required unless functional_affect is used.
573
+ * Affect equations. Required: esm 1.0.0 removed the `functional_affect` alternative, so `affects` is the only affect channel. Every LHS MUST name an unknown (`event_affects_parameter` otherwise).
562
574
  */
563
575
  affects: AffectEquation[];
564
576
  affect_neg?: null | AffectEquation[];
565
- root_find?: "left" | "right" | "all";
577
+ root_find?: "left" | "right";
566
578
  reinitialize?: boolean;
567
579
  /**
568
580
  * Map from a target system referenced by this coupling entry to the template-library imports registered into THAT component's template scope (esm-spec §9.7.10) — assembler-chosen discretization for a PDE component as it is wired into the assembly. Each key MUST name a model/reaction-system this entry references (template_inject_target_unknown otherwise); a key resolving to neither a model nor a reaction system is template_inject_target_not_component -- which from 1.0.0 is also how a key naming a `data_sources` entry is reported, since a data source is not a component and the separate template_inject_target_is_loader code is retired. Values use the §9.7.2 entry shape. Load-time only; consumed by the §9.6.3 fixpoint; does not survive parse→emit.
@@ -578,15 +590,15 @@ type CouplingEvent1 = {
578
590
  [k: string]: unknown;
579
591
  };
580
592
  /**
581
- * A named index set (RFC semiring-faq-unified-ir §5.2): the declaration shape for an iteration domain referenced from an `aggregate` range via { "from": <name> }. Covers grid axes and categorical dimensions under one shape. Exactly one of four kinds, each requiring its own fields.
593
+ * A named index set (RFC semiring-faq-unified-ir §5.2): the declaration shape for an iteration domain referenced from a `faq` range via { "from": <name> }. Covers grid axes and categorical dimensions under one shape. Exactly one of four kinds, each requiring its own fields.
582
594
  */
583
595
  type IndexSet = IndexSet1 & {
584
596
  /**
585
- * Which of the four index-set forms this entry is. "interval": a dense [1..size] grid axis. "categorical": an explicit enumeration of members. "derived": a data-derived set materialized from an index-set-producing node (a `distinct` `aggregate`, or an `intersect_polygon` ring leaf whose clipped ring has a data-dependent vertex count, §8.1). "ragged": a per-parent (dependent) inner set backed by CSR offsets/values factors.
597
+ * Which of the four index-set forms this entry is. "interval": a dense [1..size] grid axis. "categorical": an explicit enumeration of members. "derived": a data-derived set materialized from an index-set-producing node (a `distinct` `faq`, or an `intersect_polygon` ring leaf whose clipped ring has a data-dependent vertex count, §8.1). "ragged": a per-parent (dependent) inner set backed by an `offsets` per-parent length factor and a padded `values` member factor; a `faq` range over it binds the POSITION k in 1…offsets[parent] (esm-spec §4.3.1 "Ragged ranges").
586
598
  */
587
599
  kind: "interval" | "categorical" | "derived" | "ragged";
588
600
  /**
589
- * A load-time integer expression over metaparameters (esm-spec §9.7.6): an integer literal, a declared metaparameter name, or `{op: +|-|*|/, args: [...]}` over metaparameter expressions (unary `-` allowed). Folded to a concrete integer at load with exact 64-bit arithmetic; `/` MUST divide exactly and overflow is an error (`metaparameter_type_error`). Admissible in structural integer sites (`index_sets` interval `size`, `aggregate` dense `ranges` tuple entries, `makearray` `regions` bound pairs) AND as an import-edge / subsystem-edge binding VALUE, whose free names resolve in the importing document's metaparameter scope.
601
+ * A load-time integer expression over metaparameters (esm-spec §9.7.6): an integer literal, a declared metaparameter name, or `{op: +|-|*|/, args: [...]}` over metaparameter expressions (unary `-` allowed). Folded to a concrete integer at load with exact 64-bit arithmetic; `/` MUST divide exactly and overflow is an error (`metaparameter_type_error`). Admissible in structural integer sites (`index_sets` interval `size`, `faq` dense `ranges` tuple entries, `makearray` `regions` bound pairs) AND as an import-edge / subsystem-edge binding VALUE, whose free names resolve in the importing document's metaparameter scope.
590
602
  */
591
603
  size?: number | string | {
592
604
  op: "+" | "-" | "*" | "/";
@@ -600,11 +612,11 @@ type IndexSet = IndexSet1 & {
600
612
  */
601
613
  members?: unknown[];
602
614
  /**
603
- * derived: the id of the index-set-producing node (RFC §5.5) that materializes this set, named by its `id`. Usually an `aggregate` node (`distinct: true`) — e.g. the unique edges discovered from a face→vertex relation. It MAY also be an `intersect_polygon` geometry-kernel leaf (RFC §8.1): the clipped overlap ring it returns has a data-dependent number of vertices, so the ring's vertex set is exactly such a derived index set, and `polygon_area` is then an ordinary `sum_product` FAQ over it. Required when kind is "derived".
615
+ * derived: the id of the index-set-producing node (RFC §5.5) that materializes this set, named by its `id`. Usually a `faq` node (`distinct: true`) — e.g. the unique edges discovered from a face→vertex relation. It MAY also be an `intersect_polygon` geometry-kernel leaf (RFC §8.1): the clipped overlap ring it returns has a data-dependent number of vertices, so the ring's vertex set is exactly such a derived index set, and `polygon_area` is then an ordinary `sum_product` FAQ over it. Required when kind is "derived".
604
616
  */
605
617
  from_faq?: string;
606
618
  /**
607
- * derived: name of the buffer that receives the surviving member key per invented position (CONFORMANCE_SPEC §5.5, Hook 1) — fed back as a `const` factor so an aggregate ranging over the compact derived axis can gather the full-grid rows that axis selects. That is what downstream aggregates do; it is not a claim about ORIENTATION — an aggregate that reduces OVER the compact axis, and one whose overlap-gate envelope factors are themselves such gathers, gather exactly the same way (CONFORMANCE_SPEC §5.5.7). OPTIONAL, and only meaningful when kind is "derived" and `from_faq` names an overlap-gated `distinct` aggregate. Pairs with a provider axis marked `gated_by: "<this set>"`, which is DEFERRED past value-invention and then fetched with a Selection built from the materialised members in sorted order (the `gated_select` pushdown).
619
+ * derived: name of the buffer that receives the surviving member key per invented position (CONFORMANCE_SPEC §5.5, Hook 1) — fed back as a `const` factor so a `faq` ranging over the compact derived axis can gather the full-grid rows that axis selects. That is what downstream FAQ nodes do; it is not a claim about ORIENTATION — a `faq` that reduces OVER the compact axis, and one whose overlap-gate envelope factors are themselves such gathers, gather exactly the same way (CONFORMANCE_SPEC §5.5.7). OPTIONAL, and only meaningful when kind is "derived" and `from_faq` names an overlap-gated `distinct` `faq`. Pairs with a provider axis marked `gated_by: "<this set>"`, which is DEFERRED past value-invention and then fetched with a Selection built from the materialised members in sorted order (the `gated_select` pushdown).
608
620
  */
609
621
  member_factor?: string;
610
622
  /**
@@ -612,11 +624,11 @@ type IndexSet = IndexSet1 & {
612
624
  */
613
625
  of?: string[];
614
626
  /**
615
- * ragged: name of the keyed factor giving |set(i)| for each parent tuple — the per-parent length / CSR offsets (e.g. MPAS nEdgesOnCell). Required when kind is "ragged".
627
+ * ragged: name of the keyed factor giving |set(i)| for each parent tuple — the per-parent LENGTH, not a cumulative offset (e.g. MPAS nEdgesOnCell). A `faq` range {"from": <this set>, "of": ["i"]} binds the POSITION k in 1…offsets[i] (esm-spec §4.3.1 "Ragged ranges"). Required when kind is "ragged".
616
628
  */
617
629
  offsets?: string;
618
630
  /**
619
- * ragged: name of the keyed factor giving the member at (i, k) for k in 1…|set(i)| — the flattened CSR member array (e.g. edgesOnCell). Required when kind is "ragged".
631
+ * ragged: name of the keyed factor giving the member at (i, k) for k in 1…|set(i)| — a PADDED [parent, max length] array whose row i holds parent i's members in positions 1…offsets[i] (e.g. MPAS edgesOnCell); entries past offsets[i] are padding and are never read. Because a range over the set binds the position k, a `faq` body reads a member by gathering it explicitly, index(values, i, k); a body that never reads this array is `ragged_values_not_gathered`. The one exception is a value-invention `faq` (distinct / skolem / rank / argmin / argmax), whose ragged range binds the member values[i, k] itself (esm-spec §4.3.1 "Ragged ranges"). Required when kind is "ragged".
620
632
  */
621
633
  values?: string;
622
634
  };
@@ -678,7 +690,7 @@ interface ESMFormat2 {
678
690
  [k: string]: DataSource;
679
691
  };
680
692
  /**
681
- * File-local symbol-to-positive-integer mappings used by the 'enum' AST op to make categorical lookups cross-binding-portable. Each entry is an enum name; its value is an object mapping symbolic names (strings) to positive integers. Two .esm files may declare an enum of the same name with different mappings; enums are file-local and never merged across files. See esm-spec.md §9.3.
693
+ * File-local symbol-to-integer mappings used by the 'enum' AST op to make categorical lookups cross-binding-portable. Each entry is an enum name; its value is an object mapping symbolic names (strings) to integers — any integer, negative, zero or positive, since an enum member is a categorical code rather than a 1-based position. Two .esm files may declare an enum of the same name with different mappings; enums are file-local and never merged across files. See esm-spec.md §9.3.
682
694
  */
683
695
  enums?: {
684
696
  [k: string]: EnumDeclaration;
@@ -695,7 +707,7 @@ interface ESMFormat2 {
695
707
  [k: string]: FunctionTable;
696
708
  };
697
709
  /**
698
- * Document-scoped registry of named index sets (RFC semiring-faq-unified-ir §5.2), keyed by name — the single, document-level declaration site for every iteration domain (grid axes, categorical dimensions, data-derived sets) shared by all models in the document. An `aggregate` range references one by name as { "from": <name> }. Each entry is an interval (dense axis), categorical enumeration, data-derived set, or ragged / dependent inner set. A reference resolves by name to exactly one entry; resolvers MUST error on an undeclared name.
710
+ * Document-scoped registry of named index sets (RFC semiring-faq-unified-ir §5.2), keyed by name — the single, document-level declaration site for every iteration domain (grid axes, categorical dimensions, data-derived sets) shared by all models in the document. A `faq` range references one by name as { "from": <name> }. Each entry is an interval (dense axis), categorical enumeration, data-derived set, or ragged / dependent inner set. A reference resolves by name to exactly one entry; resolvers MUST error on an undeclared name.
699
711
  */
700
712
  index_sets?: {
701
713
  [k: string]: IndexSet;
@@ -706,6 +718,7 @@ interface ESMFormat2 {
706
718
  coordinates?: {
707
719
  [k: string]: Coordinate;
708
720
  };
721
+ solver?: Solver;
709
722
  /**
710
723
  * Top-level rewrite rules / templates — the payload of a template-library file (esm-spec §9.7.1). Only valid in a library file (which carries no models/reaction_systems/data_loaders/coupling/domain, `template_import_not_library` when imported otherwise); component-local templates stay inside their model/reaction_system (§9.6.1). Arrives at esm 0.8.0 (`template_import_version_too_old`).
711
724
  */
@@ -828,7 +841,7 @@ interface Model$1 {
828
841
  * Named child subsystems, keyed by unique identifier. A subsystem is a child model, or a reference to an external file containing exactly one. Enables hierarchical model composition. Variables in subsystems are referenced via dot notation: "ParentModel.ChildModel.var". Each subsystem can be defined inline or included by reference via a local file path or URL. A `data_sources` entry is NOT a component and cannot be a subsystem: a model reaches external data through a parameter whose `update` names the source.
829
842
  */
830
843
  subsystems?: {
831
- [k: string]: Model$1 | DataSource | SubsystemRef;
844
+ [k: string]: Model$1 | SubsystemRef;
832
845
  };
833
846
  tolerance?: Tolerance;
834
847
  /**
@@ -915,136 +928,13 @@ interface ContinuousEvent {
915
928
  /**
916
929
  * Root-finding direction.
917
930
  */
918
- root_find?: "left" | "right" | "all";
931
+ root_find?: "left" | "right";
919
932
  /**
920
933
  * Whether to reinitialize the system after the event.
921
934
  */
922
935
  reinitialize?: boolean;
923
936
  description?: string;
924
937
  }
925
- /**
926
- * A named external data source reduced to pure I/O: it locates, reads, decodes, slices, and filters bytes on disk. It performs no reprojection and no regridding, and — from 1.0.0 — it exposes no variables and is NOT a component. It cannot be a coupling endpoint, a subsystem, or a scoped-name path root; a model consumes it by declaring a parameter whose `update` names this source and binds one of its `file_variable`s. Grid geometry a source reads (coordinates, connectivity, metric arrays) arrives as ordinary parameters and is transformed downstream by `aggregate` FAQs and coupling expressions. Authentication and algorithm-specific tuning are runtime-only and not part of the schema.
927
- */
928
- interface DataSource {
929
- /**
930
- * Structural kind of the dataset: 'grid' (gridded array source), 'points' (scattered point/station source), or 'static' (time-invariant source). Grid geometry the source reads — coordinates, connectivity, metric arrays — arrives as ordinary parameters bound to its `file_variable`s and is consumed by `aggregate` FAQs downstream; it needs no special descriptor. Scientific role (emissions, meteorology, elevation, ...) is not schema-validated and belongs in metadata.tags.
931
- */
932
- kind: "grid" | "points" | "static";
933
- source: DataSourceLocation;
934
- temporal?: DataSourceTemporal;
935
- determinism?: DataSourceDeterminism;
936
- /**
937
- * Format-specific DECODE options, passed through to the format reader verbatim (EarthSciIO calls them `reader_kwargs`): the zip `member_glob` and `skip_header_row` of an FF10 inventory, a GeoTIFF band naming, and so on. They say how bytes become an array, never what the array means — no remap, no unit conversion, no filtering (those are `variables`, `unit_conversion` and `record_filter`). A key the bound reader does not recognise MUST be an error, so a mis-spelled option cannot silently decode something else.
938
- */
939
- reader_options?: {
940
- [k: string]: unknown;
941
- };
942
- select?: DataSourceSelect;
943
- record_filter?: DataSourceRecordFilter;
944
- extent?: DataSourceExtent;
945
- reference?: Reference;
946
- /**
947
- * Free-form metadata about the data source. The "tags" field (array of strings) is conventional for expressing scientific role (e.g. "emissions", "reanalysis") and is not schema-validated.
948
- */
949
- metadata?: {
950
- tags?: string[];
951
- [k: string]: unknown;
952
- };
953
- }
954
- /**
955
- * File discovery configuration. Describes how to locate data files at runtime via URL templates with date/variable substitutions.
956
- */
957
- interface DataSourceLocation {
958
- /**
959
- * Jinja-style URL template with substitutions. Supported: {date:<strftime>} (e.g. {date:%Y%m%d}), {var}, {sector}, {species}. Custom substitutions are allowed and the runtime must accept and pass them through.
960
- */
961
- url_template: string;
962
- /**
963
- * Ordered fallback URL templates. Runtime tries each in order, first is primary. Follows the same substitution grammar as url_template.
964
- */
965
- mirrors?: string[];
966
- }
967
- /**
968
- * Temporal coverage and record layout for a data source.
969
- */
970
- interface DataSourceTemporal {
971
- /**
972
- * ISO 8601 datetime — first timestamp available from this source.
973
- */
974
- start?: string;
975
- /**
976
- * ISO 8601 datetime — last timestamp available from this source.
977
- */
978
- end?: string;
979
- /**
980
- * ISO 8601 duration describing how much time one file covers (e.g., "P1D", "P1M", "PT3H").
981
- */
982
- file_period?: string;
983
- /**
984
- * ISO 8601 duration describing spacing between samples within a file.
985
- */
986
- frequency?: string;
987
- /**
988
- * Number of time records per file. "auto" means read from file at runtime.
989
- */
990
- records_per_file?: number | "auto";
991
- /**
992
- * Name of the time coordinate variable in the file. Used when records_per_file is absent or "auto". If both static declarations (records_per_file + frequency) and time_variable are present, the static declaration wins and time_variable is a fallback.
993
- */
994
- time_variable?: string;
995
- /**
996
- * How many time records the source returns per query time — pure I/O; the source does NOT interpolate. Absent or 1 (default): the single at-or-before record, time axis dropped (piecewise-constant between ticks). 2: the two records bracketing the current time (floor + successor), returned with the time axis kept at length 2 so a downstream model can interpolate in time (e.g. linearly). Only 1 and 2 are supported; higher-order temporal stencils are future work. Contrast records_per_file (records IN one file).
997
- */
998
- records_per_sample?: 1 | 2;
999
- }
1000
- /**
1001
- * Reproducibility contract a binary-format loader advertises to bindings. A binding that cannot honor the declared endian / float_format / integer_width MUST reject the file at load rather than silently reinterpreting bytes.
1002
- */
1003
- interface DataSourceDeterminism {
1004
- /**
1005
- * Byte order of on-wire numeric fields.
1006
- */
1007
- endian?: "little" | "big";
1008
- /**
1009
- * Floating-point format of metric fields.
1010
- */
1011
- float_format?: "ieee754_single" | "ieee754_double";
1012
- /**
1013
- * Integer width (in bits) of connectivity fields.
1014
- */
1015
- integer_width?: 32 | 64;
1016
- }
1017
- /**
1018
- * A per-axis selection of what the source DELIVERS from an on-disk array — one entry per NATIVE array dimension, in native dims order. Declared on a source (the default for every parameter drawing from it) or on a single parameter's `from` binding (which overrides the source's). Two parameters of one source MAY read the same `file_variable` under different selections, which is how a full-grid field and a prefix of it are both declared instead of one being sliced by the caller. The selection is defined over the axis the loader DELIVERS, so it follows any `record_filter`: `range 0..200` on a filtered points table is the first 200 SURVIVING records. Whether a binding pushes it down to the reader (fetching only what it keeps) or applies it after the read is an optimization; the two MUST agree exactly.
1019
- */
1020
- interface DataSourceSelect {
1021
- /**
1022
- * One selector per native array dimension, in native dims order.
1023
- *
1024
- * @minItems 1
1025
- */
1026
- axes: [DataSourceSelectAxis, ...DataSourceSelectAxis[]];
1027
- }
1028
- /**
1029
- * Which records of a `points` source are DELIVERED. The surviving mask is computed ONCE for the source and applied to every parameter drawing from it, so its columns can never fall out of alignment, and the surviving count is the source's `extent`. A record is dropped when any `require_finite` variable is non-finite at it, or when a `codes` map with `unmapped: "drop"` does not recognise its value.
1030
- */
1031
- interface DataSourceRecordFilter {
1032
- /**
1033
- * `file_variable` names whose value must be finite for a record to survive. (Pre-1.0.0 this named loader variable names; a source has no variables of its own now, so it names the file's.) A point with no coordinate cannot be placed and a row with no annual total cannot be weighted; dropping it is a declaration about the source, rather than a NaN travelling into the model to surface as an empty result later.
1034
- *
1035
- * @minItems 1
1036
- */
1037
- require_finite?: [string, ...string[]];
1038
- }
1039
- /**
1040
- * Binds the source's DELIVERED record count to a metaparameter, for a source whose extent is not knowable until it is read. A binding samples such a source BEFORE it closes metaparameters (esm-spec §9.7.6 site 4), so an index set declared `size: "N_REC"` is sized by the data itself and no caller counts rows and passes the number in. Every parameter drawing from the source MUST agree on the count — that agreement is also the alignment check.
1041
- */
1042
- interface DataSourceExtent {
1043
- /**
1044
- * Name of the `metaparameters` entry it binds. Declare that entry with a `default` (conventionally 0) so the document still validates and loads standalone.
1045
- */
1046
- metaparameter: string;
1047
- }
1048
938
  /**
1049
939
  * A reference to an external ESM file containing a model or reaction system definition. The ref field can be a relative or absolute local file path, or an HTTP/HTTPS URL. Relative paths are resolved relative to the directory of the referencing file.
1050
940
  */
@@ -1071,6 +961,12 @@ interface SubsystemRef {
1071
961
  * Template-library imports registered into the REFERENCED component's template scope (esm-spec §9.7.10) — assembler-chosen discretization for a mounted PDE component, without editing the leaf file. Same entry shape as §9.7.2; target implicit (this edge mounts one component). Load-time only; consumed by the §9.6.3 fixpoint; does not survive parse→emit.
1072
962
  */
1073
963
  expression_template_imports?: TemplateImport[];
964
+ /**
965
+ * Mount-edge index-set renaming (esm-spec §4.7 'Mount-edge index-set renaming'; docs/content/rfcs/mount-edge-index-set-renaming.md): a map from an index-set name AS THE RESOLVED MOUNTED DOCUMENT SPELLS IT to the name it takes in the mounting document. `index_sets` is a document-scoped registry, so two mounts that independently use one axis name at different lengths (a 59-layer atmospheric column and a 4-layer soil column both over `lev`) collide with subsystem_index_set_conflict; this field is the §9.7.7 renaming mechanism at a component-mount edge, restricted to index sets because they are the only declaration kind a mount contributes to document scope. Applied as ONE simultaneous substitution AFTER the referenced document resolves completely (its own imports, this edge's `bindings` and injection, its metaparameter close and the §9.6.3 fixpoint) and BEFORE its `index_sets` merge, transitively through every occurrence inside the mounted document — `index_sets` keys and ragged `of` lists, `{"from": …}` ranges, the `wrt`/`dim`/`var` axis scalars and bare-axis-name `integral` bounds, `where` `shape` constraints, variable/parameter `shape` lists, `Assertion.coords` keys, and `DataSourceSelectAxis.gated_by`. Keys must name an index set of the resolved mounted document (subsystem_index_set_rename_unknown_name); targets are dotted identifiers (template_import_rename_invalid) and must be distinct (template_import_rename_collision). The map need not be total: an unnamed axis passes through unrenamed, so a deliberately shared axis still merges deep-equal. Load-time only; consumed at the mount; does not survive parse→emit.
966
+ */
967
+ index_set_rename?: {
968
+ [k: string]: string;
969
+ };
1074
970
  }
1075
971
  /**
1076
972
  * One entry of `expression_template_imports` (esm-spec §9.7.2): imports the templates (and index_sets / open metaparameters) of a template-library file. `ref` uses the §4.7 reference formats (relative path, absolute path, or URL), resolved at load before validation with canonical-path cycle detection (`template_import_cycle`). `only` filters which template names become visible to the importer (`template_import_unknown_name` for unknown names). `bindings` closes the target document's open metaparameters to integers at this edge (esm-spec §9.7.6); metaparameters left unbound are re-exported into the importing document's scope. `prefix` / `rename` namespace the surviving exported names (templates after `only`, index sets, still-open metaparameters) into the importer's vocabulary, applied transitively through every occurrence inside the imported declarations; `rebind` rewrites free variable names (keyed factors and other free names in template bodies) at the same point (esm-spec §9.7.7). Renaming happens after this edge's `bindings` instantiation and `only` filtering and before the §9.7.4/§9.7.5 merge, so the same file imported under different renames registers as distinct instances while identical edges dedupe. Identifier grammar, unknown-name, and collision checks are resolver-level (`template_import_rename_invalid`, `template_import_rename_unknown_name`, `template_import_rebind_unknown_name`, `template_import_rename_collision`).
@@ -1110,7 +1006,7 @@ interface TemplateImport {
1110
1006
  };
1111
1007
  }
1112
1008
  /**
1113
- * Model-level default numerical tolerance for tests, used when a test or assertion does not provide its own.
1009
+ * Model-level default numerical tolerance for tests. Resolution is PER FIELD over four levels (esm-spec 6.6.4): assertion, test, model, then the implementation default (rel = 1e-6, no abs bound). abs and rel each take their value from the innermost level that declares that field, so a test or assertion that declares only one bound does not mask the other from here. An explicit 0 is a declaration ('no bound of this kind'), not an absence, and stops the fallthrough - including the fallthrough to the implementation default, which is why rel: 0 is the only way to ask for an absolute-only comparison.
1114
1010
  */
1115
1011
  interface Tolerance {
1116
1012
  /**
@@ -1118,7 +1014,7 @@ interface Tolerance {
1118
1014
  */
1119
1015
  abs?: number;
1120
1016
  /**
1121
- * Relative tolerance: |actual - expected| / max(|expected|, epsilon) <= rel.
1017
+ * Relative tolerance: |actual - expected| <= rel * max(|actual|, |expected|). Symmetric in actual and expected -- the scale is the larger of the two magnitudes, not |expected| alone.
1122
1018
  */
1123
1019
  rel?: number;
1124
1020
  }
@@ -1135,16 +1031,22 @@ interface Test {
1135
1031
  */
1136
1032
  description?: string;
1137
1033
  /**
1138
- * Initial-value overrides for unknowns, keyed by variable name (local to this component). Values not listed fall back to the variable's declared default.
1034
+ * Initial-value overrides for unknowns, keyed by variable name (local to this component). Values not listed fall back to the variable's declared default. A shaped unknown's value may be a row-major nested JSON array matching its declared shape (esm-spec §6.6.2).
1139
1035
  */
1140
1036
  initial_conditions?: {
1141
- [k: string]: number;
1037
+ /**
1038
+ * Initial value for one unknown: a number, or — for a SHAPED unknown — a row-major nested JSON array matching its declared shape after metaparameter folding (a mismatch is a load-time error). A scalar broadcasts to every element (esm-spec §6.6.2).
1039
+ */
1040
+ [k: string]: number | NumericArrayLiteral;
1142
1041
  };
1143
1042
  /**
1144
- * Parameter overrides, keyed by parameter name (local to this component). Values not listed fall back to the parameter's declared default.
1043
+ * Parameter overrides, keyed by parameter name (local to this component). Values not listed fall back to the parameter's declared default. A shaped parameter's value may be a row-major nested JSON array matching its declared shape (esm-spec §6.6.2).
1145
1044
  */
1146
1045
  parameter_overrides?: {
1147
- [k: string]: number;
1046
+ /**
1047
+ * Value for one parameter: a number, or — for a SHAPED parameter — a row-major nested JSON array matching its declared shape after metaparameter folding (a mismatch is a load-time error). A scalar broadcasts to every element (esm-spec §6.6.2).
1048
+ */
1049
+ [k: string]: number | NumericArrayLiteral;
1148
1050
  };
1149
1051
  time_span: TimeSpan;
1150
1052
  tolerance?: Tolerance1;
@@ -1167,7 +1069,7 @@ interface TimeSpan {
1167
1069
  end: number;
1168
1070
  }
1169
1071
  /**
1170
- * Test-level default tolerance applied to all assertions in this test that do not override it.
1072
+ * Test-level default tolerance, merged PER FIELD (esm-spec 6.6.4): it supplies each of abs and rel that an assertion in this test does not declare, and falls through to the model-level default - and then to the implementation default rel = 1e-6 - for each field it does not declare itself. Write rel: 0 to stop that fallthrough.
1171
1073
  */
1172
1074
  interface Tolerance1 {
1173
1075
  /**
@@ -1175,12 +1077,12 @@ interface Tolerance1 {
1175
1077
  */
1176
1078
  abs?: number;
1177
1079
  /**
1178
- * Relative tolerance: |actual - expected| / max(|expected|, epsilon) <= rel.
1080
+ * Relative tolerance: |actual - expected| <= rel * max(|actual|, |expected|). Symmetric in actual and expected -- the scale is the larger of the two magnitudes, not |expected| alone.
1179
1081
  */
1180
1082
  rel?: number;
1181
1083
  }
1182
1084
  /**
1183
- * Per-assertion tolerance override. If present, this takes precedence over the test-level and model-level defaults.
1085
+ * Per-assertion tolerance override, merged PER FIELD over the test-level and model-level defaults and then the implementation default (esm-spec 6.6.4): each of abs and rel that this object declares wins, and each it omits falls through independently. Declaring only abs here therefore keeps an outer rel rather than dropping it, and falls through to the implementation default rel = 1e-6 when no enclosing level declares one; write rel: 0 to mean 'no relative bound'.
1184
1086
  */
1185
1087
  interface Tolerance2 {
1186
1088
  /**
@@ -1188,7 +1090,7 @@ interface Tolerance2 {
1188
1090
  */
1189
1091
  abs?: number;
1190
1092
  /**
1191
- * Relative tolerance: |actual - expected| / max(|expected|, epsilon) <= rel.
1093
+ * Relative tolerance: |actual - expected| <= rel * max(|actual|, |expected|). Symmetric in actual and expected -- the scale is the larger of the two magnitudes, not |expected| alone.
1192
1094
  */
1193
1095
  rel?: number;
1194
1096
  }
@@ -1298,7 +1200,7 @@ interface PlotSeries {
1298
1200
  variable: string;
1299
1201
  }
1300
1202
  /**
1301
- * A single in-file rewrite rule / Expression-AST template (esm-spec §9.6 / docs/rfcs/ast-expression-templates.md). The `params` are metavariables; the `body` is the replacement Expression AST in which parameter occurrences are written as bare parameter-name strings. The template is applied in one of two ways. (1) WITHOUT `match`: it is invoked explicitly by name through an `apply_expression_template` node, whose `bindings` supply each parameter's AST. (2) WITH `match`: it is an auto-applied rewrite rule — `match` is a pattern Expression in which parameters are wildcards (a parameter in an operand/`args` position binds to the matched sub-AST; a parameter in a scalar field such as `dim`/`side` binds to the matched literal), and the rule fires wherever the pattern structurally matches a node. This unifies variable substitution (a bare-metavar `match`), named-template expansion (no `match`), and rewrite-target-op lowering (an operator `match` like `{op:'D', args:['f'], wrt:'x'}` → an `aggregate`/`makearray` stencil). Either way the `body` is instantiated by pure structural substitution of the bound metavariables — no evaluation, no metaprogramming. Rewriting is an outermost-first, priority-ordered, bounded-fixpoint load-time process (esm-spec §9.6.3): each pass is a pre-order walk that fires, at each node, the matching rule of highest `priority` (ties broken by declaration order); passes repeat to a fixpoint or until MAX_REWRITE_PASSES=64, at which point a non-converging rule set is rejected with diagnostic 'rewrite_rule_nonterminating'. A rewrite-target op (esm-spec §4.2) surviving the fixpoint into an evaluation position is rejected with diagnostic 'unlowered_operator'. A `body` MAY contain `apply_expression_template` nodes referencing other match-less in-scope templates (declared locally or imported, esm-spec §9.7.2); these are checked at registration time as a statically-checked acyclic DAG (cycles rejected with 'apply_expression_template_recursive_body'; chains deeper than MAX_TEMPLATE_EXPANSION_DEPTH=32 rejected with 'template_body_expansion_too_deep'); from esm 0.9.0 they are preserved uninlined and denote their expansion (Option B, esm-spec §9.6.4; pre-0.9.0 loaders inlined them here by pure substitution). `match` patterns MUST NOT contain `apply_expression_template` nodes. An optional `where` block adds static match-scoping constraints on the captured parameters (declared-shape/index-set scoping, filtered before priority selection — see the `where` property and esm-spec §9.6.1).
1203
+ * A single in-file rewrite rule / Expression-AST template (esm-spec §9.6 / docs/rfcs/ast-expression-templates.md). The `params` are metavariables; the `body` is the replacement Expression AST in which parameter occurrences are written as bare parameter-name strings. The template is applied in one of two ways. (1) WITHOUT `match`: it is invoked explicitly by name through an `apply_expression_template` node, whose `bindings` supply each parameter's AST. (2) WITH `match`: it is an auto-applied rewrite rule — `match` is a pattern Expression in which parameters are wildcards (a parameter in an operand/`args` position binds to the matched sub-AST; a parameter in a scalar field such as `dim`/`side` binds to the matched literal), and the rule fires wherever the pattern structurally matches a node. This unifies variable substitution (a bare-metavar `match`), named-template expansion (no `match`), and rewrite-target-op lowering (an operator `match` like `{op:'D', args:['f'], wrt:'x'}` → a `faq`/`makearray` stencil). Either way the `body` is instantiated by pure structural substitution of the bound metavariables — no evaluation, no metaprogramming. Rewriting is an outermost-first, priority-ordered, bounded-fixpoint load-time process (esm-spec §9.6.3): each pass is a pre-order walk that fires, at each node, the matching rule of highest `priority` (ties broken by declaration order); passes repeat to a fixpoint or until MAX_REWRITE_PASSES=64, at which point a non-converging rule set is rejected with diagnostic 'rewrite_rule_nonterminating'. A rewrite-target op (esm-spec §4.2) surviving the fixpoint into an evaluation position is rejected with diagnostic 'unlowered_operator'. A `body` MAY contain `apply_expression_template` nodes referencing other match-less in-scope templates (declared locally or imported, esm-spec §9.7.2); these are checked at registration time as a statically-checked acyclic DAG (cycles rejected with 'apply_expression_template_recursive_body'; chains deeper than MAX_TEMPLATE_EXPANSION_DEPTH=32 rejected with 'template_body_expansion_too_deep'); from esm 0.9.0 they are preserved uninlined and denote their expansion (Option B, esm-spec §9.6.4; pre-0.9.0 loaders inlined them here by pure substitution). `match` patterns MUST NOT contain `apply_expression_template` nodes. An optional `where` block adds static match-scoping constraints on the captured parameters (declared-shape/index-set scoping, filtered before priority selection — see the `where` property and esm-spec §9.6.1).
1302
1204
  */
1303
1205
  interface ExpressionTemplate {
1304
1206
  /**
@@ -1434,7 +1336,7 @@ interface StoichiometryEntry {
1434
1336
  stoichiometry: number;
1435
1337
  }
1436
1338
  /**
1437
- * System-level default numerical tolerance for tests, used when a test or assertion does not provide its own.
1339
+ * System-level default numerical tolerance for tests. Resolution is PER FIELD over four levels (esm-spec 6.6.4): assertion, test, system, then the implementation default (rel = 1e-6, no abs bound). abs and rel each take their value from the innermost level that declares that field, so a test or assertion that declares only one bound does not mask the other from here. An explicit 0 is a declaration ('no bound of this kind'), not an absence, and stops the fallthrough - including the fallthrough to the implementation default, which is why rel: 0 is the only way to ask for an absolute-only comparison.
1438
1340
  */
1439
1341
  interface Tolerance3 {
1440
1342
  /**
@@ -1442,12 +1344,135 @@ interface Tolerance3 {
1442
1344
  */
1443
1345
  abs?: number;
1444
1346
  /**
1445
- * Relative tolerance: |actual - expected| / max(|expected|, epsilon) <= rel.
1347
+ * Relative tolerance: |actual - expected| <= rel * max(|actual|, |expected|). Symmetric in actual and expected -- the scale is the larger of the two magnitudes, not |expected| alone.
1446
1348
  */
1447
1349
  rel?: number;
1448
1350
  }
1449
1351
  /**
1450
- * A file-local enum mapping symbolic names to positive integers (esm-spec.md §9.3). Within a single enum, integer values MUST be unique. Across enums, values MAY collide (each enum is its own namespace). Bindings resolve enum-op nodes at load time before evaluating expressions.
1352
+ * A named external data source reduced to pure I/O: it locates, reads, decodes, slices, and filters bytes on disk. It performs no reprojection and no regridding, and — from 1.0.0 — it exposes no variables and is NOT a component. It cannot be a coupling endpoint, a subsystem, or a scoped-name path root; a model consumes it by declaring a parameter whose `update` names this source and binds one of its `file_variable`s. Grid geometry a source reads (coordinates, connectivity, metric arrays) arrives as ordinary parameters and is transformed downstream by `faq` nodes and coupling expressions. Authentication and algorithm-specific tuning are runtime-only and not part of the schema.
1353
+ */
1354
+ interface DataSource {
1355
+ /**
1356
+ * Structural kind of the dataset: 'grid' (gridded array source), 'points' (scattered point/station source), or 'static' (time-invariant source). Grid geometry the source reads — coordinates, connectivity, metric arrays — arrives as ordinary parameters bound to its `file_variable`s and is consumed by `faq` nodes downstream; it needs no special descriptor. Scientific role (emissions, meteorology, elevation, ...) is not schema-validated and belongs in metadata.tags.
1357
+ */
1358
+ kind: "grid" | "points" | "static";
1359
+ source: DataSourceLocation;
1360
+ temporal?: DataSourceTemporal;
1361
+ determinism?: DataSourceDeterminism;
1362
+ /**
1363
+ * Format-specific DECODE options, passed through to the format reader verbatim (EarthSciIO calls them `reader_kwargs`): the zip `member_glob` and `skip_header_row` of an FF10 inventory, a GeoTIFF band naming, and so on. They say how bytes become an array, never what the array means — no remap, no unit conversion, no filtering (those are `variables`, `unit_conversion` and `record_filter`). A key the bound reader does not recognise MUST be an error, so a mis-spelled option cannot silently decode something else.
1364
+ */
1365
+ reader_options?: {
1366
+ [k: string]: unknown;
1367
+ };
1368
+ select?: DataSourceSelect;
1369
+ record_filter?: DataSourceRecordFilter;
1370
+ extent?: DataSourceExtent;
1371
+ reference?: Reference;
1372
+ /**
1373
+ * Free-form metadata about the data source. The "tags" field (array of strings) is conventional for expressing scientific role (e.g. "emissions", "reanalysis") and is not schema-validated.
1374
+ */
1375
+ metadata?: {
1376
+ tags?: string[];
1377
+ [k: string]: unknown;
1378
+ };
1379
+ }
1380
+ /**
1381
+ * File discovery configuration. Describes how to locate data files at runtime via URL templates with date/variable substitutions.
1382
+ */
1383
+ interface DataSourceLocation {
1384
+ /**
1385
+ * Jinja-style URL template with substitutions. Supported: {date:<strftime>} (e.g. {date:%Y%m%d}), {var}, {sector}, {species}. Custom substitutions are allowed and the runtime must accept and pass them through.
1386
+ */
1387
+ url_template: string;
1388
+ /**
1389
+ * Ordered fallback URL templates. Runtime tries each in order, first is primary. Follows the same substitution grammar as url_template.
1390
+ */
1391
+ mirrors?: string[];
1392
+ }
1393
+ /**
1394
+ * Temporal coverage and record layout for a data source.
1395
+ */
1396
+ interface DataSourceTemporal {
1397
+ /**
1398
+ * ISO 8601 datetime — first timestamp available from this source.
1399
+ */
1400
+ start?: string;
1401
+ /**
1402
+ * ISO 8601 datetime — last timestamp available from this source.
1403
+ */
1404
+ end?: string;
1405
+ /**
1406
+ * ISO 8601 duration describing how much time one file covers (e.g., "P1D", "P1M", "PT3H").
1407
+ */
1408
+ file_period?: string;
1409
+ /**
1410
+ * ISO 8601 duration describing spacing between samples within a file.
1411
+ */
1412
+ frequency?: string;
1413
+ /**
1414
+ * Number of time records per file. "auto" means read from file at runtime.
1415
+ */
1416
+ records_per_file?: number | "auto";
1417
+ /**
1418
+ * Name of the time coordinate variable in the file. Used when records_per_file is absent or "auto". If both static declarations (records_per_file + frequency) and time_variable are present, the static declaration wins and time_variable is a fallback.
1419
+ */
1420
+ time_variable?: string;
1421
+ /**
1422
+ * How many time records the source returns per query time — pure I/O; the source does NOT interpolate. Absent or 1 (default): the single at-or-before record, time axis dropped (piecewise-constant between ticks). 2: the two records bracketing the current time (floor + successor), returned with the time axis kept at length 2 so a downstream model can interpolate in time (e.g. linearly). Only 1 and 2 are supported; higher-order temporal stencils are future work. Contrast records_per_file (records IN one file).
1423
+ */
1424
+ records_per_sample?: 1 | 2;
1425
+ }
1426
+ /**
1427
+ * Reproducibility contract a binary-format loader advertises to bindings. A binding that cannot honor the declared endian / float_format / integer_width MUST reject the file at load rather than silently reinterpreting bytes.
1428
+ */
1429
+ interface DataSourceDeterminism {
1430
+ /**
1431
+ * Byte order of on-wire numeric fields.
1432
+ */
1433
+ endian?: "little" | "big";
1434
+ /**
1435
+ * Floating-point format of metric fields.
1436
+ */
1437
+ float_format?: "ieee754_single" | "ieee754_double";
1438
+ /**
1439
+ * Integer width (in bits) of connectivity fields.
1440
+ */
1441
+ integer_width?: 32 | 64;
1442
+ }
1443
+ /**
1444
+ * A per-axis selection of what the source DELIVERS from an on-disk array — one entry per NATIVE array dimension, in native dims order. Declared on a source (the default for every parameter drawing from it) or on a single parameter's `from` binding (which overrides the source's). Two parameters of one source MAY read the same `file_variable` under different selections, which is how a full-grid field and a prefix of it are both declared instead of one being sliced by the caller. The selection is defined over the axis the loader DELIVERS, so it follows any `record_filter`: `range 0..200` on a filtered points table is the first 200 SURVIVING records. Whether a binding pushes it down to the reader (fetching only what it keeps) or applies it after the read is an optimization; the two MUST agree exactly.
1445
+ */
1446
+ interface DataSourceSelect {
1447
+ /**
1448
+ * One selector per native array dimension, in native dims order.
1449
+ *
1450
+ * @minItems 1
1451
+ */
1452
+ axes: [DataSourceSelectAxis, ...DataSourceSelectAxis[]];
1453
+ }
1454
+ /**
1455
+ * Which records of a `points` source are DELIVERED. The surviving mask is computed ONCE for the source and applied to every parameter drawing from it, so its columns can never fall out of alignment, and the surviving count is the source's `extent`. A record is dropped when any `require_finite` variable is non-finite at it, or when a `codes` map with `unmapped: "drop"` does not recognise its value.
1456
+ */
1457
+ interface DataSourceRecordFilter {
1458
+ /**
1459
+ * `file_variable` names whose value must be finite for a record to survive. (Pre-1.0.0 this named loader variable names; a source has no variables of its own now, so it names the file's.) A point with no coordinate cannot be placed and a row with no annual total cannot be weighted; dropping it is a declaration about the source, rather than a NaN travelling into the model to surface as an empty result later.
1460
+ *
1461
+ * @minItems 1
1462
+ */
1463
+ require_finite?: [string, ...string[]];
1464
+ }
1465
+ /**
1466
+ * Binds the source's DELIVERED record count to a metaparameter, for a source whose extent is not knowable until it is read. A binding samples such a source BEFORE it closes metaparameters (esm-spec §9.7.6 site 4), so an index set declared `size: "N_REC"` is sized by the data itself and no caller counts rows and passes the number in. Every parameter drawing from the source MUST agree on the count — that agreement is also the alignment check.
1467
+ */
1468
+ interface DataSourceExtent {
1469
+ /**
1470
+ * Name of the `metaparameters` entry it binds. Declare that entry with a `default` (conventionally 0) so the document still validates and loads standalone.
1471
+ */
1472
+ metaparameter: string;
1473
+ }
1474
+ /**
1475
+ * A file-local enum mapping symbolic names to integers (esm-spec.md §9.3). The value domain is the WHOLE integer range — negative, zero and positive — because an enum member is a categorical CODE, not a position: it lowers to a `const` NUMBER used in arithmetic and in `join.on` key comparisons, where 0 and -1 are ordinary values (a source table's `opModeID = 0` or `polProcessID = -1` is a real code, not an absence). The two 1-based constructs in the format — `index`-op / index-set coordinates and `makearray` regions — are separate and carry their own bounds validation; using an enum member as an `index` argument is an authoring choice the index op bounds-checks, not a constraint on this block. Within a single enum, integer values MUST be unique. Across enums, values MAY collide (each enum is its own namespace). Bindings resolve enum-op nodes at load time before evaluating expressions.
1451
1476
  */
1452
1477
  interface EnumDeclaration {
1453
1478
  [k: string]: number;
@@ -1470,6 +1495,10 @@ interface CouplingOperatorCompose {
1470
1495
  translate?: {
1471
1496
  [k: string]: TranslateTarget;
1472
1497
  };
1498
+ /**
1499
+ * The entry's MERGE INTENT (esm-libraries-spec §4.7.1 step 5). TRI-STATE: absent is NOT the same as `false`, which is why this property declares no default. ABSENT — the author has not said; an entry that merges NOTHING is then `operator_compose_no_merge`, a hard refusal at flatten (such an entry is indistinguishable from one that is not there: the operator would integrate a private decoupled system from its own defaults and the other system would receive no contribution), while a PARTIAL merge is a warning. `true` — the `systems[1]` equations are CONTRIBUTIONS and every one of them whose LHS names a dependent variable MUST match an equation of `systems[0]`; any shortfall, PARTIAL included, is `operator_compose_require_match_unmatched`. There is no 'some is enough' reading. `false` — a standalone-contributing operator, DECLARED: unmatched equations are expected, are preserved per step 5, and nothing is reported.
1500
+ */
1501
+ require_match?: boolean;
1473
1502
  /**
1474
1503
  * Strategy for mapping between 0D and spatial systems.
1475
1504
  */
@@ -1653,7 +1682,28 @@ interface FunctionTableAxis {
1653
1682
  values: [number, number, ...number[]];
1654
1683
  }
1655
1684
  /**
1656
- * Document-scoped named integers bound at load (esm-spec §9.7.6): at import/subsystem edges via `bindings`, at the loader API for the root document, or by `default`. Admissible — as names or `{op, args}` integer expressions — in `index_sets` interval sizes, `aggregate` dense ranges, and `makearray` regions (folded exactly at load), and substituted as integer literals in ordinary expression positions. A metaparameter name MUST NOT collide with any visible variable/parameter/species/index-set name (`metaparameter_name_conflict`).
1685
+ * Document-scoped, OPTIONAL solver hints (esm-spec §2.2): numerics the document knows about ITSELF, which each binding maps to its own integrator. Purely additive — a document without it validates, flattens and emits exactly as before. Every field is ADVISORY: a binding MAY ignore any or all of them and still conform. Advisory governs the MECHANISM, never the OUTCOME — a binding that ignores every field and still converges conforms; the requirement to integrate successfully and agree within the CONFORMANCE_SPEC §5.9 error band is untouched by this block and is not excused by it. What IS normative: parse it, validate it, round-trip it VERBATIM (it is authored configuration, a peer of `tolerance` and `parameter_overrides` — not a load-time construct like `expression_template_imports`), leave the flattened system unchanged, and reject it in a document declaring `esm` below 1.1.0 with `solver_version_too_old`. This block is NOT for algorithm names (`BDF`/`LSODA`/`Rosenbrock23` are per-binding identifiers and would not be portable — there is deliberately no `alg` field), NOT for binding-specific compile knobs (`cse` is a sympy.lambdify concern), and NOT a DAE declaration (`system_class` is DERIVED from the equation set). An empty block (`"solver": {}`) is LEGAL and means exactly what absence means; it NORMALIZES to absence at load, so it does not survive `parse -> emit` and the typed value never holds a block with nothing set. That normalization is why the schema does NOT carry `minProperties: 1`: every other optional top-level container (`coordinates`, `index_sets`, `metaparameters`, `coupling_roles`) admits an empty object, and a lone exception here would be a rule a reader has to learn for no gain. Arrives at esm 1.1.0.
1686
+ */
1687
+ interface Solver {
1688
+ /**
1689
+ * The author's declaration of the system's stiffness. A binding MAY select an implicit / BDF-family integrator on `high`. ABSENCE IS NOT A DEFAULT VALUE: a document that omits this key has not declared its stiffness and does not thereby declare `low`; bindings MUST NOT read absence as an assertion about the system. The motivating case is the POLLU stiff-ODE benchmark (Verwer 1994), whose rate constants span ~8e-7 to ~7e9 1/s: scipy's LSODA cannot integrate it at all while BDF reproduces the published reference, a fact that is true of the MODEL rather than of any runner.
1690
+ */
1691
+ stiffness?: "low" | "moderate" | "high";
1692
+ /**
1693
+ * Absolute INTEGRATION tolerance the document asks for. Spelled as the `solve()` keyword (API_SPEC §4) so it passes through literally. This is a DIFFERENT QUANTITY from the `tolerance` object on a model / reaction system / test / assertion (esm-spec §6.6.4), which is the tolerance an assertion is COMPARED at; the two resolve independently and neither substitutes for the other. Resolution order, most-specific first: an explicit argument at the `solve()` call site, then this field, then the binding default (1e-6).
1694
+ */
1695
+ abstol?: number;
1696
+ /**
1697
+ * Relative INTEGRATION tolerance the document asks for. Same resolution order and the same distinction from the assertion-comparison `tolerance` object as `abstol`; the binding default is 1e-4.
1698
+ */
1699
+ reltol?: number;
1700
+ /**
1701
+ * Advisory: the system tolerates or benefits from this operator-splitting convention. Carries NO prescribed substep structure and does not amend esm-spec §9's single-flat-system model — a binding that does not split ignores it. The vocabulary is deliberately the one the discretization RFC §7.5 dimensional-splitting field already uses, so the word means one thing across the spec, even though that occurrence is executable and this one is a hint.
1702
+ */
1703
+ splitting?: "none" | "lie" | "strang";
1704
+ }
1705
+ /**
1706
+ * Document-scoped named integers bound at load (esm-spec §9.7.6): at import/subsystem edges via `bindings`, at the loader API for the root document, or by `default`. Admissible — as names or `{op, args}` integer expressions — in `index_sets` interval sizes, `faq` dense ranges, and `makearray` regions (folded exactly at load), and substituted as integer literals in ordinary expression positions. A metaparameter name MUST NOT collide with any visible variable/parameter/species/index-set name (`metaparameter_name_conflict`).
1657
1707
  */
1658
1708
  interface Metaparameters {
1659
1709
  [k: string]: {
@@ -1669,6 +1719,186 @@ interface Metaparameters {
1669
1719
  };
1670
1720
  }
1671
1721
 
1722
+ /**
1723
+ * Central registry of the diagnostic code STRINGS emitted by this binding, plus
1724
+ * a neutral diagnostic base class.
1725
+ *
1726
+ * Cross-binding contract — values must never change; see Python `ErrorCode`
1727
+ * enum (`pkg/earthsci-ast-py/src/earthsci_ast/error_handling.py`). These strings
1728
+ * are pinned by the shared conformance fixtures: every value below equals, byte
1729
+ * for byte, a literal currently emitted somewhere in `src/`. This module only
1730
+ * CENTRALIZES the references — it does not (and must not) change any emitted
1731
+ * string. Adding a new diagnostic means adding an entry here AND coordinating
1732
+ * the value across every binding.
1733
+ *
1734
+ * Keys are the SCREAMING_SNAKE_CASE form of the value (mirroring the Python
1735
+ * enum) so a reference reads as `ERROR_CODES.UNDEFINED_VARIABLE`.
1736
+ */
1737
+ declare const ERROR_CODES: {
1738
+ readonly ANALYSIS: "analysis";
1739
+ readonly CIRCULAR_DEPENDENCY: "circular_dependency";
1740
+ readonly DIMENSIONAL_MISMATCH: "dimensional_mismatch";
1741
+ readonly JOIN_KEY_INVALID_TYPE: "join_key_invalid_type";
1742
+ readonly JOIN_SIDE_AMBIGUOUS: "join_side_ambiguous";
1743
+ readonly JOIN_SYMS_UNKNOWN_SYMBOL: "join_syms_unknown_symbol";
1744
+ readonly DOMAIN_UNIT_MISMATCH: "domain_unit_mismatch";
1745
+ readonly COUPLE_MULTIPLICATIVE_NO_TENDENCY: "couple_multiplicative_no_tendency";
1746
+ readonly OPERATOR_COMPOSE_NO_MERGE: "operator_compose_no_merge";
1747
+ readonly OPERATOR_COMPOSE_PARTIAL_MERGE: "operator_compose_partial_merge";
1748
+ readonly OPERATOR_COMPOSE_REQUIRE_MATCH_UNMATCHED: "operator_compose_require_match_unmatched";
1749
+ readonly OPERATOR_COMPOSE_AMBIGUOUS_BARE_NAME: "operator_compose_ambiguous_bare_name";
1750
+ readonly RELATIONAL_NODE_IN_CONTINUOUS: "relational_node_in_continuous";
1751
+ readonly DERIVED_INDEX_SET_UNMATERIALIZED: "derived_index_set_unmaterialized";
1752
+ readonly UNDEFINED_INDEX_SET: "undefined_index_set";
1753
+ readonly RAGGED_VALUES_NOT_GATHERED: "ragged_values_not_gathered";
1754
+ readonly INVALID_BROADCAST_FN: "invalid_broadcast_fn";
1755
+ readonly ARRAY_SHAPE_MISMATCH: "array_shape_mismatch";
1756
+ readonly OBSERVED_CYCLE: "observed_cycle";
1757
+ readonly AMBIGUOUS_OUTPUT_NAME: "ambiguous_output_name";
1758
+ readonly RECURRENCE_NOT_WELLFOUNDED: "recurrence_not_wellfounded";
1759
+ readonly RECURRENCE_UNSUPPORTED_FORM: "recurrence_unsupported_form";
1760
+ readonly INDEXED_DEFINITION_UNSUPPORTED_FORM: "indexed_definition_unsupported_form";
1761
+ readonly UNSUPPORTED_CONSTRUCT: "unsupported_construct";
1762
+ readonly CALLBACK_UNREGISTERED: "callback_unregistered";
1763
+ readonly DATA_SOURCE_UNBOUND: "data_source_unbound";
1764
+ readonly COMPILER_UNKNOWN: "compiler_unknown";
1765
+ readonly COMPILER_UNAVAILABLE: "compiler_unavailable";
1766
+ readonly COMPILER_REFUSED_RULE: "compiler_refused_rule";
1767
+ readonly EQUATION_COUNT_MISMATCH: "equation_count_mismatch";
1768
+ readonly EVENT_AFFECTS_PARAMETER: "event_affects_parameter";
1769
+ readonly EQUATION_DEFINES_PARAMETER: "equation_defines_parameter";
1770
+ readonly UNBOUND_INDEX_SYMBOL: "unbound_index_symbol";
1771
+ readonly EVENT_VAR_UNDECLARED: "event_var_undeclared";
1772
+ readonly FACTOR_WITH_EXPRESSION_TRANSFORM: "factor_with_expression_transform";
1773
+ readonly IC_IN_REACTION_SYSTEM: "ic_in_reaction_system";
1774
+ readonly INVALID_STOICHIOMETRY: "invalid_stoichiometry";
1775
+ readonly INVALID_TEMPORAL_DURATION: "invalid_temporal_duration";
1776
+ readonly NULL_REACTION: "null_reaction";
1777
+ readonly AMBIGUOUS_SUBSYSTEM_REF: "ambiguous_subsystem_ref";
1778
+ readonly DATA_SOURCE_UNDEFINED: "data_source_undefined";
1779
+ readonly DATA_SOURCE_URL_UNRESOLVED: "data_source_url_unresolved";
1780
+ readonly RESERVED_VARIABLE_NAME: "reserved_variable_name";
1781
+ readonly ARRAY_DEFAULT_WITHOUT_SHAPE: "array_default_without_shape";
1782
+ readonly UNDEFINED_PARAMETER: "undefined_parameter";
1783
+ readonly UNDEFINED_SPECIES: "undefined_species";
1784
+ readonly UNDEFINED_SYSTEM: "undefined_system";
1785
+ readonly UNDEFINED_VARIABLE: "undefined_variable";
1786
+ readonly UNKNOWN_OVERRIDE_KEY: "unknown_override_key";
1787
+ readonly ASSERTION_RANK_MISMATCH: "assertion_rank_mismatch";
1788
+ readonly UNIT_ERROR: "unit_error";
1789
+ readonly UNIT_INCONSISTENCY: "unit_inconsistency";
1790
+ readonly UNIT_PARSE_ERROR: "unit_parse_error";
1791
+ readonly UNPARSEABLE_UNIT: "unparseable_unit";
1792
+ readonly UNRESOLVED_SCOPED_REF: "unresolved_scoped_ref";
1793
+ readonly UNRESOLVED_SUBSYSTEM_REF: "unresolved_subsystem_ref";
1794
+ readonly JSON_PARSE_ERROR: "json_parse_error";
1795
+ readonly UNEXPECTED_ERROR: "unexpected_error";
1796
+ readonly SCHEMA_VALIDATION_ERROR: "schema_validation_error";
1797
+ readonly PARSE_ERROR: "parse_error";
1798
+ readonly EXPRESSION_TEMPLATE_ERROR: "expression_template_error";
1799
+ readonly ENUM_LOWERING_ERROR: "enum_lowering_error";
1800
+ readonly NONFINITE_NUMBER: "nonfinite_number";
1801
+ readonly LOAD_ERROR: "load_error";
1802
+ readonly SOLVER_VERSION_TOO_OLD: "solver_version_too_old";
1803
+ readonly CONST_UNITS_VERSION_TOO_OLD: "const_units_version_too_old";
1804
+ readonly APPLY_EXPRESSION_TEMPLATE_BINDINGS_MISMATCH: "apply_expression_template_bindings_mismatch";
1805
+ readonly APPLY_EXPRESSION_TEMPLATE_INVALID_DECLARATION: "apply_expression_template_invalid_declaration";
1806
+ readonly APPLY_EXPRESSION_TEMPLATE_RECURSIVE_BODY: "apply_expression_template_recursive_body";
1807
+ readonly APPLY_EXPRESSION_TEMPLATE_UNKNOWN_TEMPLATE: "apply_expression_template_unknown_template";
1808
+ readonly APPLY_EXPRESSION_TEMPLATE_VERSION_TOO_OLD: "apply_expression_template_version_too_old";
1809
+ readonly REWRITE_RULE_NONTERMINATING: "rewrite_rule_nonterminating";
1810
+ readonly TEMPLATE_BODY_EXPANSION_TOO_DEEP: "template_body_expansion_too_deep";
1811
+ readonly TEMPLATE_BODY_REFERENCES_COUPLING_REWRITTEN_VARIABLE: "template_body_references_coupling_rewritten_variable";
1812
+ readonly TEMPLATE_CONSTRAINT_UNKNOWN_INDEX_SET: "template_constraint_unknown_index_set";
1813
+ readonly METAPARAMETER_NAME_CONFLICT: "metaparameter_name_conflict";
1814
+ readonly METAPARAMETER_TYPE_ERROR: "metaparameter_type_error";
1815
+ readonly METAPARAMETER_UNBOUND: "metaparameter_unbound";
1816
+ readonly TEMPLATE_IMPORT_CYCLE: "template_import_cycle";
1817
+ readonly TEMPLATE_IMPORT_INDEX_SET_CONFLICT: "template_import_index_set_conflict";
1818
+ readonly TEMPLATE_IMPORT_IS_COUPLING_LIBRARY: "template_import_is_coupling_library";
1819
+ readonly TEMPLATE_IMPORT_NAME_CONFLICT: "template_import_name_conflict";
1820
+ readonly TEMPLATE_IMPORT_NOT_LIBRARY: "template_import_not_library";
1821
+ readonly TEMPLATE_IMPORT_REBIND_UNKNOWN_NAME: "template_import_rebind_unknown_name";
1822
+ readonly TEMPLATE_IMPORT_RENAME_COLLISION: "template_import_rename_collision";
1823
+ readonly TEMPLATE_IMPORT_RENAME_INVALID: "template_import_rename_invalid";
1824
+ readonly TEMPLATE_IMPORT_RENAME_UNKNOWN_NAME: "template_import_rename_unknown_name";
1825
+ readonly TEMPLATE_IMPORT_UNKNOWN_NAME: "template_import_unknown_name";
1826
+ readonly TEMPLATE_IMPORT_UNRESOLVED: "template_import_unresolved";
1827
+ readonly TEMPLATE_IMPORT_VERSION_TOO_OLD: "template_import_version_too_old";
1828
+ readonly TEMPLATE_INJECT_TARGET_NOT_COMPONENT: "template_inject_target_not_component";
1829
+ readonly TEMPLATE_INJECT_TARGET_UNKNOWN: "template_inject_target_unknown";
1830
+ readonly TEMPLATE_LIBRARY_ILLEGAL_PAYLOAD: "template_library_illegal_payload";
1831
+ readonly GEOMETRY_MANIFOLD_INVALID: "geometry_manifold_invalid";
1832
+ readonly MAKEARRAY_REGION_INVERTED: "makearray_region_inverted";
1833
+ readonly SUBSYSTEM_INDEX_SET_CONFLICT: "subsystem_index_set_conflict";
1834
+ readonly SUBSYSTEM_INDEX_SET_RENAME_UNKNOWN_NAME: "subsystem_index_set_rename_unknown_name";
1835
+ readonly SUBSYSTEM_INDEX_SET_RENAME_UNSUPPORTED_MOUNT_FORM: "subsystem_index_set_rename_unsupported_mount_form";
1836
+ /**
1837
+ * A §4.7 `{ ref }` mount at a form this binding does not implement — a
1838
+ * top-level `reaction_systems.<k>` `{ ref }` (esm-spec §4.7 "Two mount forms,
1839
+ * one mechanism"). Refused at `/reaction_systems/<k>` rather than left as an
1840
+ * unresolved stub.
1841
+ */
1842
+ readonly MOUNT_FORM_UNSUPPORTED: "mount_form_unsupported";
1843
+ readonly SUBSYSTEM_REF_IS_COUPLING_LIBRARY: "subsystem_ref_is_coupling_library";
1844
+ readonly SUBSYSTEM_REF_IS_TEMPLATE_LIBRARY: "subsystem_ref_is_template_library";
1845
+ readonly COUPLING_EDGE_UNKNOWN_ROLE: "coupling_edge_unknown_role";
1846
+ readonly COUPLING_IMPORT_BIND_NOT_A_COMPONENT: "coupling_import_bind_not_a_component";
1847
+ readonly COUPLING_IMPORT_NOT_LIBRARY: "coupling_import_not_library";
1848
+ readonly COUPLING_IMPORT_ROLE_UNBOUND: "coupling_import_role_unbound";
1849
+ readonly COUPLING_IMPORT_UNKNOWN_ROLE: "coupling_import_unknown_role";
1850
+ readonly COUPLING_IMPORT_UNRESOLVED: "coupling_import_unresolved";
1851
+ readonly COUPLING_LIBRARY_ILLEGAL_PAYLOAD: "coupling_library_illegal_payload";
1852
+ readonly COUPLING_LIBRARY_NESTED_IMPORT: "coupling_library_nested_import";
1853
+ readonly COUPLING_ROLE_UNUSED: "coupling_role_unused";
1854
+ readonly ENUM_OP_MALFORMED: "enum_op_malformed";
1855
+ readonly UNKNOWN_ENUM: "unknown_enum";
1856
+ readonly UNKNOWN_ENUM_SYMBOL: "unknown_enum_symbol";
1857
+ readonly TABLE_LOOKUP_UNKNOWN_TABLE: "table_lookup_unknown_table";
1858
+ readonly TABLE_LOOKUP_AXIS_NAME_MISMATCH: "table_lookup_axis_name_mismatch";
1859
+ readonly TABLE_LOOKUP_OUTPUT_OUT_OF_RANGE: "table_lookup_output_out_of_range";
1860
+ readonly TABLE_INTERPOLATION_AXES_MISMATCH: "table_interpolation_axes_mismatch";
1861
+ readonly TABLE_DATA_SHAPE_MISMATCH: "table_data_shape_mismatch";
1862
+ readonly TABLE_AXIS_NAN: "table_axis_nan";
1863
+ readonly TABLE_OUT_OF_BOUNDS_UNSUPPORTED: "table_out_of_bounds_unsupported";
1864
+ readonly UNKNOWN_CLOSED_FUNCTION: "unknown_closed_function";
1865
+ readonly CLOSED_FUNCTION_ARITY: "closed_function_arity";
1866
+ readonly CLOSED_FUNCTION_OVERFLOW: "closed_function_overflow";
1867
+ readonly INTERP_AXIS_LENGTH_MISMATCH: "interp_axis_length_mismatch";
1868
+ readonly INTERP_AXIS_NOT_CONST: "interp_axis_not_const";
1869
+ readonly INTERP_AXIS_TOO_SHORT: "interp_axis_too_short";
1870
+ readonly INTERP_NAN_IN_AXIS: "interp_nan_in_axis";
1871
+ readonly INTERP_NON_MONOTONIC_AXIS: "interp_non_monotonic_axis";
1872
+ readonly INTERP_TABLE_NOT_CONST: "interp_table_not_const";
1873
+ readonly SEARCHSORTED_NAN_IN_TABLE: "searchsorted_nan_in_table";
1874
+ readonly SEARCHSORTED_NON_MONOTONIC: "searchsorted_non_monotonic";
1875
+ readonly CONST_NOT_SCALAR: "const_not_scalar";
1876
+ readonly ENUM_NOT_LOWERED: "enum_not_lowered";
1877
+ readonly FN_MISSING_NAME: "fn_missing_name";
1878
+ readonly INVALID_EXPRESSION: "invalid_expression";
1879
+ readonly UNBOUND_VARIABLE: "unbound_variable";
1880
+ readonly UNLOWERED_OPERATOR: "unlowered_operator";
1881
+ readonly UNSUPPORTED_OPERATOR: "unsupported_operator";
1882
+ readonly CONFLICTING_DERIVATIVE: "conflicting_derivative";
1883
+ readonly DIMENSION_PROMOTION: "dimension_promotion";
1884
+ readonly FLATTEN_ERROR: "flatten_error";
1885
+ readonly UNEVALUABLE_OPERATOR: "unevaluable_operator";
1886
+ };
1887
+ /** A diagnostic code string from {@link ERROR_CODES}. */
1888
+ type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
1889
+ /**
1890
+ * Neutral base for EarthSciAST diagnostics: an `Error` carrying a stable `code`
1891
+ * string (from {@link ERROR_CODES}) and optional structured `details`. This is
1892
+ * purely additive — a single home for future diagnostics. It intentionally does
1893
+ * NOT touch the existing `EsmMachineryError` / `EnumLoweringError` /
1894
+ * `ClosedFunctionError` classes, which another file owns.
1895
+ */
1896
+ declare class EsmDiagnosticError extends Error {
1897
+ readonly code: string;
1898
+ readonly details?: Record<string, unknown> | undefined;
1899
+ constructor(code: string, message: string, details?: Record<string, unknown> | undefined);
1900
+ }
1901
+
1672
1902
  /**
1673
1903
  * Tagged numeric-literal AST nodes and lossless JSON I/O per
1674
1904
  * discretization RFC §5.4.1 (int/float distinction) and §5.4.6
@@ -1696,6 +1926,7 @@ interface Metaparameters {
1696
1926
  * NaN and ±Infinity are rejected on serialize with
1697
1927
  * `E_CANONICAL_NONFINITE` per RFC §5.4.6.
1698
1928
  */
1929
+
1699
1930
  interface NumericLiteral {
1700
1931
  readonly kind: 'int' | 'float';
1701
1932
  readonly value: number;
@@ -1715,7 +1946,7 @@ declare function isFloatLit(x: unknown): x is NumericLiteral & {
1715
1946
  * at the boundary between kind-aware and kind-agnostic code.
1716
1947
  */
1717
1948
  declare function numericValue(x: unknown): number | undefined;
1718
- declare class LosslessJsonParseError extends Error {
1949
+ declare class LosslessJsonParseError extends EsmDiagnosticError {
1719
1950
  readonly position: number;
1720
1951
  constructor(message: string, position: number);
1721
1952
  }
@@ -1741,7 +1972,7 @@ declare function losslessJsonParse(text: string): unknown;
1741
1972
  * `canonicalize.ts` re-exports this class under the same name, so consumers may
1742
1973
  * keep importing `CanonicalizeError` from either module.
1743
1974
  */
1744
- declare class CanonicalizeError extends Error {
1975
+ declare class CanonicalizeError extends EsmDiagnosticError {
1745
1976
  /** Stable RFC §5.4.6 / §5.4.7 error code. */
1746
1977
  readonly code: string;
1747
1978
  constructor(code: string, message?: string);
@@ -1783,7 +2014,7 @@ declare function losslessJsonStringify(value: unknown): string;
1783
2014
  * `.0` override when the result is an integer-valued plain-decimal token.
1784
2015
  *
1785
2016
  * NAMING: this is the DOCUMENT-serialization float emitter (used by
1786
- * {@link losslessJsonStringify} and `save()`), which relies on
2017
+ * {@link losslessJsonStringify} and `toJson()`), which relies on
1787
2018
  * `ToString(Number)`'s own exponent formatting. It is deliberately NOT the
1788
2019
  * strict RFC §5.4.6 CANONICAL-FORM emitter — that is the confusingly-similar
1789
2020
  * `formatCanonicalFloat` in `canonicalize.ts`, which additionally normalizes
@@ -1794,7 +2025,7 @@ declare function losslessJsonStringify(value: unknown): string;
1794
2025
  declare function formatFloatToken(value: number): string;
1795
2026
 
1796
2027
  /**
1797
- * EarthSciML Serialization Format TypeScript type definitions — plus a small
2028
+ * EarthSciML Abstract Syntax Tree Format TypeScript type definitions — plus a small
1798
2029
  * set of RUNTIME re-exports.
1799
2030
  *
1800
2031
  * Provides the complete type definitions for the ESM format: the auto-generated
@@ -1807,11 +2038,30 @@ declare function formatFloatToken(value: number): string;
1807
2038
  *
1808
2039
  * Canonical alias names (duplicates are kept for back-compat but marked
1809
2040
  * `@deprecated`):
1810
- * - root file structure → `EsmFile` (aliases: `EsmFormat`, generated `ESMFormat`)
2041
+ * - root file structure → `EsmFile` (alias: the generated `ESMFormat`)
1811
2042
  * - operator node → `ExpressionNode` (alias: `ExprNode`)
1812
2043
  * - `Expression` (wire / schema-shaped value) and `Expr` (widened in-memory
1813
2044
  * value that MAY carry a tagged `NumericLiteral`) are DISTINCT types, not
1814
2045
  * aliases — pick by whether you hold a wire value or an in-memory one.
2046
+ *
2047
+ * DO NOT collapse `Expression` and `Expr` into one type. The phase-6 surface
2048
+ * tidy (API_SPEC.md §8) proposed it and it was refused, because the two are not
2049
+ * an alias pair in either direction:
2050
+ *
2051
+ * - `Expression` is GENERATED from `esm-schema.json` by json2ts (plus
2052
+ * `scripts/fix-generated-expression.mjs`). Deleting it means the next
2053
+ * `npm run generate-types` puts it back.
2054
+ * - The relationship is a strict, ONE-WAY subtype: `Expression` is assignable
2055
+ * to `Expr`, and `Expr` is NOT assignable to `Expression` — the compiler
2056
+ * rejects it, because `Expr` admits the tagged `NumericLiteral` leaf that
2057
+ * only exists in memory. Collapsing onto `Expr` would let a tagged literal
2058
+ * reach a serialization boundary typed for the wire; collapsing onto
2059
+ * `Expression` would delete the tagged leaf the discretization RFC §5.4.1
2060
+ * requires.
2061
+ *
2062
+ * Both names are load-bearing and heavily used (roughly 400 references each in
2063
+ * this package), and the editor is written almost entirely against `Expression`
2064
+ * because it edits wire values.
1815
2065
  */
1816
2066
 
1817
2067
  /**
@@ -1856,9 +2106,64 @@ type EsmFile = ESMFormat1 & Omit<ESMFormat2, 'models'> & {
1856
2106
  models?: {
1857
2107
  [k: string]: Model | SubsystemRef;
1858
2108
  };
2109
+ /**
2110
+ * NON-SCHEMA, LOADER-POPULATED. The per-component `expression_templates`
2111
+ * registries of the Option-B loaded image, keyed
2112
+ * `"models.<name>"` / `"reaction_systems.<name>"` — the Julia / Python
2113
+ * `component_templates` key shape.
2114
+ *
2115
+ * `loadInput` (`parse.ts`) snapshots these between
2116
+ * `lowerExpressionTemplates` (which preserves the per-component blocks) and
2117
+ * `expandDocument` (which strips them), because esm-libraries-spec §4.7.5
2118
+ * step 4 requires `flatten` to carry the MERGED template registry as a
2119
+ * first-class field of the flattened representation — see
2120
+ * `mergedTemplateRegistry` in `flatten-template-registry.ts`.
2121
+ *
2122
+ * It is NOT part of `esm-schema.json`, is NEVER serialized (`toJson`
2123
+ * excludes it), and is absent on a document that declares no per-component
2124
+ * `expression_templates` block — i.e. on almost every document. Values are
2125
+ * raw JSON template declarations (`{params, body, match?, …}`), not the
2126
+ * typed `Expression` form.
2127
+ *
2128
+ * `loadInput` attaches it as a NON-ENUMERABLE own property, so
2129
+ * `Object.keys`, `JSON.stringify`, spread and structural equality all see
2130
+ * exactly the document and nothing else — the conformance round-trip
2131
+ * (`loadString(toJson(loadString(f)))` deep-equals `loadString(f)`) would
2132
+ * otherwise fail for every template-bearing fixture. A consequence worth
2133
+ * knowing: a shallow copy (`{...file}`) DROPS it, so a pass that rebuilds
2134
+ * the file object must re-attach it if the result will be flattened.
2135
+ */
2136
+ componentTemplates?: Record<string, unknown>;
2137
+ /**
2138
+ * NON-SCHEMA, LOADER-POPULATED. The loader-API metaparameter bindings this
2139
+ * document was loaded with (esm-spec §9.7.6 binding site 4) — the map a
2140
+ * discovered §8.9.4 `extent` arrives as.
2141
+ *
2142
+ * `loadInput` (`parse.ts`) attaches it so `resolveSubsystemRefsSync` can
2143
+ * BACKFILL each `subsystems.<k>` mount edge's close with it (§4.7 "Two mount
2144
+ * forms, one mechanism"): in this binding ref resolution is a separate step
2145
+ * from `load`, so the bindings have no other route from the loader call to
2146
+ * the mount edge. Filtered at the edge to the names the LEAF declares, and
2147
+ * an explicit edge `binding` always wins.
2148
+ *
2149
+ * NON-ENUMERABLE for the same reason `componentTemplates` is: the
2150
+ * conformance round-trip compares loaded documents structurally, and a
2151
+ * loader sidecar is not part of the document. Absent unless the document was
2152
+ * loaded with `metaparameters`.
2153
+ */
2154
+ loaderMetaparameters?: Record<string, number>;
2155
+ /**
2156
+ * NON-SCHEMA, LOADER-POPULATED. The directory relative `coupling_import`
2157
+ * refs resolve against (esm-spec §10.10 -> §4.7: "relative to the directory
2158
+ * of the referencing file"), recorded by `loadInput` when the load had an
2159
+ * explicit `basePath` (`loadPath` always passes the file's directory) and
2160
+ * preferred by `expandCouplingImports` over `CouplingImportOptions.basePath`.
2161
+ * NON-ENUMERABLE for the same reason `componentTemplates` is: the
2162
+ * `coupling_import` entry must round-trip verbatim (§10.10.3), so the base is
2163
+ * kept beside the document, never written into its refs.
2164
+ */
2165
+ couplingImportBase?: string;
1859
2166
  };
1860
- /** @deprecated Prefer {@link EsmFile}. Identical to the generated `ESMFormat`. */
1861
- type EsmFormat = ESMFormat;
1862
2167
  /** @deprecated Prefer {@link ExpressionNode} (the generated name). */
1863
2168
  type ExprNode = ExpressionNode;
1864
2169
 
@@ -2098,6 +2403,7 @@ type Model = Omit<Model$1, 'variables' | 'subsystems'> & {
2098
2403
  * counts their own axis made TS the only binding that could report a mismatch
2099
2404
  * between the two spellings of a number density.
2100
2405
  */
2406
+
2101
2407
  interface CanonicalDims {
2102
2408
  kg?: number;
2103
2409
  m?: number;
@@ -2108,12 +2414,65 @@ interface CanonicalDims {
2108
2414
  cd?: number;
2109
2415
  rad?: number;
2110
2416
  }
2417
+ /**
2418
+ * The EXACT scale of a unit relative to SI (esm-spec §4.8.1 "Scales are EXACT"):
2419
+ * a product of prime powers and a power of π, each with a rational exponent, so
2420
+ * `mi` is 2^4 * 3^2 * 5^-3 * 11 * 127. Multiplying units adds exponents,
2421
+ * dividing subtracts them and a rational power multiplies them, so a composite
2422
+ * unit's scale never rounds. Every scale AGREEMENT (`m + km`, `m/s = mi/h`) is
2423
+ * decided with {@link ExactScale.equals}; the floating-point `scale` is kept for
2424
+ * numeric conversion only.
2425
+ */
2426
+ declare class ExactScale {
2427
+ private readonly primes;
2428
+ private readonly piExp;
2429
+ private constructor();
2430
+ private static readonly ONE;
2431
+ /** Exactly 1. */
2432
+ static one(): ExactScale;
2433
+ /** A positive safe integer. */
2434
+ static integer(value: number): ExactScale;
2435
+ /** `num/den` for positive safe integers. */
2436
+ static ratio(num: number, den: number): ExactScale;
2437
+ /** `10^k`. */
2438
+ static pow10(k: number): ExactScale;
2439
+ /** π. */
2440
+ static pi(): ExactScale;
2441
+ /**
2442
+ * The exact value of a positive decimal literal as the registry writes it
2443
+ * (`"0.3048"`, `"133.322387415"`, `"2.6867e20"`), read from its TEXT.
2444
+ */
2445
+ static decimal(literal: string): ExactScale;
2446
+ private normalized;
2447
+ private combine;
2448
+ /** The scale of a product of units. */
2449
+ multiply(other: ExactScale): ExactScale;
2450
+ /** The scale of a quotient of units. */
2451
+ divide(other: ExactScale): ExactScale;
2452
+ /** The scale of a unit raised to a (rational) power. */
2453
+ power(exponent: number): ExactScale;
2454
+ /** Whether two scales are the same number. */
2455
+ equals(other: ExactScale): boolean;
2456
+ /** Whether this is exactly 1. */
2457
+ isOne(): boolean;
2458
+ /** The nearest double, for diagnostics and numeric conversion — never for comparison. */
2459
+ toNumber(): number;
2460
+ /**
2461
+ * `p/q`, `p/q*pi` or `p/q*pi^k` in lowest terms (`/q` omitted when it is 1),
2462
+ * the spelling tests/conformance/unit_registry pins; `null` when an exponent is
2463
+ * not whole (`sqrt(km)`).
2464
+ */
2465
+ ratioString(): string | null;
2466
+ toString(): string;
2467
+ }
2111
2468
  interface ParsedUnit {
2112
2469
  dims: CanonicalDims;
2113
2470
  scale: number;
2114
2471
  offset?: number;
2472
+ /** The scale every agreement is decided on (esm-spec §4.8.1). */
2473
+ exact: ExactScale;
2115
2474
  }
2116
- declare class UnitConversionError extends Error {
2475
+ declare class UnitConversionError extends EsmDiagnosticError {
2117
2476
  constructor(message: string);
2118
2477
  }
2119
2478
  /**
@@ -2203,7 +2562,7 @@ interface UnitResult {
2203
2562
  * `null` is not "dimensionless" — it is "this analysis cannot say", and it is
2204
2563
  * the value returned for an unknown variable, an unparseable unit
2205
2564
  * declaration, and any operator whose dimensional semantics this module does
2206
- * not model (`index`, `fn`, `aggregate`, `makearray`, `table_lookup`, ...).
2565
+ * not model (`index`, `fn`, `faq`, `makearray`, `table_lookup`, ...).
2207
2566
  * Keeping the two apart is what stops a structural op from being *assumed*
2208
2567
  * dimensionless and thereby manufacturing a false mismatch against a
2209
2568
  * dimensional operand. `null` propagates through every combining rule, and a
@@ -2245,7 +2604,7 @@ interface UnitWarning {
2245
2604
  * registry gap is a false rejection.)
2246
2605
  * - `analysis` — the checker cannot DETERMINE a dimension: a symbolic
2247
2606
  * exponent (`x^n`, whose dimension depends on `n`'s runtime value), an
2248
- * operator with no dimensional rule (`aggregate`, `index`, `fn`,
2607
+ * operator with no dimensional rule (`faq`, `index`, `fn`,
2249
2608
  * `table_lookup`), a malformed arity, an unknown variable. Genuinely
2250
2609
  * undeterminable — a statement about the checker, not the file. → WARNING,
2251
2610
  * and the dimension is reported UNKNOWN and the check SKIPPED, never assumed
@@ -2376,14 +2735,14 @@ interface SchemaError {
2376
2735
  /**
2377
2736
  * Parse error - thrown when JSON parsing fails
2378
2737
  */
2379
- declare class ParseError extends Error {
2738
+ declare class ParseError extends EsmDiagnosticError {
2380
2739
  originalError?: Error | undefined;
2381
2740
  constructor(message: string, originalError?: Error | undefined);
2382
2741
  }
2383
2742
  /**
2384
2743
  * Schema validation error - thrown when schema validation fails
2385
2744
  */
2386
- declare class SchemaValidationError extends Error {
2745
+ declare class SchemaValidationError extends EsmDiagnosticError {
2387
2746
  errors: SchemaError[];
2388
2747
  constructor(message: string, errors: SchemaError[]);
2389
2748
  }
@@ -2399,7 +2758,7 @@ declare const SCHEMA_VERSION: string;
2399
2758
  */
2400
2759
  declare function validateSchema(data: unknown): SchemaError[];
2401
2760
  /**
2402
- * Options controlling how `load()` parses and represents an ESM file.
2761
+ * Options controlling how the `load*` entry points parse and represent an ESM file.
2403
2762
  */
2404
2763
  interface LoadOptions {
2405
2764
  /**
@@ -2466,21 +2825,51 @@ interface LoadOptions {
2466
2825
  onVersionWarning?: ((message: string) => void) | undefined;
2467
2826
  }
2468
2827
  /**
2469
- * Load an ESM file from a JSON string or pre-parsed object
2828
+ * Read and parse an ESM document from a filesystem path.
2829
+ *
2830
+ * The file's own directory anchors relative `expression_template_imports`
2831
+ * refs and `{ref}` subsystem refs unless `options.basePath` overrides it.
2832
+ * Requires synchronous file access (Node); browser hosts should read the
2833
+ * bytes themselves and call {@link loadString}.
2834
+ *
2835
+ * @param path - Filesystem path to the `.esm` document
2836
+ * @param options - Optional load-time settings (see {@link LoadOptions})
2837
+ * @returns Typed EsmFile object
2838
+ * @throws {ParseError} When JSON parsing fails or version is incompatible
2839
+ * @throws {SchemaValidationError} When schema validation fails
2840
+ */
2841
+ declare function loadPath(path: string, options?: LoadOptions): EsmFile;
2842
+ /**
2843
+ * Parse an ESM document from JSON TEXT.
2470
2844
  *
2471
- * @param input - JSON string or pre-parsed JavaScript object
2845
+ * @param json - The document as a JSON string
2472
2846
  * @param options - Optional load-time settings (see {@link LoadOptions})
2473
2847
  * @returns Typed EsmFile object
2474
2848
  * @throws {ParseError} When JSON parsing fails or version is incompatible
2475
2849
  * @throws {SchemaValidationError} When schema validation fails
2476
2850
  */
2477
- declare function load(input: string | object, options?: LoadOptions): EsmFile;
2851
+ declare function loadString(json: string, options?: LoadOptions): EsmFile;
2852
+ /**
2853
+ * Parse an ESM document that is ALREADY a JavaScript object — the same
2854
+ * document a `.esm` file holds, just already `JSON.parse`d.
2855
+ *
2856
+ * `options.canonical` has no effect here: canonical mode tags numeric
2857
+ * literals during JSON decoding, and this entry point does no decoding.
2858
+ * Callers who want tagged leaves should run `losslessJsonParse` on the text
2859
+ * themselves, or use {@link loadString}.
2860
+ *
2861
+ * @param doc - The already-parsed document
2862
+ * @param options - Optional load-time settings (see {@link LoadOptions})
2863
+ * @returns Typed EsmFile object
2864
+ * @throws {SchemaValidationError} When schema validation fails
2865
+ */
2866
+ declare function loadDocument(doc: object, options?: LoadOptions): EsmFile;
2478
2867
 
2479
2868
  /**
2480
2869
  * ESM Format JSON Serialization (esm-cs3).
2481
2870
  *
2482
- * `save(file)` emits an `EsmFile` as wire-form JSON suitable for round-trip
2483
- * through `load()`. Mirrors the Python and Julia serializers in three respects:
2871
+ * `toJson(file)` emits an `EsmFile` as wire-form JSON suitable for round-trip
2872
+ * through `loadString()`. Mirrors the Python and Julia serializers in three respects:
2484
2873
  *
2485
2874
  * 1. **AST canonical numeric handling.** `NumericLiteral` tagged leaves
2486
2875
  * (the in-memory int/float carrier produced by `losslessJsonParse` and
@@ -2500,7 +2889,7 @@ declare function load(input: string | object, options?: LoadOptions): EsmFile;
2500
2889
  * 3. **Wire-form keys.** TypeScript types are generated from the JSON
2501
2890
  * schema, so the in-memory shape already matches the wire form (no
2502
2891
  * Python-style dataclass → wire field-name remapping is needed).
2503
- * Object key order is the insertion order produced by `load()` /
2892
+ * Object key order is the insertion order produced by `loadString()` /
2504
2893
  * authored constructors, which is itself schema-driven.
2505
2894
  *
2506
2895
  * The Python reference at `pkg/earthsci-ast-py/src/earthsci_ast/serialize.py`
@@ -2509,8 +2898,8 @@ declare function load(input: string | object, options?: LoadOptions): EsmFile;
2509
2898
  * delegating shape preservation to the generated types.
2510
2899
  */
2511
2900
 
2512
- /** Optional behavior controls for {@link save}. */
2513
- interface SaveOptions {
2901
+ /** Optional behavior controls for {@link toJson} / {@link writePath}. */
2902
+ interface ToJsonOptions {
2514
2903
  /**
2515
2904
  * When `true`, emit byte-canonical JSON per RFC §5.4.6: integer-tagged
2516
2905
  * `NumericLiteral` leaves as integer tokens, float-tagged leaves with
@@ -2530,16 +2919,31 @@ interface SaveOptions {
2530
2919
  indent?: number;
2531
2920
  }
2532
2921
  /**
2533
- * Serialize an `EsmFile` to wire-form JSON.
2922
+ * Serialize an `EsmFile` to wire-form JSON. PURE — it never touches disk;
2923
+ * {@link writePath} is the writer.
2534
2924
  *
2535
2925
  * @param file - The `EsmFile` to serialize.
2536
- * @param options - Optional behavior controls (see {@link SaveOptions}).
2926
+ * @param options - Optional behavior controls (see {@link ToJsonOptions}).
2537
2927
  * @returns Wire-form JSON string.
2538
2928
  * @throws {CanonicalNonfiniteError} In `canonical: true` mode, if a
2539
2929
  * `NumericLiteral` leaf holds NaN or ±Infinity (RFC §5.4.6 forbids
2540
2930
  * non-finite numbers in the canonical wire form).
2541
2931
  */
2542
- declare function save(file: EsmFile, options?: SaveOptions): string;
2932
+ declare function toJson(file: EsmFile, options?: ToJsonOptions): string;
2933
+ /**
2934
+ * {@link toJson} with no indentation — the single-line wire form. Present in
2935
+ * every binding, because Rust and Go have no default arguments and so cannot
2936
+ * express `toJson(file, { indent: 0 })`.
2937
+ */
2938
+ declare function toJsonCompact(file: EsmFile, options?: ToJsonOptions): string;
2939
+ /**
2940
+ * Write an `EsmFile` to `path` as wire-form JSON. Returns nothing: no
2941
+ * function in this API both writes and hands back the payload — call
2942
+ * {@link toJson} when you want the string.
2943
+ *
2944
+ * Requires synchronous file access (Node).
2945
+ */
2946
+ declare function writePath(file: EsmFile, path: string, options?: ToJsonOptions): void;
2543
2947
 
2544
2948
  /**
2545
2949
  * Shared validation result / error types for the structural-validation modules.
@@ -2604,14 +3008,37 @@ interface ValidateOptions {
2604
3008
  basePath?: string;
2605
3009
  }
2606
3010
  /**
2607
- * Validate ESM data and return structured validation result.
3011
+ * Validate a TYPED ESM DOCUMENT and return a structured validation result.
2608
3012
  *
2609
- * @param data - ESM data as JSON string or object
3013
+ * API_SPEC.md §8 item 13: `validate` takes a typed document in EVERY binding.
3014
+ * It used to accept `string | object` here, so `validate(someString)` meant
3015
+ * "parse this JSON text" in TypeScript and Python but was a type error in Rust
3016
+ * and Go. The text convenience now has its own name, {@link validateText}, so
3017
+ * the argument type says which one you are calling.
3018
+ *
3019
+ * Passing a string is rejected at compile time by the signature and at runtime
3020
+ * by an explicit guard, rather than silently doing something else.
3021
+ *
3022
+ * @param document - a loaded ESM document (or a plain object of the same shape)
2610
3023
  * @param options - Optional {@link ValidateOptions}; pass `basePath` to let
2611
3024
  * relative `{ref}` / template-import targets be opened and resolved.
2612
3025
  * @returns ValidationResult with validation status and errors
2613
3026
  */
2614
- declare function validate(data: string | object, options?: ValidateOptions): ValidationResult;
3027
+ declare function validate(document: EsmFile | object, options?: ValidateOptions): ValidationResult;
3028
+ /**
3029
+ * Validate ESM JSON **text**: parse it, then run {@link validate} on the result.
3030
+ *
3031
+ * A malformed document does not throw — it comes back as a `ValidationResult`
3032
+ * carrying a single `json_parse_error` schema error, the same envelope the old
3033
+ * string-accepting `validate()` produced.
3034
+ *
3035
+ * The canonical name is `validate_text` (API_SPEC.md §8 item 13). There is no
3036
+ * `validatePath` counterpart in this binding: `@earthsciml/ast` is built for
3037
+ * the browser as well as Node, and the package has no filesystem story on the
3038
+ * public surface for a synchronous read. Read the file yourself and call
3039
+ * `validateText`, passing `basePath` so relative `{ref}` targets still resolve.
3040
+ */
3041
+ declare function validateText(text: string, options?: ValidateOptions): ValidationResult;
2615
3042
 
2616
3043
  /**
2617
3044
  * The esm 1.0.0 classification API (esm-spec §6.3.1).
@@ -2654,13 +3081,30 @@ declare function odeStates(model: Model): string[];
2654
3081
  /** Membership test for {@link odeStates}. */
2655
3082
  declare function isOdeState(model: Model, name: string): boolean;
2656
3083
  /**
2657
- * Unknowns defined by a BARE-VARIABLE LHS (`y ~ f(…)`) — eliminable,
2658
- * materializable. An unknown that is already an ODE state is not observed.
3084
+ * Unknowns an equation DEFINES — its LHS naming them, bare (`y ~ f(…)`) or
3085
+ * indexed (`y[i] ~ f(…)`, which defines the whole array `y`) — eliminable,
3086
+ * materializable (esm-spec §6.3.1). An unknown that is already an ODE state is
3087
+ * not observed.
3088
+ *
3089
+ * The split from {@link algebraicUnknowns} is SEMANTIC, not syntactic: observed
3090
+ * when an equation defines the unknown, algebraic when it is only constrained.
3091
+ * The defining form is read through the LHS's BASE NAME, so an arrayed
3092
+ * definition is observed exactly as its scalar counterpart is; only a genuine
3093
+ * expression LHS, one that names no single variable (`H*H*SO4 ~ Ksp`), is an
3094
+ * implicit constraint. §6.3.1 used to spell the criterion "a bare-variable
3095
+ * LHS", which was written for scalar equations and contradicted the semantic
3096
+ * criterion in the arrayed case.
3097
+ *
3098
+ * Note that *eliminable* is not *inlineable*: a scalar observed is eliminated
3099
+ * by substituting its definition into every consumer, while an arrayed one
3100
+ * materializes into a buffer its consumers index. Both are observed; a caller
3101
+ * that wants the strict inlineable form asks {@link observedDefinitions} for it
3102
+ * with `bareOnly`.
2659
3103
  */
2660
3104
  declare function observedUnknowns(model: Model): string[];
2661
3105
  /**
2662
- * The DEFINING EXPRESSION of every observed unknown: the RHS of the
2663
- * bare-variable-LHS equation whose LHS is that name.
3106
+ * The DEFINING EXPRESSION of every observed unknown: the RHS of the equation
3107
+ * whose LHS names it, bare (`y ~ f(…)`) or indexed (`y[i] ~ f(…)`).
2664
3108
  *
2665
3109
  * This is the 1.0.0 relocation, in one place. An observed unknown's definition
2666
3110
  * used to live in `variables[v].expression`; it now lives in the model's
@@ -2671,11 +3115,14 @@ declare function observedUnknowns(model: Model): string[];
2671
3115
  * Only the first equation for a name is recorded: a second one is an unbalanced
2672
3116
  * system, which {@link validateEquationBalance} reports rather than this.
2673
3117
  */
2674
- declare function observedDefinitions(model: Model): Map<string, Expression>;
3118
+ declare function observedDefinitions(model: Model, options?: {
3119
+ bareOnly?: boolean;
3120
+ }): Map<string, Expression>;
2675
3121
  /**
2676
- * Unknowns constrained only implicitly (`H*H*SO4 ~ Ksp`) — everything left once
2677
- * the ODE states and the observed unknowns are removed. Defining this set by
2678
- * elimination is what makes the three sets a partition by construction.
3122
+ * Unknowns constrained only implicitly — no equation names them on its LHS
3123
+ * (`H*H*SO4 ~ Ksp`). Everything left once the ODE states and the observed
3124
+ * unknowns are removed; defining this set by elimination is what makes the
3125
+ * three sets a partition by construction.
2679
3126
  */
2680
3127
  declare function algebraicUnknowns(model: Model): string[];
2681
3128
  /** The update rules of a parameter, normalized to an array (possibly empty). */
@@ -2717,6 +3164,17 @@ declare function constantParameters(model: Model): string[];
2717
3164
  declare function systemKind(model: Model): SystemKind;
2718
3165
  /** The model's explicit `system_kind` field, or `null` when absent. */
2719
3166
  declare function declaredSystemKind(model: Model): SystemKind | null;
3167
+ /**
3168
+ * The kind a caller choosing a solver actually asks for: the DECLARED
3169
+ * `system_kind` when the document states one, otherwise the DERIVED kind.
3170
+ *
3171
+ * API_SPEC.md §8 item 11 rules the family as three distinct questions plus this
3172
+ * one composition — {@link systemKind} derives, {@link declaredSystemKind}
3173
+ * reads the field, and this composes them as `declared ?? derived`. It never
3174
+ * returns `null`: an undeclared model still has a derived kind. Matches Go's
3175
+ * `EffectiveSystemKind`.
3176
+ */
3177
+ declare function effectiveSystemKind(model: Model): SystemKind;
2720
3178
  /** Every derived set for one model node, as the conformance goldens spell it. */
2721
3179
  interface ModelClassification {
2722
3180
  odeStates: string[];
@@ -2788,7 +3246,7 @@ declare function joinCadence(a: CadenceClass, b: CadenceClass): CadenceClass;
2788
3246
  /** The join of any number of cadence classes; `const` when there are none. */
2789
3247
  declare function joinAll(classes: Iterable<CadenceClass>): CadenceClass;
2790
3248
  /** Raised when the observed-definition chain contains a cycle. */
2791
- declare class CadenceCycleError extends Error {
3249
+ declare class CadenceCycleError extends EsmDiagnosticError {
2792
3250
  readonly cycle: string[];
2793
3251
  constructor(cycle: string[]);
2794
3252
  }
@@ -2807,7 +3265,26 @@ declare class CadenceSeeder {
2807
3265
  private readonly memo;
2808
3266
  private readonly inProgress;
2809
3267
  private readonly independentVariable;
3268
+ /**
3269
+ * Memo for {@link isRecurrence}. A recurrence body reads itself once per lag —
3270
+ * 38 times in `tests/fixtures/recurrence/07_recurrence_thirty_eight_lags.esm`
3271
+ * — and each read reaches `leaf` as a separate self-edge, so without this the
3272
+ * whole well-foundedness analysis would re-run per lag.
3273
+ */
3274
+ private readonly recurrenceMemo;
2810
3275
  constructor(model: Model, esmFile?: EsmFile | undefined);
3276
+ /**
3277
+ * Whether `name`'s defining equation is a causal-recurrence CANDIDATE --
3278
+ * array-shaped, with an `index` self-read in its own RHS -- and so whether its
3279
+ * self-edge is an ordering the §4.3.1.1 rules govern rather than a cycle.
3280
+ *
3281
+ * Candidacy, not well-foundedness: see `isRecurrenceCandidate` for why the
3282
+ * stricter predicate would mask the very codes §5.19.5 requires. Delegated
3283
+ * wholesale to `recurrence.js` so the seeder and the validator cannot disagree
3284
+ * about which equations the construct covers. Memoized because a recurrence
3285
+ * body reads itself once per lag.
3286
+ */
3287
+ private isRecurrence;
2811
3288
  /** The set of observed unknowns, for callers that want to enumerate them. */
2812
3289
  observedNames(): string[];
2813
3290
  /**
@@ -2907,6 +3384,8 @@ interface ComponentGraph {
2907
3384
  /** All coupling relationships */
2908
3385
  edges: CouplingEdge[];
2909
3386
  }
3387
+ /** Which of the two graphs §4.8 defines. */
3388
+ type GraphKind = 'component' | 'expression';
2910
3389
  /**
2911
3390
  * Directed graph with node/edge lists plus adjacency, predecessor, and
2912
3391
  * successor lookups (ESM Libraries Specification §4.8). Nodes are addressed by
@@ -2914,6 +3393,17 @@ interface ComponentGraph {
2914
3393
  * those keys through `source`/`target`.
2915
3394
  */
2916
3395
  interface Graph<N, E> {
3396
+ /**
3397
+ * Which of the two §4.8 graphs this is. Carried explicitly because the DOT
3398
+ * and Mermaid exporters name the graph in their header
3399
+ * (`digraph ComponentGraph` / `digraph ExpressionGraph`) and an EMPTY graph —
3400
+ * `tests/valid/data_sources_only.esm` produces two — has no node to sniff.
3401
+ * The other bindings get this from their type system (Julia dispatches on
3402
+ * `Graph{ComponentNode,CouplingEdge}`, Python reads `node_type`, Go and Rust
3403
+ * have two distinct structs); TypeScript's `Graph` is one generic interface,
3404
+ * so it carries the tag.
3405
+ */
3406
+ kind?: GraphKind;
2917
3407
  /** All nodes in the graph */
2918
3408
  nodes: N[];
2919
3409
  /** All edges in the graph */
@@ -2939,8 +3429,9 @@ interface VariableNode {
2939
3429
  * categories a consumer of the graph actually wants, recovered from the
2940
3430
  * equations and from each parameter's `distribution` / `update`.
2941
3431
  *
2942
- * `state` is an ODE state, `observed` an unknown with a bare-variable-LHS
2943
- * definition, `algebraic` an implicitly-constrained unknown, `brownian` a
3432
+ * `state` is an ODE state, `observed` an unknown some equation DEFINES (its
3433
+ * LHS naming it, bare or indexed), `algebraic` one that is only implicitly
3434
+ * CONSTRAINED (no equation names it on its LHS), `brownian` a
2944
3435
  * wiener-updated parameter, `discrete` a parameter with any other update,
2945
3436
  * `parameter` a sampled or constant one, and `species` a reaction-system
2946
3437
  * species.
@@ -2956,7 +3447,9 @@ interface VariableNode {
2956
3447
  *
2957
3448
  * The `equation_index` sentinel {@link NON_EQUATION_INDEX} (`-1`) marks a
2958
3449
  * dependency that does not originate from a positionally-numbered equation or
2959
- * reaction — i.e. an observed/expression definition or a coupling variable map.
3450
+ * reaction — a coupling variable map, or the synthetic `expr_result` edges of a
3451
+ * BARE-EXPRESSION target. (An observed unknown's definition IS one of the
3452
+ * model's equations from 1.0.0, so its edges carry that equation's index.)
2960
3453
  */
2961
3454
  interface DependencyEdge {
2962
3455
  /** Source variable name */
@@ -2976,7 +3469,7 @@ interface DependencyEdge {
2976
3469
  /**
2977
3470
  * Position of the equation/reaction that created this dependency, or
2978
3471
  * {@link NON_EQUATION_INDEX} (`-1`) when the dependency has no positional
2979
- * equation (observed-variable definitions and coupling maps).
3472
+ * equation (coupling maps and bare-expression targets).
2980
3473
  */
2981
3474
  equation_index: number;
2982
3475
  /** The expression that created this dependency */
@@ -2988,17 +3481,6 @@ interface DependencyEdge {
2988
3481
  * model components and edges are coupling rules, with adjacency helpers.
2989
3482
  */
2990
3483
  declare function componentGraph(file: EsmFile): Graph<ComponentNode, CouplingEdge>;
2991
- /**
2992
- * Extract the system graph from an ESM file in the legacy `{nodes, edges}`
2993
- * {@link ComponentGraph} shape (edges as {@link CouplingEdge} carrying
2994
- * `from`/`to`).
2995
- *
2996
- * @deprecated Prefer {@link componentGraph}, which returns the richer
2997
- * {@link Graph} with adjacency/predecessor/successor helpers. This snake_case
2998
- * alias is retained because the editor's web-components consume the flat
2999
- * `{nodes, edges}` shape; it will be removed once those callers migrate.
3000
- */
3001
- declare function component_graph(esmFile: EsmFile): ComponentGraph;
3002
3484
  /**
3003
3485
  * Utility to check if a component exists in the ESM file
3004
3486
  */
@@ -3027,10 +3509,24 @@ declare function expressionGraph(target: EsmFile | Model | ReactionSystem | Equa
3027
3509
  * Export graph as Graphviz DOT format.
3028
3510
  * Node shapes: box for models and reaction systems, diamond for operators.
3029
3511
  * Edge styles: solid for compose, dashed for variable_map.
3512
+ *
3513
+ * The header NAMES the graph — `digraph ComponentGraph` / `digraph
3514
+ * ExpressionGraph`. §4.8.3 requires a DOT export and specifies no syntax for it,
3515
+ * so the cross-binding tie-break is the majority: Python, Go, Rust and Julia all
3516
+ * emitted the named form and this binding alone emitted a bare `digraph {`.
3517
+ * `tests/conformance/graph/cases.json` pins it.
3030
3518
  */
3031
3519
  declare function toDot<N extends object, E>(graph: Graph<N, E>): string;
3032
3520
  /**
3033
3521
  * Export graph as Mermaid flowchart format for Markdown embedding.
3522
+ *
3523
+ * The header is `graph TD`. §4.8.3 requires a Mermaid export and specifies no
3524
+ * syntax for it, so the cross-binding tie-break is the majority, applied to the
3525
+ * keyword and the direction independently: `graph` beats `flowchart` 4-1
3526
+ * (Python, Go, Rust and Julia against this binding) and `TD` beats `LR` 3-2
3527
+ * (this binding, Python and Julia against Go and Rust). `graph` is Mermaid's
3528
+ * legacy spelling of `flowchart`; both render, and the majority points at
3529
+ * `graph`. `tests/conformance/graph/cases.json` pins it.
3034
3530
  */
3035
3531
  declare function toMermaid<N extends object, E>(graph: Graph<N, E>): string;
3036
3532
  /**
@@ -3351,13 +3847,13 @@ declare function groupSubexpressionsByType(commonSubexpressions: CommonSubexpres
3351
3847
  * does not cover. Callers that need a boolean answer should use
3352
3848
  * {@link isDifferentiable}.
3353
3849
  */
3354
- declare class NonDifferentiableExpressionError extends Error {
3850
+ declare class NonDifferentiableExpressionError extends EsmDiagnosticError {
3355
3851
  readonly op: string;
3356
3852
  readonly variable: string;
3357
3853
  constructor(op: string, variable: string);
3358
3854
  }
3359
3855
  /** Thrown by {@link higherOrderDerivative} when `order` is not positive. */
3360
- declare class InvalidDerivativeOrderError extends Error {
3856
+ declare class InvalidDerivativeOrderError extends EsmDiagnosticError {
3361
3857
  readonly order: number;
3362
3858
  constructor(order: number);
3363
3859
  }
@@ -3459,13 +3955,6 @@ interface AnalysisOptions {
3459
3955
  * @returns Complete analysis results
3460
3956
  */
3461
3957
  declare function analyzeExpression(target: Expr | Model | EsmFile, options?: AnalysisOptions): AnalysisResults;
3462
- /**
3463
- * Static namespace wrapping {@link analyzeExpression}.
3464
- * @deprecated Call {@link analyzeExpression} directly.
3465
- */
3466
- declare const ExpressionAnalyzer: {
3467
- analyze: typeof analyzeExpression;
3468
- };
3469
3958
 
3470
3959
  /**
3471
3960
  * Pretty-printing formatters for ESM format expressions, equations, models, and files.
@@ -3522,8 +4011,8 @@ declare function toMathML(expr: Expr | Equation | Model | ReactionSystem | React
3522
4011
  * functions, derivatives (`D(x)/Dt` and `D(x, t)`), open/user function calls;
3523
4012
  * - array & call-shaped tier: array literals `[…]` (`const`), indexing
3524
4013
  * `a[i, j]` (`index`), dotted closed-function calls `datetime.year(t)` (`fn`),
3525
- * the `true` literal, and `integral` / `reshape` / `transpose` / `concat`;
3526
- * - reduction & array-query tier: `aggregate` reductions
4014
+ * the `true` and `false` literals, and `integral` / `reshape` / `transpose` / `concat`;
4015
+ * - reduction & array-query tier: `faq` reductions
3527
4016
  * `sum[i] (expr) where {i in set, j in lo:hi} join(a=b) if pred distinct
3528
4017
  * key=k [semiring=…]` (all clause shapes), the `argmin`/`argmax` arg-witnesses
3529
4018
  * `argmin[g] (expr) where {…}`, template application
@@ -3537,9 +4026,9 @@ declare function toMathML(expr: Expr | Equation | Model | ReactionSystem | React
3537
4026
  * reconstructs as a plain `+` reduction — the join-less `sum_product` annotation
3538
4027
  * (semantically identical there) is not recovered; both reprint identically.
3539
4028
  *
3540
- * Still deferred (need dedicated surface syntax — a later pass): `table_lookup`,
3541
- * `broadcast`, `enum`, and `intersect_polygon` (its `id` field is not printed, so
3542
- * it can't round-trip). Those are refused with an {@link ExpressionParseError}.
4029
+ * Still deferred (need dedicated surface syntax — a later pass): `broadcast` and
4030
+ * `enum`. Those are refused with an {@link ExpressionParseError}, as is the
4031
+ * call spelling `table_lookup(…)` (its real surface is `visc[T=temp]`).
3543
4032
  *
3544
4033
  * Design rules: multiplication is ALWAYS explicit (`k * A`) — no implicit
3545
4034
  * juxtaposition, because identifiers are multi-letter (`NO2`, `O3`, `k_photo`).
@@ -3555,7 +4044,7 @@ declare function toMathML(expr: Expr | Equation | Model | ReactionSystem | React
3555
4044
  */
3556
4045
 
3557
4046
  /** Thrown when an expression string cannot be parsed. */
3558
- declare class ExpressionParseError extends Error {
4047
+ declare class ExpressionParseError extends EsmDiagnosticError {
3559
4048
  /** 0-based character offset into the source where parsing failed. */
3560
4049
  pos: number;
3561
4050
  constructor(message: string,
@@ -3698,7 +4187,7 @@ declare function substituteInReactionSystem(system: ReactionSystem, bindings: Re
3698
4187
  * @param system ReactionSystem to derive ODEs from
3699
4188
  * @returns Model with species as state variables, derived ODEs plus constraints
3700
4189
  */
3701
- declare function deriveODEs(system: ReactionSystem): Model;
4190
+ declare function deriveOdes(system: ReactionSystem): Model;
3702
4191
  /**
3703
4192
  * Compute stoichiometric matrix from a reaction system
3704
4193
  *
@@ -3743,6 +4232,14 @@ declare function substrateMatrix(system: ReactionSystem): number[][];
3743
4232
  * @returns Product stoichiometric matrix
3744
4233
  */
3745
4234
  declare function productMatrix(system: ReactionSystem): number[][];
4235
+ /**
4236
+ * @deprecated Spelling alias for {@link deriveOdes}, kept for one minor per
4237
+ * API_SPEC.md §10. `deriveODEs` violated §2/§2.1: the canonical name is
4238
+ * `derive_odes`, whose TypeScript transliteration is `deriveOdes`, and this
4239
+ * binding already spelled the siblings `odeStates` / `isOdeState` that way.
4240
+ * Removed at the next major.
4241
+ */
4242
+ declare const deriveODEs: typeof deriveOdes;
3746
4243
 
3747
4244
  /**
3748
4245
  * Immutable editing operations for the ESM format
@@ -3754,7 +4251,7 @@ declare function productMatrix(system: ReactionSystem): number[][];
3754
4251
  /**
3755
4252
  * Error thrown when attempting to remove a variable that is still referenced
3756
4253
  */
3757
- declare class VariableInUseError extends Error {
4254
+ declare class VariableInUseError extends EsmDiagnosticError {
3758
4255
  variableName: string;
3759
4256
  references: string[];
3760
4257
  constructor(variableName: string, references: string[]);
@@ -3762,7 +4259,7 @@ declare class VariableInUseError extends Error {
3762
4259
  /**
3763
4260
  * Error thrown when attempting an operation on a non-existent entity
3764
4261
  */
3765
- declare class EntityNotFoundError extends Error {
4262
+ declare class EntityNotFoundError extends EsmDiagnosticError {
3766
4263
  entityType: string;
3767
4264
  entityName: string;
3768
4265
  constructor(entityType: string, entityName: string);
@@ -3985,20 +4482,70 @@ declare function contains(expr: Expr, varName: string): boolean;
3985
4482
  */
3986
4483
  declare function simplify(expr: Expr): Expr;
3987
4484
 
4485
+ /**
4486
+ * `table_lookup` → `interp.linear` / `interp.bilinear` / `index` lowering —
4487
+ * esm-spec §9.5.3.
4488
+ *
4489
+ * A `table_lookup` node is SUGAR. It names a `function_tables` entry plus one
4490
+ * input expression per declared axis, and §9.5.3 gives the exact §9.2
4491
+ * closed-function tree it stands for. Nothing in this binding evaluated
4492
+ * `table_lookup` itself: the op is absent from the evaluable-core op registry,
4493
+ * so `evaluateExpression` refused it as an `unlowered_operator` — and the only
4494
+ * lowering that existed lived INSIDE a test harness
4495
+ * (`function-tables-lowering.test.ts`). A document whose observed is defined by
4496
+ * a `table_lookup` therefore validated, round-tripped, and then failed every
4497
+ * evaluation that depended on it, while the same lookup written out by hand in
4498
+ * the lowered form worked (issue #188).
4499
+ *
4500
+ * **This runs at EVALUATION, not at load, and that is the point.** §9.5.4 makes
4501
+ * `function_tables` / `table_lookup` first-class AUTHORED constructs that must
4502
+ * survive a round trip, and `toJson` serializes the typed `EsmFile` that
4503
+ * `loadString` produced — so lowering in `parse.ts` (where `lowerEnums` sits)
4504
+ * would emit the lowered `fn` form and break §9.5.4. §9.5.3 admits either an
4505
+ * in-memory transformation or a direct evaluator dispatch; `codegen.ts`
4506
+ * dispatches on the node and lowers it here, one node at a time, leaving the
4507
+ * loaded image authored-as-written.
4508
+ *
4509
+ * Bit-equivalence with the hand-written inline-`const` lookup (§9.5's central
4510
+ * promise) comes for free: the lowered tree drives the very same
4511
+ * `closed-functions.ts` `interp.linear` / `interp.bilinear` implementations an
4512
+ * author would have invoked by hand.
4513
+ *
4514
+ * **`out_of_bounds: "error"` is REFUSED, not silently clamped.** `"clamp"` is
4515
+ * required of every binding and `"error"` is "conformant when implemented"
4516
+ * (§9.5.1); this binding does not implement it, so §9.5.3a makes the lookup a
4517
+ * `table_out_of_bounds_unsupported` error here rather than an `interp.*` tree
4518
+ * that answers in a mode the author did not ask for. That would be the same
4519
+ * defect as the one this module exists to fix: a wrong number with nothing in
4520
+ * the result to say so. The document still LOADS and still round-trips — it
4521
+ * simply does not evaluate.
4522
+ */
4523
+
4524
+ /** A document's `function_tables` block (esm-spec §9.5.1), keyed by table id. */
4525
+ type FunctionTables = {
4526
+ [k: string]: FunctionTable;
4527
+ };
4528
+
3988
4529
  /**
3989
4530
  * Tree-walking scalar evaluator (`compileExpression` / `evaluateExpression`)
3990
4531
  * — the EarthSciAST TypeScript in-process runner. Despite the historical
3991
- * "codegen" filename this performs NO code generation or lowering: a
3992
- * canonical-form `Expr` is walked directly. `compileExpression` returns a
3993
- * closure over a free-variable bindings map that returns the scalar numeric
3994
- * result; `evaluateExpression` walks and applies in one step.
4532
+ * "codegen" filename this performs NO code generation: a canonical-form `Expr`
4533
+ * is walked directly. `compileExpression` returns a closure over a
4534
+ * free-variable bindings map that returns the scalar numeric result;
4535
+ * `evaluateExpression` walks and applies in one step.
3995
4536
  *
3996
4537
  * Structural / array ops and the closed-function registry are dispatched to
3997
- * their consumers; ANY op the evaluable-core op-registry does not know — the
3998
- * open-tier rewrite-target sugar `grad`/`div`/`laplacian`/`integral`, a user op,
3999
- * or a spatial / right-hand-side `D` — is rejected here as an unlowered
4000
- * rewrite-target: it must be lowered to a stencil by a rewrite rule before
4001
- * evaluation.
4538
+ * their consumers. `table_lookup` is the one op lowered rather than dispatched
4539
+ * — to its §9.5.3 `interp.*` form, one node at a time, on the way through; see
4540
+ * `lower-table-lookups.ts` for why that happens HERE and not at load.
4541
+ *
4542
+ * Both entry points walk the whole expression BEFORE evaluating any of it
4543
+ * (esm-spec §9.6.6) and refuse an operator this evaluator cannot evaluate: an op
4544
+ * outside the §4.2 evaluable core — the open-tier sugar
4545
+ * `grad`/`div`/`laplacian`/`integral`, a user op, or a spatial / right-hand-side
4546
+ * `D` — with `unlowered_operator`, and a core op with no scalar rule — the
4547
+ * array/query and value-invention ops, `Pre`, an unlowered `enum` — with
4548
+ * `unevaluable_operator`. An op in an untaken `ifelse` branch is refused too.
4002
4549
  */
4003
4550
 
4004
4551
  /**
@@ -4007,6 +4554,19 @@ declare function simplify(expr: Expr): Expr;
4007
4554
  * returns the scalar result.
4008
4555
  */
4009
4556
  type CompiledExpression = (bindings: Map<string, number>) => number;
4557
+ /**
4558
+ * Document context an expression may need beyond its free-variable bindings.
4559
+ *
4560
+ * Only `table_lookup` needs any: the node names a `function_tables` entry that
4561
+ * lives on the DOCUMENT, not in the expression, so an expression lifted out of
4562
+ * an `EsmFile` cannot be evaluated without being handed the block it refers to
4563
+ * (`{ functionTables: file.function_tables }`). Every other op is
4564
+ * self-contained, which is why this is optional.
4565
+ */
4566
+ interface EvaluateOptions {
4567
+ /** The document's `function_tables` block (esm-spec §9.5.1). */
4568
+ functionTables?: FunctionTables | undefined;
4569
+ }
4010
4570
  /**
4011
4571
  * Error carrying the stable, cross-binding `unlowered_operator` diagnostic
4012
4572
  * (esm-spec §4.2 / §9.6.3 constraint 6 / §9.6.8). Raised when a rewrite-target
@@ -4016,10 +4576,24 @@ type CompiledExpression = (bindings: Map<string, number>) => number;
4016
4576
  * is open); the gate fires only at evaluation, mirroring the Julia `_compile`
4017
4577
  * gate in tree_walk.jl.
4018
4578
  */
4019
- declare class UnloweredOperatorError extends Error {
4020
- readonly code = "unlowered_operator";
4579
+ declare class UnloweredOperatorError extends EsmDiagnosticError {
4580
+ readonly code: typeof ERROR_CODES.UNLOWERED_OPERATOR;
4021
4581
  constructor(message: string);
4022
4582
  }
4583
+ /**
4584
+ * Error carrying the stable, cross-binding `unevaluable_operator` diagnostic
4585
+ * (esm-spec §9.6.6): an op that IS in the §4.2 evaluable-core set but that this
4586
+ * scalar evaluator has no rule for. The complement of
4587
+ * {@link UnloweredOperatorError}, which is for an op OUTSIDE the core, so it
4588
+ * carries no rewrite-rule advice: the op needs an earlier pipeline stage (value
4589
+ * invention, or a load-time lowering pass), not a rewrite rule.
4590
+ */
4591
+ declare class UnevaluableOperatorError extends EsmDiagnosticError {
4592
+ readonly code: typeof ERROR_CODES.UNEVALUABLE_OPERATOR;
4593
+ /** The offending operator name. */
4594
+ readonly op: string;
4595
+ constructor(op: string, remedy?: string);
4596
+ }
4023
4597
  /**
4024
4598
  * Error raised by the tree-walking evaluator for a node it cannot reduce to a
4025
4599
  * scalar: an unbound variable, an unsupported operator, an unlowered `enum`, a
@@ -4033,8 +4607,7 @@ declare class UnloweredOperatorError extends Error {
4033
4607
  * These `code` values are binding-local diagnostics for the in-process runner,
4034
4608
  * distinct from the cross-language conformance codes in `errors.ts`.
4035
4609
  */
4036
- declare class EvaluatorError extends Error {
4037
- readonly code: string;
4610
+ declare class EvaluatorError extends EsmDiagnosticError {
4038
4611
  constructor(code: string, message: string);
4039
4612
  }
4040
4613
  /**
@@ -4046,8 +4619,11 @@ declare class EvaluatorError extends Error {
4046
4619
  * load time) and array-valued `const` nodes (those are consumed by
4047
4620
  * container ops such as `interp.searchsorted` and `index`, not by
4048
4621
  * scalar evaluation).
4622
+ *
4623
+ * Pass `{ functionTables: file.function_tables }` to evaluate an expression
4624
+ * containing `table_lookup` nodes — see {@link EvaluateOptions}.
4049
4625
  */
4050
- declare function compileExpression(expr: Expr): CompiledExpression;
4626
+ declare function compileExpression(expr: Expr, options?: EvaluateOptions): CompiledExpression;
4051
4627
  /**
4052
4628
  * Compile and apply in one step. Equivalent to
4053
4629
  * `compileExpression(expr)(bindings)` but avoids allocating a closure
@@ -4055,7 +4631,7 @@ declare function compileExpression(expr: Expr): CompiledExpression;
4055
4631
  * fixed-point observed-variable resolution, unit-conversion
4056
4632
  * folding).
4057
4633
  */
4058
- declare function evaluateExpression(expr: Expr, bindings: Map<string, number>): number;
4634
+ declare function evaluateExpression(expr: Expr, bindings: Map<string, number>, options?: EvaluateOptions): number;
4059
4635
 
4060
4636
  /**
4061
4637
  * Migration utilities for ESM format version upgrades.
@@ -4089,7 +4665,7 @@ declare function evaluateExpression(expr: Expr, bindings: Map<string, number>):
4089
4665
  /**
4090
4666
  * Error thrown when migration fails.
4091
4667
  */
4092
- declare class MigrationError extends Error {
4668
+ declare class MigrationError extends EsmDiagnosticError {
4093
4669
  constructor(message: string);
4094
4670
  }
4095
4671
  /**
@@ -4104,7 +4680,14 @@ declare function canMigrate(sourceVersion: string, targetVersion: string): boole
4104
4680
  * - everything else — including EVERY 0.x version, which 1.0.0's clean break
4105
4681
  * puts out of reach of a marker bump — → `[]`.
4106
4682
  */
4107
- declare function getSupportedMigrationTargets(sourceVersion: string): string[];
4683
+ declare function supportedMigrationTargets(sourceVersion: string): string[];
4684
+ /**
4685
+ * @deprecated Use {@link supportedMigrationTargets}. The canonical name is
4686
+ * `supported_migration_targets` (Julia, Python and Go already spell it that
4687
+ * way); the harmonized API drops the `get` prefix. Kept for one minor per
4688
+ * API_SPEC.md §10; removed at the next major.
4689
+ */
4690
+ declare const getSupportedMigrationTargets: typeof supportedMigrationTargets;
4108
4691
  /**
4109
4692
  * Migrate an ESM file from its current schema version to the target version.
4110
4693
  *
@@ -4162,72 +4745,396 @@ declare function isCouplingLibraryDoc(raw: unknown): boolean;
4162
4745
  declare function expandCouplingImports(file: EsmFile, options?: CouplingImportOptions): CouplingEntry[] | undefined;
4163
4746
 
4164
4747
  /**
4165
- * Coupled System Flattening for the ESM format
4166
- *
4167
- * Transforms a multi-system ESM file into a single unified flattened system
4168
- * by namespacing all variables with their source system prefix and processing
4169
- * coupling entries to produce a unified equation set.
4748
+ * Coupled System Flattening for the ESM format (esm-libraries-spec §4.7.5).
4749
+ *
4750
+ * Transforms a multi-system ESM file into a single unified flattened system:
4751
+ * every variable dot-namespaced by its owning component, every coupling rule
4752
+ * resolved INTO the equation set, and the registries a consumer needs
4753
+ * (`index_sets`, `function_tables`, the merged `template_registry`) carried
4754
+ * along so the flattened form is self-describing.
4755
+ *
4756
+ * ## The canonical field set (esm-libraries-spec §4.7.5 step 4, `esm: 1.0.0`)
4757
+ *
4758
+ * Step 4's field table is a CROSS-BINDING CONTRACT, not a suggestion: the
4759
+ * canonical `snake_case` names transliterate to `camelCase` here per
4760
+ * API_SPEC.md §2, and the shared corpus `tests/conformance/flatten/cases.json`
4761
+ * (generated from the Python oracle) pins every field of it, INCLUDING ORDER.
4762
+ *
4763
+ * Three rules from that section drive shapes this module would otherwise get
4764
+ * wrong, and each cost a real defect before it was written down:
4765
+ *
4766
+ * - **The parameter subsets partition `parameters`; they are not siblings of
4767
+ * it.** esm-spec §6.3.1 says `brownian_parameters` / `discrete_parameters` /
4768
+ * `sampled_parameters` / `constant_parameters` *partition the parameters*, so
4769
+ * a `wiener`-updated entry appears in BOTH {@link FlattenedSystem.parameters}
4770
+ * and {@link FlattenedSystem.brownianParameters}. This binding used to
4771
+ * EXCLUDE it (under the old name `brownianVariables`), which made the
4772
+ * parameter vector's LENGTH depend on whether the model happened to be
4773
+ * stochastic and left the four sets partitioning nothing.
4774
+ * - **`algebraicVariables` is a SUBSET of `stateVariables`.** `stateVariables`
4775
+ * is the SOLVED-FOR VECTOR (an implementation axis), not §6.3.1's
4776
+ * classification of the unknowns; a DAE solves for its algebraic unknowns, so
4777
+ * they ride in the vector.
4778
+ * - **`fieldIcs` entries are REMOVED from `equations`.** An initial condition is
4779
+ * a datum, not an equation of motion.
4780
+ *
4781
+ * ## Ordering is observable
4782
+ *
4783
+ * Every ordered map and list below is in DOCUMENT ORDER: components in the order
4784
+ * the file declares them (models first, then reaction systems), and within a
4785
+ * component the order it declares its variables; a coupling-merged entry keeps
4786
+ * the position of its first occurrence. A parameter vector is positional, so
4787
+ * lexicographic sorting or host-map iteration order is NON-CONFORMING. The
4788
+ * `name -> variable` maps are plain objects, whose string-key insertion order
4789
+ * JavaScript preserves.
4790
+ *
4791
+ * The Python binding (`pkg/earthsci-ast-py`) is the flatten oracle; this module
4792
+ * mirrors its semantics function for function.
4170
4793
  */
4171
4794
 
4172
4795
  /** Options for {@link flatten}. Only needed when the file uses `coupling_import`. */
4173
4796
  type FlattenOptions = CouplingImportOptions;
4174
4797
  /**
4175
- * A single equation in the flattened system, with dot-namespaced variable names.
4798
+ * Base class for every error {@link flatten} raises. Mirrors Rust's
4799
+ * `FlattenError` and Python's `FlattenError` for cross-language error-name
4800
+ * parity.
4176
4801
  */
4177
- interface FlattenedEquation {
4178
- /** Dot-namespaced LHS variable name (e.g., "Atmos.O3") */
4179
- lhs: string;
4802
+ declare class FlattenError extends EsmDiagnosticError {
4803
+ constructor(message: string, code?: string);
4804
+ }
4805
+ /**
4806
+ * Two systems define non-additive equations for the same dependent variable —
4807
+ * the §4.7.5 over-determination check. Such a system is over-determined: one
4808
+ * contribution to `d[X]/dt` would silently shadow the other.
4809
+ */
4810
+ declare class ConflictingDerivativeError extends FlattenError {
4811
+ constructor(message: string);
4812
+ }
4813
+ /**
4814
+ * A `couple` connector equation with `transform: "multiplicative"` targets
4815
+ * something with no tendency to multiply.
4816
+ *
4817
+ * esm-spec §10.3 and esm-libraries-spec §4.7.2 define `multiplicative` against
4818
+ * the target's EXISTING ODE right-hand side. When `to` names a parameter, an
4819
+ * observed, an algebraic unknown, or an undefined name, there is no `D(to)` and
4820
+ * the operation has no meaning.
4821
+ *
4822
+ * Silently dropping the connector equation — what this binding did before — is
4823
+ * the one outcome a coupling mis-specification must not have: the document
4824
+ * declares a coupling and the flattened system carries no trace of it.
4825
+ *
4826
+ * `additive` has no counterpart error because zero is the additive identity, so
4827
+ * an additive term against an absent tendency simply becomes the tendency.
4828
+ */
4829
+ declare class CoupleMultiplicativeNoTendencyError extends FlattenError {
4830
+ constructor(message: string);
4831
+ }
4832
+ /**
4833
+ * An `operator_compose` entry merged NOTHING (esm-libraries-spec §4.7.1 step 5).
4834
+ *
4835
+ * Such an entry is indistinguishable from one that is not there: the operator
4836
+ * integrates a private, decoupled system from its own defaults, the other system
4837
+ * receives no contribution at all, and the only evidence is a state count one too
4838
+ * high. That is the one outcome a coupling mis-specification must not have, so it
4839
+ * is refused rather than reported.
4840
+ *
4841
+ * An operator that genuinely contributes only states of its own — a transport
4842
+ * operator whose single equation defines its own wind field, say — says so with
4843
+ * `require_match: false`, and is then permitted.
4844
+ */
4845
+ declare class OperatorComposeNoMergeError extends FlattenError {
4846
+ constructor(message: string);
4847
+ }
4848
+ /**
4849
+ * An `operator_compose` entry declared `require_match: true` and one of
4850
+ * `systems[1]`'s equations found no equation of `systems[0]` to land on
4851
+ * (esm-libraries-spec §4.7.1 step 5).
4852
+ *
4853
+ * Step 5 otherwise preserves an unmatched equation unchanged, and a PARTIAL
4854
+ * shortfall is only a warning by default; `require_match` is the author's opt-in
4855
+ * to make it fatal. A PARTIAL match throws here — there is no "some is enough"
4856
+ * reading an author could rely on.
4857
+ */
4858
+ declare class OperatorComposeRequireMatchError extends FlattenError {
4859
+ constructor(message: string);
4860
+ }
4861
+ /**
4862
+ * The bare-name fallback would unify two STATE variables and the document has
4863
+ * not said which spelling survives (esm-libraries-spec §4.7.1 step 3).
4864
+ *
4865
+ * The fallback binds `A.x` to `B.x` on the strength of a shared local name alone.
4866
+ * When both are states, each carries its own INITIAL CONDITION, and the merge has
4867
+ * to delete one of them — so the choice decides which IC the flattened system
4868
+ * integrates from. Nothing in the document expresses that choice, and picking one
4869
+ * silently is how flipping the entry's `systems` order came to change the answer.
4870
+ *
4871
+ * A match where only ONE side is a state is NOT ambiguous: the other carries no
4872
+ * initial condition, so the state is the owner and the merge renames onto it.
4873
+ */
4874
+ declare class OperatorComposeAmbiguousBareNameError extends FlattenError {
4875
+ constructor(message: string);
4876
+ }
4877
+ /**
4878
+ * An `identity`-transform `variable_map` bridges two variables whose declared,
4879
+ * non-empty units differ (esm-libraries-spec §4.7.6). `conversion_factor` and
4880
+ * `param_to_var` are exempt: the first declares the conversion explicitly, the
4881
+ * second does not imply unit equivalence at the mapping site.
4882
+ */
4883
+ declare class DomainUnitMismatchError extends FlattenError {
4884
+ constructor(message: string);
4885
+ }
4886
+ /**
4887
+ * A variable or equation cannot be promoted onto the target grid — raised by the
4888
+ * §10.5 pointwise spatial lift when a species' operator makearrays yield no
4889
+ * full-rank interior-stencil gather.
4890
+ */
4891
+ declare class DimensionPromotionError extends FlattenError {
4892
+ constructor(message: string);
4893
+ }
4894
+ /**
4895
+ * The DERIVED role of a flattened variable (esm-spec §6.3.1) — never a declared
4896
+ * type, which from 1.0.0 is only `unknown` or `parameter`.
4897
+ *
4898
+ * - `state` — solved for and defined by no equation of its own: an ODE state or
4899
+ * an algebraic unknown.
4900
+ * - `observed` — an unknown an equation DEFINES, at either LHS spelling. A
4901
+ * scalar one is eliminable by inlining and appears in `observedVariables`
4902
+ * alone; an arrayed one materializes into a buffer and appears in
4903
+ * `stateVariables` too, keeping this role in both (esm-libraries-spec §4.7.5
4904
+ * step 4: `stateVariables` is the solved-for vector, not a classification).
4905
+ * - `parameter` — a parameter of any cadence.
4906
+ * - `species` — a reaction-system state (a `state` that came from a species).
4907
+ */
4908
+ type FlattenedVariableRole = 'state' | 'parameter' | 'observed' | 'species';
4909
+ /**
4910
+ * One variable of the flattened system, carrying the COMPLETE declared metadata
4911
+ * step 4 requires ("Full metadata, not names"): a consumer must be able to build
4912
+ * a solver problem from the flattened form alone, without re-reading the source
4913
+ * document.
4914
+ */
4915
+ interface FlattenedVariable {
4916
+ /** Dot-namespaced name, e.g. `"OU.theta"`. */
4917
+ name: string;
4918
+ /** The DERIVED role — see {@link FlattenedVariableRole}. */
4919
+ type: FlattenedVariableRole;
4920
+ units?: string;
4921
+ /** For an unknown, its value at t=0; for a parameter, its constant value. */
4922
+ default?: number;
4923
+ description?: string;
4924
+ /** The namespaced prefix of the component that declared it. */
4925
+ sourceSystem?: string;
4926
+ /** Ordered index-set names for an arrayed variable; absent means scalar. */
4927
+ shape?: string[];
4180
4928
  /**
4181
- * Expression string with namespaced references.
4182
- *
4183
- * For equations produced from a `couple` connector, the string uses a
4184
- * `transform(expr)` pseudo-function convention: the connector's `transform`
4185
- * (`"additive"` / `"multiplicative"` / `"replacement"`) is emitted as the
4186
- * function name wrapping the (already-scoped) connector expression — e.g.
4187
- * `additive(A.x)`. These are provenance/intent markers, not scalar operators
4188
- * in {@link OPS}; a downstream consumer interprets the wrapper.
4189
- */
4190
- rhs: string;
4191
- /** Name of the source system this equation originated from */
4192
- sourceSystem: string;
4929
+ * The declared cadence machinery (parameters only), with its expression
4930
+ * slots — a `condition` / `crossing` `when` and the value `expression` —
4931
+ * namespaced like any other reference (§4.7.5 step 2).
4932
+ */
4933
+ update?: ParameterUpdateSpec;
4934
+ /** The declared sampling law, carried verbatim (parameters only). */
4935
+ distribution?: Distribution;
4936
+ }
4937
+ /**
4938
+ * An insertion-ORDERED map from namespaced name to variable. Order is part of
4939
+ * the cross-binding contract (see the module doc); JavaScript preserves the
4940
+ * insertion order of string keys that do not look like array indices, which a
4941
+ * dot-namespaced variable name never does.
4942
+ */
4943
+ type FlattenedVariableMap = Record<string, FlattenedVariable>;
4944
+ /**
4945
+ * A data-fed PARAMETER lowered to a flattened array input (esm-spec §8.5).
4946
+ *
4947
+ * From 1.0.0 a data source is not a component: a model consumes one by declaring
4948
+ * a parameter whose `update` is `{kind: "data", source, from: {file_variable}}`.
4949
+ * The parameter IS the loaded field and owns the units; this descriptor is what
4950
+ * lets a simulator execute the source at its cadence and bind the resulting
4951
+ * array into the RHS as a read-only input.
4952
+ */
4953
+ interface LoaderField {
4954
+ /** The namespaced parameter symbol, e.g. `"Plume.wind"`. */
4955
+ name: string;
4956
+ /** The owning component's namespaced prefix, e.g. `"Plume"`. */
4957
+ owner: string;
4958
+ /** The `data_sources` key the parameter's `update` names. */
4959
+ subkey: string;
4960
+ /** The source-file variable the binding names. */
4961
+ var: string;
4962
+ /** The resolved `data_sources` entry (carries kind / source / temporal). */
4963
+ dataSource: DataSource;
4964
+ /**
4965
+ * Source-seeded cadence (CONFORMANCE_SPEC §5.7.2): a source WITH a `temporal`
4966
+ * block is time-varying (`discrete`); one without is read once (`const`).
4967
+ */
4968
+ cadence: 'const' | 'discrete';
4969
+ /** The binding's declared `unit_conversion` (§8.5), when the document has one. */
4970
+ unitConversion?: Expression;
4193
4971
  }
4194
4972
  /**
4195
- * Metadata describing the origin of the flattened system.
4973
+ * A single equation of the flattened system, with dot-namespaced Expression
4974
+ * TREES.
4975
+ *
4976
+ * ### Breaking change in `esm 1.0.0`
4977
+ *
4978
+ * `lhs` / `rhs` used to be pretty-printed STRINGS produced by a flatten-local,
4979
+ * fully-parenthesizing printer. They are now the canonical Expression AST, which
4980
+ * is what every other binding carries and what the shared corpus pins (rendered
4981
+ * through the shared `toAscii`). A caller that wants text calls `toAscii` on
4982
+ * them — one renderer for the whole toolkit rather than a second, subtly
4983
+ * different one living here.
4196
4984
  */
4985
+ interface FlattenedEquation {
4986
+ /** Dot-namespaced LHS, e.g. `D(Atmos.O3, t)` or a bare `Atmos.total`. */
4987
+ lhs: Expression;
4988
+ /** Dot-namespaced RHS, with coupling contributions merged in. */
4989
+ rhs: Expression;
4990
+ /** Name of the source system this equation originated from. */
4991
+ sourceSystem: string;
4992
+ }
4993
+ /** Metadata describing the origin of the flattened system. */
4197
4994
  interface FlattenMetadata {
4198
- /** Names of all source systems that were flattened */
4995
+ /** Names of all top-level source systems, in document order. */
4199
4996
  sourceSystems: string[];
4200
- /** Human-readable descriptions of coupling rules applied */
4997
+ /** Human-readable descriptions of every coupling rule, in array order. */
4201
4998
  couplingRules: string[];
4999
+ /** `operator_apply` entries, recorded as opaque runtime references. */
5000
+ operatorApplies: string[];
5001
+ /** `callback` entries, recorded as opaque runtime references. */
5002
+ callbacks: string[];
5003
+ /**
5004
+ * Every state spelling an `operator_compose` renaming match DELETED, mapped
5005
+ * onto the survivor it was folded into (issue #230).
5006
+ *
5007
+ * The flattener's own retarget reaches equation ASTs; this is the map a
5008
+ * CONSUMER that addresses a state by name needs — a `parameter_overrides` /
5009
+ * `initial_conditions` key, an output selection — to resolve a spelling the
5010
+ * merge moved out from under it. Empty for a document with no renaming merge,
5011
+ * which is the overwhelming majority.
5012
+ */
5013
+ mergedVariableRenames: Record<string, string>;
5014
+ /**
5015
+ * Every name a COUPLING rule rewrote OUT of the flattened equations: a
5016
+ * `variable_map`'s substituted target, plus every merged-away spelling of
5017
+ * {@link FlattenMetadata.mergedVariableRenames}.
5018
+ *
5019
+ * Only the registry guard reads it. A surviving `expression_templates` body
5020
+ * is authored source that no equation walk reaches, so a body naming one of
5021
+ * these is REFUSED rather than resolved (see `checkRegistryCouplingRewrites`,
5022
+ * CONFORMANCE_SPEC §5.35). Unlike `mergedVariableRenames` this is not a
5023
+ * consumer-facing map: it says a name is GONE, not where it went.
5024
+ */
5025
+ couplingRewrittenNames: Set<string>;
5026
+ }
5027
+ /** A deferred `ic` equation (esm-spec §11.4.1): an initial condition, not dynamics. */
5028
+ interface FieldInitialCondition {
5029
+ /** The namespaced state the condition pins. */
5030
+ state: string;
5031
+ /** The value at t=0. */
5032
+ expr: Expression;
4202
5033
  }
4203
5034
  /**
4204
- * A fully flattened representation of a coupled ESM system.
5035
+ * A fully flattened representation of a coupled ESM system — the canonical
5036
+ * field set of esm-libraries-spec §4.7.5 step 4.
5037
+ *
5038
+ * ### Removed in `esm 1.0.0`
5039
+ *
5040
+ * `variables: Record<string, string>` (observed name -> expression string) is
5041
+ * gone. It predates the canonical shape and duplicated, in a bespoke string
5042
+ * form, information the canonical fields already carry: WHICH unknowns are
5043
+ * observed is {@link observedVariables} (with full metadata rather than a bare
5044
+ * name), and each one's DEFINING EXPRESSION is its equation in
5045
+ * {@link equations}, as an AST. The coupling-derived entries it also collected
5046
+ * are no longer a side table: a `variable_map` now performs the substitution and
5047
+ * the promotion the spec calls for, so its effect is visible in `equations` and
5048
+ * `parameters` instead. Nothing it held is lost.
5049
+ *
5050
+ * Python exposes a `variables` property with a DIFFERENT meaning (namespaced
5051
+ * name -> role label); this binding deliberately does not reuse the name for a
5052
+ * third thing.
4205
5053
  */
4206
5054
  interface FlattenedSystem {
4207
- /** All state variable names (dot-namespaced) */
4208
- stateVariables: string[];
4209
- /** All parameter names (dot-namespaced) */
4210
- parameters: string[];
4211
- /** All brownian (Wiener) noise variables (dot-namespaced). Any brownian => SDE system. */
4212
- brownianVariables: string[];
4213
- /** Observed/derived variables: namespaced name -> expression string */
4214
- variables: Record<string, string>;
4215
- /** All equations from all systems, with namespaced references */
5055
+ /**
5056
+ * Always contains `"t"`. A spatial axis appears only while an UNDISCRETIZED
5057
+ * spatial differential still names it, so a discretized (array) system stays a
5058
+ * pure ODE.
5059
+ */
5060
+ independentVariables: string[];
5061
+ /**
5062
+ * The SOLVED-FOR VECTOR: every unknown the solver advances or solves for —
5063
+ * differential unknowns, PLUS {@link algebraicVariables}, PLUS any arrayed
5064
+ * observed that materializes into a buffer. NOT esm-spec §6.3.1's `odeStates`.
5065
+ */
5066
+ stateVariables: FlattenedVariableMap;
5067
+ /** ALL parameters of every cadence, minus any promoted by `variable_map`. */
5068
+ parameters: FlattenedVariableMap;
5069
+ /** Unknowns DEFINED by an equation naming them on its LHS (esm-spec §6.3.1). */
5070
+ observedVariables: FlattenedVariableMap;
5071
+ /**
5072
+ * Unknowns CONSTRAINED only by an expression-LHS equation. A SUBSET of
5073
+ * {@link stateVariables} — a DAE solves for them.
5074
+ */
5075
+ algebraicVariables: FlattenedVariableMap;
5076
+ /**
5077
+ * Parameters whose `update.kind` is `"wiener"` — the SDE noise sources. A
5078
+ * SUBSET of {@link parameters}, not a sibling bucket (esm-spec §6.3.1).
5079
+ */
5080
+ brownianParameters: FlattenedVariableMap;
5081
+ /** Parameters carrying any OTHER `update`. A SUBSET of {@link parameters}. */
5082
+ discreteParameters: FlattenedVariableMap;
5083
+ /**
5084
+ * The governing equations — dynamics and constraints — coupling applied,
5085
+ * dot-namespaced. Entries classified out into {@link fieldIcs} are REMOVED.
5086
+ */
4216
5087
  equations: FlattenedEquation[];
4217
- /** Provenance metadata */
5088
+ /** Continuous events, dot-namespaced. */
5089
+ continuousEvents: ContinuousEvent[];
5090
+ /** Discrete events, dot-namespaced. */
5091
+ discreteEvents: DiscreteEvent[];
5092
+ /** The file's `domain` section, unchanged, or `null`. */
5093
+ domain: Domain | null;
5094
+ /** Provenance metadata. */
4218
5095
  metadata: FlattenMetadata;
5096
+ /** Document-scoped index-set registry; required to interpret arrayed equations. */
5097
+ indexSets: Record<string, unknown>;
5098
+ /** Merged function-table registry; resolves `table_lookup`. */
5099
+ functionTables: Record<string, unknown>;
5100
+ /**
5101
+ * The MERGED expression-template registry (esm-spec §9.6.4 rule 7, §10.7):
5102
+ * the union of the component registries with their bodies component-scoped
5103
+ * FIRST, deep-equal same-name entries deduplicated at first occurrence, and
5104
+ * non-deep-equal collisions renamed along the reference DAG.
5105
+ */
5106
+ templateRegistry: Record<string, unknown>;
5107
+ /** Deferred scoped-reference / array `ic` equations (esm-spec §11.4.1). */
5108
+ fieldIcs: FieldInitialCondition[];
5109
+ /** Provider-served loaded fields the system consumes. */
5110
+ loaderFields: LoaderField[];
5111
+ /** Post-lift grid shapes for arrayed states, e.g. `{ "Chemistry.O3": [4, 2] }`. */
5112
+ liftedShapes: Record<string, number[]>;
5113
+ /**
5114
+ * The system kind DERIVED from the FLATTENED system (esm-spec §6.3.1),
5115
+ * testing `brownianParameters` FIRST — which is exactly why that bucket must
5116
+ * survive flattening.
5117
+ */
5118
+ systemKind: SystemKind;
4219
5119
  }
4220
5120
  /**
4221
- * Flatten a multi-system ESM file into a single unified system.
5121
+ * Flatten a coupled multi-system ESM file into a single unified system
5122
+ * (esm-libraries-spec §4.7.5).
4222
5123
  *
4223
- * The algorithm:
4224
- * 1. Iterates over all models and reaction_systems in the file
4225
- * 2. Namespaces all variables with their system name prefix (dot notation)
4226
- * 3. Processes coupling entries to produce variable mappings and connector equations
4227
- * 4. Returns a unified flattened system
5124
+ * The result is the canonical intermediate representation: dot-namespaced
5125
+ * variables carrying their full declared metadata, equations as Expression trees,
5126
+ * coupling rules resolved INTO the equation set, and the registries needed to
5127
+ * consume it without re-reading the source document.
4228
5128
  *
4229
- * @param file - The ESM file to flatten
4230
- * @returns A FlattenedSystem with all variables namespaced and equations unified
5129
+ * @throws {FlattenError} when the file has no models and no reaction systems, or
5130
+ * when a variable carries a type esm 1.0.0 removed.
5131
+ * @throws {ConflictingDerivativeError} when two source systems define
5132
+ * non-additive equations for the same dependent variable.
5133
+ * @throws {CoupleMultiplicativeNoTendencyError} when a `couple` connector
5134
+ * equation with `transform: "multiplicative"` targets something with no
5135
+ * `D(to)` tendency to multiply (esm-spec §10.3).
5136
+ * @throws {DomainUnitMismatchError} when an `identity` `variable_map` bridges two
5137
+ * variables whose declared, non-empty units differ.
4231
5138
  */
4232
5139
  declare function flatten(file: EsmFile, options?: FlattenOptions): FlattenedSystem;
4233
5140
 
@@ -4255,7 +5162,7 @@ declare function flatten(file: EsmFile, options?: FlattenOptions): FlattenedSyst
4255
5162
  /**
4256
5163
  * Error thrown when a circular reference is detected during subsystem resolution.
4257
5164
  */
4258
- declare class CircularReferenceError extends Error {
5165
+ declare class CircularReferenceError extends EsmDiagnosticError {
4259
5166
  /** The chain of references that form the cycle */
4260
5167
  readonly chain: string[];
4261
5168
  constructor(chain: string[]);
@@ -4275,10 +5182,13 @@ declare class CircularReferenceError extends Error {
4275
5182
  * only layer that can tell those two apart — the synchronous `validate()` does
4276
5183
  * no I/O and reports every unresolved `{ref}` as `unresolved_subsystem_ref`.
4277
5184
  */
4278
- declare class RefLoadError extends Error {
5185
+ declare class RefLoadError extends EsmDiagnosticError {
4279
5186
  /** The reference path or URL that failed to load */
4280
5187
  readonly ref: string;
4281
- /** Canonical code: `unresolved_subsystem_ref` or `ambiguous_subsystem_ref`. */
5188
+ /**
5189
+ * Canonical code: `unresolved_subsystem_ref`, `ambiguous_subsystem_ref`, or
5190
+ * `mount_form_unsupported` for a mount form this binding does not implement.
5191
+ */
4282
5192
  readonly code: string;
4283
5193
  /**
4284
5194
  * JSON Pointer of the SUBSYSTEM ENTRY that carries the bad ref (e.g.
@@ -4300,7 +5210,7 @@ declare class RefLoadError extends Error {
4300
5210
  * cycle detection and component inlining exist in exactly one place, so the sync
4301
5211
  * and async paths cannot drift apart.
4302
5212
  */
4303
- declare function resolveSubsystemRefs(file: EsmFile, basePath: string): Promise<void>;
5213
+ declare function resolveSubsystemRefs(file: EsmFile, basePath: string, loaderMetaparameters?: Readonly<Record<string, number>>): Promise<void>;
4304
5214
  /**
4305
5215
  * esm-spec §9.7.10 form C: build a throwaway `EsmFile` in which component
4306
5216
  * `mname` has the run's `imports` (raw §9.7.2 entries) appended to its own
@@ -4313,7 +5223,7 @@ declare function resolveSubsystemRefs(file: EsmFile, basePath: string): Promise<
4313
5223
  * The raw base is re-read from `sourcePath` when given (relative import `ref`s
4314
5224
  * resolve against its directory), else re-serialized from `file`; `baseDir`
4315
5225
  * anchors the injected `ref`s. Mirrors the Julia reference
4316
- * `_ephemeral_injected_file` (`EarthSciAST.jl/src/pde_inline_tests.jl`).
5226
+ * `_ephemeral_injected_file` (`EarthSciAST.jl/src/inline_tests.jl`).
4317
5227
  *
4318
5228
  * This binding does not numerically simulate PDEs; the ephemeral build is the
4319
5229
  * structural-lowering half of form C (the leaf's rewrite-target is lowered in
@@ -4321,6 +5231,181 @@ declare function resolveSubsystemRefs(file: EsmFile, basePath: string): Promise<
4321
5231
  */
4322
5232
  declare function ephemeralInjectedFile(file: EsmFile | null, sourcePath: string | null, mname: string, imports: readonly unknown[], baseDir: string): Promise<EsmFile>;
4323
5233
 
5234
+ /**
5235
+ * Build-time reference resolution for the semiring-FAQ unified IR: the
5236
+ * intra-document node-id / index-set dependency DAG.
5237
+ *
5238
+ * Not to be confused with `./ref-loading.js`, which inlines cross-file
5239
+ * `{ "ref": ... }` subsystem mounts at load time — this module never touches
5240
+ * the filesystem; it wires id-addressed edges inside ONE document.
5241
+ *
5242
+ * It implements *node addressing* and *reference-edge resolution* — the hard
5243
+ * prerequisite the §6.1 cadence-partition pass of the
5244
+ * `semiring-faq-unified-ir` RFC calls out:
5245
+ *
5246
+ * > "node addressing — referencing a node by id — is a hard prerequisite: the
5247
+ * > pass cannot be built until `from_faq` and join references are real edges
5248
+ * > in this DAG."
5249
+ *
5250
+ * Three kinds of name/id reference become real, queryable graph edges
5251
+ * (RFC §6.1 "Propagation"):
5252
+ *
5253
+ * - an aggregate node → an index set it iterates (`ranges[*].from`);
5254
+ * - a `kind: "derived"` index set → its `from_faq` node (by stable id);
5255
+ * - an aggregate `join.on` factor → the factor it names.
5256
+ *
5257
+ * Like the Julia, Python, Rust and Go passes, this one walks the RAW parsed
5258
+ * document rather than the typed layer: `index_sets`, node `id`,
5259
+ * `ranges[*].from` and `join` are exactly the fields the typed layer drops, so
5260
+ * working the raw shape is what keeps the five bindings in step.
5261
+ *
5262
+ * PORTED FROM the Python binding (`earthsci_ast/reference_resolution.py`),
5263
+ * which is the most complete of the four: it registers every node BEFORE
5264
+ * resolving any reference (a two-step walk), so a `join.on` or `from_faq` may
5265
+ * name a node that appears later in the document. Rust's single-pass
5266
+ * `register_and_process` cannot do that. Vertex keys, edge kinds, error codes
5267
+ * and message shapes follow Python byte for byte.
5268
+ */
5269
+
5270
+ /**
5271
+ * A reference could not be resolved, or the reference graph has a cycle.
5272
+ *
5273
+ * Carries the stable `code` (one of the `E_REF_*` constants above) so callers
5274
+ * and the cross-binding conformance suite can assert on the failure mode. For a
5275
+ * cycle, `cycle` holds the offending vertex-key path.
5276
+ */
5277
+ declare class ReferenceResolutionError extends EsmDiagnosticError {
5278
+ code: string;
5279
+ readonly cycle: string[] | undefined;
5280
+ constructor(code: string, message: string, cycle?: string[]);
5281
+ }
5282
+ /** The three kinds of vertex in the reference graph. */
5283
+ declare const VertexKind: {
5284
+ readonly NODE: "node";
5285
+ readonly INDEX_SET: "index_set";
5286
+ readonly FACTOR: "factor";
5287
+ };
5288
+ type VertexKind = (typeof VertexKind)[keyof typeof VertexKind];
5289
+ /** The three kinds of reference edge (RFC §6.1 "Propagation"). */
5290
+ declare const EdgeKind: {
5291
+ /** aggregate node → the index set it iterates (`ranges[*].from`). */
5292
+ readonly RANGE_FROM: "range_from";
5293
+ /** `kind: "derived"` index set → the node that materializes it (`from_faq`). */
5294
+ readonly FROM_FAQ: "from_faq";
5295
+ /** aggregate node → a factor named by `join.on`. */
5296
+ readonly JOIN_FACTOR: "join_factor";
5297
+ };
5298
+ type EdgeKind = (typeof EdgeKind)[keyof typeof EdgeKind];
5299
+ /**
5300
+ * A vertex in the reference graph, addressed by a kind-namespaced `key`.
5301
+ *
5302
+ * `key` is `` `${kind}:${name}` ``. For a `node` vertex, `name` is the node's
5303
+ * stable address: its explicit `id` when it has one, else its structural path
5304
+ * (e.g. `equations/0/rhs/expr`).
5305
+ */
5306
+ interface ReferenceVertex {
5307
+ key: string;
5308
+ kind: VertexKind;
5309
+ name: string;
5310
+ op?: string;
5311
+ nodeId?: string;
5312
+ path?: string;
5313
+ }
5314
+ /** A directed `source → target` edge: *source references / depends on target*. */
5315
+ interface ReferenceEdge {
5316
+ source: string;
5317
+ target: string;
5318
+ kind: EdgeKind;
5319
+ }
5320
+ type RawObject = Record<string, unknown>;
5321
+ /**
5322
+ * The resolved reference DAG for one model — the partition pass's input.
5323
+ *
5324
+ * Vertices are keyed by their kind-namespaced `key`. Edges point from a vertex
5325
+ * to a vertex it *depends on*, so a bottom-up {@link topologicalOrder} walk
5326
+ * visits each vertex after its dependencies — exactly the order
5327
+ * `class(n) = max(class(inputs))` propagation needs.
5328
+ */
5329
+ declare class ReferenceGraph {
5330
+ readonly model: string;
5331
+ /** Insertion-ordered `key -> vertex`. */
5332
+ readonly vertices: Map<string, ReferenceVertex>;
5333
+ readonly edges: ReferenceEdge[];
5334
+ private readonly out;
5335
+ private readonly inn;
5336
+ constructor(model?: string);
5337
+ /** @internal */
5338
+ ensureVertex(vertex: ReferenceVertex): void;
5339
+ /** @internal */
5340
+ addEdge(source: string, target: string, kind: EdgeKind): void;
5341
+ /** Vertices `key` references / depends on (its out-neighbours). */
5342
+ dependencies(key: string): string[];
5343
+ /** Vertices that reference / depend on `key` (its in-neighbours). */
5344
+ dependents(key: string): string[];
5345
+ edgesOfKind(kind: EdgeKind): ReferenceEdge[];
5346
+ /**
5347
+ * A reference cycle as a vertex-key path, or `null` if acyclic.
5348
+ *
5349
+ * Three-colour DFS over the dependency edges, traversing sorted keys and
5350
+ * sorted neighbours so the reported path is deterministic and matches the
5351
+ * other bindings. The returned path is `[v, …, v]` (the repeated vertex
5352
+ * closes the cycle).
5353
+ */
5354
+ detectCycle(): string[] | null;
5355
+ /**
5356
+ * Bottom-up order (dependencies before dependents).
5357
+ *
5358
+ * Throws {@link ReferenceResolutionError} (`E_REF_CYCLE`) if the graph is
5359
+ * cyclic — a cycle among reference edges is an out-of-scope
5360
+ * implicit/iterative solve (RFC §6.1 "Acyclicity").
5361
+ */
5362
+ topologicalOrder(): string[];
5363
+ }
5364
+ /**
5365
+ * Resolve the reference edges of ONE `model` into a {@link ReferenceGraph}.
5366
+ *
5367
+ * @param model - the raw model object (as parsed, not the typed layer)
5368
+ * @param modelName - the model's key in the document, used in diagnostics
5369
+ * @param indexSets - the DOCUMENT-SCOPED `index_sets` registry. `esm-schema.json`
5370
+ * declares `index_sets` only at document scope: since v0.8.0 it is a sibling
5371
+ * of `models` rather than nested inside each model, and 1.0.0 keeps it there.
5372
+ * {@link resolveReferences} reads it once from the document root and threads
5373
+ * it into every model, which is how a caller normally arrives here. It is an
5374
+ * OPTIONAL TRAILING argument — not a separate function, and not required
5375
+ * (API_SPEC.md §8 item 17). A model-nested `index_sets` key (pre-0.8.0) is
5376
+ * merged ON TOP, so a model-level entry wins a key collision, matching Go and
5377
+ * Rust exactly. Reading ONLY the nested key is the Julia bug §8 item 17
5378
+ * records — it threw on any 1.0.0 document with a top-level registry — and
5379
+ * this binding does not reproduce it.
5380
+ *
5381
+ * Throws {@link ReferenceResolutionError} on a duplicate node id, an undeclared
5382
+ * `ranges[*].from` index set, a `from_faq` naming no node, or an unresolved
5383
+ * `join.on` factor. Cycles are reported lazily by
5384
+ * {@link ReferenceGraph.topologicalOrder}, or eagerly by
5385
+ * {@link resolveReferences}.
5386
+ *
5387
+ * `from_faq` resolves at DOCUMENT scope (esm-spec.md §9.7.5). A caller holding
5388
+ * one model gets that model as its own document, which is the right answer for
5389
+ * a one-model document; {@link resolveReferences} is the document-scoped entry
5390
+ * point and resolves against every model's nodes.
5391
+ */
5392
+ declare function buildReferenceGraph(model: Model | RawObject, modelName?: string, indexSets?: Record<string, unknown>): ReferenceGraph;
5393
+ /**
5394
+ * Resolve reference edges for EVERY model in `document`.
5395
+ *
5396
+ * Returns a `modelName -> ReferenceGraph` map. Throws
5397
+ * {@link ReferenceResolutionError} on any unresolved reference *or* reference
5398
+ * cycle (each model's graph is checked acyclic eagerly here).
5399
+ *
5400
+ * The document-scoped `index_sets` registry is read once from the top level and
5401
+ * threaded into every model, so a caller never assembles it by hand.
5402
+ *
5403
+ * Node ids are collected from EVERY model first, so a derived index set's
5404
+ * `from_faq` may name a producer in any model (esm-spec.md §9.7.5) and a
5405
+ * duplicate id anywhere in the document is an error.
5406
+ */
5407
+ declare function resolveReferences(document: EsmFile | RawObject): Map<string, ReferenceGraph>;
5408
+
4324
5409
  /**
4325
5410
  * Canonical AST form per discretization RFC §5.4.
4326
5411
  *
@@ -4356,7 +5441,7 @@ declare function canonicalJson(expr: Expr): string;
4356
5441
  * emitted as bare JSON integers by {@link canonicalJson} directly.
4357
5442
  *
4358
5443
  * NAMING: distinct from `formatFloatToken` in `./numeric-literal` (the
4359
- * document-serialization emitter used by `save()` / `losslessJsonStringify`).
5444
+ * document-serialization emitter used by `toJson()` / `losslessJsonStringify`).
4360
5445
  * This canonical version additionally NORMALIZES exponent notation — it strips
4361
5446
  * the leading `+` (RFC §5.4.6: "no leading + on the exponent") so `1e25` emits
4362
5447
  * as `1e25`, not `1e+25`, and forces the §5.4.6 exponent thresholds. Use this
@@ -4381,13 +5466,14 @@ declare function formatCanonicalFloat(f: number): string;
4381
5466
  * reference implementation (pkg/EarthSciAST.jl/src/registered_functions.jl).
4382
5467
  */
4383
5468
  /** Stable diagnostic codes raised by the registry. */
5469
+
4384
5470
  type ClosedFunctionErrorCode = 'unknown_closed_function' | 'closed_function_arity' | 'closed_function_overflow' | 'searchsorted_non_monotonic' | 'searchsorted_nan_in_table' | 'interp_non_monotonic_axis' | 'interp_axis_length_mismatch' | 'interp_nan_in_axis' | 'interp_axis_too_short' | 'interp_table_not_const' | 'interp_axis_not_const';
4385
5471
  /**
4386
5472
  * Error thrown by closed function dispatch and load-time table validation.
4387
5473
  * `code` identifies the spec-pinned diagnostic; cross-binding harnesses
4388
5474
  * compare against this exact string.
4389
5475
  */
4390
- declare class ClosedFunctionError extends Error {
5476
+ declare class ClosedFunctionError extends EsmDiagnosticError {
4391
5477
  code: ClosedFunctionErrorCode;
4392
5478
  constructor(code: ClosedFunctionErrorCode, message: string);
4393
5479
  }
@@ -4427,7 +5513,19 @@ declare function interpLinear(table: readonly number[], axis: readonly number[],
4427
5513
  * `table[i][j]` is the value at `(axis_x[i], axis_y[j])`.
4428
5514
  */
4429
5515
  declare function interpBilinear(table: readonly (readonly number[])[], axisX: readonly number[], axisY: readonly number[], x: number, y: number): number;
4430
- /** Names that bindings MUST recognize (derived from the dispatch table keys). */
5516
+ /**
5517
+ * Names that bindings MUST recognize (derived from the dispatch table keys).
5518
+ *
5519
+ * A FUNCTION, matching Julia's `closed_function_names()`, Rust's
5520
+ * `closed_function_names()` and Go's `ClosedFunctionNames()` — API_SPEC.md §8
5521
+ * item 4. TypeScript alone exposed this as a constant array, so a caller
5522
+ * writing binding-agnostic code had to special-case it.
5523
+ */
5524
+ declare function closedFunctionNames(): readonly string[];
5525
+ /**
5526
+ * @deprecated Use {@link closedFunctionNames}. Kept for one minor per
5527
+ * API_SPEC.md §10; removed at the next major.
5528
+ */
4431
5529
  declare const CLOSED_FUNCTION_NAMES: readonly string[];
4432
5530
  /**
4433
5531
  * Resolve a closed-function name + already-evaluated positional args into a
@@ -4454,13 +5552,13 @@ declare function dispatchClosedFunction(name: string, args: unknown[]): number;
4454
5552
  * enum):
4455
5553
  * - `enum_op_malformed` — an `enum` op whose args are not
4456
5554
  * `[enum_name, member_name]` (two strings).
4457
- * - `enum_not_declared` — reference to an enum name not present in the file's
4458
- * top-level `enums` block.
4459
- * - `enum_member_not_found` — reference to an unknown member of a declared
4460
- * enum.
5555
+ * - `unknown_enum` — reference to an enum name not present in the file's
5556
+ * top-level `enums` block (esm-spec §4.5).
5557
+ * - `unknown_enum_symbol` — reference to an unknown member of a declared
5558
+ * enum (esm-spec §4.5).
4461
5559
  */
4462
5560
 
4463
- declare class EnumLoweringError extends Error {
5561
+ declare class EnumLoweringError extends EsmDiagnosticError {
4464
5562
  code: string;
4465
5563
  constructor(code: string, message: string);
4466
5564
  }
@@ -4471,19 +5569,6 @@ declare class EnumLoweringError extends Error {
4471
5569
  */
4472
5570
  declare function lowerEnums(file: EsmFile): EsmFile;
4473
5571
 
4474
- /**
4475
- * Neutral base for EarthSciAST diagnostics: an `Error` carrying a stable `code`
4476
- * string (from {@link ERROR_CODES}) and optional structured `details`. This is
4477
- * purely additive — a single home for future diagnostics. It intentionally does
4478
- * NOT touch the existing `EsmMachineryError` / `EnumLoweringError` /
4479
- * `ClosedFunctionError` classes, which another file owns.
4480
- */
4481
- declare class EsmDiagnosticError extends Error {
4482
- readonly code: string;
4483
- readonly details?: Record<string, unknown> | undefined;
4484
- constructor(code: string, message: string, details?: Record<string, unknown> | undefined);
4485
- }
4486
-
4487
5572
  /**
4488
5573
  * Load-time rewrite pass for `expression_templates` (esm-spec §9.6 /
4489
5574
  * docs/rfcs/ast-expression-templates.md, docs/rfcs/open-op-namespace-fixpoint-rewrite.md).
@@ -4697,9 +5782,25 @@ interface TemplateResolveOptions {
4697
5782
  /**
4698
5783
  * Schema validator applied to each import target (a target failing schema
4699
5784
  * validation is `template_import_unresolved`, mirroring the Julia
4700
- * reference). Supplied by `load()`; optional for direct/raw use.
5785
+ * reference). Supplied by the `load*` entry points; optional for direct/raw use.
4701
5786
  */
4702
5787
  validateSchema?: ((raw: unknown) => TemplateSchemaError[]) | undefined;
5788
+ /**
5789
+ * Metaparameter names declared by the documents this one MOUNTS (esm-spec
5790
+ * §4.7, either mount form, transitively) — see
5791
+ * {@link collectMountDeclaredMetaparameters}. Widens the §9.7.6 site-4
5792
+ * check: a loader-API binding naming one of these is meaningful even though
5793
+ * THIS document does not declare it, because the mount edge forwards it into
5794
+ * the leaf's own close.
5795
+ */
5796
+ mountDeclared?: ReadonlySet<string> | undefined;
5797
+ /**
5798
+ * This call IS a §4.7 mount edge, so index-set sizes this scope cannot close
5799
+ * stay symbolic for the MOUNTING document's registry to close (§9.7.6 site
5800
+ * 5) instead of being `metaparameter_unbound` here. A root document leaves it
5801
+ * unset.
5802
+ */
5803
+ mountedLeaf?: boolean | undefined;
4703
5804
  }
4704
5805
  /**
4705
5806
  * `expression_template_imports`, top-level `expression_templates`
@@ -4754,5 +5855,70 @@ declare function emitDocument(rawSource: unknown, basePath: string, options?: Te
4754
5855
  */
4755
5856
  declare function applyScopeInjections(raw: unknown, injected?: readonly unknown[]): JsonObject | null;
4756
5857
 
4757
- export { CLOSED_FUNCTION_NAMES, CadenceCycleError, CadenceSeeder, CanonicalNonfiniteError, CanonicalizeError, CircularReferenceError, ClosedFunctionError, DEFAULT_MIN_COMPLEXITY, E_CANONICAL_DIVBY_ZERO, E_CANONICAL_NONFINITE, EntityNotFoundError, EnumLoweringError, EsmMachineryError, EvaluatorError, expandDocument as Expand, ExpressionAnalyzer, ExpressionParseError, EsmMachineryError as ExpressionTemplateError, InvalidDerivativeOrderError, LosslessJsonParseError, MAX_TEMPLATE_EXPANSION_DEPTH, MigrationError, NonDifferentiableExpressionError, ParseError, RefLoadError, SCHEMA_VERSION, SchemaValidationError, UnitConversionError, UnloweredOperatorError, SCHEMA_VERSION as VERSION, VariableInUseError, addContinuousEvent, addCoupling, addDiscreteEvent, addEquation, addReaction, addSpecies, addVariable, algebraicUnknowns, analyzeComplexity, analyzeExpression, applyScopeInjections, authoredTemplateNames, brownianParameters, buildDependencyGraph, buildEmittedDocument, canMigrate, canonicalJson, canonicalize, checkDimensions, classifyComplexity, classifyDocument, classifyModel, compareComplexity, compileExpression, componentExists, componentGraph, component_graph, compose, constantParameters, contains, convertUnits, declaredSystemKind, deriveODEs, detectStabilityIssues, differentiate, discreteParameters, dispatchClosedFunction, emitDocument, emitEsmString, ephemeralInjectedFile, estimateParallelPotential, estimateSavings, evaluateExpression, expandCouplingImports, expandDocument, expressionCadence, expressionGraph, extract, findCommonSubexpressions, findCommonSubexpressionsAcrossExpressions, findCommonSubexpressionsInEsmFile, findCommonSubexpressionsInModel, findCriticalPoints, findDeadVariables, findDependencyChains, findExpensiveSubexpressions, flatten, flattenTemplateRegistries, floatLit, formatCanonicalFloat, formatChemicalName, formatFloatToken, freeParameters, freeVariables, generateFactoredVariableNames, getComponentType, getSupportedMigrationTargets, gradient, groupSubexpressionsByType, higherOrderDerivative, intLit, interpBilinear, interpLinear, isCouplingLibraryDoc, isDifferentiable, isFloatLit, isIntLit, isNumericLiteral, isOdeState, isTemplateLibraryDoc, joinAll, joinCadence, leafCadence, load, losslessJsonParse, losslessJsonStringify, lowerEnums, lowerExpressionTemplates, mapVariable, merge, migrate, numericValue, observedDefinitions, observedUnknowns, odeStates, parameterClass, parameters, parseEquation, parseExpression, parseReaction, parseUnit, parseUnitForConversion, partialDerivatives, productMatrix, rejectExpressionTemplatesPreV04, rejectTemplateImportsPreV08, removeCoupling, removeEquation, removeEvent, removeReaction, removeSpecies, removeVariable, renameVariable, resolveSubsystemRefs, resolveTemplateMachinery, sampledParameters, save, searchsortedFirst, simplify, stoichiometricMatrix, substitute, substituteInEquations, substituteInModel, substituteInReactionSystem, substrateMatrix, systemKind, toAscii, toDot, toJsonGraph, toLatex, toMathML, toMermaid, toUnicode, tryParseUnit, unitsCompatible, unknowns, updateRules, validate, validateInterpAxis, validateSchema, validateSearchsortedTable, validateUnits };
4758
- export type { AffectEquation, Analysis, AnalysisOptions, AnalysisResults, Assertion, Assertion1, CadenceClass, CanonicalDims, ClosedFunctionErrorCode, CommonSubexpression, CompiledExpression, ComplexityMetrics, ComponentGraph, ComponentNode, ConditionUpdate, ConnectorEquation, ContinuousEvent, Coordinate, Coordinate1, CouplingCallback, CouplingCouple, CouplingEdge, CouplingEntry, CouplingEvent, CouplingEvent1, CouplingImport, CouplingImportOptions, CouplingOperatorCompose, CouplingVariableMap, CouplingVariableMap1, CovarianceMatrix, CrossingUpdate, DataSource, DataSourceBinding, DataSourceCodes, DataSourceDeterminism, DataSourceExtent, DataSourceLocation, DataSourceRecordFilter, DataSourceSelect, DataSourceSelectAxis, DataSourceTemporal, DataUpdate, DependencyEdge, DependencyGraph, DependencyNode, DependencyRelation, DerivativeResult, DiscreteEvent, DiscreteEventTrigger, Distribution, Domain, ESMFormat, ESMFormat1, ESMFormat2, EnumDeclaration, Equation, EsmFile, EsmFormat, Expr, ExprNode, ExprNodeOf, Expression, ExpressionLocation, ExpressionNode, ExpressionNode1, ExpressionTemplate, FlattenMetadata, FlattenOptions, FlattenedEquation, FlattenedSystem, FlattenedTemplateRegistries, FunctionTable, FunctionTableAxis, FunctionalUpdate, Graph, IndexSet, IndexSet1, LoadOptions, LognormalDistribution, Metadata, MetaparameterExpression, Metaparameters, Model, ModelClassification, ModelVariable, ModelVariable1, NonWienerParameterUpdate, NormalDistribution, NumericLiteral, Parameter, Parameter1, ParameterClass, ParameterSweep, ParameterUpdate, ParameterUpdateSpec, ParsedUnit, Plot, Plot1, PlotAxis, PlotSeries, PlotValue, Reaction, ReactionSystem, Reference, RemeshUpdate, SaveOptions, ScheduleUpdate, SchemaError, Species, StabilityIssue, StoichiometryEntry, SubsystemRef, SweepDimension, SweepDimension1, SweepRange, SystemKind, TemplateImport, TemplateResolveOptions, TemplateSchemaError, Test, TimeSpan, Tolerance, Tolerance1, Tolerance2, Tolerance3, TranslateTarget, UniformDistribution, UnitResult, UnitWarning, UnknownClass, UpdateValueForm, ValidationError, ValidationResult, VariableKind, VariableNode, WienerUpdate };
5858
+ /**
5859
+ * Document-scoped solver hints (esm-spec §2.2).
5860
+ *
5861
+ * The `solver` block records numerics the document knows about *itself* —
5862
+ * stiffness, integration tolerances, and a splitting hint — which each binding
5863
+ * maps to its own integrator.
5864
+ *
5865
+ * Every field is ADVISORY: a binding may ignore any or all of them and still
5866
+ * conform. Advisory governs the MECHANISM, never the OUTCOME — the
5867
+ * CONFORMANCE_SPEC §5.9 requirement to integrate successfully and agree within
5868
+ * the error band is untouched by this block and is not excused by it.
5869
+ *
5870
+ * This module carries the parts that are NOT advisory: the spec-version gate
5871
+ * (§2.2.4) and the §2.2.2 tolerance resolution order. TypeScript ships no
5872
+ * integrator, so `resolveTolerances` exists for parity and for callers driving
5873
+ * another runtime.
5874
+ */
5875
+
5876
+ /** Binding defaults (API_SPEC §5.8) — the bottom of the §2.2.2 chain. */
5877
+ declare const DEFAULT_RELTOL = 0.0001;
5878
+ declare const DEFAULT_ABSTOL = 0.000001;
5879
+ /**
5880
+ * Reject a top-level `solver` block in a file declaring esm < 1.1.0.
5881
+ *
5882
+ * The block arrives at `esm: 1.1.0`; a document declaring an earlier version
5883
+ * that carries one is rejected with `solver_version_too_old` (esm-spec
5884
+ * §2.2.4). Mirrors `rejectTemplateImportsPreV08`.
5885
+ */
5886
+ declare function rejectSolverPreV11(view: unknown): void;
5887
+ /**
5888
+ * Resolve integration tolerances most-specific first (esm-spec §2.2.2):
5889
+ *
5890
+ * 1. An explicit argument at the call site — wins outright.
5891
+ * 2. Otherwise the document's `solver.abstol` / `solver.reltol`.
5892
+ * 3. Otherwise the binding default (`reltol` 1e-4, `abstol` 1e-6).
5893
+ *
5894
+ * The two resolve INDEPENDENTLY, so a document declaring only `reltol` leaves
5895
+ * `abstol` on the default — the same per-field fall-through §6.6.4 uses.
5896
+ *
5897
+ * These are INTEGRATION tolerances, a different quantity from the `tolerance`
5898
+ * object an assertion is COMPARED at (§6.6.4).
5899
+ */
5900
+ declare function resolveTolerances(solver: Solver | undefined | null, opts?: {
5901
+ abstol?: number;
5902
+ reltol?: number;
5903
+ }): {
5904
+ abstol: number;
5905
+ reltol: number;
5906
+ };
5907
+
5908
+ /**
5909
+ * The package's OWN version — distinct from {@link SCHEMA_VERSION}, which is
5910
+ * the `.esm` FORMAT version this build implements. The two are unrelated
5911
+ * numbers and used to be conflated: `VERSION` meant the schema version here
5912
+ * and the package version in Rust, so the same name read two different
5913
+ * things depending on which binding you were in. `VERSION` is gone; every
5914
+ * binding now exposes exactly `SCHEMA_VERSION` and `LIBRARY_VERSION`.
5915
+ *
5916
+ * package.json is the source of truth. It cannot be imported here —
5917
+ * tsconfig's `rootDir` is `./src`, and a runtime read would break browser
5918
+ * hosts — so the value is mirrored, and `version.test.ts` fails the build if
5919
+ * the mirror drifts. Same arrangement Rust uses for its `SCHEMA_VERSION`.
5920
+ */
5921
+ declare const LIBRARY_VERSION = "0.3.0";
5922
+
5923
+ export { CLOSED_FUNCTION_NAMES, CadenceCycleError, CadenceSeeder, CanonicalNonfiniteError, CanonicalizeError, CircularReferenceError, ClosedFunctionError, ConflictingDerivativeError, CoupleMultiplicativeNoTendencyError, DEFAULT_ABSTOL, DEFAULT_MIN_COMPLEXITY, DEFAULT_RELTOL, DimensionPromotionError, DomainUnitMismatchError, ERROR_CODES, E_CANONICAL_DIVBY_ZERO, E_CANONICAL_NONFINITE, EdgeKind, EntityNotFoundError, EnumLoweringError, EsmDiagnosticError, EsmMachineryError, EvaluatorError, expandDocument as Expand, ExpressionParseError, EsmMachineryError as ExpressionTemplateError, FlattenError, InvalidDerivativeOrderError, LIBRARY_VERSION, LosslessJsonParseError, MAX_TEMPLATE_EXPANSION_DEPTH, MigrationError, NonDifferentiableExpressionError, OperatorComposeAmbiguousBareNameError, OperatorComposeNoMergeError, OperatorComposeRequireMatchError, ParseError, RefLoadError, ReferenceGraph, ReferenceResolutionError, SCHEMA_VERSION, SchemaValidationError, UnevaluableOperatorError, UnitConversionError, UnloweredOperatorError, VariableInUseError, VertexKind, addContinuousEvent, addCoupling, addDiscreteEvent, addEquation, addReaction, addSpecies, addVariable, algebraicUnknowns, analyzeComplexity, analyzeExpression, applyScopeInjections, authoredTemplateNames, brownianParameters, buildDependencyGraph, buildEmittedDocument, buildReferenceGraph, canMigrate, canonicalJson, canonicalize, checkDimensions, classifyComplexity, classifyDocument, classifyModel, closedFunctionNames, compareComplexity, compileExpression, componentExists, componentGraph, compose, constantParameters, contains, convertUnits, declaredSystemKind, deriveODEs, deriveOdes, detectStabilityIssues, differentiate, discreteParameters, dispatchClosedFunction, effectiveSystemKind, emitDocument, emitEsmString, ephemeralInjectedFile, estimateParallelPotential, estimateSavings, evaluateExpression, expandCouplingImports, expandDocument, expressionCadence, expressionGraph, extract, findCommonSubexpressions, findCommonSubexpressionsAcrossExpressions, findCommonSubexpressionsInEsmFile, findCommonSubexpressionsInModel, findCriticalPoints, findDeadVariables, findDependencyChains, findExpensiveSubexpressions, flatten, flattenTemplateRegistries, floatLit, formatCanonicalFloat, formatChemicalName, formatFloatToken, freeParameters, freeVariables, generateFactoredVariableNames, getComponentType, getSupportedMigrationTargets, gradient, groupSubexpressionsByType, higherOrderDerivative, intLit, interpBilinear, interpLinear, isCouplingLibraryDoc, isDifferentiable, isFloatLit, isIntLit, isNumericLiteral, isOdeState, isTemplateLibraryDoc, joinAll, joinCadence, leafCadence, loadDocument, loadPath, loadString, losslessJsonParse, losslessJsonStringify, lowerEnums, lowerExpressionTemplates, mapVariable, merge, migrate, numericValue, observedDefinitions, observedUnknowns, odeStates, parameterClass, parameters, parseEquation, parseExpression, parseReaction, parseUnit, parseUnitForConversion, partialDerivatives, productMatrix, rejectExpressionTemplatesPreV04, rejectSolverPreV11, rejectTemplateImportsPreV08, removeCoupling, removeEquation, removeEvent, removeReaction, removeSpecies, removeVariable, renameVariable, resolveReferences, resolveSubsystemRefs, resolveTemplateMachinery, resolveTolerances, sampledParameters, searchsortedFirst, simplify, stoichiometricMatrix, substitute, substituteInEquations, substituteInModel, substituteInReactionSystem, substrateMatrix, supportedMigrationTargets, systemKind, toAscii, toDot, toJson, toJsonCompact, toJsonGraph, toLatex, toMathML, toMermaid, toUnicode, tryParseUnit, unitsCompatible, unknowns, updateRules, validate, validateInterpAxis, validateSchema, validateSearchsortedTable, validateText, validateUnits, writePath };
5924
+ export type { AffectEquation, Analysis, AnalysisOptions, AnalysisResults, Assertion, Assertion1, CadenceClass, CanonicalDims, ClosedFunctionErrorCode, CommonSubexpression, CompiledExpression, ComplexityMetrics, ComponentGraph, ComponentNode, ConditionUpdate, ConnectorEquation, ContinuousEvent, Coordinate, Coordinate1, CouplingCallback, CouplingCouple, CouplingEdge, CouplingEntry, CouplingEvent, CouplingEvent1, CouplingImport, CouplingImportOptions, CouplingOperatorCompose, CouplingVariableMap, CouplingVariableMap1, CovarianceMatrix, CrossingUpdate, DataSource, DataSourceBinding, DataSourceCodes, DataSourceDeterminism, DataSourceExtent, DataSourceLocation, DataSourceRecordFilter, DataSourceSelect, DataSourceSelectAxis, DataSourceTemporal, DataUpdate, DependencyEdge, DependencyGraph, DependencyNode, DependencyRelation, DerivativeResult, DiscreteEvent, DiscreteEventTrigger, Distribution, Domain, ESMFormat, ESMFormat1, ESMFormat2, EnumDeclaration, Equation, ErrorCode, EsmFile, Expr, ExprNode, ExprNodeOf, Expression, ExpressionLocation, ExpressionNode, ExpressionNode1, ExpressionTemplate, FieldInitialCondition, FlattenMetadata, FlattenOptions, FlattenedEquation, FlattenedSystem, FlattenedTemplateRegistries, FlattenedVariable, FlattenedVariableMap, FlattenedVariableRole, FunctionTable, FunctionTableAxis, FunctionalUpdate, Graph, IndexSet, IndexSet1, LoadOptions, LoaderField, LognormalDistribution, Metadata, MetaparameterExpression, Metaparameters, Model, ModelClassification, ModelVariable, ModelVariable1, NonWienerParameterUpdate, NormalDistribution, NumericArrayLiteral, NumericLiteral, Parameter, Parameter1, ParameterClass, ParameterSweep, ParameterUpdate, ParameterUpdateSpec, ParsedUnit, Plot, Plot1, PlotAxis, PlotSeries, PlotValue, Reaction, ReactionSystem, Reference, ReferenceEdge, ReferenceVertex, RemeshUpdate, ScheduleUpdate, SchemaError, Solver, Species, StabilityIssue, StoichiometryEntry, SubsystemRef, SweepDimension, SweepDimension1, SweepRange, SystemKind, TemplateImport, TemplateResolveOptions, TemplateSchemaError, Test, TimeSpan, ToJsonOptions, Tolerance, Tolerance1, Tolerance2, Tolerance3, TranslateTarget, UniformDistribution, UnitResult, UnitWarning, UnknownClass, UpdateValueForm, ValidationError, ValidationResult, VariableKind, VariableNode, WienerUpdate };