@hviana/sema 0.5.2 → 0.5.4

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 (157) hide show
  1. package/AGENTS.md +115 -53
  2. package/HOW_IT_WORKS.md +291 -184
  3. package/jsr.json +6 -0
  4. package/package.json +1 -1
  5. package/src/mind/bridge.ts +10 -104
  6. package/src/mind/match.ts +497 -7
  7. package/src/mind/{prefix-completion.ts → mechanisms/prefix-completion.ts} +66 -92
  8. package/src/mind/mechanisms/recall.ts +38 -129
  9. package/src/mind/mechanisms/reference.ts +343 -0
  10. package/src/mind/mind.ts +12 -8
  11. package/src/mind/pipeline-mechanism.ts +120 -3
  12. package/src/mind/pipeline.ts +16 -2
  13. package/src/mind/traverse.ts +92 -1
  14. package/src/store.ts +13 -4
  15. package/test/33-multi-candidate.test.mjs +21 -11
  16. package/test/70-prefix-completion.test.mjs +1 -1
  17. package/test/72-prefix-candidate-supply.test.mjs +7 -9
  18. package/test/74-prefix-trap-not-sprung-early.test.mjs +1 -1
  19. package/test/76-reference-binding.test.mjs +543 -0
  20. package/dist/example/demo.d.ts +0 -1
  21. package/dist/example/demo.js +0 -39
  22. package/dist/example/train_base.d.ts +0 -93
  23. package/dist/example/train_base.js +0 -2269
  24. package/dist/src/alphabet.d.ts +0 -7
  25. package/dist/src/alphabet.js +0 -33
  26. package/dist/src/alu/src/alu.d.ts +0 -185
  27. package/dist/src/alu/src/alu.js +0 -440
  28. package/dist/src/alu/src/expr.d.ts +0 -61
  29. package/dist/src/alu/src/expr.js +0 -318
  30. package/dist/src/alu/src/index.d.ts +0 -11
  31. package/dist/src/alu/src/index.js +0 -19
  32. package/dist/src/alu/src/kernel-arith.d.ts +0 -16
  33. package/dist/src/alu/src/kernel-arith.js +0 -264
  34. package/dist/src/alu/src/kernel-bits.d.ts +0 -19
  35. package/dist/src/alu/src/kernel-bits.js +0 -152
  36. package/dist/src/alu/src/kernel-logic.d.ts +0 -4
  37. package/dist/src/alu/src/kernel-logic.js +0 -60
  38. package/dist/src/alu/src/kernel-nd.d.ts +0 -3
  39. package/dist/src/alu/src/kernel-nd.js +0 -208
  40. package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
  41. package/dist/src/alu/src/kernel-numeric.js +0 -366
  42. package/dist/src/alu/src/operation.d.ts +0 -168
  43. package/dist/src/alu/src/operation.js +0 -189
  44. package/dist/src/alu/src/parser.d.ts +0 -221
  45. package/dist/src/alu/src/parser.js +0 -577
  46. package/dist/src/alu/src/resonance.d.ts +0 -55
  47. package/dist/src/alu/src/resonance.js +0 -126
  48. package/dist/src/alu/src/text.d.ts +0 -31
  49. package/dist/src/alu/src/text.js +0 -73
  50. package/dist/src/alu/src/value.d.ts +0 -109
  51. package/dist/src/alu/src/value.js +0 -300
  52. package/dist/src/alu/test/alu.test.d.ts +0 -1
  53. package/dist/src/alu/test/alu.test.js +0 -764
  54. package/dist/src/bytes.d.ts +0 -14
  55. package/dist/src/bytes.js +0 -59
  56. package/dist/src/canon.d.ts +0 -45
  57. package/dist/src/canon.js +0 -85
  58. package/dist/src/config.d.ts +0 -111
  59. package/dist/src/config.js +0 -91
  60. package/dist/src/derive/src/deduction.d.ts +0 -136
  61. package/dist/src/derive/src/deduction.js +0 -159
  62. package/dist/src/derive/src/index.d.ts +0 -8
  63. package/dist/src/derive/src/index.js +0 -11
  64. package/dist/src/derive/src/priority-queue.d.ts +0 -20
  65. package/dist/src/derive/src/priority-queue.js +0 -73
  66. package/dist/src/derive/src/rewrite.d.ts +0 -56
  67. package/dist/src/derive/src/rewrite.js +0 -100
  68. package/dist/src/derive/src/trie.d.ts +0 -90
  69. package/dist/src/derive/src/trie.js +0 -217
  70. package/dist/src/derive/test/derive.test.d.ts +0 -1
  71. package/dist/src/derive/test/derive.test.js +0 -122
  72. package/dist/src/extension.d.ts +0 -37
  73. package/dist/src/extension.js +0 -7
  74. package/dist/src/geometry.d.ts +0 -233
  75. package/dist/src/geometry.js +0 -1089
  76. package/dist/src/index.d.ts +0 -17
  77. package/dist/src/index.js +0 -19
  78. package/dist/src/ingest-cache.d.ts +0 -41
  79. package/dist/src/ingest-cache.js +0 -165
  80. package/dist/src/meter.d.ts +0 -176
  81. package/dist/src/meter.js +0 -274
  82. package/dist/src/mind/articulation.d.ts +0 -6
  83. package/dist/src/mind/articulation.js +0 -99
  84. package/dist/src/mind/attention.d.ts +0 -430
  85. package/dist/src/mind/attention.js +0 -2418
  86. package/dist/src/mind/bridge.d.ts +0 -39
  87. package/dist/src/mind/bridge.js +0 -998
  88. package/dist/src/mind/canonical.d.ts +0 -34
  89. package/dist/src/mind/canonical.js +0 -93
  90. package/dist/src/mind/frame-filler.d.ts +0 -15
  91. package/dist/src/mind/frame-filler.js +0 -535
  92. package/dist/src/mind/graph-search.d.ts +0 -294
  93. package/dist/src/mind/graph-search.js +0 -996
  94. package/dist/src/mind/index.d.ts +0 -9
  95. package/dist/src/mind/index.js +0 -5
  96. package/dist/src/mind/junction.d.ts +0 -137
  97. package/dist/src/mind/junction.js +0 -358
  98. package/dist/src/mind/learning.d.ts +0 -75
  99. package/dist/src/mind/learning.js +0 -265
  100. package/dist/src/mind/match.d.ts +0 -192
  101. package/dist/src/mind/match.js +0 -666
  102. package/dist/src/mind/mechanisms/alu.d.ts +0 -4
  103. package/dist/src/mind/mechanisms/alu.js +0 -36
  104. package/dist/src/mind/mechanisms/cast.d.ts +0 -89
  105. package/dist/src/mind/mechanisms/cast.js +0 -1186
  106. package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
  107. package/dist/src/mind/mechanisms/confluence.js +0 -271
  108. package/dist/src/mind/mechanisms/cover.d.ts +0 -6
  109. package/dist/src/mind/mechanisms/cover.js +0 -232
  110. package/dist/src/mind/mechanisms/extraction.d.ts +0 -33
  111. package/dist/src/mind/mechanisms/extraction.js +0 -327
  112. package/dist/src/mind/mechanisms/recall.d.ts +0 -16
  113. package/dist/src/mind/mechanisms/recall.js +0 -558
  114. package/dist/src/mind/mind.d.ts +0 -386
  115. package/dist/src/mind/mind.js +0 -754
  116. package/dist/src/mind/pipeline-mechanism.d.ts +0 -183
  117. package/dist/src/mind/pipeline-mechanism.js +0 -521
  118. package/dist/src/mind/pipeline.d.ts +0 -49
  119. package/dist/src/mind/pipeline.js +0 -331
  120. package/dist/src/mind/prefix-completion.d.ts +0 -59
  121. package/dist/src/mind/prefix-completion.js +0 -270
  122. package/dist/src/mind/primitives.d.ts +0 -85
  123. package/dist/src/mind/primitives.js +0 -333
  124. package/dist/src/mind/rationale.d.ts +0 -139
  125. package/dist/src/mind/rationale.js +0 -163
  126. package/dist/src/mind/reasoning.d.ts +0 -42
  127. package/dist/src/mind/reasoning.js +0 -305
  128. package/dist/src/mind/recognition.d.ts +0 -20
  129. package/dist/src/mind/recognition.js +0 -696
  130. package/dist/src/mind/resonance.d.ts +0 -35
  131. package/dist/src/mind/resonance.js +0 -365
  132. package/dist/src/mind/trace.d.ts +0 -15
  133. package/dist/src/mind/trace.js +0 -73
  134. package/dist/src/mind/traverse.d.ts +0 -158
  135. package/dist/src/mind/traverse.js +0 -716
  136. package/dist/src/mind/types.d.ts +0 -418
  137. package/dist/src/mind/types.js +0 -179
  138. package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
  139. package/dist/src/rabitq-ivf/src/database.js +0 -201
  140. package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
  141. package/dist/src/rabitq-ivf/src/index.js +0 -4
  142. package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
  143. package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
  144. package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
  145. package/dist/src/rabitq-ivf/src/prng.js +0 -36
  146. package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
  147. package/dist/src/rabitq-ivf/src/rabitq.js +0 -313
  148. package/dist/src/sema.d.ts +0 -41
  149. package/dist/src/sema.js +0 -77
  150. package/dist/src/store-sqlite.d.ts +0 -184
  151. package/dist/src/store-sqlite.js +0 -942
  152. package/dist/src/store.d.ts +0 -762
  153. package/dist/src/store.js +0 -1823
  154. package/dist/src/vec.d.ts +0 -31
  155. package/dist/src/vec.js +0 -109
  156. package/src/mind/frame-filler.ts +0 -604
  157. package/test/69-frame-filler.test.mjs +0 -115
@@ -1,1186 +0,0 @@
1
- // counterfactual.ts — Counterfactual Transfer / CAST (Section 4 of the mind).
2
- //
3
- // When a query weaves together byte-string evidence from multiple independently-
4
- // learnt structures (disjoint run alignments, literal or distributional), CAST
5
- // attempts to transfer structure between them — substitution, redirection, or
6
- // analogical comparison — producing a counterfactual answer that goes beyond what
7
- // the ordinary cover-and-extract pipeline can reach.
8
- //
9
- // CAST is a configuration of the elementary match-and-project operation
10
- // (match.ts): matcher = alignGraded (literal W-gram runs + halo-matched pre.rec.sites),
11
- // gate = the frame gate below + analogyStrength, projection = insert / project /
12
- // juxtapose.
13
- import { read } from "../primitives.js";
14
- import { argmaxBy, corpusN, edgeAncestors, hubBound, sharedReachMemo, } from "../traverse.js";
15
- import { analogyStrength, follow, project, reverseContext, sharedFrameStrengthOf, } from "../match.js";
16
- import { joinWithBridge } from "../resonance.js";
17
- import { restatesQuery } from "../reasoning.js";
18
- import { CONCEPT, STEP } from "../graph-search.js";
19
- import { concat2, indexOf } from "../../bytes.js";
20
- import { consensusFloor, dominates } from "../../geometry.js";
21
- import { unexplainedLabel, unexplainedSpans, } from "../rationale.js";
22
- import { rItem, rNode } from "../trace.js";
23
- import { dismissedKnownContent } from "../bridge.js";
24
- import { leafIdRun } from "../canonical.js";
25
- // ── CAST gates ────────────────────────────────────────────────────────────
26
- //
27
- // The frame gate has TWO components, both derived from the weave itself:
28
- //
29
- // 1. MIN WEAVE — the same 2 as the precondition `points.length < 2` (CAST
30
- // needs at least two aligned structures to form a weave). Frame requires
31
- // evidence BEYOND the minimum pair — a third structure agreeing — so the
32
- // depth gate is `depth > MIN_WEAVE`. One definition, two uses.
33
- //
34
- // 2. HALF-DOMINANCE — `dominates(framed, len)` (the same test
35
- // collectRegions, liftAnswer, and confluence's filler gate all use): a
36
- // span more than half scaffolding no longer discriminates its own content.
37
- // The per-byte test `dominates(depth[i], aligned)` classifies a byte as
38
- // frame; the per-run test `dominates(framedCount, runLen)` decides
39
- // whether the run is usable.
40
- //
41
- // Both are derived from structural quantities (aligned points, run length),
42
- // never tuned. The constants below are the weave's own shape, not thresholds.
43
- //
44
- // DO NOT replace the frame gates with the structural IDF (reachOf +
45
- // dominates): it was tried and empirically REFUTED (17-intelligence's
46
- // reorder probe). CAST's frame is WEAVE-LOCAL — "what the aligned
47
- // structures share among THEMSELVES" — while the IDF is corpus-global; a
48
- // phrase common to the aligned exemplars (" describe it", "the importance
49
- // of") is frame here even when it reaches only a corpus minority, and
50
- // treating it as content lets the substitution branch fire on reordered
51
- // single-fact queries. The two commonality notions coincide often, but
52
- // neither derives the other.
53
- /** The minimum number of aligned structures to form a weave — the same 2 that
54
- * gates CAST entry (`points.length < 2`). Frame requires MORE than this
55
- * minimum: `depth > MIN_WEAVE` means at least three structures agree on a
56
- * byte, so no byte is frame when only the minimum pair exists. */
57
- const MIN_WEAVE = 2;
58
- /** The seat that establishes a node's role in an analogical comparison:
59
- * the REVERSE context (what leads to it) when a predecessor genuinely
60
- * ESTABLISHES id — introduces or describes it by name — else the FORWARD
61
- * continuation (what it leads to), else `fallback`.
62
- *
63
- * An earlier version gated this purely on `prevCount(id) > 0`: any
64
- * predecessor at all was treated as proof of a genuine named ENTITY
65
- * (seat it by what established it), while no predecessor meant a bare
66
- * learnt CONTEXT (seat it by what it leads to, since voicing it verbatim
67
- * would answer a question with a question). That test measured the wrong
68
- * thing — a broad sample of this store's own question-shaped nodes showed
69
- * the large majority (≈71%) have at least one predecessor, most of them a
70
- * handful of generic, high-fan-out sentences that recur as an INCIDENTAL
71
- * neighbour to dozens of otherwise-unrelated destinations (a SmolSent-
72
- * style sentence-adjacency artifact, never naming or describing what
73
- * follows). Traced live: "What is the capital of France?" — whose own
74
- * forward edge unambiguously resolves to "The capital of France is
75
- * Paris." — has exactly one such incidental predecessor ("Create an
76
- * example of a types of questions a GPT model can answer.?"), wrongly
77
- * read as disqualifying proof of "genuine entity."
78
- *
79
- * A plain forward-first swap (matching {@link project}'s universal
80
- * priority) over-corrected: test/29's C2/C3 pin that a genuine entity
81
- * analog (e.g. "Leonardo da Vinci", established by "The Mona Lisa was
82
- * painted by Leonardo da Vinci.") must be seated by that establishing
83
- * sentence, NOT by its own biography fact — voicing the bio leaks exactly
84
- * what a comparison must keep out, and loses the embedded "Mona Lisa"
85
- * term C3 relies on for a further hop.
86
- *
87
- * The distinguishing signal is content-addressed, not a count: a genuine
88
- * establishing predecessor's bytes CONTAIN id's own bytes — it names or
89
- * describes id ("...painted by Leonardo da Vinci." contains "Leonardo da
90
- * Vinci"). An incidental adjacency predecessor never does — it merely
91
- * preceded id in some unrelated document without ever mentioning it. No
92
- * new tuned constant: containment is the same primitive `restatesQuery`
93
- * and `dominates`-style checks already use throughout this codebase.
94
- *
95
- * `allowForward` (default true) gates the FORWARD branch specifically —
96
- * see the call sites below: the DOMINANT is what the query is actually
97
- * ASKING, so completing it forward is the whole point; an ANALOG is only
98
- * being CITED for comparison; the query never asked about IT, so chasing
99
- * its own further continuation drifts onto whatever coincidentally
100
- * follows it in the corpus. Traced live: the analog "What is the capital
101
- * of Japan?\nTokyo is the capital of Japan." is ALREADY a complete,
102
- * self-answering unit (prevCount 0, so no establishing predecessor
103
- * either) — its sole forward edge is "And what is the capital of the
104
- * Moon?", an unrelated quiz question sharing nothing but corpus
105
- * adjacency. With forward disallowed, an analog like this falls through
106
- * to `fallback` — its own bytes, exactly the complete fact that made it a
107
- * genuine analog in the first place. See
108
- * test/41-seatofnode-direction.test.mjs and
109
- * test/43-cast-analog-seat.test.mjs. */
110
- export async function seatOfNode(ctx, id, guide, fallback, allowForward = true) {
111
- const rev = ctx.store.prevFirst(id, hubBound(ctx));
112
- if (rev.length > 0) {
113
- const own = read(ctx, id);
114
- const establishing = rev.some((p) => indexOf(read(ctx, p), own, 0) >= 0);
115
- if (establishing) {
116
- const back = reverseContext(ctx, id, guide, rev);
117
- if (back !== null)
118
- return back;
119
- }
120
- }
121
- // The "last resort, non-establishing reverse" fallback below is itself a
122
- // LESS CERTAIN projection (the same tier as forward) — an analog
123
- // (allowForward: false) must stop at `fallback` (its own bytes) here
124
- // rather than fall back to a predecessor that already failed the
125
- // establishing check just above.
126
- if (!allowForward)
127
- return fallback;
128
- const fwd = await follow(ctx, id, guide);
129
- if (fwd !== null)
130
- return fwd;
131
- return reverseContext(ctx, id, guide, rev) ?? fallback;
132
- }
133
- /** CAST's own entry gates, checked once here and reused by
134
- /** The main CAST entry point. Given a query and its pre-computed pre.rec.sites,
135
- * determine whether the query weaves together multiple independent learnt
136
- * structures (by graded alignment — literal first, then halo-matched pre.rec.sites).
137
- * If so, attempt substitution, redirection, AND analogical comparison —
138
- * each schema is tried independently and every one that fires yields its
139
- * OWN candidate; think's grounding decider (which already compares weights
140
- * across mechanisms) picks among them, so CAST no longer needs an internal
141
- * priority order.
142
- *
143
- * `climb`, when given, is {@link castFloor}'s own climb result — reused
144
- * instead of re-running climbAttentionAll (see the note on {@link
145
- * CastFloor}). Its gates (`query.length`, `edgeSourceCount`,
146
- * `ranked.length < 2`) MUST stay in sync with castFloor's — one is the
147
- * other's admissible lower bound, checked before this runs.
148
- *
149
- * Returns the array of {@link CastResult}s that fired (possibly empty). */
150
- export async function counterfactualTransfer(ctx, query, pre) {
151
- // Opened unconditionally, at entry — the same convention recall.ts's
152
- // recallByResonance and extraction.ts's extractBySkill use, so every exit
153
- // path (five gates below, then the schemas themselves) closes through
154
- // ONE scope and inspectRationale never hits a silent dead end. Only the
155
- // first two gates duplicate floor()'s own admissible bound (query length,
156
- // ranked anchor count) — required to stay in sync per this function's own
157
- // doc comment above, and effectively dead through the ordinary pipeline
158
- // (floor() returning null already stops run() from being called at all),
159
- // but this function is also exported and callable directly, so they stay
160
- // and get the same honest trace as everything past them.
161
- const t = ctx.trace?.enter("counterfactual", [rItem(query, "query")]);
162
- const fail = (note) => {
163
- t?.done([], note);
164
- return [];
165
- };
166
- const quantum = ctx.space.maxGroup;
167
- if (query.length < 2 * quantum || ctx.store.edgeSourceCount() === 0) {
168
- return fail("query below the two-quantum floor, or no edges learnt yet");
169
- }
170
- const { roots, ranked } = await pre.attention();
171
- if (ranked.length < 2) {
172
- return fail(`only ${ranked.length} ranked anchor(s) — CAST needs at least two`);
173
- }
174
- const weave = await pre.weave();
175
- const points = weave.points;
176
- const depth = weave.depth;
177
- // CAST'S OWN SINGLE-VS-MULTI TEST, MEASURED FROM THE QUERY.
178
- //
179
- // `points.length >= 2` reads as "two structures to transfer between", but
180
- // measured, it functions as "the query is about more than one thing" — and
181
- // it only discriminates because the weave's exclusivity eliminates hard
182
- // enough that a single-topic query cannot reach two points. The condition
183
- // is carried by the elimination, not by anything CAST measures. Traced on
184
- // test/24 3.1 ("the importance of gender equality in the workplace"): the
185
- // climb is byte-identical either way (16 of 31 sub-regions, one context),
186
- // and relaxing the weave alone makes CAST fire and answer about the 1992
187
- // Dream Team.
188
- //
189
- // What actually separates 3.1 from a genuine comparison (test/29 C2, "How is
190
- // Shakespeare like Leonardo da Vinci?") is CONTENT: C2's two points are
191
- // evidenced by DIFFERENT query spans, while 3.1's extra points align to the
192
- // same shared frame the first one already explains. So require two points
193
- // that explain genuinely different parts of the query — a second point must
194
- // contribute at least one perception quantum of query bytes the
195
- // best-covered point does not. Derived from the runs themselves, order-free,
196
- // and independent of how many points survived.
197
- const coveredBy = (p) => {
198
- const set = new Set();
199
- for (const r of p.runs)
200
- for (let i = r.qs; i < r.qe; i++)
201
- set.add(i);
202
- return set;
203
- };
204
- let widest = points[0];
205
- let widestN = -1;
206
- for (const p of points) {
207
- const n = coveredBy(p).size;
208
- if (n > widestN) {
209
- widestN = n;
210
- widest = p;
211
- }
212
- }
213
- const widestSet = widest === undefined
214
- ? new Set()
215
- : coveredBy(widest);
216
- let distinct = points.length === 0 ? 0 : 1;
217
- for (const p of points) {
218
- if (p === widest)
219
- continue;
220
- let own = 0;
221
- for (const i of coveredBy(p))
222
- if (!widestSet.has(i))
223
- own++;
224
- if (own >= quantum) {
225
- distinct = 2;
226
- break;
227
- }
228
- }
229
- // THE CLIMB ANSWERS THE SAME QUESTION, AND IT ANSWERS IT ORDER-FREE. Runs
230
- // are literal W-gram agreement, so two structures the query names in its own
231
- // words can share no run at all: on `How is ice like steel?` the query's
232
- // `ice` and the stored `Ice is cold` agree on nothing but the ` is `
233
- // scaffolding `Steel is hard` also matches, and the run test above reads one
234
- // topic. The climb had already read two — it elected `Ice is cold` from
235
- // q4-9 and `Steel is hard` from q16-20, two disjoint places — and DISPERSION
236
- // (Attention.clusters) is exactly that reading: not how much evidence, but
237
- // how many separate places in the query corroborate it. Measured against
238
- // the case this gate exists to refuse, test/24 3.1: a genuinely single-topic
239
- // query reads clusters 1, while C1's single committed root reads 2.
240
- //
241
- // Either source is sufficient — bytes the other point does not explain, or
242
- // places the climb found the query's evidence in — and neither is a count of
243
- // weave survivors.
244
- // Dispersion alone is a property of the QUERY, not of the pair being woven,
245
- // so it is read together with the pair's own elected spans: two points count
246
- // as two topics when the climb found the query dispersed AND it elected them
247
- // from places at least a quantum apart. (Dispersion alone was measured and
248
- // is too weak — it let CAST into test/33's near-tie and test/24's list
249
- // skill, whose points the climb elects from the same place.)
250
- const dispersed = roots.length >= MIN_WEAVE ||
251
- roots.some((r) => r.clusters >= MIN_WEAVE);
252
- const apart = points.some((a) => points.some((b) => a !== b &&
253
- (b.start - a.end >= quantum || a.start - b.end >= quantum)));
254
- // …and only where there is something left to transfer. When ONE point
255
- // already explains the query down to the last quantum there is no analogy to
256
- // draw — the query is that structure, restated or truncated — and the
257
- // dispersion the climb reports is the SAME topic corroborated twice, not two
258
- // topics. Measured on test/33's `steel is hard so steel is`, a prefix of one
259
- // stored fact: its root disperses into 2 clusters purely because the fact
260
- // repeats `steel is`, while that one point's runs cover all 25 query bytes.
261
- const unexplained = query.length - widestN;
262
- const aligned = distinct >= 2 || (dispersed && apart && unexplained >= quantum)
263
- ? points.length
264
- : 1;
265
- if (aligned < 2) {
266
- return fail(`only ${aligned} structure(s) aligned across the query — CAST needs ` +
267
- `at least two to transfer between`);
268
- }
269
- // ── Frame gate (half-dominance, weave-local) ─────────────────────────
270
- // A byte is FRAME when more than MIN_WEAVE aligned structures cover it
271
- // AND those structures are a majority of all aligned structures.
272
- // Per-byte: frame(i) ⇔ depth[i] > MIN_WEAVE ∧ dominates(depth[i], aligned)
273
- // Per-run: usable(r) ⇔ ¬dominates(framedCount, runLen)
274
- const isFrame = (i) => depth[i] > MIN_WEAVE && dominates(depth[i], aligned);
275
- const framedCount = (qs, qe) => {
276
- let n = 0;
277
- for (let i = qs; i < qe; i++)
278
- if (isFrame(i))
279
- n++;
280
- return n;
281
- };
282
- const usable = (qs, qe) => !dominates(framedCount(qs, qe), qe - qs);
283
- // The weave's DOMINANT is its principal STRUCTURE — the aligned point
284
- // explaining the most query bytes — not the climb's top-ranked TOPIC.
285
- // The two used to coincide (approximate votes from a query's novel spans
286
- // boosted whichever exemplar shared its frame), but the contrastive
287
- // margin ranks the query's own exact site first, and CAST's schemas all
288
- // orient around the frame-bearing structure: the substitution/redirection
289
- // seat is displaced IN the dominant, and comparison seats the analogs by
290
- // the contexts that establish their roles. Coverage is weave-local and
291
- // derived (sum of aligned run lengths); ties keep the ranked order.
292
- let dominant = points[0];
293
- let domCover = -1;
294
- for (const p of points) {
295
- let cover = 0;
296
- for (const r of p.runs)
297
- cover += r.qe - r.qs;
298
- if (cover > domCover) {
299
- domCover = cover;
300
- dominant = p;
301
- }
302
- }
303
- const isRoot = (id) => roots.some((r) => r.anchor === id);
304
- // The weave must touch a COMMITTED point of attention: the dominant
305
- // structure itself, or another aligned point the climb committed to.
306
- if (!points.some((p) => isRoot(p.anchor))) {
307
- t?.done([
308
- ...points.map((p) => rNode(ctx, p.anchor, "aligned")),
309
- ...roots.map((r) => rNode(ctx, r.anchor, "committed-root")),
310
- ], `${points.length} aligned structure(s), but none is one of the climb's ` +
311
- `${roots.length} committed root(s) — CAST refuses to transfer through ` +
312
- `content the climb itself never settled on`, {
313
- aligned: points.map((p) => ({
314
- anchor: p.anchor,
315
- vote: p.vote,
316
- runs: p.runs.map((r) => ({ ...r })),
317
- coveredBytes: p.runs.reduce((n, r) => n + r.qe - r.qs, 0),
318
- })),
319
- committedRoots: roots.map((r) => ({
320
- anchor: r.anchor,
321
- vote: r.vote,
322
- })),
323
- });
324
- return [];
325
- }
326
- // WOVEN — is anything actually brought TOGETHER? A run restating a site
327
- // the query already contains is not, by itself, evidence of that; but TWO
328
- // points restating DIFFERENT sites is exactly a comparison ("How is
329
- // Michelangelo like Homer?" names both entities, recognition finds both,
330
- // and the weave aligns each to its own stored structure). The escape
331
- // clause alone called that unwoven — a reading that held only while
332
- // recognition UNDER-reported sites, and test/29 A2 started failing the
333
- // moment recognition's interior chains stopped dying mid-form.
334
- //
335
- // Both points must be evidenced in what the asker JUST SAID. A multi-turn
336
- // query is the whole transcript, so the earlier turns' own questions are
337
- // aligned points too — traced on test/48, the weave for `And what is the
338
- // capital of Spain?` holds `What is the capital of France?` (runs q0-61,
339
- // entirely inside the previous turn and its answer) beside the new question
340
- // (q65-94). Two points, two named sites, and nothing woven at all: one of
341
- // them is conversation history. The current turn is the bytes past the last
342
- // answered span — the same `askerBytes` notion computeWeave prices its read
343
- // budget with — so requiring both points to have evidence THERE separates a
344
- // genuine two-place weave from a follow-up. Single-turn queries have no
345
- // answered spans, so the current turn is the whole query and nothing changes.
346
- const turnStart = ctx.answeredSpans.reduce((n, [, e]) => Math.max(n, e), 0);
347
- const inTurn = points.filter((p) => p.runs.some((r) => r.qe > turnStart));
348
- const siteAt = (r) => pre.rec.sites.findIndex((s) => r.qs >= s.start && r.qe <= s.end);
349
- const namedSites = new Set();
350
- for (const p of inTurn) {
351
- for (const r of p.runs) {
352
- const i = siteAt(r);
353
- if (i >= 0)
354
- namedSites.add(i);
355
- }
356
- }
357
- const woven = points.some((p) => p.runs.some((r) => siteAt(r) < 0)) ||
358
- (inTurn.length >= MIN_WEAVE && namedSites.size >= MIN_WEAVE);
359
- if (!woven) {
360
- return fail(`every aligned run restates a recognised query site — nothing was ` +
361
- `actually WOVEN across structures, so there is nothing to transfer`);
362
- }
363
- // Each schema tried below RECORDS its candidate (when it fires) rather than
364
- // returning immediately — every schema that succeeds contributes its own
365
- // candidate, and the grounding decider's own weight comparison (not CAST's
366
- // former internal priority) picks among them.
367
- //
368
- // `accounted` is SCHEMA-SPECIFIC, not the whole weave's alignment: a
369
- // schema only actually TRANSFERS BETWEEN the two points its own logic
370
- // names (substitution: the filled subject + the displaced seat;
371
- // redirection: the displaced seat + the named substitute; comparison:
372
- // the dominant + its analog) — a THIRD point the weave happened to align
373
- // but this schema never touched contributes nothing to what THIS answer
374
- // explains. Pricing every schema against the SAME "every kept point's
375
- // every run" span would let the cheapest schema win on move-cost alone
376
- // regardless of which one actually used more of the query; pricing it
377
- // against only a fragment of even its OWN two points (e.g. one run
378
- // instead of the point's full aligned evidence) is just as wrong the
379
- // other way — it starves an otherwise-correct schema of credit for
380
- // evidence it legitimately relied on. Each call site below passes the
381
- // full run set of exactly the points ITS OWN transfer used — no more,
382
- // no less.
383
- const runSpans = (p) => p.runs.map((r) => [r.qs, r.qe]);
384
- const results = [];
385
- const record = (answer, note, used, moves, accounted) => {
386
- if (answer === null)
387
- return;
388
- ctx.trace?.step("castSchema", [rItem(query, "query")], [rItem(answer, "answer")], note);
389
- results.push({
390
- bytes: answer,
391
- used: used ?? new Set(),
392
- accounted,
393
- moves,
394
- unexplained: unexplainedLabel(query, accounted),
395
- });
396
- };
397
- 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");
398
- const lastRun = (p) => p.runs[p.runs.length - 1];
399
- const qv = pre.guide;
400
- // ── SUBSTITUTION ──────────────────────────────────────────────────
401
- const fillerOf = (s, r = s.runs[0]) => r.cs < quantum
402
- ? s.ctx.subarray(0, r.cs + (r.qe - r.qs))
403
- : query.subarray(r.qs, r.qe);
404
- // THE FILLER IS WHAT THE SUBJECT CONTRIBUTES BEFORE THE SEAT — CLIPPED HERE,
405
- // NOT ARBITRATED BY RANK. A subject whose alignment runs INTO the seat span
406
- // agrees with the displaced structure there; those shared bytes are frame,
407
- // and only the part before the seat is the subject's own contribution.
408
- // Reading `runs[0]` whole made this schema depend on the weave having
409
- // already cut that overlap away for it: on `steel is frigid` the weave's
410
- // exclusivity handed `steel is hard so steel is strong` the run q0-5
411
- // (`steel`) only because the seat's point ranked higher and took q5-15
412
- // first. Read without that cut the same run is q0-9 (`steel is `), it ends
413
- // PAST the seat at q5, and substitution found no subject at all — a schema
414
- // silently reading a global elimination order as if it were local evidence.
415
- // Clipping at the seat derives the same span from the two points actually
416
- // involved, so the reading no longer moves when the weave's order does.
417
- const fillerRun = (s, at) => {
418
- const r0 = s.runs[0];
419
- if (r0.qs >= at)
420
- return null;
421
- const qe = Math.min(r0.qe, at);
422
- return qe - r0.qs >= Math.min(quantum, s.ctx.length)
423
- ? (qe === r0.qe ? r0 : { ...r0, qe })
424
- : null;
425
- };
426
- // The subject is the closest structure whose FILLER RUN precedes the seat.
427
- // The gate is on `runs[0]` — the run `fillerOf` actually reads — not on the
428
- // point's LAST run: requiring the subject's whole alignment to end before
429
- // the seat disqualifies any structure the query mentions on BOTH sides of
430
- // it, which is the shape CAST exists for. Measured on
431
- // `steel is frigid so steel is ???` against `steel is hard so steel is
432
- // strong` (runs "steel is " at 0-9 and "d so steel is " at 14-28) and
433
- // `water is frigid so water is freezing` (seat "frigi" at 9-14): the
434
- // subject's filler run sits squarely before the seat, but its second run —
435
- // the recurrence AFTER it, the very thing that makes the sentence an
436
- // analogy — pushed lastRun past the seat and no substitution fired at all.
437
- // The ordering key follows the gate to the same run, so "closest preceding"
438
- // still means closest by the evidence actually used.
439
- const beforeOf = (p, r) => argmaxBy(points.flatMap((s) => {
440
- if (s === p)
441
- return [];
442
- const f = fillerRun(s, r.qs);
443
- return f !== null && f.cs < quantum && usable(f.qs, f.qe)
444
- ? [{ point: s, run: f }]
445
- : [];
446
- }), (s) => s.run.qs, -Infinity, true)?.item;
447
- const displacement = points
448
- .map((p) => {
449
- const r = p.runs[0];
450
- if (r.cs < quantum || !usable(r.qs, r.qe)) {
451
- return null;
452
- }
453
- const before = beforeOf(p, r);
454
- if (before === undefined)
455
- return null;
456
- if (r.cs > fillerOf(before.point, before.run).length + quantum) {
457
- return null;
458
- }
459
- // SUBSTITUTION MUST ACTUALLY DISPLACE. The schema's premise is that the
460
- // displaced structure's seat is held by something ELSE, which the
461
- // subject then replaces. When the subject's filler already occurs in
462
- // that structure, there is nothing to displace — the "transfer" restates
463
- // the structure with its own occupant put back, and the answer is a
464
- // tautology. Measured on `Michelangelo is to sculpture as who is to
465
- // literature?`: the weave aligned the concept `Michelangelo` and the
466
- // exemplar `The David was sculpted by Michelangelo.`, and substitution
467
- // produced `Michelangelo sculpted by Michelangelo.` — then outbid every
468
- // honest candidate with it (test/29 A2). Byte containment, the same
469
- // primitive the self-evidence and contradiction guards use.
470
- if (indexOf(p.ctx, fillerOf(before.point, before.run), 0) >= 0) {
471
- return null;
472
- }
473
- return { p, before, depth: p.ctx.length - r.cs };
474
- })
475
- .filter((c) => c !== null);
476
- const picked = argmaxBy(displacement, (c) => c.depth, -Infinity, true);
477
- const proj = picked?.item.p ?? null;
478
- const subj = picked?.item.before ?? null;
479
- if (proj !== null && subj !== null) {
480
- const seat = proj.runs[0];
481
- const filler = fillerOf(subj.point, subj.run);
482
- const tail = proj.ctx.subarray(seat.cs);
483
- let answer = await joinWithBridge(ctx, filler, tail);
484
- const fwd = await follow(ctx, proj.anchor, qv);
485
- if (fwd !== null && indexOf(answer, fwd, 0) < 0 &&
486
- !restatesQuery(query, fwd)) {
487
- answer = concat2(answer, fwd);
488
- }
489
- ctx.trace?.step("projectCounterfactual", [
490
- rItem(filler, "filler", subj.point.anchor),
491
- rNode(ctx, proj.anchor, "displaced-structure"),
492
- ], [rItem(answer, "projection")], "transfer the displaced structure onto the subject filler (seat substitution)");
493
- record(answer, "counterfactual substitution — the subject fills the analog's seat", new Set([subj.point.anchor, proj.anchor]),
494
- // The acts performed: one seat INSERT projection + one edge FOLLOW.
495
- STEP + STEP,
496
- // What substitution actually READ: the two points it transfers
497
- // between — the subject filling the seat, and the displaced
498
- // structure whose seat it fills — not every OTHER point the weave
499
- // happened to align (a third, unrelated point in the same weave
500
- // contributes nothing to what substitution itself explains).
501
- [...runSpans(subj.point), ...runSpans(proj)]);
502
- }
503
- // ── REDIRECTION ────────────────────────────────────────────────────
504
- // REDIRECTION IS ABOUT THE SUBSTITUTE THE QUERY NAMES, SO IT LOOKS FOR THE
505
- // RUN THAT NAMES ONE. A structure is named when the query quotes it from
506
- // its own opening bytes (`cs === 0`) — `…were Lyon?` against `Lyon is a city
507
- // in France`. Reading that off `runs[0]` assumed the weave had already
508
- // eliminated everything the point shares with the dominant, which is the
509
- // elimination deciding the schema again: relaxed, the same point also aligns
510
- // the query's trailing ` France` (cs 17, frame it shares with `what is the
511
- // capital of France?`), that run sorts FIRST, and redirection stopped seeing
512
- // a named substitute at all. Scanning the point's runs for the naming one
513
- // is the same reading, taken from the runs rather than from their order, and
514
- // "latest named" then means latest by the run actually relied on.
515
- const named = points.flatMap((p) => {
516
- const r = p.runs.find((r) => r.cs === 0 && usable(r.qs, r.qe));
517
- return r !== undefined ? [{ point: p, run: r }] : [];
518
- });
519
- // …and it must be named AFTER what it displaces. Redirection replaces the
520
- // ANSWER, so the substitute is the newest thing the query says — `…of France
521
- // were Lyon?` names Lyon past everything the displaced structure aligned.
522
- // The old `latest last run` reduce encoded this implicitly and only held
523
- // while trimming kept the dominant's runs latest; stated on the naming run
524
- // it is the same reading without that dependency. Measured on test/29 D1
525
- // (`steel is frigid`), where the point with a naming run is the SUBJECT at
526
- // q0-9, ahead of the dominant's q5-15: redirection must not fire, and
527
- // substitution — which is what that shape is — keeps the case.
528
- const last = argmaxBy(named.filter((n) => n.run.qs > lastRun(dominant).qs), (n) => n.run.qs, -Infinity, true)?.item;
529
- // Displacement test, capped at the hub bound: a hub anchor can carry a
530
- // corpus-sized fan-out, and each continuation costs a full byte
531
- // reconstruction plus an O(|query|·|bytes|) scan. The first √N edges (the
532
- // same insertion-order convention chooseNext caps by) decide; past a hub's
533
- // cap the test reads "none of the established continuations appears".
534
- const domNext = ctx.store.nextFirst(dominant.anchor, hubBound(ctx));
535
- const displaced = domNext
536
- .every((n) => indexOf(query, read(ctx, n), 0) < 0);
537
- if (last !== undefined && last.point !== dominant && displaced) {
538
- const g = await project(ctx, last.point.anchor, qv);
539
- if (g !== null) {
540
- ctx.trace?.step("projectCounterfactual", [
541
- rNode(ctx, dominant.anchor, "displaced-structure"),
542
- rNode(ctx, last.point.anchor, "substitute"),
543
- ], [rItem(g, "projection")], "the substitute's own fact replaces the displaced structure's answer");
544
- record(g, "counterfactual redirection — the named substitute's fact is followed", new Set([dominant.anchor, last.point.anchor]),
545
- // One forward projection across the substitute's own fact.
546
- STEP,
547
- // What redirection READ: the displaced structure's own recognized
548
- // seat (still explained — this schema RECOGNIZES it as the slot
549
- // being overridden, it just doesn't answer from it) plus the named
550
- // substitute's own aligned run — not every OTHER point the weave
551
- // happened to align.
552
- [...runSpans(dominant), ...runSpans(last.point)]);
553
- }
554
- }
555
- // ── COMPARISON ─────────────────────────────────────────────────────
556
- // Collect every qualifying non-dominant point as a candidate analog.
557
- // When a point's own anchor is structurally at the wrong level
558
- // (e.g. a long exemplar sentence whose halo does not resemble the
559
- // dominant's), its nextOf targets often point to the right level — the
560
- // person / concept the exemplar is about. Trying both prevents a
561
- // seed-dependent failure where the climb ranks an exemplar above a
562
- // person node and the person node is excluded from points by run-
563
- // overlap trimming.
564
- // The seat that establishes a candidate's role — see {@link seatOfNode}.
565
- const seatOf = (p, allowForward = true) => seatOfNode(ctx, p.anchor, qv, p.ctx, allowForward);
566
- // QUERY-SCALE — "this learnt context is the same size as the question", the
567
- // bound comparison holds its dominant and its analogs to. A byte-exact
568
- // `<= query.length` made that judgement turn on a difference the
569
- // architecture cannot perceive: on `The Weeping Woman was painted by Pablo
570
- // Picasso.` (47 bytes) the weave's dominant was `The Night Watch was painted
571
- // by Rembrandt van Rijn.` (50) — three bytes over, so comparison refused
572
- // outright, while the interchangeable `The Mona Lisa was painted by Leonardo
573
- // da Vinci.` (47) would have passed. WHICH exemplar becomes dominant is
574
- // settled by run-claiming order among equals, so a 3-byte difference was
575
- // deciding whether the schema fires at all (test/33 1b). W is the smallest
576
- // distinction perception can make — the same quantum countClusters separates
577
- // neighbourhoods by — so a context within one quantum of the query's length
578
- // carries no independently perceivable unit beyond it and is the same scale.
579
- //
580
- // ONE QUANTUM OF EXCESS IS AN ABSOLUTE UNIT, AND SCALE IS NOT ABSOLUTE.
581
- // `n - query.length < quantum` calls a 504-byte context the same scale as a
582
- // 500-byte query while refusing a 47-byte context on a 42-byte one — the
583
- // same 5 bytes, opposite verdicts, because the bar never looks at what it is
584
- // measuring against. Measured on test/29 C3, whose query is C2's verbatim:
585
- // the climb elects the exemplar SENTENCE (47) rather than the entity, five
586
- // bytes past a 42-byte query, and comparison refused a pair it accepts at
587
- // C2's grain. Read the excess against the query with `dominates` — the same
588
- // half-dominance predicate this file uses for frame, and the one scale-free
589
- // reading of "the seat sentence must not dominate the comparison" available
590
- // without inventing a ratio.
591
- const queryScale = (n) => !dominates(n - query.length, query.length);
592
- const analogs = [];
593
- for (const p of points) {
594
- if (p === dominant)
595
- continue;
596
- // Push the point's own anchor only when its context fits within
597
- // the query (the seat sentence must not dominate the comparison).
598
- if (queryScale(p.ctx.length) &&
599
- indexOf(dominant.ctx, p.ctx, 0) < 0 &&
600
- indexOf(p.ctx, dominant.ctx, 0) < 0 &&
601
- indexOf(query, p.ctx, 0) < 0) {
602
- analogs.push({ anchor: p.anchor, point: p, src: p });
603
- }
604
- // Reach through to the point's continuation targets regardless
605
- // of the point's own context length: when the point is a leaf
606
- // (exemplar sentence), its nextOf is the hub (person / concept)
607
- // that makes a genuine cross-domain analog, and the hub's own
608
- // (shorter) context will be the seat.
609
- // Capped like every fan-out: a hub anchor's full continuation list is
610
- // corpus-sized, and each candidate costs a read plus O(|query|·|bytes|)
611
- // scans — only the first √N (insertion order, the same convention
612
- // chooseNext caps by) are reachable as analogs.
613
- for (const nid of ctx.store.nextFirst(p.anchor, hubBound(ctx))) {
614
- const nctx = read(ctx, nid);
615
- if (!queryScale(nctx.length) ||
616
- indexOf(dominant.ctx, nctx, 0) >= 0 ||
617
- indexOf(nctx, dominant.ctx, 0) >= 0 ||
618
- indexOf(query, nctx, 0) >= 0)
619
- continue;
620
- analogs.push({ anchor: nid, point: null, src: p });
621
- }
622
- }
623
- // MEASURED AND REFUTED — proposing analogs from the dominant's halo when the
624
- // query-local generator finds none. Both loops above are query-local (an
625
- // aligned point, or one forward hop off one), while the gate that judges
626
- // candidates — analogyStrength's halo tier — is cross-domain by construction
627
- // and is licence enough on its own (`bestHalo` exempts it from the naming and
628
- // trusted-root bars). So the gate reads as strictly more capable than the
629
- // generator, and closing that asymmetry looks like the fix for test/29 A2
630
- // (`Michelangelo is to sculpture as who is to literature?`, whose only stored
631
- // content is `Michelangelo`: the weave aligns the concept and its own
632
- // exemplar, each contains the other, so every candidate is excluded and
633
- // comparison checks zero).
634
- //
635
- // It proposes nothing. Measured on A2's own 13-pair corpus: `Michelangelo`
636
- // HAS a halo, and `haloSiblings` returns not one sibling above
637
- // significanceBar — the distributional company that would make Shakespeare a
638
- // cross-domain analog was never trained. The asymmetry is real but it is not
639
- // what stops A2; the corpus is.
640
- let bestAnalog = null;
641
- let bestSim = 0;
642
- let bestHalo = false;
643
- // Whether the query itself NAMES a candidate. A directly aligned point
644
- // is named by construction — its runs ARE query bytes. A hop-reached
645
- // candidate is named when its own bytes contain the query text of an
646
- // aligned run of the point whose continuation edge reached it (that
647
- // alignment IS the query evidence the hop rests on — the same reading
648
- // cmpAccounted already prices): "William Shakespeare", reached off
649
- // "Macbeth was written by William Shakespeare.", contains the src's
650
- // 12-byte aligned run " Shakespeare" — test/29 C2/C3. The run must span
651
- // at least TWO perception windows (2·W, the same two-quantum floor
652
- // CAST's own entry gate holds the whole query to): a single shared
653
- // W-window is exactly the frame tier's own evidence quantum — the level
654
- // "half the corpus" shares — and stopword scraps (" the ", "he b",
655
- // 4–5 bytes) never reach two windows, while a genuinely named entity
656
- // does. NOT the weave's usable()/frame filter: weave depth counts every
657
- // ranked exemplar, so a query's own named entity recurring across
658
- // exemplars ("Shakespeare" in Hamlet+Macbeth+…) is wrongly classified as
659
- // frame — measured live, it silently disqualified C3's genuine analog.
660
- const namedByQuery = (c) => {
661
- if (c.point !== null)
662
- return true;
663
- const bytes = read(ctx, c.anchor);
664
- return c.src.runs.some((r) => r.qe - r.qs >= 2 * quantum &&
665
- indexOf(bytes, query.subarray(r.qs, r.qe), 0) >= 0);
666
- };
667
- // Whether any committed root's consensus vote clears the SAME trust bar
668
- // recallByResonance applies before grounding through a climb root:
669
- // consensusFloor(N) = ln(N) + 1/2. The climb's FIRST root is
670
- // deliberately floor-free (attention.ts: "the dominant one always
671
- // grounds") — fine for ORIENTING mechanisms, not for voicing learnt
672
- // content the query never asked about. Computed once here; both the
673
- // hub fallback below and the comparison gate consume it.
674
- const rootTrusted = roots.some((r) => r.vote >= consensusFloor(corpusN(ctx)));
675
- // The context that ESTABLISHES a filler — the same reverse context, under
676
- // the same naming test, `seatOfNode` uses to VOICE an analog (a predecessor
677
- // whose bytes CONTAIN the node's: it names or describes it, rather than
678
- // merely having preceded it somewhere). Memoised: the analogy loop below
679
- // asks about the same dominant every time, and only ever asks at all when
680
- // the cheap tiers already read zero.
681
- // A NODE NOTHING ESTABLISHES IS ITS OWN ESTABLISHING CONTEXT — the same
682
- // reading `seatOfNode` takes one gate up: a bare filler was learnt as some
683
- // context's answer and has a predecessor that NAMES it, so no establishing
684
- // predecessor means the node already IS a learnt context. Returning null
685
- // there made the tier depend on both sides being elected at the same GRAIN:
686
- // test/29 C2's climb elects the entity `Leonardo da Vinci` (established by
687
- // `The Mona Lisa was painted by…`) and reads 0.371, while C3's identical
688
- // query elects that sentence ITSELF for the same side, whose own
689
- // predecessor establishes nothing — the tier read 0.000 and comparison
690
- // never fired, on a pair that is strictly MORE explicit about its frame.
691
- const estMemo = new Map();
692
- const establishing = (id) => {
693
- const hit = estMemo.get(id);
694
- if (hit !== undefined)
695
- return hit;
696
- const own = read(ctx, id);
697
- const rev = reverseContext(ctx, id, pre.guide);
698
- const out = rev !== null && indexOf(rev, own, 0) >= 0 ? rev : own;
699
- estMemo.set(id, out);
700
- return out;
701
- };
702
- // COMPARISON VOICES WHAT IT COMPARED. When the frame tier decided the
703
- // analogy, the two establishing contexts it read ARE the roles being
704
- // compared, so the schema below voices those same bytes instead of
705
- // re-deriving a seat that can land somewhere else entirely. Measured on
706
- // test/29 C3: the dominant is the exemplar sentence `The Mona Lisa was
707
- // painted by Leonardo da Vinci.`, nothing establishes it, so `seatOf`
708
- // took its FORWARD continuation and voiced `Leonardo was a Renaissance
709
- // polymath` — the analog's own biography, exactly what C2 pins comparison
710
- // must never leak, from the branch whose own doc says forward completion
711
- // is right for a DOMINANT (true when the dominant is a bare name whose
712
- // continuation establishes it; false when it already IS the establishing
713
- // context). Only frame-tier pairs are affected: a halo-tier analogy was
714
- // never measured on these bytes and keeps the seat it always had.
715
- const frameSeats = new Map();
716
- for (const c of analogs) {
717
- const ev = await analogyStrength(ctx, dominant.anchor, c.anchor);
718
- let sim = ev.score;
719
- const halo = ev.halo;
720
- // ROLE IS ESTABLISHED BY CONTEXT, NOT BY A NAME. When neither halo tier
721
- // fired and the two anchors' own bytes share no learnt frame either, the
722
- // anchors are FILLERS — bare entity names — not the frame-bearing
723
- // structures the tier is about. Read the tier on what establishes each
724
- // one instead: the aligned point's own context (or, for a hop-reached
725
- // candidate, the point whose continuation edge reached it — the same
726
- // context `cmpAccounted` already prices as that hop's query evidence).
727
- // Both are ALREADY IN HAND, so this costs no extra read.
728
- //
729
- // Measured on test/29's corpus: "Michelangelo" vs "Homer" reads 0.000
730
- // while "The David was sculpted by Michelangelo." vs "The Iliad was
731
- // written by Homer." reads 0.452 — and a context in a different frame
732
- // ("Water boils at one hundred degrees.") still reads 0.000. The tier
733
- // was never failing to discriminate; it was reading the fillers.
734
- //
735
- // Still the FRAME tier (`halo` stays false), so this evidence remains
736
- // subject to the naming / trusted-root bar the comparison gate holds all
737
- // frame evidence to — a wider READING of the same tier, not a new licence.
738
- // Containment is excluded for the same reason the generator excludes it:
739
- // a context that contains the other establishes nothing independent.
740
- if (!halo && sim === 0) {
741
- // For a hop-reached candidate the thing whose ROLE is in question is
742
- // the point the query named, not the fact one edge past it: "Homer"
743
- // was named and "The Iliad was written by Homer." establishes it,
744
- // while the hop's own destination ("Homer was an ancient Greek poet")
745
- // has no establishing predecessor at all. The same reading
746
- // `namedByQuery` and `cmpAccounted` already take of a hop.
747
- const da = establishing(dominant.anchor);
748
- const ca = establishing(c.point !== null ? c.anchor : c.src.anchor);
749
- if (indexOf(da, ca, 0) < 0 && indexOf(ca, da, 0) < 0) {
750
- sim = sharedFrameStrengthOf(ctx, da, ca);
751
- frameSeats.set(c, [da, ca]);
752
- }
753
- }
754
- ctx.trace?.step("tryAnalog", [
755
- rNode(ctx, dominant.anchor, "dominant"),
756
- rNode(ctx, c.anchor, "candidate", sim),
757
- ], [], `analogy strength ${sim.toFixed(4)}${halo ? " (halo tier)" : ""}`);
758
- if (sim > bestSim) {
759
- bestSim = sim;
760
- bestAnalog = c;
761
- bestHalo = halo;
762
- }
763
- }
764
- // When every candidate fails the similarity gates (halo company — now
765
- // deterministic signatures, see sema.ts — and the shared-frame tier),
766
- // fall back to a candidate that is a genuine structural hub (edges in
767
- // BOTH directions). A hub node — a person, concept, or category — is
768
- // the kind of thing that makes sense to compare across domains. A leaf
769
- // value (extracted span, terminal answer) has edges in at most one
770
- // direction and comparing it would preempt the extraction pipeline,
771
- // which is the right mechanism for those. A fallback comparison carries
772
- // NO similarity evidence — it stays honest only because the grounding
773
- // decider weighs it against mechanisms that explain more of the query
774
- // (extraction accounts its whole located envelope; see extraction.ts).
775
- //
776
- // WHICH hub: not the first in `analogs` order — that order flows from the
777
- // vote ranking, which flows from approximate resonance, which is seed-
778
- // dependent. Pick by evidence instead: combined edge support (prevCount +
779
- // fan-out), tie-broken by poured halo MASS (episode corroboration — the
780
- // direct distributional evidence), then by LOWEST node id. The id order
781
- // is a property of the corpus, not of the seed — but note ids are SIGNED:
782
- // byte leaves occupy −256…−1, so "lowest id" is creation order only among
783
- // multi-byte nodes and byte-value order among leaves. Either way it is
784
- // deterministic, which is all the final tie-break must be.
785
- if (bestAnalog === null && analogs.length > 0) {
786
- let hubSupport = -1;
787
- let hubMass = -1;
788
- const fanClamp = hubBound(ctx) + 1;
789
- for (const c of analogs) {
790
- // A fallback comparison carries NO similarity evidence at all. Its
791
- // honesty rests on the grounding decider discounting it against
792
- // richer candidates (the design note below) — an assumption that
793
- // holds only when the climb itself settled on this query with real
794
- // evidence. Under a root the consensus floor does not trust, an
795
- // unnamed, hop-reached hub is pure corpus adjacency: refusing it is
796
- // what kept the live wrong echo silent. A hub the query itself
797
- // NAMED stays eligible either way (test/29 C2/C3's "William
798
- // Shakespeare"); an unnamed one under a TRUSTED root stays eligible
799
- // too (test/33 1b's deliberately weak second candidate).
800
- if (!rootTrusted && !namedByQuery(c))
801
- continue;
802
- // Evidence clamped at the hub bound: beyond √N + 1 the exact fan-out
803
- // no longer discriminates (every mega-hub ties at the clamp), and
804
- // counting it exactly would require the corpus-sized read.
805
- const fanOut = ctx.store.nextFirst(c.anchor, fanClamp).length;
806
- if (fanOut === 0)
807
- continue;
808
- const support = ctx.store.prevCount(c.anchor);
809
- if (support === 0)
810
- continue;
811
- const total = support + fanOut;
812
- if (total < hubSupport)
813
- continue;
814
- const mass = ctx.store.haloMass(c.anchor);
815
- if (total > hubSupport ||
816
- mass > hubMass ||
817
- (mass === hubMass && bestAnalog !== null &&
818
- c.anchor < bestAnalog.anchor)) {
819
- hubSupport = total;
820
- hubMass = mass;
821
- bestAnalog = c;
822
- }
823
- }
824
- if (bestAnalog !== null) {
825
- ctx.trace?.step("tryAnalog", [], [rNode(ctx, bestAnalog.anchor, "fallback", hubSupport)], "no candidate passed the similarity gates — using the best-supported structural hub");
826
- }
827
- }
828
- ctx.trace?.step("tryAnalog", [], bestAnalog !== null ? [rNode(ctx, bestAnalog.anchor, "best", bestSim)] : [], bestAnalog !== null
829
- ? `best analog with strength ${bestSim.toFixed(4)}`
830
- : `no analog candidate passed (${analogs.length} checked)`);
831
- // COMPARISON gate — analogical comparison seats the dominant against ONE
832
- // analog, so it presupposes the query is ABOUT a single thing. When the
833
- // consensus climb instead committed to MULTIPLE independent points of
834
- // attention (`roots.length > 1`), the query names independent topics to
835
- // FUSE — the reasoner's fuseAttention already combines them — not analogs
836
- // to compare. Firing here would juxtapose two co-scaffolded but unrelated
837
- // records (each sharing only the corpus preamble), out-accounting the
838
- // honest thin multi-root grounding with a frame echo. Derived from the
839
- // climb's own forest, never tuned; substitution/redirection stay
840
- // unaffected — they orient around a displaced seat, not a whole-topic
841
- // analogy.
842
- //
843
- // roots.length <= 1 is a PROXY for "the query is about one thing" — it is
844
- // only as good as the climb's own root-commitment, which depends on
845
- // recognise() having found something to commit a root TO. When the
846
- // query's newest content genuinely isn't recognised (not boundary noise —
847
- // real, uncommitted content; see the session's own investigation of the
848
- // France→Spain live trace), the climb under-commits roots and this proxy
849
- // is fooled: comparison looks licensed to treat the query as one topic
850
- // when it is not.
851
- //
852
- // The direct check is the SAME accounted spans comparison is about to
853
- // cite as its evidence: unexplainedSpans (rationale.ts, the same gap
854
- // computation the trace's own `unexplained` diagnostic uses) names every
855
- // stretch of the query NEITHER the dominant NOR the analog's evidence
856
- // touches. A short comparison query ("How is ice like steel?") legitimately
857
- // accounts for only its two short entity spans — the surrounding "How is
858
- // ... like ...?" framing is real but SHORT, split into several small gaps,
859
- // none of them the bulk of the query. The live bug's shape is different in
860
- // kind, not degree: ONE contiguous, substantial gap — a whole second
861
- // question the query added that comparison's two spans never touch at all.
862
- //
863
- // Two bars, both derived, neither tuned:
864
- // • the largest gap must not DOMINATE the whole query (the same
865
- // predicate CAST's own frame gate uses) — rules out a gap that is
866
- // most of the query outright;
867
- // • the largest gap must be SMALLER than the dominant's own established
868
- // context. A gap can't be dismissed as mere connective framing once
869
- // it is at least as large as the topic being compared FROM — at that
870
- // scale it isn't glue between two named things, it's substantial
871
- // enough to be a second topic in its own right. This is what
872
- // actually separates the live bug (a 47-byte gap against a 30-byte
873
- // dominant — the ignored content is bigger than the topic itself)
874
- // from ordinary short comparisons (a 9-byte gap against an 11-byte
875
- // dominant — the gap is smaller than what's being compared): the two
876
- // cases land on the same side of "half the query" often enough
877
- // (both can exceed or clear it) that the query-relative bar alone
878
- // does not reliably separate them — the topic-relative scale does.
879
- const cmpAccounted = bestAnalog !== null
880
- ? [...runSpans(dominant), ...runSpans(bestAnalog.point ?? bestAnalog.src)]
881
- : [];
882
- const cmpGaps = unexplainedSpans(query.length, cmpAccounted);
883
- const cmpMaxGap = cmpGaps.reduce((n, [s, e]) => Math.max(n, e - s), 0);
884
- // An analog that is not itself a directly ALIGNED point (point !== null —
885
- // its own runs are query bytes, the query NAMED it) was only reached
886
- // through a continuation hop or the structural-hub fallback. Voicing
887
- // learnt content the query never named is the same act recallByResonance
888
- // refuses to perform through a climb root whose consensus vote is below
889
- // consensusFloor(N) = ln(N) + 1/2 (recall.ts's minVote), so comparison
890
- // holds the climb to that SAME bar before citing a hop-reached analog:
891
- // some committed root must clear the floor. The climb's FIRST root is
892
- // deliberately floor-free (attention.ts: "the dominant one always
893
- // grounds") — fine for ORIENTING mechanisms, not for transferring
894
- // unnamed content through. The live bug this gates (real trained store,
895
- // 325k edge sources, floor 13.2): the query's stopword scraps pooled a
896
- // 1.92 vote that committed an unrelated haiku exemplar as the sole root,
897
- // and comparison voiced that exemplar's continuation through a
898
- // hop-reached analog while every other mechanism honestly refused. A
899
- // directly aligned analog needs no floor — the query's own bytes are its
900
- // evidence (test/29 C1's "Steel is hard" for "How is ice like steel?").
901
- // See test/50-cast-analog-consensus-floor.
902
- // A HALO-tier best analog needs neither: its similarity already cleared
903
- // significanceBar-gated distributional company (analogyStrength's
904
- // `halo`) — genuine evidence in its own right, the very case the halo
905
- // gate exists for (test/33 1b's nickname-corroborated analog). Only a
906
- // FRAME-tier or fallback analog — whose "similarity" is an unbarred
907
- // coverage fraction or nothing — needs the query's naming or the climb's
908
- // trust.
909
- // A NAMING MUST NAME SOMETHING. `analogNamed` licences comparison on the
910
- // claim that the query's own bytes evidence the analog — but that claim is
911
- // only worth what those bytes discriminate. `edgeAncestors` already has the
912
- // system's verdict for content that discriminates nothing: SATURATION, the
913
- // √N parent-fan-out abstention `explainedSpan` (bridge.ts) and the climb
914
- // both respect. A window in too many places to discriminate cannot be
915
- // evidence that the query meant THIS analog rather than any other.
916
- //
917
- // So the naming must rest on at least ONE window that is not saturated —
918
- // not every window, which would be far too strong: test/29 C1's naming is
919
- // [" is "=SAT, "teel"=1, " is "=SAT], and the one discriminative run is
920
- // exactly what makes it a naming. Measured over the accounted runs of
921
- // every `analogNamed` comparison in the suite (contextsReached per window):
922
- //
923
- // C1 " cold"=1 "teel "=1 N=4 → names
924
- // C2 "Leonardo da Vinci"=4 " Shakespeare"=5 N=22 → names
925
- // C3 " Leonardo da Vinci"=3 " Shakespeare"=4 N=13 → names
926
- // "what i"=2 " the capital of France"=2 "Lyon"=1 → names
927
- // " is "=SAT "teel"=1 " is "=SAT → names
928
- // "The "=3 " painted by "=3 "Michelangelo"=2 → names
929
- // 50 " name"=SAT " the "=SAT "ing "=SAT
930
- // " the "=SAT "he b"=SAT "ing "=SAT N=205 → names NOTHING
931
- //
932
- // test/50's junk comparison is the only one in the suite whose naming is
933
- // saturated end to end: it "names" its analog with " the " and "ing ". The
934
- // ignored-known principle cannot reach that case — the planet probe's gaps
935
- // ("planet", "biggest", "sun") are genuinely untrained, so
936
- // `dismissedKnownContent` correctly returns false and there is no ignored
937
- // known content to find. This is a different question: not "did the
938
- // comparison ignore what the store knows" but "did the query name this
939
- // analog at all". Derived, never tuned — the saturation limit is
940
- // `edgeAncestors`' own √N, computed nowhere new.
941
- const namingDiscriminates = () => {
942
- const N = corpusN(ctx);
943
- const W = ctx.space.maxGroup;
944
- const memo = sharedReachMemo(ctx);
945
- for (const [from, to] of cmpAccounted) {
946
- for (let o = from; o + W <= to; o++) {
947
- const ids = leafIdRun(ctx, query, o, o + W);
948
- if (ids === null)
949
- continue;
950
- const wid = ctx.store.findBranch(ids);
951
- if (wid === null)
952
- continue;
953
- const r = edgeAncestors(ctx, wid, N, memo);
954
- if (!r.saturated && r.roots.length > 0)
955
- return true;
956
- }
957
- }
958
- return false;
959
- };
960
- const analogNamed = bestAnalog !== null && namedByQuery(bestAnalog) &&
961
- namingDiscriminates();
962
- // NOTE — two further gates were tried here and empirically REFUTED,
963
- // recorded so they are not re-tried:
964
- // • dominant self-coverage (dominant's aligned runs must dominate its
965
- // own ctx): legitimate dominants sit at the same coverage as junk
966
- // ones ("The Mona Lisa was painted by…" 16/47 vs the live junk
967
- // haiku ~10/54) — no separation.
968
- // • denying the shared-frame similarity tier to hop-reached analogs:
969
- // semantically right in isolation, but it merely promoted the next
970
- // junk candidate — an ALIGNED scrap-matched point ("The affluence…",
971
- // frame 0.157) — into bestAnalog on the live store, and the aligned
972
- // configuration is byte-structurally IDENTICAL to test/29 C1's
973
- // legitimate one ("Steel is hard", frame 0.364): every derived
974
- // local separator measured (run length, site overlap, frame
975
- // query-containment, weave-usable classification) falls on the same
976
- // side for both. Only corpus-scale consensus separates them, which
977
- // is exactly what `rootTrusted` prices.
978
- // FRAME-tier evidence under an UNTRUSTED root is comparison's weakest
979
- // licence (an unbarred coverage fraction, a climb the consensus floor
980
- // does not trust). There it is additionally held to the IGNORED-KNOWN
981
- // principle (dismissedKnownContent, bridge.ts): the two analogs' aligned
982
- // runs must account for every STORED window of the query. This is the
983
- // byte-structural separator the refuted-gates note below could not find
984
- // locally: a legitimate small-corpus comparison ("How is ice like
985
- // steel?") leaves only UNATTESTED spans ("How ", " like ") unexplained,
986
- // while a scrap-matched junk pair leaves the query's own trained content
987
- // ("…songs…times…", "…planet…sun.") dismissed as gaps. Halo-tier and
988
- // trusted-root comparisons are exempt — their evidence already stands.
989
- // A TRUSTED ROOT IS NOT A LICENCE TO IGNORE WHAT THE STORE KNOWS. This
990
- // exemption used to read `!(bestHalo || rootTrusted)`, so a root clearing
991
- // consensusFloor discarded the ignored-known verdict entirely — and that
992
- // verdict is the one piece of evidence in this gate that actually sees the
993
- // failure: measured on test/50's probes, `dismissedKnownContent` returns
994
- // TRUE for both ("songs"/"times"/"planet"-class trained content left in the
995
- // comparison's gaps) while `rootTrusted` is also true, so the gate read
996
- // false and comparison fired on a junk analog.
997
- //
998
- // The root's trust says the CLIMB settled on something; it says nothing
999
- // about whether THIS comparison's own evidence covers the query's known
1000
- // content, which is a different question about a different quantity. Halo
1001
- // stays exempt — halo-tier company is independent evidence in its own right
1002
- // (test/33 1b's nickname-corroborated analog), which is exactly what a
1003
- // pooled consensus vote is not.
1004
- //
1005
- // Measured cost, and it is a candidate COUNT, not an answer: test/33 1b
1006
- // ("expected at least two CAST candidates") loses one of its two, because
1007
- // the comparison schema now honestly declines. The junk analogs it used to
1008
- // supply were never the ones that test is about.
1009
- const cmpDismisses = !bestHalo &&
1010
- dismissedKnownContent(ctx, query, cmpAccounted);
1011
- if (bestAnalog !== null &&
1012
- (bestHalo || analogNamed || rootTrusted) &&
1013
- !cmpDismisses &&
1014
- queryScale(dominant.ctx.length) &&
1015
- roots.length <= 1 &&
1016
- !dominates(cmpMaxGap, query.length) &&
1017
- cmpMaxGap < dominant.ctx.length) {
1018
- ctx.trace?.step("validateAnalogy", [
1019
- rNode(ctx, dominant.anchor, "analog", bestSim),
1020
- rNode(ctx, bestAnalog.anchor, "analog", bestSim),
1021
- ], [], "the two structures keep distributional company beyond chance — genuine analogs");
1022
- const seats = frameSeats.get(bestAnalog);
1023
- const a = seats !== undefined ? seats[0] : await seatOf(dominant);
1024
- // The analog is only being CITED for comparison — the query never asked
1025
- // about it — so its seat never chases a FORWARD continuation (see
1026
- // seatOfNode's `allowForward`): only reverse (if a predecessor genuinely
1027
- // establishes it) or its own bytes. A DIRECTLY aligned point
1028
- // (bestAnalog.point !== null) still goes through seatOfNode for that
1029
- // reverse check (a bare entity NAME like "Leonardo da Vinci" needs it —
1030
- // test/29's C2/C3). A nextOf DESCENDANT (point === null) was already
1031
- // reached by following ONE meaningful hop off another aligned point (the
1032
- // alignment loop above: "its nextOf is the hub... and the hub's own
1033
- // [...] context will be the seat") — its own bytes ARE that seat
1034
- // directly, with no predecessor to even check (it was found by a
1035
- // forward edge, not matched in the query).
1036
- let b = seats !== undefined
1037
- ? seats[1]
1038
- : bestAnalog.point !== null
1039
- ? await seatOf(bestAnalog.point, false)
1040
- : read(ctx, bestAnalog.anchor);
1041
- // AN ECHO IS NOT A VOICE. `allowForward: false` above leaves seatOfNode
1042
- // with one last resort — the point's OWN BYTES — and when the aligned
1043
- // anchor is a QUESTION node those bytes are the question itself. The
1044
- // comparison then hands the asker their own words back: "What is the
1045
- // capital of France? And what is the largest planet?" answered "The
1046
- // capital of France is Paris.What is the largest planet?", one topic
1047
- // answered and the other merely repeated. (The same corpus answered BOTH
1048
- // when asked in the opposite order — the echo was never about the topic,
1049
- // only about whether the climb happened to land on the question node or
1050
- // the answer node.)
1051
- //
1052
- // The fix is NOT to allow the forward edge for every directly aligned
1053
- // analog. "Directly aligned" does not mean "the query named it": a point
1054
- // can be aligned by HALO similarity with no literal overlap at all, and
1055
- // test/43 pins exactly that case — an analog whose own bytes are already a
1056
- // complete Q+A unit, cited structurally, whose forward edge is an
1057
- // unrelated next quiz question. There, stopping at its own bytes is
1058
- // right, because those bytes are an answer and nothing was echoed.
1059
- //
1060
- // What separates the two is the RESTATEMENT, which is directly testable:
1061
- // a seat whose bytes already occur in the query says nothing the asker did
1062
- // not just say, so it cannot be this analog's contribution — and only then
1063
- // is the continuation the query literally asked for worth following. Same
1064
- // `restatesQuery` primitive the substitution schema above already gates
1065
- // its own forward step on; no new constant and no new notion of "named".
1066
- // Read the restatement UNDER THE RESPONSE'S OWN EQUIVALENCE. Byte-exact
1067
- // containment misses the case that actually occurs: the trained node is
1068
- // "What is the largest planet?" while the query asks "And what is the
1069
- // largest planet?" — the same words, one capital letter apart, so
1070
- // `indexOf` finds nothing and the echo sails through. `ctx.canon` is the
1071
- // response's injected notion of "the same text" (case, width, whitespace);
1072
- // consulting it here is the same fallback `resolve` already makes when an
1073
- // exact content lookup misses, and it keeps this mechanism from carrying
1074
- // any idea of its own about what a character is.
1075
- const echoesQuery = (x) => {
1076
- if (restatesQuery(query, x))
1077
- return true;
1078
- const canon = ctx.canon;
1079
- if (canon === null)
1080
- return false;
1081
- const cq = canon(query), cx = canon(x);
1082
- return cx.length < cq.length && indexOf(cq, cx, 0) >= 0;
1083
- };
1084
- if (echoesQuery(b)) {
1085
- const fwd = await follow(ctx, bestAnalog.anchor, qv);
1086
- if (fwd !== null && fwd.length > 0 && !echoesQuery(fwd))
1087
- b = fwd;
1088
- }
1089
- // VOICED IN THE ORDER THE QUERY POSED THEM. `a` is the DOMINANT point
1090
- // and `b` the analog, which is a ranking by consensus strength — not by
1091
- // where either was asked about. Reading the pair out in that ranking
1092
- // makes a two-topic answer's order depend on which topic resonated
1093
- // harder, so the same two questions asked in the opposite order produce
1094
- // the same sentence: measured on test/57, "What is the largest planet?
1095
- // And what is the capital of France?" answered "The capital of France is
1096
- // Paris.The largest planet is Jupiter." — both halves right, the order
1097
- // backwards, because France was the dominant point (accounted [[33,62],
1098
- // [0,27]] — the runs are literally in reverse query order).
1099
- //
1100
- // This is the SAME rule fuseAttention already applies one layer up ("a
1101
- // multi-topic answer should read in the order the question posed its
1102
- // topics"), applied to the pair a single comparison voices itself. Each
1103
- // point's position is the earliest query byte its own aligned runs stand
1104
- // on — the same runs `cmpAccounted` prices the schema by, so order and
1105
- // cost read one source.
1106
- const earliest = (p) => runSpans(p).reduce((m, [s]) => Math.min(m, s), Infinity);
1107
- const analogPoint = bestAnalog.point ?? bestAnalog.src;
1108
- const swap = earliest(analogPoint) < earliest(dominant);
1109
- const answer = swap
1110
- ? await joinWithBridge(ctx, b, a)
1111
- : await joinWithBridge(ctx, a, b);
1112
- record(answer, "analogical comparison — each analog voiced by the context that establishes its role", new Set([dominant.anchor, bestAnalog.anchor]),
1113
- // A halo-mediated act (the analogy gate) plus two seat projections.
1114
- CONCEPT + STEP + STEP,
1115
- // What comparison READ: the dominant's own aligned runs, plus the
1116
- // aligned runs of the point that named the analog — the analog itself
1117
- // when it was an aligned point, else the source point whose
1118
- // continuation edge reached it (that alignment IS the query evidence
1119
- // the hop rests on).
1120
- cmpAccounted);
1121
- }
1122
- else if (bestAnalog !== null &&
1123
- queryScale(dominant.ctx.length) &&
1124
- roots.length <= 1) {
1125
- ctx.trace?.step("validateAnalogy", [
1126
- rNode(ctx, dominant.anchor, "analog", bestSim),
1127
- rNode(ctx, bestAnalog.anchor, "analog", bestSim),
1128
- ], [], !(bestHalo || analogNamed || rootTrusted)
1129
- ? `the best analog carries no halo-tier company evidence, was never ` +
1130
- `named by the query, and no committed root's consensus vote ` +
1131
- `clears the floor, so comparison refuses to voice it`
1132
- : cmpDismisses
1133
- ? `a frame-tier analog under an untrusted root dismisses stored ` +
1134
- `query content its alignment never accounted for — comparison ` +
1135
- `refuses to ignore what the store knows`
1136
- : `comparison's own accounted evidence leaves a ${cmpMaxGap}-byte gap in ` +
1137
- `a ${query.length}-byte query against a ${dominant.ctx.length}-byte ` +
1138
- `dominant — too large to be mere framing — so it refuses rather ` +
1139
- `than paper over it with an analog the query never asked about`);
1140
- }
1141
- t?.done(results.map((r) => rItem(r.bytes, "answer")), results.length > 0
1142
- ? `${results.length} counterfactual schema(s) fired — the grounding decider weighs them`
1143
- : "no counterfactual weave — the ordinary pipeline decides");
1144
- return results;
1145
- }
1146
- export const castMechanism = {
1147
- name: "cast",
1148
- provenance: "cast",
1149
- async floor(_ctx, query, pre, worthRunning) {
1150
- const W = _ctx.space.maxGroup;
1151
- // Cheap checks first — no pre-computation needed.
1152
- if (query.length < 2 * W || _ctx.store.edgeSourceCount() === 0)
1153
- return null;
1154
- // CAST's floor, when it exists, is ALWAYS exactly 2*STEP — the climb and
1155
- // the weave only decide whether it exists (2*STEP) or not (null), they
1156
- // never tighten the number itself. So if 2*STEP already can't beat
1157
- // whatever incumbent has already won this response (cover runs first —
1158
- // see defaultMechanisms), no analysis can change the outcome: RETURN THE
1159
- // BOUND uninvested (still admissible) and let the pipeline's own check
1160
- // prune run() with the truthful "cannot beat incumbent" note. This is
1161
- // the SAME admissible-floor economy worthRunning applies to run(),
1162
- // applied to floor()'s own investment — uniformly, whatever mechanism
1163
- // supplied the incumbent (an extension's computed result is not
1164
- // special-cased; any sufficiently cheap incumbent prunes the same way).
1165
- if (!worthRunning(2 * STEP))
1166
- return 2 * STEP;
1167
- // Now first-touch the shared analyses (climb, then the weave built on
1168
- // it). If another mechanism already triggered either, this awaits the
1169
- // cached result; otherwise it's computed once here and reused in run().
1170
- if ((await pre.attention()).ranked.length < 2)
1171
- return null;
1172
- if ((await pre.weave()).points.length < 2)
1173
- return null;
1174
- return 2 * STEP;
1175
- },
1176
- async run(ctx, query, pre) {
1177
- const casts = await counterfactualTransfer(ctx, query, pre);
1178
- return casts.map((c) => ({
1179
- bytes: c.bytes,
1180
- accounted: c.accounted,
1181
- moves: c.moves,
1182
- used: c.used,
1183
- unexplained: c.unexplained,
1184
- }));
1185
- },
1186
- };