@hviana/sema 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -15,6 +15,15 @@ import { type JunctionSynonymSides } from "./junction.js";
|
|
|
15
15
|
* spec's original two-value type cannot do. */
|
|
16
16
|
export type ClimbConsensusJunctionTier = CrossRegionTier;
|
|
17
17
|
export type RegionOutcome = "voted" | "no-ann-hit" | "no-structural-reach" | "saturated-abstention" | "nonpositive-df-weight" | "contrastive-margin-rejection";
|
|
18
|
+
/** The best DIFFERENT-conclusion rival the contrastive-margin gate found
|
|
19
|
+
* while scanning an ordinary (approximate) region's ANN hits — spec §1.
|
|
20
|
+
* Its roots/saturation/contextsReached are already available through
|
|
21
|
+
* `reaches` (serialiseReaches) for `node`; not duplicated here. */
|
|
22
|
+
export interface ConsensusContrastiveRivalTrace {
|
|
23
|
+
node: number;
|
|
24
|
+
rank: number;
|
|
25
|
+
score: number;
|
|
26
|
+
}
|
|
18
27
|
export interface ConsensusRegionTrace {
|
|
19
28
|
index: number;
|
|
20
29
|
source: "perceived" | "recognised";
|
|
@@ -40,6 +49,7 @@ export interface ConsensusRegionTrace {
|
|
|
40
49
|
dfWeight?: number;
|
|
41
50
|
contrastiveMargin?: number;
|
|
42
51
|
contrastiveNoiseFloor?: number;
|
|
52
|
+
contrastiveRival?: ConsensusContrastiveRivalTrace;
|
|
43
53
|
mutualWeight?: number;
|
|
44
54
|
voteWeightPerRoot?: number;
|
|
45
55
|
focusWeightPerRoot?: number;
|
|
@@ -82,6 +92,77 @@ export interface JunctionVoteTrace {
|
|
|
82
92
|
explainedAwayRegionIndices: number[];
|
|
83
93
|
absorbed: number;
|
|
84
94
|
tier?: ClimbConsensusJunctionTier;
|
|
95
|
+
/** Zero-based index into `crossRegion.probes` — the probe this vote was
|
|
96
|
+
* produced from (spec §8). */
|
|
97
|
+
probe: number;
|
|
98
|
+
confidence: number;
|
|
99
|
+
/** "Evidence bytes" — the container-coverage byte count (the existing
|
|
100
|
+
* `bestCov` variable at the push site). */
|
|
101
|
+
evidenceBytes: number;
|
|
102
|
+
mutualWeight: number;
|
|
103
|
+
voteWeightPerRoot: number;
|
|
104
|
+
}
|
|
105
|
+
/** Whether one DAG/synonym tier attempt was even made for a probe, and how
|
|
106
|
+
* many candidate containers it returned — spec §2/§3. */
|
|
107
|
+
export interface CrossRegionTierAttemptTrace {
|
|
108
|
+
attempted: boolean;
|
|
109
|
+
candidatesReturned: number;
|
|
110
|
+
}
|
|
111
|
+
/** Aggregate outcome of the container-selection loop for a DAG/synonym tier
|
|
112
|
+
* that returned at least one container — spec §4. Only aggregate counts
|
|
113
|
+
* and the final outcome are recorded, never every candidate. */
|
|
114
|
+
export interface CrossRegionStructuralTrace {
|
|
115
|
+
tier: "exact" | "single-synonym" | "double-synonym";
|
|
116
|
+
selfEvidenceRejected: number;
|
|
117
|
+
contradictionRejected: number;
|
|
118
|
+
passedGuards: number;
|
|
119
|
+
selectedNode?: number;
|
|
120
|
+
outcome: "all-rejected" | "saturated" | "no-roots" | "nonpositive-idf" | "accepted";
|
|
121
|
+
}
|
|
122
|
+
/** One retained structural-resonance variant that actually issued its own
|
|
123
|
+
* ANN query — spec §5. */
|
|
124
|
+
export interface StructuralResonanceVariantTrace {
|
|
125
|
+
kind: StructuralVariant["kind"];
|
|
126
|
+
semanticConfidence: number;
|
|
127
|
+
leftSiblingId?: number;
|
|
128
|
+
rightSiblingId?: number;
|
|
129
|
+
annHitsReturned: number;
|
|
130
|
+
}
|
|
131
|
+
/** One merged structural-resonance proposal actually examined via
|
|
132
|
+
* edgeAncestors — spec §5. Retains node/variant/scores, but NOT
|
|
133
|
+
* roots/saturation/contextsReached/idf (already in `reaches`). */
|
|
134
|
+
export interface StructuralResonanceCandidateTrace {
|
|
135
|
+
node: number;
|
|
136
|
+
variant: StructuralVariant["kind"];
|
|
137
|
+
leftSiblingId?: number;
|
|
138
|
+
rightSiblingId?: number;
|
|
139
|
+
annScore: number;
|
|
140
|
+
semanticConfidence: number;
|
|
141
|
+
effectiveScore: number;
|
|
142
|
+
outcome: "saturated" | "no-roots" | "nonpositive-idf" | "same-as-endpoint" | "same-as-selected" | "selected" | "contrastive-rival";
|
|
143
|
+
}
|
|
144
|
+
export interface StructuralResonanceTrace {
|
|
145
|
+
variantBudget: number;
|
|
146
|
+
variants: StructuralResonanceVariantTrace[];
|
|
147
|
+
mergedProposals: number;
|
|
148
|
+
examined: StructuralResonanceCandidateTrace[];
|
|
149
|
+
contrastiveMargin?: number;
|
|
150
|
+
noiseFloor: number;
|
|
151
|
+
outcome: "ineligible" | "empty" | "no-valid-proposal" | "margin-rejected" | "accepted";
|
|
152
|
+
ineligibleReasons?: Array<"between-region" | "not-both-strong" | "not-both-known" | "gap-too-large">;
|
|
153
|
+
}
|
|
154
|
+
/** One cross-region pair the ladder actually probed — spec §2. Exactly one
|
|
155
|
+
* of these is pushed per pair that incremented `probes`. */
|
|
156
|
+
export interface CrossRegionProbeTrace {
|
|
157
|
+
leftRegionIndex: number;
|
|
158
|
+
rightRegionIndex: number;
|
|
159
|
+
betweenRegionIndices: number[];
|
|
160
|
+
exact: CrossRegionTierAttemptTrace;
|
|
161
|
+
singleSynonym: CrossRegionTierAttemptTrace;
|
|
162
|
+
doubleSynonym: CrossRegionTierAttemptTrace;
|
|
163
|
+
structural?: CrossRegionStructuralTrace;
|
|
164
|
+
resonance?: StructuralResonanceTrace;
|
|
165
|
+
outcome: "accepted" | "structural-rejected" | "resonance-ineligible" | "resonance-rejected";
|
|
85
166
|
}
|
|
86
167
|
export interface ClimbConsensusData {
|
|
87
168
|
version: 1;
|
|
@@ -114,6 +195,8 @@ export interface ClimbConsensusData {
|
|
|
114
195
|
probesAttempted: number;
|
|
115
196
|
junctionVotes: JunctionVoteTrace[];
|
|
116
197
|
supersededOrdinaryVotes: number;
|
|
198
|
+
probes: CrossRegionProbeTrace[];
|
|
199
|
+
stopReason: "insufficient-regions" | "probe-limit" | "pairs-exhausted";
|
|
117
200
|
};
|
|
118
201
|
saturation?: {
|
|
119
202
|
regionIntervals: Array<{
|
|
@@ -145,7 +228,9 @@ interface TraceDraft {
|
|
|
145
228
|
maximalRegions: number;
|
|
146
229
|
probeLimit: number;
|
|
147
230
|
probesAttempted: number;
|
|
231
|
+
stopReason?: "insufficient-regions" | "probe-limit" | "pairs-exhausted";
|
|
148
232
|
};
|
|
233
|
+
crossRegionProbes: CrossRegionProbeTrace[];
|
|
149
234
|
supersededOrdinaryVotes: number;
|
|
150
235
|
saturation?: {
|
|
151
236
|
regionIntervals: Array<{
|
|
@@ -293,7 +378,7 @@ export declare function structuralResonance(ctx: MindContext, query: Uint8Array,
|
|
|
293
378
|
* candidate whose reach is exactly one side's own conclusion is not new
|
|
294
379
|
* evidence of a joint whole; it is that side's resonance rediscovering
|
|
295
380
|
* itself through a synthetic gist still dominated by its own direction. */
|
|
296
|
-
ownRootsA: readonly number[] | undefined, ownRootsB: readonly number[] | undefined): Promise<{
|
|
381
|
+
ownRootsA: readonly number[] | undefined, ownRootsB: readonly number[] | undefined, trace?: StructuralResonanceTrace): Promise<{
|
|
297
382
|
proposal: StructuralResonanceProposal;
|
|
298
383
|
reach: AncestorReach;
|
|
299
384
|
idf: number;
|
|
@@ -21,6 +21,7 @@ function newTraceDraft(perceivedCount) {
|
|
|
21
21
|
perceivedCount,
|
|
22
22
|
regions: [],
|
|
23
23
|
crossRegionJunctionVotes: [],
|
|
24
|
+
crossRegionProbes: [],
|
|
24
25
|
supersededOrdinaryVotes: 0,
|
|
25
26
|
anchors: [],
|
|
26
27
|
};
|
|
@@ -416,9 +417,12 @@ export async function voteRegions(ctx, query, regions, k, mode, N, reachMemo, td
|
|
|
416
417
|
// nothing. Gating at the noise floor keeps frame-echo suppression (a frame
|
|
417
418
|
// region's margin ≈ 0 is gated out) without penalising honest evidence.
|
|
418
419
|
let contrastiveMargin;
|
|
420
|
+
let contrastiveRival;
|
|
419
421
|
if (!known) {
|
|
420
422
|
let margin = score;
|
|
421
|
-
|
|
423
|
+
const hitsForRival = await ensureHits();
|
|
424
|
+
for (let hi = 0; hi < hitsForRival.length; hi++) {
|
|
425
|
+
const h = hitsForRival[hi];
|
|
422
426
|
if (h.id === voterId)
|
|
423
427
|
continue;
|
|
424
428
|
const r2 = edgeAncestors(ctx, h.id, N, reachMemo);
|
|
@@ -428,6 +432,9 @@ export async function voteRegions(ctx, query, regions, k, mode, N, reachMemo, td
|
|
|
428
432
|
if (sameRoots(r2.roots, reach.roots))
|
|
429
433
|
continue; // same conclusion
|
|
430
434
|
margin = score - h.score; // hits are nearest-first: the best rival
|
|
435
|
+
if (td) {
|
|
436
|
+
contrastiveRival = { node: h.id, rank: hi, score: h.score };
|
|
437
|
+
}
|
|
431
438
|
break;
|
|
432
439
|
}
|
|
433
440
|
contrastiveMargin = margin;
|
|
@@ -440,6 +447,7 @@ export async function voteRegions(ctx, query, regions, k, mode, N, reachMemo, td
|
|
|
440
447
|
dfWeight: wf,
|
|
441
448
|
contrastiveMargin: margin,
|
|
442
449
|
contrastiveNoiseFloor: noiseFloor,
|
|
450
|
+
...(contrastiveRival ? { contrastiveRival } : {}),
|
|
443
451
|
});
|
|
444
452
|
continue;
|
|
445
453
|
}
|
|
@@ -488,6 +496,7 @@ export async function voteRegions(ctx, query, regions, k, mode, N, reachMemo, td
|
|
|
488
496
|
? {
|
|
489
497
|
contrastiveMargin,
|
|
490
498
|
contrastiveNoiseFloor: estimatorNoise(ctx.store.D),
|
|
499
|
+
...(contrastiveRival ? { contrastiveRival } : {}),
|
|
491
500
|
}
|
|
492
501
|
: {}),
|
|
493
502
|
mutualWeight: mutual,
|
|
@@ -1011,8 +1020,10 @@ export async function structuralResonance(ctx, query, ra, rb, sides, k, N, reach
|
|
|
1011
1020
|
* candidate whose reach is exactly one side's own conclusion is not new
|
|
1012
1021
|
* evidence of a joint whole; it is that side's resonance rediscovering
|
|
1013
1022
|
* itself through a synthetic gist still dominated by its own direction. */
|
|
1014
|
-
ownRootsA, ownRootsB) {
|
|
1023
|
+
ownRootsA, ownRootsB, trace) {
|
|
1015
1024
|
const { variants } = buildStructuralVariants(ctx, ra, rb, sides);
|
|
1025
|
+
if (trace)
|
|
1026
|
+
trace.variantBudget = ctx.cfg.haloQueryK;
|
|
1016
1027
|
const middleBytes = query.subarray(ra.end, rb.start);
|
|
1017
1028
|
const middlePart = middleBytes.length === 0
|
|
1018
1029
|
? null
|
|
@@ -1025,6 +1036,15 @@ ownRootsA, ownRootsB) {
|
|
|
1025
1036
|
parts.push(variant.right);
|
|
1026
1037
|
const synthetic = composeStructuralGist(ctx.space, parts);
|
|
1027
1038
|
const hits = await ctx.store.resonate(synthetic, k);
|
|
1039
|
+
if (trace) {
|
|
1040
|
+
trace.variants.push({
|
|
1041
|
+
kind: variant.kind,
|
|
1042
|
+
semanticConfidence: variant.semanticConfidence,
|
|
1043
|
+
leftSiblingId: variant.leftSiblingId,
|
|
1044
|
+
rightSiblingId: variant.rightSiblingId,
|
|
1045
|
+
annHitsReturned: hits.length,
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1028
1048
|
for (const hit of hits) {
|
|
1029
1049
|
const candidate = {
|
|
1030
1050
|
id: hit.id,
|
|
@@ -1041,43 +1061,92 @@ ownRootsA, ownRootsB) {
|
|
|
1041
1061
|
}
|
|
1042
1062
|
}
|
|
1043
1063
|
}
|
|
1044
|
-
if (
|
|
1064
|
+
if (trace)
|
|
1065
|
+
trace.mergedProposals = proposals.size;
|
|
1066
|
+
if (proposals.size === 0) {
|
|
1067
|
+
if (trace) {
|
|
1068
|
+
trace.noiseFloor = estimatorNoise(ctx.store.D);
|
|
1069
|
+
trace.outcome = "empty";
|
|
1070
|
+
}
|
|
1045
1071
|
return null;
|
|
1072
|
+
}
|
|
1046
1073
|
const sorted = [...proposals.values()].sort((a, b) => b.effectiveScore - a.effectiveScore || a.id - b.id);
|
|
1074
|
+
// One shared shape for every `examined` entry (spec §5): only `outcome`
|
|
1075
|
+
// varies across the six exit points below, so build it once instead of
|
|
1076
|
+
// repeating the six-field literal at each site.
|
|
1077
|
+
const recordExamined = (p, outcome) => {
|
|
1078
|
+
if (!trace)
|
|
1079
|
+
return;
|
|
1080
|
+
trace.examined.push({
|
|
1081
|
+
node: p.id,
|
|
1082
|
+
variant: p.variant,
|
|
1083
|
+
leftSiblingId: p.leftSiblingId,
|
|
1084
|
+
rightSiblingId: p.rightSiblingId,
|
|
1085
|
+
annScore: p.annScore,
|
|
1086
|
+
semanticConfidence: p.semanticConfidence,
|
|
1087
|
+
effectiveScore: p.effectiveScore,
|
|
1088
|
+
outcome,
|
|
1089
|
+
});
|
|
1090
|
+
};
|
|
1047
1091
|
let selected = null;
|
|
1048
1092
|
let selectedReach = null;
|
|
1049
1093
|
let selectedIdf = 0;
|
|
1050
1094
|
let rival = null;
|
|
1051
1095
|
for (const p of sorted) {
|
|
1052
1096
|
const reach = edgeAncestors(ctx, p.id, N, reachMemo);
|
|
1053
|
-
if (reach.saturated || reach.roots.length === 0)
|
|
1097
|
+
if (reach.saturated || reach.roots.length === 0) {
|
|
1098
|
+
recordExamined(p, reach.saturated ? "saturated" : "no-roots");
|
|
1054
1099
|
continue;
|
|
1100
|
+
}
|
|
1055
1101
|
const idf = Math.log(N / Math.max(1, reach.contextsReached));
|
|
1056
|
-
if (idf <= 0)
|
|
1102
|
+
if (idf <= 0) {
|
|
1103
|
+
recordExamined(p, "nonpositive-idf");
|
|
1057
1104
|
continue;
|
|
1105
|
+
}
|
|
1058
1106
|
// Self-evidence backstop (see the param doc above): a candidate that is
|
|
1059
1107
|
// exactly one side's own already-voted conclusion carries no JOINT
|
|
1060
1108
|
// evidence — skip it as if it never survived.
|
|
1061
1109
|
if ((ownRootsA && sameRoots(reach.roots, ownRootsA)) ||
|
|
1062
|
-
(ownRootsB && sameRoots(reach.roots, ownRootsB)))
|
|
1110
|
+
(ownRootsB && sameRoots(reach.roots, ownRootsB))) {
|
|
1111
|
+
recordExamined(p, "same-as-endpoint");
|
|
1063
1112
|
continue;
|
|
1113
|
+
}
|
|
1064
1114
|
if (selected === null) {
|
|
1065
1115
|
selected = p;
|
|
1066
1116
|
selectedReach = reach;
|
|
1067
1117
|
selectedIdf = idf;
|
|
1118
|
+
recordExamined(p, "selected");
|
|
1068
1119
|
}
|
|
1069
1120
|
else if (!sameRoots(reach.roots, selectedReach.roots)) {
|
|
1070
1121
|
rival = p;
|
|
1122
|
+
recordExamined(p, "contrastive-rival");
|
|
1071
1123
|
break;
|
|
1072
1124
|
}
|
|
1125
|
+
else {
|
|
1126
|
+
recordExamined(p, "same-as-selected");
|
|
1127
|
+
}
|
|
1073
1128
|
}
|
|
1074
|
-
if (selected === null || selectedReach === null)
|
|
1129
|
+
if (selected === null || selectedReach === null) {
|
|
1130
|
+
if (trace) {
|
|
1131
|
+
trace.noiseFloor = estimatorNoise(ctx.store.D);
|
|
1132
|
+
trace.outcome = "no-valid-proposal";
|
|
1133
|
+
}
|
|
1075
1134
|
return null;
|
|
1135
|
+
}
|
|
1076
1136
|
const margin = rival
|
|
1077
1137
|
? selected.effectiveScore - rival.effectiveScore
|
|
1078
1138
|
: selected.effectiveScore;
|
|
1079
|
-
if (
|
|
1139
|
+
if (trace) {
|
|
1140
|
+
trace.contrastiveMargin = margin;
|
|
1141
|
+
trace.noiseFloor = estimatorNoise(ctx.store.D);
|
|
1142
|
+
}
|
|
1143
|
+
if (margin <= estimatorNoise(ctx.store.D)) {
|
|
1144
|
+
if (trace)
|
|
1145
|
+
trace.outcome = "margin-rejected";
|
|
1080
1146
|
return null;
|
|
1147
|
+
}
|
|
1148
|
+
if (trace)
|
|
1149
|
+
trace.outcome = "accepted";
|
|
1081
1150
|
return { proposal: selected, reach: selectedReach, idf: selectedIdf };
|
|
1082
1151
|
}
|
|
1083
1152
|
async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
@@ -1136,6 +1205,7 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1136
1205
|
maximalRegions: cand.length,
|
|
1137
1206
|
probeLimit: k,
|
|
1138
1207
|
probesAttempted: 0, // updated below as probes accrue
|
|
1208
|
+
stopReason: cand.length < 2 ? "insufficient-regions" : undefined,
|
|
1139
1209
|
};
|
|
1140
1210
|
}
|
|
1141
1211
|
if (cand.length < 2)
|
|
@@ -1205,6 +1275,30 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1205
1275
|
if (td?.crossRegionSummary) {
|
|
1206
1276
|
td.crossRegionSummary.probesAttempted = probes;
|
|
1207
1277
|
}
|
|
1278
|
+
// Trace-only per-probe bookkeeping (spec §2-§7) — built incrementally
|
|
1279
|
+
// as the ladder runs, pushed exactly once at whichever exit fires
|
|
1280
|
+
// below. `pushProbe` is called at every continue/success exit for
|
|
1281
|
+
// THIS pair so the invariant `probes.length === probesAttempted`
|
|
1282
|
+
// holds regardless of which tier settled it.
|
|
1283
|
+
const probe = td
|
|
1284
|
+
? {
|
|
1285
|
+
leftRegionIndex: cand[a],
|
|
1286
|
+
rightRegionIndex: cand[b],
|
|
1287
|
+
betweenRegionIndices: [...between],
|
|
1288
|
+
exact: { attempted: false, candidatesReturned: 0 },
|
|
1289
|
+
singleSynonym: { attempted: false, candidatesReturned: 0 },
|
|
1290
|
+
doubleSynonym: { attempted: false, candidatesReturned: 0 },
|
|
1291
|
+
outcome: "structural-rejected",
|
|
1292
|
+
}
|
|
1293
|
+
: undefined;
|
|
1294
|
+
let probePushed = false;
|
|
1295
|
+
const pushProbe = (outcome) => {
|
|
1296
|
+
if (!td || !probe || probePushed)
|
|
1297
|
+
return;
|
|
1298
|
+
probe.outcome = outcome;
|
|
1299
|
+
td.crossRegionProbes.push(probe);
|
|
1300
|
+
probePushed = true;
|
|
1301
|
+
};
|
|
1208
1302
|
const left = query.subarray(ra.start, ra.end);
|
|
1209
1303
|
const right = query.subarray(rb.start, rb.end);
|
|
1210
1304
|
// Phrase-scale contract, exactly as the bridge: the glue between the two
|
|
@@ -1219,11 +1313,37 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1219
1313
|
const sides = await loadJunctionSynonymSides(ctx, left, right);
|
|
1220
1314
|
let tier = "exact";
|
|
1221
1315
|
let containers = junctionContainersFrom(ctx, left, right, cap, seedsOf(cand[a]), seedsOf(cand[b]), undefined, true);
|
|
1316
|
+
if (probe) {
|
|
1317
|
+
probe.exact = {
|
|
1318
|
+
attempted: true,
|
|
1319
|
+
candidatesReturned: containers.length,
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1222
1322
|
if (containers.length === 0) {
|
|
1223
1323
|
// Tiers 2-4 — synonym containers (junctionSynonyms itself runs
|
|
1224
1324
|
// single-synonym first, falling to double-synonym only when
|
|
1225
1325
|
// single-synonym found nothing — see junction.ts).
|
|
1226
1326
|
const syn = await junctionSynonyms(ctx, left, right, maxInterior, true, sides);
|
|
1327
|
+
if (probe) {
|
|
1328
|
+
const singleAttempted = sides.leftSiblings.length > 0 ||
|
|
1329
|
+
sides.rightSiblings.length > 0;
|
|
1330
|
+
const singleReturned = syn[0]?.tier === "single-synonym"
|
|
1331
|
+
? syn.length
|
|
1332
|
+
: 0;
|
|
1333
|
+
const doubleAttempted = singleAttempted && singleReturned === 0 &&
|
|
1334
|
+
sides.leftSiblings.length > 0 && sides.rightSiblings.length > 0;
|
|
1335
|
+
const doubleReturned = syn[0]?.tier === "double-synonym"
|
|
1336
|
+
? syn.length
|
|
1337
|
+
: 0;
|
|
1338
|
+
probe.singleSynonym = {
|
|
1339
|
+
attempted: singleAttempted,
|
|
1340
|
+
candidatesReturned: singleReturned,
|
|
1341
|
+
};
|
|
1342
|
+
probe.doubleSynonym = {
|
|
1343
|
+
attempted: doubleAttempted,
|
|
1344
|
+
candidatesReturned: doubleReturned,
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1227
1347
|
if (syn.length > 0) {
|
|
1228
1348
|
containers = syn;
|
|
1229
1349
|
tier = syn[0].tier;
|
|
@@ -1262,16 +1382,55 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1262
1382
|
// backstop, so both sides earn their place here the same way an
|
|
1263
1383
|
// ordinary approximate region earns its individual vote.
|
|
1264
1384
|
const gap = rb.start - ra.end;
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1385
|
+
const reasons = [];
|
|
1386
|
+
if (between.length > 0)
|
|
1387
|
+
reasons.push("between-region");
|
|
1388
|
+
if (!strong.has(cand[a]) || !strong.has(cand[b])) {
|
|
1389
|
+
reasons.push("not-both-strong");
|
|
1390
|
+
}
|
|
1391
|
+
if (!ra.known || !rb.known)
|
|
1392
|
+
reasons.push("not-both-known");
|
|
1393
|
+
if (gap > maxInterior)
|
|
1394
|
+
reasons.push("gap-too-large");
|
|
1395
|
+
let resonanceTrace;
|
|
1396
|
+
if (reasons.length > 0) {
|
|
1397
|
+
if (probe) {
|
|
1398
|
+
resonanceTrace = {
|
|
1399
|
+
variantBudget: ctx.cfg.haloQueryK,
|
|
1400
|
+
variants: [],
|
|
1401
|
+
mergedProposals: 0,
|
|
1402
|
+
examined: [],
|
|
1403
|
+
noiseFloor: estimatorNoise(ctx.store.D),
|
|
1404
|
+
outcome: "ineligible",
|
|
1405
|
+
ineligibleReasons: reasons,
|
|
1406
|
+
};
|
|
1407
|
+
probe.resonance = resonanceTrace;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
else {
|
|
1411
|
+
if (probe) {
|
|
1412
|
+
// `outcome`/`noiseFloor` are required fields with no natural
|
|
1413
|
+
// "unset" value; structuralResonance (called just below) always
|
|
1414
|
+
// overwrites both before returning, on every one of its exit
|
|
1415
|
+
// paths — these are never read in their initial form.
|
|
1416
|
+
resonanceTrace = {
|
|
1417
|
+
variantBudget: ctx.cfg.haloQueryK,
|
|
1418
|
+
variants: [],
|
|
1419
|
+
mergedProposals: 0,
|
|
1420
|
+
examined: [],
|
|
1421
|
+
noiseFloor: 0,
|
|
1422
|
+
outcome: "empty",
|
|
1423
|
+
};
|
|
1424
|
+
probe.resonance = resonanceTrace;
|
|
1425
|
+
}
|
|
1269
1426
|
const ownRootsA = rvs.votes.find((v) => v.start === ra.start && v.end === ra.end)?.roots;
|
|
1270
1427
|
const ownRootsB = rvs.votes.find((v) => v.start === rb.start && v.end === rb.end)?.roots;
|
|
1271
|
-
structuralPick = await structuralResonance(ctx, query, ra, rb, sides, k, N, reachMemo, ownRootsA, ownRootsB);
|
|
1428
|
+
structuralPick = await structuralResonance(ctx, query, ra, rb, sides, k, N, reachMemo, ownRootsA, ownRootsB, resonanceTrace);
|
|
1272
1429
|
}
|
|
1273
|
-
if (structuralPick === null)
|
|
1430
|
+
if (structuralPick === null) {
|
|
1431
|
+
pushProbe(reasons.length > 0 ? "resonance-ineligible" : "resonance-rejected");
|
|
1274
1432
|
continue;
|
|
1433
|
+
}
|
|
1275
1434
|
tier = "structural-resonance";
|
|
1276
1435
|
}
|
|
1277
1436
|
let best = null;
|
|
@@ -1294,6 +1453,21 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1294
1453
|
confidence = structuralPick.proposal.effectiveScore;
|
|
1295
1454
|
}
|
|
1296
1455
|
else {
|
|
1456
|
+
// Aggregate structural-tier trace (spec §4) — one per DAG tier that
|
|
1457
|
+
// returned at least one container (exact, single-synonym or
|
|
1458
|
+
// double-synonym); only aggregate counts and the final outcome are
|
|
1459
|
+
// recorded, never every candidate.
|
|
1460
|
+
const structuralTrace = probe
|
|
1461
|
+
? {
|
|
1462
|
+
tier: tier,
|
|
1463
|
+
selfEvidenceRejected: 0,
|
|
1464
|
+
contradictionRejected: 0,
|
|
1465
|
+
passedGuards: 0,
|
|
1466
|
+
outcome: "all-rejected",
|
|
1467
|
+
}
|
|
1468
|
+
: undefined;
|
|
1469
|
+
if (probe)
|
|
1470
|
+
probe.structural = structuralTrace;
|
|
1297
1471
|
// N-ARY selection: the container covering the MOST remaining candidate
|
|
1298
1472
|
// forms wins (then tightest interior, then lowest id). Reads are
|
|
1299
1473
|
// cache hits — every container's bytes were already read by the walk.
|
|
@@ -1311,8 +1485,11 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1311
1485
|
const ri = indexOf(bytes, right, 0);
|
|
1312
1486
|
if (li >= 0 && ri >= 0) {
|
|
1313
1487
|
const joined = bytes.subarray(Math.min(li, ri), Math.max(li + left.length, ri + right.length));
|
|
1314
|
-
if (indexOf(query, joined, 0) >= 0)
|
|
1488
|
+
if (indexOf(query, joined, 0) >= 0) {
|
|
1489
|
+
if (structuralTrace)
|
|
1490
|
+
structuralTrace.selfEvidenceRejected++;
|
|
1315
1491
|
continue; // query says it itself
|
|
1492
|
+
}
|
|
1316
1493
|
}
|
|
1317
1494
|
// CONTRADICTION GUARD: a between-region already carrying its own
|
|
1318
1495
|
// vote must actually recur in this container's bytes — otherwise
|
|
@@ -1320,8 +1497,12 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1320
1497
|
// ra/rb, and letting it stand in for the gap would silently
|
|
1321
1498
|
// override evidence the query itself already resolved there.
|
|
1322
1499
|
if (between.some((bi) => indexOf(bytes, query.subarray(regions[bi].start, regions[bi].end), 0) < 0)) {
|
|
1500
|
+
if (structuralTrace)
|
|
1501
|
+
structuralTrace.contradictionRejected++;
|
|
1323
1502
|
continue;
|
|
1324
1503
|
}
|
|
1504
|
+
if (structuralTrace)
|
|
1505
|
+
structuralTrace.passedGuards++;
|
|
1325
1506
|
let cov = left.length + right.length;
|
|
1326
1507
|
const extras = [];
|
|
1327
1508
|
for (const ei of cand) {
|
|
@@ -1345,14 +1526,31 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1345
1526
|
bestCov = cov;
|
|
1346
1527
|
}
|
|
1347
1528
|
}
|
|
1348
|
-
if (best === null)
|
|
1349
|
-
|
|
1529
|
+
if (best === null) {
|
|
1530
|
+
// every container was self-evidence / contradiction — outcome
|
|
1531
|
+
// stays "all-rejected".
|
|
1532
|
+
pushProbe("structural-rejected");
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1350
1535
|
const r = edgeAncestors(ctx, best.id, N, reachMemo);
|
|
1351
|
-
if (r.saturated || r.roots.length === 0)
|
|
1536
|
+
if (r.saturated || r.roots.length === 0) {
|
|
1537
|
+
if (structuralTrace) {
|
|
1538
|
+
structuralTrace.outcome = r.saturated ? "saturated" : "no-roots";
|
|
1539
|
+
}
|
|
1540
|
+
pushProbe("structural-rejected");
|
|
1352
1541
|
continue;
|
|
1542
|
+
}
|
|
1353
1543
|
const df = Math.log(N / Math.max(1, r.contextsReached));
|
|
1354
|
-
if (df <= 0)
|
|
1544
|
+
if (df <= 0) {
|
|
1545
|
+
if (structuralTrace)
|
|
1546
|
+
structuralTrace.outcome = "nonpositive-idf";
|
|
1547
|
+
pushProbe("structural-rejected");
|
|
1355
1548
|
continue;
|
|
1549
|
+
}
|
|
1550
|
+
if (structuralTrace) {
|
|
1551
|
+
structuralTrace.outcome = "accepted";
|
|
1552
|
+
structuralTrace.selectedNode = best.id;
|
|
1553
|
+
}
|
|
1356
1554
|
reach = r;
|
|
1357
1555
|
idf = df;
|
|
1358
1556
|
// Confidence used by voting (spec §13): exact junction = 1;
|
|
@@ -1429,6 +1627,7 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1429
1627
|
wFocus: w,
|
|
1430
1628
|
absorbed: 1 + explainedAway,
|
|
1431
1629
|
});
|
|
1630
|
+
pushProbe("accepted");
|
|
1432
1631
|
if (td) {
|
|
1433
1632
|
td.crossRegionJunctionVotes.push({
|
|
1434
1633
|
container: best.id,
|
|
@@ -1438,6 +1637,11 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1438
1637
|
explainedAwayRegionIndices: explainedAwayIndices,
|
|
1439
1638
|
absorbed: 1 + explainedAway,
|
|
1440
1639
|
tier,
|
|
1640
|
+
probe: td.crossRegionProbes.length - 1,
|
|
1641
|
+
confidence,
|
|
1642
|
+
evidenceBytes: bestCov,
|
|
1643
|
+
mutualWeight: mutual,
|
|
1644
|
+
voteWeightPerRoot: w,
|
|
1441
1645
|
});
|
|
1442
1646
|
}
|
|
1443
1647
|
const label = [cand[a], cand[b], ...bestExtras]
|
|
@@ -1475,6 +1679,11 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1475
1679
|
}
|
|
1476
1680
|
if (td)
|
|
1477
1681
|
td.supersededOrdinaryVotes = superseded.size;
|
|
1682
|
+
if (td?.crossRegionSummary) {
|
|
1683
|
+
td.crossRegionSummary.stopReason = probes >= k
|
|
1684
|
+
? "probe-limit"
|
|
1685
|
+
: "pairs-exhausted";
|
|
1686
|
+
}
|
|
1478
1687
|
return { votes: out, superseded };
|
|
1479
1688
|
}
|
|
1480
1689
|
/** Emit the "climbConsensus" step — the human-readable note this always
|
|
@@ -1529,9 +1738,14 @@ export function traceAttention(ctx, regions, regionVoter, roots, steps = [], td,
|
|
|
1529
1738
|
...(td.crossRegionSummary
|
|
1530
1739
|
? {
|
|
1531
1740
|
crossRegion: {
|
|
1532
|
-
|
|
1741
|
+
eligibleRegions: td.crossRegionSummary.eligibleRegions,
|
|
1742
|
+
maximalRegions: td.crossRegionSummary.maximalRegions,
|
|
1743
|
+
probeLimit: td.crossRegionSummary.probeLimit,
|
|
1744
|
+
probesAttempted: td.crossRegionSummary.probesAttempted,
|
|
1533
1745
|
junctionVotes: td.crossRegionJunctionVotes,
|
|
1534
1746
|
supersededOrdinaryVotes: td.supersededOrdinaryVotes,
|
|
1747
|
+
probes: td.crossRegionProbes,
|
|
1748
|
+
stopReason: td.crossRegionSummary.stopReason ?? "pairs-exhausted",
|
|
1535
1749
|
},
|
|
1536
1750
|
}
|
|
1537
1751
|
: {}),
|