universal-physics-tensor 11.0.0 → 12.0.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.
Files changed (287) hide show
  1. package/README.md +36 -34
  2. package/data/applied-cases.json +1892 -0
  3. package/data/applied-cases.schema.json +194 -0
  4. package/data/atlas/atlas.json +21 -21
  5. package/data/atlas/atlas.jsonld +1 -1
  6. package/data/atlas/diffusion.json +5 -5
  7. package/data/atlas/oscillators.json +7 -7
  8. package/data/atlas/waves.json +11 -11
  9. package/data/bridge-catalog.json +20 -4
  10. package/data/units.json +6 -25
  11. package/dist/atlas/benchmark/stats.d.ts.map +1 -1
  12. package/dist/atlas/benchmark/stats.js +6 -20
  13. package/dist/atlas/benchmark/stats.js.map +1 -1
  14. package/dist/atlas/physjs-entries.generated.d.ts +13 -4
  15. package/dist/atlas/physjs-entries.generated.d.ts.map +1 -1
  16. package/dist/atlas/physjs-entries.generated.js +15 -4
  17. package/dist/atlas/physjs-entries.generated.js.map +1 -1
  18. package/dist/atlas/physjs-reviewed.js +2 -2
  19. package/dist/atlas/physjs-reviewed.js.map +1 -1
  20. package/dist/cases/catalog-checks.d.ts +10 -0
  21. package/dist/cases/catalog-checks.d.ts.map +1 -0
  22. package/dist/cases/catalog-checks.js +241 -0
  23. package/dist/cases/catalog-checks.js.map +1 -0
  24. package/dist/cases/catalog.d.ts +83 -0
  25. package/dist/cases/catalog.d.ts.map +1 -0
  26. package/dist/cases/catalog.js +118 -0
  27. package/dist/cases/catalog.js.map +1 -0
  28. package/dist/cases/engine.d.ts +13 -0
  29. package/dist/cases/engine.d.ts.map +1 -0
  30. package/dist/cases/engine.js +179 -0
  31. package/dist/cases/engine.js.map +1 -0
  32. package/dist/cases/index.d.ts.map +1 -1
  33. package/dist/cases/index.js +5 -7
  34. package/dist/cases/index.js.map +1 -1
  35. package/dist/cases/methods.d.ts +215 -0
  36. package/dist/cases/methods.d.ts.map +1 -0
  37. package/dist/cases/methods.js +743 -0
  38. package/dist/cases/methods.js.map +1 -0
  39. package/dist/cases/types.d.ts +12 -0
  40. package/dist/cases/types.d.ts.map +1 -1
  41. package/dist/cases/types.js.map +1 -1
  42. package/dist/cli/args.d.ts +2 -0
  43. package/dist/cli/args.d.ts.map +1 -1
  44. package/dist/cli/args.js.map +1 -1
  45. package/dist/cli/command.d.ts +3 -0
  46. package/dist/cli/command.d.ts.map +1 -1
  47. package/dist/cli/command.js.map +1 -1
  48. package/dist/cli/commands/_discovery-opts.d.ts +1 -0
  49. package/dist/cli/commands/_discovery-opts.d.ts.map +1 -1
  50. package/dist/cli/commands/_discovery-opts.js +1 -0
  51. package/dist/cli/commands/_discovery-opts.js.map +1 -1
  52. package/dist/cli/commands/discover.d.ts.map +1 -1
  53. package/dist/cli/commands/discover.js +116 -71
  54. package/dist/cli/commands/discover.js.map +1 -1
  55. package/dist/cli/commands/evaluate.js +2 -2
  56. package/dist/cli/commands/evaluate.js.map +1 -1
  57. package/dist/cli/commands/explain.d.ts.map +1 -1
  58. package/dist/cli/commands/explain.js +22 -2
  59. package/dist/cli/commands/explain.js.map +1 -1
  60. package/dist/cli/commands/ground.d.ts.map +1 -1
  61. package/dist/cli/commands/ground.js +6 -1
  62. package/dist/cli/commands/ground.js.map +1 -1
  63. package/dist/cli/commands/map.d.ts.map +1 -1
  64. package/dist/cli/commands/map.js +9 -6
  65. package/dist/cli/commands/map.js.map +1 -1
  66. package/dist/cli/determination.d.ts +1 -0
  67. package/dist/cli/determination.d.ts.map +1 -1
  68. package/dist/cli/determination.js.map +1 -1
  69. package/dist/cli/flag-help.d.ts +1 -0
  70. package/dist/cli/flag-help.d.ts.map +1 -1
  71. package/dist/cli/flag-help.js +1 -0
  72. package/dist/cli/flag-help.js.map +1 -1
  73. package/dist/cli/graphs.d.ts +2 -0
  74. package/dist/cli/graphs.d.ts.map +1 -1
  75. package/dist/cli/graphs.js +1 -0
  76. package/dist/cli/graphs.js.map +1 -1
  77. package/dist/cli/main.d.ts +1 -0
  78. package/dist/cli/main.d.ts.map +1 -1
  79. package/dist/cli/main.js +1 -0
  80. package/dist/cli/main.js.map +1 -1
  81. package/dist/cli/output.d.ts +8 -0
  82. package/dist/cli/output.d.ts.map +1 -1
  83. package/dist/cli/output.js +8 -0
  84. package/dist/cli/output.js.map +1 -1
  85. package/dist/cli/statuses.d.ts.map +1 -1
  86. package/dist/cli/statuses.js +12 -2
  87. package/dist/cli/statuses.js.map +1 -1
  88. package/dist/cli/version.d.ts +1 -0
  89. package/dist/cli/version.d.ts.map +1 -1
  90. package/dist/cli/version.js +1 -0
  91. package/dist/cli/version.js.map +1 -1
  92. package/dist/cli-api.d.ts +2 -0
  93. package/dist/cli-api.d.ts.map +1 -1
  94. package/dist/cli-api.js +2 -0
  95. package/dist/cli-api.js.map +1 -1
  96. package/dist/composition/canonical-graph.d.ts +2 -2
  97. package/dist/composition/canonical-graph.js +2 -2
  98. package/dist/composition/canonical-graph.js.map +1 -1
  99. package/dist/composition/consequence.d.ts +15 -5
  100. package/dist/composition/consequence.d.ts.map +1 -1
  101. package/dist/composition/consequence.js +6 -4
  102. package/dist/composition/consequence.js.map +1 -1
  103. package/dist/composition/discovery.d.ts +78 -14
  104. package/dist/composition/discovery.d.ts.map +1 -1
  105. package/dist/composition/discovery.js +73 -23
  106. package/dist/composition/discovery.js.map +1 -1
  107. package/dist/composition/expr-eval.d.ts +1 -1
  108. package/dist/composition/expr-eval.d.ts.map +1 -1
  109. package/dist/composition/expr-eval.js +4 -4
  110. package/dist/composition/expr-eval.js.map +1 -1
  111. package/dist/composition/expr-simplify.d.ts +10 -14
  112. package/dist/composition/expr-simplify.d.ts.map +1 -1
  113. package/dist/composition/expr-simplify.js +49 -52
  114. package/dist/composition/expr-simplify.js.map +1 -1
  115. package/dist/composition/grounding.d.ts.map +1 -1
  116. package/dist/composition/grounding.js +19 -7
  117. package/dist/composition/grounding.js.map +1 -1
  118. package/dist/composition/mathts-quiet.d.ts +4 -6
  119. package/dist/composition/mathts-quiet.d.ts.map +1 -1
  120. package/dist/composition/mathts-quiet.js +4 -14
  121. package/dist/composition/mathts-quiet.js.map +1 -1
  122. package/dist/composition/own-equation-magnitude.d.ts +95 -0
  123. package/dist/composition/own-equation-magnitude.d.ts.map +1 -0
  124. package/dist/composition/own-equation-magnitude.js +270 -0
  125. package/dist/composition/own-equation-magnitude.js.map +1 -0
  126. package/dist/composition/probe/backend-protocol.d.ts +3 -0
  127. package/dist/composition/probe/backend-protocol.d.ts.map +1 -1
  128. package/dist/composition/probe/backend-protocol.js.map +1 -1
  129. package/dist/composition/probe/candidate-store.d.ts +1 -0
  130. package/dist/composition/probe/candidate-store.d.ts.map +1 -1
  131. package/dist/composition/probe/candidate-store.js +1 -0
  132. package/dist/composition/probe/candidate-store.js.map +1 -1
  133. package/dist/composition/probe/corpus.d.ts +4 -2
  134. package/dist/composition/probe/corpus.d.ts.map +1 -1
  135. package/dist/composition/probe/corpus.js +3 -3
  136. package/dist/composition/probe/corpus.js.map +1 -1
  137. package/dist/composition/probe/dataset.d.ts +1 -0
  138. package/dist/composition/probe/dataset.d.ts.map +1 -1
  139. package/dist/composition/probe/dataset.js.map +1 -1
  140. package/dist/composition/probe/experiment-design.d.ts +2 -0
  141. package/dist/composition/probe/experiment-design.d.ts.map +1 -1
  142. package/dist/composition/probe/experiment-design.js.map +1 -1
  143. package/dist/composition/probe/falsify.d.ts +2 -0
  144. package/dist/composition/probe/falsify.d.ts.map +1 -1
  145. package/dist/composition/probe/falsify.js.map +1 -1
  146. package/dist/composition/probe/fit.d.ts +1 -0
  147. package/dist/composition/probe/fit.d.ts.map +1 -1
  148. package/dist/composition/probe/fit.js.map +1 -1
  149. package/dist/composition/probe/generator.d.ts +3 -0
  150. package/dist/composition/probe/generator.d.ts.map +1 -1
  151. package/dist/composition/probe/generator.js +5 -6
  152. package/dist/composition/probe/generator.js.map +1 -1
  153. package/dist/composition/probe/limits.d.ts +1 -0
  154. package/dist/composition/probe/limits.d.ts.map +1 -1
  155. package/dist/composition/probe/limits.js.map +1 -1
  156. package/dist/composition/probe/pipeline.d.ts +5 -0
  157. package/dist/composition/probe/pipeline.d.ts.map +1 -1
  158. package/dist/composition/probe/pipeline.js +64 -21
  159. package/dist/composition/probe/pipeline.js.map +1 -1
  160. package/dist/composition/probe/power-law.d.ts.map +1 -1
  161. package/dist/composition/probe/power-law.js +3 -7
  162. package/dist/composition/probe/power-law.js.map +1 -1
  163. package/dist/composition/probe/problem.d.ts +1 -0
  164. package/dist/composition/probe/problem.d.ts.map +1 -1
  165. package/dist/composition/probe/problem.js.map +1 -1
  166. package/dist/composition/probe/residual.d.ts +1 -0
  167. package/dist/composition/probe/residual.d.ts.map +1 -1
  168. package/dist/composition/probe/residual.js +1 -0
  169. package/dist/composition/probe/residual.js.map +1 -1
  170. package/dist/composition/probe/run-manifest.d.ts +1 -0
  171. package/dist/composition/probe/run-manifest.d.ts.map +1 -1
  172. package/dist/composition/probe/run-manifest.js.map +1 -1
  173. package/dist/composition/probe/scalar-search.d.ts +50 -0
  174. package/dist/composition/probe/scalar-search.d.ts.map +1 -0
  175. package/dist/composition/probe/scalar-search.js +169 -0
  176. package/dist/composition/probe/scalar-search.js.map +1 -0
  177. package/dist/composition/probe/scoring.d.ts +1 -0
  178. package/dist/composition/probe/scoring.d.ts.map +1 -1
  179. package/dist/composition/probe/scoring.js.map +1 -1
  180. package/dist/composition/probe/study.d.ts +2 -2
  181. package/dist/composition/probe/study.d.ts.map +1 -1
  182. package/dist/composition/probe/study.js +6 -10
  183. package/dist/composition/probe/study.js.map +1 -1
  184. package/dist/composition/probe/t-tail.d.ts +3 -4
  185. package/dist/composition/probe/t-tail.d.ts.map +1 -1
  186. package/dist/composition/probe/t-tail.js +7 -10
  187. package/dist/composition/probe/t-tail.js.map +1 -1
  188. package/dist/composition/proposed-bridges.d.ts +11 -2
  189. package/dist/composition/proposed-bridges.d.ts.map +1 -1
  190. package/dist/composition/proposed-bridges.js +7 -5
  191. package/dist/composition/proposed-bridges.js.map +1 -1
  192. package/dist/composition/rational-exponent.d.ts +2 -2
  193. package/dist/composition/rational-exponent.d.ts.map +1 -1
  194. package/dist/composition/rational-exponent.js +4 -4
  195. package/dist/composition/rational-exponent.js.map +1 -1
  196. package/dist/composition/representative-values.d.ts.map +1 -1
  197. package/dist/composition/representative-values.js +6 -0
  198. package/dist/composition/representative-values.js.map +1 -1
  199. package/dist/composition/retrodiction.d.ts +15 -0
  200. package/dist/composition/retrodiction.d.ts.map +1 -1
  201. package/dist/composition/retrodiction.js +10 -3
  202. package/dist/composition/retrodiction.js.map +1 -1
  203. package/dist/composition/user-equation.d.ts +1 -0
  204. package/dist/composition/user-equation.d.ts.map +1 -1
  205. package/dist/composition/user-equation.js +1 -0
  206. package/dist/composition/user-equation.js.map +1 -1
  207. package/dist/core/constants.d.ts +12 -11
  208. package/dist/core/constants.d.ts.map +1 -1
  209. package/dist/core/constants.js +24 -21
  210. package/dist/core/constants.js.map +1 -1
  211. package/dist/diff/bridge-ast-gradient.d.ts +27 -23
  212. package/dist/diff/bridge-ast-gradient.d.ts.map +1 -1
  213. package/dist/diff/bridge-ast-gradient.js +75 -173
  214. package/dist/diff/bridge-ast-gradient.js.map +1 -1
  215. package/dist/dimensional/ast-types.d.ts +15 -0
  216. package/dist/dimensional/ast-types.d.ts.map +1 -1
  217. package/dist/dimensional/ast-types.js.map +1 -1
  218. package/dist/dimensional/curvature-composite.d.ts +1 -1
  219. package/dist/dimensional/curvature-composite.js.map +1 -1
  220. package/dist/dimensional/exact-scale.d.ts +41 -11
  221. package/dist/dimensional/exact-scale.d.ts.map +1 -1
  222. package/dist/dimensional/exact-scale.js +115 -123
  223. package/dist/dimensional/exact-scale.js.map +1 -1
  224. package/dist/dimensional/mathts-unit-delegation.d.ts +29 -0
  225. package/dist/dimensional/mathts-unit-delegation.d.ts.map +1 -0
  226. package/dist/dimensional/mathts-unit-delegation.js +187 -0
  227. package/dist/dimensional/mathts-unit-delegation.js.map +1 -0
  228. package/dist/dimensional/mathts-unit-wrapper.d.ts +33 -0
  229. package/dist/dimensional/mathts-unit-wrapper.d.ts.map +1 -0
  230. package/dist/dimensional/mathts-unit-wrapper.js +190 -0
  231. package/dist/dimensional/mathts-unit-wrapper.js.map +1 -0
  232. package/dist/dimensional/scalar-lowering.d.ts +21 -0
  233. package/dist/dimensional/scalar-lowering.d.ts.map +1 -0
  234. package/dist/dimensional/scalar-lowering.js +45 -0
  235. package/dist/dimensional/scalar-lowering.js.map +1 -0
  236. package/dist/dimensional/unit-data.d.ts +5 -1
  237. package/dist/dimensional/unit-data.d.ts.map +1 -1
  238. package/dist/dimensional/unit-data.js +28 -4
  239. package/dist/dimensional/unit-data.js.map +1 -1
  240. package/dist/dimensional/units.d.ts +2 -0
  241. package/dist/dimensional/units.d.ts.map +1 -1
  242. package/dist/dimensional/units.js +59 -3
  243. package/dist/dimensional/units.js.map +1 -1
  244. package/dist/dimensional/validator.d.ts +2 -0
  245. package/dist/dimensional/validator.d.ts.map +1 -1
  246. package/dist/dimensional/validator.js.map +1 -1
  247. package/dist/index.js.map +1 -1
  248. package/dist/numerical/connection-lowering-helpers.d.ts +3 -1
  249. package/dist/numerical/connection-lowering-helpers.d.ts.map +1 -1
  250. package/dist/numerical/connection-lowering-helpers.js +11 -15
  251. package/dist/numerical/connection-lowering-helpers.js.map +1 -1
  252. package/dist/numerical/pderiv.d.ts.map +1 -1
  253. package/dist/numerical/pderiv.js +2 -6
  254. package/dist/numerical/pderiv.js.map +1 -1
  255. package/dist/numerical/quadrature.js +3 -3
  256. package/dist/numerical/quadrature.js.map +1 -1
  257. package/dist/numerical/statistics.js +1 -1
  258. package/dist/numerical/statistics.js.map +1 -1
  259. package/dist/numerical/strides.d.ts +3 -2
  260. package/dist/numerical/strides.d.ts.map +1 -1
  261. package/dist/numerical/strides.js +3 -10
  262. package/dist/numerical/strides.js.map +1 -1
  263. package/package.json +9 -12
  264. package/dist/cases/brownian-sphere.d.ts +0 -68
  265. package/dist/cases/brownian-sphere.d.ts.map +0 -1
  266. package/dist/cases/brownian-sphere.js +0 -355
  267. package/dist/cases/brownian-sphere.js.map +0 -1
  268. package/dist/cases/damped-resonator.d.ts +0 -23
  269. package/dist/cases/damped-resonator.d.ts.map +0 -1
  270. package/dist/cases/damped-resonator.js +0 -221
  271. package/dist/cases/damped-resonator.js.map +0 -1
  272. package/dist/cases/kepler-rv.d.ts +0 -13
  273. package/dist/cases/kepler-rv.d.ts.map +0 -1
  274. package/dist/cases/kepler-rv.js +0 -173
  275. package/dist/cases/kepler-rv.js.map +0 -1
  276. package/dist/cases/lumped-cooling.d.ts +0 -51
  277. package/dist/cases/lumped-cooling.d.ts.map +0 -1
  278. package/dist/cases/lumped-cooling.js +0 -259
  279. package/dist/cases/lumped-cooling.js.map +0 -1
  280. package/dist/cases/resistor-noise.d.ts +0 -14
  281. package/dist/cases/resistor-noise.d.ts.map +0 -1
  282. package/dist/cases/resistor-noise.js +0 -215
  283. package/dist/cases/resistor-noise.js.map +0 -1
  284. package/dist/cases/skin-depth.d.ts +0 -17
  285. package/dist/cases/skin-depth.d.ts.map +0 -1
  286. package/dist/cases/skin-depth.js +0 -170
  287. package/dist/cases/skin-depth.js.map +0 -1
@@ -46,6 +46,7 @@ import { conventionScaleToSI } from '../dimensional/unit-convention.js';
46
46
  import { allQuantities } from './quantities.js';
47
47
  import { CANONICAL_EQUATIONS } from '../canonical/registry.js';
48
48
  import { format } from '../dimensional/algebra.js';
49
+ import { deriveOwnEquationMagnitudes } from './own-equation-magnitude.js';
49
50
  // ── registry-only attribute lookup (D1 axis gate; Option-A resolution) ─────
50
51
  // Built from the centralized `Quantity` nodes ONLY (never canonical
51
52
  // per-equation stamps — audit adjudication F1). Names that aren't a
@@ -82,20 +83,39 @@ const CANONICAL_QUANTITY_NAMES = (() => {
82
83
  }
83
84
  return s;
84
85
  })();
86
+ /**
87
+ * Whether the magnitude falsifier ran on a candidate AND could have failed: both endpoints had a
88
+ * representative magnitude (`magnitudeChecked`) and the comparison is not anchor-invariant. An
89
+ * anchor-invariant pass is fixed by the graph at every anchor, so it cannot fail and is not a
90
+ * check (AGENTS law 2). The one owner of this predicate: the verdict split between `promising`
91
+ * and `promising-unchecked`, the CLI readiness count and the grounding ledger all read it.
92
+ * @internal
93
+ */
94
+ export function hasMagnitudeEvidence(c) {
95
+ return c.magnitudeChecked && c.magnitudeAnchorInvariant !== true;
96
+ }
97
+ /**
98
+ * Whether a verdict is one of the two promising tiers, `promising` (with magnitude evidence) or
99
+ * `promising-unchecked` (without): the structural set the consequence and proposal layers
100
+ * process, so splitting the tiers loses no proposal. A selector, never a count: the two tiers are
101
+ * reported apart and never added (AGENTS law 4).
102
+ * @internal
103
+ */
104
+ export function isPromisingTier(verdict) {
105
+ return verdict === 'promising' || verdict === 'promising-unchecked';
106
+ }
85
107
  /**
86
108
  * The component partition of quantity names: names in one equation are one component, and so
87
109
  * are the two sides of every identification. The shared partition of `quantity-clusters.ts`
88
110
  * (a name is an item; its keys are the equations and identifications it appears in). Returns
89
111
  * name → a representative of its component (its first name), read only for equality.
90
112
  *
91
- * RECORDED BEHAVIOUR, kept on purpose: an equation with no source (an all-constant right-hand
92
- * side, `CE-bohr-radius`) registers no name at all, so its target is absent from the map and
93
- * an identification touching it never counts as merging components. The union-find this
94
- * replaced only registered a name when it took part in a union, and the census, the canonical
95
- * funnel counts and the `map --proposed` golden pin that reading. Registering such a target
96
- * makes `bohr-radius ≟ hubble-distance` promising; whether it should is a catalog decision,
97
- * not this module's (`tests/composition/quantity-clusters.test.ts` pins the recorded reading
98
- * by name).
113
+ * An equation with no source (an all-constant right-hand side, `CE-bohr-radius`) registers its
114
+ * target under its own key, so the target is a component member of its own and an identification
115
+ * touching it can merge components (the owner's decision of 2026-10-16, option 1). Before it, the
116
+ * union-find this replaced registered a name only when it took part in a union, so the target of
117
+ * every source-less equation (eight on the canonical graph) was absent from the map and no
118
+ * identification touching it merged anything.
99
119
  */
100
120
  function quantityComponents(edges, idents) {
101
121
  const keysByName = new Map();
@@ -107,8 +127,6 @@ function quantityComponents(edges, idents) {
107
127
  keys.push(key);
108
128
  };
109
129
  edges.forEach((e, i) => {
110
- if (e.sources.length === 0)
111
- return; // recorded behaviour: a source-less target is not registered
112
130
  for (const s of e.sources)
113
131
  tag(s.name, `edge:${i}`);
114
132
  tag(e.target.name, `edge:${i}`);
@@ -173,16 +191,24 @@ export function buildDiscoveryContext(edges, opts) {
173
191
  const groundTruth = opts.groundTruth ?? ANCHOR_DEFAULT;
174
192
  const anchor = Object.keys(groundTruth);
175
193
  const baseReport = retrodict(edges, groundTruth, { identifications: baseIdents });
194
+ const repVals = opts.representativeValues ?? REPRESENTATIVE_VALUES;
195
+ const constantValues = forwardEvaluate(edges, {}, baseIdents);
176
196
  return {
177
197
  baseIdents,
178
198
  groundTruth,
179
199
  anchor,
180
- repVals: opts.representativeValues ?? REPRESENTATIVE_VALUES,
200
+ repVals,
181
201
  maxOrders: opts.maxOrdersOfMagnitude ?? DEFAULT_MAX_ORDERS_OF_MAGNITUDE,
182
202
  attributesByName: opts.quantityAttributes ?? REGISTRY_ATTRIBUTES_BY_NAME,
183
203
  // Candidate-invariant: the anchor's forward evaluation, the base component
184
204
  // partition, and the base forward closure all depend only on (edges, opts).
185
205
  anchorValues: forwardEvaluate(edges, groundTruth, baseIdents),
206
+ constantValues,
207
+ derivedMagnitudes: deriveOwnEquationMagnitudes(edges, {
208
+ representativeValues: repVals,
209
+ constantValues,
210
+ identifications: baseIdents,
211
+ }),
186
212
  anchorValuesProbe: forwardEvaluate(edges, Object.fromEntries(Object.entries(groundTruth).map(([k, v]) => [k, v * ANCHOR_PROBE_FACTOR])), baseIdents),
187
213
  comps: quantityComponents(edges, baseIdents),
188
214
  closureBase: forwardClosure(edges, anchor, baseIdents),
@@ -264,10 +290,14 @@ export function magnitudeStrength(orders, sigmaA, sigmaB) {
264
290
  /**
265
291
  * The magnitude gate: an INDEPENDENT falsifier the single-anchor graph cannot make. It runs only
266
292
  * when both endpoints have a representative value and abstains (never false-rejects) otherwise.
267
- * A value comes from the sourced static table first, else — for graph-derived quantities the
268
- * table omits by design (schwarzschild-radius, thermal-de-broglie-wavelength) — from the
269
- * quantity's value AT THE REGISTERED ANCHOR, a definite magnitude for this run, with the default
270
- * σ of one decade. The verdict is `ordersApart > ctx.maxOrders`; `p` is reported beside it.
293
+ * A value comes from the sourced static table first; else from the value the graph fixes with NO
294
+ * anchor input (a constant computed from registered constants alone, `ctx.constantValues`: not
295
+ * anchor-derived); else from the quantity's own equation evaluated from the table and the
296
+ * constants (`ctx.derivedMagnitudes`, at its propagated σ, never above one decade: not
297
+ * anchor-derived); else — for graph-derived quantities the table omits by design
298
+ * (schwarzschild-radius, thermal-de-broglie-wavelength) — from the quantity's value AT THE
299
+ * REGISTERED ANCHOR, a definite magnitude for this run. The constant and the anchor value take the
300
+ * default σ of one decade. The verdict is `ordersApart > ctx.maxOrders`; `p` is reported beside it.
271
301
  *
272
302
  * @internal
273
303
  */
@@ -278,13 +308,29 @@ export function magnitudeGate(candidate, ctx) {
278
308
  // would make log10 blow up (log10(0) = -∞), spoofing or silently disabling
279
309
  // the falsifier. Symmetric validation across the table and anchor sources.
280
310
  if (rv !== undefined && Number.isFinite(rv.value) && rv.value !== 0) {
281
- return { value: rv.value, fromAnchor: false, sigmaDecades: sigmaDecadesOf(rv) };
311
+ return { value: rv.value, fromAnchor: false, fromOwnEquation: false, sigmaDecades: sigmaDecadesOf(rv) };
312
+ }
313
+ // A value the graph fixes with no anchor input is a constant: comparing it can fail by the
314
+ // clash threshold at every anchor, so it is not anchor-derived and the invariance probe does
315
+ // not withhold it. Only a value that needs the anchor is `fromAnchor`.
316
+ const cv = ctx.constantValues.get(name);
317
+ if (cv !== undefined) {
318
+ const value = cv * conventionScaleToSI(name);
319
+ if (Number.isFinite(value) && value !== 0) {
320
+ return { value, fromAnchor: false, fromOwnEquation: false, sigmaDecades: DEFAULT_SIGMA_DECADES };
321
+ }
322
+ }
323
+ // The quantity's own equation, from the table and the constants alone (already in SI, and
324
+ // already refused when its σ is above one decade): not anchor-derived either.
325
+ const dv = ctx.derivedMagnitudes.values.get(name);
326
+ if (dv !== undefined) {
327
+ return { value: dv.value, fromAnchor: false, fromOwnEquation: true, sigmaDecades: dv.sigmaDecades };
282
328
  }
283
329
  const av = ctx.anchorValues.get(name);
284
330
  if (av !== undefined) {
285
331
  const value = av * conventionScaleToSI(name);
286
332
  if (Number.isFinite(value) && value !== 0) {
287
- return { value, fromAnchor: true, sigmaDecades: DEFAULT_SIGMA_DECADES };
333
+ return { value, fromAnchor: true, fromOwnEquation: false, sigmaDecades: DEFAULT_SIGMA_DECADES };
288
334
  }
289
335
  }
290
336
  return undefined;
@@ -302,6 +348,7 @@ export function magnitudeGate(candidate, ctx) {
302
348
  clash: ordersApart !== null && ordersApart > ctx.maxOrders,
303
349
  p: checked && ordersApart !== null ? magnitudeStrength(ordersApart, a.sigmaDecades, b.sigmaDecades) : null,
304
350
  missing: [a === undefined ? candidate.a : null, b === undefined ? candidate.b : null].filter((e) => e !== null),
351
+ derived: [a?.fromOwnEquation === true ? candidate.a : null, b?.fromOwnEquation === true ? candidate.b : null].filter((e) => e !== null),
305
352
  };
306
353
  }
307
354
  /**
@@ -433,7 +480,7 @@ export function vetInContext(edges, candidate, ctx) {
433
480
  else if (axisClash)
434
481
  verdict = 'axis-clash';
435
482
  else if (mergesComponents && unlocksFromAnchor.length > 0 && !subsuming)
436
- verdict = 'promising';
483
+ verdict = hasMagnitudeEvidence({ magnitudeChecked, magnitudeAnchorInvariant }) ? 'promising' : 'promising-unchecked';
437
484
  else
438
485
  verdict = 'inert';
439
486
  // Score: all falsifications sink to the bottom; a subsuming (tautological)
@@ -470,6 +517,7 @@ export function vetInContext(edges, candidate, ctx) {
470
517
  magnitudeChecked,
471
518
  magnitudeUsedAnchor,
472
519
  magnitudeAnchorInvariant,
520
+ magnitudeDerived: gate.derived,
473
521
  magnitudeMissing: gate.missing,
474
522
  axisUnstated,
475
523
  subsuming,
@@ -483,7 +531,8 @@ export function vetInContext(edges, candidate, ctx) {
483
531
  }
484
532
  /**
485
533
  * Run the full discovery funnel: propose cross-cluster candidates, vet each
486
- * against the inference suite, and rank — `promising` first, then by score.
534
+ * against the inference suite, and rank — `promising` first, then `promising-unchecked`, then
535
+ * the rest, and by score within a verdict.
487
536
  * The output is the physicist's worklist (vetted, provenance-traced), not a
488
537
  * list of discoveries.
489
538
  *
@@ -492,10 +541,11 @@ export function vetInContext(edges, candidate, ctx) {
492
541
  export function rankDiscoveries(edges, opts = {}) {
493
542
  const VERDICT_RANK = {
494
543
  promising: 0,
495
- inert: 1,
496
- 'magnitude-clash': 2,
497
- contradictory: 3,
498
- 'axis-clash': 4,
544
+ 'promising-unchecked': 1,
545
+ inert: 2,
546
+ 'magnitude-clash': 3,
547
+ contradictory: 4,
548
+ 'axis-clash': 5,
499
549
  };
500
550
  const ctx = buildDiscoveryContext(edges, opts);
501
551
  const candidates = proposeLinkCandidates(edges);
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/composition/discovery.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Discovery loop — vet link candidates through the verification primitives\n * (Direction 2).\n *\n * `proposeLinkCandidates` surfaces cross-cluster quantity pairs that share a\n * dimension and hands the raw coincidences to a human (the counts at a given\n * catalog are measured by the tests and recorded in NOTES.md). This\n * module closes the loop: it HYPOTHESIZES each candidate identification\n * `a ≡ b` and tests it with the machinery that already exists —\n *\n * - mergesComponents — does identifying a≡b connect two previously\n * disconnected parts of the graph? (structural\n * significance)\n * - unlocksFromAnchor — what becomes determinable from an anchor input\n * set WITH the identification that was not before?\n * (`forwardClosure`)\n * - numericallyConsistent — does retrodiction over the graph stay\n * all-consistent once the identification is\n * added, or does it introduce a contradiction?\n * (`retrodict` — the STRONG filter)\n *\n * so the physicist adjudicates a handful of VETTED, provenance-traced\n * candidates instead of dimensional noise.\n *\n * ⚠ Still a REVIEW SURFACE, never automated discovery (Part-VI §XXVII-B). A\n * shared dimension is a weak prior; the weight is on the numeric and\n * structural signals, and the numeric check only exercises the part of the\n * graph reachable from the anchor. `promising` means \"worth a physicist's\n * minute\", not \"true\".\n *\n * INTERNAL — not on the public surface (mirrors bridge-analysis.ts);\n * surfaced via `upt discover`.\n *\n * @module composition/discovery\n */\n\nimport type { BridgeEdge } from './edge.js';\nimport type { QuantityIdentification } from './compose.js';\nimport { QUANTITY_IDENTIFICATIONS, effectiveAttributes } from './compose.js';\nimport { GATE_AXES } from './axes.js';\nimport type { RegimeAttributes } from './quantity.js';\nimport { classifyIdentifiability, forwardClosure } from './identifiability.js';\nimport { retrodict, retrodictNode, forwardEvaluate } from './retrodiction.js';\nimport { proposeLinkCandidates } from './bridge-analysis.js';\nimport type { LinkCandidate } from './bridge-analysis.js';\nimport { clusterBySharedKeys } from './quantity-clusters.js';\nimport { normalCDF } from '@danielsimonjr/mathts-functions';\nimport { M_SUN_KG } from '../core/constants.js';\nimport { DEFAULT_SIGMA_DECADES, REPRESENTATIVE_VALUES, sigmaDecadesOf } from './representative-values.js';\nimport type { RepresentativeValue } from './representative-values.js';\nimport { conventionScaleToSI } from '../dimensional/unit-convention.js';\nimport { allQuantities } from './quantities.js';\nimport { CANONICAL_EQUATIONS } from '../canonical/registry.js';\nimport { format } from '../dimensional/algebra.js';\n\n// ── registry-only attribute lookup (D1 axis gate; Option-A resolution) ─────\n// Built from the centralized `Quantity` nodes ONLY (never canonical\n// per-equation stamps — audit adjudication F1). Names that aren't a\n// registry node (most bare canonical-equation variables) are simply absent,\n// so they always abstain — the canonical-only axis-clash=0 invariant holds\n// by construction, not by a special case.\n/** The canonical quantity→attributes map (registry nodes + per-equation stamps).\n * Exported for the axis-discrimination audit. @internal */\nexport const REGISTRY_ATTRIBUTES_BY_NAME: ReadonlyMap<string, RegimeAttributes> = new Map(\n allQuantities().map((quantity) => [quantity.name, quantity.attributes]),\n);\n\n// The axes the D1 regime-label gate compares are `GATE_AXES`, imported from the\n// `axes.ts` registry (`AXES.filter(gated)`) at the top of this module. Currently\n// scale + force; the 2026-07-05 extensible-axis expansion added\n// symmetry/topology/statistics as UNGATED attributes (they only abstain until the\n// discrimination audit earns them a gate — flip `gated: true` in axes.ts).\n\n// ── canonical-kind indexes (Sub-project C) ──────────────────────────────────\n// Discovery quantity names and canonical variable names are different\n// namespaces; the alignable axis is the DIMENSION. These are informational\n// annotations on each candidate (no verdict/score effect): they give the\n// physicist standard-physics context, not a hard filter (the registry is small).\nconst CANONICAL_KINDS_BY_DIM: ReadonlyMap<string, readonly string[]> = (() => {\n const m = new Map<string, Set<string>>();\n for (const ce of CANONICAL_EQUATIONS) {\n const key = format(ce.dimensional.target.dim);\n (m.get(key) ?? m.set(key, new Set()).get(key)!).add(ce.domain);\n }\n return new Map([...m].map(([k, v]) => [k, [...v].sort()]));\n})();\n\nconst CANONICAL_QUANTITY_NAMES: ReadonlySet<string> = (() => {\n const s = new Set<string>();\n for (const ce of CANONICAL_EQUATIONS) {\n s.add(ce.dimensional.target.name);\n for (const g of ce.dimensional.governing) s.add(g.name);\n }\n return s;\n})();\n\n/** A candidate after vetting against the inference suite. @hypothesis @public */\nexport interface VettedCandidate {\n readonly a: string;\n readonly b: string;\n /** Shared SI dimension (from the proposer). */\n readonly dim: string;\n /** One endpoint is in the anchored (established) cluster. */\n readonly touchesCore: boolean;\n /** The names share a word token (same physical KIND). */\n readonly sameKind: boolean;\n /**\n * Identifying a≡b merges two distinct connected components of the\n * quantity graph — it connects physics the catalog keeps separate.\n */\n readonly mergesComponents: boolean;\n /**\n * Quantities newly determinable from the anchor known-set once the\n * identification is added (structural unlock). Sorted.\n */\n readonly unlocksFromAnchor: readonly string[];\n /**\n * Retrodiction over the graph WITH the hypothesized identification creates\n * no numerical contradiction in the anchor-reachable subgraph. The strong\n * filter. A node whose routes already disagree in the base graph is the\n * graph's finding (`retrodict` reports it) and does not make a candidate\n * inconsistent; only a disagreement the hypothesis CREATES does.\n */\n readonly numericallyConsistent: boolean;\n /** Nodes that became inconsistent under the identification and were not\n * inconsistent in the base graph (the falsification, when\n * `numericallyConsistent` is false). Sorted. */\n readonly inconsistentNodes: readonly string[];\n /**\n * Orders of magnitude between the representative values of a and b\n * (`|log10|va| − log10|vb||`), or `null` when either has no representative\n * value (the gate abstained — see `magnitudeChecked`).\n */\n readonly ordersApart: number | null;\n /**\n * The calibrated STRENGTH of the magnitude falsifier against the two values' stated\n * uncertainties: `2·Φ(−ordersApart / √(σ_a² + σ_b²))`, with σ in decades per representative\n * value (`sigmaDecades`, default one decade: the table's \"good to about one order\"; an\n * anchor-derived value takes the default), through MathTS `normalCDF` of the negative\n * argument so a far tail is not rounded to 0 by `1 − cdf`. It is reported BESIDE the verdict\n * and changes no verdict: the clash threshold stays `ordersApart > maxOrders`. A small p says\n * the two magnitudes are hard to reconcile at their stated precision; 2.9 decades at σ = 1\n * is p ≈ 0.040, no longer a clean pass, while 3.1 decades stays a clash. NOT a credibility\n * score: it says nothing about whether the identification is true. `null` when the gate\n * abstained. Optional on the type; the funnel always sets it.\n */\n readonly magnitudeP?: number | null;\n /** Both endpoints had a representative value, so the magnitude gate ran. */\n readonly magnitudeChecked: boolean;\n /** The magnitude gate read at least one endpoint value from the graph at\n * the anchor (no static table entry) rather than the sourced table. */\n readonly magnitudeUsedAnchor: boolean;\n /**\n * The magnitude comparison used an anchor-derived value, and the ratio of the\n * two magnitudes does NOT move when every anchor input is rescaled by\n * {@link ANCHOR_PROBE_FACTOR}. The graph then fixes the ratio at every\n * anchor, so agreement in magnitude is an identity of the graph and not\n * evidence for the identification. The case that exposed it: the anchor sets\n * the temperature to the Hawking temperature of the anchor mass M, and then\n * the thermal wavelength is exactly 4π ℓ_P for EVERY M. Its anchor-derived\n * magnitude is not a representative magnitude of the quantity at all.\n * A quantity whose value really is a constant belongs in the\n * representative-value table, where it is not anchor-derived.\n * A clash stays a clash (the graph itself fixes the two apart); only a pass is\n * withheld, in `describeGrounding`. Optional on the type so hand-built\n * candidates need not state it; the funnel always sets it.\n */\n readonly magnitudeAnchorInvariant?: boolean;\n /**\n * The endpoints with no representative magnitude (neither in the sourced\n * table nor valued at the anchor) — why the magnitude gate abstained. Empty\n * when it ran. Optional on the type; the funnel always sets it.\n */\n readonly magnitudeMissing?: readonly string[];\n /**\n * Per gate axis that abstained, the endpoints that state no value on it —\n * why the axis gate had nothing to compare. Optional; the funnel always sets it.\n */\n readonly axisUnstated?: readonly { readonly axis: string; readonly endpoints: readonly string[] }[];\n /**\n * One endpoint name's hyphen-token set is a strict subset of the other's\n * (`mass` ⊂ `reference-mass`) — a generic↔specific identification of the\n * same KIND. Near-tautological (identifying a generic quantity with one of\n * its own specializations), so it is barred from `promising`.\n */\n readonly subsuming: boolean;\n /**\n * - `magnitude-clash` — representative values differ by more than the\n * threshold N orders (an independent falsification the\n * single-anchor graph cannot make). Checked first.\n * - `contradictory` — breaks numerical consistency (a graph falsification).\n * - `axis-clash` — the identification's stated `scale`/`force` regime\n * attributes disagree. A regime-LABEL prior against a\n * literal identity (D1), not a physical test: it\n * compares catalog labels and runs no computation, so\n * it is never counted or described as a falsification.\n * - `promising` — consistent AND connects disconnected physics AND\n * unlocks ≥1 quantity. Worth physicist review.\n * - `inert` — consistent but structurally/numerically idle (a\n * dimensional coincidence with no consequence).\n */\n readonly verdict: 'promising' | 'inert' | 'contradictory' | 'magnitude-clash' | 'axis-clash';\n /** Composite ranking score (higher = more worth review). */\n readonly score: number;\n /**\n * At least one of the `scale`/`force` axes had a resolved (non-conflicting)\n * value on BOTH endpoints, so the axis-compatibility gate produced a real\n * comparison (whether or not it clashed). `false` means every gate axis\n * abstained (silent on one/both sides, or a fold conflict) — the gate had\n * nothing to check. Populated on EVERY candidate, independent of verdict.\n */\n readonly axisChecked: boolean;\n /**\n * Per-axis clash descriptions (`'scale: quantum ≠ cosmological'`), sorted.\n * Empty when no gate axis clashed — including when `axisChecked` is\n * `false`, and even when a stronger falsifier (magnitude/contradictory)\n * won the verdict (no falsifier shadows another's annotation).\n */\n readonly axisClashes: readonly string[];\n /**\n * Standard-physics domains whose canonical equation has a TARGET of this\n * candidate's dimension (Sub-project C). Informational context — an empty\n * list means no canonical equation targets this dimension.\n */\n readonly canonicalKinds: readonly string[];\n /** At least one endpoint name is a quantity a canonical equation uses\n * (weak name-based prior; informational). */\n readonly touchesCanonical: boolean;\n}\n\n/** Options for the discovery loop. @internal */\nexport interface DiscoveryOptions {\n /**\n * Numeric anchor for the consistency check (and the closure unlock).\n * Default `{ mass: M_sun }` — the pre-registered retrodiction anchor.\n */\n readonly groundTruth?: Readonly<Record<string, number>>;\n /** Extra identifications honored as the baseline (default registered). */\n readonly identifications?: readonly QuantityIdentification[];\n /**\n * Max orders of magnitude two identified quantities may differ before the\n * identification is falsified as a `magnitude-clash`. Default {@link DEFAULT_MAX_ORDERS_OF_MAGNITUDE} — generous\n * enough for O(1) dimensionless prefactors and unit-convention slack, strict\n * enough to kill the scale-clash decoys.\n */\n readonly maxOrdersOfMagnitude?: number;\n /**\n * Sourced order-of-magnitude values for the gate (default\n * `REPRESENTATIVE_VALUES`). Injectable for testing and so the future\n * canonical-equation registry can supply them centrally.\n */\n readonly representativeValues?: Readonly<Record<string, RepresentativeValue>>;\n /**\n * Registry-only `scale`/`force`/`information` attributes by quantity name,\n * for the D1 axis-compatibility gate (default: every centralized\n * `Quantity.attributes`). Injectable for testing — never canonical\n * per-equation stamps (Option-A resolution).\n */\n readonly quantityAttributes?: ReadonlyMap<string, RegimeAttributes>;\n}\n\n/**\n * The component partition of quantity names: names in one equation are one component, and so\n * are the two sides of every identification. The shared partition of `quantity-clusters.ts`\n * (a name is an item; its keys are the equations and identifications it appears in). Returns\n * name → a representative of its component (its first name), read only for equality.\n *\n * RECORDED BEHAVIOUR, kept on purpose: an equation with no source (an all-constant right-hand\n * side, `CE-bohr-radius`) registers no name at all, so its target is absent from the map and\n * an identification touching it never counts as merging components. The union-find this\n * replaced only registered a name when it took part in a union, and the census, the canonical\n * funnel counts and the `map --proposed` golden pin that reading. Registering such a target\n * makes `bohr-radius ≟ hubble-distance` promising; whether it should is a catalog decision,\n * not this module's (`tests/composition/quantity-clusters.test.ts` pins the recorded reading\n * by name).\n */\nfunction quantityComponents(\n edges: readonly BridgeEdge[],\n idents: readonly QuantityIdentification[],\n): Map<string, string> {\n const keysByName = new Map<string, string[]>();\n const tag = (name: string, key: string): void => {\n const keys = keysByName.get(name);\n if (keys === undefined) keysByName.set(name, [key]);\n else keys.push(key);\n };\n edges.forEach((e, i) => {\n if (e.sources.length === 0) return; // recorded behaviour: a source-less target is not registered\n for (const s of e.sources) tag(s.name, `edge:${i}`);\n tag(e.target.name, `edge:${i}`);\n });\n idents.forEach((id, i) => {\n tag(id.from, `ident:${i}`);\n tag(id.to, `ident:${i}`);\n });\n const names = [...keysByName.keys()];\n const roots = new Map<string, string>();\n for (const cluster of clusterBySharedKeys(names, (name) => keysByName.get(name) ?? [], (name) => name)) {\n for (const name of cluster) roots.set(name, cluster[0]);\n }\n return roots;\n}\n\n/**\n * The ground truth the funnel anchors magnitudes to when the caller gives none: one solar mass.\n * Exported so the CLI can print the anchor a result is relative to (audit I3).\n * @internal\n */\nexport const ANCHOR_DEFAULT: Readonly<Record<string, number>> = { mass: M_SUN_KG };\n\n/**\n * The magnitude-clash threshold when the caller gives none: how many orders of magnitude two\n * identified quantities may differ. Exported so the CLI states the default it runs with.\n * @internal\n */\nexport const DEFAULT_MAX_ORDERS_OF_MAGNITUDE = 3;\n\n/**\n * Factor every anchor input is multiplied by to probe whether a magnitude ratio\n * depends on the anchor at all (persona finding L3). Three decades moves any\n * log-ratio that depends on the anchor far beyond {@link ANCHOR_INVARIANCE_TOL}.\n * @internal\n */\nconst ANCHOR_PROBE_FACTOR = 1e3;\n\n/** Decades within which a log-ratio counts as unchanged by the probe. @internal */\nconst ANCHOR_INVARIANCE_TOL = 1e-9;\n\n/**\n * One name's hyphen-token set is a STRICT subset of the other's\n * (`mass` ⊂ `reference-mass`, `mass` ⊂ `planck-mass`) — i.e. identifying a\n * generic quantity with one of its own specializations of the same kind.\n * Such an identification is near-tautological (it asserts the specialization\n * IS its genus), not a discovered cross-cluster link, so it is barred from\n * `promising`. Returns false for equal token sets and for pairs sharing only\n * a partial token (`schwarzschild-radius` ⊄ `foerster-radius`).\n */\nfunction nameSubsumes(a: string, b: string): boolean {\n const ta = new Set(a.split('-'));\n const tb = new Set(b.split('-'));\n if (ta.size === tb.size) return false;\n const [small, big] = ta.size < tb.size ? [ta, tb] : [tb, ta];\n for (const t of small) if (!big.has(t)) return false;\n return true;\n}\n\n/**\n * Candidate-invariant state for the discovery funnel — everything in\n * `vetLinkCandidate` that depends only on `(edges, opts)`, not on the specific\n * candidate. Computed once by `buildDiscoveryContext` and shared across every\n * candidate so `rankDiscoveries` does loop-invariant work once instead of N\n * times. Holds read-only structures — `vetInContext` never mutates them, so\n * sharing is safe. `tests/composition/discovery.test.ts` pins that directly:\n * two builds are equal, and vetting every candidate leaves the context equal\n * to a snapshot taken before.\n *\n * @internal\n */\nexport interface DiscoveryContext {\n readonly baseIdents: readonly QuantityIdentification[];\n readonly groundTruth: Readonly<Record<string, number>>;\n /** `Object.keys(groundTruth)` — the anchor known-set names. */\n readonly anchor: readonly string[];\n readonly repVals: Readonly<Record<string, RepresentativeValue>>;\n readonly maxOrders: number;\n /** Registry-only attribute lookup for the D1 axis gate (opts override or\n * `REGISTRY_ATTRIBUTES_BY_NAME`). */\n readonly attributesByName: ReadonlyMap<string, RegimeAttributes>;\n /** `forwardEvaluate(edges, groundTruth, baseIdents)` — anchor magnitudes. */\n readonly anchorValues: ReadonlyMap<string, number>;\n /** The same evaluation with every anchor input times {@link ANCHOR_PROBE_FACTOR}. */\n readonly anchorValuesProbe: ReadonlyMap<string, number>;\n /** `quantityComponents(edges, baseIdents)` — base component roots. */\n readonly comps: ReadonlyMap<string, string>;\n /** `forwardClosure(edges, anchor, baseIdents)` — base determinable set. */\n readonly closureBase: ReadonlySet<string>;\n /**\n * The targets the retrodiction of the anchor finds inconsistent with no\n * hypothesized identification (empty when the base is consistent). An\n * identification of two quantities outside {@link closureBase} cannot fire,\n * so that candidate's numeric report is this one.\n */\n readonly baseInconsistentNodes: readonly string[];\n}\n\n/** Resolve options and compute the candidate-invariant discovery state once.\n * @internal */\nexport function buildDiscoveryContext(\n edges: readonly BridgeEdge[],\n opts: DiscoveryOptions,\n): DiscoveryContext {\n const baseIdents = opts.identifications ?? QUANTITY_IDENTIFICATIONS;\n const groundTruth = opts.groundTruth ?? ANCHOR_DEFAULT;\n const anchor = Object.keys(groundTruth);\n const baseReport = retrodict(edges, groundTruth, { identifications: baseIdents });\n return {\n baseIdents,\n groundTruth,\n anchor,\n repVals: opts.representativeValues ?? REPRESENTATIVE_VALUES,\n maxOrders: opts.maxOrdersOfMagnitude ?? DEFAULT_MAX_ORDERS_OF_MAGNITUDE,\n attributesByName: opts.quantityAttributes ?? REGISTRY_ATTRIBUTES_BY_NAME,\n // Candidate-invariant: the anchor's forward evaluation, the base component\n // partition, and the base forward closure all depend only on (edges, opts).\n anchorValues: forwardEvaluate(edges, groundTruth, baseIdents),\n anchorValuesProbe: forwardEvaluate(\n edges,\n Object.fromEntries(\n Object.entries(groundTruth).map(([k, v]) => [k, v * ANCHOR_PROBE_FACTOR]),\n ),\n baseIdents,\n ),\n comps: quantityComponents(edges, baseIdents),\n closureBase: forwardClosure(edges, anchor, baseIdents),\n baseInconsistentNodes: baseReport.results\n .filter((r) => r.outcome === 'inconsistent')\n .map((r) => r.target)\n .sort(),\n };\n}\n\n/**\n * The quantities whose value can depend on one of `seeds`: the seeds, every\n * `to` of an identification from the set, and every target of an edge with a\n * source in the set, to a fixed point. An over-approximation (an edge that\n * does not fire still lists its target), which is the safe side: a target\n * left out would keep its base retrodiction unexamined.\n */\nfunction dependents(\n edges: readonly BridgeEdge[],\n seeds: readonly string[],\n idents: readonly QuantityIdentification[],\n): Set<string> {\n const set = new Set<string>(seeds);\n let changed = true;\n while (changed) {\n changed = false;\n for (const id of idents) {\n if (set.has(id.from) && !set.has(id.to)) {\n set.add(id.to);\n changed = true;\n }\n }\n for (const e of edges) {\n if (set.has(e.target.name)) continue;\n if (e.sources.some((s) => set.has(s.name))) {\n set.add(e.target.name);\n changed = true;\n }\n }\n }\n return set;\n}\n\n/**\n * The nodes the hypothesis `a ≡ b` makes inconsistent that the base graph did\n * not already have: inconsistent(hypothesis) minus inconsistent(base). Only a\n * node downstream of `a` or `b` can change, so only those nodes are\n * classified and retrodicted, with the same over-determined rule and the\n * same tolerance `retrodict` applies to the whole graph. Sorted.\n */\nfunction newInconsistencies(\n edges: readonly BridgeEdge[],\n groundTruth: Readonly<Record<string, number>>,\n anchor: readonly string[],\n candidate: LinkCandidate,\n withHyp: readonly QuantityIdentification[],\n baseInconsistentNodes: readonly string[],\n): string[] {\n const base = new Set(baseInconsistentNodes);\n const nodes: string[] = [];\n for (const target of [...dependents(edges, [candidate.a, candidate.b], withHyp)].sort()) {\n if (base.has(target)) continue;\n const classified = classifyIdentifiability(edges, anchor, target, { identifications: withHyp });\n if (classified.verdict !== 'over-determined') continue;\n const result = retrodictNode(edges, groundTruth, target, { identifications: withHyp });\n if (result.outcome === 'inconsistent') nodes.push(target);\n }\n return nodes;\n}\n\n/** One endpoint's magnitude for the gate: its value, where it came from, and its σ in decades. */\ninterface GateMagnitude {\n readonly value: number;\n readonly fromAnchor: boolean;\n readonly sigmaDecades: number;\n}\n\n/** What the magnitude gate found for one candidate. @internal */\nexport interface MagnitudeGate {\n /** The two magnitudes, when both were found (the gate ran). */\n readonly a: GateMagnitude | undefined;\n readonly b: GateMagnitude | undefined;\n readonly checked: boolean;\n readonly usedAnchor: boolean;\n /** `|log10|a| − log10|b||`, or null when the gate abstained. */\n readonly ordersApart: number | null;\n /** The verdict: `ordersApart > maxOrders`. */\n readonly clash: boolean;\n /** The calibrated strength `2·Φ(−orders/√(σ_a² + σ_b²))`, or null when the gate abstained. */\n readonly p: number | null;\n /** The endpoints with no magnitude (why the gate abstained); empty when it ran. */\n readonly missing: readonly string[];\n}\n\n/** MathTS `normalCDF`, read back as the number it is. */\nfunction phi(z: number): number {\n const v: unknown = normalCDF(z);\n if (typeof v !== 'number') throw new TypeError('normalCDF did not return a number for a number argument');\n return v;\n}\n\n/**\n * The two-sided tail of a gap of `orders` decades between two values whose log10 carry\n * standard deviations `sigmaA` and `sigmaB` decades: `2·Φ(−orders/√(σ_a² + σ_b²))`, through the\n * NEGATIVE argument (`1 − Φ` rounds a far tail to 0; the lower tail is accurate). A strength of\n * the falsifier, not a credibility score.\n *\n * @internal\n */\nexport function magnitudeStrength(orders: number, sigmaA: number, sigmaB: number): number {\n return 2 * phi(-orders / Math.sqrt(sigmaA * sigmaA + sigmaB * sigmaB));\n}\n\n/**\n * The magnitude gate: an INDEPENDENT falsifier the single-anchor graph cannot make. It runs only\n * when both endpoints have a representative value and abstains (never false-rejects) otherwise.\n * A value comes from the sourced static table first, else — for graph-derived quantities the\n * table omits by design (schwarzschild-radius, thermal-de-broglie-wavelength) — from the\n * quantity's value AT THE REGISTERED ANCHOR, a definite magnitude for this run, with the default\n * σ of one decade. The verdict is `ordersApart > ctx.maxOrders`; `p` is reported beside it.\n *\n * @internal\n */\nexport function magnitudeGate(candidate: LinkCandidate, ctx: DiscoveryContext): MagnitudeGate {\n const magnitudeOf = (name: string): GateMagnitude | undefined => {\n const rv = ctx.repVals[name];\n // Both paths apply the same finite-and-nonzero gate: a 0/∞/NaN magnitude\n // would make log10 blow up (log10(0) = -∞), spoofing or silently disabling\n // the falsifier. Symmetric validation across the table and anchor sources.\n if (rv !== undefined && Number.isFinite(rv.value) && rv.value !== 0) {\n return { value: rv.value, fromAnchor: false, sigmaDecades: sigmaDecadesOf(rv) };\n }\n const av = ctx.anchorValues.get(name);\n if (av !== undefined) {\n const value = av * conventionScaleToSI(name);\n if (Number.isFinite(value) && value !== 0) {\n return { value, fromAnchor: true, sigmaDecades: DEFAULT_SIGMA_DECADES };\n }\n }\n return undefined;\n };\n const a = magnitudeOf(candidate.a);\n const b = magnitudeOf(candidate.b);\n const checked = a !== undefined && b !== undefined;\n const ordersApart = checked ? Math.abs(Math.log10(Math.abs(a.value)) - Math.log10(Math.abs(b.value))) : null;\n return {\n a,\n b,\n checked,\n usedAnchor: checked && (a.fromAnchor || b.fromAnchor),\n ordersApart,\n clash: ordersApart !== null && ordersApart > ctx.maxOrders,\n p: checked && ordersApart !== null ? magnitudeStrength(ordersApart, a.sigmaDecades, b.sigmaDecades) : null,\n missing: [a === undefined ? candidate.a : null, b === undefined ? candidate.b : null].filter(\n (e): e is string => e !== null,\n ),\n };\n}\n\n/**\n * Vet one link candidate by hypothesizing the identification a≡b and\n * measuring its structural and numerical consequences. See module docs.\n *\n * @internal\n */\nexport function vetLinkCandidate(\n edges: readonly BridgeEdge[],\n candidate: LinkCandidate,\n opts: DiscoveryOptions = {},\n): VettedCandidate {\n return vetInContext(edges, candidate, buildDiscoveryContext(edges, opts));\n}\n\n/**\n * The per-candidate vetting body, given pre-computed candidate-invariant\n * `ctx`. The hypothesis-augmented closure is per-candidate. Retrodiction is\n * per-candidate only when an endpoint is already in the anchor closure;\n * otherwise the identification cannot fire and the base report stands.\n *\n * @internal\n */\nexport function vetInContext(\n edges: readonly BridgeEdge[],\n candidate: LinkCandidate,\n ctx: DiscoveryContext,\n): VettedCandidate {\n const {\n baseIdents,\n groundTruth,\n anchor,\n anchorValuesProbe,\n comps,\n closureBase,\n attributesByName,\n baseInconsistentNodes,\n } = ctx;\n\n // Magnitude gate: an INDEPENDENT falsifier the single-anchor graph can't make\n // (`magnitudeGate`): the verdict is the hard threshold, `p` its calibrated strength.\n const gate = magnitudeGate(candidate, ctx);\n const va = gate.a;\n const vb = gate.b;\n const magnitudeChecked = gate.checked;\n const magnitudeUsedAnchor = gate.usedAnchor;\n const ordersApart = gate.ordersApart;\n const magnitudeClash = gate.clash;\n\n // Anchor-invariance probe: re-read each anchor-derived magnitude at the\n // rescaled anchor. A table value is a constant and does not move.\n const probeOf = (\n name: string,\n m: { value: number; fromAnchor: boolean },\n ): number | undefined => {\n if (!m.fromAnchor) return m.value;\n const probe = anchorValuesProbe.get(name);\n return probe === undefined ? undefined : probe * conventionScaleToSI(name);\n };\n let magnitudeAnchorInvariant = false;\n if (magnitudeUsedAnchor) {\n const pa = probeOf(candidate.a, va!);\n const pb = probeOf(candidate.b, vb!);\n if (pa !== undefined && pb !== undefined && pa !== 0 && pb !== 0 && Number.isFinite(pa) && Number.isFinite(pb)) {\n const la = Math.log10(Math.abs(va!.value));\n const lb = Math.log10(Math.abs(vb!.value));\n const lpa = Math.log10(Math.abs(pa));\n const lpb = Math.log10(Math.abs(pb));\n magnitudeAnchorInvariant = Math.abs((lpa - lpb) - (la - lb)) <= ANCHOR_INVARIANCE_TOL;\n }\n }\n\n // A generic↔specialization identification (mass ≟ reference-mass) is\n // near-tautological — barred from `promising` regardless of structure.\n const subsuming = nameSubsumes(candidate.a, candidate.b);\n\n // Axis-compatibility gate (D1): a literal identity cannot have endpoints\n // whose STATED scale/force regimes disagree. Either side silent on an\n // axis (including a fold-conflict, resolved as unstated by\n // `effectiveAttributes`) abstains that axis rather than clashing.\n // Populated on every candidate, independent of which verdict wins.\n const attrsA = effectiveAttributes(candidate.a, attributesByName, baseIdents);\n const attrsB = effectiveAttributes(candidate.b, attributesByName, baseIdents);\n const checkedAxes: string[] = [];\n const axisClashes: string[] = [];\n const axisUnstated: { axis: string; endpoints: string[] }[] = [];\n // GATE_AXES is registry-derived (string[]); every registry axis name is a\n // RegimeAttributes key whose value is a string union — read via a string record.\n const recA = attrsA as Record<string, string | undefined>;\n const recB = attrsB as Record<string, string | undefined>;\n for (const axis of GATE_AXES) {\n const av = recA[axis];\n const bv = recB[axis];\n if (av === undefined || bv === undefined) {\n axisUnstated.push({\n axis,\n endpoints: [av === undefined ? candidate.a : null, bv === undefined ? candidate.b : null].filter(\n (e): e is string => e !== null,\n ),\n });\n continue; // abstain\n }\n checkedAxes.push(axis);\n if (av !== bv) axisClashes.push(`${axis}: ${av} ≠ ${bv}`);\n }\n axisClashes.sort();\n const axisChecked = checkedAxes.length > 0;\n const axisClash = axisClashes.length > 0;\n\n // The hypothesized identification, added in both directions so a≡b is a\n // full merge (identifications are directional in the engine).\n const rationale = `HYPOTHESIS (unadjudicated): ${candidate.a} ≡ ${candidate.b} — same dimension ${candidate.dim}`;\n const hypothesis: QuantityIdentification[] = [\n { from: candidate.a, to: candidate.b, rationale },\n { from: candidate.b, to: candidate.a, rationale },\n ];\n const withHyp = [...baseIdents, ...hypothesis];\n\n // Structural: does a≡b merge two components? (base partition from ctx)\n const mergesComponents =\n comps.has(candidate.a) &&\n comps.has(candidate.b) &&\n comps.get(candidate.a) !== comps.get(candidate.b);\n\n // Neither endpoint in the anchor closure means the identification cannot\n // fire: `forwardClosure` copies a value across `a ≡ b` only from a\n // determinable side. The hypothesis closure is then the base closure, and no\n // value in the graph moves, so neither closure nor retrodiction is recomputed\n // (9.0.0 audit §4 C9: 4004 of 4196 catalog candidates are this case).\n const touchesClosure = closureBase.has(candidate.a) || closureBase.has(candidate.b);\n\n // Closure unlock: forward closure from the anchor, with vs without. The base\n // closure is candidate-invariant (ctx); only the hypothesis closure is fresh.\n const closureHyp = touchesClosure ? forwardClosure(edges, anchor, withHyp) : closureBase;\n const unlocksFromAnchor = [...closureHyp]\n .filter((q) => !closureBase.has(q))\n .sort();\n\n // Numeric: the hypothesis must not CREATE a disagreement. A node whose\n // routes already disagree in the base graph is the graph's finding\n // (`retrodict` reports it), not this candidate's, so the attribution is\n // inconsistent(hypothesis) minus inconsistent(base) (9.0.0 audit §4 C4).\n // Only a target downstream of `a` or `b` can change under the hypothesis;\n // every other target's retrodiction is the base one, so only those targets\n // are retrodicted (§4 C9).\n const inconsistentNodes = touchesClosure\n ? newInconsistencies(edges, groundTruth, anchor, candidate, withHyp, baseInconsistentNodes)\n : [];\n const numericallyConsistent = inconsistentNodes.length === 0;\n\n // Verdict precedence: a magnitude clash is the most decisive, most\n // interpretable falsification, checked before the graph contradiction;\n // the regime-label prior (axis-clash) is weaker than both numeric\n // falsifiers and is not one itself, but still outranks promising/inert (D1).\n let verdict: VettedCandidate['verdict'];\n if (magnitudeClash) verdict = 'magnitude-clash';\n else if (!numericallyConsistent) verdict = 'contradictory';\n else if (axisClash) verdict = 'axis-clash';\n else if (mergesComponents && unlocksFromAnchor.length > 0 && !subsuming)\n verdict = 'promising';\n else verdict = 'inert';\n\n // Score: all falsifications sink to the bottom; a subsuming (tautological)\n // identification sinks within `inert`; otherwise reward structural merges,\n // unlocks, and the proposer's weak priors.\n let score = 0;\n if (magnitudeClash || !numericallyConsistent || axisClash) {\n score = -1;\n } else if (subsuming) {\n score = 0;\n } else {\n score += mergesComponents ? 4 : 0;\n score += Math.min(unlocksFromAnchor.length, 4);\n score += candidate.touchesCore ? 1 : 0;\n score += candidate.sameKind ? 1 : 0;\n }\n\n const canonicalKinds = CANONICAL_KINDS_BY_DIM.get(candidate.dim) ?? [];\n const touchesCanonical =\n CANONICAL_QUANTITY_NAMES.has(candidate.a) ||\n CANONICAL_QUANTITY_NAMES.has(candidate.b);\n\n return {\n a: candidate.a,\n b: candidate.b,\n dim: candidate.dim,\n touchesCore: candidate.touchesCore,\n sameKind: candidate.sameKind,\n mergesComponents,\n unlocksFromAnchor,\n numericallyConsistent,\n inconsistentNodes,\n ordersApart,\n magnitudeP: gate.p,\n magnitudeChecked,\n magnitudeUsedAnchor,\n magnitudeAnchorInvariant,\n magnitudeMissing: gate.missing,\n axisUnstated,\n subsuming,\n verdict,\n score,\n axisChecked,\n axisClashes,\n canonicalKinds,\n touchesCanonical,\n };\n}\n\n/**\n * Run the full discovery funnel: propose cross-cluster candidates, vet each\n * against the inference suite, and rank — `promising` first, then by score.\n * The output is the physicist's worklist (vetted, provenance-traced), not a\n * list of discoveries.\n *\n * @public\n */\nexport function rankDiscoveries(\n edges: readonly BridgeEdge[],\n opts: DiscoveryOptions = {},\n): VettedCandidate[] {\n const VERDICT_RANK: Record<VettedCandidate['verdict'], number> = {\n promising: 0,\n inert: 1,\n 'magnitude-clash': 2,\n contradictory: 3,\n 'axis-clash': 4,\n };\n const ctx = buildDiscoveryContext(edges, opts);\n const candidates = proposeLinkCandidates(edges);\n const vetted = candidates.map((c) => vetInContext(edges, c, ctx));\n vetted.sort(\n (x, y) =>\n VERDICT_RANK[x.verdict] - VERDICT_RANK[y.verdict] ||\n y.score - x.score ||\n x.a.localeCompare(y.a) ||\n x.b.localeCompare(y.b),\n );\n return vetted;\n}\n"]}
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/composition/discovery.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Discovery loop — vet link candidates through the verification primitives\n * (Direction 2).\n *\n * `proposeLinkCandidates` surfaces cross-cluster quantity pairs that share a\n * dimension and hands the raw coincidences to a human (the counts at a given\n * catalog are measured by the tests and recorded in NOTES.md). This\n * module closes the loop: it HYPOTHESIZES each candidate identification\n * `a ≡ b` and tests it with the machinery that already exists —\n *\n * - mergesComponents — does identifying a≡b connect two previously\n * disconnected parts of the graph? (structural\n * significance)\n * - unlocksFromAnchor — what becomes determinable from an anchor input\n * set WITH the identification that was not before?\n * (`forwardClosure`)\n * - numericallyConsistent — does retrodiction over the graph stay\n * all-consistent once the identification is\n * added, or does it introduce a contradiction?\n * (`retrodict` — the STRONG filter)\n *\n * so the physicist adjudicates a handful of VETTED, provenance-traced\n * candidates instead of dimensional noise.\n *\n * ⚠ Still a REVIEW SURFACE, never automated discovery (Part-VI §XXVII-B). A\n * shared dimension is a weak prior; the weight is on the numeric and\n * structural signals, and the numeric check only exercises the part of the\n * graph reachable from the anchor. `promising` means \"worth a physicist's\n * minute\", not \"true\".\n *\n * INTERNAL — not on the public surface (mirrors bridge-analysis.ts);\n * surfaced via `upt discover`.\n *\n * @module composition/discovery\n */\n\nimport type { BridgeEdge } from './edge.js';\nimport type { QuantityIdentification } from './compose.js';\nimport { QUANTITY_IDENTIFICATIONS, effectiveAttributes } from './compose.js';\nimport { GATE_AXES } from './axes.js';\nimport type { RegimeAttributes } from './quantity.js';\nimport { classifyIdentifiability, forwardClosure } from './identifiability.js';\nimport { retrodict, retrodictNode, forwardEvaluate } from './retrodiction.js';\nimport { proposeLinkCandidates } from './bridge-analysis.js';\nimport type { LinkCandidate } from './bridge-analysis.js';\nimport { clusterBySharedKeys } from './quantity-clusters.js';\nimport { normalCDF } from '@danielsimonjr/mathts-functions';\nimport { M_SUN_KG } from '../core/constants.js';\nimport { DEFAULT_SIGMA_DECADES, REPRESENTATIVE_VALUES, sigmaDecadesOf } from './representative-values.js';\nimport type { RepresentativeValue } from './representative-values.js';\nimport { conventionScaleToSI } from '../dimensional/unit-convention.js';\nimport { allQuantities } from './quantities.js';\nimport { CANONICAL_EQUATIONS } from '../canonical/registry.js';\nimport { format } from '../dimensional/algebra.js';\nimport { deriveOwnEquationMagnitudes } from './own-equation-magnitude.js';\nimport type { DerivedMagnitudes } from './own-equation-magnitude.js';\n\n// ── registry-only attribute lookup (D1 axis gate; Option-A resolution) ─────\n// Built from the centralized `Quantity` nodes ONLY (never canonical\n// per-equation stamps — audit adjudication F1). Names that aren't a\n// registry node (most bare canonical-equation variables) are simply absent,\n// so they always abstain — the canonical-only axis-clash=0 invariant holds\n// by construction, not by a special case.\n/** The canonical quantity→attributes map (registry nodes + per-equation stamps).\n * Exported for the axis-discrimination audit. @internal */\nexport const REGISTRY_ATTRIBUTES_BY_NAME: ReadonlyMap<string, RegimeAttributes> = new Map(\n allQuantities().map((quantity) => [quantity.name, quantity.attributes]),\n);\n\n// The axes the D1 regime-label gate compares are `GATE_AXES`, imported from the\n// `axes.ts` registry (`AXES.filter(gated)`) at the top of this module. Currently\n// scale + force; the 2026-07-05 extensible-axis expansion added\n// symmetry/topology/statistics as UNGATED attributes (they only abstain until the\n// discrimination audit earns them a gate — flip `gated: true` in axes.ts).\n\n// ── canonical-kind indexes (Sub-project C) ──────────────────────────────────\n// Discovery quantity names and canonical variable names are different\n// namespaces; the alignable axis is the DIMENSION. These are informational\n// annotations on each candidate (no verdict/score effect): they give the\n// physicist standard-physics context, not a hard filter (the registry is small).\nconst CANONICAL_KINDS_BY_DIM: ReadonlyMap<string, readonly string[]> = (() => {\n const m = new Map<string, Set<string>>();\n for (const ce of CANONICAL_EQUATIONS) {\n const key = format(ce.dimensional.target.dim);\n (m.get(key) ?? m.set(key, new Set()).get(key)!).add(ce.domain);\n }\n return new Map([...m].map(([k, v]) => [k, [...v].sort()]));\n})();\n\nconst CANONICAL_QUANTITY_NAMES: ReadonlySet<string> = (() => {\n const s = new Set<string>();\n for (const ce of CANONICAL_EQUATIONS) {\n s.add(ce.dimensional.target.name);\n for (const g of ce.dimensional.governing) s.add(g.name);\n }\n return s;\n})();\n\n/** A candidate after vetting against the inference suite. @hypothesis @public */\nexport interface VettedCandidate {\n readonly a: string;\n readonly b: string;\n /** Shared SI dimension (from the proposer). */\n readonly dim: string;\n /** One endpoint is in the anchored (established) cluster. */\n readonly touchesCore: boolean;\n /** The names share a word token (same physical KIND). */\n readonly sameKind: boolean;\n /**\n * Identifying a≡b merges two distinct connected components of the\n * quantity graph — it connects physics the catalog keeps separate.\n */\n readonly mergesComponents: boolean;\n /**\n * Quantities newly determinable from the anchor known-set once the\n * identification is added (structural unlock). Sorted.\n */\n readonly unlocksFromAnchor: readonly string[];\n /**\n * Retrodiction over the graph WITH the hypothesized identification creates\n * no numerical contradiction in the anchor-reachable subgraph. The strong\n * filter. A node whose routes already disagree in the base graph is the\n * graph's finding (`retrodict` reports it) and does not make a candidate\n * inconsistent; only a disagreement the hypothesis CREATES does.\n */\n readonly numericallyConsistent: boolean;\n /** Nodes that became inconsistent under the identification and were not\n * inconsistent in the base graph (the falsification, when\n * `numericallyConsistent` is false). Sorted. */\n readonly inconsistentNodes: readonly string[];\n /**\n * Orders of magnitude between the representative values of a and b\n * (`|log10|va| − log10|vb||`), or `null` when either has no representative\n * value (the gate abstained — see `magnitudeChecked`).\n */\n readonly ordersApart: number | null;\n /**\n * The calibrated STRENGTH of the magnitude falsifier against the two values' stated\n * uncertainties: `2·Φ(−ordersApart / √(σ_a² + σ_b²))`, with σ in decades per representative\n * value (`sigmaDecades`, default one decade: the table's \"good to about one order\"; an\n * anchor-derived value takes the default; a value from the quantity's own equation, its\n * propagated σ), through MathTS `normalCDF` of the negative\n * argument so a far tail is not rounded to 0 by `1 − cdf`. It is reported BESIDE the verdict\n * and changes no verdict: the clash threshold stays `ordersApart > maxOrders`. A small p says\n * the two magnitudes are hard to reconcile at their stated precision; 2.9 decades at σ = 1\n * is p ≈ 0.040, no longer a clean pass, while 3.1 decades stays a clash. NOT a credibility\n * score: it says nothing about whether the identification is true. `null` when the gate\n * abstained. Optional on the type; the funnel always sets it.\n */\n readonly magnitudeP?: number | null;\n /** Both endpoints had a representative value, so the magnitude gate ran. */\n readonly magnitudeChecked: boolean;\n /** The magnitude gate read at least one endpoint value from the graph at\n * the anchor (no static table entry) rather than the sourced table. A value\n * the graph computes from registered constants alone, with no anchor input\n * (the target of a source-less equation), is a constant and is not\n * anchor-derived. */\n readonly magnitudeUsedAnchor: boolean;\n /**\n * The magnitude comparison used an anchor-derived value, and the ratio of the\n * two magnitudes does NOT move when every anchor input is rescaled by\n * {@link ANCHOR_PROBE_FACTOR}. The graph then fixes the ratio at every\n * anchor, so agreement in magnitude is an identity of the graph and not\n * evidence for the identification. The case that exposed it: the anchor sets\n * the temperature to the Hawking temperature of the anchor mass M, and then\n * the thermal wavelength is exactly 4π ℓ_P for EVERY M. Its anchor-derived\n * magnitude is not a representative magnitude of the quantity at all.\n * A quantity whose value really is a constant is not anchor-derived: the\n * sourced table states it, or the graph computes it from registered constants\n * with no anchor input (`DiscoveryContext.constantValues`), and the probe does\n * not withhold a comparison of two such values, which can fail by the clash\n * threshold.\n * A clash stays a clash (the graph itself fixes the two apart); only a pass is\n * withheld, in `describeGrounding`. Optional on the type so hand-built\n * candidates need not state it; the funnel always sets it.\n */\n readonly magnitudeAnchorInvariant?: boolean;\n /**\n * The endpoints whose magnitude the gate took from the quantity's own equation\n * (`own-equation-magnitude.ts`): no table value and no no-anchor constant, but\n * an edge into it whose every source the table or the constants value, at a\n * propagated σ within one decade. Such a value is not anchor-derived. Empty\n * when neither endpoint's magnitude came from there. Optional on the type; the\n * funnel always sets it.\n */\n readonly magnitudeDerived?: readonly string[];\n /**\n * The endpoints with no representative magnitude (not in the sourced table,\n * not a no-anchor constant, not derived from their own equation, not valued\n * at the anchor) — why the magnitude gate abstained. Empty when it ran.\n * Optional on the type; the funnel always sets it.\n */\n readonly magnitudeMissing?: readonly string[];\n /**\n * Per gate axis that abstained, the endpoints that state no value on it —\n * why the axis gate had nothing to compare. Optional; the funnel always sets it.\n */\n readonly axisUnstated?: readonly { readonly axis: string; readonly endpoints: readonly string[] }[];\n /**\n * One endpoint name's hyphen-token set is a strict subset of the other's\n * (`mass` ⊂ `reference-mass`) — a generic↔specific identification of the\n * same KIND. Near-tautological (identifying a generic quantity with one of\n * its own specializations), so it is barred from `promising`.\n */\n readonly subsuming: boolean;\n /**\n * - `magnitude-clash` — representative values differ by more than the\n * threshold N orders (an independent falsification the\n * single-anchor graph cannot make). Checked first.\n * - `contradictory` — breaks numerical consistency (a graph falsification).\n * - `axis-clash` — the identification's stated `scale`/`force` regime\n * attributes disagree. A regime-LABEL prior against a\n * literal identity (D1), not a physical test: it\n * compares catalog labels and runs no computation, so\n * it is never counted or described as a falsification.\n * - `promising` — consistent AND connects disconnected physics AND\n * unlocks ≥1 quantity, AND the magnitude falsifier ran\n * and could have failed ({@link hasMagnitudeEvidence}).\n * Worth physicist review.\n * - `promising-unchecked` — the same structure, but the magnitude falsifier\n * never tested it: it abstained (an endpoint has no\n * representative magnitude) or its pass is anchor-invariant\n * and so could not fail. A separate count, never added to\n * `promising` (AGENTS law 4).\n * - `inert` — consistent but structurally/numerically idle (a\n * dimensional coincidence with no consequence).\n */\n readonly verdict: 'promising' | 'promising-unchecked' | 'inert' | 'contradictory' | 'magnitude-clash' | 'axis-clash';\n /** Composite ranking score (higher = more worth review). */\n readonly score: number;\n /**\n * At least one of the `scale`/`force` axes had a resolved (non-conflicting)\n * value on BOTH endpoints, so the axis-compatibility gate produced a real\n * comparison (whether or not it clashed). `false` means every gate axis\n * abstained (silent on one/both sides, or a fold conflict) — the gate had\n * nothing to check. Populated on EVERY candidate, independent of verdict.\n */\n readonly axisChecked: boolean;\n /**\n * Per-axis clash descriptions (`'scale: quantum ≠ cosmological'`), sorted.\n * Empty when no gate axis clashed — including when `axisChecked` is\n * `false`, and even when a stronger falsifier (magnitude/contradictory)\n * won the verdict (no falsifier shadows another's annotation).\n */\n readonly axisClashes: readonly string[];\n /**\n * Standard-physics domains whose canonical equation has a TARGET of this\n * candidate's dimension (Sub-project C). Informational context — an empty\n * list means no canonical equation targets this dimension.\n */\n readonly canonicalKinds: readonly string[];\n /** At least one endpoint name is a quantity a canonical equation uses\n * (weak name-based prior; informational). */\n readonly touchesCanonical: boolean;\n}\n\n/**\n * Whether the magnitude falsifier ran on a candidate AND could have failed: both endpoints had a\n * representative magnitude (`magnitudeChecked`) and the comparison is not anchor-invariant. An\n * anchor-invariant pass is fixed by the graph at every anchor, so it cannot fail and is not a\n * check (AGENTS law 2). The one owner of this predicate: the verdict split between `promising`\n * and `promising-unchecked`, the CLI readiness count and the grounding ledger all read it.\n * @internal\n */\nexport function hasMagnitudeEvidence(\n c: Pick<VettedCandidate, 'magnitudeChecked' | 'magnitudeAnchorInvariant'>,\n): boolean {\n return c.magnitudeChecked && c.magnitudeAnchorInvariant !== true;\n}\n\n/**\n * Whether a verdict is one of the two promising tiers, `promising` (with magnitude evidence) or\n * `promising-unchecked` (without): the structural set the consequence and proposal layers\n * process, so splitting the tiers loses no proposal. A selector, never a count: the two tiers are\n * reported apart and never added (AGENTS law 4).\n * @internal\n */\nexport function isPromisingTier(verdict: VettedCandidate['verdict']): boolean {\n return verdict === 'promising' || verdict === 'promising-unchecked';\n}\n\n/** Options for the discovery loop. @internal */\nexport interface DiscoveryOptions {\n /**\n * Numeric anchor for the consistency check (and the closure unlock).\n * Default `{ mass: M_sun }` — the pre-registered retrodiction anchor.\n */\n readonly groundTruth?: Readonly<Record<string, number>>;\n /** Extra identifications honored as the baseline (default registered). */\n readonly identifications?: readonly QuantityIdentification[];\n /**\n * Max orders of magnitude two identified quantities may differ before the\n * identification is falsified as a `magnitude-clash`. Default {@link DEFAULT_MAX_ORDERS_OF_MAGNITUDE} — generous\n * enough for O(1) dimensionless prefactors and unit-convention slack, strict\n * enough to kill the scale-clash decoys.\n */\n readonly maxOrdersOfMagnitude?: number;\n /**\n * Sourced order-of-magnitude values for the gate (default\n * `REPRESENTATIVE_VALUES`). Injectable for testing and so the future\n * canonical-equation registry can supply them centrally.\n */\n readonly representativeValues?: Readonly<Record<string, RepresentativeValue>>;\n /**\n * Registry-only `scale`/`force`/`information` attributes by quantity name,\n * for the D1 axis-compatibility gate (default: every centralized\n * `Quantity.attributes`). Injectable for testing — never canonical\n * per-equation stamps (Option-A resolution).\n */\n readonly quantityAttributes?: ReadonlyMap<string, RegimeAttributes>;\n}\n\n/**\n * The component partition of quantity names: names in one equation are one component, and so\n * are the two sides of every identification. The shared partition of `quantity-clusters.ts`\n * (a name is an item; its keys are the equations and identifications it appears in). Returns\n * name → a representative of its component (its first name), read only for equality.\n *\n * An equation with no source (an all-constant right-hand side, `CE-bohr-radius`) registers its\n * target under its own key, so the target is a component member of its own and an identification\n * touching it can merge components (the owner's decision of 2026-10-16, option 1). Before it, the\n * union-find this replaced registered a name only when it took part in a union, so the target of\n * every source-less equation (eight on the canonical graph) was absent from the map and no\n * identification touching it merged anything.\n */\nfunction quantityComponents(\n edges: readonly BridgeEdge[],\n idents: readonly QuantityIdentification[],\n): Map<string, string> {\n const keysByName = new Map<string, string[]>();\n const tag = (name: string, key: string): void => {\n const keys = keysByName.get(name);\n if (keys === undefined) keysByName.set(name, [key]);\n else keys.push(key);\n };\n edges.forEach((e, i) => {\n for (const s of e.sources) tag(s.name, `edge:${i}`);\n tag(e.target.name, `edge:${i}`);\n });\n idents.forEach((id, i) => {\n tag(id.from, `ident:${i}`);\n tag(id.to, `ident:${i}`);\n });\n const names = [...keysByName.keys()];\n const roots = new Map<string, string>();\n for (const cluster of clusterBySharedKeys(names, (name) => keysByName.get(name) ?? [], (name) => name)) {\n for (const name of cluster) roots.set(name, cluster[0]);\n }\n return roots;\n}\n\n/**\n * The ground truth the funnel anchors magnitudes to when the caller gives none: one solar mass.\n * Exported so the CLI can print the anchor a result is relative to (audit I3).\n * @internal\n */\nexport const ANCHOR_DEFAULT: Readonly<Record<string, number>> = { mass: M_SUN_KG };\n\n/**\n * The magnitude-clash threshold when the caller gives none: how many orders of magnitude two\n * identified quantities may differ. Exported so the CLI states the default it runs with.\n * @internal\n */\nexport const DEFAULT_MAX_ORDERS_OF_MAGNITUDE = 3;\n\n/**\n * Factor every anchor input is multiplied by to probe whether a magnitude ratio\n * depends on the anchor at all (persona finding L3). Three decades moves any\n * log-ratio that depends on the anchor far beyond {@link ANCHOR_INVARIANCE_TOL}.\n * @internal\n */\nconst ANCHOR_PROBE_FACTOR = 1e3;\n\n/** Decades within which a log-ratio counts as unchanged by the probe. @internal */\nconst ANCHOR_INVARIANCE_TOL = 1e-9;\n\n/**\n * One name's hyphen-token set is a STRICT subset of the other's\n * (`mass` ⊂ `reference-mass`, `mass` ⊂ `planck-mass`) — i.e. identifying a\n * generic quantity with one of its own specializations of the same kind.\n * Such an identification is near-tautological (it asserts the specialization\n * IS its genus), not a discovered cross-cluster link, so it is barred from\n * `promising`. Returns false for equal token sets and for pairs sharing only\n * a partial token (`schwarzschild-radius` ⊄ `foerster-radius`).\n */\nfunction nameSubsumes(a: string, b: string): boolean {\n const ta = new Set(a.split('-'));\n const tb = new Set(b.split('-'));\n if (ta.size === tb.size) return false;\n const [small, big] = ta.size < tb.size ? [ta, tb] : [tb, ta];\n for (const t of small) if (!big.has(t)) return false;\n return true;\n}\n\n/**\n * Candidate-invariant state for the discovery funnel — everything in\n * `vetLinkCandidate` that depends only on `(edges, opts)`, not on the specific\n * candidate. Computed once by `buildDiscoveryContext` and shared across every\n * candidate so `rankDiscoveries` does loop-invariant work once instead of N\n * times. Holds read-only structures — `vetInContext` never mutates them, so\n * sharing is safe. `tests/composition/discovery.test.ts` pins that directly:\n * two builds are equal, and vetting every candidate leaves the context equal\n * to a snapshot taken before.\n *\n * @internal\n */\nexport interface DiscoveryContext {\n readonly baseIdents: readonly QuantityIdentification[];\n readonly groundTruth: Readonly<Record<string, number>>;\n /** `Object.keys(groundTruth)` — the anchor known-set names. */\n readonly anchor: readonly string[];\n readonly repVals: Readonly<Record<string, RepresentativeValue>>;\n readonly maxOrders: number;\n /** Registry-only attribute lookup for the D1 axis gate (opts override or\n * `REGISTRY_ATTRIBUTES_BY_NAME`). */\n readonly attributesByName: ReadonlyMap<string, RegimeAttributes>;\n /** `forwardEvaluate(edges, groundTruth, baseIdents)` — anchor magnitudes. */\n readonly anchorValues: ReadonlyMap<string, number>;\n /**\n * `forwardEvaluate(edges, {}, baseIdents)` — the values the graph fixes with NO anchor input:\n * the targets of source-less equations (an all-constant right-hand side such as the Rydberg\n * energy or the Planck length) and whatever they alone determine. Such a value is a constant,\n * not an anchor-derived magnitude, so the magnitude gate reads it as `fromAnchor: false`.\n */\n readonly constantValues: ReadonlyMap<string, number>;\n /**\n * The magnitudes the graph derives from the sourced table and the constants alone, never from\n * the anchor (`deriveOwnEquationMagnitudes`), with every refusal and its reason: a σ above one\n * decade, two disagreeing edges, or a σ that could not be measured.\n */\n readonly derivedMagnitudes: DerivedMagnitudes;\n /** The same evaluation with every anchor input times {@link ANCHOR_PROBE_FACTOR}. */\n readonly anchorValuesProbe: ReadonlyMap<string, number>;\n /** `quantityComponents(edges, baseIdents)` — base component roots. */\n readonly comps: ReadonlyMap<string, string>;\n /** `forwardClosure(edges, anchor, baseIdents)` — base determinable set. */\n readonly closureBase: ReadonlySet<string>;\n /**\n * The targets the retrodiction of the anchor finds inconsistent with no\n * hypothesized identification (empty when the base is consistent). An\n * identification of two quantities outside {@link closureBase} cannot fire,\n * so that candidate's numeric report is this one.\n */\n readonly baseInconsistentNodes: readonly string[];\n}\n\n/** Resolve options and compute the candidate-invariant discovery state once.\n * @internal */\nexport function buildDiscoveryContext(\n edges: readonly BridgeEdge[],\n opts: DiscoveryOptions,\n): DiscoveryContext {\n const baseIdents = opts.identifications ?? QUANTITY_IDENTIFICATIONS;\n const groundTruth = opts.groundTruth ?? ANCHOR_DEFAULT;\n const anchor = Object.keys(groundTruth);\n const baseReport = retrodict(edges, groundTruth, { identifications: baseIdents });\n const repVals = opts.representativeValues ?? REPRESENTATIVE_VALUES;\n const constantValues = forwardEvaluate(edges, {}, baseIdents);\n return {\n baseIdents,\n groundTruth,\n anchor,\n repVals,\n maxOrders: opts.maxOrdersOfMagnitude ?? DEFAULT_MAX_ORDERS_OF_MAGNITUDE,\n attributesByName: opts.quantityAttributes ?? REGISTRY_ATTRIBUTES_BY_NAME,\n // Candidate-invariant: the anchor's forward evaluation, the base component\n // partition, and the base forward closure all depend only on (edges, opts).\n anchorValues: forwardEvaluate(edges, groundTruth, baseIdents),\n constantValues,\n derivedMagnitudes: deriveOwnEquationMagnitudes(edges, {\n representativeValues: repVals,\n constantValues,\n identifications: baseIdents,\n }),\n anchorValuesProbe: forwardEvaluate(\n edges,\n Object.fromEntries(\n Object.entries(groundTruth).map(([k, v]) => [k, v * ANCHOR_PROBE_FACTOR]),\n ),\n baseIdents,\n ),\n comps: quantityComponents(edges, baseIdents),\n closureBase: forwardClosure(edges, anchor, baseIdents),\n baseInconsistentNodes: baseReport.results\n .filter((r) => r.outcome === 'inconsistent')\n .map((r) => r.target)\n .sort(),\n };\n}\n\n/**\n * The quantities whose value can depend on one of `seeds`: the seeds, every\n * `to` of an identification from the set, and every target of an edge with a\n * source in the set, to a fixed point. An over-approximation (an edge that\n * does not fire still lists its target), which is the safe side: a target\n * left out would keep its base retrodiction unexamined.\n */\nfunction dependents(\n edges: readonly BridgeEdge[],\n seeds: readonly string[],\n idents: readonly QuantityIdentification[],\n): Set<string> {\n const set = new Set<string>(seeds);\n let changed = true;\n while (changed) {\n changed = false;\n for (const id of idents) {\n if (set.has(id.from) && !set.has(id.to)) {\n set.add(id.to);\n changed = true;\n }\n }\n for (const e of edges) {\n if (set.has(e.target.name)) continue;\n if (e.sources.some((s) => set.has(s.name))) {\n set.add(e.target.name);\n changed = true;\n }\n }\n }\n return set;\n}\n\n/**\n * The nodes the hypothesis `a ≡ b` makes inconsistent that the base graph did\n * not already have: inconsistent(hypothesis) minus inconsistent(base). Only a\n * node downstream of `a` or `b` can change, so only those nodes are\n * classified and retrodicted, with the same over-determined rule and the\n * same tolerance `retrodict` applies to the whole graph. Sorted.\n */\nfunction newInconsistencies(\n edges: readonly BridgeEdge[],\n groundTruth: Readonly<Record<string, number>>,\n anchor: readonly string[],\n candidate: LinkCandidate,\n withHyp: readonly QuantityIdentification[],\n baseInconsistentNodes: readonly string[],\n): string[] {\n const base = new Set(baseInconsistentNodes);\n const nodes: string[] = [];\n for (const target of [...dependents(edges, [candidate.a, candidate.b], withHyp)].sort()) {\n if (base.has(target)) continue;\n const classified = classifyIdentifiability(edges, anchor, target, { identifications: withHyp });\n if (classified.verdict !== 'over-determined') continue;\n const result = retrodictNode(edges, groundTruth, target, { identifications: withHyp });\n if (result.outcome === 'inconsistent') nodes.push(target);\n }\n return nodes;\n}\n\n/** One endpoint's magnitude for the gate: its value, where it came from, and its σ in decades. */\ninterface GateMagnitude {\n readonly value: number;\n readonly fromAnchor: boolean;\n /** Derived from the quantity's own equation (`ctx.derivedMagnitudes`). */\n readonly fromOwnEquation: boolean;\n readonly sigmaDecades: number;\n}\n\n/** What the magnitude gate found for one candidate. @internal */\nexport interface MagnitudeGate {\n /** The two magnitudes, when both were found (the gate ran). */\n readonly a: GateMagnitude | undefined;\n readonly b: GateMagnitude | undefined;\n readonly checked: boolean;\n readonly usedAnchor: boolean;\n /** `|log10|a| − log10|b||`, or null when the gate abstained. */\n readonly ordersApart: number | null;\n /** The verdict: `ordersApart > maxOrders`. */\n readonly clash: boolean;\n /** The calibrated strength `2·Φ(−orders/√(σ_a² + σ_b²))`, or null when the gate abstained. */\n readonly p: number | null;\n /** The endpoints with no magnitude (why the gate abstained); empty when it ran. */\n readonly missing: readonly string[];\n /** The endpoints whose magnitude came from their own equation, in candidate order. */\n readonly derived: readonly string[];\n}\n\n/** MathTS `normalCDF`, read back as the number it is. */\nfunction phi(z: number): number {\n const v: unknown = normalCDF(z);\n if (typeof v !== 'number') throw new TypeError('normalCDF did not return a number for a number argument');\n return v;\n}\n\n/**\n * The two-sided tail of a gap of `orders` decades between two values whose log10 carry\n * standard deviations `sigmaA` and `sigmaB` decades: `2·Φ(−orders/√(σ_a² + σ_b²))`, through the\n * NEGATIVE argument (`1 − Φ` rounds a far tail to 0; the lower tail is accurate). A strength of\n * the falsifier, not a credibility score.\n *\n * @internal\n */\nexport function magnitudeStrength(orders: number, sigmaA: number, sigmaB: number): number {\n return 2 * phi(-orders / Math.sqrt(sigmaA * sigmaA + sigmaB * sigmaB));\n}\n\n/**\n * The magnitude gate: an INDEPENDENT falsifier the single-anchor graph cannot make. It runs only\n * when both endpoints have a representative value and abstains (never false-rejects) otherwise.\n * A value comes from the sourced static table first; else from the value the graph fixes with NO\n * anchor input (a constant computed from registered constants alone, `ctx.constantValues`: not\n * anchor-derived); else from the quantity's own equation evaluated from the table and the\n * constants (`ctx.derivedMagnitudes`, at its propagated σ, never above one decade: not\n * anchor-derived); else — for graph-derived quantities the table omits by design\n * (schwarzschild-radius, thermal-de-broglie-wavelength) — from the quantity's value AT THE\n * REGISTERED ANCHOR, a definite magnitude for this run. The constant and the anchor value take the\n * default σ of one decade. The verdict is `ordersApart > ctx.maxOrders`; `p` is reported beside it.\n *\n * @internal\n */\nexport function magnitudeGate(candidate: LinkCandidate, ctx: DiscoveryContext): MagnitudeGate {\n const magnitudeOf = (name: string): GateMagnitude | undefined => {\n const rv = ctx.repVals[name];\n // Both paths apply the same finite-and-nonzero gate: a 0/∞/NaN magnitude\n // would make log10 blow up (log10(0) = -∞), spoofing or silently disabling\n // the falsifier. Symmetric validation across the table and anchor sources.\n if (rv !== undefined && Number.isFinite(rv.value) && rv.value !== 0) {\n return { value: rv.value, fromAnchor: false, fromOwnEquation: false, sigmaDecades: sigmaDecadesOf(rv) };\n }\n // A value the graph fixes with no anchor input is a constant: comparing it can fail by the\n // clash threshold at every anchor, so it is not anchor-derived and the invariance probe does\n // not withhold it. Only a value that needs the anchor is `fromAnchor`.\n const cv = ctx.constantValues.get(name);\n if (cv !== undefined) {\n const value = cv * conventionScaleToSI(name);\n if (Number.isFinite(value) && value !== 0) {\n return { value, fromAnchor: false, fromOwnEquation: false, sigmaDecades: DEFAULT_SIGMA_DECADES };\n }\n }\n // The quantity's own equation, from the table and the constants alone (already in SI, and\n // already refused when its σ is above one decade): not anchor-derived either.\n const dv = ctx.derivedMagnitudes.values.get(name);\n if (dv !== undefined) {\n return { value: dv.value, fromAnchor: false, fromOwnEquation: true, sigmaDecades: dv.sigmaDecades };\n }\n const av = ctx.anchorValues.get(name);\n if (av !== undefined) {\n const value = av * conventionScaleToSI(name);\n if (Number.isFinite(value) && value !== 0) {\n return { value, fromAnchor: true, fromOwnEquation: false, sigmaDecades: DEFAULT_SIGMA_DECADES };\n }\n }\n return undefined;\n };\n const a = magnitudeOf(candidate.a);\n const b = magnitudeOf(candidate.b);\n const checked = a !== undefined && b !== undefined;\n const ordersApart = checked ? Math.abs(Math.log10(Math.abs(a.value)) - Math.log10(Math.abs(b.value))) : null;\n return {\n a,\n b,\n checked,\n usedAnchor: checked && (a.fromAnchor || b.fromAnchor),\n ordersApart,\n clash: ordersApart !== null && ordersApart > ctx.maxOrders,\n p: checked && ordersApart !== null ? magnitudeStrength(ordersApart, a.sigmaDecades, b.sigmaDecades) : null,\n missing: [a === undefined ? candidate.a : null, b === undefined ? candidate.b : null].filter(\n (e): e is string => e !== null,\n ),\n derived: [a?.fromOwnEquation === true ? candidate.a : null, b?.fromOwnEquation === true ? candidate.b : null].filter(\n (e): e is string => e !== null,\n ),\n };\n}\n\n/**\n * Vet one link candidate by hypothesizing the identification a≡b and\n * measuring its structural and numerical consequences. See module docs.\n *\n * @internal\n */\nexport function vetLinkCandidate(\n edges: readonly BridgeEdge[],\n candidate: LinkCandidate,\n opts: DiscoveryOptions = {},\n): VettedCandidate {\n return vetInContext(edges, candidate, buildDiscoveryContext(edges, opts));\n}\n\n/**\n * The per-candidate vetting body, given pre-computed candidate-invariant\n * `ctx`. The hypothesis-augmented closure is per-candidate. Retrodiction is\n * per-candidate only when an endpoint is already in the anchor closure;\n * otherwise the identification cannot fire and the base report stands.\n *\n * @internal\n */\nexport function vetInContext(\n edges: readonly BridgeEdge[],\n candidate: LinkCandidate,\n ctx: DiscoveryContext,\n): VettedCandidate {\n const {\n baseIdents,\n groundTruth,\n anchor,\n anchorValuesProbe,\n comps,\n closureBase,\n attributesByName,\n baseInconsistentNodes,\n } = ctx;\n\n // Magnitude gate: an INDEPENDENT falsifier the single-anchor graph can't make\n // (`magnitudeGate`): the verdict is the hard threshold, `p` its calibrated strength.\n const gate = magnitudeGate(candidate, ctx);\n const va = gate.a;\n const vb = gate.b;\n const magnitudeChecked = gate.checked;\n const magnitudeUsedAnchor = gate.usedAnchor;\n const ordersApart = gate.ordersApart;\n const magnitudeClash = gate.clash;\n\n // Anchor-invariance probe: re-read each anchor-derived magnitude at the\n // rescaled anchor. A table value is a constant and does not move.\n const probeOf = (\n name: string,\n m: { value: number; fromAnchor: boolean },\n ): number | undefined => {\n if (!m.fromAnchor) return m.value;\n const probe = anchorValuesProbe.get(name);\n return probe === undefined ? undefined : probe * conventionScaleToSI(name);\n };\n let magnitudeAnchorInvariant = false;\n if (magnitudeUsedAnchor) {\n const pa = probeOf(candidate.a, va!);\n const pb = probeOf(candidate.b, vb!);\n if (pa !== undefined && pb !== undefined && pa !== 0 && pb !== 0 && Number.isFinite(pa) && Number.isFinite(pb)) {\n const la = Math.log10(Math.abs(va!.value));\n const lb = Math.log10(Math.abs(vb!.value));\n const lpa = Math.log10(Math.abs(pa));\n const lpb = Math.log10(Math.abs(pb));\n magnitudeAnchorInvariant = Math.abs((lpa - lpb) - (la - lb)) <= ANCHOR_INVARIANCE_TOL;\n }\n }\n\n // A generic↔specialization identification (mass ≟ reference-mass) is\n // near-tautological — barred from `promising` regardless of structure.\n const subsuming = nameSubsumes(candidate.a, candidate.b);\n\n // Axis-compatibility gate (D1): a literal identity cannot have endpoints\n // whose STATED scale/force regimes disagree. Either side silent on an\n // axis (including a fold-conflict, resolved as unstated by\n // `effectiveAttributes`) abstains that axis rather than clashing.\n // Populated on every candidate, independent of which verdict wins.\n const attrsA = effectiveAttributes(candidate.a, attributesByName, baseIdents);\n const attrsB = effectiveAttributes(candidate.b, attributesByName, baseIdents);\n const checkedAxes: string[] = [];\n const axisClashes: string[] = [];\n const axisUnstated: { axis: string; endpoints: string[] }[] = [];\n // GATE_AXES is registry-derived (string[]); every registry axis name is a\n // RegimeAttributes key whose value is a string union — read via a string record.\n const recA = attrsA as Record<string, string | undefined>;\n const recB = attrsB as Record<string, string | undefined>;\n for (const axis of GATE_AXES) {\n const av = recA[axis];\n const bv = recB[axis];\n if (av === undefined || bv === undefined) {\n axisUnstated.push({\n axis,\n endpoints: [av === undefined ? candidate.a : null, bv === undefined ? candidate.b : null].filter(\n (e): e is string => e !== null,\n ),\n });\n continue; // abstain\n }\n checkedAxes.push(axis);\n if (av !== bv) axisClashes.push(`${axis}: ${av} ≠ ${bv}`);\n }\n axisClashes.sort();\n const axisChecked = checkedAxes.length > 0;\n const axisClash = axisClashes.length > 0;\n\n // The hypothesized identification, added in both directions so a≡b is a\n // full merge (identifications are directional in the engine).\n const rationale = `HYPOTHESIS (unadjudicated): ${candidate.a} ≡ ${candidate.b} — same dimension ${candidate.dim}`;\n const hypothesis: QuantityIdentification[] = [\n { from: candidate.a, to: candidate.b, rationale },\n { from: candidate.b, to: candidate.a, rationale },\n ];\n const withHyp = [...baseIdents, ...hypothesis];\n\n // Structural: does a≡b merge two components? (base partition from ctx)\n const mergesComponents =\n comps.has(candidate.a) &&\n comps.has(candidate.b) &&\n comps.get(candidate.a) !== comps.get(candidate.b);\n\n // Neither endpoint in the anchor closure means the identification cannot\n // fire: `forwardClosure` copies a value across `a ≡ b` only from a\n // determinable side. The hypothesis closure is then the base closure, and no\n // value in the graph moves, so neither closure nor retrodiction is recomputed\n // (9.0.0 audit §4 C9: 4004 of 4196 catalog candidates are this case).\n const touchesClosure = closureBase.has(candidate.a) || closureBase.has(candidate.b);\n\n // Closure unlock: forward closure from the anchor, with vs without. The base\n // closure is candidate-invariant (ctx); only the hypothesis closure is fresh.\n const closureHyp = touchesClosure ? forwardClosure(edges, anchor, withHyp) : closureBase;\n const unlocksFromAnchor = [...closureHyp]\n .filter((q) => !closureBase.has(q))\n .sort();\n\n // Numeric: the hypothesis must not CREATE a disagreement. A node whose\n // routes already disagree in the base graph is the graph's finding\n // (`retrodict` reports it), not this candidate's, so the attribution is\n // inconsistent(hypothesis) minus inconsistent(base) (9.0.0 audit §4 C4).\n // Only a target downstream of `a` or `b` can change under the hypothesis;\n // every other target's retrodiction is the base one, so only those targets\n // are retrodicted (§4 C9).\n const inconsistentNodes = touchesClosure\n ? newInconsistencies(edges, groundTruth, anchor, candidate, withHyp, baseInconsistentNodes)\n : [];\n const numericallyConsistent = inconsistentNodes.length === 0;\n\n // Verdict precedence: a magnitude clash is the most decisive, most\n // interpretable falsification, checked before the graph contradiction;\n // the regime-label prior (axis-clash) is weaker than both numeric\n // falsifiers and is not one itself, but still outranks promising/inert (D1).\n let verdict: VettedCandidate['verdict'];\n if (magnitudeClash) verdict = 'magnitude-clash';\n else if (!numericallyConsistent) verdict = 'contradictory';\n else if (axisClash) verdict = 'axis-clash';\n else if (mergesComponents && unlocksFromAnchor.length > 0 && !subsuming)\n verdict = hasMagnitudeEvidence({ magnitudeChecked, magnitudeAnchorInvariant }) ? 'promising' : 'promising-unchecked';\n else verdict = 'inert';\n\n // Score: all falsifications sink to the bottom; a subsuming (tautological)\n // identification sinks within `inert`; otherwise reward structural merges,\n // unlocks, and the proposer's weak priors.\n let score = 0;\n if (magnitudeClash || !numericallyConsistent || axisClash) {\n score = -1;\n } else if (subsuming) {\n score = 0;\n } else {\n score += mergesComponents ? 4 : 0;\n score += Math.min(unlocksFromAnchor.length, 4);\n score += candidate.touchesCore ? 1 : 0;\n score += candidate.sameKind ? 1 : 0;\n }\n\n const canonicalKinds = CANONICAL_KINDS_BY_DIM.get(candidate.dim) ?? [];\n const touchesCanonical =\n CANONICAL_QUANTITY_NAMES.has(candidate.a) ||\n CANONICAL_QUANTITY_NAMES.has(candidate.b);\n\n return {\n a: candidate.a,\n b: candidate.b,\n dim: candidate.dim,\n touchesCore: candidate.touchesCore,\n sameKind: candidate.sameKind,\n mergesComponents,\n unlocksFromAnchor,\n numericallyConsistent,\n inconsistentNodes,\n ordersApart,\n magnitudeP: gate.p,\n magnitudeChecked,\n magnitudeUsedAnchor,\n magnitudeAnchorInvariant,\n magnitudeDerived: gate.derived,\n magnitudeMissing: gate.missing,\n axisUnstated,\n subsuming,\n verdict,\n score,\n axisChecked,\n axisClashes,\n canonicalKinds,\n touchesCanonical,\n };\n}\n\n/**\n * Run the full discovery funnel: propose cross-cluster candidates, vet each\n * against the inference suite, and rank — `promising` first, then `promising-unchecked`, then\n * the rest, and by score within a verdict.\n * The output is the physicist's worklist (vetted, provenance-traced), not a\n * list of discoveries.\n *\n * @public\n */\nexport function rankDiscoveries(\n edges: readonly BridgeEdge[],\n opts: DiscoveryOptions = {},\n): VettedCandidate[] {\n const VERDICT_RANK: Record<VettedCandidate['verdict'], number> = {\n promising: 0,\n 'promising-unchecked': 1,\n inert: 2,\n 'magnitude-clash': 3,\n contradictory: 4,\n 'axis-clash': 5,\n };\n const ctx = buildDiscoveryContext(edges, opts);\n const candidates = proposeLinkCandidates(edges);\n const vetted = candidates.map((c) => vetInContext(edges, c, ctx));\n vetted.sort(\n (x, y) =>\n VERDICT_RANK[x.verdict] - VERDICT_RANK[y.verdict] ||\n y.score - x.score ||\n x.a.localeCompare(y.a) ||\n x.b.localeCompare(y.b),\n );\n return vetted;\n}\n"]}
@@ -7,7 +7,7 @@
7
7
  * DIFFERENT AST — so this is the missing primitive that makes a bridge's
8
8
  * `symbolic` form executable.
9
9
  *
10
- * The tree is lowered with `createScalarBuilder` and evaluated with
10
+ * The tree is lowered with `toScalarNode` (`dimensional/scalar-lowering`, which writes a one-argument `-` as a negation) and evaluated with
11
11
  * `evaluateScalar`. This module does not print a formula and does not parse
12
12
  * one. Leaf resolution order (Adam A-1, A-5): caller `values` win, then the
13
13
  * `CONSTANTS` registry, then a spelled-out multiple of π, then a base-10
@@ -1 +1 @@
1
- {"version":3,"file":"expr-eval.d.ts","sourceRoot":"","sources":["../../src/composition/expr-eval.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAK5D;yCACyC;AACzC,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AA+CD;;;;;GAKG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,QAAQ,EACd,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GAC5C,MAAM,CAOR"}
1
+ {"version":3,"file":"expr-eval.d.ts","sourceRoot":"","sources":["../../src/composition/expr-eval.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAM5D;yCACyC;AACzC,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AA6CD;;;;;GAKG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,QAAQ,EACd,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GAC5C,MAAM,CAOR"}
@@ -7,7 +7,7 @@
7
7
  * DIFFERENT AST — so this is the missing primitive that makes a bridge's
8
8
  * `symbolic` form executable.
9
9
  *
10
- * The tree is lowered with `createScalarBuilder` and evaluated with
10
+ * The tree is lowered with `toScalarNode` (`dimensional/scalar-lowering`, which writes a one-argument `-` as a negation) and evaluated with
11
11
  * `evaluateScalar`. This module does not print a formula and does not parse
12
12
  * one. Leaf resolution order (Adam A-1, A-5): caller `values` win, then the
13
13
  * `CONSTANTS` registry, then a spelled-out multiple of π, then a base-10
@@ -19,11 +19,12 @@
19
19
  *
20
20
  * @module composition/expr-eval
21
21
  */
22
- import { createScalarBuilder, ScalarBuildError } from '@danielsimonjr/mathts-expression';
22
+ import { ScalarBuildError } from '@danielsimonjr/mathts-expression';
23
23
  import { evaluateScalar, ScalarEvalError } from '@danielsimonjr/mathts-functions';
24
24
  import { forEachScalarLeaf } from '../dimensional/ast-leaves.js';
25
25
  import { CONSTANTS, piMultipleValue } from '../dimensional/symbolic-constants.js';
26
26
  import { FORMULA_NAMED } from '../dimensional/formula-names.js';
27
+ import { toScalarNode } from '../dimensional/scalar-lowering.js';
27
28
  /** A scalar `ExprNode` could not be evaluated (unsupported arm / unresolved
28
29
  * leaf / non-finite result). @public */
29
30
  export class SymbolicEvalError extends Error {
@@ -32,7 +33,6 @@ export class SymbolicEvalError extends Error {
32
33
  this.name = 'SymbolicEvalError';
33
34
  }
34
35
  }
35
- const builder = createScalarBuilder();
36
36
  /** Symbol names on the scalar arms. Other kinds are left for the builder to reject. */
37
37
  function symbolNames(node, out) {
38
38
  forEachScalarLeaf(node, (leaf) => out.add(leaf.name), { transcendental: true, abs: true });
@@ -80,7 +80,7 @@ function asSymbolic(err) {
80
80
  */
81
81
  export function evalExpr(node, values = {}) {
82
82
  try {
83
- const lowered = builder.from(node);
83
+ const lowered = toScalarNode(node);
84
84
  return evaluateScalar(lowered, scopeFor(node, values));
85
85
  }
86
86
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"expr-eval.js","sourceRoot":"","sources":["../../src/composition/expr-eval.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scalar `ExprNode` value evaluator (v0.12 symbolic composition).\n *\n * Numerically evaluates a SCALAR `ExprNode` (the `symbol` / `op` /\n * `transcendental` / `abs` arms) given leaf values. The dimensional validator infers\n * dimensions but never computes values, and the formula parser evaluates a\n * DIFFERENT AST — so this is the missing primitive that makes a bridge's\n * `symbolic` form executable.\n *\n * The tree is lowered with `createScalarBuilder` and evaluated with\n * `evaluateScalar`. This module does not print a formula and does not parse\n * one. Leaf resolution order (Adam A-1, A-5): caller `values` win, then the\n * `CONSTANTS` registry, then a spelled-out multiple of π, then a base-10\n * numeric-literal symbol (`'2'`, `'3'`), which MathTS resolves. An unresolved\n * or non-finite operator result throws. Tensor / integral / derivative arms\n * are out of scope and throw.\n *\n * INTERNAL — not on the public surface.\n *\n * @module composition/expr-eval\n */\n\nimport { createScalarBuilder, ScalarBuildError } from '@danielsimonjr/mathts-expression';\nimport { evaluateScalar, ScalarEvalError } from '@danielsimonjr/mathts-functions';\nimport type { ExprNode } from '../dimensional/validator.js';\nimport { forEachScalarLeaf } from '../dimensional/ast-leaves.js';\nimport { CONSTANTS, piMultipleValue } from '../dimensional/symbolic-constants.js';\nimport { FORMULA_NAMED } from '../dimensional/formula-names.js';\n\n/** A scalar `ExprNode` could not be evaluated (unsupported arm / unresolved\n * leaf / non-finite result). @public */\nexport class SymbolicEvalError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SymbolicEvalError';\n }\n}\n\nconst builder = createScalarBuilder();\n\n/** Symbol names on the scalar arms. Other kinds are left for the builder to reject. */\nfunction symbolNames(node: ExprNode, out: Set<string>): void {\n forEachScalarLeaf(node, (leaf) => out.add(leaf.name), { transcendental: true, abs: true });\n}\n\n/**\n * Caller bindings, then registered constants and spelled-out π multiples\n * that the caller did not set. Numeric-literal names stay unset so MathTS\n * reads them.\n */\nfunction scopeFor(\n node: ExprNode,\n values: Readonly<Record<string, number>>,\n): Record<string, number> {\n const scope: Record<string, number> = { ...values };\n const names = new Set<string>();\n symbolNames(node, names);\n for (const name of names) {\n if (Object.prototype.hasOwnProperty.call(scope, name)) continue;\n const constant = CONSTANTS[name];\n if (constant !== undefined) {\n scope[name] = constant.value;\n continue;\n }\n const named = FORMULA_NAMED.find((row) => row.name === name);\n if (named !== undefined) {\n scope[name] = named.value;\n continue;\n }\n const pi = piMultipleValue(name);\n if (pi !== undefined) scope[name] = pi;\n }\n return scope;\n}\n\nfunction asSymbolic(err: unknown): Error {\n if (err instanceof ScalarEvalError || err instanceof ScalarBuildError) {\n // MathTS names its own function first (`evaluateScalar:`); the message is kept whole, not rewritten.\n return new SymbolicEvalError(`evalExpr: ${err.message}`);\n }\n return err instanceof Error ? err : new SymbolicEvalError(String(err));\n}\n\n/**\n * Evaluate a scalar `ExprNode` to a number. See module docs for the leaf\n * resolution order and scope.\n *\n * @internal\n */\nexport function evalExpr(\n node: ExprNode,\n values: Readonly<Record<string, number>> = {},\n): number {\n try {\n const lowered = builder.from(node);\n return evaluateScalar(lowered, scopeFor(node, values));\n } catch (err) {\n throw asSymbolic(err);\n }\n}\n"]}
1
+ {"version":3,"file":"expr-eval.js","sourceRoot":"","sources":["../../src/composition/expr-eval.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scalar `ExprNode` value evaluator (v0.12 symbolic composition).\n *\n * Numerically evaluates a SCALAR `ExprNode` (the `symbol` / `op` /\n * `transcendental` / `abs` arms) given leaf values. The dimensional validator infers\n * dimensions but never computes values, and the formula parser evaluates a\n * DIFFERENT AST — so this is the missing primitive that makes a bridge's\n * `symbolic` form executable.\n *\n * The tree is lowered with `toScalarNode` (`dimensional/scalar-lowering`, which writes a one-argument `-` as a negation) and evaluated with\n * `evaluateScalar`. This module does not print a formula and does not parse\n * one. Leaf resolution order (Adam A-1, A-5): caller `values` win, then the\n * `CONSTANTS` registry, then a spelled-out multiple of π, then a base-10\n * numeric-literal symbol (`'2'`, `'3'`), which MathTS resolves. An unresolved\n * or non-finite operator result throws. Tensor / integral / derivative arms\n * are out of scope and throw.\n *\n * INTERNAL — not on the public surface.\n *\n * @module composition/expr-eval\n */\n\nimport { ScalarBuildError } from '@danielsimonjr/mathts-expression';\nimport { evaluateScalar, ScalarEvalError } from '@danielsimonjr/mathts-functions';\nimport type { ExprNode } from '../dimensional/validator.js';\nimport { forEachScalarLeaf } from '../dimensional/ast-leaves.js';\nimport { CONSTANTS, piMultipleValue } from '../dimensional/symbolic-constants.js';\nimport { FORMULA_NAMED } from '../dimensional/formula-names.js';\nimport { toScalarNode } from '../dimensional/scalar-lowering.js';\n\n/** A scalar `ExprNode` could not be evaluated (unsupported arm / unresolved\n * leaf / non-finite result). @public */\nexport class SymbolicEvalError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SymbolicEvalError';\n }\n}\n\n/** Symbol names on the scalar arms. Other kinds are left for the builder to reject. */\nfunction symbolNames(node: ExprNode, out: Set<string>): void {\n forEachScalarLeaf(node, (leaf) => out.add(leaf.name), { transcendental: true, abs: true });\n}\n\n/**\n * Caller bindings, then registered constants and spelled-out π multiples\n * that the caller did not set. Numeric-literal names stay unset so MathTS\n * reads them.\n */\nfunction scopeFor(\n node: ExprNode,\n values: Readonly<Record<string, number>>,\n): Record<string, number> {\n const scope: Record<string, number> = { ...values };\n const names = new Set<string>();\n symbolNames(node, names);\n for (const name of names) {\n if (Object.prototype.hasOwnProperty.call(scope, name)) continue;\n const constant = CONSTANTS[name];\n if (constant !== undefined) {\n scope[name] = constant.value;\n continue;\n }\n const named = FORMULA_NAMED.find((row) => row.name === name);\n if (named !== undefined) {\n scope[name] = named.value;\n continue;\n }\n const pi = piMultipleValue(name);\n if (pi !== undefined) scope[name] = pi;\n }\n return scope;\n}\n\nfunction asSymbolic(err: unknown): Error {\n if (err instanceof ScalarEvalError || err instanceof ScalarBuildError) {\n // MathTS names its own function first (`evaluateScalar:`); the message is kept whole, not rewritten.\n return new SymbolicEvalError(`evalExpr: ${err.message}`);\n }\n return err instanceof Error ? err : new SymbolicEvalError(String(err));\n}\n\n/**\n * Evaluate a scalar `ExprNode` to a number. See module docs for the leaf\n * resolution order and scope.\n *\n * @internal\n */\nexport function evalExpr(\n node: ExprNode,\n values: Readonly<Record<string, number>> = {},\n): number {\n try {\n const lowered = toScalarNode(node);\n return evaluateScalar(lowered, scopeFor(node, values));\n } catch (err) {\n throw asSymbolic(err);\n }\n}\n"]}
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Symbolic simplification of a scalar `ExprNode` via MathTS (v0.12 — optional
3
- * supplement to symbolic composition).
2
+ * Symbolic simplification of a scalar `ExprNode` through MathTS, a supplement
3
+ * to symbolic composition.
4
4
  *
5
5
  * Composing two bridges' symbolic forms leaves an UNSIMPLIFIED AST — CT-1 is
6
6
  * `k_B·(ℏc³/8πG·mass·k_B)·ln2`, with `k_B` uncancelled. This folds it to the
7
7
  * canonical `ℏc³·ln2/(8πG·mass)` using `@danielsimonjr/mathts-functions`'
8
- * `simplify` (the same OPTIONAL peer the Path-A formula parser loads).
8
+ * `simplify`, a required dependency.
9
9
  *
10
10
  * Pipeline (Adam+Eve-vetted — docs/planning/v0.12.0-Symbolic-Simplification-
11
11
  * Design.md): GENSYM every non-exponent leaf to a mathjs-safe identifier
@@ -19,7 +19,7 @@
19
19
  * 3. numeric — `evalExpr` agrees over ≥2 finite synthesized probes at
20
20
  * rel tol 1e-9 (Adam CRITICAL-2 / EVE-C).
21
21
  * A representable result that DISAGREES dimensionally/numerically THROWS (a
22
- * bug to surface); anything we cannot represent / verify / load degrades to a
22
+ * bug to surface); anything we cannot represent or verify degrades to a
23
23
  * graceful no-op (return the original, `simplified:false`).
24
24
  *
25
25
  * INTERNAL — surfaced via `upt symbolic --simplify`.
@@ -51,22 +51,18 @@ interface SimplifyResult {
51
51
  readonly simplified: boolean;
52
52
  }
53
53
  /**
54
- * Whether the optional MathTS simplifier peer is installed AND passes its
55
- * smoke test.
56
- *
57
- * {@link simplifyExpr} returns `simplified: false` both when the peer is absent
58
- * and when it is present but could not reduce the expression, so a caller that
59
- * must report WHY nothing happened (the atlas witness runner's `peer-absent`
60
- * versus `not-simplified`) needs this separate signal. Shares the same cached
61
- * detection, so it costs nothing after the first call.
54
+ * Whether the MathTS simplifier is usable. MathTS is a required dependency, so
55
+ * the answer is true; a simplifier that fails its smoke test throws instead.
56
+ * The atlas witness runner reads it to choose between the simplifier and its
57
+ * `peer-absent` path, which a caller still reaches by passing `null`.
62
58
  *
63
59
  * @internal
64
60
  */
65
61
  export declare function isSimplifierAvailable(): Promise<boolean>;
66
62
  /**
67
63
  * Simplify a scalar `ExprNode` via MathTS, guarded. Returns the original
68
- * unchanged (`simplified:false`) when the peer is absent or the result cannot
69
- * be represented/verified; THROWS {@link SimplificationError} only when a
64
+ * unchanged (`simplified:false`) when the result cannot be represented or
65
+ * verified; THROWS {@link SimplificationError} only when a
70
66
  * representable result disagrees dimensionally/numerically. See module docs.
71
67
  *
72
68
  * @internal
@@ -1 +1 @@
1
- {"version":3,"file":"expr-simplify.d.ts","sourceRoot":"","sources":["../../src/composition/expr-simplify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAG5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAKzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAmFxD,sEAAsE;AACtE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;CACzB;AAID;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,GAC1B,MAAM,CAmBR;AA4GD,0DAA0D;AAC1D,UAAU,cAAc;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE9D;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,CAgE1E;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAI7E"}
1
+ {"version":3,"file":"expr-simplify.d.ts","sourceRoot":"","sources":["../../src/composition/expr-simplify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAG5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAKzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAyExD,sEAAsE;AACtE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;CACzB;AAID;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,GAC1B,MAAM,CAwBR;AA6GD,0DAA0D;AAC1D,UAAU,cAAc;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAG9D;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,CA+D1E;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAI7E"}