@hviana/sema 0.4.0 → 0.4.2

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 (154) hide show
  1. package/CONTRIBUTING.md +92 -10
  2. package/LICENSE.md +2 -2
  3. package/package.json +1 -1
  4. package/src/ingest-cache.ts +4 -0
  5. package/src/meter.ts +5 -0
  6. package/src/mind/attention.ts +18 -1
  7. package/src/mind/bridge.ts +292 -54
  8. package/src/mind/junction.ts +21 -7
  9. package/src/mind/match.ts +92 -1
  10. package/src/mind/mechanisms/cast.ts +12 -0
  11. package/src/mind/mechanisms/confluence.ts +30 -1
  12. package/src/mind/mechanisms/cover.ts +36 -4
  13. package/src/mind/mechanisms/recall.ts +21 -44
  14. package/src/mind/mind.ts +39 -2
  15. package/src/mind/pipeline-mechanism.ts +86 -4
  16. package/src/mind/pipeline.ts +1 -1
  17. package/src/mind/reasoning.ts +15 -8
  18. package/src/mind/recognition.ts +40 -0
  19. package/src/mind/resonance.ts +0 -0
  20. package/src/mind/traverse.ts +17 -15
  21. package/src/mind/types.ts +10 -0
  22. package/test/49-natural-units-synonym-bridge.test.mjs +56 -15
  23. package/dist/example/demo.d.ts +0 -1
  24. package/dist/example/demo.js +0 -39
  25. package/dist/example/train_base.d.ts +0 -87
  26. package/dist/example/train_base.js +0 -2252
  27. package/dist/src/alphabet.d.ts +0 -7
  28. package/dist/src/alphabet.js +0 -33
  29. package/dist/src/alu/src/alu.d.ts +0 -185
  30. package/dist/src/alu/src/alu.js +0 -440
  31. package/dist/src/alu/src/expr.d.ts +0 -61
  32. package/dist/src/alu/src/expr.js +0 -318
  33. package/dist/src/alu/src/index.d.ts +0 -11
  34. package/dist/src/alu/src/index.js +0 -19
  35. package/dist/src/alu/src/kernel-arith.d.ts +0 -16
  36. package/dist/src/alu/src/kernel-arith.js +0 -264
  37. package/dist/src/alu/src/kernel-bits.d.ts +0 -19
  38. package/dist/src/alu/src/kernel-bits.js +0 -152
  39. package/dist/src/alu/src/kernel-logic.d.ts +0 -4
  40. package/dist/src/alu/src/kernel-logic.js +0 -60
  41. package/dist/src/alu/src/kernel-nd.d.ts +0 -3
  42. package/dist/src/alu/src/kernel-nd.js +0 -208
  43. package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
  44. package/dist/src/alu/src/kernel-numeric.js +0 -366
  45. package/dist/src/alu/src/operation.d.ts +0 -168
  46. package/dist/src/alu/src/operation.js +0 -189
  47. package/dist/src/alu/src/parser.d.ts +0 -221
  48. package/dist/src/alu/src/parser.js +0 -577
  49. package/dist/src/alu/src/resonance.d.ts +0 -55
  50. package/dist/src/alu/src/resonance.js +0 -126
  51. package/dist/src/alu/src/text.d.ts +0 -31
  52. package/dist/src/alu/src/text.js +0 -73
  53. package/dist/src/alu/src/value.d.ts +0 -109
  54. package/dist/src/alu/src/value.js +0 -300
  55. package/dist/src/alu/test/alu.test.d.ts +0 -1
  56. package/dist/src/alu/test/alu.test.js +0 -764
  57. package/dist/src/bytes.d.ts +0 -14
  58. package/dist/src/bytes.js +0 -59
  59. package/dist/src/canon.d.ts +0 -26
  60. package/dist/src/canon.js +0 -57
  61. package/dist/src/config.d.ts +0 -111
  62. package/dist/src/config.js +0 -91
  63. package/dist/src/derive/src/deduction.d.ts +0 -136
  64. package/dist/src/derive/src/deduction.js +0 -159
  65. package/dist/src/derive/src/index.d.ts +0 -8
  66. package/dist/src/derive/src/index.js +0 -11
  67. package/dist/src/derive/src/priority-queue.d.ts +0 -20
  68. package/dist/src/derive/src/priority-queue.js +0 -73
  69. package/dist/src/derive/src/rewrite.d.ts +0 -56
  70. package/dist/src/derive/src/rewrite.js +0 -100
  71. package/dist/src/derive/src/trie.d.ts +0 -90
  72. package/dist/src/derive/src/trie.js +0 -217
  73. package/dist/src/derive/test/derive.test.d.ts +0 -1
  74. package/dist/src/derive/test/derive.test.js +0 -122
  75. package/dist/src/extension.d.ts +0 -37
  76. package/dist/src/extension.js +0 -7
  77. package/dist/src/geometry.d.ts +0 -175
  78. package/dist/src/geometry.js +0 -823
  79. package/dist/src/index.d.ts +0 -17
  80. package/dist/src/index.js +0 -19
  81. package/dist/src/ingest-cache.d.ts +0 -41
  82. package/dist/src/ingest-cache.js +0 -161
  83. package/dist/src/meter.d.ts +0 -171
  84. package/dist/src/meter.js +0 -269
  85. package/dist/src/mind/articulation.d.ts +0 -6
  86. package/dist/src/mind/articulation.js +0 -99
  87. package/dist/src/mind/attention.d.ts +0 -414
  88. package/dist/src/mind/attention.js +0 -2065
  89. package/dist/src/mind/bridge.d.ts +0 -39
  90. package/dist/src/mind/bridge.js +0 -738
  91. package/dist/src/mind/canonical.d.ts +0 -34
  92. package/dist/src/mind/canonical.js +0 -93
  93. package/dist/src/mind/graph-search.d.ts +0 -294
  94. package/dist/src/mind/graph-search.js +0 -996
  95. package/dist/src/mind/index.d.ts +0 -9
  96. package/dist/src/mind/index.js +0 -5
  97. package/dist/src/mind/junction.d.ts +0 -134
  98. package/dist/src/mind/junction.js +0 -331
  99. package/dist/src/mind/learning.d.ts +0 -75
  100. package/dist/src/mind/learning.js +0 -270
  101. package/dist/src/mind/match.d.ts +0 -166
  102. package/dist/src/mind/match.js +0 -565
  103. package/dist/src/mind/mechanisms/alu.d.ts +0 -4
  104. package/dist/src/mind/mechanisms/alu.js +0 -36
  105. package/dist/src/mind/mechanisms/cast.d.ts +0 -89
  106. package/dist/src/mind/mechanisms/cast.js +0 -773
  107. package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
  108. package/dist/src/mind/mechanisms/confluence.js +0 -225
  109. package/dist/src/mind/mechanisms/cover.d.ts +0 -6
  110. package/dist/src/mind/mechanisms/cover.js +0 -203
  111. package/dist/src/mind/mechanisms/extraction.d.ts +0 -33
  112. package/dist/src/mind/mechanisms/extraction.js +0 -300
  113. package/dist/src/mind/mechanisms/recall.d.ts +0 -16
  114. package/dist/src/mind/mechanisms/recall.js +0 -388
  115. package/dist/src/mind/mind.d.ts +0 -330
  116. package/dist/src/mind/mind.js +0 -594
  117. package/dist/src/mind/pipeline-mechanism.d.ts +0 -172
  118. package/dist/src/mind/pipeline-mechanism.js +0 -382
  119. package/dist/src/mind/pipeline.d.ts +0 -49
  120. package/dist/src/mind/pipeline.js +0 -275
  121. package/dist/src/mind/primitives.d.ts +0 -66
  122. package/dist/src/mind/primitives.js +0 -306
  123. package/dist/src/mind/rationale.d.ts +0 -139
  124. package/dist/src/mind/rationale.js +0 -163
  125. package/dist/src/mind/reasoning.d.ts +0 -40
  126. package/dist/src/mind/reasoning.js +0 -272
  127. package/dist/src/mind/recognition.d.ts +0 -20
  128. package/dist/src/mind/recognition.js +0 -463
  129. package/dist/src/mind/resonance.d.ts +0 -23
  130. package/dist/src/mind/resonance.js +0 -0
  131. package/dist/src/mind/trace.d.ts +0 -15
  132. package/dist/src/mind/trace.js +0 -73
  133. package/dist/src/mind/traverse.d.ts +0 -124
  134. package/dist/src/mind/traverse.js +0 -649
  135. package/dist/src/mind/types.d.ts +0 -323
  136. package/dist/src/mind/types.js +0 -130
  137. package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
  138. package/dist/src/rabitq-ivf/src/database.js +0 -201
  139. package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
  140. package/dist/src/rabitq-ivf/src/index.js +0 -4
  141. package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
  142. package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
  143. package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
  144. package/dist/src/rabitq-ivf/src/prng.js +0 -36
  145. package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
  146. package/dist/src/rabitq-ivf/src/rabitq.js +0 -283
  147. package/dist/src/sema.d.ts +0 -31
  148. package/dist/src/sema.js +0 -63
  149. package/dist/src/store-sqlite.d.ts +0 -184
  150. package/dist/src/store-sqlite.js +0 -942
  151. package/dist/src/store.d.ts +0 -678
  152. package/dist/src/store.js +0 -1703
  153. package/dist/src/vec.d.ts +0 -31
  154. package/dist/src/vec.js +0 -109
@@ -1,773 +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, hubBound } from "../traverse.js";
15
- import { analogyStrength, follow, project, reverseContext, } 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
- // ── CAST gates ────────────────────────────────────────────────────────────
25
- //
26
- // The frame gate has TWO components, both derived from the weave itself:
27
- //
28
- // 1. MIN WEAVE — the same 2 as the precondition `points.length < 2` (CAST
29
- // needs at least two aligned structures to form a weave). Frame requires
30
- // evidence BEYOND the minimum pair — a third structure agreeing — so the
31
- // depth gate is `depth > MIN_WEAVE`. One definition, two uses.
32
- //
33
- // 2. HALF-DOMINANCE — `dominates(framed, len)` (the same test
34
- // collectRegions, liftAnswer, and confluence's filler gate all use): a
35
- // span more than half scaffolding no longer discriminates its own content.
36
- // The per-byte test `dominates(depth[i], aligned)` classifies a byte as
37
- // frame; the per-run test `dominates(framedCount, runLen)` decides
38
- // whether the run is usable.
39
- //
40
- // Both are derived from structural quantities (aligned points, run length),
41
- // never tuned. The constants below are the weave's own shape, not thresholds.
42
- //
43
- // DO NOT replace the frame gates with the structural IDF (reachOf +
44
- // dominates): it was tried and empirically REFUTED (17-intelligence's
45
- // reorder probe). CAST's frame is WEAVE-LOCAL — "what the aligned
46
- // structures share among THEMSELVES" — while the IDF is corpus-global; a
47
- // phrase common to the aligned exemplars (" describe it", "the importance
48
- // of") is frame here even when it reaches only a corpus minority, and
49
- // treating it as content lets the substitution branch fire on reordered
50
- // single-fact queries. The two commonality notions coincide often, but
51
- // neither derives the other.
52
- /** The minimum number of aligned structures to form a weave — the same 2 that
53
- * gates CAST entry (`points.length < 2`). Frame requires MORE than this
54
- * minimum: `depth > MIN_WEAVE` means at least three structures agree on a
55
- * byte, so no byte is frame when only the minimum pair exists. */
56
- const MIN_WEAVE = 2;
57
- /** The seat that establishes a node's role in an analogical comparison:
58
- * the REVERSE context (what leads to it) when a predecessor genuinely
59
- * ESTABLISHES id — introduces or describes it by name — else the FORWARD
60
- * continuation (what it leads to), else `fallback`.
61
- *
62
- * An earlier version gated this purely on `prevCount(id) > 0`: any
63
- * predecessor at all was treated as proof of a genuine named ENTITY
64
- * (seat it by what established it), while no predecessor meant a bare
65
- * learnt CONTEXT (seat it by what it leads to, since voicing it verbatim
66
- * would answer a question with a question). That test measured the wrong
67
- * thing — a broad sample of this store's own question-shaped nodes showed
68
- * the large majority (≈71%) have at least one predecessor, most of them a
69
- * handful of generic, high-fan-out sentences that recur as an INCIDENTAL
70
- * neighbour to dozens of otherwise-unrelated destinations (a SmolSent-
71
- * style sentence-adjacency artifact, never naming or describing what
72
- * follows). Traced live: "What is the capital of France?" — whose own
73
- * forward edge unambiguously resolves to "The capital of France is
74
- * Paris." — has exactly one such incidental predecessor ("Create an
75
- * example of a types of questions a GPT model can answer.?"), wrongly
76
- * read as disqualifying proof of "genuine entity."
77
- *
78
- * A plain forward-first swap (matching {@link project}'s universal
79
- * priority) over-corrected: test/29's C2/C3 pin that a genuine entity
80
- * analog (e.g. "Leonardo da Vinci", established by "The Mona Lisa was
81
- * painted by Leonardo da Vinci.") must be seated by that establishing
82
- * sentence, NOT by its own biography fact — voicing the bio leaks exactly
83
- * what a comparison must keep out, and loses the embedded "Mona Lisa"
84
- * term C3 relies on for a further hop.
85
- *
86
- * The distinguishing signal is content-addressed, not a count: a genuine
87
- * establishing predecessor's bytes CONTAIN id's own bytes — it names or
88
- * describes id ("...painted by Leonardo da Vinci." contains "Leonardo da
89
- * Vinci"). An incidental adjacency predecessor never does — it merely
90
- * preceded id in some unrelated document without ever mentioning it. No
91
- * new tuned constant: containment is the same primitive `restatesQuery`
92
- * and `dominates`-style checks already use throughout this codebase.
93
- *
94
- * `allowForward` (default true) gates the FORWARD branch specifically —
95
- * see the call sites below: the DOMINANT is what the query is actually
96
- * ASKING, so completing it forward is the whole point; an ANALOG is only
97
- * being CITED for comparison; the query never asked about IT, so chasing
98
- * its own further continuation drifts onto whatever coincidentally
99
- * follows it in the corpus. Traced live: the analog "What is the capital
100
- * of Japan?\nTokyo is the capital of Japan." is ALREADY a complete,
101
- * self-answering unit (prevCount 0, so no establishing predecessor
102
- * either) — its sole forward edge is "And what is the capital of the
103
- * Moon?", an unrelated quiz question sharing nothing but corpus
104
- * adjacency. With forward disallowed, an analog like this falls through
105
- * to `fallback` — its own bytes, exactly the complete fact that made it a
106
- * genuine analog in the first place. See
107
- * test/41-seatofnode-direction.test.mjs and
108
- * test/43-cast-analog-seat.test.mjs. */
109
- export async function seatOfNode(ctx, id, guide, fallback, allowForward = true) {
110
- const rev = ctx.store.prevFirst(id, hubBound(ctx));
111
- if (rev.length > 0) {
112
- const own = read(ctx, id);
113
- const establishing = rev.some((p) => indexOf(read(ctx, p), own, 0) >= 0);
114
- if (establishing) {
115
- const back = reverseContext(ctx, id, guide, rev);
116
- if (back !== null)
117
- return back;
118
- }
119
- }
120
- // The "last resort, non-establishing reverse" fallback below is itself a
121
- // LESS CERTAIN projection (the same tier as forward) — an analog
122
- // (allowForward: false) must stop at `fallback` (its own bytes) here
123
- // rather than fall back to a predecessor that already failed the
124
- // establishing check just above.
125
- if (!allowForward)
126
- return fallback;
127
- const fwd = await follow(ctx, id, guide);
128
- if (fwd !== null)
129
- return fwd;
130
- return reverseContext(ctx, id, guide, rev) ?? fallback;
131
- }
132
- /** CAST's own entry gates, checked once here and reused by
133
- /** The main CAST entry point. Given a query and its pre-computed pre.rec.sites,
134
- * determine whether the query weaves together multiple independent learnt
135
- * structures (by graded alignment — literal first, then halo-matched pre.rec.sites).
136
- * If so, attempt substitution, redirection, AND analogical comparison —
137
- * each schema is tried independently and every one that fires yields its
138
- * OWN candidate; think's grounding decider (which already compares weights
139
- * across mechanisms) picks among them, so CAST no longer needs an internal
140
- * priority order.
141
- *
142
- * `climb`, when given, is {@link castFloor}'s own climb result — reused
143
- * instead of re-running climbAttentionAll (see the note on {@link
144
- * CastFloor}). Its gates (`query.length`, `edgeSourceCount`,
145
- * `ranked.length < 2`) MUST stay in sync with castFloor's — one is the
146
- * other's admissible lower bound, checked before this runs.
147
- *
148
- * Returns the array of {@link CastResult}s that fired (possibly empty). */
149
- export async function counterfactualTransfer(ctx, query, pre) {
150
- // Opened unconditionally, at entry — the same convention recall.ts's
151
- // recallByResonance and extraction.ts's extractBySkill use, so every exit
152
- // path (five gates below, then the schemas themselves) closes through
153
- // ONE scope and inspectRationale never hits a silent dead end. Only the
154
- // first two gates duplicate floor()'s own admissible bound (query length,
155
- // ranked anchor count) — required to stay in sync per this function's own
156
- // doc comment above, and effectively dead through the ordinary pipeline
157
- // (floor() returning null already stops run() from being called at all),
158
- // but this function is also exported and callable directly, so they stay
159
- // and get the same honest trace as everything past them.
160
- const t = ctx.trace?.enter("counterfactual", [rItem(query, "query")]);
161
- const fail = (note) => {
162
- t?.done([], note);
163
- return [];
164
- };
165
- const quantum = ctx.space.maxGroup;
166
- if (query.length < 2 * quantum || ctx.store.edgeSourceCount() === 0) {
167
- return fail("query below the two-quantum floor, or no edges learnt yet");
168
- }
169
- const { roots, ranked } = await pre.attention();
170
- if (ranked.length < 2) {
171
- return fail(`only ${ranked.length} ranked anchor(s) — CAST needs at least two`);
172
- }
173
- const weave = await pre.weave();
174
- const points = weave.points;
175
- const depth = weave.depth;
176
- const aligned = points.length;
177
- if (aligned < 2) {
178
- return fail(`only ${aligned} structure(s) aligned across the query — CAST needs ` +
179
- `at least two to transfer between`);
180
- }
181
- // ── Frame gate (half-dominance, weave-local) ─────────────────────────
182
- // A byte is FRAME when more than MIN_WEAVE aligned structures cover it
183
- // AND those structures are a majority of all aligned structures.
184
- // Per-byte: frame(i) ⇔ depth[i] > MIN_WEAVE ∧ dominates(depth[i], aligned)
185
- // Per-run: usable(r) ⇔ ¬dominates(framedCount, runLen)
186
- const isFrame = (i) => depth[i] > MIN_WEAVE && dominates(depth[i], aligned);
187
- const framedCount = (qs, qe) => {
188
- let n = 0;
189
- for (let i = qs; i < qe; i++)
190
- if (isFrame(i))
191
- n++;
192
- return n;
193
- };
194
- const usable = (qs, qe) => !dominates(framedCount(qs, qe), qe - qs);
195
- // The weave's DOMINANT is its principal STRUCTURE — the aligned point
196
- // explaining the most query bytes — not the climb's top-ranked TOPIC.
197
- // The two used to coincide (approximate votes from a query's novel spans
198
- // boosted whichever exemplar shared its frame), but the contrastive
199
- // margin ranks the query's own exact site first, and CAST's schemas all
200
- // orient around the frame-bearing structure: the substitution/redirection
201
- // seat is displaced IN the dominant, and comparison seats the analogs by
202
- // the contexts that establish their roles. Coverage is weave-local and
203
- // derived (sum of aligned run lengths); ties keep the ranked order.
204
- let dominant = points[0];
205
- let domCover = -1;
206
- for (const p of points) {
207
- let cover = 0;
208
- for (const r of p.runs)
209
- cover += r.qe - r.qs;
210
- if (cover > domCover) {
211
- domCover = cover;
212
- dominant = p;
213
- }
214
- }
215
- const isRoot = (id) => roots.some((r) => r.anchor === id);
216
- // The weave must touch a COMMITTED point of attention: the dominant
217
- // structure itself, or another aligned point the climb committed to.
218
- if (!points.some((p) => isRoot(p.anchor))) {
219
- t?.done([
220
- ...points.map((p) => rNode(ctx, p.anchor, "aligned")),
221
- ...roots.map((r) => rNode(ctx, r.anchor, "committed-root")),
222
- ], `${points.length} aligned structure(s), but none is one of the climb's ` +
223
- `${roots.length} committed root(s) — CAST refuses to transfer through ` +
224
- `content the climb itself never settled on`);
225
- return [];
226
- }
227
- const woven = points.some((p) => p.runs.some((r) => !pre.rec.sites.some((s) => r.qs >= s.start && r.qe <= s.end)));
228
- if (!woven) {
229
- return fail(`every aligned run restates a recognised query site — nothing was ` +
230
- `actually WOVEN across structures, so there is nothing to transfer`);
231
- }
232
- // Each schema tried below RECORDS its candidate (when it fires) rather than
233
- // returning immediately — every schema that succeeds contributes its own
234
- // candidate, and the grounding decider's own weight comparison (not CAST's
235
- // former internal priority) picks among them.
236
- //
237
- // `accounted` is SCHEMA-SPECIFIC, not the whole weave's alignment: a
238
- // schema only actually TRANSFERS BETWEEN the two points its own logic
239
- // names (substitution: the filled subject + the displaced seat;
240
- // redirection: the displaced seat + the named substitute; comparison:
241
- // the dominant + its analog) — a THIRD point the weave happened to align
242
- // but this schema never touched contributes nothing to what THIS answer
243
- // explains. Pricing every schema against the SAME "every kept point's
244
- // every run" span would let the cheapest schema win on move-cost alone
245
- // regardless of which one actually used more of the query; pricing it
246
- // against only a fragment of even its OWN two points (e.g. one run
247
- // instead of the point's full aligned evidence) is just as wrong the
248
- // other way — it starves an otherwise-correct schema of credit for
249
- // evidence it legitimately relied on. Each call site below passes the
250
- // full run set of exactly the points ITS OWN transfer used — no more,
251
- // no less.
252
- const runSpans = (p) => p.runs.map((r) => [r.qs, r.qe]);
253
- const results = [];
254
- const record = (answer, note, used, moves, accounted) => {
255
- if (answer === null)
256
- return;
257
- ctx.trace?.step("castSchema", [rItem(query, "query")], [rItem(answer, "answer")], note);
258
- results.push({
259
- bytes: answer,
260
- used: used ?? new Set(),
261
- accounted,
262
- moves,
263
- unexplained: unexplainedLabel(query, accounted),
264
- });
265
- };
266
- 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");
267
- const lastRun = (p) => p.runs[p.runs.length - 1];
268
- const qv = pre.guide;
269
- // ── SUBSTITUTION ──────────────────────────────────────────────────
270
- const fillerOf = (s) => {
271
- const r = s.runs[0];
272
- return r.cs < quantum
273
- ? s.ctx.subarray(0, r.cs + (r.qe - r.qs))
274
- : query.subarray(r.qs, r.qe);
275
- };
276
- // The subject is the closest structure whose FILLER RUN precedes the seat.
277
- // The gate is on `runs[0]` — the run `fillerOf` actually reads — not on the
278
- // point's LAST run: requiring the subject's whole alignment to end before
279
- // the seat disqualifies any structure the query mentions on BOTH sides of
280
- // it, which is the shape CAST exists for. Measured on
281
- // `steel is frigid so steel is ???` against `steel is hard so steel is
282
- // strong` (runs "steel is " at 0-9 and "d so steel is " at 14-28) and
283
- // `water is frigid so water is freezing` (seat "frigi" at 9-14): the
284
- // subject's filler run sits squarely before the seat, but its second run —
285
- // the recurrence AFTER it, the very thing that makes the sentence an
286
- // analogy — pushed lastRun past the seat and no substitution fired at all.
287
- // The ordering key follows the gate to the same run, so "closest preceding"
288
- // still means closest by the evidence actually used.
289
- const beforeOf = (p, r) => argmaxBy(points.filter((s) => s !== p && s.runs[0].qe <= r.qs &&
290
- s.runs[0].cs < quantum &&
291
- usable(s.runs[0].qs, s.runs[0].qe)), (s) => s.runs[0].qs, -Infinity, true)?.item;
292
- const displacement = points
293
- .map((p) => {
294
- const r = p.runs[0];
295
- if (r.cs < quantum || !usable(r.qs, r.qe)) {
296
- return null;
297
- }
298
- const before = beforeOf(p, r);
299
- if (before === undefined)
300
- return null;
301
- if (r.cs > fillerOf(before).length + quantum)
302
- return null;
303
- // SUBSTITUTION MUST ACTUALLY DISPLACE. The schema's premise is that the
304
- // displaced structure's seat is held by something ELSE, which the
305
- // subject then replaces. When the subject's filler already occurs in
306
- // that structure, there is nothing to displace — the "transfer" restates
307
- // the structure with its own occupant put back, and the answer is a
308
- // tautology. Measured on `Michelangelo is to sculpture as who is to
309
- // literature?`: the weave aligned the concept `Michelangelo` and the
310
- // exemplar `The David was sculpted by Michelangelo.`, and substitution
311
- // produced `Michelangelo sculpted by Michelangelo.` — then outbid every
312
- // honest candidate with it (test/29 A2). Byte containment, the same
313
- // primitive the self-evidence and contradiction guards use.
314
- if (indexOf(p.ctx, fillerOf(before), 0) >= 0)
315
- return null;
316
- return { p, before, depth: p.ctx.length - r.cs };
317
- })
318
- .filter((c) => c !== null);
319
- const picked = argmaxBy(displacement, (c) => c.depth, -Infinity, true);
320
- const proj = picked?.item.p ?? null;
321
- const subj = picked?.item.before ?? null;
322
- if (proj !== null && subj !== null) {
323
- const seat = proj.runs[0];
324
- const filler = fillerOf(subj);
325
- const tail = proj.ctx.subarray(seat.cs);
326
- let answer = await joinWithBridge(ctx, filler, tail);
327
- const fwd = await follow(ctx, proj.anchor, qv);
328
- if (fwd !== null && indexOf(answer, fwd, 0) < 0 &&
329
- !restatesQuery(query, fwd)) {
330
- answer = concat2(answer, fwd);
331
- }
332
- ctx.trace?.step("projectCounterfactual", [
333
- rItem(filler, "filler", subj.anchor),
334
- rNode(ctx, proj.anchor, "displaced-structure"),
335
- ], [rItem(answer, "projection")], "transfer the displaced structure onto the subject filler (seat substitution)");
336
- record(answer, "counterfactual substitution — the subject fills the analog's seat", new Set([subj.anchor, proj.anchor]),
337
- // The acts performed: one seat INSERT projection + one edge FOLLOW.
338
- STEP + STEP,
339
- // What substitution actually READ: the two points it transfers
340
- // between — the subject filling the seat, and the displaced
341
- // structure whose seat it fills — not every OTHER point the weave
342
- // happened to align (a third, unrelated point in the same weave
343
- // contributes nothing to what substitution itself explains).
344
- [...runSpans(subj), ...runSpans(proj)]);
345
- }
346
- // ── REDIRECTION ────────────────────────────────────────────────────
347
- const last = points.reduce((a, b) => lastRun(b).qs > lastRun(a).qs ? b : a);
348
- // Displacement test, capped at the hub bound: a hub anchor can carry a
349
- // corpus-sized fan-out, and each continuation costs a full byte
350
- // reconstruction plus an O(|query|·|bytes|) scan. The first √N edges (the
351
- // same insertion-order convention chooseNext caps by) decide; past a hub's
352
- // cap the test reads "none of the established continuations appears".
353
- const domNext = ctx.store.nextFirst(dominant.anchor, hubBound(ctx));
354
- const displaced = domNext
355
- .every((n) => indexOf(query, read(ctx, n), 0) < 0);
356
- if (last !== dominant &&
357
- last.runs[0].cs === 0 && displaced &&
358
- usable(last.runs[0].qs, last.runs[0].qe)) {
359
- const g = await project(ctx, last.anchor, qv);
360
- if (g !== null) {
361
- ctx.trace?.step("projectCounterfactual", [
362
- rNode(ctx, dominant.anchor, "displaced-structure"),
363
- rNode(ctx, last.anchor, "substitute"),
364
- ], [rItem(g, "projection")], "the substitute's own fact replaces the displaced structure's answer");
365
- record(g, "counterfactual redirection — the named substitute's fact is followed", new Set([dominant.anchor, last.anchor]),
366
- // One forward projection across the substitute's own fact.
367
- STEP,
368
- // What redirection READ: the displaced structure's own recognized
369
- // seat (still explained — this schema RECOGNIZES it as the slot
370
- // being overridden, it just doesn't answer from it) plus the named
371
- // substitute's own aligned run — not every OTHER point the weave
372
- // happened to align.
373
- [...runSpans(dominant), ...runSpans(last)]);
374
- }
375
- }
376
- // ── COMPARISON ─────────────────────────────────────────────────────
377
- // Collect every qualifying non-dominant point as a candidate analog.
378
- // When a point's own anchor is structurally at the wrong level
379
- // (e.g. a long exemplar sentence whose halo does not resemble the
380
- // dominant's), its nextOf targets often point to the right level — the
381
- // person / concept the exemplar is about. Trying both prevents a
382
- // seed-dependent failure where the climb ranks an exemplar above a
383
- // person node and the person node is excluded from points by run-
384
- // overlap trimming.
385
- // The seat that establishes a candidate's role — see {@link seatOfNode}.
386
- const seatOf = (p, allowForward = true) => seatOfNode(ctx, p.anchor, qv, p.ctx, allowForward);
387
- // QUERY-SCALE — "this learnt context is the same size as the question", the
388
- // bound comparison holds its dominant and its analogs to. A byte-exact
389
- // `<= query.length` made that judgement turn on a difference the
390
- // architecture cannot perceive: on `The Weeping Woman was painted by Pablo
391
- // Picasso.` (47 bytes) the weave's dominant was `The Night Watch was painted
392
- // by Rembrandt van Rijn.` (50) — three bytes over, so comparison refused
393
- // outright, while the interchangeable `The Mona Lisa was painted by Leonardo
394
- // da Vinci.` (47) would have passed. WHICH exemplar becomes dominant is
395
- // settled by run-claiming order among equals, so a 3-byte difference was
396
- // deciding whether the schema fires at all (test/33 1b). W is the smallest
397
- // distinction perception can make — the same quantum countClusters separates
398
- // neighbourhoods by — so a context within one quantum of the query's length
399
- // carries no independently perceivable unit beyond it and is the same scale.
400
- const queryScale = (n) => n - query.length < quantum;
401
- const analogs = [];
402
- for (const p of points) {
403
- if (p === dominant)
404
- continue;
405
- // Push the point's own anchor only when its context fits within
406
- // the query (the seat sentence must not dominate the comparison).
407
- if (queryScale(p.ctx.length) &&
408
- indexOf(dominant.ctx, p.ctx, 0) < 0 &&
409
- indexOf(p.ctx, dominant.ctx, 0) < 0 &&
410
- indexOf(query, p.ctx, 0) < 0) {
411
- analogs.push({ anchor: p.anchor, point: p, src: p });
412
- }
413
- // Reach through to the point's continuation targets regardless
414
- // of the point's own context length: when the point is a leaf
415
- // (exemplar sentence), its nextOf is the hub (person / concept)
416
- // that makes a genuine cross-domain analog, and the hub's own
417
- // (shorter) context will be the seat.
418
- // Capped like every fan-out: a hub anchor's full continuation list is
419
- // corpus-sized, and each candidate costs a read plus O(|query|·|bytes|)
420
- // scans — only the first √N (insertion order, the same convention
421
- // chooseNext caps by) are reachable as analogs.
422
- for (const nid of ctx.store.nextFirst(p.anchor, hubBound(ctx))) {
423
- const nctx = read(ctx, nid);
424
- if (!queryScale(nctx.length) ||
425
- indexOf(dominant.ctx, nctx, 0) >= 0 ||
426
- indexOf(nctx, dominant.ctx, 0) >= 0 ||
427
- indexOf(query, nctx, 0) >= 0)
428
- continue;
429
- analogs.push({ anchor: nid, point: null, src: p });
430
- }
431
- }
432
- // MEASURED AND REFUTED — proposing analogs from the dominant's halo when the
433
- // query-local generator finds none. Both loops above are query-local (an
434
- // aligned point, or one forward hop off one), while the gate that judges
435
- // candidates — analogyStrength's halo tier — is cross-domain by construction
436
- // and is licence enough on its own (`bestHalo` exempts it from the naming and
437
- // trusted-root bars). So the gate reads as strictly more capable than the
438
- // generator, and closing that asymmetry looks like the fix for test/29 A2
439
- // (`Michelangelo is to sculpture as who is to literature?`, whose only stored
440
- // content is `Michelangelo`: the weave aligns the concept and its own
441
- // exemplar, each contains the other, so every candidate is excluded and
442
- // comparison checks zero).
443
- //
444
- // It proposes nothing. Measured on A2's own 13-pair corpus: `Michelangelo`
445
- // HAS a halo, and `haloSiblings` returns not one sibling above
446
- // significanceBar — the distributional company that would make Shakespeare a
447
- // cross-domain analog was never trained. The asymmetry is real but it is not
448
- // what stops A2; the corpus is.
449
- let bestAnalog = null;
450
- let bestSim = 0;
451
- let bestHalo = false;
452
- // Whether the query itself NAMES a candidate. A directly aligned point
453
- // is named by construction — its runs ARE query bytes. A hop-reached
454
- // candidate is named when its own bytes contain the query text of an
455
- // aligned run of the point whose continuation edge reached it (that
456
- // alignment IS the query evidence the hop rests on — the same reading
457
- // cmpAccounted already prices): "William Shakespeare", reached off
458
- // "Macbeth was written by William Shakespeare.", contains the src's
459
- // 12-byte aligned run " Shakespeare" — test/29 C2/C3. The run must span
460
- // at least TWO perception windows (2·W, the same two-quantum floor
461
- // CAST's own entry gate holds the whole query to): a single shared
462
- // W-window is exactly the frame tier's own evidence quantum — the level
463
- // "half the corpus" shares — and stopword scraps (" the ", "he b",
464
- // 4–5 bytes) never reach two windows, while a genuinely named entity
465
- // does. NOT the weave's usable()/frame filter: weave depth counts every
466
- // ranked exemplar, so a query's own named entity recurring across
467
- // exemplars ("Shakespeare" in Hamlet+Macbeth+…) is wrongly classified as
468
- // frame — measured live, it silently disqualified C3's genuine analog.
469
- const namedByQuery = (c) => {
470
- if (c.point !== null)
471
- return true;
472
- const bytes = read(ctx, c.anchor);
473
- return c.src.runs.some((r) => r.qe - r.qs >= 2 * quantum &&
474
- indexOf(bytes, query.subarray(r.qs, r.qe), 0) >= 0);
475
- };
476
- // Whether any committed root's consensus vote clears the SAME trust bar
477
- // recallByResonance applies before grounding through a climb root:
478
- // consensusFloor(N) = ln(N) + 1/2. The climb's FIRST root is
479
- // deliberately floor-free (attention.ts: "the dominant one always
480
- // grounds") — fine for ORIENTING mechanisms, not for voicing learnt
481
- // content the query never asked about. Computed once here; both the
482
- // hub fallback below and the comparison gate consume it.
483
- const rootTrusted = roots.some((r) => r.vote >= consensusFloor(corpusN(ctx)));
484
- for (const c of analogs) {
485
- const { score: sim, halo } = await analogyStrength(ctx, dominant.anchor, c.anchor);
486
- ctx.trace?.step("tryAnalog", [
487
- rNode(ctx, dominant.anchor, "dominant"),
488
- rNode(ctx, c.anchor, "candidate", sim),
489
- ], [], `analogy strength ${sim.toFixed(4)}${halo ? " (halo tier)" : ""}`);
490
- if (sim > bestSim) {
491
- bestSim = sim;
492
- bestAnalog = c;
493
- bestHalo = halo;
494
- }
495
- }
496
- // When every candidate fails the similarity gates (halo company — now
497
- // deterministic signatures, see sema.ts — and the shared-frame tier),
498
- // fall back to a candidate that is a genuine structural hub (edges in
499
- // BOTH directions). A hub node — a person, concept, or category — is
500
- // the kind of thing that makes sense to compare across domains. A leaf
501
- // value (extracted span, terminal answer) has edges in at most one
502
- // direction and comparing it would preempt the extraction pipeline,
503
- // which is the right mechanism for those. A fallback comparison carries
504
- // NO similarity evidence — it stays honest only because the grounding
505
- // decider weighs it against mechanisms that explain more of the query
506
- // (extraction accounts its whole located envelope; see extraction.ts).
507
- //
508
- // WHICH hub: not the first in `analogs` order — that order flows from the
509
- // vote ranking, which flows from approximate resonance, which is seed-
510
- // dependent. Pick by evidence instead: combined edge support (prevCount +
511
- // fan-out), tie-broken by poured halo MASS (episode corroboration — the
512
- // direct distributional evidence), then by LOWEST node id. The id order
513
- // is a property of the corpus, not of the seed — but note ids are SIGNED:
514
- // byte leaves occupy −256…−1, so "lowest id" is creation order only among
515
- // multi-byte nodes and byte-value order among leaves. Either way it is
516
- // deterministic, which is all the final tie-break must be.
517
- if (bestAnalog === null && analogs.length > 0) {
518
- let hubSupport = -1;
519
- let hubMass = -1;
520
- const fanClamp = hubBound(ctx) + 1;
521
- for (const c of analogs) {
522
- // A fallback comparison carries NO similarity evidence at all. Its
523
- // honesty rests on the grounding decider discounting it against
524
- // richer candidates (the design note below) — an assumption that
525
- // holds only when the climb itself settled on this query with real
526
- // evidence. Under a root the consensus floor does not trust, an
527
- // unnamed, hop-reached hub is pure corpus adjacency: refusing it is
528
- // what kept the live wrong echo silent. A hub the query itself
529
- // NAMED stays eligible either way (test/29 C2/C3's "William
530
- // Shakespeare"); an unnamed one under a TRUSTED root stays eligible
531
- // too (test/33 1b's deliberately weak second candidate).
532
- if (!rootTrusted && !namedByQuery(c))
533
- continue;
534
- // Evidence clamped at the hub bound: beyond √N + 1 the exact fan-out
535
- // no longer discriminates (every mega-hub ties at the clamp), and
536
- // counting it exactly would require the corpus-sized read.
537
- const fanOut = ctx.store.nextFirst(c.anchor, fanClamp).length;
538
- if (fanOut === 0)
539
- continue;
540
- const support = ctx.store.prevCount(c.anchor);
541
- if (support === 0)
542
- continue;
543
- const total = support + fanOut;
544
- if (total < hubSupport)
545
- continue;
546
- const mass = ctx.store.haloMass(c.anchor);
547
- if (total > hubSupport ||
548
- mass > hubMass ||
549
- (mass === hubMass && bestAnalog !== null &&
550
- c.anchor < bestAnalog.anchor)) {
551
- hubSupport = total;
552
- hubMass = mass;
553
- bestAnalog = c;
554
- }
555
- }
556
- if (bestAnalog !== null) {
557
- ctx.trace?.step("tryAnalog", [], [rNode(ctx, bestAnalog.anchor, "fallback", hubSupport)], "no candidate passed the similarity gates — using the best-supported structural hub");
558
- }
559
- }
560
- ctx.trace?.step("tryAnalog", [], bestAnalog !== null ? [rNode(ctx, bestAnalog.anchor, "best", bestSim)] : [], bestAnalog !== null
561
- ? `best analog with strength ${bestSim.toFixed(4)}`
562
- : `no analog candidate passed (${analogs.length} checked)`);
563
- // COMPARISON gate — analogical comparison seats the dominant against ONE
564
- // analog, so it presupposes the query is ABOUT a single thing. When the
565
- // consensus climb instead committed to MULTIPLE independent points of
566
- // attention (`roots.length > 1`), the query names independent topics to
567
- // FUSE — the reasoner's fuseAttention already combines them — not analogs
568
- // to compare. Firing here would juxtapose two co-scaffolded but unrelated
569
- // records (each sharing only the corpus preamble), out-accounting the
570
- // honest thin multi-root grounding with a frame echo. Derived from the
571
- // climb's own forest, never tuned; substitution/redirection stay
572
- // unaffected — they orient around a displaced seat, not a whole-topic
573
- // analogy.
574
- //
575
- // roots.length <= 1 is a PROXY for "the query is about one thing" — it is
576
- // only as good as the climb's own root-commitment, which depends on
577
- // recognise() having found something to commit a root TO. When the
578
- // query's newest content genuinely isn't recognised (not boundary noise —
579
- // real, uncommitted content; see the session's own investigation of the
580
- // France→Spain live trace), the climb under-commits roots and this proxy
581
- // is fooled: comparison looks licensed to treat the query as one topic
582
- // when it is not.
583
- //
584
- // The direct check is the SAME accounted spans comparison is about to
585
- // cite as its evidence: unexplainedSpans (rationale.ts, the same gap
586
- // computation the trace's own `unexplained` diagnostic uses) names every
587
- // stretch of the query NEITHER the dominant NOR the analog's evidence
588
- // touches. A short comparison query ("How is ice like steel?") legitimately
589
- // accounts for only its two short entity spans — the surrounding "How is
590
- // ... like ...?" framing is real but SHORT, split into several small gaps,
591
- // none of them the bulk of the query. The live bug's shape is different in
592
- // kind, not degree: ONE contiguous, substantial gap — a whole second
593
- // question the query added that comparison's two spans never touch at all.
594
- //
595
- // Two bars, both derived, neither tuned:
596
- // • the largest gap must not DOMINATE the whole query (the same
597
- // predicate CAST's own frame gate uses) — rules out a gap that is
598
- // most of the query outright;
599
- // • the largest gap must be SMALLER than the dominant's own established
600
- // context. A gap can't be dismissed as mere connective framing once
601
- // it is at least as large as the topic being compared FROM — at that
602
- // scale it isn't glue between two named things, it's substantial
603
- // enough to be a second topic in its own right. This is what
604
- // actually separates the live bug (a 47-byte gap against a 30-byte
605
- // dominant — the ignored content is bigger than the topic itself)
606
- // from ordinary short comparisons (a 9-byte gap against an 11-byte
607
- // dominant — the gap is smaller than what's being compared): the two
608
- // cases land on the same side of "half the query" often enough
609
- // (both can exceed or clear it) that the query-relative bar alone
610
- // does not reliably separate them — the topic-relative scale does.
611
- const cmpAccounted = bestAnalog !== null
612
- ? [...runSpans(dominant), ...runSpans(bestAnalog.point ?? bestAnalog.src)]
613
- : [];
614
- const cmpGaps = unexplainedSpans(query.length, cmpAccounted);
615
- const cmpMaxGap = cmpGaps.reduce((n, [s, e]) => Math.max(n, e - s), 0);
616
- // An analog that is not itself a directly ALIGNED point (point !== null —
617
- // its own runs are query bytes, the query NAMED it) was only reached
618
- // through a continuation hop or the structural-hub fallback. Voicing
619
- // learnt content the query never named is the same act recallByResonance
620
- // refuses to perform through a climb root whose consensus vote is below
621
- // consensusFloor(N) = ln(N) + 1/2 (recall.ts's minVote), so comparison
622
- // holds the climb to that SAME bar before citing a hop-reached analog:
623
- // some committed root must clear the floor. The climb's FIRST root is
624
- // deliberately floor-free (attention.ts: "the dominant one always
625
- // grounds") — fine for ORIENTING mechanisms, not for transferring
626
- // unnamed content through. The live bug this gates (real trained store,
627
- // 325k edge sources, floor 13.2): the query's stopword scraps pooled a
628
- // 1.92 vote that committed an unrelated haiku exemplar as the sole root,
629
- // and comparison voiced that exemplar's continuation through a
630
- // hop-reached analog while every other mechanism honestly refused. A
631
- // directly aligned analog needs no floor — the query's own bytes are its
632
- // evidence (test/29 C1's "Steel is hard" for "How is ice like steel?").
633
- // See test/50-cast-analog-consensus-floor.
634
- // A HALO-tier best analog needs neither: its similarity already cleared
635
- // significanceBar-gated distributional company (analogyStrength's
636
- // `halo`) — genuine evidence in its own right, the very case the halo
637
- // gate exists for (test/33 1b's nickname-corroborated analog). Only a
638
- // FRAME-tier or fallback analog — whose "similarity" is an unbarred
639
- // coverage fraction or nothing — needs the query's naming or the climb's
640
- // trust.
641
- const analogNamed = bestAnalog !== null && namedByQuery(bestAnalog);
642
- // NOTE — two further gates were tried here and empirically REFUTED,
643
- // recorded so they are not re-tried:
644
- // • dominant self-coverage (dominant's aligned runs must dominate its
645
- // own ctx): legitimate dominants sit at the same coverage as junk
646
- // ones ("The Mona Lisa was painted by…" 16/47 vs the live junk
647
- // haiku ~10/54) — no separation.
648
- // • denying the shared-frame similarity tier to hop-reached analogs:
649
- // semantically right in isolation, but it merely promoted the next
650
- // junk candidate — an ALIGNED scrap-matched point ("The affluence…",
651
- // frame 0.157) — into bestAnalog on the live store, and the aligned
652
- // configuration is byte-structurally IDENTICAL to test/29 C1's
653
- // legitimate one ("Steel is hard", frame 0.364): every derived
654
- // local separator measured (run length, site overlap, frame
655
- // query-containment, weave-usable classification) falls on the same
656
- // side for both. Only corpus-scale consensus separates them, which
657
- // is exactly what `rootTrusted` prices.
658
- // FRAME-tier evidence under an UNTRUSTED root is comparison's weakest
659
- // licence (an unbarred coverage fraction, a climb the consensus floor
660
- // does not trust). There it is additionally held to the IGNORED-KNOWN
661
- // principle (dismissedKnownContent, bridge.ts): the two analogs' aligned
662
- // runs must account for every STORED window of the query. This is the
663
- // byte-structural separator the refuted-gates note below could not find
664
- // locally: a legitimate small-corpus comparison ("How is ice like
665
- // steel?") leaves only UNATTESTED spans ("How ", " like ") unexplained,
666
- // while a scrap-matched junk pair leaves the query's own trained content
667
- // ("…songs…times…", "…planet…sun.") dismissed as gaps. Halo-tier and
668
- // trusted-root comparisons are exempt — their evidence already stands.
669
- const cmpDismisses = !(bestHalo || rootTrusted) &&
670
- dismissedKnownContent(ctx, query, cmpAccounted);
671
- if (bestAnalog !== null &&
672
- (bestHalo || analogNamed || rootTrusted) &&
673
- !cmpDismisses &&
674
- queryScale(dominant.ctx.length) &&
675
- roots.length <= 1 &&
676
- !dominates(cmpMaxGap, query.length) &&
677
- cmpMaxGap < dominant.ctx.length) {
678
- ctx.trace?.step("validateAnalogy", [
679
- rNode(ctx, dominant.anchor, "analog", bestSim),
680
- rNode(ctx, bestAnalog.anchor, "analog", bestSim),
681
- ], [], "the two structures keep distributional company beyond chance — genuine analogs");
682
- const a = await seatOf(dominant);
683
- // The analog is only being CITED for comparison — the query never asked
684
- // about it — so its seat never chases a FORWARD continuation (see
685
- // seatOfNode's `allowForward`): only reverse (if a predecessor genuinely
686
- // establishes it) or its own bytes. A DIRECTLY aligned point
687
- // (bestAnalog.point !== null) still goes through seatOfNode for that
688
- // reverse check (a bare entity NAME like "Leonardo da Vinci" needs it —
689
- // test/29's C2/C3). A nextOf DESCENDANT (point === null) was already
690
- // reached by following ONE meaningful hop off another aligned point (the
691
- // alignment loop above: "its nextOf is the hub... and the hub's own
692
- // [...] context will be the seat") — its own bytes ARE that seat
693
- // directly, with no predecessor to even check (it was found by a
694
- // forward edge, not matched in the query).
695
- const b = bestAnalog.point !== null
696
- ? await seatOf(bestAnalog.point, false)
697
- : read(ctx, bestAnalog.anchor);
698
- const answer = await joinWithBridge(ctx, a, b);
699
- record(answer, "analogical comparison — each analog voiced by the context that establishes its role", new Set([dominant.anchor, bestAnalog.anchor]),
700
- // A halo-mediated act (the analogy gate) plus two seat projections.
701
- CONCEPT + STEP + STEP,
702
- // What comparison READ: the dominant's own aligned runs, plus the
703
- // aligned runs of the point that named the analog — the analog itself
704
- // when it was an aligned point, else the source point whose
705
- // continuation edge reached it (that alignment IS the query evidence
706
- // the hop rests on).
707
- cmpAccounted);
708
- }
709
- else if (bestAnalog !== null &&
710
- queryScale(dominant.ctx.length) &&
711
- roots.length <= 1) {
712
- ctx.trace?.step("validateAnalogy", [
713
- rNode(ctx, dominant.anchor, "analog", bestSim),
714
- rNode(ctx, bestAnalog.anchor, "analog", bestSim),
715
- ], [], !(bestHalo || analogNamed || rootTrusted)
716
- ? `the best analog carries no halo-tier company evidence, was never ` +
717
- `named by the query, and no committed root's consensus vote ` +
718
- `clears the floor, so comparison refuses to voice it`
719
- : cmpDismisses
720
- ? `a frame-tier analog under an untrusted root dismisses stored ` +
721
- `query content its alignment never accounted for — comparison ` +
722
- `refuses to ignore what the store knows`
723
- : `comparison's own accounted evidence leaves a ${cmpMaxGap}-byte gap in ` +
724
- `a ${query.length}-byte query against a ${dominant.ctx.length}-byte ` +
725
- `dominant — too large to be mere framing — so it refuses rather ` +
726
- `than paper over it with an analog the query never asked about`);
727
- }
728
- t?.done(results.map((r) => rItem(r.bytes, "answer")), results.length > 0
729
- ? `${results.length} counterfactual schema(s) fired — the grounding decider weighs them`
730
- : "no counterfactual weave — the ordinary pipeline decides");
731
- return results;
732
- }
733
- export const castMechanism = {
734
- name: "cast",
735
- provenance: "cast",
736
- async floor(_ctx, query, pre, worthRunning) {
737
- const W = _ctx.space.maxGroup;
738
- // Cheap checks first — no pre-computation needed.
739
- if (query.length < 2 * W || _ctx.store.edgeSourceCount() === 0)
740
- return null;
741
- // CAST's floor, when it exists, is ALWAYS exactly 2*STEP — the climb and
742
- // the weave only decide whether it exists (2*STEP) or not (null), they
743
- // never tighten the number itself. So if 2*STEP already can't beat
744
- // whatever incumbent has already won this response (cover runs first —
745
- // see defaultMechanisms), no analysis can change the outcome: RETURN THE
746
- // BOUND uninvested (still admissible) and let the pipeline's own check
747
- // prune run() with the truthful "cannot beat incumbent" note. This is
748
- // the SAME admissible-floor economy worthRunning applies to run(),
749
- // applied to floor()'s own investment — uniformly, whatever mechanism
750
- // supplied the incumbent (an extension's computed result is not
751
- // special-cased; any sufficiently cheap incumbent prunes the same way).
752
- if (!worthRunning(2 * STEP))
753
- return 2 * STEP;
754
- // Now first-touch the shared analyses (climb, then the weave built on
755
- // it). If another mechanism already triggered either, this awaits the
756
- // cached result; otherwise it's computed once here and reused in run().
757
- if ((await pre.attention()).ranked.length < 2)
758
- return null;
759
- if ((await pre.weave()).points.length < 2)
760
- return null;
761
- return 2 * STEP;
762
- },
763
- async run(ctx, query, pre) {
764
- const casts = await counterfactualTransfer(ctx, query, pre);
765
- return casts.map((c) => ({
766
- bytes: c.bytes,
767
- accounted: c.accounted,
768
- moves: c.moves,
769
- used: c.used,
770
- unexplained: c.unexplained,
771
- }));
772
- },
773
- };