@hviana/sema 0.8.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +29 -29
- package/TRADEMARKS.md +0 -1
- package/dist/src/config.d.ts +28 -0
- package/dist/src/config.js +20 -0
- package/dist/src/geometry.d.ts +21 -0
- package/dist/src/geometry.js +21 -0
- package/dist/src/meter.d.ts +76 -0
- package/dist/src/meter.js +95 -0
- package/dist/src/mind/attention.d.ts +4 -0
- package/dist/src/mind/attention.js +165 -16
- package/dist/src/mind/canonical.d.ts +16 -0
- package/dist/src/mind/canonical.js +41 -0
- package/dist/src/mind/corpus.d.ts +40 -0
- package/dist/src/mind/corpus.js +149 -0
- package/dist/src/mind/graph-search.d.ts +7 -0
- package/dist/src/mind/graph-search.js +254 -24
- package/dist/src/mind/index.d.ts +3 -1
- package/dist/src/mind/index.js +1 -0
- package/dist/src/mind/match.d.ts +9 -4
- package/dist/src/mind/match.js +147 -61
- package/dist/src/mind/mechanisms/cast.js +19 -3
- package/dist/src/mind/mechanisms/confluence.js +24 -0
- package/dist/src/mind/mechanisms/cover.js +6 -0
- package/dist/src/mind/mechanisms/recall.js +32 -4
- package/dist/src/mind/mind.d.ts +57 -0
- package/dist/src/mind/mind.js +72 -1
- package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
- package/dist/src/mind/pipeline.js +66 -20
- package/dist/src/mind/primitives.js +9 -1
- package/dist/src/mind/rationale.d.ts +28 -1
- package/dist/src/mind/rationale.js +22 -1
- package/dist/src/mind/reasoning.d.ts +25 -3
- package/dist/src/mind/reasoning.js +125 -20
- package/dist/src/mind/recognition.js +4 -8
- package/dist/src/mind/resonance.js +20 -1
- package/dist/src/mind/trace.js +1 -0
- package/dist/src/mind/traverse.js +15 -3
- package/dist/src/mind/types.d.ts +49 -4
- package/docs/INVARIANTS.md +2 -2
- package/docs/architecture/bounded-reads.md +1 -1
- package/docs/architecture/commonality.md +2 -2
- package/docs/architecture/cost-model.md +2 -2
- package/docs/architecture/determinism.md +7 -7
- package/docs/architecture/match-project.md +2 -3
- package/docs/architecture/mechanism-market.md +10 -10
- package/docs/architecture/meter.md +5 -5
- package/docs/architecture/store.md +3 -3
- package/docs/failures/tempting-but-wrong.md +34 -6
- package/docs/harness/gates.md +2 -2
- package/docs/mechanisms/cast.md +2 -2
- package/docs/mechanisms/cover.md +2 -3
- package/docs/mechanisms/extraction.md +7 -7
- package/docs/mechanisms/recall.md +8 -9
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/alu/README.md +11 -12
- package/src/config.ts +48 -0
- package/src/geometry.ts +21 -0
- package/src/meter.ts +98 -0
- package/src/mind/attention.ts +167 -16
- package/src/mind/canonical.ts +43 -0
- package/src/mind/corpus.ts +202 -0
- package/src/mind/graph-search.ts +277 -23
- package/src/mind/index.ts +8 -1
- package/src/mind/match.ts +148 -57
- package/src/mind/mechanisms/cast.ts +20 -2
- package/src/mind/mechanisms/confluence.ts +24 -0
- package/src/mind/mechanisms/cover.ts +5 -0
- package/src/mind/mechanisms/recall.ts +32 -4
- package/src/mind/mind.ts +125 -0
- package/src/mind/pipeline-mechanism.ts +7 -0
- package/src/mind/pipeline.ts +79 -22
- package/src/mind/primitives.ts +9 -1
- package/src/mind/rationale.ts +35 -1
- package/src/mind/reasoning.ts +145 -13
- package/src/mind/recognition.ts +4 -8
- package/src/mind/resonance.ts +19 -1
- package/src/mind/trace.ts +1 -0
- package/src/mind/traverse.ts +16 -6
- package/src/mind/types.ts +53 -4
- package/test/100-complete-grounding-trace.test.mjs +109 -0
- package/test/101-alignment-gap-bound.test.mjs +106 -0
- package/test/102-production-composes-at-scale.test.mjs +110 -0
- package/test/103-alignment-gap-budget.test.mjs +89 -0
- package/test/104-composition-is-reported.test.mjs +90 -0
- package/test/105-derive-through-reports-its-refusal.test.mjs +137 -0
- package/test/106-the-join-fires.test.mjs +94 -0
- package/test/107-the-join-is-counted.test.mjs +81 -0
- package/test/108-the-join-chains.test.mjs +78 -0
- package/test/109-the-pivot-is-counted.test.mjs +60 -0
- package/test/110-the-reasoner-stops-when-the-question-is-answered.test.mjs +91 -0
- package/test/111-the-cover-assembly-is-counted.test.mjs +74 -0
- package/test/112-the-exploration-does-not-grow-with-the-hub.test.mjs +89 -0
- package/test/113-the-rationale-payload-is-bounded.test.mjs +84 -0
- package/test/114-alignment-budget-is-per-sweep.test.mjs +93 -0
- package/test/116-the-extension-is-gated-by-the-pipelines-own-remainder.test.mjs +100 -0
- package/test/117-corpus-search.test.mjs +171 -0
- package/test/118-the-join-reaches-a-key-off-the-cut.test.mjs +74 -0
- package/test/119-the-work-does-not-grow-with-the-corpus.test.mjs +122 -0
- package/test/120-composition-is-consequence.test.mjs +132 -0
- package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +128 -0
- package/test/122-the-climb-search-does-not-grow-with-the-corpus.test.mjs +117 -0
- package/test/123-the-paired-formulas-agree.test.mjs +90 -0
- package/test/125-the-post-grounding-branch-publishes-its-operand.test.mjs +51 -0
- package/test/126-the-pipeline-does-not-name-mechanisms.test.mjs +42 -0
- package/test/128-the-leads-somewhere-pair-agrees.test.mjs +83 -0
- package/test/129-the-trace-payload-shape.test.mjs +164 -0
- package/test/14-scaling.test.mjs +10 -7
- package/test/32-confluence.test.mjs +68 -0
- package/test/38-reason-restate-guard.test.mjs +8 -2
- package/test/43-cast-analog-seat.test.mjs +10 -0
- package/test/55-cost-meter.test.mjs +859 -0
- package/test/76-reference-binding.test.mjs +6 -1
- package/test/89-completion-recursion.test.mjs +30 -5
package/src/mind/pipeline.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { ComputedSpan } from "../extension.js";
|
|
|
15
15
|
import { gistOf, read, resolve } from "./primitives.js";
|
|
16
16
|
import { recognise } from "./recognition.js";
|
|
17
17
|
import { fuseAttention, reason } from "./reasoning.js";
|
|
18
|
-
import { unexplainedSpans } from "./rationale.js";
|
|
18
|
+
import { unaccountedBytes, unexplainedSpans } from "./rationale.js";
|
|
19
19
|
import { rItem } from "./trace.js";
|
|
20
20
|
import { hubBound } from "./traverse.js";
|
|
21
21
|
import { type PipelineMechanism, Precomputed } from "./pipeline-mechanism.js";
|
|
@@ -253,8 +253,7 @@ export async function think(
|
|
|
253
253
|
}
|
|
254
254
|
const grade = (w: number) => Math.floor(w / STEP);
|
|
255
255
|
const unaccounted = (spans: ReadonlyArray<[number, number]>): number =>
|
|
256
|
-
unexplainedSpans(query.length, spans)
|
|
257
|
-
.reduce((sum, [s, e]) => sum + (e - s), 0);
|
|
256
|
+
unaccountedBytes(unexplainedSpans(query.length, spans));
|
|
258
257
|
const weigh = (
|
|
259
258
|
accounted: ReadonlyArray<[number, number]>,
|
|
260
259
|
moves: number,
|
|
@@ -504,14 +503,11 @@ export async function think(
|
|
|
504
503
|
}
|
|
505
504
|
const answer: Uint8Array = decided.bytes;
|
|
506
505
|
const provenance = decided.provenance as Provenance;
|
|
507
|
-
const
|
|
506
|
+
const declaredUsed = decided.used;
|
|
508
507
|
|
|
509
508
|
// ── Post-grounding, gated by provenance ──────────────────────────────
|
|
510
|
-
const preConsumed =
|
|
511
|
-
|
|
512
|
-
: provenance === "recall" || provenance === "recall-echo"
|
|
513
|
-
? new Set<number>()
|
|
514
|
-
: new Set(recognise(ctx, answer).sites.map((s) => s.payload));
|
|
509
|
+
const preConsumed = declaredUsed ??
|
|
510
|
+
new Set(recognise(ctx, answer).sites.map((s) => s.payload));
|
|
515
511
|
// A grounding that DECLARED itself complete is not extended: the answer is
|
|
516
512
|
// already a trained form's own continuation, reached through an identity
|
|
517
513
|
// claim about the query, so a multi-hop pivot could only chain past the
|
|
@@ -540,17 +536,76 @@ export async function think(
|
|
|
540
536
|
// `preConsumed` is derived by re-recognising the answer — "everything in
|
|
541
537
|
// it", not "what it voiced" — and a containment rule over that would
|
|
542
538
|
// suppress every pivot the answer legitimately contains.
|
|
543
|
-
const voiced =
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
539
|
+
const voiced = declaredUsed === undefined ? [] : [...declaredUsed].flatMap(
|
|
540
|
+
(id) => ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n)),
|
|
541
|
+
);
|
|
542
|
+
// WHAT THIS BRANCH READ, published where it was read. Post-grounding decides
|
|
543
|
+
// by `decided.used` and by the provenance NAME; the operands were invisible in
|
|
544
|
+
// the trace, so a change to the branching could not be shown equivalent or
|
|
545
|
+
// otherwise from outside — three separate investigations failed on exactly
|
|
546
|
+
// that gap. A gap in instrumentation is a defect IN the instrumentation
|
|
547
|
+
// (AGENTS.md §6): closed here, once, as counts only — never content.
|
|
548
|
+
ctx.trace?.step(
|
|
549
|
+
"postGrounding",
|
|
550
|
+
[rItem(answer, provenance)],
|
|
551
|
+
[],
|
|
552
|
+
`used=${decided.used !== undefined ? "declared" : "absent"} · ` +
|
|
553
|
+
`preConsumed=${preConsumed.size} · voiced=${voiced.length}`,
|
|
554
|
+
undefined,
|
|
555
|
+
{
|
|
556
|
+
version: 1,
|
|
557
|
+
provenance,
|
|
558
|
+
usedDeclared: decided.used !== undefined,
|
|
559
|
+
preConsumed: preConsumed.size,
|
|
560
|
+
voiced: voiced.length,
|
|
561
|
+
},
|
|
562
|
+
);
|
|
563
|
+
// REPORTABLE, NOT SILENT. A declared-complete grounding ends the derivation
|
|
564
|
+
// here, and that decision is part of the derivation's shape: the reader of a
|
|
565
|
+
// rationale must be able to see that the chain stopped because the mechanism
|
|
566
|
+
// claimed the query WAS the context, not because nothing followed. The step
|
|
567
|
+
// carries the claim, not a re-description of the answer — the extension is
|
|
568
|
+
// skipped, so there is no output item to show.
|
|
569
|
+
if (decided.complete) {
|
|
570
|
+
ctx.trace?.step(
|
|
571
|
+
"completeGrounding",
|
|
572
|
+
[rItem(answer, provenance)],
|
|
573
|
+
[],
|
|
574
|
+
"grounding declared complete — the query IS the context, so " +
|
|
575
|
+
"post-grounding extension is skipped",
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
// THE REASONER JUDGES ITS OWN EXTENSIONS BY THE PIPELINE'S REMAINDER, not by
|
|
579
|
+
// the ladder's `accounted` — and by the SAME reading the fuse gate below uses,
|
|
580
|
+
// with the same W floor. `accounted` is a COST quantity (measured: a query
|
|
581
|
+
// fully explained by one computed span plus bridged connectors reports
|
|
582
|
+
// `accounted: []` while nothing is unexplained), and a remainder under one
|
|
583
|
+
// river-fold quantum is bridging punctuation, never a second topic — so it
|
|
584
|
+
// licenses no extension and blocks none.
|
|
585
|
+
const explained: Array<[number, number]> = [
|
|
586
|
+
...decided.accounted,
|
|
587
|
+
...pre.computed.map((u): [number, number] => [u.i, u.j]),
|
|
588
|
+
];
|
|
589
|
+
const uncovered = unexplainedSpans(query.length, explained)
|
|
590
|
+
.filter(([a, b]) => b - a >= ctx.space.maxGroup);
|
|
591
|
+
// PUBLISHED, NOT RECOMPUTED: the same `uncovered` the gates below read. A
|
|
592
|
+
// write-only accounting (meter contract 1), so the number that licenses an
|
|
593
|
+
// extension or a fusion stops being invisible.
|
|
594
|
+
if (meter) {
|
|
595
|
+
meter.postGroundingRemainderSpans += uncovered.length;
|
|
596
|
+
meter.postGroundingRemainderBytes += unaccountedBytes(uncovered);
|
|
597
|
+
}
|
|
598
|
+
// The extension is kept as a WHOLE (bytes + what it carried + how many steps),
|
|
599
|
+
// not just its bytes: pricing it — `steps · STEP` against `PASS · unaccounted`
|
|
600
|
+
// — is the caller's job, one comparison away. `reasoned` stays the bytes so
|
|
601
|
+
// everything downstream is untouched.
|
|
602
|
+
const extension = decided.complete ? undefined : meter
|
|
549
603
|
? await meter.time(
|
|
550
604
|
"reason",
|
|
551
|
-
() => reason(ctx, query, answer, preConsumed, pre, voiced),
|
|
605
|
+
() => reason(ctx, query, answer, preConsumed, pre, voiced, uncovered),
|
|
552
606
|
)
|
|
553
|
-
: await reason(ctx, query, answer, preConsumed, pre, voiced);
|
|
607
|
+
: await reason(ctx, query, answer, preConsumed, pre, voiced, uncovered);
|
|
608
|
+
const reasoned = extension?.bytes ?? answer;
|
|
554
609
|
|
|
555
610
|
// Fuse only when the query has a genuine REMAINDER no mechanism's
|
|
556
611
|
// structural evidence touched at all. `decided.accounted` alone
|
|
@@ -568,10 +623,6 @@ export async function think(
|
|
|
568
623
|
// observed: a single space between two fully-computed arithmetic spans
|
|
569
624
|
// ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
|
|
570
625
|
// corpus fact, corrupting "4 6" into "4 63".
|
|
571
|
-
const explained: Array<[number, number]> = [
|
|
572
|
-
...decided.accounted,
|
|
573
|
-
...pre.computed.map((u): [number, number] => [u.i, u.j]),
|
|
574
|
-
];
|
|
575
626
|
const remainder = unaccounted(explained);
|
|
576
627
|
// Whether the winning candidate's entire recognised substance is
|
|
577
628
|
// COMPUTED — every accounted span exactly a pre.computed span, nothing
|
|
@@ -615,7 +666,13 @@ export async function think(
|
|
|
615
666
|
|
|
616
667
|
done(
|
|
617
668
|
fused,
|
|
618
|
-
|
|
669
|
+
// NO CLAIM ABOUT FUSION HERE. `fuseAttention` is entered whenever a
|
|
670
|
+
// remainder ≥ W exists and returns early when there is nothing to bridge, so
|
|
671
|
+
// this note used to assert a fusion that frequently did not happen (measured:
|
|
672
|
+
// "What is the capital of France famous for" fuses 0 times). The fusion is
|
|
673
|
+
// reported by `fuseAttention`'s own `done` when it happens — the layer that
|
|
674
|
+
// did the work is the layer that says so.
|
|
675
|
+
"grounded, reasoned forward",
|
|
619
676
|
);
|
|
620
677
|
return { bytes: fused, provenance };
|
|
621
678
|
}
|
package/src/mind/primitives.ts
CHANGED
|
@@ -338,7 +338,15 @@ export function canonResolve(
|
|
|
338
338
|
// on exactly the node the canonical-case query would have found.
|
|
339
339
|
const folded = foldTree(ctx, perceive(ctx, bytesOf), 0).node;
|
|
340
340
|
const use = folded ?? id;
|
|
341
|
-
|
|
341
|
+
// THE ADMISSION PREDICATE, by its own pair of probes: `traverse.ts`'s
|
|
342
|
+
// `leadsSomewhere` is edge-or-halo, and `hasHalo` is the one that carries
|
|
343
|
+
// the mass bar (`mass >= minHaloMass`). Asking `haloMass(use) > 0` instead
|
|
344
|
+
// is the same answer only while `minHaloMass <= 1` (its default): raise the
|
|
345
|
+
// bar and this site would rank a node as leading on evidence the law
|
|
346
|
+
// refuses. Calling `leadsSomewhere` here is not possible — `traverse.ts`
|
|
347
|
+
// imports THIS file, so it would be a cycle — which is why the pair is
|
|
348
|
+
// spelled out rather than named.
|
|
349
|
+
const leads = store.hasNext(use) || store.hasHalo(use);
|
|
342
350
|
if (
|
|
343
351
|
best === null || (leads && !bestLeads) ||
|
|
344
352
|
(leads === bestLeads && use < best)
|
package/src/mind/rationale.ts
CHANGED
|
@@ -49,6 +49,17 @@ export interface RationaleItem {
|
|
|
49
49
|
* caller asked to carry it (off by default — a D-float array per item would
|
|
50
50
|
* bury the reasoning it is meant to explain). */
|
|
51
51
|
v?: Vec;
|
|
52
|
+
/** The element's OWN bytes, attached BY REFERENCE when the step was built from
|
|
53
|
+
* bytes (a `rationale.ts` item made from a node carries none: read it back
|
|
54
|
+
* through `node`). `text` is a RENDERING and cannot stand in for them — it
|
|
55
|
+
* decodes UTF-8 and DROPS NUL bytes, so a key containing one is unrecoverable
|
|
56
|
+
* from it, which is exactly how a join refusal (`deriveThroughMiss`) became
|
|
57
|
+
* impossible to test exactly without re-encoding. Treat as READ-ONLY: the
|
|
58
|
+
* array belongs to the caller (and may be a view into the query).
|
|
59
|
+
*
|
|
60
|
+
* Costs nothing when nothing inspects: items exist only while a rationale
|
|
61
|
+
* sink is attached, and this holds a reference rather than a copy. */
|
|
62
|
+
bytes?: Uint8Array;
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
/** A single completed act of inference — one mechanism, run once.
|
|
@@ -101,6 +112,20 @@ export function decodeText(bytes: Uint8Array): string {
|
|
|
101
112
|
return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
|
|
102
113
|
}
|
|
103
114
|
|
|
115
|
+
/** The BYTE COUNT of the complement — what the currency calls `unaccounted`
|
|
116
|
+
* in `weight = moves + PASS·unaccounted`. It lives here, beside the function
|
|
117
|
+
* that produces the gaps, because the price's second term has ONE definition:
|
|
118
|
+
* this was four copies of the same `reduce` (two in reasoning.ts, two in
|
|
119
|
+
* pipeline.ts) before the architecture audit of `../auditoria-arquitectura-sema.md`
|
|
120
|
+
* collapsed them. Same value at every site — the control diff is identical. */
|
|
121
|
+
export function unaccountedBytes(
|
|
122
|
+
spans: ReadonlyArray<readonly [number, number]>,
|
|
123
|
+
): number {
|
|
124
|
+
let total = 0;
|
|
125
|
+
for (const [a, b] of spans) total += b - a;
|
|
126
|
+
return total;
|
|
127
|
+
}
|
|
128
|
+
|
|
104
129
|
/** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
|
|
105
130
|
* the same union-of-spans reading think's grounding decider prices at PASS
|
|
106
131
|
* per byte, exposed here so a mechanism can turn it into a human label. */
|
|
@@ -264,7 +289,16 @@ export class Rationale {
|
|
|
264
289
|
};
|
|
265
290
|
}
|
|
266
291
|
|
|
267
|
-
/**
|
|
292
|
+
/** WHY THIS NAME IS A FREE STRING, when the derivation's moves are a closed
|
|
293
|
+
* union: a mechanism name is WRITTEN and DISPLAYED, and it COMPOSES with
|
|
294
|
+
* the nesting — `mechanism` is the whole path (`["respond", "think",
|
|
295
|
+
* "recognise"]`), which no fixed union can express. Nothing branches on it:
|
|
296
|
+
* `nothing here drives the inference; it only WITNESSES it`. A vocabulary
|
|
297
|
+
* that is only witnessed needs no union; one that is read does
|
|
298
|
+
* (`DerivationMove`, in graph-search.ts). The asymmetry is the design, not
|
|
299
|
+
* a drift.
|
|
300
|
+
*
|
|
301
|
+
* Record a mechanism that has no sub-steps — its inputs and outputs are both
|
|
268
302
|
* known at the call site. Returns its index, for a later step to depend on. */
|
|
269
303
|
step(
|
|
270
304
|
name: string,
|
package/src/mind/reasoning.ts
CHANGED
|
@@ -8,10 +8,10 @@ import { bytesEqual, indexOf } from "../bytes.js";
|
|
|
8
8
|
import type { Attention, MindContext } from "./types.js";
|
|
9
9
|
import { resolve } from "./primitives.js";
|
|
10
10
|
import { corpusN, hubBound } from "./traverse.js";
|
|
11
|
-
import { follow, haloSiblings, project } from "./match.js";
|
|
11
|
+
import { containsSpan, follow, haloSiblings, project } from "./match.js";
|
|
12
12
|
import { joinWithBridge, pivotInto } from "./resonance.js";
|
|
13
13
|
import type { Precomputed } from "./pipeline-mechanism.js";
|
|
14
|
-
import type
|
|
14
|
+
import { type Rationale, unaccountedBytes } from "./rationale.js";
|
|
15
15
|
|
|
16
16
|
/** Whether `bytes` is a proper byte-subspan of `query` — already present in
|
|
17
17
|
* the question, so voicing it back only restates part of what was asked,
|
|
@@ -29,13 +29,32 @@ export function restatesQuery(query: Uint8Array, bytes: Uint8Array): boolean {
|
|
|
29
29
|
|
|
30
30
|
/** Extend a grounded answer forward across facts (multi-hop reasoning).
|
|
31
31
|
* Pivots on the longest unconsumed learnt context each answer contains,
|
|
32
|
-
* then follows the pivot's continuation to the next fact.
|
|
33
|
-
*
|
|
32
|
+
* then follows the pivot's continuation to the next fact. **The chain ends
|
|
33
|
+
* when it STOPS, never when a count runs out**: every exit is a refusal (no
|
|
34
|
+
* pivot, no forward step, no question material carried) and the walk is bounded
|
|
35
|
+
* by the material and the graph — `consumed` refuses to revisit a node. There
|
|
36
|
+
* is no hop allowance, so this doc deliberately names no `cfg` capacity: the
|
|
37
|
+
* cover prices every hop at `STEP` and lets the search decide the depth, and a
|
|
38
|
+
* second count here would be a second decision about the same thing.
|
|
39
|
+
* `preConsumed` carries node ids already
|
|
34
40
|
* spoken for by the grounding stage (cover/extract/CAST). `voiced` carries
|
|
35
41
|
* the BYTES of the anchors a mechanism declared it voiced (its `used` set),
|
|
36
42
|
* when it declared one — see the pivot's own containment rule. `pre` is the
|
|
37
43
|
* response's shared pre-computation — the post-grounding stages read the
|
|
38
44
|
* same container the mechanisms did. */
|
|
45
|
+
/** What the multi-hop extension produced, and what it cost: the bytes (the
|
|
46
|
+
* answer), the spans of the grounding's UNCOVERED material that each step was
|
|
47
|
+
* justified by, and how many steps were taken. The last two exist so the
|
|
48
|
+
* caller can price the extension in the ladder's own currency — `steps · STEP`
|
|
49
|
+
* against `PASS · unaccounted` — instead of taking it unconditionally. Both
|
|
50
|
+
* are FACTS, not verdicts: nothing here says whether the extension was worth
|
|
51
|
+
* it; that is the comparison's job, one layer up. */
|
|
52
|
+
export interface ReasonedAnswer {
|
|
53
|
+
bytes: Uint8Array;
|
|
54
|
+
carried: Array<[number, number]>;
|
|
55
|
+
steps: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
39
58
|
export async function reason(
|
|
40
59
|
ctx: MindContext,
|
|
41
60
|
query: Uint8Array,
|
|
@@ -43,14 +62,20 @@ export async function reason(
|
|
|
43
62
|
preConsumed: ReadonlySet<number>,
|
|
44
63
|
pre: Precomputed,
|
|
45
64
|
voiced: readonly Uint8Array[] = [],
|
|
46
|
-
|
|
65
|
+
/** The query material the GROUNDING left uncovered — the cost ladder's own
|
|
66
|
+
* `unaccounted` spans. Only the reasoner's OWN extensions are judged
|
|
67
|
+
* against it; a mechanism carrying its own `used` set owns its shape. */
|
|
68
|
+
uncovered: readonly (readonly [number, number])[] = [],
|
|
69
|
+
): Promise<ReasonedAnswer> {
|
|
47
70
|
// Echo guard: a query that is ITSELF a learnt continuation (some context's
|
|
48
71
|
// answer) is being asked back at the system — hopping forward from it would
|
|
49
72
|
// chain through the very fact that produced it and echo the conversation
|
|
50
73
|
// back. The grounded answer alone is the honest read-out. Deliberately a
|
|
51
74
|
// broad structural gate; pinned by test/31-audit.
|
|
52
75
|
const qId = pre.queryResolved;
|
|
53
|
-
if (qId !== null && ctx.store.prevCount(qId) > 0)
|
|
76
|
+
if (qId !== null && ctx.store.prevCount(qId) > 0) {
|
|
77
|
+
return { bytes: answer, carried: [], steps: 0 };
|
|
78
|
+
}
|
|
54
79
|
|
|
55
80
|
// Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
|
|
56
81
|
// the hub bound, via the store's LIMITed edge reads: a common continuation's
|
|
@@ -109,7 +134,7 @@ export async function reason(
|
|
|
109
134
|
? null
|
|
110
135
|
: ctx.store.prevFirst(groundedId, bound);
|
|
111
136
|
if (qId !== null && groundedPrev !== null && groundedPrev.includes(qId)) {
|
|
112
|
-
return answer;
|
|
137
|
+
return { bytes: answer, carried: [], steps: 0 };
|
|
113
138
|
}
|
|
114
139
|
|
|
115
140
|
const consumed = new Set<number>();
|
|
@@ -155,7 +180,27 @@ export async function reason(
|
|
|
155
180
|
const qv = pre.guide; // the response-wide guide IS the query's gist
|
|
156
181
|
let t: ReturnType<Rationale["enter"]> | undefined;
|
|
157
182
|
const startedFrom = answer;
|
|
158
|
-
|
|
183
|
+
// INSTRUMENTATION ONLY — the two facts the extension's own decision already
|
|
184
|
+
// used and threw away: the spans of uncovered material each step was
|
|
185
|
+
// JUSTIFIED by (the gate below computes which span carries it and kept only a
|
|
186
|
+
// boolean), and how many steps were taken. Nothing here decides anything:
|
|
187
|
+
// both are read after the loop, to bump counters and to let the caller compare
|
|
188
|
+
// the extension's cost against what it explains, in the ladder's own currency.
|
|
189
|
+
const carried: Array<[number, number]> = [];
|
|
190
|
+
let steps = 0;
|
|
191
|
+
// NO ALLOWANCE: THE CHAIN ENDS WHEN IT STOPS. Every exit below is the law —
|
|
192
|
+
// no pivot, no forward step, no question material carried — and the walk is
|
|
193
|
+
// bounded by the material and the graph rather than by a count: each taken
|
|
194
|
+
// step must carry a W-window of the uncovered material (finite), and
|
|
195
|
+
// `consumed` refuses to revisit a node. `recallQueryK` no longer bounds the
|
|
196
|
+
// reasoner here; it keeps its other roles (the bridge's candidate reads, the
|
|
197
|
+
// pivot's probe budget, the resonance limits).
|
|
198
|
+
//
|
|
199
|
+
// Measured before removing it: test/89 — the corpus-cost guard, the heaviest
|
|
200
|
+
// case in the suite — is green and no slower without the allowance (27 s
|
|
201
|
+
// against 30 s); and raising it from 12 to 200 changed neither the answer nor
|
|
202
|
+
// `pivotSteps` on the chain fixtures.
|
|
203
|
+
for (let hop = 0;; hop++) {
|
|
159
204
|
// Hop 0's `cur` IS `answer`, so the guard above already resolved it and
|
|
160
205
|
// read its reverse edges — reuse both rather than repeat them.
|
|
161
206
|
const curId = hop === 0 ? groundedId : resolve(ctx, cur);
|
|
@@ -188,6 +233,7 @@ export async function reason(
|
|
|
188
233
|
"the answer is itself a learnt fact — follow its continuation to the fixpoint",
|
|
189
234
|
);
|
|
190
235
|
cur = fwd;
|
|
236
|
+
steps++;
|
|
191
237
|
continue;
|
|
192
238
|
}
|
|
193
239
|
}
|
|
@@ -200,6 +246,80 @@ export async function reason(
|
|
|
200
246
|
const fc = await follow(ctx, pivot, qv);
|
|
201
247
|
consumeAll(pivot);
|
|
202
248
|
if (fc === null || bytesEqual(fc, cur) || restatesQuery(query, fc)) break;
|
|
249
|
+
// WHOSE EXTENSION IS THIS?
|
|
250
|
+
//
|
|
251
|
+
// `voiced` is what the mechanism WITHHELD (the pipeline sends the used
|
|
252
|
+
// anchors' CONTINUATIONS, not their bytes — see pipeline's own note), so a
|
|
253
|
+
// non-empty `voiced` means exactly what that note says: the grounding came
|
|
254
|
+
// from a mechanism that carries its own short `used` set (cast/join) and
|
|
255
|
+
// therefore owns the shape of its answer. The further terms inside such a
|
|
256
|
+
// seat are legitimately followable — test/29 C3's `Mona Lisa` lives inside
|
|
257
|
+
// the voiced seat and leads on to a fact about neither analog.
|
|
258
|
+
//
|
|
259
|
+
// Every other grounding is ordinary, and an extension of it is the
|
|
260
|
+
// reasoner's own inference: it is taken only while question material the
|
|
261
|
+
// grounding left uncovered remains AND the step carries some of it, judged
|
|
262
|
+
// by the mind's own line between chance and evidence — one W-byte window,
|
|
263
|
+
// no word notion, no character class, no threshold. Measured: the drift's
|
|
264
|
+
// second step (`the Eiffel Tower is in Paris` after `Paris is famous for
|
|
265
|
+
// the Eiffel Tower`) carries no window of `" famous for"` and is refused,
|
|
266
|
+
// while the first carries it. Terminates by a real argument: the uncovered
|
|
267
|
+
// material is finite and each taken extension must carry some of it.
|
|
268
|
+
const producerOwnsShape = voiced.length > 0;
|
|
269
|
+
if (!producerOwnsShape && uncovered.length > 0) {
|
|
270
|
+
const W = ctx.space.maxGroup;
|
|
271
|
+
let progress = false;
|
|
272
|
+
let justified: [number, number] | undefined;
|
|
273
|
+
for (const [a, b] of uncovered) {
|
|
274
|
+
for (let i = a; i + W <= b && !progress; i++) {
|
|
275
|
+
if (indexOf(fc, query.subarray(i, i + W), 0) >= 0) {
|
|
276
|
+
progress = true;
|
|
277
|
+
justified = [a, b];
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (progress) break;
|
|
281
|
+
}
|
|
282
|
+
if (progress && justified !== undefined) carried.push(justified);
|
|
283
|
+
if (!progress) {
|
|
284
|
+
// THE BRAKE, MADE VISIBLE. The reasoner declines a step that carries
|
|
285
|
+
// none of the material the grounding left uncovered — the drift the
|
|
286
|
+
// extension tests pin. A refusal that leaves no trace is the kind of
|
|
287
|
+
// silent cut AGENTS §6 forbids: the rationale is where a reader learns
|
|
288
|
+
// that an extension was declined for want of question material, and
|
|
289
|
+
// where the next person sees why the chain stopped here. Measured with
|
|
290
|
+
// the check disabled, test/110 and test/116 fail — so this brake is the
|
|
291
|
+
// only thing keeping the extension honest until the pivot reports its
|
|
292
|
+
// own accounted spans and the ladder can judge it instead.
|
|
293
|
+
//
|
|
294
|
+
// THE PROMISE IS NOW KEPT, AND THE BRAKE TURNS OUT TO BE THE LADDER'S
|
|
295
|
+
// OWN CONSEQUENCE. The extension reports what it carried (`carried`,
|
|
296
|
+
// the span each step was justified by) and what it cost (`steps`), both
|
|
297
|
+
// counted in the meter (`reasonCarriedBytes`, `reasonSteps`), so the
|
|
298
|
+
// ladder CAN judge it: it accepts while
|
|
299
|
+
//
|
|
300
|
+
// steps · STEP < PASS · carried
|
|
301
|
+
//
|
|
302
|
+
// and this brake accepts whenever the step carries a `W`-window, i.e.
|
|
303
|
+
// whenever `carried ≥ W ≥ 1`. With `PASS/STEP = 1000` the two therefore
|
|
304
|
+
// agree on every extension with `steps ≤ 1000 · carried` — and every
|
|
305
|
+
// extension this repository produces takes 0 or 1 steps (measured on
|
|
306
|
+
// chains of 3, 8, 20 and 40 links). Above that bound the ladder would
|
|
307
|
+
// refuse what this brake accepts, which is the corner named in the
|
|
308
|
+
// closure report's limits: a chain of thousands of links explaining a
|
|
309
|
+
// handful of bytes. No guard is added for it — a limit without a
|
|
310
|
+
// derivation is exactly what the brake must not become.
|
|
311
|
+
const left = unaccountedBytes(uncovered);
|
|
312
|
+
ctx.trace?.step(
|
|
313
|
+
"pivotRefused",
|
|
314
|
+
[rItem(cur, "answer"), rItem(query, "query")],
|
|
315
|
+
uncovered.map(([a, b]) => rItem(query.subarray(a, b), "uncovered")),
|
|
316
|
+
`the step carries none of the question material the grounding left ` +
|
|
317
|
+
`uncovered (${left} byte(s) in ${uncovered.length} span(s)) — refused`,
|
|
318
|
+
);
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (ctx.meter) ctx.meter.pivotSteps++;
|
|
203
323
|
t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
|
|
204
324
|
ctx.trace?.step(
|
|
205
325
|
"pivotStep",
|
|
@@ -208,12 +328,25 @@ export async function reason(
|
|
|
208
328
|
"pivot on the shared span this answer contains, then step forward across that fact",
|
|
209
329
|
);
|
|
210
330
|
cur = fc;
|
|
331
|
+
steps++;
|
|
332
|
+
}
|
|
333
|
+
// INSTRUMENTATION ONLY — the extension's two facts, untraced (meter.ts
|
|
334
|
+
// contract 1: a counter never reaches a decision). They are what a caller
|
|
335
|
+
// needs to PRICE the extension instead of taking it unconditionally: the work
|
|
336
|
+
// it did (`steps · STEP`) and the uncovered material it carried.
|
|
337
|
+
if (ctx.meter) {
|
|
338
|
+
ctx.meter.reasonSteps += steps;
|
|
339
|
+
ctx.meter.reasonCarriedBytes += unaccountedBytes(carried);
|
|
211
340
|
}
|
|
212
341
|
t?.done(
|
|
213
342
|
[rItem(cur, "answer", resolve(ctx, cur) ?? undefined)],
|
|
343
|
+
// A FIXPOINT: no further step was possible. This note used to also cover an
|
|
344
|
+
// exhausted hop allowance — a different fact, and the reason F1 added a
|
|
345
|
+
// counter for it. The allowance is gone, so the only way out of the loop is
|
|
346
|
+
// a refusal, and the note is true again by construction.
|
|
214
347
|
"the multi-hop chain's fixpoint",
|
|
215
348
|
);
|
|
216
|
-
return cur;
|
|
349
|
+
return { bytes: cur, carried, steps };
|
|
217
350
|
}
|
|
218
351
|
|
|
219
352
|
/** Fuse independent points of attention into one answer (multi-topic).
|
|
@@ -407,9 +540,8 @@ export async function fuseAttention(
|
|
|
407
540
|
[rItem(out, "answer", resolve(ctx, out) ?? undefined)],
|
|
408
541
|
`fused ${pieces.length} independent points of attention into one answer`,
|
|
409
542
|
);
|
|
543
|
+
// THE FACT IS THE FUSED ANSWER, not the call: every early return above hands
|
|
544
|
+
// back `primary` untouched. Untraced on purpose (meter.ts contract 1).
|
|
545
|
+
if (ctx.meter) ctx.meter.fuseRuns++;
|
|
410
546
|
return out;
|
|
411
547
|
}
|
|
412
|
-
|
|
413
|
-
// (resonance.js is already a static dependency above — `bridge` — so the old
|
|
414
|
-
// dynamic import of pivotInto guarded against a cycle that does not exist.)
|
|
415
|
-
import { containsSpan } from "./match.js";
|
package/src/mind/recognition.ts
CHANGED
|
@@ -535,7 +535,10 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
|
|
|
535
535
|
// emitted, so a caller can retry a trimmed edge on the miss path only.
|
|
536
536
|
if (end - start < W) return false;
|
|
537
537
|
if (flatProbe(start, end) === null) {
|
|
538
|
-
if (!canonBudget)
|
|
538
|
+
if (!canonBudget) {
|
|
539
|
+
if (ctx.meter) ctx.meter.canonProbesDenied++;
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
539
542
|
if (!canonAdmits(start, end)) return false;
|
|
540
543
|
}
|
|
541
544
|
const id = resolveSpan(start, end);
|
|
@@ -556,13 +559,6 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
|
|
|
556
559
|
// endpoints in order and running out partway along the query. A form
|
|
557
560
|
// longer than that is out of this tier's reach — but so is a form the
|
|
558
561
|
// chain cannot span, and that is exactly the trade the budget prices.
|
|
559
|
-
// The factor is chainReach(W), the same W² scale the chain already
|
|
560
|
-
// trusts; no new constant.
|
|
561
|
-
// The factor is chainReach(W) — the same W² scale the chain itself
|
|
562
|
-
// trusts — so the cap is derived from the fold's geometry, never tuned.
|
|
563
|
-
// (It was briefly an environment variable while the cost was being
|
|
564
|
-
// measured; an env-read here would make inference non-reproducible,
|
|
565
|
-
// which the determinism contract forbids outright.)
|
|
566
562
|
// The factor is chainReach(W) — the same W² scale the chain itself
|
|
567
563
|
// trusts — so the cap is derived from the fold's geometry, never tuned.
|
|
568
564
|
// (It was briefly an environment variable while the cost was being
|
package/src/mind/resonance.ts
CHANGED
|
@@ -329,7 +329,14 @@ export async function pivotInto(
|
|
|
329
329
|
consumed: ReadonlySet<number>,
|
|
330
330
|
voiced: readonly Uint8Array[] = [],
|
|
331
331
|
): Promise<number | null> {
|
|
332
|
-
|
|
332
|
+
// The pivot's OWN shortlist capacity — not `recallQueryK`: they are different
|
|
333
|
+
// quantities (a mechanical sweep's probe budget vs the bridge's candidate-read
|
|
334
|
+
// allowance), and one number serving both means tightening either silently
|
|
335
|
+
// starves the other. The reason is the duplication, NOT a measured strangle:
|
|
336
|
+
// an earlier version of this comment claimed "at recallQueryK 1 the pivot finds
|
|
337
|
+
// no pivot", and that was probed and is FALSE on test/23's fixture — the
|
|
338
|
+
// strangle is fixture-specific, so it is not the evidence for this split.
|
|
339
|
+
const k = ctx.cfg.pivotProbeK;
|
|
333
340
|
// ONE perception of the answer, shared by the probe budget and the walk —
|
|
334
341
|
// this used to fold the same bytes twice, back to back, on every hop.
|
|
335
342
|
const tree = perceive(ctx, answer);
|
|
@@ -365,6 +372,17 @@ export async function pivotInto(
|
|
|
365
372
|
}
|
|
366
373
|
for (const c of n.kids) queue.push(c); // breadth-first: larger regions first
|
|
367
374
|
}
|
|
375
|
+
// The sweep's two FACTS, untraced (meter.ts contract 1: a counter never
|
|
376
|
+
// reaches a decision). `probes` is the work done; the shortfall is the
|
|
377
|
+
// capacity the cap withheld — the branches the breadth-first order never got
|
|
378
|
+
// to. Whether that withheld anything that mattered is NOT said here: the
|
|
379
|
+
// order spends the largest regions first, and recognition below still
|
|
380
|
+
// contributes every exact containment candidate regardless of the budget.
|
|
381
|
+
if (ctx.meter) {
|
|
382
|
+
ctx.meter.pivotProbes += probes;
|
|
383
|
+
const unprobed = branchCount - probeCap;
|
|
384
|
+
if (unprobed > 0) ctx.meter.pivotBranchesUnprobed += unprobed;
|
|
385
|
+
}
|
|
368
386
|
// THE FULL recognition, memo-shared with every other reader of these bytes.
|
|
369
387
|
// A "skip the edge trims here" variant was refuted (see recognise's own
|
|
370
388
|
// note): those trims are what find a WHOLE trained form embedded at an
|
package/src/mind/trace.ts
CHANGED
package/src/mind/traverse.ts
CHANGED
|
@@ -10,6 +10,13 @@ import { cosine, Vec } from "../vec.js";
|
|
|
10
10
|
import type { AncestorReach, MindContext, SaturationStop } from "./types.js";
|
|
11
11
|
import { gistOf, read } from "./primitives.js";
|
|
12
12
|
import { canonicalWindows, leafIdPrefix, leafIdRun } from "./canonical.js";
|
|
13
|
+
// Imported at the TOP, where every other import is. They used to sit 800 lines
|
|
14
|
+
// down under a note claiming the position mattered ("before trace module is
|
|
15
|
+
// loaded") — it does not: an ES module's static imports are HOISTED, so the
|
|
16
|
+
// file's line order never decides load order. The note described an intention
|
|
17
|
+
// the runtime does not honour; the imports move and the claim goes.
|
|
18
|
+
import { decodeText } from "./rationale.js";
|
|
19
|
+
import type { RationaleItem } from "./rationale.js";
|
|
13
20
|
|
|
14
21
|
// ── Session structural memo ─────────────────────────────────────────────
|
|
15
22
|
//
|
|
@@ -765,7 +772,15 @@ export function chooseNext(
|
|
|
765
772
|
// for the prevCount calls in the loop above, never for extra rItemShort
|
|
766
773
|
// byte-reads.
|
|
767
774
|
if (ctx.trace) {
|
|
768
|
-
|
|
775
|
+
// A BOUNDED SAMPLE, AND THE COUNT. The step used to carry EVERY candidate
|
|
776
|
+
// it weighed — measured on the trained store, 1559 out-items in one step
|
|
777
|
+
// (hubBound's own size) and 1082 in another (the hub's degree). The
|
|
778
|
+
// rationale's job is to explain the CHOICE, and the count is what says how
|
|
779
|
+
// wide the field was; the declared candidate budget (`recallQueryK`) is what
|
|
780
|
+
// bounds the sample, so no number is invented here.
|
|
781
|
+
const others = capped
|
|
782
|
+
.filter((c) => c !== best)
|
|
783
|
+
.slice(0, ctx.cfg.rationaleSampleK);
|
|
769
784
|
ctx.trace.step(
|
|
770
785
|
"disambiguate",
|
|
771
786
|
[rItemShort(ctx, best, "halo-evidence", bestSupport)],
|
|
@@ -816,11 +831,6 @@ export function chooseAmong(
|
|
|
816
831
|
: { id: candidates[0], score: -Infinity };
|
|
817
832
|
}
|
|
818
833
|
|
|
819
|
-
// ── Trace shim (used by chooseNext before trace module is loaded) ────────
|
|
820
|
-
|
|
821
|
-
import { decodeText } from "./rationale.js";
|
|
822
|
-
import type { RationaleItem } from "./rationale.js";
|
|
823
|
-
|
|
824
834
|
function rItemShort(
|
|
825
835
|
ctx: MindContext,
|
|
826
836
|
id: number,
|
package/src/mind/types.ts
CHANGED
|
@@ -65,12 +65,44 @@ export interface GraphSearchHost {
|
|
|
65
65
|
starts: ReadonlySet<number>;
|
|
66
66
|
};
|
|
67
67
|
chooseNext?(node: number): number | undefined;
|
|
68
|
+
/** The lengths `p` for which `prefix ‖ tail[0..p]` IS A STORED NODE, ascending
|
|
69
|
+
* — the join's candidate set, in the tail's own coordinates. Optional: a host
|
|
70
|
+
* that cannot answer makes the join fall back to every prefix, which is exact
|
|
71
|
+
* and complete but pays a `resolve` per offset.
|
|
72
|
+
*
|
|
73
|
+
* WHY NOT THE FOLD'S CUTS. A key names a relation exactly when the
|
|
74
|
+
* concatenation is a node, and a node's end is the end of ITS OWN stream —
|
|
75
|
+
* where the fold never emits a cut (geometry's `emit` guards `at >= n`). So a
|
|
76
|
+
* key can end strictly inside the tail with no boundary anywhere near it:
|
|
77
|
+
* measured, "stockholm mayor" exists, leads on to the mayor fact, and its
|
|
78
|
+
* boundary 6 is in neither the tail's cuts ([4,7]) nor the concatenation's.
|
|
79
|
+
* The fold's boundaries are a SUBSET of the real ends, not a proxy for them,
|
|
80
|
+
* and using them skipped the shortest names first — which is a semantic law,
|
|
81
|
+
* not an optimisation (test/106, test/108 pin it). */
|
|
82
|
+
contentKeyEnds?(prefix: Uint8Array, tail: Uint8Array): readonly number[];
|
|
68
83
|
/** The admission predicate — `traverse.ts`'s `leadsSomewhere`, its ONE
|
|
69
84
|
* definition: does this node bear an edge or a halo? Optional, so a bare
|
|
70
85
|
* host (a raw Store and nothing else) still works; when present, the search
|
|
71
86
|
* uses it rather than re-probing the store, which keeps the predicate
|
|
72
87
|
* single-defined AND memoised on the response-scoped struct cache. */
|
|
73
88
|
leadsSomewhere?(id: number): boolean;
|
|
89
|
+
/** Report a SEARCH REFUSAL into the rationale — the channel AGENTS §6
|
|
90
|
+
* requires: a callback threaded through a call chain must FEED the
|
|
91
|
+
* rationale, the way `GraphSearch`'s `onDerivation` feeds `traceDerivation`,
|
|
92
|
+
* never a channel of its own. Optional, so a bare host stays silent rather
|
|
93
|
+
* than crashing. */
|
|
94
|
+
reportSearch?(
|
|
95
|
+
name: string,
|
|
96
|
+
parts: ReadonlyArray<Uint8Array>,
|
|
97
|
+
note: string,
|
|
98
|
+
): void;
|
|
99
|
+
/** The CANONICAL resolver ({@link canonResolve}), optional like
|
|
100
|
+
* {@link leadsSomewhere}. The store's keys were written through the
|
|
101
|
+
* canonical fold, so a fact's `Gustaf Molander` and the deposited
|
|
102
|
+
* `gustaf molander` are the SAME node (measured inside a response: the
|
|
103
|
+
* canonical resolver maps the surface form to the deposited node while a raw
|
|
104
|
+
* resolve returns null). A bare host falls back to the plain probe. */
|
|
105
|
+
canonResolve?(bytes: Uint8Array): number | null;
|
|
74
106
|
}
|
|
75
107
|
|
|
76
108
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -119,11 +151,28 @@ export interface Attention {
|
|
|
119
151
|
* strength and its place.
|
|
120
152
|
* `vote` is a sum over every region that agreed, so it grows with how many
|
|
121
153
|
* places corroborated; `peak` is what the strongest one of them said on its
|
|
122
|
-
* own.
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
154
|
+
* own. THIS USED TO PRESCRIBE THE WRONG OPERAND. It read: "a consumer
|
|
155
|
+
* holding this point to consensusFloor(N) — a bar that prices ONE region's
|
|
156
|
+
* maximally-discriminative evidence — must read `peak`, not `vote`." The
|
|
157
|
+
* engine reads the POOLED vote, and thresholds.md §2 derives the floor for
|
|
158
|
+
* exactly that ("Pooled-vote significance floor": one maximally-specific
|
|
159
|
+
* region contributes at most ln N, and ln(N)+1/2 demands corroboration
|
|
160
|
+
* BEYOND one region). MEASURED across 27 anchors on 6 queries: all 11
|
|
161
|
+
* admissions cleared the floor by the sum and NONE by `peak` alone — a gate
|
|
162
|
+
* reading `peak` would refuse every root the engine elects. `peak` remains
|
|
163
|
+
* what it is: the strongest SINGLE region's contribution. */
|
|
126
164
|
peak: number;
|
|
165
|
+
/** The IDF-WEIGHTED sum behind this point — the quantity `consensusFloor` is
|
|
166
|
+
* derived for, and therefore the one the floor gates must read. It is
|
|
167
|
+
* MODE-INDEPENDENT by construction (its per-region weight is
|
|
168
|
+
* `mutual · idf / roots`, never the mode-dependent `wf`), so gating on it
|
|
169
|
+
* makes an anchor's admission the same in `inverse`, `direct` and `combined`.
|
|
170
|
+
* In `inverse` — the only mode the engine runs — it equals `vote` exactly
|
|
171
|
+
* (measured, test/55 test 17), so nothing about today's verdicts changes.
|
|
172
|
+
* MEASURED before this field existed: gating on `vote` DID flip a verdict,
|
|
173
|
+
* anchor 87 of test/55's query (inverse 2.682 admitted, direct 1.468
|
|
174
|
+
* refused, floor 2.292). */
|
|
175
|
+
idfVote: number;
|
|
127
176
|
/** SCALE-INVARIANT confidence: the fraction of the query's OWN regions
|
|
128
177
|
* whose evidence this point accounts for (Σ RegionVote.absorbed among
|
|
129
178
|
* its contributors, over the query's total region count) — read PER-
|