@hviana/sema 0.2.4 → 0.2.6
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/package.json +1 -1
- package/src/geometry.ts +52 -3
- package/src/mind/attention.ts +1469 -120
- package/src/mind/index.ts +16 -0
- package/src/mind/junction.ts +125 -16
- package/src/mind/mind.ts +15 -0
- package/src/mind/rationale.ts +12 -4
- package/src/mind/traverse.ts +75 -7
- package/src/mind/types.ts +25 -0
- package/test/51-structural-resonance-ladder.test.mjs +552 -0
- package/test/52-climb-consensus-instrumentation.test.mjs +324 -0
- package/test/53-cross-region-probe-instrumentation.test.mjs +356 -0
- package/dist/example/demo.d.ts +0 -1
- package/dist/example/demo.js +0 -39
- package/dist/example/train_base.d.ts +0 -87
- package/dist/example/train_base.js +0 -2252
- 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 -26
- package/dist/src/canon.js +0 -57
- package/dist/src/config.d.ts +0 -111
- package/dist/src/config.js +0 -91
- package/dist/src/derive/src/deduction.d.ts +0 -125
- package/dist/src/derive/src/deduction.js +0 -155
- package/dist/src/derive/src/index.d.ts +0 -7
- 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 -176
- package/dist/src/geometry.js +0 -546
- package/dist/src/index.d.ts +0 -16
- package/dist/src/index.js +0 -18
- package/dist/src/ingest-cache.d.ts +0 -41
- package/dist/src/ingest-cache.js +0 -161
- 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 -88
- package/dist/src/mind/attention.js +0 -997
- package/dist/src/mind/bridge.d.ts +0 -30
- package/dist/src/mind/bridge.js +0 -569
- package/dist/src/mind/canonical.d.ts +0 -29
- package/dist/src/mind/canonical.js +0 -88
- package/dist/src/mind/graph-search.d.ts +0 -285
- package/dist/src/mind/graph-search.js +0 -956
- package/dist/src/mind/index.d.ts +0 -5
- package/dist/src/mind/index.js +0 -5
- package/dist/src/mind/junction.d.ts +0 -95
- package/dist/src/mind/junction.js +0 -262
- package/dist/src/mind/learning.d.ts +0 -47
- package/dist/src/mind/learning.js +0 -248
- package/dist/src/mind/match.d.ts +0 -126
- package/dist/src/mind/match.js +0 -441
- 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 -716
- package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
- package/dist/src/mind/mechanisms/confluence.js +0 -225
- package/dist/src/mind/mechanisms/cover.d.ts +0 -6
- package/dist/src/mind/mechanisms/cover.js +0 -203
- package/dist/src/mind/mechanisms/extraction.d.ts +0 -67
- package/dist/src/mind/mechanisms/extraction.js +0 -387
- package/dist/src/mind/mechanisms/recall.d.ts +0 -13
- package/dist/src/mind/mechanisms/recall.js +0 -325
- package/dist/src/mind/mind.d.ts +0 -274
- package/dist/src/mind/mind.js +0 -572
- package/dist/src/mind/pipeline-mechanism.d.ts +0 -142
- package/dist/src/mind/pipeline-mechanism.js +0 -213
- package/dist/src/mind/pipeline.d.ts +0 -20
- package/dist/src/mind/pipeline.js +0 -217
- package/dist/src/mind/primitives.d.ts +0 -70
- package/dist/src/mind/primitives.js +0 -287
- package/dist/src/mind/rationale.d.ts +0 -134
- package/dist/src/mind/rationale.js +0 -162
- package/dist/src/mind/reasoning.d.ts +0 -34
- package/dist/src/mind/reasoning.js +0 -240
- package/dist/src/mind/recognition.d.ts +0 -20
- package/dist/src/mind/recognition.js +0 -390
- package/dist/src/mind/resonance.d.ts +0 -23
- package/dist/src/mind/resonance.js +0 -0
- 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 -113
- package/dist/src/mind/traverse.js +0 -504
- package/dist/src/mind/types.d.ts +0 -271
- package/dist/src/mind/types.js +0 -130
- 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 -283
- package/dist/src/sema.d.ts +0 -31
- package/dist/src/sema.js +0 -63
- package/dist/src/store-sqlite.d.ts +0 -184
- package/dist/src/store-sqlite.js +0 -942
- package/dist/src/store.d.ts +0 -663
- package/dist/src/store.js +0 -1618
- package/dist/src/vec.d.ts +0 -31
- package/dist/src/vec.js +0 -109
package/src/mind/attention.ts
CHANGED
|
@@ -25,23 +25,405 @@ import type {
|
|
|
25
25
|
Region,
|
|
26
26
|
RegionVote,
|
|
27
27
|
SaturationInfo,
|
|
28
|
+
SaturationStop,
|
|
28
29
|
} from "./types.js";
|
|
29
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
composeStructuralGist,
|
|
32
|
+
consensusFloor,
|
|
33
|
+
dominates,
|
|
34
|
+
estimatorNoise,
|
|
35
|
+
type StructuralPart,
|
|
36
|
+
} from "../geometry.js";
|
|
30
37
|
import { foldTree, gistOf, latin1Key, perceive, read } from "./primitives.js";
|
|
31
38
|
import { recognise } from "./recognition.js";
|
|
32
39
|
import { leafIdRun } from "./canonical.js";
|
|
33
|
-
import { corpusN, edgeAncestors } from "./traverse.js";
|
|
40
|
+
import { corpusN, edgeAncestors, hubBound } from "./traverse.js";
|
|
34
41
|
import {
|
|
35
42
|
cachedRead,
|
|
43
|
+
type Junction,
|
|
36
44
|
junctionContainersFrom,
|
|
37
45
|
junctionSeeds,
|
|
38
46
|
junctionSynonyms,
|
|
47
|
+
type JunctionSynonymSides,
|
|
48
|
+
loadJunctionSynonymSides,
|
|
49
|
+
type SynonymJunction,
|
|
39
50
|
walkCache,
|
|
40
51
|
} from "./junction.js";
|
|
52
|
+
import type { Vec } from "../vec.js";
|
|
41
53
|
import { indexOf } from "../bytes.js";
|
|
42
54
|
import type { RationaleItem } from "./rationale.js";
|
|
43
55
|
import { rDeriv, rItem, rNode, traceDerivation } from "./trace.js";
|
|
44
56
|
|
|
57
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
58
|
+
// climbConsensus / inspectRationale instrumentation.
|
|
59
|
+
//
|
|
60
|
+
// Purely additive tracing over the consensus climb: it never changes an
|
|
61
|
+
// inference result or the human-readable rationale text traceAttention
|
|
62
|
+
// already produces (see below) — it only exposes, as one structured `data`
|
|
63
|
+
// payload on the SAME "climbConsensus" step, the machinery that produced
|
|
64
|
+
// that text: every structural saturation stop, candidate breadth versus
|
|
65
|
+
// evidence that actually contributed, and the decisions that removed or
|
|
66
|
+
// accepted evidence (§ objective of the instrumentation spec).
|
|
67
|
+
//
|
|
68
|
+
// The mutable collection buffers (the `TraceDraft` below) are allocated ONLY
|
|
69
|
+
// when `ctx.trace` is set — every call site that would otherwise push onto
|
|
70
|
+
// one of these arrays or sets is gated by `td?.` / `if (td)`, so a plain
|
|
71
|
+
// (untraced) climb pays exactly zero allocation for this instrumentation.
|
|
72
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
73
|
+
|
|
74
|
+
/** How the newly-added graded junction ladder (junction.ts / attention.ts's
|
|
75
|
+
* {@link CrossRegionTier}) is reported on a `junctionVotes` entry. The
|
|
76
|
+
* instrumentation spec this implements predates that ladder and only knew
|
|
77
|
+
* two tiers ("exact" | "synonym"); with the richer `CrossRegionTier` now
|
|
78
|
+
* the real shape of a junction vote's provenance, `junctionVotes[].tier`
|
|
79
|
+
* reports it DIRECTLY (the tier as-is: "exact" | "single-synonym" |
|
|
80
|
+
* "double-synonym" | "structural-resonance") rather than collapsing every
|
|
81
|
+
* non-exact tier into a lossy "synonym" bucket — the whole point of
|
|
82
|
+
* exposing `tier` here is to let a debugger tell a halo-sibling
|
|
83
|
+
* substitution apart from a structural-resonance ANN guess, which the
|
|
84
|
+
* spec's original two-value type cannot do. */
|
|
85
|
+
export type ClimbConsensusJunctionTier = CrossRegionTier;
|
|
86
|
+
|
|
87
|
+
export type RegionOutcome =
|
|
88
|
+
| "voted"
|
|
89
|
+
| "no-ann-hit"
|
|
90
|
+
| "no-structural-reach"
|
|
91
|
+
| "saturated-abstention"
|
|
92
|
+
| "nonpositive-df-weight"
|
|
93
|
+
| "contrastive-margin-rejection";
|
|
94
|
+
|
|
95
|
+
/** The best DIFFERENT-conclusion rival the contrastive-margin gate found
|
|
96
|
+
* while scanning an ordinary (approximate) region's ANN hits — spec §1.
|
|
97
|
+
* Its roots/saturation/contextsReached are already available through
|
|
98
|
+
* `reaches` (serialiseReaches) for `node`; not duplicated here. */
|
|
99
|
+
export interface ConsensusContrastiveRivalTrace {
|
|
100
|
+
node: number;
|
|
101
|
+
rank: number;
|
|
102
|
+
score: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface ConsensusRegionTrace {
|
|
106
|
+
index: number;
|
|
107
|
+
source: "perceived" | "recognised";
|
|
108
|
+
span: [number, number];
|
|
109
|
+
chunk: boolean;
|
|
110
|
+
known: boolean;
|
|
111
|
+
|
|
112
|
+
canonicalId?: number;
|
|
113
|
+
canonicalUsable: boolean;
|
|
114
|
+
canonicalFailed: boolean;
|
|
115
|
+
|
|
116
|
+
annQueried: boolean;
|
|
117
|
+
annHitsReturned: number;
|
|
118
|
+
annHitsExamined: number;
|
|
119
|
+
|
|
120
|
+
selected?: {
|
|
121
|
+
source: "canonical" | "ann";
|
|
122
|
+
node: number;
|
|
123
|
+
rank?: number;
|
|
124
|
+
score: number;
|
|
125
|
+
fallback?: "orphan" | "saturated-tie";
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
reachNode?: number;
|
|
129
|
+
outcome: RegionOutcome;
|
|
130
|
+
|
|
131
|
+
idf?: number;
|
|
132
|
+
dfWeight?: number;
|
|
133
|
+
|
|
134
|
+
contrastiveMargin?: number;
|
|
135
|
+
contrastiveNoiseFloor?: number;
|
|
136
|
+
contrastiveRival?: ConsensusContrastiveRivalTrace;
|
|
137
|
+
|
|
138
|
+
mutualWeight?: number;
|
|
139
|
+
voteWeightPerRoot?: number;
|
|
140
|
+
focusWeightPerRoot?: number;
|
|
141
|
+
|
|
142
|
+
ordinaryVoteProduced: boolean;
|
|
143
|
+
superseded: boolean;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface ConsensusReachTrace {
|
|
147
|
+
node: number;
|
|
148
|
+
roots: number[];
|
|
149
|
+
contextsReached: number;
|
|
150
|
+
saturated: boolean;
|
|
151
|
+
saturation?: SaturationStop;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export type AnchorRejectionReason =
|
|
155
|
+
| "below-natural-break"
|
|
156
|
+
| "below-consensus-floor"
|
|
157
|
+
| "leading-saturation";
|
|
158
|
+
|
|
159
|
+
export interface ConsensusAnchorTrace {
|
|
160
|
+
anchor: number;
|
|
161
|
+
rank: number;
|
|
162
|
+
|
|
163
|
+
pooledVote: number;
|
|
164
|
+
idfVote: number;
|
|
165
|
+
|
|
166
|
+
candidateBreadth: number;
|
|
167
|
+
contributingVotes: number;
|
|
168
|
+
contributingEvidence: number;
|
|
169
|
+
breadth: number;
|
|
170
|
+
contributingSpans: Array<[number, number]>;
|
|
171
|
+
clusters: number;
|
|
172
|
+
|
|
173
|
+
commit: {
|
|
174
|
+
status: "root" | "overlap" | "rejected";
|
|
175
|
+
dominant: boolean;
|
|
176
|
+
passesNaturalBreak?: boolean;
|
|
177
|
+
passesConsensusFloor?: boolean;
|
|
178
|
+
pastLeadingSaturation?: boolean;
|
|
179
|
+
rejectionReasons: AnchorRejectionReason[];
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface JunctionVoteTrace {
|
|
184
|
+
container: number;
|
|
185
|
+
span: [number, number];
|
|
186
|
+
roots: number[];
|
|
187
|
+
sourceRegionIndices: number[];
|
|
188
|
+
explainedAwayRegionIndices: number[];
|
|
189
|
+
absorbed: number;
|
|
190
|
+
tier?: ClimbConsensusJunctionTier;
|
|
191
|
+
|
|
192
|
+
/** Zero-based index into `crossRegion.probes` — the probe this vote was
|
|
193
|
+
* produced from (spec §8). */
|
|
194
|
+
probe: number;
|
|
195
|
+
confidence: number;
|
|
196
|
+
/** "Evidence bytes" — the container-coverage byte count (the existing
|
|
197
|
+
* `bestCov` variable at the push site). */
|
|
198
|
+
evidenceBytes: number;
|
|
199
|
+
mutualWeight: number;
|
|
200
|
+
voteWeightPerRoot: number;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Whether one DAG/synonym tier attempt was even made for a probe, and how
|
|
204
|
+
* many candidate containers it returned — spec §2/§3. */
|
|
205
|
+
export interface CrossRegionTierAttemptTrace {
|
|
206
|
+
attempted: boolean;
|
|
207
|
+
candidatesReturned: number;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Aggregate outcome of the container-selection loop for a DAG/synonym tier
|
|
211
|
+
* that returned at least one container — spec §4. Only aggregate counts
|
|
212
|
+
* and the final outcome are recorded, never every candidate. */
|
|
213
|
+
export interface CrossRegionStructuralTrace {
|
|
214
|
+
tier: "exact" | "single-synonym" | "double-synonym";
|
|
215
|
+
selfEvidenceRejected: number;
|
|
216
|
+
contradictionRejected: number;
|
|
217
|
+
passedGuards: number;
|
|
218
|
+
selectedNode?: number;
|
|
219
|
+
outcome:
|
|
220
|
+
| "all-rejected"
|
|
221
|
+
| "saturated"
|
|
222
|
+
| "no-roots"
|
|
223
|
+
| "nonpositive-idf"
|
|
224
|
+
| "accepted";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** One retained structural-resonance variant that actually issued its own
|
|
228
|
+
* ANN query — spec §5. */
|
|
229
|
+
export interface StructuralResonanceVariantTrace {
|
|
230
|
+
kind: StructuralVariant["kind"];
|
|
231
|
+
semanticConfidence: number;
|
|
232
|
+
leftSiblingId?: number;
|
|
233
|
+
rightSiblingId?: number;
|
|
234
|
+
annHitsReturned: number;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** One merged structural-resonance proposal actually examined via
|
|
238
|
+
* edgeAncestors — spec §5. Retains node/variant/scores, but NOT
|
|
239
|
+
* roots/saturation/contextsReached/idf (already in `reaches`). */
|
|
240
|
+
export interface StructuralResonanceCandidateTrace {
|
|
241
|
+
node: number;
|
|
242
|
+
variant: StructuralVariant["kind"];
|
|
243
|
+
leftSiblingId?: number;
|
|
244
|
+
rightSiblingId?: number;
|
|
245
|
+
annScore: number;
|
|
246
|
+
semanticConfidence: number;
|
|
247
|
+
effectiveScore: number;
|
|
248
|
+
outcome:
|
|
249
|
+
| "saturated"
|
|
250
|
+
| "no-roots"
|
|
251
|
+
| "nonpositive-idf"
|
|
252
|
+
| "same-as-endpoint"
|
|
253
|
+
| "same-as-selected"
|
|
254
|
+
| "selected"
|
|
255
|
+
| "contrastive-rival";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface StructuralResonanceTrace {
|
|
259
|
+
variantBudget: number;
|
|
260
|
+
variants: StructuralResonanceVariantTrace[];
|
|
261
|
+
mergedProposals: number;
|
|
262
|
+
examined: StructuralResonanceCandidateTrace[];
|
|
263
|
+
contrastiveMargin?: number;
|
|
264
|
+
noiseFloor: number;
|
|
265
|
+
outcome:
|
|
266
|
+
| "ineligible"
|
|
267
|
+
| "empty"
|
|
268
|
+
| "no-valid-proposal"
|
|
269
|
+
| "margin-rejected"
|
|
270
|
+
| "accepted";
|
|
271
|
+
ineligibleReasons?: Array<
|
|
272
|
+
"between-region" | "not-both-strong" | "not-both-known" | "gap-too-large"
|
|
273
|
+
>;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** One cross-region pair the ladder actually probed — spec §2. Exactly one
|
|
277
|
+
* of these is pushed per pair that incremented `probes`. */
|
|
278
|
+
export interface CrossRegionProbeTrace {
|
|
279
|
+
leftRegionIndex: number;
|
|
280
|
+
rightRegionIndex: number;
|
|
281
|
+
betweenRegionIndices: number[];
|
|
282
|
+
exact: CrossRegionTierAttemptTrace;
|
|
283
|
+
singleSynonym: CrossRegionTierAttemptTrace;
|
|
284
|
+
doubleSynonym: CrossRegionTierAttemptTrace;
|
|
285
|
+
structural?: CrossRegionStructuralTrace;
|
|
286
|
+
resonance?: StructuralResonanceTrace;
|
|
287
|
+
outcome:
|
|
288
|
+
| "accepted"
|
|
289
|
+
| "structural-rejected"
|
|
290
|
+
| "resonance-ineligible"
|
|
291
|
+
| "resonance-rejected";
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export interface ClimbConsensusData {
|
|
295
|
+
version: 1;
|
|
296
|
+
|
|
297
|
+
cache: {
|
|
298
|
+
hit: boolean;
|
|
299
|
+
detailAvailable: boolean;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
config: {
|
|
303
|
+
annK: number;
|
|
304
|
+
crossRegionProbeLimit: number;
|
|
305
|
+
mode: DFMode;
|
|
306
|
+
corpusN?: number;
|
|
307
|
+
dimension?: number;
|
|
308
|
+
hubBound?: number;
|
|
309
|
+
estimatorNoise?: number;
|
|
310
|
+
naturalBreak?: number;
|
|
311
|
+
consensusFloor?: number;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
candidates: {
|
|
315
|
+
perceived: number;
|
|
316
|
+
recognised: number;
|
|
317
|
+
total: number;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
regions?: ConsensusRegionTrace[];
|
|
321
|
+
reaches?: ConsensusReachTrace[];
|
|
322
|
+
|
|
323
|
+
crossRegion?: {
|
|
324
|
+
eligibleRegions: number;
|
|
325
|
+
maximalRegions: number;
|
|
326
|
+
probeLimit: number;
|
|
327
|
+
probesAttempted: number;
|
|
328
|
+
junctionVotes: JunctionVoteTrace[];
|
|
329
|
+
supersededOrdinaryVotes: number;
|
|
330
|
+
probes: CrossRegionProbeTrace[];
|
|
331
|
+
stopReason: "insufficient-regions" | "probe-limit" | "pairs-exhausted";
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
saturation?: {
|
|
335
|
+
regionIntervals: Array<{ start: number; end: number }>;
|
|
336
|
+
hasLeading: boolean;
|
|
337
|
+
leadingEnd: number;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
pooling?: {
|
|
341
|
+
inputVotes: number;
|
|
342
|
+
eligibleVotes: number;
|
|
343
|
+
saturationMaskedVotes: number;
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
anchors?: ConsensusAnchorTrace[];
|
|
347
|
+
|
|
348
|
+
result: AttentionRead;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** The mutable collection buffers threaded through one traced consensus
|
|
352
|
+
* climb — allocated exactly once, in {@link computeAttention}, only when
|
|
353
|
+
* `ctx.trace` is set. Every field mirrors a `ClimbConsensusData` array/map,
|
|
354
|
+
* built incrementally as the pipeline runs so commit-time decisions (in
|
|
355
|
+
* particular) are recorded LIVE, not reconstructed afterward. */
|
|
356
|
+
interface TraceDraft {
|
|
357
|
+
perceivedCount: number;
|
|
358
|
+
regions: ConsensusRegionTrace[];
|
|
359
|
+
crossRegionJunctionVotes: JunctionVoteTrace[];
|
|
360
|
+
crossRegionSummary?: {
|
|
361
|
+
eligibleRegions: number;
|
|
362
|
+
maximalRegions: number;
|
|
363
|
+
probeLimit: number;
|
|
364
|
+
probesAttempted: number;
|
|
365
|
+
stopReason?: "insufficient-regions" | "probe-limit" | "pairs-exhausted";
|
|
366
|
+
};
|
|
367
|
+
crossRegionProbes: CrossRegionProbeTrace[];
|
|
368
|
+
supersededOrdinaryVotes: number;
|
|
369
|
+
saturation?: {
|
|
370
|
+
regionIntervals: Array<{ start: number; end: number }>;
|
|
371
|
+
hasLeading: boolean;
|
|
372
|
+
leadingEnd: number;
|
|
373
|
+
};
|
|
374
|
+
pooling?: {
|
|
375
|
+
inputVotes: number;
|
|
376
|
+
eligibleVotes: number;
|
|
377
|
+
saturationMaskedVotes: number;
|
|
378
|
+
};
|
|
379
|
+
anchors: ConsensusAnchorTrace[];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** The config/corpus context {@link traceAttention} needs to fill in
|
|
383
|
+
* `ClimbConsensusData.config` and `.result` at whichever exit fires —
|
|
384
|
+
* threaded down from {@link computeAttention} rather than re-derived, so
|
|
385
|
+
* every emission point reports the SAME numbers the real climb used. */
|
|
386
|
+
interface ClimbConsensusCfg {
|
|
387
|
+
k: number;
|
|
388
|
+
mode: DFMode;
|
|
389
|
+
perceivedCount: number;
|
|
390
|
+
totalRegions: number;
|
|
391
|
+
N?: number;
|
|
392
|
+
reachMemo?: ReadonlyMap<number, AncestorReach>;
|
|
393
|
+
naturalBreak?: number;
|
|
394
|
+
consensusFloor?: number;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function newTraceDraft(perceivedCount: number): TraceDraft {
|
|
398
|
+
return {
|
|
399
|
+
perceivedCount,
|
|
400
|
+
regions: [],
|
|
401
|
+
crossRegionJunctionVotes: [],
|
|
402
|
+
crossRegionProbes: [],
|
|
403
|
+
supersededOrdinaryVotes: 0,
|
|
404
|
+
anchors: [],
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** Serialise the shared `reachMemo` into the plain, authoritative saturation
|
|
409
|
+
* profile (spec §5) — every distinct node any tier's `edgeAncestors` call
|
|
410
|
+
* climbed from during this response, in insertion (first-consulted) order. */
|
|
411
|
+
function serialiseReaches(
|
|
412
|
+
reachMemo: ReadonlyMap<number, AncestorReach>,
|
|
413
|
+
): ConsensusReachTrace[] {
|
|
414
|
+
const out: ConsensusReachTrace[] = [];
|
|
415
|
+
for (const [node, r] of reachMemo) {
|
|
416
|
+
out.push({
|
|
417
|
+
node,
|
|
418
|
+
roots: [...r.roots],
|
|
419
|
+
contextsReached: r.contextsReached,
|
|
420
|
+
saturated: r.saturated,
|
|
421
|
+
...(r.saturation ? { saturation: r.saturation } : {}),
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return out;
|
|
425
|
+
}
|
|
426
|
+
|
|
45
427
|
// ── Public entry points ───────────────────────────────────────────────────
|
|
46
428
|
|
|
47
429
|
/** Climb the query's perceived byte regions up the structural DAG via
|
|
@@ -85,12 +467,27 @@ export async function climbAttentionAll(
|
|
|
85
467
|
}
|
|
86
468
|
const hit = byRead.get(modeKey);
|
|
87
469
|
if (hit !== undefined) {
|
|
470
|
+
// Cache-hit exit (spec §9): the abbreviated payload shape — only what
|
|
471
|
+
// is actually stored in the cached AttentionRead is reported. No
|
|
472
|
+
// candidate, reach, saturation, pooling or anchor detail is fabricated
|
|
473
|
+
// (that per-region detail was never retained by the memo).
|
|
474
|
+
const data: ClimbConsensusData | undefined = ctx.trace
|
|
475
|
+
? {
|
|
476
|
+
version: 1,
|
|
477
|
+
cache: { hit: true, detailAvailable: false },
|
|
478
|
+
config: { annK: k, crossRegionProbeLimit: k, mode },
|
|
479
|
+
candidates: { perceived: 0, recognised: 0, total: 0 },
|
|
480
|
+
result: hit,
|
|
481
|
+
}
|
|
482
|
+
: undefined;
|
|
88
483
|
ctx.trace?.step(
|
|
89
484
|
"climbConsensus",
|
|
90
485
|
[rItem(query, "query")],
|
|
91
486
|
hit.roots.map((r) => rNode(ctx, r.anchor, "anchor", r.vote)),
|
|
92
487
|
`(cached) consensus already computed for this query — ` +
|
|
93
488
|
`${hit.roots.length} point(s) of attention`,
|
|
489
|
+
undefined,
|
|
490
|
+
data,
|
|
94
491
|
);
|
|
95
492
|
return hit;
|
|
96
493
|
}
|
|
@@ -110,6 +507,7 @@ export async function computeAttention(
|
|
|
110
507
|
mode: DFMode,
|
|
111
508
|
): Promise<AttentionRead> {
|
|
112
509
|
const regions = collectRegions(ctx, query);
|
|
510
|
+
const perceivedCount = regions.length;
|
|
113
511
|
|
|
114
512
|
// Recognised sites carry structural evidence that perceived sub-regions
|
|
115
513
|
// miss: a word crossing a W-boundary is split into chunks whose partial
|
|
@@ -130,14 +528,30 @@ export async function computeAttention(
|
|
|
130
528
|
});
|
|
131
529
|
}
|
|
132
530
|
|
|
133
|
-
|
|
531
|
+
// The trace draft (spec §9): allocated ONLY when a trace was requested —
|
|
532
|
+
// every downstream consumer gates its own writes on `td?` / `if (td)`, so
|
|
533
|
+
// an untraced climb pays zero allocation for this instrumentation.
|
|
534
|
+
const td: TraceDraft | undefined = ctx.trace
|
|
535
|
+
? newTraceDraft(perceivedCount)
|
|
536
|
+
: undefined;
|
|
537
|
+
const cfg0: ClimbConsensusCfg = {
|
|
538
|
+
k,
|
|
539
|
+
mode,
|
|
540
|
+
perceivedCount,
|
|
541
|
+
totalRegions: regions.length,
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
if (regions.length === 0) {
|
|
545
|
+
traceAttention(ctx, [], [], [], undefined, td, cfg0);
|
|
546
|
+
return { roots: [], ranked: [] };
|
|
547
|
+
}
|
|
134
548
|
|
|
135
549
|
const N = corpusN(ctx);
|
|
136
550
|
// One climb per distinct anchor for the WHOLE query: regions sharing a
|
|
137
551
|
// chunk, and canonicalChunkId's prefix probes, all hit this memo instead of
|
|
138
552
|
// re-reading the anchor's full edge fan-out from the store.
|
|
139
553
|
const reachMemo = new Map<number, AncestorReach>();
|
|
140
|
-
const rvs = await voteRegions(ctx, query, regions, k, mode, N, reachMemo);
|
|
554
|
+
const rvs = await voteRegions(ctx, query, regions, k, mode, N, reachMemo, td);
|
|
141
555
|
|
|
142
556
|
// ── Cross-region: DIRECT region-to-region interaction ─────────────────
|
|
143
557
|
// Two regions whose individual climbs land on DIFFERENT contexts leave
|
|
@@ -152,6 +566,7 @@ export async function computeAttention(
|
|
|
152
566
|
k,
|
|
153
567
|
N,
|
|
154
568
|
reachMemo,
|
|
569
|
+
td,
|
|
155
570
|
);
|
|
156
571
|
// A vote SUPERSEDED by exact joint evidence (its bytes literally live
|
|
157
572
|
// inside the joint container, yet it climbed elsewhere — grid aliasing)
|
|
@@ -162,16 +577,37 @@ export async function computeAttention(
|
|
|
162
577
|
...cross.votes,
|
|
163
578
|
]
|
|
164
579
|
: rvs.votes;
|
|
580
|
+
// Mark, on the per-region trace, the source region of every superseded
|
|
581
|
+
// ordinary vote (spec §4's final rule) — an explicit pass over the exact
|
|
582
|
+
// set crossRegionVotes' explaining-away logic removed, never inferred
|
|
583
|
+
// from `absorbed`.
|
|
584
|
+
if (td && cross.superseded.size > 0) {
|
|
585
|
+
for (const rv of cross.superseded) {
|
|
586
|
+
const region = td.regions.find(
|
|
587
|
+
(r) => r.span[0] === rv.start && r.span[1] === rv.end,
|
|
588
|
+
);
|
|
589
|
+
if (region) region.superseded = true;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
165
592
|
// ──────────────────────────────────────────────────────────────────────
|
|
166
593
|
|
|
594
|
+
const cfg: ClimbConsensusCfg = { ...cfg0, N, reachMemo };
|
|
595
|
+
|
|
167
596
|
if (allVotes.length === 0) {
|
|
168
|
-
traceAttention(ctx, regions, rvs.voters, []);
|
|
597
|
+
traceAttention(ctx, regions, rvs.voters, [], undefined, td, cfg);
|
|
169
598
|
return { roots: [], ranked: [] };
|
|
170
599
|
}
|
|
171
600
|
|
|
172
601
|
const sat = detectSaturated(ctx, regions, rvs.saturated);
|
|
173
|
-
|
|
174
|
-
|
|
602
|
+
if (td) {
|
|
603
|
+
td.saturation = {
|
|
604
|
+
regionIntervals: sat.intervals.map((iv) => ({ ...iv })),
|
|
605
|
+
hasLeading: sat.hasLeading,
|
|
606
|
+
leadingEnd: sat.leadingEnd,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
const pooled = poolVotes(ctx, allVotes, sat, N, td);
|
|
610
|
+
return commitVotes(ctx, pooled, sat, regions, rvs.voters, N, td, cfg);
|
|
175
611
|
}
|
|
176
612
|
|
|
177
613
|
export function collectRegions(ctx: MindContext, query: Uint8Array): Region[] {
|
|
@@ -212,6 +648,7 @@ export async function voteRegions(
|
|
|
212
648
|
mode: DFMode,
|
|
213
649
|
N: number,
|
|
214
650
|
reachMemo?: Map<number, AncestorReach>,
|
|
651
|
+
td?: TraceDraft,
|
|
215
652
|
): Promise<{
|
|
216
653
|
votes: RegionVote[];
|
|
217
654
|
saturated: boolean[];
|
|
@@ -224,6 +661,37 @@ export async function voteRegions(
|
|
|
224
661
|
|
|
225
662
|
for (let ri = 0; ri < regions.length; ri++) {
|
|
226
663
|
const { v, start, end, chunk, known } = regions[ri];
|
|
664
|
+
// Trace-only bookkeeping for this region — allocated only under `td`
|
|
665
|
+
// (i.e. only when ctx.trace is set); see ConsensusRegionTrace/
|
|
666
|
+
// RegionOutcome (spec §4). `examinedIds` tracks distinct ANN hits
|
|
667
|
+
// whose edgeAncestors reach was actually CONSULTED here (not merely
|
|
668
|
+
// returned by resonate) — the fallback/margin loops below add to it.
|
|
669
|
+
const examinedIds = td ? new Set<number>() : undefined;
|
|
670
|
+
let annQueried = false;
|
|
671
|
+
let fallbackKind: "orphan" | "saturated-tie" | undefined;
|
|
672
|
+
const recordRegion = (
|
|
673
|
+
outcome: RegionOutcome,
|
|
674
|
+
extra: Partial<ConsensusRegionTrace> = {},
|
|
675
|
+
) => {
|
|
676
|
+
if (!td) return;
|
|
677
|
+
td.regions[ri] = {
|
|
678
|
+
index: ri,
|
|
679
|
+
source: ri < td.perceivedCount ? "perceived" : "recognised",
|
|
680
|
+
span: [start, end],
|
|
681
|
+
chunk,
|
|
682
|
+
known,
|
|
683
|
+
canonicalId: canonicalId ?? undefined,
|
|
684
|
+
canonicalUsable,
|
|
685
|
+
canonicalFailed,
|
|
686
|
+
annQueried,
|
|
687
|
+
annHitsReturned: hits ? hits.length : 0,
|
|
688
|
+
annHitsExamined: examinedIds ? examinedIds.size : 0,
|
|
689
|
+
outcome,
|
|
690
|
+
ordinaryVoteProduced: outcome === "voted",
|
|
691
|
+
superseded: false,
|
|
692
|
+
...extra,
|
|
693
|
+
};
|
|
694
|
+
};
|
|
227
695
|
|
|
228
696
|
// EXACT-FIRST: a chunk whose canonical anchor is content-addressed needs
|
|
229
697
|
// no estimator — identity is exact, so its score is 1 BY DEFINITION (the
|
|
@@ -243,23 +711,35 @@ export async function voteRegions(
|
|
|
243
711
|
(ctx.store.hasParents(canonicalId) ||
|
|
244
712
|
ctx.store.hasContainers(canonicalId));
|
|
245
713
|
let hits: readonly Hit[] | null = null;
|
|
246
|
-
const ensureHits = async (): Promise<readonly Hit[]> =>
|
|
247
|
-
hits
|
|
714
|
+
const ensureHits = async (): Promise<readonly Hit[]> => {
|
|
715
|
+
if (hits === null) {
|
|
716
|
+
hits = await ctx.store.resonate(v, k);
|
|
717
|
+
annQueried = true;
|
|
718
|
+
}
|
|
719
|
+
return hits;
|
|
720
|
+
};
|
|
248
721
|
|
|
249
722
|
const canonicalFailed = chunk && canonicalId === null;
|
|
250
723
|
let voterId: number;
|
|
251
724
|
let score: number;
|
|
252
725
|
let scoreId: number; // the node the score was measured against
|
|
726
|
+
let selectedSource: "canonical" | "ann";
|
|
253
727
|
if (canonicalUsable) {
|
|
254
728
|
voterId = canonicalId!;
|
|
255
729
|
score = 1;
|
|
256
730
|
scoreId = canonicalId!;
|
|
731
|
+
selectedSource = "canonical";
|
|
257
732
|
} else {
|
|
258
733
|
const h = await ensureHits();
|
|
259
|
-
if (h.length === 0)
|
|
734
|
+
if (h.length === 0) {
|
|
735
|
+
recordRegion("no-ann-hit");
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
260
738
|
voterId = h[0].id;
|
|
261
739
|
score = h[0].score;
|
|
262
740
|
scoreId = h[0].id;
|
|
741
|
+
selectedSource = "ann";
|
|
742
|
+
examinedIds?.add(voterId);
|
|
263
743
|
}
|
|
264
744
|
|
|
265
745
|
let reach = edgeAncestors(ctx, voterId, N, reachMemo);
|
|
@@ -273,6 +753,7 @@ export async function voteRegions(
|
|
|
273
753
|
for (const h of await ensureHits()) {
|
|
274
754
|
if (h.id === voterId) continue;
|
|
275
755
|
const r2 = edgeAncestors(ctx, h.id, N, reachMemo);
|
|
756
|
+
examinedIds?.add(h.id);
|
|
276
757
|
if (r2.saturated || r2.roots.length > 0) {
|
|
277
758
|
ctx.trace?.step(
|
|
278
759
|
"anchorFallback",
|
|
@@ -284,6 +765,8 @@ export async function voteRegions(
|
|
|
284
765
|
voterId = h.id;
|
|
285
766
|
score = h.score;
|
|
286
767
|
scoreId = h.id;
|
|
768
|
+
selectedSource = "ann";
|
|
769
|
+
fallbackKind = "orphan";
|
|
287
770
|
break;
|
|
288
771
|
}
|
|
289
772
|
}
|
|
@@ -307,6 +790,7 @@ export async function voteRegions(
|
|
|
307
790
|
if (h.id === voterId) continue;
|
|
308
791
|
if (h.score < score - band) break; // hits are nearest-first
|
|
309
792
|
const r2 = edgeAncestors(ctx, h.id, N, reachMemo);
|
|
793
|
+
examinedIds?.add(h.id);
|
|
310
794
|
if (!r2.saturated && r2.roots.length > 0) {
|
|
311
795
|
ctx.trace?.step(
|
|
312
796
|
"anchorFallback",
|
|
@@ -318,20 +802,52 @@ export async function voteRegions(
|
|
|
318
802
|
voterId = h.id;
|
|
319
803
|
score = h.score;
|
|
320
804
|
scoreId = h.id;
|
|
805
|
+
selectedSource = "ann";
|
|
806
|
+
fallbackKind = "saturated-tie";
|
|
321
807
|
break;
|
|
322
808
|
}
|
|
323
809
|
}
|
|
324
810
|
}
|
|
325
811
|
regionSaturated[ri] = reach.saturated;
|
|
326
|
-
|
|
327
|
-
|
|
812
|
+
const selected: ConsensusRegionTrace["selected"] | undefined = !td
|
|
813
|
+
? undefined
|
|
814
|
+
: (() => {
|
|
815
|
+
const rank: number | undefined = selectedSource === "ann"
|
|
816
|
+
? (hits as readonly Hit[] | null)?.findIndex((h: Hit) =>
|
|
817
|
+
h.id === voterId
|
|
818
|
+
)
|
|
819
|
+
: undefined;
|
|
820
|
+
return {
|
|
821
|
+
source: selectedSource,
|
|
822
|
+
node: voterId,
|
|
823
|
+
score,
|
|
824
|
+
...(rank !== undefined ? { rank } : {}),
|
|
825
|
+
...(fallbackKind ? { fallback: fallbackKind } : {}),
|
|
826
|
+
};
|
|
827
|
+
})();
|
|
828
|
+
if (reach.roots.length === 0) {
|
|
829
|
+
recordRegion("no-structural-reach", { selected, reachNode: voterId });
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
if (reach.saturated) {
|
|
833
|
+
recordRegion("saturated-abstention", { selected, reachNode: voterId });
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
328
836
|
|
|
329
837
|
// One IDF per region — dfWeight() and the focus weight used to compute
|
|
330
838
|
// the same logarithm independently.
|
|
331
839
|
const idf = Math.log(N / Math.max(1, reach.contextsReached));
|
|
332
840
|
const df = Math.log(1 + reach.contextsReached);
|
|
333
841
|
const wf = mode === "direct" ? df : mode === "combined" ? idf + df : idf;
|
|
334
|
-
if (wf <= 0)
|
|
842
|
+
if (wf <= 0) {
|
|
843
|
+
recordRegion("nonpositive-df-weight", {
|
|
844
|
+
selected,
|
|
845
|
+
reachNode: voterId,
|
|
846
|
+
idf,
|
|
847
|
+
dfWeight: wf,
|
|
848
|
+
});
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
335
851
|
|
|
336
852
|
// CONTRASTIVE-MARGIN GATE — the compensation the linear (byte-proportional)
|
|
337
853
|
// fold demands, applied to APPROXIMATE evidence only. Under the linear
|
|
@@ -356,17 +872,38 @@ export async function voteRegions(
|
|
|
356
872
|
// (reordered / paraphrased queries) below the floor so they grounded
|
|
357
873
|
// nothing. Gating at the noise floor keeps frame-echo suppression (a frame
|
|
358
874
|
// region's margin ≈ 0 is gated out) without penalising honest evidence.
|
|
875
|
+
let contrastiveMargin: number | undefined;
|
|
876
|
+
let contrastiveRival: ConsensusContrastiveRivalTrace | undefined;
|
|
359
877
|
if (!known) {
|
|
360
878
|
let margin = score;
|
|
361
|
-
|
|
879
|
+
const hitsForRival = await ensureHits();
|
|
880
|
+
for (let hi = 0; hi < hitsForRival.length; hi++) {
|
|
881
|
+
const h = hitsForRival[hi];
|
|
362
882
|
if (h.id === voterId) continue;
|
|
363
883
|
const r2 = edgeAncestors(ctx, h.id, N, reachMemo);
|
|
884
|
+
examinedIds?.add(h.id);
|
|
364
885
|
if (r2.saturated || r2.roots.length === 0) continue; // concludes nothing
|
|
365
886
|
if (sameRoots(r2.roots, reach.roots)) continue; // same conclusion
|
|
366
887
|
margin = score - h.score; // hits are nearest-first: the best rival
|
|
888
|
+
if (td) {
|
|
889
|
+
contrastiveRival = { node: h.id, rank: hi, score: h.score };
|
|
890
|
+
}
|
|
367
891
|
break;
|
|
368
892
|
}
|
|
369
|
-
|
|
893
|
+
contrastiveMargin = margin;
|
|
894
|
+
const noiseFloor = estimatorNoise(ctx.store.D);
|
|
895
|
+
if (margin <= noiseFloor) {
|
|
896
|
+
recordRegion("contrastive-margin-rejection", {
|
|
897
|
+
selected,
|
|
898
|
+
reachNode: voterId,
|
|
899
|
+
idf,
|
|
900
|
+
dfWeight: wf,
|
|
901
|
+
contrastiveMargin: margin,
|
|
902
|
+
contrastiveNoiseFloor: noiseFloor,
|
|
903
|
+
...(contrastiveRival ? { contrastiveRival } : {}),
|
|
904
|
+
});
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
370
907
|
}
|
|
371
908
|
|
|
372
909
|
// MUTUAL-EXPLANATION WEIGHT (angle + magnitude). Under the linear fold
|
|
@@ -406,6 +943,22 @@ export async function voteRegions(
|
|
|
406
943
|
if (ctx.trace) {
|
|
407
944
|
regionVoter[ri] = { id: voterId, score, w: wf };
|
|
408
945
|
}
|
|
946
|
+
recordRegion("voted", {
|
|
947
|
+
selected,
|
|
948
|
+
reachNode: voterId,
|
|
949
|
+
idf,
|
|
950
|
+
dfWeight: wf,
|
|
951
|
+
...(contrastiveMargin !== undefined
|
|
952
|
+
? {
|
|
953
|
+
contrastiveMargin,
|
|
954
|
+
contrastiveNoiseFloor: estimatorNoise(ctx.store.D),
|
|
955
|
+
...(contrastiveRival ? { contrastiveRival } : {}),
|
|
956
|
+
}
|
|
957
|
+
: {}),
|
|
958
|
+
mutualWeight: mutual,
|
|
959
|
+
voteWeightPerRoot: w,
|
|
960
|
+
focusWeightPerRoot: wFocus,
|
|
961
|
+
});
|
|
409
962
|
}
|
|
410
963
|
return {
|
|
411
964
|
votes: regionVotes,
|
|
@@ -431,6 +984,7 @@ export function poolVotes(
|
|
|
431
984
|
regionVotes: readonly RegionVote[],
|
|
432
985
|
sat: SaturationInfo,
|
|
433
986
|
N: number,
|
|
987
|
+
td?: TraceDraft,
|
|
434
988
|
): {
|
|
435
989
|
votes: Map<number, number>;
|
|
436
990
|
votesIdf: Map<number, number>;
|
|
@@ -453,6 +1007,13 @@ export function poolVotes(
|
|
|
453
1007
|
}
|
|
454
1008
|
eligible.push(ri);
|
|
455
1009
|
}
|
|
1010
|
+
if (td) {
|
|
1011
|
+
td.pooling = {
|
|
1012
|
+
inputVotes: regionVotes.length,
|
|
1013
|
+
eligibleVotes: eligible.length,
|
|
1014
|
+
saturationMaskedVotes: regionVotes.length - eligible.length,
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
456
1017
|
|
|
457
1018
|
const key = (it: AItem) =>
|
|
458
1019
|
it.kind === "region"
|
|
@@ -612,11 +1173,13 @@ export function commitVotes(
|
|
|
612
1173
|
regions: readonly Region[],
|
|
613
1174
|
regionVoter: ReadonlyArray<{ id: number; score: number; w: number } | null>,
|
|
614
1175
|
N: number,
|
|
1176
|
+
td?: TraceDraft,
|
|
1177
|
+
cfg?: ClimbConsensusCfg,
|
|
615
1178
|
): AttentionRead {
|
|
616
1179
|
const { votes, votesIdf, support, regionSupport, regionSpans, steps } =
|
|
617
1180
|
pooled;
|
|
618
1181
|
if (votes.size === 0) {
|
|
619
|
-
traceAttention(ctx, regions, regionVoter, [], steps);
|
|
1182
|
+
traceAttention(ctx, regions, regionVoter, [], steps, td, cfg);
|
|
620
1183
|
return { roots: [], ranked: [] };
|
|
621
1184
|
}
|
|
622
1185
|
|
|
@@ -658,25 +1221,121 @@ export function commitVotes(
|
|
|
658
1221
|
const floor = consensusFloor(N);
|
|
659
1222
|
const placed: Attention[] = [];
|
|
660
1223
|
const roots: Attention[] = [];
|
|
661
|
-
|
|
1224
|
+
const recordAnchor = (
|
|
1225
|
+
point: Attention,
|
|
1226
|
+
rank: number,
|
|
1227
|
+
status: "root" | "overlap" | "rejected",
|
|
1228
|
+
dominant: boolean,
|
|
1229
|
+
passesNaturalBreak: boolean | undefined,
|
|
1230
|
+
passesConsensusFloor: boolean | undefined,
|
|
1231
|
+
pastLeadingSaturation: boolean | undefined,
|
|
1232
|
+
rejectionReasons: AnchorRejectionReason[],
|
|
1233
|
+
) => {
|
|
1234
|
+
if (!td) return;
|
|
1235
|
+
td.anchors.push({
|
|
1236
|
+
anchor: point.anchor,
|
|
1237
|
+
rank,
|
|
1238
|
+
pooledVote: point.vote,
|
|
1239
|
+
idfVote: votesIdf.get(point.anchor) ?? 0,
|
|
1240
|
+
candidateBreadth: regions.length,
|
|
1241
|
+
contributingVotes: regionSpans.get(point.anchor)?.length ?? 0,
|
|
1242
|
+
contributingEvidence: regionSupport.get(point.anchor) ?? 0,
|
|
1243
|
+
breadth: point.breadth,
|
|
1244
|
+
contributingSpans: regionSpans.get(point.anchor) ?? [],
|
|
1245
|
+
clusters: point.clusters,
|
|
1246
|
+
commit: {
|
|
1247
|
+
status,
|
|
1248
|
+
dominant,
|
|
1249
|
+
passesNaturalBreak,
|
|
1250
|
+
passesConsensusFloor,
|
|
1251
|
+
pastLeadingSaturation,
|
|
1252
|
+
rejectionReasons,
|
|
1253
|
+
},
|
|
1254
|
+
});
|
|
1255
|
+
};
|
|
1256
|
+
for (let rank = 0; rank < ranked.length; rank++) {
|
|
1257
|
+
const point = ranked[rank];
|
|
662
1258
|
const absorbed = placed.some((p) => overlaps(point, p));
|
|
663
|
-
|
|
1259
|
+
// Commit decisions are recorded LIVE, inside this loop, in the exact
|
|
1260
|
+
// shape the gates below apply them — never reconstructed afterward from
|
|
1261
|
+
// the final `roots` (spec §8's explicit requirement).
|
|
1262
|
+
let status: "root" | "overlap" | "rejected";
|
|
1263
|
+
let dominant = false;
|
|
1264
|
+
let passesNaturalBreak: boolean | undefined;
|
|
1265
|
+
let passesConsensusFloor: boolean | undefined;
|
|
1266
|
+
let pastLeadingSaturation: boolean | undefined;
|
|
1267
|
+
const rejectionReasons: AnchorRejectionReason[] = [];
|
|
1268
|
+
if (absorbed) {
|
|
1269
|
+
status = "overlap";
|
|
1270
|
+
} else {
|
|
664
1271
|
const pastLeading = !sat.hasLeading ||
|
|
665
1272
|
roots.length === 0 || point.start >= sat.leadingEnd;
|
|
1273
|
+
pastLeadingSaturation = pastLeading;
|
|
666
1274
|
const vote = votesIdf.get(point.anchor) ?? 0;
|
|
667
|
-
if (
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
1275
|
+
if (roots.length === 0) {
|
|
1276
|
+
// The first non-overlapping root is DOMINANT and bypasses the two
|
|
1277
|
+
// vote thresholds (it always grounds) — only the leading-saturation
|
|
1278
|
+
// gate still applies to it.
|
|
1279
|
+
dominant = true;
|
|
1280
|
+
if (pastLeading) {
|
|
1281
|
+
status = "root";
|
|
1282
|
+
} else {
|
|
1283
|
+
status = "rejected";
|
|
1284
|
+
rejectionReasons.push("leading-saturation");
|
|
1285
|
+
}
|
|
1286
|
+
} else {
|
|
1287
|
+
passesNaturalBreak = vote >= rootCut;
|
|
1288
|
+
passesConsensusFloor = vote >= floor;
|
|
1289
|
+
if (passesNaturalBreak && passesConsensusFloor && pastLeading) {
|
|
1290
|
+
status = "root";
|
|
1291
|
+
} else {
|
|
1292
|
+
status = "rejected";
|
|
1293
|
+
if (!passesNaturalBreak) rejectionReasons.push("below-natural-break");
|
|
1294
|
+
if (!passesConsensusFloor) {
|
|
1295
|
+
rejectionReasons.push("below-consensus-floor");
|
|
1296
|
+
}
|
|
1297
|
+
if (!pastLeading) rejectionReasons.push("leading-saturation");
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (status === "root") {
|
|
671
1301
|
roots.push(point);
|
|
672
1302
|
} else {
|
|
1303
|
+
recordAnchor(
|
|
1304
|
+
point,
|
|
1305
|
+
rank,
|
|
1306
|
+
status,
|
|
1307
|
+
dominant,
|
|
1308
|
+
passesNaturalBreak,
|
|
1309
|
+
passesConsensusFloor,
|
|
1310
|
+
pastLeadingSaturation,
|
|
1311
|
+
rejectionReasons,
|
|
1312
|
+
);
|
|
673
1313
|
continue;
|
|
674
1314
|
}
|
|
675
1315
|
}
|
|
1316
|
+
recordAnchor(
|
|
1317
|
+
point,
|
|
1318
|
+
rank,
|
|
1319
|
+
status,
|
|
1320
|
+
dominant,
|
|
1321
|
+
passesNaturalBreak,
|
|
1322
|
+
passesConsensusFloor,
|
|
1323
|
+
pastLeadingSaturation,
|
|
1324
|
+
rejectionReasons,
|
|
1325
|
+
);
|
|
676
1326
|
placed.push(point);
|
|
677
1327
|
}
|
|
678
1328
|
|
|
679
|
-
traceAttention(
|
|
1329
|
+
traceAttention(
|
|
1330
|
+
ctx,
|
|
1331
|
+
regions,
|
|
1332
|
+
regionVoter,
|
|
1333
|
+
roots,
|
|
1334
|
+
steps,
|
|
1335
|
+
td,
|
|
1336
|
+
cfg ? { ...cfg, naturalBreak: rootCut, consensusFloor: floor } : undefined,
|
|
1337
|
+
ranked,
|
|
1338
|
+
);
|
|
680
1339
|
return { roots, ranked };
|
|
681
1340
|
}
|
|
682
1341
|
|
|
@@ -843,6 +1502,331 @@ export function naturalBreak(votes: number[]): number {
|
|
|
843
1502
|
// container does not hold (a genuine second topic) are untouched.
|
|
844
1503
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
845
1504
|
|
|
1505
|
+
// ── Structural-resonance — the FINAL approximate tier ──────────────────────
|
|
1506
|
+
//
|
|
1507
|
+
// Reached only when every DAG junction tier (exact, single-synonym, double-
|
|
1508
|
+
// synonym) found no container. Composes a hypothetical structural gist from
|
|
1509
|
+
// ALREADY-EXISTING structural vectors — the two endpoint regions' own gists
|
|
1510
|
+
// (or, per variant, a halo sibling's stored gist occupying the same slot)
|
|
1511
|
+
// plus the REAL middle-query structure between them — and asks the ANN index
|
|
1512
|
+
// what already-learnt whole resembles that composition. It never perceives
|
|
1513
|
+
// concatenated endpoint bytes and never fabricates a rewritten query string;
|
|
1514
|
+
// see {@link composeStructuralGist}.
|
|
1515
|
+
|
|
1516
|
+
export type CrossRegionTier =
|
|
1517
|
+
| "exact"
|
|
1518
|
+
| "single-synonym"
|
|
1519
|
+
| "double-synonym"
|
|
1520
|
+
| "structural-resonance";
|
|
1521
|
+
|
|
1522
|
+
export interface StructuralVariant {
|
|
1523
|
+
left: StructuralPart;
|
|
1524
|
+
right: StructuralPart;
|
|
1525
|
+
kind:
|
|
1526
|
+
| "exact-exact"
|
|
1527
|
+
| "left-synonym"
|
|
1528
|
+
| "right-synonym"
|
|
1529
|
+
| "double-synonym";
|
|
1530
|
+
semanticConfidence: number;
|
|
1531
|
+
leftSiblingId?: number;
|
|
1532
|
+
rightSiblingId?: number;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
export interface StructuralResonanceProposal {
|
|
1536
|
+
id: number;
|
|
1537
|
+
annScore: number;
|
|
1538
|
+
semanticConfidence: number;
|
|
1539
|
+
effectiveScore: number;
|
|
1540
|
+
variant: StructuralVariant["kind"];
|
|
1541
|
+
leftSiblingId?: number;
|
|
1542
|
+
rightSiblingId?: number;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
const VARIANT_KIND_ORDER: Record<StructuralVariant["kind"], number> = {
|
|
1546
|
+
"exact-exact": -1,
|
|
1547
|
+
"left-synonym": 0,
|
|
1548
|
+
"right-synonym": 1,
|
|
1549
|
+
"double-synonym": 2,
|
|
1550
|
+
};
|
|
1551
|
+
|
|
1552
|
+
/** A node's structural gist, read directly from its own stored bytes — the
|
|
1553
|
+
* repository's node → gist accessor: content is immutable and perception is
|
|
1554
|
+
* a pure function of bytes, so re-perceiving a node's full stored bytes
|
|
1555
|
+
* reproduces exactly the gist it was interned with. Never concatenates
|
|
1556
|
+
* the sibling's bytes with anything else. */
|
|
1557
|
+
function storedNodeGist(ctx: MindContext, id: number): Vec {
|
|
1558
|
+
return gistOf(ctx, read(ctx, id));
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/** A halo sibling's structural part, occupying the ORIGINAL query-region
|
|
1562
|
+
* slot length — the sibling replaces only the DIRECTION, never the query's
|
|
1563
|
+
* own bytes, position, or gap (see §6 of the spec this implements). */
|
|
1564
|
+
function siblingPart(
|
|
1565
|
+
ctx: MindContext,
|
|
1566
|
+
sibling: Hit,
|
|
1567
|
+
originalRegion: { start: number; end: number },
|
|
1568
|
+
): StructuralPart {
|
|
1569
|
+
return {
|
|
1570
|
+
v: storedNodeGist(ctx, sibling.id),
|
|
1571
|
+
len: originalRegion.end - originalRegion.start,
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
/** Build, bound and order every mandatory structural variant (§7-8): the
|
|
1576
|
+
* exact/exact composition is always kept; up to `ctx.cfg.haloQueryK`
|
|
1577
|
+
* synonym variants (single- and double-synonym combined, one shared
|
|
1578
|
+
* budget) are appended, ordered by confidence, then kind, then sibling id. */
|
|
1579
|
+
export function buildStructuralVariants(
|
|
1580
|
+
ctx: MindContext,
|
|
1581
|
+
ra: Region,
|
|
1582
|
+
rb: Region,
|
|
1583
|
+
sides: JunctionSynonymSides,
|
|
1584
|
+
): {
|
|
1585
|
+
variants: StructuralVariant[];
|
|
1586
|
+
exactLeft: StructuralPart;
|
|
1587
|
+
exactRight: StructuralPart;
|
|
1588
|
+
} {
|
|
1589
|
+
const exactLeft: StructuralPart = { v: ra.v, len: ra.end - ra.start };
|
|
1590
|
+
const exactRight: StructuralPart = { v: rb.v, len: rb.end - rb.start };
|
|
1591
|
+
|
|
1592
|
+
const synonymVariants: StructuralVariant[] = [];
|
|
1593
|
+
for (const ls of sides.leftSiblings) {
|
|
1594
|
+
synonymVariants.push({
|
|
1595
|
+
left: siblingPart(ctx, ls, ra),
|
|
1596
|
+
right: exactRight,
|
|
1597
|
+
kind: "left-synonym",
|
|
1598
|
+
semanticConfidence: ls.score,
|
|
1599
|
+
leftSiblingId: ls.id,
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
for (const rs of sides.rightSiblings) {
|
|
1603
|
+
synonymVariants.push({
|
|
1604
|
+
left: exactLeft,
|
|
1605
|
+
right: siblingPart(ctx, rs, rb),
|
|
1606
|
+
kind: "right-synonym",
|
|
1607
|
+
semanticConfidence: rs.score,
|
|
1608
|
+
rightSiblingId: rs.id,
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
for (const ls of sides.leftSiblings) {
|
|
1612
|
+
for (const rs of sides.rightSiblings) {
|
|
1613
|
+
synonymVariants.push({
|
|
1614
|
+
left: siblingPart(ctx, ls, ra),
|
|
1615
|
+
right: siblingPart(ctx, rs, rb),
|
|
1616
|
+
kind: "double-synonym",
|
|
1617
|
+
semanticConfidence: Math.min(ls.score, rs.score),
|
|
1618
|
+
leftSiblingId: ls.id,
|
|
1619
|
+
rightSiblingId: rs.id,
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// §8: semantic confidence desc, then kind (left-synonym, right-synonym,
|
|
1625
|
+
// double-synonym), then left sibling id asc, then right sibling id asc.
|
|
1626
|
+
synonymVariants.sort((a, b) =>
|
|
1627
|
+
b.semanticConfidence - a.semanticConfidence ||
|
|
1628
|
+
VARIANT_KIND_ORDER[a.kind] - VARIANT_KIND_ORDER[b.kind] ||
|
|
1629
|
+
(a.leftSiblingId ?? -1) - (b.leftSiblingId ?? -1) ||
|
|
1630
|
+
(a.rightSiblingId ?? -1) - (b.rightSiblingId ?? -1)
|
|
1631
|
+
);
|
|
1632
|
+
|
|
1633
|
+
const variants: StructuralVariant[] = [
|
|
1634
|
+
{
|
|
1635
|
+
left: exactLeft,
|
|
1636
|
+
right: exactRight,
|
|
1637
|
+
kind: "exact-exact",
|
|
1638
|
+
semanticConfidence: 1,
|
|
1639
|
+
},
|
|
1640
|
+
...synonymVariants.slice(0, ctx.cfg.haloQueryK),
|
|
1641
|
+
];
|
|
1642
|
+
return { variants, exactLeft, exactRight };
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
/** Deterministic best-of tie-break for two proposals ranked for the SAME
|
|
1646
|
+
* candidate id — effectiveScore, then annScore, then semanticConfidence,
|
|
1647
|
+
* then variant kind, then sibling ids (§10). */
|
|
1648
|
+
function betterProposal(
|
|
1649
|
+
a: StructuralResonanceProposal,
|
|
1650
|
+
b: StructuralResonanceProposal,
|
|
1651
|
+
): boolean {
|
|
1652
|
+
if (a.effectiveScore !== b.effectiveScore) {
|
|
1653
|
+
return a.effectiveScore > b.effectiveScore;
|
|
1654
|
+
}
|
|
1655
|
+
if (a.annScore !== b.annScore) return a.annScore > b.annScore;
|
|
1656
|
+
if (a.semanticConfidence !== b.semanticConfidence) {
|
|
1657
|
+
return a.semanticConfidence > b.semanticConfidence;
|
|
1658
|
+
}
|
|
1659
|
+
if (VARIANT_KIND_ORDER[a.variant] !== VARIANT_KIND_ORDER[b.variant]) {
|
|
1660
|
+
return VARIANT_KIND_ORDER[a.variant] < VARIANT_KIND_ORDER[b.variant];
|
|
1661
|
+
}
|
|
1662
|
+
if ((a.leftSiblingId ?? -1) !== (b.leftSiblingId ?? -1)) {
|
|
1663
|
+
return (a.leftSiblingId ?? -1) < (b.leftSiblingId ?? -1);
|
|
1664
|
+
}
|
|
1665
|
+
return (a.rightSiblingId ?? -1) < (b.rightSiblingId ?? -1);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
/** The final approximate tier: compose every retained structural variant,
|
|
1669
|
+
* ANN-query each, merge proposals by candidate id, and validate the winner
|
|
1670
|
+
* through the SAME structural gates every other tier answers to (saturation,
|
|
1671
|
+
* roots, IDF, contrastive margin). Returns null when nothing survives. */
|
|
1672
|
+
export async function structuralResonance(
|
|
1673
|
+
ctx: MindContext,
|
|
1674
|
+
query: Uint8Array,
|
|
1675
|
+
ra: Region,
|
|
1676
|
+
rb: Region,
|
|
1677
|
+
sides: JunctionSynonymSides,
|
|
1678
|
+
k: number,
|
|
1679
|
+
N: number,
|
|
1680
|
+
reachMemo: Map<number, AncestorReach>,
|
|
1681
|
+
/** Each side's OWN individual climb roots (from voteRegions), when it cast
|
|
1682
|
+
* one — the self-evidence backstop structural-resonance needs and the
|
|
1683
|
+
* exact tier gets for free from literal byte containment (§11's whole
|
|
1684
|
+
* premise: recover a JOINT context neither side votes for alone). A
|
|
1685
|
+
* candidate whose reach is exactly one side's own conclusion is not new
|
|
1686
|
+
* evidence of a joint whole; it is that side's resonance rediscovering
|
|
1687
|
+
* itself through a synthetic gist still dominated by its own direction. */
|
|
1688
|
+
ownRootsA: readonly number[] | undefined,
|
|
1689
|
+
ownRootsB: readonly number[] | undefined,
|
|
1690
|
+
trace?: StructuralResonanceTrace,
|
|
1691
|
+
): Promise<
|
|
1692
|
+
| { proposal: StructuralResonanceProposal; reach: AncestorReach; idf: number }
|
|
1693
|
+
| null
|
|
1694
|
+
> {
|
|
1695
|
+
const { variants } = buildStructuralVariants(ctx, ra, rb, sides);
|
|
1696
|
+
if (trace) trace.variantBudget = ctx.cfg.haloQueryK;
|
|
1697
|
+
|
|
1698
|
+
const middleBytes = query.subarray(ra.end, rb.start);
|
|
1699
|
+
const middlePart: StructuralPart | null = middleBytes.length === 0
|
|
1700
|
+
? null
|
|
1701
|
+
: { v: perceive(ctx, middleBytes).v, len: middleBytes.length };
|
|
1702
|
+
|
|
1703
|
+
const proposals = new Map<number, StructuralResonanceProposal>();
|
|
1704
|
+
for (const variant of variants) {
|
|
1705
|
+
const parts: StructuralPart[] = [variant.left];
|
|
1706
|
+
if (middlePart) parts.push(middlePart);
|
|
1707
|
+
parts.push(variant.right);
|
|
1708
|
+
const synthetic = composeStructuralGist(ctx.space, parts);
|
|
1709
|
+
const hits = await ctx.store.resonate(synthetic, k);
|
|
1710
|
+
if (trace) {
|
|
1711
|
+
trace.variants.push({
|
|
1712
|
+
kind: variant.kind,
|
|
1713
|
+
semanticConfidence: variant.semanticConfidence,
|
|
1714
|
+
leftSiblingId: variant.leftSiblingId,
|
|
1715
|
+
rightSiblingId: variant.rightSiblingId,
|
|
1716
|
+
annHitsReturned: hits.length,
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
for (const hit of hits) {
|
|
1720
|
+
const candidate: StructuralResonanceProposal = {
|
|
1721
|
+
id: hit.id,
|
|
1722
|
+
annScore: hit.score,
|
|
1723
|
+
semanticConfidence: variant.semanticConfidence,
|
|
1724
|
+
effectiveScore: hit.score * variant.semanticConfidence,
|
|
1725
|
+
variant: variant.kind,
|
|
1726
|
+
leftSiblingId: variant.leftSiblingId,
|
|
1727
|
+
rightSiblingId: variant.rightSiblingId,
|
|
1728
|
+
};
|
|
1729
|
+
const prev = proposals.get(hit.id);
|
|
1730
|
+
if (prev === undefined || betterProposal(candidate, prev)) {
|
|
1731
|
+
proposals.set(hit.id, candidate);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
if (trace) trace.mergedProposals = proposals.size;
|
|
1736
|
+
if (proposals.size === 0) {
|
|
1737
|
+
if (trace) {
|
|
1738
|
+
trace.noiseFloor = estimatorNoise(ctx.store.D);
|
|
1739
|
+
trace.outcome = "empty";
|
|
1740
|
+
}
|
|
1741
|
+
return null;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
const sorted = [...proposals.values()].sort((a, b) =>
|
|
1745
|
+
b.effectiveScore - a.effectiveScore || a.id - b.id
|
|
1746
|
+
);
|
|
1747
|
+
|
|
1748
|
+
// One shared shape for every `examined` entry (spec §5): only `outcome`
|
|
1749
|
+
// varies across the six exit points below, so build it once instead of
|
|
1750
|
+
// repeating the six-field literal at each site.
|
|
1751
|
+
const recordExamined = (
|
|
1752
|
+
p: StructuralResonanceProposal,
|
|
1753
|
+
outcome: StructuralResonanceCandidateTrace["outcome"],
|
|
1754
|
+
) => {
|
|
1755
|
+
if (!trace) return;
|
|
1756
|
+
trace.examined.push({
|
|
1757
|
+
node: p.id,
|
|
1758
|
+
variant: p.variant,
|
|
1759
|
+
leftSiblingId: p.leftSiblingId,
|
|
1760
|
+
rightSiblingId: p.rightSiblingId,
|
|
1761
|
+
annScore: p.annScore,
|
|
1762
|
+
semanticConfidence: p.semanticConfidence,
|
|
1763
|
+
effectiveScore: p.effectiveScore,
|
|
1764
|
+
outcome,
|
|
1765
|
+
});
|
|
1766
|
+
};
|
|
1767
|
+
|
|
1768
|
+
let selected: StructuralResonanceProposal | null = null;
|
|
1769
|
+
let selectedReach: AncestorReach | null = null;
|
|
1770
|
+
let selectedIdf = 0;
|
|
1771
|
+
let rival: StructuralResonanceProposal | null = null;
|
|
1772
|
+
for (const p of sorted) {
|
|
1773
|
+
const reach = edgeAncestors(ctx, p.id, N, reachMemo);
|
|
1774
|
+
if (reach.saturated || reach.roots.length === 0) {
|
|
1775
|
+
recordExamined(p, reach.saturated ? "saturated" : "no-roots");
|
|
1776
|
+
continue;
|
|
1777
|
+
}
|
|
1778
|
+
const idf = Math.log(N / Math.max(1, reach.contextsReached));
|
|
1779
|
+
if (idf <= 0) {
|
|
1780
|
+
recordExamined(p, "nonpositive-idf");
|
|
1781
|
+
continue;
|
|
1782
|
+
}
|
|
1783
|
+
// Self-evidence backstop (see the param doc above): a candidate that is
|
|
1784
|
+
// exactly one side's own already-voted conclusion carries no JOINT
|
|
1785
|
+
// evidence — skip it as if it never survived.
|
|
1786
|
+
if (
|
|
1787
|
+
(ownRootsA && sameRoots(reach.roots, ownRootsA)) ||
|
|
1788
|
+
(ownRootsB && sameRoots(reach.roots, ownRootsB))
|
|
1789
|
+
) {
|
|
1790
|
+
recordExamined(p, "same-as-endpoint");
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
if (selected === null) {
|
|
1794
|
+
selected = p;
|
|
1795
|
+
selectedReach = reach;
|
|
1796
|
+
selectedIdf = idf;
|
|
1797
|
+
recordExamined(p, "selected");
|
|
1798
|
+
} else if (!sameRoots(reach.roots, selectedReach!.roots)) {
|
|
1799
|
+
rival = p;
|
|
1800
|
+
recordExamined(p, "contrastive-rival");
|
|
1801
|
+
break;
|
|
1802
|
+
} else {
|
|
1803
|
+
recordExamined(p, "same-as-selected");
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
if (selected === null || selectedReach === null) {
|
|
1807
|
+
if (trace) {
|
|
1808
|
+
trace.noiseFloor = estimatorNoise(ctx.store.D);
|
|
1809
|
+
trace.outcome = "no-valid-proposal";
|
|
1810
|
+
}
|
|
1811
|
+
return null;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
const margin = rival
|
|
1815
|
+
? selected.effectiveScore - rival.effectiveScore
|
|
1816
|
+
: selected.effectiveScore;
|
|
1817
|
+
if (trace) {
|
|
1818
|
+
trace.contrastiveMargin = margin;
|
|
1819
|
+
trace.noiseFloor = estimatorNoise(ctx.store.D);
|
|
1820
|
+
}
|
|
1821
|
+
if (margin <= estimatorNoise(ctx.store.D)) {
|
|
1822
|
+
if (trace) trace.outcome = "margin-rejected";
|
|
1823
|
+
return null;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
if (trace) trace.outcome = "accepted";
|
|
1827
|
+
return { proposal: selected, reach: selectedReach, idf: selectedIdf };
|
|
1828
|
+
}
|
|
1829
|
+
|
|
846
1830
|
async function crossRegionVotes(
|
|
847
1831
|
ctx: MindContext,
|
|
848
1832
|
query: Uint8Array,
|
|
@@ -851,6 +1835,7 @@ async function crossRegionVotes(
|
|
|
851
1835
|
k: number,
|
|
852
1836
|
N: number,
|
|
853
1837
|
reachMemo: Map<number, AncestorReach>,
|
|
1838
|
+
td?: TraceDraft,
|
|
854
1839
|
): Promise<{ votes: RegionVote[]; superseded: Set<RegionVote> }> {
|
|
855
1840
|
// Candidate regions: every region that ALREADY CAST ITS OWN VOTE in
|
|
856
1841
|
// voteRegions — individually idf > 0, genuinely discriminative on its own,
|
|
@@ -902,6 +1887,15 @@ async function crossRegionVotes(
|
|
|
902
1887
|
)
|
|
903
1888
|
);
|
|
904
1889
|
const none = { votes: [], superseded: new Set<RegionVote>() };
|
|
1890
|
+
if (td) {
|
|
1891
|
+
td.crossRegionSummary = {
|
|
1892
|
+
eligibleRegions: eligible.length,
|
|
1893
|
+
maximalRegions: cand.length,
|
|
1894
|
+
probeLimit: k,
|
|
1895
|
+
probesAttempted: 0, // updated below as probes accrue
|
|
1896
|
+
stopReason: cand.length < 2 ? "insufficient-regions" : undefined,
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
905
1899
|
if (cand.length < 2) return none;
|
|
906
1900
|
cand.sort((x, y) =>
|
|
907
1901
|
regions[x].start - regions[y].start || regions[x].end - regions[y].end
|
|
@@ -972,6 +1966,34 @@ async function crossRegionVotes(
|
|
|
972
1966
|
regions.some((r) => r.known && r.start <= ra.start && rb.end <= r.end)
|
|
973
1967
|
) continue;
|
|
974
1968
|
probes++;
|
|
1969
|
+
if (td?.crossRegionSummary) {
|
|
1970
|
+
td.crossRegionSummary.probesAttempted = probes;
|
|
1971
|
+
}
|
|
1972
|
+
// Trace-only per-probe bookkeeping (spec §2-§7) — built incrementally
|
|
1973
|
+
// as the ladder runs, pushed exactly once at whichever exit fires
|
|
1974
|
+
// below. `pushProbe` is called at every continue/success exit for
|
|
1975
|
+
// THIS pair so the invariant `probes.length === probesAttempted`
|
|
1976
|
+
// holds regardless of which tier settled it.
|
|
1977
|
+
const probe: CrossRegionProbeTrace | undefined = td
|
|
1978
|
+
? {
|
|
1979
|
+
leftRegionIndex: cand[a],
|
|
1980
|
+
rightRegionIndex: cand[b],
|
|
1981
|
+
betweenRegionIndices: [...between],
|
|
1982
|
+
exact: { attempted: false, candidatesReturned: 0 },
|
|
1983
|
+
singleSynonym: { attempted: false, candidatesReturned: 0 },
|
|
1984
|
+
doubleSynonym: { attempted: false, candidatesReturned: 0 },
|
|
1985
|
+
outcome: "structural-rejected",
|
|
1986
|
+
}
|
|
1987
|
+
: undefined;
|
|
1988
|
+
let probePushed = false;
|
|
1989
|
+
const pushProbe = (
|
|
1990
|
+
outcome: CrossRegionProbeTrace["outcome"],
|
|
1991
|
+
) => {
|
|
1992
|
+
if (!td || !probe || probePushed) return;
|
|
1993
|
+
probe.outcome = outcome;
|
|
1994
|
+
td.crossRegionProbes.push(probe);
|
|
1995
|
+
probePushed = true;
|
|
1996
|
+
};
|
|
975
1997
|
|
|
976
1998
|
const left = query.subarray(ra.start, ra.end);
|
|
977
1999
|
const right = query.subarray(rb.start, rb.end);
|
|
@@ -980,112 +2002,322 @@ async function crossRegionVotes(
|
|
|
980
2002
|
const maxInterior = (left.length + right.length) * ctx.space.maxGroup;
|
|
981
2003
|
const cap = left.length + right.length + maxInterior;
|
|
982
2004
|
|
|
983
|
-
//
|
|
984
|
-
//
|
|
985
|
-
//
|
|
986
|
-
//
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
2005
|
+
// The graded ladder (spec §1): exact DAG junction, then single-synonym,
|
|
2006
|
+
// then double-synonym, then — only when every DAG tier found nothing —
|
|
2007
|
+
// structural-resonance. `sides` (the two halo sibling lists) is loaded
|
|
2008
|
+
// ONCE and reused by junctionSynonyms AND structural-resonance, so no
|
|
2009
|
+
// ladder rung repeats a halo ANN query an earlier rung already paid for.
|
|
2010
|
+
const sides = await loadJunctionSynonymSides(ctx, left, right);
|
|
2011
|
+
|
|
2012
|
+
let tier: CrossRegionTier = "exact";
|
|
2013
|
+
let containers: Array<Junction | SynonymJunction> =
|
|
2014
|
+
junctionContainersFrom(
|
|
2015
|
+
ctx,
|
|
2016
|
+
left,
|
|
2017
|
+
right,
|
|
2018
|
+
cap,
|
|
2019
|
+
seedsOf(cand[a]),
|
|
2020
|
+
seedsOf(cand[b]),
|
|
2021
|
+
undefined,
|
|
2022
|
+
true,
|
|
2023
|
+
);
|
|
2024
|
+
if (probe) {
|
|
2025
|
+
probe.exact = {
|
|
2026
|
+
attempted: true,
|
|
2027
|
+
candidatesReturned: containers.length,
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
997
2030
|
if (containers.length === 0) {
|
|
998
|
-
//
|
|
999
|
-
|
|
2031
|
+
// Tiers 2-4 — synonym containers (junctionSynonyms itself runs
|
|
2032
|
+
// single-synonym first, falling to double-synonym only when
|
|
2033
|
+
// single-synonym found nothing — see junction.ts).
|
|
2034
|
+
const syn = await junctionSynonyms(
|
|
1000
2035
|
ctx,
|
|
1001
2036
|
left,
|
|
1002
2037
|
right,
|
|
1003
2038
|
maxInterior,
|
|
1004
2039
|
true,
|
|
2040
|
+
sides,
|
|
1005
2041
|
);
|
|
2042
|
+
if (probe) {
|
|
2043
|
+
const singleAttempted = sides.leftSiblings.length > 0 ||
|
|
2044
|
+
sides.rightSiblings.length > 0;
|
|
2045
|
+
const singleReturned = syn[0]?.tier === "single-synonym"
|
|
2046
|
+
? syn.length
|
|
2047
|
+
: 0;
|
|
2048
|
+
const doubleAttempted = singleAttempted && singleReturned === 0 &&
|
|
2049
|
+
sides.leftSiblings.length > 0 && sides.rightSiblings.length > 0;
|
|
2050
|
+
const doubleReturned = syn[0]?.tier === "double-synonym"
|
|
2051
|
+
? syn.length
|
|
2052
|
+
: 0;
|
|
2053
|
+
probe.singleSynonym = {
|
|
2054
|
+
attempted: singleAttempted,
|
|
2055
|
+
candidatesReturned: singleReturned,
|
|
2056
|
+
};
|
|
2057
|
+
probe.doubleSynonym = {
|
|
2058
|
+
attempted: doubleAttempted,
|
|
2059
|
+
candidatesReturned: doubleReturned,
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
if (syn.length > 0) {
|
|
2063
|
+
containers = syn;
|
|
2064
|
+
tier = syn[0].tier;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
// Tier 5 — structural-resonance ANN, the FINAL approximate proposal
|
|
2069
|
+
// path. Only reached when every DAG tier found NOTHING, and only when
|
|
2070
|
+
// there is no already-corroborated region between the endpoints (a
|
|
2071
|
+
// between-region with its own vote is evidence the gap already means
|
|
2072
|
+
// something specific — an ANN guess must not override it).
|
|
2073
|
+
let structuralPick:
|
|
2074
|
+
| {
|
|
2075
|
+
proposal: StructuralResonanceProposal;
|
|
2076
|
+
reach: AncestorReach;
|
|
2077
|
+
idf: number;
|
|
2078
|
+
}
|
|
2079
|
+
| null = null;
|
|
2080
|
+
if (containers.length === 0) {
|
|
2081
|
+
// Structural-resonance composes each side's OWN gist directly (no
|
|
2082
|
+
// byte-containment truth backs it, unlike the DAG tiers) — so, unlike
|
|
2083
|
+
// the DAG ladder (which tolerates one approximate side because byte
|
|
2084
|
+
// containment cannot lie), the ANN tier requires BOTH sides to be
|
|
2085
|
+
// KNOWN (content-addressed, exact identities): an approximate chunk
|
|
2086
|
+
// fragment's own resonance is noise at any tier, and composing noise
|
|
2087
|
+
// into a synthetic gist only manufactures a plausible-looking but
|
|
2088
|
+
// spurious ANN neighbour, not evidence of a genuine joint whole.
|
|
2089
|
+
// PHRASE-SCALE CONTRACT — the same one the DAG tiers hold their glue
|
|
2090
|
+
// to (see maxInterior above): a junction, exact or approximate, is a
|
|
2091
|
+
// whole the two forms nearly exhaust, not two arbitrary landmarks
|
|
2092
|
+
// anywhere in a long, multi-topic query. Without this, structural-
|
|
2093
|
+
// resonance would pair opposite ends of an unrelated scaffolding-
|
|
2094
|
+
// dominated query and manufacture a plausible-looking ANN neighbour
|
|
2095
|
+
// for a "gap" that never was a phrase.
|
|
2096
|
+
// BOTH sides must be independently DISCRIMINATIVE (individually
|
|
2097
|
+
// voted — `strong`, not merely a content-addressed `known` chunk):
|
|
2098
|
+
// a shared, non-discriminative scaffolding run (a repeated system
|
|
2099
|
+
// preamble) can be `known` without ever being distinctive evidence
|
|
2100
|
+
// of anything, and composing its own gist into a synthetic query
|
|
2101
|
+
// manufactures a plausible-looking but spurious ANN neighbour. The
|
|
2102
|
+
// DAG tiers can tolerate one merely-`known` side because byte
|
|
2103
|
+
// containment cannot lie; structural-resonance has no such
|
|
2104
|
+
// backstop, so both sides earn their place here the same way an
|
|
2105
|
+
// ordinary approximate region earns its individual vote.
|
|
2106
|
+
const gap = rb.start - ra.end;
|
|
2107
|
+
const reasons: NonNullable<
|
|
2108
|
+
StructuralResonanceTrace["ineligibleReasons"]
|
|
2109
|
+
> = [];
|
|
2110
|
+
if (between.length > 0) reasons.push("between-region");
|
|
2111
|
+
if (!strong.has(cand[a]) || !strong.has(cand[b])) {
|
|
2112
|
+
reasons.push("not-both-strong");
|
|
2113
|
+
}
|
|
2114
|
+
if (!ra.known || !rb.known) reasons.push("not-both-known");
|
|
2115
|
+
if (gap > maxInterior) reasons.push("gap-too-large");
|
|
2116
|
+
let resonanceTrace: StructuralResonanceTrace | undefined;
|
|
2117
|
+
if (reasons.length > 0) {
|
|
2118
|
+
if (probe) {
|
|
2119
|
+
resonanceTrace = {
|
|
2120
|
+
variantBudget: ctx.cfg.haloQueryK,
|
|
2121
|
+
variants: [],
|
|
2122
|
+
mergedProposals: 0,
|
|
2123
|
+
examined: [],
|
|
2124
|
+
noiseFloor: estimatorNoise(ctx.store.D),
|
|
2125
|
+
outcome: "ineligible",
|
|
2126
|
+
ineligibleReasons: reasons,
|
|
2127
|
+
};
|
|
2128
|
+
probe.resonance = resonanceTrace;
|
|
2129
|
+
}
|
|
2130
|
+
} else {
|
|
2131
|
+
if (probe) {
|
|
2132
|
+
// `outcome`/`noiseFloor` are required fields with no natural
|
|
2133
|
+
// "unset" value; structuralResonance (called just below) always
|
|
2134
|
+
// overwrites both before returning, on every one of its exit
|
|
2135
|
+
// paths — these are never read in their initial form.
|
|
2136
|
+
resonanceTrace = {
|
|
2137
|
+
variantBudget: ctx.cfg.haloQueryK,
|
|
2138
|
+
variants: [],
|
|
2139
|
+
mergedProposals: 0,
|
|
2140
|
+
examined: [],
|
|
2141
|
+
noiseFloor: 0,
|
|
2142
|
+
outcome: "empty",
|
|
2143
|
+
};
|
|
2144
|
+
probe.resonance = resonanceTrace;
|
|
2145
|
+
}
|
|
2146
|
+
const ownRootsA = rvs.votes.find((v) =>
|
|
2147
|
+
v.start === ra.start && v.end === ra.end
|
|
2148
|
+
)?.roots;
|
|
2149
|
+
const ownRootsB = rvs.votes.find((v) =>
|
|
2150
|
+
v.start === rb.start && v.end === rb.end
|
|
2151
|
+
)?.roots;
|
|
2152
|
+
structuralPick = await structuralResonance(
|
|
2153
|
+
ctx,
|
|
2154
|
+
query,
|
|
2155
|
+
ra,
|
|
2156
|
+
rb,
|
|
2157
|
+
sides,
|
|
2158
|
+
k,
|
|
2159
|
+
N,
|
|
2160
|
+
reachMemo,
|
|
2161
|
+
ownRootsA,
|
|
2162
|
+
ownRootsB,
|
|
2163
|
+
resonanceTrace,
|
|
2164
|
+
);
|
|
2165
|
+
}
|
|
2166
|
+
if (structuralPick === null) {
|
|
2167
|
+
pushProbe(
|
|
2168
|
+
reasons.length > 0 ? "resonance-ineligible" : "resonance-rejected",
|
|
2169
|
+
);
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
tier = "structural-resonance";
|
|
1006
2173
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
// N-ARY selection: the container covering the MOST remaining candidate
|
|
1010
|
-
// forms wins (then tightest interior, then lowest id). Reads are
|
|
1011
|
-
// cache hits — every container's bytes were already read by the walk.
|
|
1012
|
-
//
|
|
1013
|
-
// SELF-EVIDENCE GUARD: a junction is BINDING evidence only when the
|
|
1014
|
-
// container joins forms the query mentions APART. When the container's
|
|
1015
|
-
// own joined occurrence (left..right including its interior) is a
|
|
1016
|
-
// literal substring of the query, the query already spells that phrase
|
|
1017
|
-
// out contiguously — perception already voted with it, and grid shards
|
|
1018
|
-
// of one phrase pairing "around" a gap chunk would merely rediscover
|
|
1019
|
-
// the phrase they are shards of, then explain away its rivals.
|
|
1020
|
-
let best: (typeof containers)[number] | null = null;
|
|
2174
|
+
|
|
2175
|
+
let best: (Junction | SynonymJunction) | null = null;
|
|
1021
2176
|
let bestExtras: number[] = [];
|
|
1022
2177
|
let bestCov = -1;
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
2178
|
+
let reach: AncestorReach;
|
|
2179
|
+
let idf: number;
|
|
2180
|
+
let confidence: number;
|
|
2181
|
+
|
|
2182
|
+
if (structuralPick !== null) {
|
|
2183
|
+
// A resonance proposal is NOT a Junction — there is no container to
|
|
2184
|
+
// read bytes from, so the self-evidence/contradiction/N-ary
|
|
2185
|
+
// machinery below (byte-verified against a real container) does not
|
|
2186
|
+
// apply; per spec §13, no N-ary extra-region coverage for resonance
|
|
2187
|
+
// proposals.
|
|
2188
|
+
best = { id: structuralPick.proposal.id, interior: new Uint8Array(0) };
|
|
2189
|
+
bestExtras = [];
|
|
2190
|
+
bestCov = rb.end - ra.start;
|
|
2191
|
+
reach = structuralPick.reach;
|
|
2192
|
+
idf = structuralPick.idf;
|
|
2193
|
+
confidence = structuralPick.proposal.effectiveScore;
|
|
2194
|
+
} else {
|
|
2195
|
+
// Aggregate structural-tier trace (spec §4) — one per DAG tier that
|
|
2196
|
+
// returned at least one container (exact, single-synonym or
|
|
2197
|
+
// double-synonym); only aggregate counts and the final outcome are
|
|
2198
|
+
// recorded, never every candidate.
|
|
2199
|
+
const structuralTrace: CrossRegionStructuralTrace | undefined = probe
|
|
2200
|
+
? {
|
|
2201
|
+
tier: tier as "exact" | "single-synonym" | "double-synonym",
|
|
2202
|
+
selfEvidenceRejected: 0,
|
|
2203
|
+
contradictionRejected: 0,
|
|
2204
|
+
passedGuards: 0,
|
|
2205
|
+
outcome: "all-rejected",
|
|
2206
|
+
}
|
|
2207
|
+
: undefined;
|
|
2208
|
+
if (probe) probe.structural = structuralTrace;
|
|
2209
|
+
|
|
2210
|
+
// N-ARY selection: the container covering the MOST remaining candidate
|
|
2211
|
+
// forms wins (then tightest interior, then lowest id). Reads are
|
|
2212
|
+
// cache hits — every container's bytes were already read by the walk.
|
|
2213
|
+
//
|
|
2214
|
+
// SELF-EVIDENCE GUARD: a junction is BINDING evidence only when the
|
|
2215
|
+
// container joins forms the query mentions APART. When the container's
|
|
2216
|
+
// own joined occurrence (left..right including its interior) is a
|
|
2217
|
+
// literal substring of the query, the query already spells that phrase
|
|
2218
|
+
// out contiguously — perception already voted with it, and grid shards
|
|
2219
|
+
// of one phrase pairing "around" a gap chunk would merely rediscover
|
|
2220
|
+
// the phrase they are shards of, then explain away its rivals.
|
|
2221
|
+
for (const c of containers) {
|
|
2222
|
+
const bytes = cachedRead(ctx, cache, c.id, cap);
|
|
2223
|
+
const li = indexOf(bytes, left, 0);
|
|
2224
|
+
const ri = indexOf(bytes, right, 0);
|
|
2225
|
+
if (li >= 0 && ri >= 0) {
|
|
2226
|
+
const joined = bytes.subarray(
|
|
2227
|
+
Math.min(li, ri),
|
|
2228
|
+
Math.max(li + left.length, ri + right.length),
|
|
2229
|
+
);
|
|
2230
|
+
if (indexOf(query, joined, 0) >= 0) {
|
|
2231
|
+
if (structuralTrace) structuralTrace.selfEvidenceRejected++;
|
|
2232
|
+
continue; // query says it itself
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
// CONTRADICTION GUARD: a between-region already carrying its own
|
|
2236
|
+
// vote must actually recur in this container's bytes — otherwise
|
|
2237
|
+
// the container is a different learnt whole that happens to share
|
|
2238
|
+
// ra/rb, and letting it stand in for the gap would silently
|
|
2239
|
+
// override evidence the query itself already resolved there.
|
|
2240
|
+
if (
|
|
2241
|
+
between.some((bi) =>
|
|
2242
|
+
indexOf(
|
|
2243
|
+
bytes,
|
|
2244
|
+
query.subarray(regions[bi].start, regions[bi].end),
|
|
2245
|
+
0,
|
|
2246
|
+
) < 0
|
|
2247
|
+
)
|
|
2248
|
+
) {
|
|
2249
|
+
if (structuralTrace) structuralTrace.contradictionRejected++;
|
|
2250
|
+
continue;
|
|
2251
|
+
}
|
|
2252
|
+
if (structuralTrace) structuralTrace.passedGuards++;
|
|
2253
|
+
let cov = left.length + right.length;
|
|
2254
|
+
const extras: number[] = [];
|
|
2255
|
+
for (const ei of cand) {
|
|
2256
|
+
if (ei === cand[a] || ei === cand[b] || consumed.has(ei)) continue;
|
|
2257
|
+
const e = regions[ei];
|
|
2258
|
+
if (overlapsSpan(e, ra) || overlapsSpan(e, rb)) continue;
|
|
2259
|
+
const eb = query.subarray(e.start, e.end);
|
|
2260
|
+
if (indexOf(bytes, eb, 0) >= 0) {
|
|
2261
|
+
extras.push(ei);
|
|
2262
|
+
cov += eb.length;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
if (
|
|
2266
|
+
cov > bestCov ||
|
|
2267
|
+
(cov === bestCov && best !== null &&
|
|
2268
|
+
(c.interior.length < best.interior.length ||
|
|
2269
|
+
(c.interior.length === best.interior.length && c.id < best.id)))
|
|
2270
|
+
) {
|
|
2271
|
+
best = c;
|
|
2272
|
+
bestExtras = extras;
|
|
2273
|
+
bestCov = cov;
|
|
2274
|
+
}
|
|
1033
2275
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
// override evidence the query itself already resolved there.
|
|
1039
|
-
if (
|
|
1040
|
-
between.some((bi) =>
|
|
1041
|
-
indexOf(
|
|
1042
|
-
bytes,
|
|
1043
|
-
query.subarray(regions[bi].start, regions[bi].end),
|
|
1044
|
-
0,
|
|
1045
|
-
) < 0
|
|
1046
|
-
)
|
|
1047
|
-
) {
|
|
2276
|
+
if (best === null) {
|
|
2277
|
+
// every container was self-evidence / contradiction — outcome
|
|
2278
|
+
// stays "all-rejected".
|
|
2279
|
+
pushProbe("structural-rejected");
|
|
1048
2280
|
continue;
|
|
1049
2281
|
}
|
|
1050
|
-
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
if (
|
|
1054
|
-
|
|
1055
|
-
if (overlapsSpan(e, ra) || overlapsSpan(e, rb)) continue;
|
|
1056
|
-
const eb = query.subarray(e.start, e.end);
|
|
1057
|
-
if (indexOf(bytes, eb, 0) >= 0) {
|
|
1058
|
-
extras.push(ei);
|
|
1059
|
-
cov += eb.length;
|
|
2282
|
+
|
|
2283
|
+
const r = edgeAncestors(ctx, best.id, N, reachMemo);
|
|
2284
|
+
if (r.saturated || r.roots.length === 0) {
|
|
2285
|
+
if (structuralTrace) {
|
|
2286
|
+
structuralTrace.outcome = r.saturated ? "saturated" : "no-roots";
|
|
1060
2287
|
}
|
|
2288
|
+
pushProbe("structural-rejected");
|
|
2289
|
+
continue;
|
|
1061
2290
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
(
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
2291
|
+
const df = Math.log(N / Math.max(1, r.contextsReached));
|
|
2292
|
+
if (df <= 0) {
|
|
2293
|
+
if (structuralTrace) structuralTrace.outcome = "nonpositive-idf";
|
|
2294
|
+
pushProbe("structural-rejected");
|
|
2295
|
+
continue;
|
|
2296
|
+
}
|
|
2297
|
+
if (structuralTrace) {
|
|
2298
|
+
structuralTrace.outcome = "accepted";
|
|
2299
|
+
structuralTrace.selectedNode = best.id;
|
|
1071
2300
|
}
|
|
2301
|
+
reach = r;
|
|
2302
|
+
idf = df;
|
|
2303
|
+
// Confidence used by voting (spec §13): exact junction = 1;
|
|
2304
|
+
// single/double-synonym = the sibling(s)' score(s), carried on the
|
|
2305
|
+
// SynonymJunction the ladder selected.
|
|
2306
|
+
confidence = "confidence" in best ? best.confidence : 1;
|
|
1072
2307
|
}
|
|
1073
|
-
if (best === null) continue; // every container was self-evidence
|
|
1074
2308
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
//
|
|
1081
|
-
// every composed form. Mutual-explanation weight over their COMBINED
|
|
1082
|
-
// byte length — the same magnitude reading voteRegions uses, here with
|
|
1083
|
-
// the estimator collapsed to certainty, so mutual = min(ratio, 1/ratio).
|
|
2309
|
+
// MUTUAL-EXPLANATION WEIGHT — the same formula for every tier, with
|
|
2310
|
+
// `confidence` collapsed to certainty (1) for exact evidence: under
|
|
2311
|
+
// that collapse this is byte-for-byte the old exact-only formula
|
|
2312
|
+
// (min(1,ratio)·min(1,1/ratio)). For structural-resonance,
|
|
2313
|
+
// `confidence` is already annScore·semanticConfidence — never
|
|
2314
|
+
// multiplied a second time.
|
|
1084
2315
|
const lenR = Math.max(1, bestCov);
|
|
1085
2316
|
const ratio = Math.sqrt(
|
|
1086
2317
|
Math.max(1, ctx.store.contentLen(best.id, lenR * ctx.store.D)) / lenR,
|
|
1087
2318
|
);
|
|
1088
|
-
const mutual = Math.min(1,
|
|
2319
|
+
const mutual = Math.min(1, confidence * ratio) *
|
|
2320
|
+
Math.min(1, confidence / ratio);
|
|
1089
2321
|
const w = (mutual * idf) / reach.roots.length;
|
|
1090
2322
|
let spanStart = ra.start;
|
|
1091
2323
|
let spanEnd = rb.end;
|
|
@@ -1107,15 +2339,33 @@ async function crossRegionVotes(
|
|
|
1107
2339
|
// for, not evidence lost — `absorbed` (RegionVote's breadth-accounting
|
|
1108
2340
|
// field) must credit the junction with all of it, not just the ONE
|
|
1109
2341
|
// pooled axiom it collapses to.
|
|
1110
|
-
|
|
1111
|
-
|
|
2342
|
+
// Only EXACT DAG evidence may explain away ordinary votes (spec §15).
|
|
2343
|
+
// Single-synonym, double-synonym, and structural-resonance may ADD
|
|
2344
|
+
// supporting evidence but never remove it: their evidence is itself
|
|
2345
|
+
// approximate (a sibling substitution, or an ANN guess), so treating
|
|
2346
|
+
// their byte-containment the way exact containment is treated would
|
|
2347
|
+
// let an approximation override a genuine, independently-voted region.
|
|
1112
2348
|
let explainedAway = 0;
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
2349
|
+
// Exact set of ORIGINAL region indices this junction explained away —
|
|
2350
|
+
// recorded live as `superseded.add` fires (spec §3's explicit rule:
|
|
2351
|
+
// never inferred from `absorbed` afterward).
|
|
2352
|
+
const explainedAwayIndices: number[] = [];
|
|
2353
|
+
if (tier === "exact") {
|
|
2354
|
+
const containerBytes = cachedRead(ctx, cache, best.id, cap);
|
|
2355
|
+
const jointRoots = new Set(reach.roots);
|
|
2356
|
+
for (const rv of rvs.votes) {
|
|
2357
|
+
if (rv.roots.some((r) => jointRoots.has(r))) continue;
|
|
2358
|
+
const bytes = query.subarray(rv.start, rv.end);
|
|
2359
|
+
if (indexOf(containerBytes, bytes, 0) >= 0 && !superseded.has(rv)) {
|
|
2360
|
+
superseded.add(rv);
|
|
2361
|
+
explainedAway++;
|
|
2362
|
+
if (td) {
|
|
2363
|
+
const idx = regions.findIndex((r) =>
|
|
2364
|
+
r.start === rv.start && r.end === rv.end
|
|
2365
|
+
);
|
|
2366
|
+
if (idx >= 0) explainedAwayIndices.push(idx);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
1119
2369
|
}
|
|
1120
2370
|
}
|
|
1121
2371
|
|
|
@@ -1128,11 +2378,50 @@ async function crossRegionVotes(
|
|
|
1128
2378
|
wFocus: w,
|
|
1129
2379
|
absorbed: 1 + explainedAway,
|
|
1130
2380
|
});
|
|
2381
|
+
pushProbe("accepted");
|
|
2382
|
+
if (td) {
|
|
2383
|
+
td.crossRegionJunctionVotes.push({
|
|
2384
|
+
container: best.id,
|
|
2385
|
+
span: [spanStart, spanEnd],
|
|
2386
|
+
roots: [...reach.roots],
|
|
2387
|
+
sourceRegionIndices: [cand[a], cand[b], ...bestExtras],
|
|
2388
|
+
explainedAwayRegionIndices: explainedAwayIndices,
|
|
2389
|
+
absorbed: 1 + explainedAway,
|
|
2390
|
+
tier,
|
|
2391
|
+
probe: td.crossRegionProbes.length - 1,
|
|
2392
|
+
confidence,
|
|
2393
|
+
evidenceBytes: bestCov,
|
|
2394
|
+
mutualWeight: mutual,
|
|
2395
|
+
voteWeightPerRoot: w,
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
1131
2398
|
|
|
1132
2399
|
const label = [cand[a], cand[b], ...bestExtras]
|
|
1133
2400
|
.sort((x, y) => regions[x].start - regions[y].start)
|
|
1134
2401
|
.map((ri) => dec(query.subarray(regions[ri].start, regions[ri].end)))
|
|
1135
2402
|
.join(" ▸ ");
|
|
2403
|
+
const tierNote = tier === "exact"
|
|
2404
|
+
? `junction node ${best.id}` +
|
|
2405
|
+
(best.interior.length === 0
|
|
2406
|
+
? " (adjacent)"
|
|
2407
|
+
: ` (interior "${dec(best.interior)}")`) +
|
|
2408
|
+
", by content-addressed ascent"
|
|
2409
|
+
: tier === "structural-resonance"
|
|
2410
|
+
? `structurally-composed ANN proposal, node ${best.id} — the query ` +
|
|
2411
|
+
`structurally composed the endpoint regions, the real middle-` +
|
|
2412
|
+
`query structure, and the selected halo-sibling endpoint ` +
|
|
2413
|
+
`direction(s) (variant ${structuralPick!.proposal.variant}, ` +
|
|
2414
|
+
`annScore ${structuralPick!.proposal.annScore.toFixed(3)} × ` +
|
|
2415
|
+
`semanticConfidence ${
|
|
2416
|
+
structuralPick!.proposal.semanticConfidence.toFixed(3)
|
|
2417
|
+
} = effectiveScore ${
|
|
2418
|
+
structuralPick!.proposal.effectiveScore.toFixed(3)
|
|
2419
|
+
}); it did not concatenate endpoint bytes or rewrite the query`
|
|
2420
|
+
: `${tier} junction node ${best.id}` +
|
|
2421
|
+
(best.interior.length === 0
|
|
2422
|
+
? " (adjacent)"
|
|
2423
|
+
: ` (interior "${dec(best.interior)}")`) +
|
|
2424
|
+
`, by halo-sibling DAG ascent (confidence ${confidence.toFixed(3)})`;
|
|
1136
2425
|
ctx.trace?.step(
|
|
1137
2426
|
"crossRegion",
|
|
1138
2427
|
[{ text: label, role: "pair" }],
|
|
@@ -1141,11 +2430,7 @@ async function crossRegionVotes(
|
|
|
1141
2430
|
node: r,
|
|
1142
2431
|
role: "joint-context",
|
|
1143
2432
|
})),
|
|
1144
|
-
`${label} →
|
|
1145
|
-
(best.interior.length === 0
|
|
1146
|
-
? " (adjacent)"
|
|
1147
|
-
: ` (interior "${dec(best.interior)}")`) +
|
|
1148
|
-
` → ${reach.roots.length} context(s), by content-addressed ascent` +
|
|
2433
|
+
`${label} → ${tierNote} → ${reach.roots.length} context(s)` +
|
|
1149
2434
|
(superseded.size > 0
|
|
1150
2435
|
? `; ${superseded.size} aliasing vote(s) explained away`
|
|
1151
2436
|
: ""),
|
|
@@ -1154,15 +2439,30 @@ async function crossRegionVotes(
|
|
|
1154
2439
|
}
|
|
1155
2440
|
}
|
|
1156
2441
|
|
|
2442
|
+
if (td) td.supersededOrdinaryVotes = superseded.size;
|
|
2443
|
+
if (td?.crossRegionSummary) {
|
|
2444
|
+
td.crossRegionSummary.stopReason = probes >= k
|
|
2445
|
+
? "probe-limit"
|
|
2446
|
+
: "pairs-exhausted";
|
|
2447
|
+
}
|
|
1157
2448
|
return { votes: out, superseded };
|
|
1158
2449
|
}
|
|
1159
2450
|
|
|
2451
|
+
/** Emit the "climbConsensus" step — the human-readable note this always
|
|
2452
|
+
* produced, now paired (when `ctx.trace` and `cfg` are both present) with
|
|
2453
|
+
* the structured {@link ClimbConsensusData} payload on the SAME step's
|
|
2454
|
+
* `data` field. Every exit of {@link computeAttention} funnels through
|
|
2455
|
+
* here, so instrumentation and the existing rationale text can never drift
|
|
2456
|
+
* apart — see the instrumentation spec's §9 "every exit path". */
|
|
1160
2457
|
export function traceAttention(
|
|
1161
2458
|
ctx: MindContext,
|
|
1162
2459
|
regions: ReadonlyArray<{ start: number; end: number }>,
|
|
1163
2460
|
regionVoter: ReadonlyArray<{ id: number; score: number; w: number } | null>,
|
|
1164
2461
|
roots: ReadonlyArray<Attention>,
|
|
1165
2462
|
steps: ReadonlyArray<DerivationStep> = [],
|
|
2463
|
+
td?: TraceDraft,
|
|
2464
|
+
cfg?: ClimbConsensusCfg,
|
|
2465
|
+
ranked: ReadonlyArray<Attention> = roots,
|
|
1166
2466
|
): void {
|
|
1167
2467
|
if (!ctx.trace) return;
|
|
1168
2468
|
const voters: RationaleItem[] = [];
|
|
@@ -1177,6 +2477,54 @@ export function traceAttention(
|
|
|
1177
2477
|
// The pooled-evidence decision, one DerivationStep per anchor — the same
|
|
1178
2478
|
// shape {@link GraphSearch}'s own cover steps take (see traceDerivation).
|
|
1179
2479
|
if (steps.length > 0) traceDerivation(ctx, steps);
|
|
2480
|
+
|
|
2481
|
+
const data: ClimbConsensusData | undefined = (td && cfg)
|
|
2482
|
+
? {
|
|
2483
|
+
version: 1,
|
|
2484
|
+
cache: { hit: false, detailAvailable: true },
|
|
2485
|
+
config: {
|
|
2486
|
+
annK: cfg.k,
|
|
2487
|
+
crossRegionProbeLimit: cfg.k,
|
|
2488
|
+
mode: cfg.mode,
|
|
2489
|
+
...(cfg.N !== undefined ? { corpusN: cfg.N } : {}),
|
|
2490
|
+
dimension: ctx.store.D,
|
|
2491
|
+
...(cfg.N !== undefined ? { hubBound: hubBound(ctx) } : {}),
|
|
2492
|
+
estimatorNoise: estimatorNoise(ctx.store.D),
|
|
2493
|
+
...(cfg.naturalBreak !== undefined
|
|
2494
|
+
? { naturalBreak: cfg.naturalBreak }
|
|
2495
|
+
: {}),
|
|
2496
|
+
...(cfg.consensusFloor !== undefined
|
|
2497
|
+
? { consensusFloor: cfg.consensusFloor }
|
|
2498
|
+
: {}),
|
|
2499
|
+
},
|
|
2500
|
+
candidates: {
|
|
2501
|
+
perceived: cfg.perceivedCount,
|
|
2502
|
+
recognised: cfg.totalRegions - cfg.perceivedCount,
|
|
2503
|
+
total: cfg.totalRegions,
|
|
2504
|
+
},
|
|
2505
|
+
...(td.regions.length > 0 ? { regions: td.regions } : {}),
|
|
2506
|
+
...(cfg.reachMemo ? { reaches: serialiseReaches(cfg.reachMemo) } : {}),
|
|
2507
|
+
...(td.crossRegionSummary
|
|
2508
|
+
? {
|
|
2509
|
+
crossRegion: {
|
|
2510
|
+
eligibleRegions: td.crossRegionSummary.eligibleRegions,
|
|
2511
|
+
maximalRegions: td.crossRegionSummary.maximalRegions,
|
|
2512
|
+
probeLimit: td.crossRegionSummary.probeLimit,
|
|
2513
|
+
probesAttempted: td.crossRegionSummary.probesAttempted,
|
|
2514
|
+
junctionVotes: td.crossRegionJunctionVotes,
|
|
2515
|
+
supersededOrdinaryVotes: td.supersededOrdinaryVotes,
|
|
2516
|
+
probes: td.crossRegionProbes,
|
|
2517
|
+
stopReason: td.crossRegionSummary.stopReason ?? "pairs-exhausted",
|
|
2518
|
+
},
|
|
2519
|
+
}
|
|
2520
|
+
: {}),
|
|
2521
|
+
...(td.saturation ? { saturation: td.saturation } : {}),
|
|
2522
|
+
...(td.pooling ? { pooling: td.pooling } : {}),
|
|
2523
|
+
...(td.anchors.length > 0 ? { anchors: td.anchors } : {}),
|
|
2524
|
+
result: { roots: [...roots], ranked: [...ranked] },
|
|
2525
|
+
}
|
|
2526
|
+
: undefined;
|
|
2527
|
+
|
|
1180
2528
|
t.done(
|
|
1181
2529
|
roots.map((r) => rNode(ctx, r.anchor, "anchor", r.vote)),
|
|
1182
2530
|
roots.length === 0
|
|
@@ -1188,5 +2536,6 @@ export function traceAttention(
|
|
|
1188
2536
|
: `${voters.length} of ${regions.length} sub-regions voted; consensus ordered ${roots.length} INDEPENDENT points of attention (votes ${
|
|
1189
2537
|
roots.map((r) => r.vote.toFixed(2)).join(", ")
|
|
1190
2538
|
})`,
|
|
2539
|
+
data,
|
|
1191
2540
|
);
|
|
1192
2541
|
}
|