@hviana/sema 0.1.5 → 0.1.7

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 (170) hide show
  1. package/AGENTS.md +6 -5
  2. package/CITATION.cff +49 -0
  3. package/HOW_IT_WORKS.md +11 -12
  4. package/README.md +7 -5
  5. package/dist/example/demo.js +25 -25
  6. package/dist/example/train_base.d.ts +19 -21
  7. package/dist/example/train_base.js +1700 -1952
  8. package/dist/src/alphabet.d.ts +3 -3
  9. package/dist/src/alphabet.js +24 -27
  10. package/dist/src/alu/src/alu.d.ts +169 -194
  11. package/dist/src/alu/src/alu.js +374 -414
  12. package/dist/src/alu/src/expr.d.ts +36 -43
  13. package/dist/src/alu/src/expr.js +252 -278
  14. package/dist/src/alu/src/index.d.ts +9 -98
  15. package/dist/src/alu/src/index.js +10 -64
  16. package/dist/src/alu/src/kernel-arith.d.ts +1 -5
  17. package/dist/src/alu/src/kernel-arith.js +195 -285
  18. package/dist/src/alu/src/kernel-bits.d.ts +1 -5
  19. package/dist/src/alu/src/kernel-bits.js +81 -115
  20. package/dist/src/alu/src/kernel-logic.js +33 -63
  21. package/dist/src/alu/src/kernel-nd.js +155 -195
  22. package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
  23. package/dist/src/alu/src/kernel-numeric.js +259 -331
  24. package/dist/src/alu/src/operation.d.ts +112 -132
  25. package/dist/src/alu/src/operation.js +149 -163
  26. package/dist/src/alu/src/parser.d.ts +168 -175
  27. package/dist/src/alu/src/parser.js +382 -421
  28. package/dist/src/alu/src/resonance.d.ts +16 -25
  29. package/dist/src/alu/src/resonance.js +49 -56
  30. package/dist/src/alu/src/text.d.ts +3 -7
  31. package/dist/src/alu/src/text.js +31 -37
  32. package/dist/src/alu/src/value.d.ts +14 -14
  33. package/dist/src/alu/src/value.js +150 -160
  34. package/dist/src/alu/test/alu.test.js +543 -656
  35. package/dist/src/bytes.d.ts +1 -5
  36. package/dist/src/bytes.js +33 -40
  37. package/dist/src/config.d.ts +99 -102
  38. package/dist/src/config.js +78 -83
  39. package/dist/src/derive/src/deduction.d.ts +60 -62
  40. package/dist/src/derive/src/deduction.js +103 -109
  41. package/dist/src/derive/src/index.d.ts +1 -7
  42. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  43. package/dist/src/derive/src/priority-queue.js +57 -61
  44. package/dist/src/derive/src/rewrite.d.ts +15 -18
  45. package/dist/src/derive/src/rewrite.js +59 -67
  46. package/dist/src/derive/src/trie.d.ts +56 -56
  47. package/dist/src/derive/src/trie.js +175 -179
  48. package/dist/src/derive/test/derive.test.js +96 -100
  49. package/dist/src/extension.d.ts +13 -16
  50. package/dist/src/geometry.d.ts +29 -43
  51. package/dist/src/geometry.js +324 -268
  52. package/dist/src/index.d.ts +2 -17
  53. package/dist/src/index.js +2 -12
  54. package/dist/src/ingest-cache.d.ts +25 -30
  55. package/dist/src/ingest-cache.js +108 -127
  56. package/dist/src/mind/articulation.d.ts +1 -5
  57. package/dist/src/mind/articulation.js +77 -112
  58. package/dist/src/mind/attention.d.ts +40 -108
  59. package/dist/src/mind/attention.js +754 -872
  60. package/dist/src/mind/canonical.d.ts +4 -19
  61. package/dist/src/mind/canonical.js +27 -31
  62. package/dist/src/mind/graph-search.d.ts +201 -225
  63. package/dist/src/mind/graph-search.js +742 -821
  64. package/dist/src/mind/index.d.ts +2 -10
  65. package/dist/src/mind/junction.d.ts +31 -58
  66. package/dist/src/mind/junction.js +172 -237
  67. package/dist/src/mind/learning.d.ts +16 -52
  68. package/dist/src/mind/learning.js +143 -165
  69. package/dist/src/mind/match.d.ts +20 -69
  70. package/dist/src/mind/match.js +259 -318
  71. package/dist/src/mind/mechanisms/alu.js +16 -16
  72. package/dist/src/mind/mechanisms/cast.d.ts +9 -13
  73. package/dist/src/mind/mechanisms/cast.js +363 -456
  74. package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
  75. package/dist/src/mind/mechanisms/confluence.js +152 -183
  76. package/dist/src/mind/mechanisms/cover.d.ts +2 -8
  77. package/dist/src/mind/mechanisms/cover.js +148 -210
  78. package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
  79. package/dist/src/mind/mechanisms/extraction.js +234 -288
  80. package/dist/src/mind/mechanisms/recall.d.ts +6 -10
  81. package/dist/src/mind/mechanisms/recall.js +141 -185
  82. package/dist/src/mind/mind.d.ts +194 -155
  83. package/dist/src/mind/mind.js +392 -289
  84. package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
  85. package/dist/src/mind/pipeline-mechanism.js +161 -172
  86. package/dist/src/mind/pipeline.d.ts +4 -14
  87. package/dist/src/mind/pipeline.js +125 -189
  88. package/dist/src/mind/primitives.d.ts +20 -33
  89. package/dist/src/mind/primitives.js +124 -120
  90. package/dist/src/mind/rationale.d.ts +86 -98
  91. package/dist/src/mind/rationale.js +113 -121
  92. package/dist/src/mind/reasoning.d.ts +2 -13
  93. package/dist/src/mind/reasoning.js +129 -166
  94. package/dist/src/mind/recognition.d.ts +1 -4
  95. package/dist/src/mind/recognition.js +198 -210
  96. package/dist/src/mind/resonance.d.ts +5 -22
  97. package/dist/src/mind/resonance.js +0 -0
  98. package/dist/src/mind/trace.d.ts +6 -25
  99. package/dist/src/mind/trace.js +50 -58
  100. package/dist/src/mind/traverse.d.ts +29 -58
  101. package/dist/src/mind/traverse.js +344 -356
  102. package/dist/src/mind/types.d.ts +122 -127
  103. package/dist/src/mind/types.js +60 -69
  104. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  105. package/dist/src/rabitq-ivf/src/database.js +201 -0
  106. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  107. package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
  108. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  109. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  110. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  111. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  112. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  113. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  114. package/dist/src/sema.d.ts +9 -13
  115. package/dist/src/sema.js +26 -40
  116. package/dist/src/store-sqlite.d.ts +171 -166
  117. package/dist/src/store-sqlite.js +768 -662
  118. package/dist/src/store.d.ts +597 -630
  119. package/dist/src/store.js +1434 -1581
  120. package/dist/src/vec.d.ts +5 -9
  121. package/dist/src/vec.js +61 -73
  122. package/example/train_base.ts +33 -15
  123. package/index.html +65 -0
  124. package/package.json +1 -1
  125. package/src/alu/README.md +1 -1
  126. package/src/alu/src/index.ts +1 -1
  127. package/src/config.ts +19 -27
  128. package/src/geometry.ts +93 -1
  129. package/src/index.ts +6 -11
  130. package/src/mind/attention.ts +11 -6
  131. package/src/mind/mechanisms/recall.ts +29 -13
  132. package/src/mind/mind.ts +250 -19
  133. package/src/mind/primitives.ts +43 -6
  134. package/src/mind/recognition.ts +26 -8
  135. package/src/mind/traverse.ts +47 -0
  136. package/src/mind/types.ts +20 -21
  137. package/src/rabitq-ivf/README.md +56 -0
  138. package/src/rabitq-ivf/src/database.ts +276 -0
  139. package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
  140. package/src/rabitq-ivf/src/ivf.ts +1330 -0
  141. package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
  142. package/src/store-sqlite.ts +204 -9
  143. package/src/store.ts +27 -34
  144. package/test/08-storage.test.mjs +3 -3
  145. package/test/13-conversation.test.mjs +190 -20
  146. package/test/14-scaling.test.mjs +2 -2
  147. package/test/35-ivf.test.mjs +263 -0
  148. package/test/36-bloom.test.mjs +123 -0
  149. package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
  150. package/dist/src/rabitq-hnsw/src/database.js +0 -405
  151. package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
  152. package/dist/src/rabitq-hnsw/src/heap.js +0 -94
  153. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
  154. package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
  155. package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
  156. package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
  157. package/dist/src/rabitq-hnsw/src/prng.js +0 -38
  158. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
  159. package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
  160. package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
  161. package/dist/src/rabitq-hnsw/src/store.js +0 -916
  162. package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
  163. package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
  164. package/src/rabitq-hnsw/README.md +0 -303
  165. package/src/rabitq-hnsw/src/database.ts +0 -492
  166. package/src/rabitq-hnsw/src/heap.ts +0 -90
  167. package/src/rabitq-hnsw/src/hnsw.ts +0 -514
  168. package/src/rabitq-hnsw/src/store.ts +0 -994
  169. package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
  170. /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
@@ -12,7 +12,7 @@
12
12
  // juxtapose.
13
13
  import { read } from "../primitives.js";
14
14
  import { argmaxBy, hubBound } from "../traverse.js";
15
- import { analogyStrength, follow, project, reverseContext } from "../match.js";
15
+ import { analogyStrength, follow, project, reverseContext, } from "../match.js";
16
16
  import { joinWithBridge } from "../resonance.js";
17
17
  import { CONCEPT, STEP } from "../graph-search.js";
18
18
  import { concat2, indexOf } from "../../bytes.js";
@@ -70,471 +70,378 @@ const MIN_WEAVE = 2;
70
70
  *
71
71
  * Returns the array of {@link CastResult}s that fired (possibly empty). */
72
72
  export async function counterfactualTransfer(ctx, query, pre) {
73
- const quantum = ctx.space.maxGroup;
74
- if (query.length < 2 * quantum || ctx.store.edgeSourceCount() === 0) {
75
- return [];
76
- }
77
- const { roots, ranked } = await pre.attention();
78
- if (ranked.length < 2) {
79
- return [];
80
- }
81
- const weave = await pre.weave();
82
- const points = weave.points;
83
- const depth = weave.depth;
84
- const aligned = points.length;
85
- if (aligned < 2) {
86
- return [];
87
- }
88
- // ── Frame gate (half-dominance, weave-local) ─────────────────────────
89
- // A byte is FRAME when more than MIN_WEAVE aligned structures cover it
90
- // AND those structures are a majority of all aligned structures.
91
- // Per-byte: frame(i) ⇔ depth[i] > MIN_WEAVE ∧ dominates(depth[i], aligned)
92
- // Per-run: usable(r) ⇔ ¬dominates(framedCount, runLen)
93
- const isFrame = (i) => depth[i] > MIN_WEAVE && dominates(depth[i], aligned);
94
- const framedCount = (qs, qe) => {
95
- let n = 0;
96
- for (let i = qs; i < qe; i++) {
97
- if (isFrame(i)) {
98
- n++;
99
- }
73
+ const quantum = ctx.space.maxGroup;
74
+ if (query.length < 2 * quantum || ctx.store.edgeSourceCount() === 0) {
75
+ return [];
100
76
  }
101
- return n;
102
- };
103
- const usable = (qs, qe) => !dominates(framedCount(qs, qe), qe - qs);
104
- // The weave's DOMINANT is its principal STRUCTURE — the aligned point
105
- // explaining the most query bytes — not the climb's top-ranked TOPIC.
106
- // The two used to coincide (approximate votes from a query's novel spans
107
- // boosted whichever exemplar shared its frame), but the contrastive
108
- // margin ranks the query's own exact site first, and CAST's schemas all
109
- // orient around the frame-bearing structure: the substitution/redirection
110
- // seat is displaced IN the dominant, and comparison seats the analogs by
111
- // the contexts that establish their roles. Coverage is weave-local and
112
- // derived (sum of aligned run lengths); ties keep the ranked order.
113
- let dominant = points[0];
114
- let domCover = -1;
115
- for (const p of points) {
116
- let cover = 0;
117
- for (const r of p.runs) {
118
- cover += r.qe - r.qs;
77
+ const { roots, ranked } = await pre.attention();
78
+ if (ranked.length < 2)
79
+ return [];
80
+ const weave = await pre.weave();
81
+ const points = weave.points;
82
+ const depth = weave.depth;
83
+ const aligned = points.length;
84
+ if (aligned < 2)
85
+ return [];
86
+ // ── Frame gate (half-dominance, weave-local) ─────────────────────────
87
+ // A byte is FRAME when more than MIN_WEAVE aligned structures cover it
88
+ // AND those structures are a majority of all aligned structures.
89
+ // Per-byte: frame(i) depth[i] > MIN_WEAVE ∧ dominates(depth[i], aligned)
90
+ // Per-run: usable(r) ¬dominates(framedCount, runLen)
91
+ const isFrame = (i) => depth[i] > MIN_WEAVE && dominates(depth[i], aligned);
92
+ const framedCount = (qs, qe) => {
93
+ let n = 0;
94
+ for (let i = qs; i < qe; i++)
95
+ if (isFrame(i))
96
+ n++;
97
+ return n;
98
+ };
99
+ const usable = (qs, qe) => !dominates(framedCount(qs, qe), qe - qs);
100
+ // The weave's DOMINANT is its principal STRUCTURE — the aligned point
101
+ // explaining the most query bytes — not the climb's top-ranked TOPIC.
102
+ // The two used to coincide (approximate votes from a query's novel spans
103
+ // boosted whichever exemplar shared its frame), but the contrastive
104
+ // margin ranks the query's own exact site first, and CAST's schemas all
105
+ // orient around the frame-bearing structure: the substitution/redirection
106
+ // seat is displaced IN the dominant, and comparison seats the analogs by
107
+ // the contexts that establish their roles. Coverage is weave-local and
108
+ // derived (sum of aligned run lengths); ties keep the ranked order.
109
+ let dominant = points[0];
110
+ let domCover = -1;
111
+ for (const p of points) {
112
+ let cover = 0;
113
+ for (const r of p.runs)
114
+ cover += r.qe - r.qs;
115
+ if (cover > domCover) {
116
+ domCover = cover;
117
+ dominant = p;
118
+ }
119
119
  }
120
- if (cover > domCover) {
121
- domCover = cover;
122
- dominant = p;
123
- }
124
- }
125
- const isRoot = (id) => roots.some((r) => r.anchor === id);
126
- // The weave must touch a COMMITTED point of attention: the dominant
127
- // structure itself, or another aligned point the climb committed to.
128
- if (!points.some((p) => isRoot(p.anchor))) {
129
- return [];
130
- }
131
- const woven = points.some((p) =>
132
- p.runs.some((r) =>
133
- !pre.rec.sites.some((s) => r.qs >= s.start && r.qe <= s.end)
134
- )
135
- );
136
- if (!woven) {
137
- return [];
138
- }
139
- const t = ctx.trace?.enter("counterfactual", [
140
- rItem(query, "query"),
141
- ]);
142
- // Each schema tried below RECORDS its candidate (when it fires) rather than
143
- // returning immediately every schema that succeeds contributes its own
144
- // candidate, and the grounding decider's own weight comparison (not CAST's
145
- // former internal priority) picks among them.
146
- //
147
- // `accounted` is SCHEMA-SPECIFIC, not the whole weave's alignment: a
148
- // schema only actually TRANSFERS BETWEEN the two points its own logic
149
- // names (substitution: the filled subject + the displaced seat;
150
- // redirection: the displaced seat + the named substitute; comparison:
151
- // the dominant + its analog) a THIRD point the weave happened to align
152
- // but this schema never touched contributes nothing to what THIS answer
153
- // explains. Pricing every schema against the SAME "every kept point's
154
- // every run" span would let the cheapest schema win on move-cost alone
155
- // regardless of which one actually used more of the query; pricing it
156
- // against only a fragment of even its OWN two points (e.g. one run
157
- // instead of the point's full aligned evidence) is just as wrong the
158
- // other way — it starves an otherwise-correct schema of credit for
159
- // evidence it legitimately relied on. Each call site below passes the
160
- // full run set of exactly the points ITS OWN transfer used — no more,
161
- // no less.
162
- const runSpans = (p) => p.runs.map((r) => [r.qs, r.qe]);
163
- const results = [];
164
- const record = (answer, note, used, moves, accounted) => {
165
- if (answer === null) {
166
- return;
167
- }
168
- ctx.trace?.step("castSchema", [rItem(query, "query")], [
169
- rItem(answer, "answer"),
170
- ], note);
171
- results.push({
172
- bytes: answer,
173
- used: used ?? new Set(),
174
- accounted,
175
- moves,
176
- unexplained: unexplainedLabel(query, accounted),
177
- });
178
- };
179
- ctx.trace?.step(
180
- "alignStructures",
181
- [rItem(query, "query")],
182
- points.map((p) => rNode(ctx, p.anchor, "structure", p.vote)),
183
- "the independent learnt structures the query weaves, by graded alignment",
184
- );
185
- const lastRun = (p) => p.runs[p.runs.length - 1];
186
- const qv = pre.guide;
187
- // ── SUBSTITUTION ──────────────────────────────────────────────────
188
- const fillerOf = (s) => {
189
- const r = s.runs[0];
190
- return r.cs < quantum
191
- ? s.ctx.subarray(0, r.cs + (r.qe - r.qs))
192
- : query.subarray(r.qs, r.qe);
193
- };
194
- const beforeOf = (p, r) =>
195
- argmaxBy(
196
- points.filter((s) =>
197
- s !== p && lastRun(s).qe <= r.qs &&
120
+ const isRoot = (id) => roots.some((r) => r.anchor === id);
121
+ // The weave must touch a COMMITTED point of attention: the dominant
122
+ // structure itself, or another aligned point the climb committed to.
123
+ if (!points.some((p) => isRoot(p.anchor)))
124
+ return [];
125
+ const woven = points.some((p) => p.runs.some((r) => !pre.rec.sites.some((s) => r.qs >= s.start && r.qe <= s.end)));
126
+ if (!woven)
127
+ return [];
128
+ const t = ctx.trace?.enter("counterfactual", [
129
+ rItem(query, "query"),
130
+ ]);
131
+ // Each schema tried below RECORDS its candidate (when it fires) rather than
132
+ // returning immediately — every schema that succeeds contributes its own
133
+ // candidate, and the grounding decider's own weight comparison (not CAST's
134
+ // former internal priority) picks among them.
135
+ //
136
+ // `accounted` is SCHEMA-SPECIFIC, not the whole weave's alignment: a
137
+ // schema only actually TRANSFERS BETWEEN the two points its own logic
138
+ // names (substitution: the filled subject + the displaced seat;
139
+ // redirection: the displaced seat + the named substitute; comparison:
140
+ // the dominant + its analog) — a THIRD point the weave happened to align
141
+ // but this schema never touched contributes nothing to what THIS answer
142
+ // explains. Pricing every schema against the SAME "every kept point's
143
+ // every run" span would let the cheapest schema win on move-cost alone
144
+ // regardless of which one actually used more of the query; pricing it
145
+ // against only a fragment of even its OWN two points (e.g. one run
146
+ // instead of the point's full aligned evidence) is just as wrong the
147
+ // other way it starves an otherwise-correct schema of credit for
148
+ // evidence it legitimately relied on. Each call site below passes the
149
+ // full run set of exactly the points ITS OWN transfer used — no more,
150
+ // no less.
151
+ const runSpans = (p) => p.runs.map((r) => [r.qs, r.qe]);
152
+ const results = [];
153
+ const record = (answer, note, used, moves, accounted) => {
154
+ if (answer === null)
155
+ return;
156
+ ctx.trace?.step("castSchema", [rItem(query, "query")], [rItem(answer, "answer")], note);
157
+ results.push({
158
+ bytes: answer,
159
+ used: used ?? new Set(),
160
+ accounted,
161
+ moves,
162
+ unexplained: unexplainedLabel(query, accounted),
163
+ });
164
+ };
165
+ ctx.trace?.step("alignStructures", [rItem(query, "query")], points.map((p) => rNode(ctx, p.anchor, "structure", p.vote)), "the independent learnt structures the query weaves, by graded alignment");
166
+ const lastRun = (p) => p.runs[p.runs.length - 1];
167
+ const qv = pre.guide;
168
+ // ── SUBSTITUTION ──────────────────────────────────────────────────
169
+ const fillerOf = (s) => {
170
+ const r = s.runs[0];
171
+ return r.cs < quantum
172
+ ? s.ctx.subarray(0, r.cs + (r.qe - r.qs))
173
+ : query.subarray(r.qs, r.qe);
174
+ };
175
+ const beforeOf = (p, r) => argmaxBy(points.filter((s) => s !== p && lastRun(s).qe <= r.qs &&
198
176
  s.runs[0].cs < quantum &&
199
- usable(s.runs[0].qs, s.runs[0].qe)
200
- ),
201
- (s) => lastRun(s).qs,
202
- -Infinity,
203
- true,
204
- )?.item;
205
- const displacement = points
206
- .map((p) => {
207
- const r = p.runs[0];
208
- if (r.cs < quantum || !usable(r.qs, r.qe)) {
209
- return null;
210
- }
211
- const before = beforeOf(p, r);
212
- if (before === undefined) {
213
- return null;
214
- }
215
- if (r.cs > fillerOf(before).length + quantum) {
216
- return null;
217
- }
218
- return { p, before, depth: p.ctx.length - r.cs };
177
+ usable(s.runs[0].qs, s.runs[0].qe)), (s) => lastRun(s).qs, -Infinity, true)?.item;
178
+ const displacement = points
179
+ .map((p) => {
180
+ const r = p.runs[0];
181
+ if (r.cs < quantum || !usable(r.qs, r.qe)) {
182
+ return null;
183
+ }
184
+ const before = beforeOf(p, r);
185
+ if (before === undefined)
186
+ return null;
187
+ if (r.cs > fillerOf(before).length + quantum)
188
+ return null;
189
+ return { p, before, depth: p.ctx.length - r.cs };
219
190
  })
220
- .filter((c) => c !== null);
221
- const picked = argmaxBy(displacement, (c) => c.depth, -Infinity, true);
222
- const proj = picked?.item.p ?? null;
223
- const subj = picked?.item.before ?? null;
224
- if (proj !== null && subj !== null) {
225
- const seat = proj.runs[0];
226
- const filler = fillerOf(subj);
227
- const tail = proj.ctx.subarray(seat.cs);
228
- let answer = await joinWithBridge(ctx, filler, tail);
229
- const fwd = await follow(ctx, proj.anchor, qv);
230
- if (fwd !== null && indexOf(answer, fwd, 0) < 0) {
231
- answer = concat2(answer, fwd);
232
- }
233
- ctx.trace?.step(
234
- "projectCounterfactual",
235
- [
236
- rItem(filler, "filler", subj.anchor),
237
- rNode(ctx, proj.anchor, "displaced-structure"),
238
- ],
239
- [rItem(answer, "projection")],
240
- "transfer the displaced structure onto the subject filler (seat substitution)",
241
- );
242
- record(
243
- answer,
244
- "counterfactual substitution the subject fills the analog's seat",
245
- new Set([subj.anchor, proj.anchor]),
246
- // The acts performed: one seat INSERT projection + one edge FOLLOW.
247
- STEP + STEP,
248
- // What substitution actually READ: the two points it transfers
249
- // between — the subject filling the seat, and the displaced
250
- // structure whose seat it fills — not every OTHER point the weave
251
- // happened to align (a third, unrelated point in the same weave
252
- // contributes nothing to what substitution itself explains).
253
- [...runSpans(subj), ...runSpans(proj)],
254
- );
255
- }
256
- // ── REDIRECTION ────────────────────────────────────────────────────
257
- const last = points.reduce((a, b) => lastRun(b).qs > lastRun(a).qs ? b : a);
258
- // Displacement test, capped at the hub bound: a hub anchor can carry a
259
- // corpus-sized fan-out, and each continuation costs a full byte
260
- // reconstruction plus an O(|query|·|bytes|) scan. The first √N edges (the
261
- // same insertion-order convention chooseNext caps by) decide; past a hub's
262
- // cap the test reads "none of the established continuations appears".
263
- const domNext = ctx.store.nextFirst(dominant.anchor, hubBound(ctx));
264
- const displaced = domNext
265
- .every((n) => indexOf(query, read(ctx, n), 0) < 0);
266
- if (
267
- last !== dominant &&
268
- last.runs[0].cs === 0 && displaced &&
269
- usable(last.runs[0].qs, last.runs[0].qe)
270
- ) {
271
- const g = await project(ctx, last.anchor, qv);
272
- if (g !== null) {
273
- ctx.trace?.step(
274
- "projectCounterfactual",
275
- [
276
- rNode(ctx, dominant.anchor, "displaced-structure"),
277
- rNode(ctx, last.anchor, "substitute"),
278
- ],
279
- [rItem(g, "projection")],
280
- "the substitute's own fact replaces the displaced structure's answer",
281
- );
282
- record(
283
- g,
284
- "counterfactual redirection — the named substitute's fact is followed",
285
- new Set([dominant.anchor, last.anchor]),
286
- // One forward projection across the substitute's own fact.
287
- STEP,
288
- // What redirection READ: the displaced structure's own recognized
289
- // seat (still explained — this schema RECOGNIZES it as the slot
290
- // being overridden, it just doesn't answer from it) plus the named
291
- // substitute's own aligned run — not every OTHER point the weave
292
- // happened to align.
293
- [...runSpans(dominant), ...runSpans(last)],
294
- );
295
- }
296
- }
297
- // ── COMPARISON ─────────────────────────────────────────────────────
298
- // Collect every qualifying non-dominant point as a candidate analog.
299
- // When a point's own anchor is structurally at the wrong level
300
- // (e.g. a long exemplar sentence whose halo does not resemble the
301
- // dominant's), its nextOf targets often point to the right level — the
302
- // person / concept the exemplar is about. Trying both prevents a
303
- // seed-dependent failure where the climb ranks an exemplar above a
304
- // person node and the person node is excluded from points by run-
305
- // overlap trimming.
306
- // The seat that establishes a candidate's role: the REVERSE projection
307
- // (the context it follows), voiced by the query gist — falling back to the
308
- // candidate's own bytes when it follows nothing. DELIBERATE STRENGTHENING
309
- // over the pre-refactor code: reverseContext also returns null when the
310
- // picked context READS EMPTY (a dangling id degrades to zero bytes), so a
311
- // corrupted-store read now falls back here instead of voicing a hollow
312
- // seat into the comparison — the same "empty bytes are no grounding"
313
- // invariant project() has always enforced.
314
- const seatOfNode = (id, fallback) => reverseContext(ctx, id, qv) ?? fallback;
315
- const seatOf = (p) => seatOfNode(p.anchor, p.ctx);
316
- const analogs = [];
317
- for (const p of points) {
318
- if (p === dominant) {
319
- continue;
320
- }
321
- // Push the point's own anchor only when its context fits within
322
- // the query (the seat sentence must not dominate the comparison).
323
- if (
324
- p.ctx.length <= query.length &&
325
- indexOf(dominant.ctx, p.ctx, 0) < 0 &&
326
- indexOf(p.ctx, dominant.ctx, 0) < 0 &&
327
- indexOf(query, p.ctx, 0) < 0
328
- ) {
329
- analogs.push({ anchor: p.anchor, point: p });
191
+ .filter((c) => c !== null);
192
+ const picked = argmaxBy(displacement, (c) => c.depth, -Infinity, true);
193
+ const proj = picked?.item.p ?? null;
194
+ const subj = picked?.item.before ?? null;
195
+ if (proj !== null && subj !== null) {
196
+ const seat = proj.runs[0];
197
+ const filler = fillerOf(subj);
198
+ const tail = proj.ctx.subarray(seat.cs);
199
+ let answer = await joinWithBridge(ctx, filler, tail);
200
+ const fwd = await follow(ctx, proj.anchor, qv);
201
+ if (fwd !== null && indexOf(answer, fwd, 0) < 0) {
202
+ answer = concat2(answer, fwd);
203
+ }
204
+ ctx.trace?.step("projectCounterfactual", [
205
+ rItem(filler, "filler", subj.anchor),
206
+ rNode(ctx, proj.anchor, "displaced-structure"),
207
+ ], [rItem(answer, "projection")], "transfer the displaced structure onto the subject filler (seat substitution)");
208
+ record(answer, "counterfactual substitution — the subject fills the analog's seat", new Set([subj.anchor, proj.anchor]),
209
+ // The acts performed: one seat INSERT projection + one edge FOLLOW.
210
+ STEP + STEP,
211
+ // What substitution actually READ: the two points it transfers
212
+ // between — the subject filling the seat, and the displaced
213
+ // structure whose seat it fills — not every OTHER point the weave
214
+ // happened to align (a third, unrelated point in the same weave
215
+ // contributes nothing to what substitution itself explains).
216
+ [...runSpans(subj), ...runSpans(proj)]);
330
217
  }
331
- // Reach through to the point's continuation targets regardless
332
- // of the point's own context length: when the point is a leaf
333
- // (exemplar sentence), its nextOf is the hub (person / concept)
334
- // that makes a genuine cross-domain analog, and the hub's own
335
- // (shorter) context will be the seat.
336
- // Capped like every fan-out: a hub anchor's full continuation list is
337
- // corpus-sized, and each candidate costs a read plus O(|query|·|bytes|)
338
- // scans only the first √N (insertion order, the same convention
339
- // chooseNext caps by) are reachable as analogs.
340
- for (const nid of ctx.store.nextFirst(p.anchor, hubBound(ctx))) {
341
- const nctx = read(ctx, nid);
342
- if (
343
- nctx.length > query.length ||
344
- indexOf(dominant.ctx, nctx, 0) >= 0 ||
345
- indexOf(nctx, dominant.ctx, 0) >= 0 ||
346
- indexOf(query, nctx, 0) >= 0
347
- ) {
348
- continue;
349
- }
350
- analogs.push({ anchor: nid, point: null });
218
+ // ── REDIRECTION ────────────────────────────────────────────────────
219
+ const last = points.reduce((a, b) => lastRun(b).qs > lastRun(a).qs ? b : a);
220
+ // Displacement test, capped at the hub bound: a hub anchor can carry a
221
+ // corpus-sized fan-out, and each continuation costs a full byte
222
+ // reconstruction plus an O(|query|·|bytes|) scan. The first √N edges (the
223
+ // same insertion-order convention chooseNext caps by) decide; past a hub's
224
+ // cap the test reads "none of the established continuations appears".
225
+ const domNext = ctx.store.nextFirst(dominant.anchor, hubBound(ctx));
226
+ const displaced = domNext
227
+ .every((n) => indexOf(query, read(ctx, n), 0) < 0);
228
+ if (last !== dominant &&
229
+ last.runs[0].cs === 0 && displaced &&
230
+ usable(last.runs[0].qs, last.runs[0].qe)) {
231
+ const g = await project(ctx, last.anchor, qv);
232
+ if (g !== null) {
233
+ ctx.trace?.step("projectCounterfactual", [
234
+ rNode(ctx, dominant.anchor, "displaced-structure"),
235
+ rNode(ctx, last.anchor, "substitute"),
236
+ ], [rItem(g, "projection")], "the substitute's own fact replaces the displaced structure's answer");
237
+ record(g, "counterfactual redirection — the named substitute's fact is followed", new Set([dominant.anchor, last.anchor]),
238
+ // One forward projection across the substitute's own fact.
239
+ STEP,
240
+ // What redirection READ: the displaced structure's own recognized
241
+ // seat (still explained — this schema RECOGNIZES it as the slot
242
+ // being overridden, it just doesn't answer from it) plus the named
243
+ // substitute's own aligned run — not every OTHER point the weave
244
+ // happened to align.
245
+ [...runSpans(dominant), ...runSpans(last)]);
246
+ }
351
247
  }
352
- }
353
- let bestAnalog = null;
354
- let bestSim = 0;
355
- for (const c of analogs) {
356
- const sim = await analogyStrength(ctx, dominant.anchor, c.anchor);
357
- ctx.trace?.step(
358
- "tryAnalog",
359
- [
360
- rNode(ctx, dominant.anchor, "dominant"),
361
- rNode(ctx, c.anchor, "candidate", sim),
362
- ],
363
- [],
364
- `analogy strength ${sim.toFixed(4)}`,
365
- );
366
- if (sim > bestSim) {
367
- bestSim = sim;
368
- bestAnalog = c;
248
+ // ── COMPARISON ─────────────────────────────────────────────────────
249
+ // Collect every qualifying non-dominant point as a candidate analog.
250
+ // When a point's own anchor is structurally at the wrong level
251
+ // (e.g. a long exemplar sentence whose halo does not resemble the
252
+ // dominant's), its nextOf targets often point to the right level — the
253
+ // person / concept the exemplar is about. Trying both prevents a
254
+ // seed-dependent failure where the climb ranks an exemplar above a
255
+ // person node and the person node is excluded from points by run-
256
+ // overlap trimming.
257
+ // The seat that establishes a candidate's role: the REVERSE projection
258
+ // (the context it follows), voiced by the query gist — falling back to the
259
+ // candidate's own bytes when it follows nothing. DELIBERATE STRENGTHENING
260
+ // over the pre-refactor code: reverseContext also returns null when the
261
+ // picked context READS EMPTY (a dangling id degrades to zero bytes), so a
262
+ // corrupted-store read now falls back here instead of voicing a hollow
263
+ // seat into the comparison — the same "empty bytes are no grounding"
264
+ // invariant project() has always enforced.
265
+ const seatOfNode = (id, fallback) => reverseContext(ctx, id, qv) ?? fallback;
266
+ const seatOf = (p) => seatOfNode(p.anchor, p.ctx);
267
+ const analogs = [];
268
+ for (const p of points) {
269
+ if (p === dominant)
270
+ continue;
271
+ // Push the point's own anchor only when its context fits within
272
+ // the query (the seat sentence must not dominate the comparison).
273
+ if (p.ctx.length <= query.length &&
274
+ indexOf(dominant.ctx, p.ctx, 0) < 0 &&
275
+ indexOf(p.ctx, dominant.ctx, 0) < 0 &&
276
+ indexOf(query, p.ctx, 0) < 0) {
277
+ analogs.push({ anchor: p.anchor, point: p });
278
+ }
279
+ // Reach through to the point's continuation targets regardless
280
+ // of the point's own context length: when the point is a leaf
281
+ // (exemplar sentence), its nextOf is the hub (person / concept)
282
+ // that makes a genuine cross-domain analog, and the hub's own
283
+ // (shorter) context will be the seat.
284
+ // Capped like every fan-out: a hub anchor's full continuation list is
285
+ // corpus-sized, and each candidate costs a read plus O(|query|·|bytes|)
286
+ // scans — only the first √N (insertion order, the same convention
287
+ // chooseNext caps by) are reachable as analogs.
288
+ for (const nid of ctx.store.nextFirst(p.anchor, hubBound(ctx))) {
289
+ const nctx = read(ctx, nid);
290
+ if (nctx.length > query.length ||
291
+ indexOf(dominant.ctx, nctx, 0) >= 0 ||
292
+ indexOf(nctx, dominant.ctx, 0) >= 0 ||
293
+ indexOf(query, nctx, 0) >= 0)
294
+ continue;
295
+ analogs.push({ anchor: nid, point: null });
296
+ }
369
297
  }
370
- }
371
- // When every candidate fails the similarity gates (halo company — now
372
- // deterministic signatures, see sema.ts — and the shared-frame tier),
373
- // fall back to a candidate that is a genuine structural hub (edges in
374
- // BOTH directions). A hub node — a person, concept, or category — is
375
- // the kind of thing that makes sense to compare across domains. A leaf
376
- // value (extracted span, terminal answer) has edges in at most one
377
- // direction and comparing it would preempt the extraction pipeline,
378
- // which is the right mechanism for those. A fallback comparison carries
379
- // NO similarity evidence — it stays honest only because the grounding
380
- // decider weighs it against mechanisms that explain more of the query
381
- // (extraction accounts its whole located envelope; see extraction.ts).
382
- //
383
- // WHICH hub: not the first in `analogs` order — that order flows from the
384
- // vote ranking, which flows from approximate resonance, which is seed-
385
- // dependent. Pick by evidence instead: combined edge support (prevCount +
386
- // fan-out), tie-broken by poured halo MASS (episode corroboration — the
387
- // direct distributional evidence), then by LOWEST node id. The id order
388
- // is a property of the corpus, not of the seed — but note ids are SIGNED:
389
- // byte leaves occupy −256…−1, so "lowest id" is creation order only among
390
- // multi-byte nodes and byte-value order among leaves. Either way it is
391
- // deterministic, which is all the final tie-break must be.
392
- if (bestAnalog === null && analogs.length > 0) {
393
- let hubSupport = -1;
394
- let hubMass = -1;
395
- const fanClamp = hubBound(ctx) + 1;
298
+ let bestAnalog = null;
299
+ let bestSim = 0;
396
300
  for (const c of analogs) {
397
- // Evidence clamped at the hub bound: beyond √N + 1 the exact fan-out
398
- // no longer discriminates (every mega-hub ties at the clamp), and
399
- // counting it exactly would require the corpus-sized read.
400
- const fanOut = ctx.store.nextFirst(c.anchor, fanClamp).length;
401
- if (fanOut === 0) {
402
- continue;
403
- }
404
- const support = ctx.store.prevCount(c.anchor);
405
- if (support === 0) {
406
- continue;
407
- }
408
- const total = support + fanOut;
409
- if (total < hubSupport) {
410
- continue;
411
- }
412
- const mass = ctx.store.haloMass(c.anchor);
413
- if (
414
- total > hubSupport ||
415
- mass > hubMass ||
416
- (mass === hubMass && bestAnalog !== null &&
417
- c.anchor < bestAnalog.anchor)
418
- ) {
419
- hubSupport = total;
420
- hubMass = mass;
421
- bestAnalog = c;
422
- }
301
+ const sim = await analogyStrength(ctx, dominant.anchor, c.anchor);
302
+ ctx.trace?.step("tryAnalog", [
303
+ rNode(ctx, dominant.anchor, "dominant"),
304
+ rNode(ctx, c.anchor, "candidate", sim),
305
+ ], [], `analogy strength ${sim.toFixed(4)}`);
306
+ if (sim > bestSim) {
307
+ bestSim = sim;
308
+ bestAnalog = c;
309
+ }
423
310
  }
424
- if (bestAnalog !== null) {
425
- ctx.trace?.step(
426
- "tryAnalog",
427
- [],
428
- [rNode(ctx, bestAnalog.anchor, "fallback", hubSupport)],
429
- "no candidate passed the similarity gates using the best-supported structural hub",
430
- );
311
+ // When every candidate fails the similarity gates (halo company now
312
+ // deterministic signatures, see sema.ts — and the shared-frame tier),
313
+ // fall back to a candidate that is a genuine structural hub (edges in
314
+ // BOTH directions). A hub node — a person, concept, or category — is
315
+ // the kind of thing that makes sense to compare across domains. A leaf
316
+ // value (extracted span, terminal answer) has edges in at most one
317
+ // direction and comparing it would preempt the extraction pipeline,
318
+ // which is the right mechanism for those. A fallback comparison carries
319
+ // NO similarity evidence — it stays honest only because the grounding
320
+ // decider weighs it against mechanisms that explain more of the query
321
+ // (extraction accounts its whole located envelope; see extraction.ts).
322
+ //
323
+ // WHICH hub: not the first in `analogs` order — that order flows from the
324
+ // vote ranking, which flows from approximate resonance, which is seed-
325
+ // dependent. Pick by evidence instead: combined edge support (prevCount +
326
+ // fan-out), tie-broken by poured halo MASS (episode corroboration — the
327
+ // direct distributional evidence), then by LOWEST node id. The id order
328
+ // is a property of the corpus, not of the seed — but note ids are SIGNED:
329
+ // byte leaves occupy −256…−1, so "lowest id" is creation order only among
330
+ // multi-byte nodes and byte-value order among leaves. Either way it is
331
+ // deterministic, which is all the final tie-break must be.
332
+ if (bestAnalog === null && analogs.length > 0) {
333
+ let hubSupport = -1;
334
+ let hubMass = -1;
335
+ const fanClamp = hubBound(ctx) + 1;
336
+ for (const c of analogs) {
337
+ // Evidence clamped at the hub bound: beyond √N + 1 the exact fan-out
338
+ // no longer discriminates (every mega-hub ties at the clamp), and
339
+ // counting it exactly would require the corpus-sized read.
340
+ const fanOut = ctx.store.nextFirst(c.anchor, fanClamp).length;
341
+ if (fanOut === 0)
342
+ continue;
343
+ const support = ctx.store.prevCount(c.anchor);
344
+ if (support === 0)
345
+ continue;
346
+ const total = support + fanOut;
347
+ if (total < hubSupport)
348
+ continue;
349
+ const mass = ctx.store.haloMass(c.anchor);
350
+ if (total > hubSupport ||
351
+ mass > hubMass ||
352
+ (mass === hubMass && bestAnalog !== null &&
353
+ c.anchor < bestAnalog.anchor)) {
354
+ hubSupport = total;
355
+ hubMass = mass;
356
+ bestAnalog = c;
357
+ }
358
+ }
359
+ if (bestAnalog !== null) {
360
+ ctx.trace?.step("tryAnalog", [], [rNode(ctx, bestAnalog.anchor, "fallback", hubSupport)], "no candidate passed the similarity gates — using the best-supported structural hub");
361
+ }
431
362
  }
432
- }
433
- ctx.trace?.step(
434
- "tryAnalog",
435
- [],
436
- bestAnalog !== null ? [rNode(ctx, bestAnalog.anchor, "best", bestSim)] : [],
437
- bestAnalog !== null
438
- ? `best analog with strength ${bestSim.toFixed(4)}`
439
- : `no analog candidate passed (${analogs.length} checked)`,
440
- );
441
- // COMPARISON gate analogical comparison seats the dominant against ONE
442
- // analog, so it presupposes the query is ABOUT a single thing. When the
443
- // consensus climb instead committed to MULTIPLE independent points of
444
- // attention (`roots.length > 1`), the query names independent topics to
445
- // FUSE — the reasoner's fuseAttention already combines them — not analogs
446
- // to compare. Firing here would juxtapose two co-scaffolded but unrelated
447
- // records (each sharing only the corpus preamble), out-accounting the
448
- // honest thin multi-root grounding with a frame echo. Derived from the
449
- // climb's own forest, never tuned; substitution/redirection stay
450
- // unaffected they orient around a displaced seat, not a whole-topic
451
- // analogy.
452
- if (
453
- bestAnalog !== null &&
454
- dominant.ctx.length <= query.length &&
455
- roots.length <= 1
456
- ) {
457
- ctx.trace?.step(
458
- "validateAnalogy",
459
- [
460
- rNode(ctx, dominant.anchor, "analog", bestSim),
461
- rNode(ctx, bestAnalog.anchor, "analog", bestSim),
462
- ],
463
- [],
464
- "the two structures keep distributional company beyond chance genuine analogs",
465
- );
466
- const a = seatOf(dominant);
467
- const b = bestAnalog.point !== null
468
- ? seatOf(bestAnalog.point)
469
- : seatOfNode(bestAnalog.anchor, read(ctx, bestAnalog.anchor));
470
- const answer = await joinWithBridge(ctx, a, b);
471
- record(
472
- answer,
473
- "analogical comparisoneach analog voiced by the context that establishes its role",
474
- new Set([dominant.anchor, bestAnalog.anchor]),
475
- // A halo-mediated act (the analogy gate) plus two seat projections.
476
- CONCEPT + STEP + STEP,
477
- // What comparison READ: the dominant's own aligned runs, plus the
478
- // analog's aligned runs when it was itself an aligned point (a nextOf
479
- // descendant was never aligned to the query directly, so it
480
- // contributes no accounted span — its seat is graph-reached, not
481
- // query-matched).
482
- [
483
- ...runSpans(dominant),
484
- ...(bestAnalog.point !== null ? runSpans(bestAnalog.point) : []),
485
- ],
486
- );
487
- }
488
- t?.done(
489
- results.map((r) => rItem(r.bytes, "answer")),
490
- results.length > 0
491
- ? `${results.length} counterfactual schema(s) fired — the grounding decider weighs them`
492
- : "no counterfactual weave — the ordinary pipeline decides",
493
- );
494
- return results;
363
+ ctx.trace?.step("tryAnalog", [], bestAnalog !== null ? [rNode(ctx, bestAnalog.anchor, "best", bestSim)] : [], bestAnalog !== null
364
+ ? `best analog with strength ${bestSim.toFixed(4)}`
365
+ : `no analog candidate passed (${analogs.length} checked)`);
366
+ // COMPARISON gate — analogical comparison seats the dominant against ONE
367
+ // analog, so it presupposes the query is ABOUT a single thing. When the
368
+ // consensus climb instead committed to MULTIPLE independent points of
369
+ // attention (`roots.length > 1`), the query names independent topics to
370
+ // FUSE the reasoner's fuseAttention already combines them — not analogs
371
+ // to compare. Firing here would juxtapose two co-scaffolded but unrelated
372
+ // records (each sharing only the corpus preamble), out-accounting the
373
+ // honest thin multi-root grounding with a frame echo. Derived from the
374
+ // climb's own forest, never tuned; substitution/redirection stay
375
+ // unaffected they orient around a displaced seat, not a whole-topic
376
+ // analogy.
377
+ if (bestAnalog !== null &&
378
+ dominant.ctx.length <= query.length &&
379
+ roots.length <= 1) {
380
+ ctx.trace?.step("validateAnalogy", [
381
+ rNode(ctx, dominant.anchor, "analog", bestSim),
382
+ rNode(ctx, bestAnalog.anchor, "analog", bestSim),
383
+ ], [], "the two structures keep distributional company beyond chance — genuine analogs");
384
+ const a = seatOf(dominant);
385
+ const b = bestAnalog.point !== null
386
+ ? seatOf(bestAnalog.point)
387
+ : seatOfNode(bestAnalog.anchor, read(ctx, bestAnalog.anchor));
388
+ const answer = await joinWithBridge(ctx, a, b);
389
+ record(answer, "analogical comparison — each analog voiced by the context that establishes its role", new Set([dominant.anchor, bestAnalog.anchor]),
390
+ // A halo-mediated act (the analogy gate) plus two seat projections.
391
+ CONCEPT + STEP + STEP,
392
+ // What comparison READ: the dominant's own aligned runs, plus the
393
+ // analog's aligned runs when it was itself an aligned point (a nextOf
394
+ // descendant was never aligned to the query directly, so it
395
+ // contributes no accounted span its seat is graph-reached, not
396
+ // query-matched).
397
+ [
398
+ ...runSpans(dominant),
399
+ ...(bestAnalog.point !== null ? runSpans(bestAnalog.point) : []),
400
+ ]);
401
+ }
402
+ t?.done(results.map((r) => rItem(r.bytes, "answer")), results.length > 0
403
+ ? `${results.length} counterfactual schema(s) fired — the grounding decider weighs them`
404
+ : "no counterfactual weave — the ordinary pipeline decides");
405
+ return results;
495
406
  }
496
407
  export const castMechanism = {
497
- name: "cast",
498
- provenance: "cast",
499
- async floor(_ctx, query, pre, worthRunning) {
500
- const W = _ctx.space.maxGroup;
501
- // Cheap checks first — no pre-computation needed.
502
- if (query.length < 2 * W || _ctx.store.edgeSourceCount() === 0) {
503
- return null;
504
- }
505
- // CAST's floor, when it exists, is ALWAYS exactly 2*STEP the climb and
506
- // the weave only decide whether it exists (2*STEP) or not (null), they
507
- // never tighten the number itself. So if 2*STEP already can't beat
508
- // whatever incumbent has already won this response (cover runs first —
509
- // see defaultMechanisms), no analysis can change the outcome: RETURN THE
510
- // BOUND uninvested (still admissible) and let the pipeline's own check
511
- // prune run() with the truthful "cannot beat incumbent" note. This is
512
- // the SAME admissible-floor economy worthRunning applies to run(),
513
- // applied to floor()'s own investment uniformly, whatever mechanism
514
- // supplied the incumbent (an extension's computed result is not
515
- // special-cased; any sufficiently cheap incumbent prunes the same way).
516
- if (!worthRunning(2 * STEP)) {
517
- return 2 * STEP;
518
- }
519
- // Now first-touch the shared analyses (climb, then the weave built on
520
- // it). If another mechanism already triggered either, this awaits the
521
- // cached result; otherwise it's computed once here and reused in run().
522
- if ((await pre.attention()).ranked.length < 2) {
523
- return null;
524
- }
525
- if ((await pre.weave()).points.length < 2) {
526
- return null;
527
- }
528
- return 2 * STEP;
529
- },
530
- async run(ctx, query, pre) {
531
- const casts = await counterfactualTransfer(ctx, query, pre);
532
- return casts.map((c) => ({
533
- bytes: c.bytes,
534
- accounted: c.accounted,
535
- moves: c.moves,
536
- used: c.used,
537
- unexplained: c.unexplained,
538
- }));
539
- },
408
+ name: "cast",
409
+ provenance: "cast",
410
+ async floor(_ctx, query, pre, worthRunning) {
411
+ const W = _ctx.space.maxGroup;
412
+ // Cheap checks first — no pre-computation needed.
413
+ if (query.length < 2 * W || _ctx.store.edgeSourceCount() === 0)
414
+ return null;
415
+ // CAST's floor, when it exists, is ALWAYS exactly 2*STEP — the climb and
416
+ // the weave only decide whether it exists (2*STEP) or not (null), they
417
+ // never tighten the number itself. So if 2*STEP already can't beat
418
+ // whatever incumbent has already won this response (cover runs first —
419
+ // see defaultMechanisms), no analysis can change the outcome: RETURN THE
420
+ // BOUND uninvested (still admissible) and let the pipeline's own check
421
+ // prune run() with the truthful "cannot beat incumbent" note. This is
422
+ // the SAME admissible-floor economy worthRunning applies to run(),
423
+ // applied to floor()'s own investment uniformly, whatever mechanism
424
+ // supplied the incumbent (an extension's computed result is not
425
+ // special-cased; any sufficiently cheap incumbent prunes the same way).
426
+ if (!worthRunning(2 * STEP))
427
+ return 2 * STEP;
428
+ // Now first-touch the shared analyses (climb, then the weave built on
429
+ // it). If another mechanism already triggered either, this awaits the
430
+ // cached result; otherwise it's computed once here and reused in run().
431
+ if ((await pre.attention()).ranked.length < 2)
432
+ return null;
433
+ if ((await pre.weave()).points.length < 2)
434
+ return null;
435
+ return 2 * STEP;
436
+ },
437
+ async run(ctx, query, pre) {
438
+ const casts = await counterfactualTransfer(ctx, query, pre);
439
+ return casts.map((c) => ({
440
+ bytes: c.bytes,
441
+ accounted: c.accounted,
442
+ moves: c.moves,
443
+ used: c.used,
444
+ unexplained: c.unexplained,
445
+ }));
446
+ },
540
447
  };