@hviana/sema 0.8.2 → 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 +11 -0
- package/dist/src/config.js +2 -0
- package/dist/src/geometry.d.ts +21 -0
- package/dist/src/geometry.js +21 -0
- package/dist/src/meter.d.ts +51 -0
- package/dist/src/meter.js +51 -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/graph-search.js +33 -14
- package/dist/src/mind/match.d.ts +1 -1
- package/dist/src/mind/match.js +5 -3
- package/dist/src/mind/mechanisms/cast.js +1 -1
- package/dist/src/mind/mechanisms/confluence.js +24 -0
- package/dist/src/mind/mechanisms/recall.js +32 -4
- package/dist/src/mind/mind.d.ts +4 -2
- package/dist/src/mind/mind.js +5 -4
- package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
- package/dist/src/mind/pipeline.js +41 -14
- 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 +21 -3
- package/dist/src/mind/reasoning.js +73 -21
- 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 +6 -2
- package/dist/src/mind/types.d.ts +36 -13
- 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 +3 -4
- 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 +13 -0
- package/src/geometry.ts +21 -0
- package/src/meter.ts +51 -0
- package/src/mind/attention.ts +167 -16
- package/src/mind/canonical.ts +43 -0
- package/src/mind/graph-search.ts +39 -14
- package/src/mind/match.ts +5 -3
- package/src/mind/mechanisms/cast.ts +3 -1
- package/src/mind/mechanisms/confluence.ts +24 -0
- package/src/mind/mechanisms/recall.ts +32 -4
- package/src/mind/mind.ts +6 -4
- package/src/mind/pipeline-mechanism.ts +7 -0
- package/src/mind/pipeline.ts +49 -16
- package/src/mind/primitives.ts +9 -1
- package/src/mind/rationale.ts +35 -1
- package/src/mind/reasoning.ts +92 -15
- 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 +7 -5
- package/src/mind/types.ts +36 -13
- package/test/105-derive-through-reports-its-refusal.test.mjs +24 -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/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/src/mind/graph-search.ts
CHANGED
|
@@ -238,6 +238,14 @@ export interface DerivationItem {
|
|
|
238
238
|
* {@link GraphSearch}'s rules fired, recovered from the rule's premise/
|
|
239
239
|
* conclusion shape (the rules carry no label, so this classifies by structure,
|
|
240
240
|
* the single place that maps rule geometry to a name). */
|
|
241
|
+
// CLOSED ON PURPOSE — AND ONLY THIS ONE IS. A derivation move is BRANCHED ON
|
|
242
|
+
// (`classifyMove`, the rationale's readers, MOVE_NOTE's fallback), so it is a
|
|
243
|
+
// closed union: adding one without teaching every reader is a compile error,
|
|
244
|
+
// which is what a closed vocabulary buys. The MECHANISM names in
|
|
245
|
+
// `rationale.ts` are the opposite case — written and displayed, never
|
|
246
|
+
// branched on — and they stay free strings that COMPOSE with the nesting
|
|
247
|
+
// (`["respond", "think", "recognise"]`). That asymmetry is deliberate; do not
|
|
248
|
+
// "fix" it by uniting the two (see test/126 for the pipeline half of it).
|
|
241
249
|
export type DerivationMove =
|
|
242
250
|
| "axiom" // a seed: a perceived leaf, a recognised form, or a computed result
|
|
243
251
|
| "follow-edge" // form→form via a continuation edge (STEP) — the core "what follows what"
|
|
@@ -1135,6 +1143,7 @@ export class GraphSearch {
|
|
|
1135
1143
|
// concepts/connectors either (those need the caller's async
|
|
1136
1144
|
// pre-resolution) — the recursion follows edges and fusion, which is what
|
|
1137
1145
|
// a deeper rewrite chain is made of.
|
|
1146
|
+
if (this.host.meter) this.host.meter.recompletes++;
|
|
1138
1147
|
const rec = this.host.recogniseSpan(bytes);
|
|
1139
1148
|
const kids = new Set(nrec.kids);
|
|
1140
1149
|
// THE NODE'S OWN KIDS ARE SITES BY STRUCTURE — recognition cannot be the
|
|
@@ -1391,20 +1400,36 @@ export class GraphSearch {
|
|
|
1391
1400
|
// duplicate read and a branch that could never be taken.
|
|
1392
1401
|
let next: number | null = null;
|
|
1393
1402
|
let keyBytes = c.bytes;
|
|
1394
|
-
// THE
|
|
1395
|
-
//
|
|
1396
|
-
//
|
|
1397
|
-
//
|
|
1398
|
-
//
|
|
1399
|
-
//
|
|
1400
|
-
//
|
|
1401
|
-
//
|
|
1402
|
-
// boundary
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1403
|
+
// THE CANDIDATE ENDS ARE THE PREFIXES THAT ARE STORED NODES, ASCENDING.
|
|
1404
|
+
// The key is `entity + prefix`, and it names a relation exactly when that
|
|
1405
|
+
// concatenation IS a node — so the ends come from a content-addressed
|
|
1406
|
+
// probe per offset (the host's `contentKeyEnds`, the learning path's own
|
|
1407
|
+
// mechanism: one leaf walk plus one `findBranch` per offset, no `resolve`),
|
|
1408
|
+
// never from the fold's boundaries. A boundary is not a proxy: a stored
|
|
1409
|
+
// member's end is the end of ITS OWN stream, and the fold never cuts at a
|
|
1410
|
+
// stream's end — measured, "stockholm mayor" exists, leads on to the mayor
|
|
1411
|
+
// fact, and its boundary 6 sits in neither the tail's cuts ([4,7]) nor the
|
|
1412
|
+
// concatenation's. Filtering the scan by "is this a node?" cannot change
|
|
1413
|
+
// the winner: a position that is not a node cannot resolve, so skipping it
|
|
1414
|
+
// is invisible; and the order stays SHORTEST FIRST, which is a semantic
|
|
1415
|
+
// law, not an optimisation (test/106, test/108 pin it).
|
|
1416
|
+
//
|
|
1417
|
+
// A host that cannot answer falls back to every prefix: exact and
|
|
1418
|
+
// complete, at a `resolve` per offset. A host that CAN answer is
|
|
1419
|
+
// authoritative even when it answers "none" — if no prefix is a node then
|
|
1420
|
+
// no key exists to resolve, so enumerating would only pay nulls. (A key
|
|
1421
|
+
// reachable through the CANONICAL equivalence alone and ending off every
|
|
1422
|
+
// node end is therefore not tried here; that dimension is unreachable on
|
|
1423
|
+
// this path by construction and is not part of the exact-key law.)
|
|
1424
|
+
const ends = this.host.contentKeyEnds?.(c.bytes, tail);
|
|
1425
|
+
const candidateEnds = function* (): Generator<number> {
|
|
1426
|
+
if (ends !== undefined) {
|
|
1427
|
+
yield* ends;
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
for (let p = 1; p <= tail.length; p++) yield p;
|
|
1431
|
+
};
|
|
1432
|
+
for (const len of candidateEnds()) {
|
|
1408
1433
|
keyBytes = concat2(c.bytes, tail.subarray(0, len));
|
|
1409
1434
|
const k = this.host.resolve(keyBytes) ??
|
|
1410
1435
|
this.host.canonResolve?.(keyBytes) ??
|
package/src/mind/match.ts
CHANGED
|
@@ -723,7 +723,7 @@ export function frameSlots(
|
|
|
723
723
|
* ANCHOR that the query displaced. Neither implies the other, and the
|
|
724
724
|
* observed failures pass the restatement guard cleanly.
|
|
725
725
|
*
|
|
726
|
-
*
|
|
726
|
+
* Four conditions, all byte-exact and all necessary:
|
|
727
727
|
*
|
|
728
728
|
* 1. the query and the anchor must be ONE STRUCTURE — what they share has to
|
|
729
729
|
* dominate the query, or the query is not a variant of the anchor at all
|
|
@@ -811,8 +811,10 @@ export function substituteAll(
|
|
|
811
811
|
const usable = pairs.filter((p) => p.needle.length > 0);
|
|
812
812
|
if (usable.length === 0) return hay;
|
|
813
813
|
// Longest needle first, so a needle that is a prefix of another can never
|
|
814
|
-
// pre-empt it. Ties cannot arise:
|
|
815
|
-
// pairwise distinct
|
|
814
|
+
// pre-empt it. Ties cannot arise: a consumer that VOICES checks the
|
|
815
|
+
// fillers pairwise with `distinct` and refuses such an instance itself —
|
|
816
|
+
// `frameSlots` reports and does not judge (see its own doc), so the refusal
|
|
817
|
+
// lives with the mechanism that needs it, not here.
|
|
816
818
|
const order = [...usable].sort((a, b) => b.needle.length - a.needle.length);
|
|
817
819
|
const out: number[] = [];
|
|
818
820
|
let i = 0;
|
|
@@ -867,7 +867,9 @@ export async function counterfactualTransfer(
|
|
|
867
867
|
// grounds") — fine for ORIENTING mechanisms, not for voicing learnt
|
|
868
868
|
// content the query never asked about. Computed once here; both the
|
|
869
869
|
// hub fallback below and the comparison gate consume it.
|
|
870
|
-
const rootTrusted = roots.some((r) =>
|
|
870
|
+
const rootTrusted = roots.some((r) =>
|
|
871
|
+
r.idfVote >= consensusFloor(corpusN(ctx))
|
|
872
|
+
); // the IDF sum: the bar's own quantity
|
|
871
873
|
// The context that ESTABLISHES a filler — the same reverse context, under
|
|
872
874
|
// the same naming test, `seatOfNode` uses to VOICE an analog (a predecessor
|
|
873
875
|
// whose bytes CONTAIN the node's: it names or describes it, rather than
|
|
@@ -146,6 +146,30 @@ export async function confluenceJoin(
|
|
|
146
146
|
const bindsAConstituent = (cover: Array<[number, number]>): boolean =>
|
|
147
147
|
cover.some(([cs, ce]) => ce - cs >= 2 * W);
|
|
148
148
|
|
|
149
|
+
// THE VOTE ENTERS AS ORDER, NEVER AS A BAR. This is the only one of the
|
|
150
|
+
// climb's four consumers (recall, fuseAttention, cast, here) that uses the
|
|
151
|
+
// evidence's MAGNITUDE without a floor, and it is legitimate by construction:
|
|
152
|
+
// `ranked` answers "which anchor is stronger" — a question about votes, so the
|
|
153
|
+
// comparison stays within one dimension — and the vote is otherwise only
|
|
154
|
+
// REPORTED (Stream.vote travels to the rationale's constraint nodes). What
|
|
155
|
+
// actually SELECTS a constraint is byte-structural and never the magnitude: a
|
|
156
|
+
// run of at least 2W (`bindsAConstituent`, with its accidental-sharing
|
|
157
|
+
// counter-examples above), disjoint covers (`disjoint`), and scaffolding never
|
|
158
|
+
// binds at all (`dominates(reachOf(…), N)`). The MEET such a stream may
|
|
159
|
+
// produce is selected the same way: a span shorter than 2W is rejected, and
|
|
160
|
+
// the winner is the one with the smallest `reach` (ties broken by the longer
|
|
161
|
+
// span) — a corpus quantity and bytes, never the vote, which appears only in
|
|
162
|
+
// the trace item.
|
|
163
|
+
// binds at all (`dominates(reachOf(…), N)`). The only cut in this loop is a
|
|
164
|
+
// BUDGET, and it is measured: stopping the scan at 2W anchors saves 50-70% of
|
|
165
|
+
// confluence's cost on non-conjunctive queries while preserving every genuinely
|
|
166
|
+
// conjunctive case, whose top anchors ARE its constraints.
|
|
167
|
+
// MEASURED (this goal, on THIS file's own conjunctive fixture): the two
|
|
168
|
+
// streams appear at ranks 1 and 4 against a budget of 2W = 8, on a query whose
|
|
169
|
+
// `ranked` is 9 — so the cut IS live (it would have returned null at the 8th
|
|
170
|
+
// anchor) and it does NOT prune the case it exists to protect. The other
|
|
171
|
+
// conjunctive fixture (the Leonardo one) finds them at ranks 0 and 1. Scope:
|
|
172
|
+
// these are the repo's conjunctive fixtures, and no more.
|
|
149
173
|
const streams: Stream[] = [];
|
|
150
174
|
const rankedCapped = ranked.length > pre.k ? ranked.slice(0, pre.k) : ranked;
|
|
151
175
|
// CONJUNCTIVITY EARLY-EXIT: a conjunctive query's top-ranked anchors
|
|
@@ -276,9 +276,36 @@ export async function recallByResonance(
|
|
|
276
276
|
// consensus", while breadth is the SCALE-INVARIANT reading — "a point whose
|
|
277
277
|
// breadth clears `dominates` (> half the query's regions corroborate it) is
|
|
278
278
|
// real consensus; one that does not is a coincidental single-region echo".
|
|
279
|
-
//
|
|
280
|
-
// comparing a POOLED SUM against a floor that prices ONE region's
|
|
281
|
-
// is a dimensional error.
|
|
279
|
+
// THIS USED TO CLAIM A DIMENSIONAL ERROR, AND THAT CLAIM WAS FALSE.
|
|
280
|
+
// It read: "comparing a POOLED SUM against a floor that prices ONE region's
|
|
281
|
+
// evidence is a dimensional error." `consensusFloor` is not priced for one
|
|
282
|
+
// region: thresholds.md §2 derives it as the POOLED-vote significance floor —
|
|
283
|
+
// "each region contributes at most ln(N/c) <= ln(N); ln(N)+1/2 demands ..." —
|
|
284
|
+
// and attention.ts says the same where it builds the vote ("the scale
|
|
285
|
+
// consensusFloor is derived for"). The comparison is in ONE dimension, and
|
|
286
|
+
// it is so because the climb WEIGHTS BY IDF: `wf` in voteRegions is
|
|
287
|
+
// `direct ? df : combined ? idf + df : idf`, and the engine only ever runs the
|
|
288
|
+
// last one (DFMode's default "inverse", the mode every non-test caller uses —
|
|
289
|
+
// `direct` and `combined` are exercised by test/24 and test/27 only, and
|
|
290
|
+
// test/24 pins that their votes DO differ). In those two the sum would leave
|
|
291
|
+
// the floor's dimension and the floor would need re-deriving.
|
|
292
|
+
//
|
|
293
|
+
// What the OR below is really for is SCALE, not dimension (the paragraph
|
|
294
|
+
// above says it): a vote that clears ln(N)+1/2 means "strong" on a small store
|
|
295
|
+
// and "weak" on a large one for the same genuine consensus, so the
|
|
296
|
+
// scale-invariant breadth reading is added beside it.
|
|
297
|
+
//
|
|
298
|
+
// AND THE PREMISE IS IDF. The deviation in the other two weighting modes is
|
|
299
|
+
// TWO-SIDED and DERIVED: `direct` DEFLATES a region (ln(1+c) < ln(N/c) for
|
|
300
|
+
// small c) and `combined` INFLATES it (ln N + ln(1+1/c)), both by at most
|
|
301
|
+
// `ln 2` — see `geometry.ts`'s `consensusFloor`, where the bound lives.
|
|
302
|
+
// MEASURED on 8 anchors across 5 queries, running the same climb in all
|
|
303
|
+
// three modes: ZERO gate inversions — every anchor's `vote >= floor` verdict
|
|
304
|
+
// is the same in `inverse`, `direct` and `combined`, even where the readings
|
|
305
|
+
// straddle the floor on opposite sides (#148: inverse 3.39, combined 4.71
|
|
306
|
+
// above it, direct 1.31 below). Pinned by test/55's test 20. The bar is not
|
|
307
|
+
// re-derived for those modes because nothing reachable needs it; the premise
|
|
308
|
+
// is IDF, and that is now written where the gate reads it.
|
|
282
309
|
//
|
|
283
310
|
// Measured on the 15.7M-node store (N=325,615, so the old floor was 13.19).
|
|
284
311
|
// The absolute vote cannot separate right from wrong at this scale, and the
|
|
@@ -348,7 +375,7 @@ export async function recallByResonance(
|
|
|
348
375
|
if (
|
|
349
376
|
forest.length > 0 &&
|
|
350
377
|
!allWindowsAreScaffolding(ctx, query) &&
|
|
351
|
-
(forest[0].
|
|
378
|
+
(forest[0].idfVote >= minVote || // the IDF sum: the bar's own quantity
|
|
352
379
|
(dominates(forest[0].breadth, 1) && forest[0].peak > Math.LN2))
|
|
353
380
|
) {
|
|
354
381
|
const g = await project(ctx, forest[0].anchor, queryGist);
|
|
@@ -602,6 +629,7 @@ export const recallMechanism: PipelineMechanism = {
|
|
|
602
629
|
moves: r.moves,
|
|
603
630
|
unexplained: r.unexplained,
|
|
604
631
|
provenance: r.echoed ? "recall-echo" : "recall",
|
|
632
|
+
used: new Set<number>(),
|
|
605
633
|
...(r.complete ? { complete: true } : {}),
|
|
606
634
|
}];
|
|
607
635
|
},
|
package/src/mind/mind.ts
CHANGED
|
@@ -16,7 +16,6 @@ import type { CorpusPair, CorpusResult } from "./corpus.js";
|
|
|
16
16
|
import { Alphabet } from "../alphabet.js";
|
|
17
17
|
import {
|
|
18
18
|
bytesToTree,
|
|
19
|
-
contentBoundaries,
|
|
20
19
|
contentFoldIncremental,
|
|
21
20
|
Grid,
|
|
22
21
|
gridToTree,
|
|
@@ -24,6 +23,7 @@ import {
|
|
|
24
23
|
reachThreshold,
|
|
25
24
|
stackGrids,
|
|
26
25
|
} from "../geometry.js";
|
|
26
|
+
import { keyEnds } from "./canonical.js";
|
|
27
27
|
import type { ContentFold } from "../geometry.js";
|
|
28
28
|
import { BoundedMap, type Store } from "../store.js";
|
|
29
29
|
import { SQliteStore } from "../store-sqlite.js";
|
|
@@ -244,6 +244,8 @@ export interface MindOptions {
|
|
|
244
244
|
seed?: number;
|
|
245
245
|
recallQueryK?: number;
|
|
246
246
|
haloQueryK?: number;
|
|
247
|
+
/** Branch nodes the pivot sweep may probe — see {@link MindConfig}. */
|
|
248
|
+
pivotProbeK?: number;
|
|
247
249
|
/** Items one rationale step may itemise — see {@link MindConfig}. */
|
|
248
250
|
rationaleSampleK?: number;
|
|
249
251
|
/** Corpus-reading capacities and budgets — see {@link MindConfig}. */
|
|
@@ -443,9 +445,9 @@ export class Mind implements MindContext {
|
|
|
443
445
|
* with the most distributional evidence (highest `prevOf` count — the
|
|
444
446
|
* structural manifestation of its halo). When evidence is equal the
|
|
445
447
|
* first-inserted edge wins. */
|
|
446
|
-
/** See {@link GraphSearchHost.
|
|
447
|
-
|
|
448
|
-
return
|
|
448
|
+
/** See {@link GraphSearchHost.contentKeyEnds}. */
|
|
449
|
+
contentKeyEnds(prefix: Uint8Array, tail: Uint8Array): readonly number[] {
|
|
450
|
+
return keyEnds(this, prefix, tail);
|
|
449
451
|
}
|
|
450
452
|
|
|
451
453
|
chooseNext(node: number): number | undefined {
|
|
@@ -675,6 +675,13 @@ export interface MechanismResult {
|
|
|
675
675
|
bytes: Uint8Array;
|
|
676
676
|
accounted: Array<[number, number]>;
|
|
677
677
|
moves: number;
|
|
678
|
+
/** WHAT THIS ANSWER SPEAKS FOR — the anchors it voices, and therefore the
|
|
679
|
+
* content the reasoner must not pivot back through. Declared by the
|
|
680
|
+
* mechanism about its OWN result, exactly like `accounted`/`unexplained`/
|
|
681
|
+
* `complete`: post-grounding honours the property and NEVER ASKS WHICH
|
|
682
|
+
* MECHANISM SET IT, so the market stays uniform. An EMPTY set is a real
|
|
683
|
+
* declaration — "this answer voices nothing" (recall) — and withholds
|
|
684
|
+
* nothing; omit the field and the pipeline re-recognises the answer. */
|
|
678
685
|
used?: ReadonlySet<number>;
|
|
679
686
|
unexplained: string;
|
|
680
687
|
/** Explicit weight override. When absent, weight = moves + PASS·unaccounted. */
|
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,11 +536,30 @@ 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
|
-
|
|
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
|
+
);
|
|
548
563
|
// REPORTABLE, NOT SILENT. A declared-complete grounding ends the derivation
|
|
549
564
|
// here, and that decision is part of the derivation's shape: the reader of a
|
|
550
565
|
// rationale must be able to see that the chain stopped because the mechanism
|
|
@@ -573,12 +588,24 @@ export async function think(
|
|
|
573
588
|
];
|
|
574
589
|
const uncovered = unexplainedSpans(query.length, explained)
|
|
575
590
|
.filter(([a, b]) => b - a >= ctx.space.maxGroup);
|
|
576
|
-
|
|
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
|
|
577
603
|
? await meter.time(
|
|
578
604
|
"reason",
|
|
579
605
|
() => reason(ctx, query, answer, preConsumed, pre, voiced, uncovered),
|
|
580
606
|
)
|
|
581
607
|
: await reason(ctx, query, answer, preConsumed, pre, voiced, uncovered);
|
|
608
|
+
const reasoned = extension?.bytes ?? answer;
|
|
582
609
|
|
|
583
610
|
// Fuse only when the query has a genuine REMAINDER no mechanism's
|
|
584
611
|
// structural evidence touched at all. `decided.accounted` alone
|
|
@@ -639,7 +666,13 @@ export async function think(
|
|
|
639
666
|
|
|
640
667
|
done(
|
|
641
668
|
fused,
|
|
642
|
-
|
|
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",
|
|
643
676
|
);
|
|
644
677
|
return { bytes: fused, provenance };
|
|
645
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,
|
|
@@ -47,14 +66,16 @@ export async function reason(
|
|
|
47
66
|
* `unaccounted` spans. Only the reasoner's OWN extensions are judged
|
|
48
67
|
* against it; a mechanism carrying its own `used` set owns its shape. */
|
|
49
68
|
uncovered: readonly (readonly [number, number])[] = [],
|
|
50
|
-
): Promise<
|
|
69
|
+
): Promise<ReasonedAnswer> {
|
|
51
70
|
// Echo guard: a query that is ITSELF a learnt continuation (some context's
|
|
52
71
|
// answer) is being asked back at the system — hopping forward from it would
|
|
53
72
|
// chain through the very fact that produced it and echo the conversation
|
|
54
73
|
// back. The grounded answer alone is the honest read-out. Deliberately a
|
|
55
74
|
// broad structural gate; pinned by test/31-audit.
|
|
56
75
|
const qId = pre.queryResolved;
|
|
57
|
-
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
|
+
}
|
|
58
79
|
|
|
59
80
|
// Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
|
|
60
81
|
// the hub bound, via the store's LIMITed edge reads: a common continuation's
|
|
@@ -113,7 +134,7 @@ export async function reason(
|
|
|
113
134
|
? null
|
|
114
135
|
: ctx.store.prevFirst(groundedId, bound);
|
|
115
136
|
if (qId !== null && groundedPrev !== null && groundedPrev.includes(qId)) {
|
|
116
|
-
return answer;
|
|
137
|
+
return { bytes: answer, carried: [], steps: 0 };
|
|
117
138
|
}
|
|
118
139
|
|
|
119
140
|
const consumed = new Set<number>();
|
|
@@ -159,7 +180,27 @@ export async function reason(
|
|
|
159
180
|
const qv = pre.guide; // the response-wide guide IS the query's gist
|
|
160
181
|
let t: ReturnType<Rationale["enter"]> | undefined;
|
|
161
182
|
const startedFrom = answer;
|
|
162
|
-
|
|
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++) {
|
|
163
204
|
// Hop 0's `cur` IS `answer`, so the guard above already resolved it and
|
|
164
205
|
// read its reverse edges — reuse both rather than repeat them.
|
|
165
206
|
const curId = hop === 0 ? groundedId : resolve(ctx, cur);
|
|
@@ -192,6 +233,7 @@ export async function reason(
|
|
|
192
233
|
"the answer is itself a learnt fact — follow its continuation to the fixpoint",
|
|
193
234
|
);
|
|
194
235
|
cur = fwd;
|
|
236
|
+
steps++;
|
|
195
237
|
continue;
|
|
196
238
|
}
|
|
197
239
|
}
|
|
@@ -227,12 +269,17 @@ export async function reason(
|
|
|
227
269
|
if (!producerOwnsShape && uncovered.length > 0) {
|
|
228
270
|
const W = ctx.space.maxGroup;
|
|
229
271
|
let progress = false;
|
|
272
|
+
let justified: [number, number] | undefined;
|
|
230
273
|
for (const [a, b] of uncovered) {
|
|
231
274
|
for (let i = a; i + W <= b && !progress; i++) {
|
|
232
|
-
if (indexOf(fc, query.subarray(i, i + W), 0) >= 0)
|
|
275
|
+
if (indexOf(fc, query.subarray(i, i + W), 0) >= 0) {
|
|
276
|
+
progress = true;
|
|
277
|
+
justified = [a, b];
|
|
278
|
+
}
|
|
233
279
|
}
|
|
234
280
|
if (progress) break;
|
|
235
281
|
}
|
|
282
|
+
if (progress && justified !== undefined) carried.push(justified);
|
|
236
283
|
if (!progress) {
|
|
237
284
|
// THE BRAKE, MADE VISIBLE. The reasoner declines a step that carries
|
|
238
285
|
// none of the material the grounding left uncovered — the drift the
|
|
@@ -243,7 +290,25 @@ export async function reason(
|
|
|
243
290
|
// the check disabled, test/110 and test/116 fail — so this brake is the
|
|
244
291
|
// only thing keeping the extension honest until the pivot reports its
|
|
245
292
|
// own accounted spans and the ladder can judge it instead.
|
|
246
|
-
|
|
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);
|
|
247
312
|
ctx.trace?.step(
|
|
248
313
|
"pivotRefused",
|
|
249
314
|
[rItem(cur, "answer"), rItem(query, "query")],
|
|
@@ -263,12 +328,25 @@ export async function reason(
|
|
|
263
328
|
"pivot on the shared span this answer contains, then step forward across that fact",
|
|
264
329
|
);
|
|
265
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);
|
|
266
340
|
}
|
|
267
341
|
t?.done(
|
|
268
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.
|
|
269
347
|
"the multi-hop chain's fixpoint",
|
|
270
348
|
);
|
|
271
|
-
return cur;
|
|
349
|
+
return { bytes: cur, carried, steps };
|
|
272
350
|
}
|
|
273
351
|
|
|
274
352
|
/** Fuse independent points of attention into one answer (multi-topic).
|
|
@@ -462,9 +540,8 @@ export async function fuseAttention(
|
|
|
462
540
|
[rItem(out, "answer", resolve(ctx, out) ?? undefined)],
|
|
463
541
|
`fused ${pieces.length} independent points of attention into one answer`,
|
|
464
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++;
|
|
465
546
|
return out;
|
|
466
547
|
}
|
|
467
|
-
|
|
468
|
-
// (resonance.js is already a static dependency above — `bridge` — so the old
|
|
469
|
-
// dynamic import of pivotInto guarded against a cycle that does not exist.)
|
|
470
|
-
import { containsSpan } from "./match.js";
|