@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.
- package/AGENTS.md +115 -53
- package/HOW_IT_WORKS.md +291 -184
- package/jsr.json +6 -0
- package/package.json +1 -1
- package/src/mind/bridge.ts +10 -104
- package/src/mind/match.ts +497 -7
- package/src/mind/{prefix-completion.ts → mechanisms/prefix-completion.ts} +66 -92
- package/src/mind/mechanisms/recall.ts +38 -129
- package/src/mind/mechanisms/reference.ts +343 -0
- package/src/mind/mind.ts +12 -8
- package/src/mind/pipeline-mechanism.ts +120 -3
- package/src/mind/pipeline.ts +16 -2
- package/src/mind/traverse.ts +92 -1
- package/src/store.ts +13 -4
- package/test/33-multi-candidate.test.mjs +21 -11
- package/test/70-prefix-completion.test.mjs +1 -1
- package/test/72-prefix-candidate-supply.test.mjs +7 -9
- package/test/74-prefix-trap-not-sprung-early.test.mjs +1 -1
- package/test/76-reference-binding.test.mjs +543 -0
- package/dist/example/demo.d.ts +0 -1
- package/dist/example/demo.js +0 -39
- package/dist/example/train_base.d.ts +0 -93
- package/dist/example/train_base.js +0 -2269
- package/dist/src/alphabet.d.ts +0 -7
- package/dist/src/alphabet.js +0 -33
- package/dist/src/alu/src/alu.d.ts +0 -185
- package/dist/src/alu/src/alu.js +0 -440
- package/dist/src/alu/src/expr.d.ts +0 -61
- package/dist/src/alu/src/expr.js +0 -318
- package/dist/src/alu/src/index.d.ts +0 -11
- package/dist/src/alu/src/index.js +0 -19
- package/dist/src/alu/src/kernel-arith.d.ts +0 -16
- package/dist/src/alu/src/kernel-arith.js +0 -264
- package/dist/src/alu/src/kernel-bits.d.ts +0 -19
- package/dist/src/alu/src/kernel-bits.js +0 -152
- package/dist/src/alu/src/kernel-logic.d.ts +0 -4
- package/dist/src/alu/src/kernel-logic.js +0 -60
- package/dist/src/alu/src/kernel-nd.d.ts +0 -3
- package/dist/src/alu/src/kernel-nd.js +0 -208
- package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
- package/dist/src/alu/src/kernel-numeric.js +0 -366
- package/dist/src/alu/src/operation.d.ts +0 -168
- package/dist/src/alu/src/operation.js +0 -189
- package/dist/src/alu/src/parser.d.ts +0 -221
- package/dist/src/alu/src/parser.js +0 -577
- package/dist/src/alu/src/resonance.d.ts +0 -55
- package/dist/src/alu/src/resonance.js +0 -126
- package/dist/src/alu/src/text.d.ts +0 -31
- package/dist/src/alu/src/text.js +0 -73
- package/dist/src/alu/src/value.d.ts +0 -109
- package/dist/src/alu/src/value.js +0 -300
- package/dist/src/alu/test/alu.test.d.ts +0 -1
- package/dist/src/alu/test/alu.test.js +0 -764
- package/dist/src/bytes.d.ts +0 -14
- package/dist/src/bytes.js +0 -59
- package/dist/src/canon.d.ts +0 -45
- package/dist/src/canon.js +0 -85
- package/dist/src/config.d.ts +0 -111
- package/dist/src/config.js +0 -91
- package/dist/src/derive/src/deduction.d.ts +0 -136
- package/dist/src/derive/src/deduction.js +0 -159
- package/dist/src/derive/src/index.d.ts +0 -8
- package/dist/src/derive/src/index.js +0 -11
- package/dist/src/derive/src/priority-queue.d.ts +0 -20
- package/dist/src/derive/src/priority-queue.js +0 -73
- package/dist/src/derive/src/rewrite.d.ts +0 -56
- package/dist/src/derive/src/rewrite.js +0 -100
- package/dist/src/derive/src/trie.d.ts +0 -90
- package/dist/src/derive/src/trie.js +0 -217
- package/dist/src/derive/test/derive.test.d.ts +0 -1
- package/dist/src/derive/test/derive.test.js +0 -122
- package/dist/src/extension.d.ts +0 -37
- package/dist/src/extension.js +0 -7
- package/dist/src/geometry.d.ts +0 -233
- package/dist/src/geometry.js +0 -1089
- package/dist/src/index.d.ts +0 -17
- package/dist/src/index.js +0 -19
- package/dist/src/ingest-cache.d.ts +0 -41
- package/dist/src/ingest-cache.js +0 -165
- package/dist/src/meter.d.ts +0 -176
- package/dist/src/meter.js +0 -274
- package/dist/src/mind/articulation.d.ts +0 -6
- package/dist/src/mind/articulation.js +0 -99
- package/dist/src/mind/attention.d.ts +0 -430
- package/dist/src/mind/attention.js +0 -2418
- package/dist/src/mind/bridge.d.ts +0 -39
- package/dist/src/mind/bridge.js +0 -998
- package/dist/src/mind/canonical.d.ts +0 -34
- package/dist/src/mind/canonical.js +0 -93
- package/dist/src/mind/frame-filler.d.ts +0 -15
- package/dist/src/mind/frame-filler.js +0 -535
- package/dist/src/mind/graph-search.d.ts +0 -294
- package/dist/src/mind/graph-search.js +0 -996
- package/dist/src/mind/index.d.ts +0 -9
- package/dist/src/mind/index.js +0 -5
- package/dist/src/mind/junction.d.ts +0 -137
- package/dist/src/mind/junction.js +0 -358
- package/dist/src/mind/learning.d.ts +0 -75
- package/dist/src/mind/learning.js +0 -265
- package/dist/src/mind/match.d.ts +0 -192
- package/dist/src/mind/match.js +0 -666
- package/dist/src/mind/mechanisms/alu.d.ts +0 -4
- package/dist/src/mind/mechanisms/alu.js +0 -36
- package/dist/src/mind/mechanisms/cast.d.ts +0 -89
- package/dist/src/mind/mechanisms/cast.js +0 -1186
- package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
- package/dist/src/mind/mechanisms/confluence.js +0 -271
- package/dist/src/mind/mechanisms/cover.d.ts +0 -6
- package/dist/src/mind/mechanisms/cover.js +0 -232
- package/dist/src/mind/mechanisms/extraction.d.ts +0 -33
- package/dist/src/mind/mechanisms/extraction.js +0 -327
- package/dist/src/mind/mechanisms/recall.d.ts +0 -16
- package/dist/src/mind/mechanisms/recall.js +0 -558
- package/dist/src/mind/mind.d.ts +0 -386
- package/dist/src/mind/mind.js +0 -754
- package/dist/src/mind/pipeline-mechanism.d.ts +0 -183
- package/dist/src/mind/pipeline-mechanism.js +0 -521
- package/dist/src/mind/pipeline.d.ts +0 -49
- package/dist/src/mind/pipeline.js +0 -331
- package/dist/src/mind/prefix-completion.d.ts +0 -59
- package/dist/src/mind/prefix-completion.js +0 -270
- package/dist/src/mind/primitives.d.ts +0 -85
- package/dist/src/mind/primitives.js +0 -333
- package/dist/src/mind/rationale.d.ts +0 -139
- package/dist/src/mind/rationale.js +0 -163
- package/dist/src/mind/reasoning.d.ts +0 -42
- package/dist/src/mind/reasoning.js +0 -305
- package/dist/src/mind/recognition.d.ts +0 -20
- package/dist/src/mind/recognition.js +0 -696
- package/dist/src/mind/resonance.d.ts +0 -35
- package/dist/src/mind/resonance.js +0 -365
- package/dist/src/mind/trace.d.ts +0 -15
- package/dist/src/mind/trace.js +0 -73
- package/dist/src/mind/traverse.d.ts +0 -158
- package/dist/src/mind/traverse.js +0 -716
- package/dist/src/mind/types.d.ts +0 -418
- package/dist/src/mind/types.js +0 -179
- package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
- package/dist/src/rabitq-ivf/src/database.js +0 -201
- package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
- package/dist/src/rabitq-ivf/src/index.js +0 -4
- package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
- package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
- package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
- package/dist/src/rabitq-ivf/src/prng.js +0 -36
- package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
- package/dist/src/rabitq-ivf/src/rabitq.js +0 -313
- package/dist/src/sema.d.ts +0 -41
- package/dist/src/sema.js +0 -77
- package/dist/src/store-sqlite.d.ts +0 -184
- package/dist/src/store-sqlite.js +0 -942
- package/dist/src/store.d.ts +0 -762
- package/dist/src/store.js +0 -1823
- package/dist/src/vec.d.ts +0 -31
- package/dist/src/vec.js +0 -109
- package/src/mind/frame-filler.ts +0 -604
- 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
|
-
};
|