@hviana/sema 0.8.2 → 0.8.5
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 +38 -37
- package/README.md +17 -38
- package/TRADEMARKS.md +0 -1
- package/dist/example/demo.js +85 -34
- package/dist/src/config.d.ts +11 -0
- package/dist/src/config.js +2 -0
- package/dist/src/geometry.d.ts +21 -10
- package/dist/src/geometry.js +21 -12
- package/dist/src/meter.d.ts +62 -0
- package/dist/src/meter.js +62 -0
- package/dist/src/mind/articulation.js +1 -1
- package/dist/src/mind/attention.d.ts +4 -0
- package/dist/src/mind/attention.js +167 -17
- package/dist/src/mind/canonical.d.ts +16 -0
- package/dist/src/mind/canonical.js +41 -0
- package/dist/src/mind/derivation.d.ts +201 -0
- package/dist/src/mind/derivation.js +327 -0
- package/dist/src/mind/graph-search.d.ts +2 -1
- package/dist/src/mind/graph-search.js +70 -29
- package/dist/src/mind/match.d.ts +3 -1
- package/dist/src/mind/match.js +7 -3
- package/dist/src/mind/mechanisms/alu.js +0 -2
- package/dist/src/mind/mechanisms/cast.d.ts +1 -5
- package/dist/src/mind/mechanisms/cast.js +16 -19
- package/dist/src/mind/mechanisms/confluence.d.ts +0 -3
- package/dist/src/mind/mechanisms/confluence.js +27 -9
- package/dist/src/mind/mechanisms/cover.js +17 -20
- package/dist/src/mind/mechanisms/extraction.d.ts +0 -1
- package/dist/src/mind/mechanisms/extraction.js +13 -8
- package/dist/src/mind/mechanisms/prefix-completion.js +0 -1
- package/dist/src/mind/mechanisms/recall.d.ts +0 -1
- package/dist/src/mind/mechanisms/recall.js +40 -13
- package/dist/src/mind/mechanisms/reference.js +3 -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 -3
- package/dist/src/mind/pipeline.js +136 -44
- package/dist/src/mind/primitives.js +9 -1
- package/dist/src/mind/rationale.d.ts +21 -5
- package/dist/src/mind/rationale.js +16 -21
- package/dist/src/mind/reasoning.d.ts +12 -20
- package/dist/src/mind/reasoning.js +190 -106
- 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/dist/src/mind/types.js +6 -3
- package/docs/INDEX.md +23 -24
- package/docs/INVARIANTS.md +16 -17
- package/docs/architecture/bounded-reads.md +5 -5
- package/docs/architecture/closure.md +65 -0
- package/docs/architecture/commonality.md +29 -20
- package/docs/architecture/cost-model.md +7 -7
- package/docs/architecture/determinism.md +7 -7
- package/docs/architecture/exact-vs-approximate.md +4 -4
- package/docs/architecture/factored-machinery.md +14 -14
- package/docs/architecture/match-project.md +2 -3
- package/docs/architecture/mechanism-market.md +16 -16
- package/docs/architecture/meter.md +10 -11
- package/docs/architecture/store.md +4 -4
- package/docs/architecture/thresholds.md +1 -1
- package/docs/failures/tempting-but-wrong.md +14 -5
- package/docs/harness/gates.md +7 -7
- package/docs/mechanisms/cast.md +2 -2
- package/docs/mechanisms/cover.md +4 -5
- package/docs/mechanisms/extraction.md +7 -7
- package/docs/mechanisms/recall.md +8 -9
- package/example/demo.ts +90 -37
- 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 -13
- package/src/meter.ts +62 -0
- package/src/mind/articulation.ts +0 -1
- package/src/mind/attention.ts +169 -17
- package/src/mind/canonical.ts +43 -0
- package/src/mind/derivation.ts +473 -0
- package/src/mind/graph-search.ts +76 -34
- package/src/mind/match.ts +7 -3
- package/src/mind/mechanisms/alu.ts +0 -2
- package/src/mind/mechanisms/cast.ts +20 -22
- package/src/mind/mechanisms/confluence.ts +27 -13
- package/src/mind/mechanisms/cover.ts +17 -20
- package/src/mind/mechanisms/extraction.ts +13 -9
- package/src/mind/mechanisms/prefix-completion.ts +0 -1
- package/src/mind/mechanisms/recall.ts +39 -13
- package/src/mind/mechanisms/reference.ts +2 -3
- package/src/mind/mind.ts +6 -4
- package/src/mind/pipeline-mechanism.ts +7 -3
- package/src/mind/pipeline.ts +160 -52
- package/src/mind/primitives.ts +9 -1
- package/src/mind/rationale.ts +27 -23
- package/src/mind/reasoning.ts +227 -120
- 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 +41 -15
- 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/133-the-decision-point-renders-the-state.test.mjs +204 -0
- package/test/134-the-law-explains-the-engines-own-refusal.test.mjs +237 -0
- package/test/135-one-law-any-producer.test.mjs +289 -0
- package/test/136-the-two-named-limits.test.mjs +205 -0
- package/test/137-the-law-lives-once-and-below.test.mjs +400 -0
- package/test/138-the-remainder-drains-only-what-a-move-declares.test.mjs +62 -0
- package/test/139-the-witness-is-engagement-not-explanation.test.mjs +51 -0
- package/test/140-irrelevant-supply-does-not-change-an-answer.test.mjs +48 -0
- package/test/141-the-question-is-paid-at-construction.test.mjs +98 -0
- package/test/32-confluence.test.mjs +68 -0
- package/test/36-already-answered-fusion.test.mjs +20 -2
- package/test/37-cluster-dispersion-fusion.test.mjs +30 -3
- package/test/38-reason-restate-guard.test.mjs +28 -2
- package/test/43-cast-analog-seat.test.mjs +10 -0
- package/test/55-cost-meter.test.mjs +862 -0
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
voicesDisplacedFiller,
|
|
25
25
|
} from "../match.js";
|
|
26
26
|
import { CONCEPT, STEP } from "../graph-search.js";
|
|
27
|
-
import {
|
|
27
|
+
import { restates as lawRestates } from "../derivation.js";
|
|
28
28
|
import type { PipelineMechanism, Precomputed } from "../pipeline-mechanism.js";
|
|
29
29
|
import { rItem, rNode } from "../trace.js";
|
|
30
30
|
import { substitutionBridge } from "../bridge.js";
|
|
@@ -35,7 +35,6 @@ export interface RecallResult {
|
|
|
35
35
|
echoed: boolean;
|
|
36
36
|
accounted: Array<[number, number]>;
|
|
37
37
|
moves: number;
|
|
38
|
-
unexplained: string;
|
|
39
38
|
/** See {@link import("../pipeline-mechanism.js").MechanismResult.complete}
|
|
40
39
|
* — set by the IDENTITY-bridge tier alone. */
|
|
41
40
|
complete?: boolean;
|
|
@@ -71,7 +70,6 @@ export async function recallByResonance(
|
|
|
71
70
|
echoed,
|
|
72
71
|
accounted,
|
|
73
72
|
moves,
|
|
74
|
-
unexplained: unexplainedLabel(query, accounted),
|
|
75
73
|
...(complete ? { complete } : {}),
|
|
76
74
|
};
|
|
77
75
|
};
|
|
@@ -156,7 +154,7 @@ export async function recallByResonance(
|
|
|
156
154
|
// conversation reads as if it were the next thing to say.
|
|
157
155
|
if (
|
|
158
156
|
g !== null && g.length > 0 &&
|
|
159
|
-
!(
|
|
157
|
+
!lawRestates(query, g, 0, { proper: true })
|
|
160
158
|
) {
|
|
161
159
|
return ground(
|
|
162
160
|
g,
|
|
@@ -196,10 +194,11 @@ export async function recallByResonance(
|
|
|
196
194
|
// same principle that keeps cast from voicing stored questions), and
|
|
197
195
|
// projecting them forward is reverse recall's containment failure in the
|
|
198
196
|
// other direction — "whatever followed these bytes in some document".
|
|
199
|
-
|
|
197
|
+
// THE EQUALITY READING of the restatement law: this tier rejects an answer
|
|
198
|
+
// that IS the question (an echo), and a proper fragment is handled by the
|
|
199
|
+
// tier's own subspan tests further down — so the law is asked with `whole`.
|
|
200
200
|
const restates = (b: Uint8Array): boolean =>
|
|
201
|
-
|
|
202
|
-
(ctx.canon !== null && bytesEqual(ctx.canon(b), qKey));
|
|
201
|
+
lawRestates(query, b, 0, { equate: ctx.canon, whole: true });
|
|
203
202
|
const idBar = identityBar(ctx.store.D, ctx.space.maxGroup, query.length);
|
|
204
203
|
if (top.score >= idBar) {
|
|
205
204
|
for (const h of whole) {
|
|
@@ -276,9 +275,36 @@ export async function recallByResonance(
|
|
|
276
275
|
// consensus", while breadth is the SCALE-INVARIANT reading — "a point whose
|
|
277
276
|
// breadth clears `dominates` (> half the query's regions corroborate it) is
|
|
278
277
|
// 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.
|
|
278
|
+
// THIS USED TO CLAIM A DIMENSIONAL ERROR, AND THAT CLAIM WAS FALSE.
|
|
279
|
+
// It read: "comparing a POOLED SUM against a floor that prices ONE region's
|
|
280
|
+
// evidence is a dimensional error." `consensusFloor` is not priced for one
|
|
281
|
+
// region: thresholds.md §2 derives it as the POOLED-vote significance floor —
|
|
282
|
+
// "each region contributes at most ln(N/c) <= ln(N); ln(N)+1/2 demands ..." —
|
|
283
|
+
// and attention.ts says the same where it builds the vote ("the scale
|
|
284
|
+
// consensusFloor is derived for"). The comparison is in ONE dimension, and
|
|
285
|
+
// it is so because the climb WEIGHTS BY IDF: `wf` in voteRegions is
|
|
286
|
+
// `direct ? df : combined ? idf + df : idf`, and the engine only ever runs the
|
|
287
|
+
// last one (DFMode's default "inverse", the mode every non-test caller uses —
|
|
288
|
+
// `direct` and `combined` are exercised by test/24 and test/27 only, and
|
|
289
|
+
// test/24 pins that their votes DO differ). In those two the sum would leave
|
|
290
|
+
// the floor's dimension and the floor would need re-deriving.
|
|
291
|
+
//
|
|
292
|
+
// What the OR below is really for is SCALE, not dimension (the paragraph
|
|
293
|
+
// above says it): a vote that clears ln(N)+1/2 means "strong" on a small store
|
|
294
|
+
// and "weak" on a large one for the same genuine consensus, so the
|
|
295
|
+
// scale-invariant breadth reading is added beside it.
|
|
296
|
+
//
|
|
297
|
+
// AND THE PREMISE IS IDF. The deviation in the other two weighting modes is
|
|
298
|
+
// TWO-SIDED and DERIVED: `direct` DEFLATES a region (ln(1+c) < ln(N/c) for
|
|
299
|
+
// small c) and `combined` INFLATES it (ln N + ln(1+1/c)), both by at most
|
|
300
|
+
// `ln 2` — see `geometry.ts`'s `consensusFloor`, where the bound lives.
|
|
301
|
+
// MEASURED on 8 anchors across 5 queries, running the same climb in all
|
|
302
|
+
// three modes: ZERO gate inversions — every anchor's `vote >= floor` verdict
|
|
303
|
+
// is the same in `inverse`, `direct` and `combined`, even where the readings
|
|
304
|
+
// straddle the floor on opposite sides (#148: inverse 3.39, combined 4.71
|
|
305
|
+
// above it, direct 1.31 below). Pinned by test/55's test 20. The bar is not
|
|
306
|
+
// re-derived for those modes because nothing reachable needs it; the premise
|
|
307
|
+
// is IDF, and that is now written where the gate reads it.
|
|
282
308
|
//
|
|
283
309
|
// Measured on the 15.7M-node store (N=325,615, so the old floor was 13.19).
|
|
284
310
|
// The absolute vote cannot separate right from wrong at this scale, and the
|
|
@@ -348,7 +374,7 @@ export async function recallByResonance(
|
|
|
348
374
|
if (
|
|
349
375
|
forest.length > 0 &&
|
|
350
376
|
!allWindowsAreScaffolding(ctx, query) &&
|
|
351
|
-
(forest[0].
|
|
377
|
+
(forest[0].idfVote >= minVote || // the IDF sum: the bar's own quantity
|
|
352
378
|
(dominates(forest[0].breadth, 1) && forest[0].peak > Math.LN2))
|
|
353
379
|
) {
|
|
354
380
|
const g = await project(ctx, forest[0].anchor, queryGist);
|
|
@@ -381,7 +407,7 @@ export async function recallByResonance(
|
|
|
381
407
|
// — never an answer (the same principle as `restates` above, extended
|
|
382
408
|
// to fragments). Genuine anchor groundings — longer than the query,
|
|
383
409
|
// or disjoint from it — pass untouched.
|
|
384
|
-
else if (g && !(
|
|
410
|
+
else if (g && !lawRestates(query, g, 0, { proper: true })) {
|
|
385
411
|
return ground(
|
|
386
412
|
g,
|
|
387
413
|
"scaffolding-dominated query — ground the consensus-climb anchor",
|
|
@@ -600,8 +626,8 @@ export const recallMechanism: PipelineMechanism = {
|
|
|
600
626
|
bytes: r.bytes,
|
|
601
627
|
accounted: r.accounted,
|
|
602
628
|
moves: r.moves,
|
|
603
|
-
unexplained: r.unexplained,
|
|
604
629
|
provenance: r.echoed ? "recall-echo" : "recall",
|
|
630
|
+
used: new Set<number>(),
|
|
605
631
|
...(r.complete ? { complete: true } : {}),
|
|
606
632
|
}];
|
|
607
633
|
},
|
|
@@ -39,7 +39,7 @@ import type { FrameInstance } from "../match.js";
|
|
|
39
39
|
import { carriesFillers, distinct, follow, substituteAll } from "../match.js";
|
|
40
40
|
import { dominates } from "../../geometry.js";
|
|
41
41
|
import { bytesEqual, indexOf } from "../../bytes.js";
|
|
42
|
-
import {
|
|
42
|
+
import { restates } from "../derivation.js";
|
|
43
43
|
import { STEP } from "../graph-search.js";
|
|
44
44
|
import type {
|
|
45
45
|
MechanismResult,
|
|
@@ -231,7 +231,7 @@ export async function bindReference(
|
|
|
231
231
|
if (bytes.length === 0) return fail("the binding produced nothing");
|
|
232
232
|
// Answering with the question is not answering — the same restated-fragment
|
|
233
233
|
// guard every recall tier applies.
|
|
234
|
-
if (
|
|
234
|
+
if (restates(query, bytes, 0, { proper: true })) {
|
|
235
235
|
return fail("the binding restates part of the question");
|
|
236
236
|
}
|
|
237
237
|
const carried = !bytesEqual(bytes, first);
|
|
@@ -278,7 +278,6 @@ export async function bindReference(
|
|
|
278
278
|
// binding claims strictly more than a one-slot binding, so where both are
|
|
279
279
|
// licensed the smaller claim wins.
|
|
280
280
|
moves: STEP * slots.length + STEP,
|
|
281
|
-
unexplained: unexplainedLabel(query, accounted),
|
|
282
281
|
// NOT scaffolding. That field counts answer bytes carried through BECAUSE
|
|
283
282
|
// NOTHING EXPLAINED THEM; a referent is carried because the frame's slot
|
|
284
283
|
// explains it, and it is accounted above. Reporting it would make every
|
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,10 +675,14 @@ 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`/`used`/
|
|
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
|
-
unexplained: string;
|
|
680
|
-
/** Explicit weight override. When absent, weight = moves + PASS·unaccounted. */
|
|
681
|
-
weight?: number;
|
|
682
686
|
/** Bytes of `bytes` that came from spans nothing recognised — the asker's
|
|
683
687
|
* own words carried through verbatim rather than derived (see
|
|
684
688
|
* {@link liftedScaffolding}). Reported, not priced: the ladder prices what
|
package/src/mind/pipeline.ts
CHANGED
|
@@ -15,8 +15,17 @@ 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 {
|
|
18
|
+
import {
|
|
19
|
+
closed,
|
|
20
|
+
type DerivationState,
|
|
21
|
+
remainderOf,
|
|
22
|
+
type Span,
|
|
23
|
+
unaccountedBytes,
|
|
24
|
+
unexplainedSpans,
|
|
25
|
+
windowOf,
|
|
26
|
+
} from "./derivation.js";
|
|
19
27
|
import { rItem } from "./trace.js";
|
|
28
|
+
import { unexplainedLabel } from "./rationale.js";
|
|
20
29
|
import { hubBound } from "./traverse.js";
|
|
21
30
|
import { type PipelineMechanism, Precomputed } from "./pipeline-mechanism.js";
|
|
22
31
|
import { coverMechanism } from "./mechanisms/cover.js";
|
|
@@ -244,7 +253,6 @@ export async function think(
|
|
|
244
253
|
weight: number;
|
|
245
254
|
used?: ReadonlySet<number>;
|
|
246
255
|
accounted: ReadonlyArray<[number, number]>;
|
|
247
|
-
unexplained: string;
|
|
248
256
|
complete?: boolean;
|
|
249
257
|
/** Bytes of this candidate's ANSWER that came from spans nothing
|
|
250
258
|
* recognised — query words carried through verbatim (see
|
|
@@ -253,8 +261,7 @@ export async function think(
|
|
|
253
261
|
}
|
|
254
262
|
const grade = (w: number) => Math.floor(w / STEP);
|
|
255
263
|
const unaccounted = (spans: ReadonlyArray<[number, number]>): number =>
|
|
256
|
-
unexplainedSpans(query.length, spans)
|
|
257
|
-
.reduce((sum, [s, e]) => sum + (e - s), 0);
|
|
264
|
+
unaccountedBytes(unexplainedSpans(query.length, spans));
|
|
258
265
|
const weigh = (
|
|
259
266
|
accounted: ReadonlyArray<[number, number]>,
|
|
260
267
|
moves: number,
|
|
@@ -394,14 +401,16 @@ export async function think(
|
|
|
394
401
|
? await meter.time(`${mech.name}.run`, () => mech.run(ctx, query, pre))
|
|
395
402
|
: await mech.run(ctx, query, pre);
|
|
396
403
|
for (const r of results) {
|
|
397
|
-
|
|
404
|
+
// ONE FORMULA, EVERY CANDIDATE: the chart's derivation reports how many
|
|
405
|
+
// discrete moves it made and which bytes it could not recognise; the
|
|
406
|
+
// currency prices both. No mechanism passes a price of its own.
|
|
407
|
+
const weight = weigh(r.accounted, r.moves);
|
|
398
408
|
consider({
|
|
399
409
|
bytes: r.bytes,
|
|
400
410
|
provenance: r.provenance ?? mech.provenance,
|
|
401
411
|
weight,
|
|
402
412
|
used: r.used,
|
|
403
413
|
accounted: r.accounted,
|
|
404
|
-
unexplained: r.unexplained,
|
|
405
414
|
complete: r.complete,
|
|
406
415
|
scaffolding: r.scaffolding,
|
|
407
416
|
});
|
|
@@ -434,14 +443,21 @@ export async function think(
|
|
|
434
443
|
: null;
|
|
435
444
|
ctx.trace?.step(
|
|
436
445
|
"decideGrounding",
|
|
437
|
-
|
|
438
|
-
|
|
446
|
+
// THE LABEL IS RENDERED WHERE IT IS SHOWN. It was a field on every
|
|
447
|
+
// mechanism's result, and at every one of them it was exactly
|
|
448
|
+
// `unexplainedLabel(query, accounted)` — a second representation of a
|
|
449
|
+
// quantity one pure function already yields, computed on every response
|
|
450
|
+
// whether or not anyone looked. Here it is computed only when a rationale
|
|
451
|
+
// is attached, because `trace?.step` short-circuits its arguments.
|
|
452
|
+
candidates.map((c) => {
|
|
453
|
+
const label = unexplainedLabel(query, c.accounted);
|
|
454
|
+
return rItem(
|
|
439
455
|
c.bytes,
|
|
440
456
|
`${c.provenance} (weight ${c.weight.toFixed(3)}${
|
|
441
|
-
|
|
457
|
+
label ? `, unexplained: "${label}"` : ""
|
|
442
458
|
})`,
|
|
443
|
-
)
|
|
444
|
-
),
|
|
459
|
+
);
|
|
460
|
+
}),
|
|
445
461
|
decided ? [rItem(decided.bytes, decided.provenance)] : [],
|
|
446
462
|
"the lightest grounding derivation wins — every mechanism weighed in the one cost ladder",
|
|
447
463
|
undefined,
|
|
@@ -504,14 +520,65 @@ export async function think(
|
|
|
504
520
|
}
|
|
505
521
|
const answer: Uint8Array = decided.bytes;
|
|
506
522
|
const provenance = decided.provenance as Provenance;
|
|
507
|
-
const
|
|
523
|
+
const declaredUsed = decided.used;
|
|
508
524
|
|
|
509
|
-
// ──
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
525
|
+
// ── THE DERIVATION STATE ─────────────────────────────────────────────
|
|
526
|
+
//
|
|
527
|
+
// THE REASONER JUDGES ITS OWN EXTENSIONS BY THE PIPELINE'S REMAINDER, not by
|
|
528
|
+
// the ladder's `accounted` — and by the SAME reading the fuse gate below uses,
|
|
529
|
+
// with the same W floor. `accounted` is a COST quantity (measured: a query
|
|
530
|
+
// fully explained by one computed span plus bridged connectors reports
|
|
531
|
+
// `accounted: []` while nothing is unexplained), and a remainder under one
|
|
532
|
+
// river-fold quantum is bridging punctuation, never a second topic — so it
|
|
533
|
+
// licenses no extension and blocks none.
|
|
534
|
+
//
|
|
535
|
+
// The state is built HERE, where these quantities are already computed, so it
|
|
536
|
+
// costs nothing new: `accounted` is what the winning transition priced,
|
|
537
|
+
// `remainder` is the coverage reading over `accounted ∪ the response's
|
|
538
|
+
// computed spans` (the union is what makes the two different quantities, and
|
|
539
|
+
// both are kept), `cost` is the ladder position, and the two declarations are
|
|
540
|
+
// the producer's own (`fixed`, `used`). What follows reads THIS state rather
|
|
541
|
+
// than a tuple rebuilt at each call site.
|
|
542
|
+
// A DERIVATION IS BORN OWING WHAT ITS ANSWER DOES NOT CARRY. The winning
|
|
543
|
+
// transition PRICED these spans, and pricing is not carrying: coverage claimed
|
|
544
|
+
// without evidence stays owed, and a later transition pays it only by carrying
|
|
545
|
+
// it (the law reads the window; see derivation.ts). Same reading, one
|
|
546
|
+
// definition — not a second spelling of it here.
|
|
547
|
+
const explained: Array<[number, number]> = [
|
|
548
|
+
...decided.accounted,
|
|
549
|
+
...pre.computed.map((u): [number, number] => [u.i, u.j]),
|
|
550
|
+
].filter(([a, b]) =>
|
|
551
|
+
windowOf([a, b], answer, query, ctx.space.maxGroup) !== null
|
|
552
|
+
);
|
|
553
|
+
// WHAT THE CONSTRUCTION WITHHOLDS, at or above one quantum: the difference between
|
|
554
|
+
// the remainder paid in full and the remainder paid by carrying. Both readings
|
|
555
|
+
// are the law's, so the floor is applied once and in one place.
|
|
556
|
+
const paidInFull = remainderOf(
|
|
557
|
+
query.length,
|
|
558
|
+
[
|
|
559
|
+
...decided.accounted,
|
|
560
|
+
...pre.computed.map((u): [number, number] => [u.i, u.j]),
|
|
561
|
+
],
|
|
562
|
+
ctx.space.maxGroup,
|
|
563
|
+
);
|
|
564
|
+
const paid = remainderOf(query.length, explained, ctx.space.maxGroup);
|
|
565
|
+
if (ctx.meter) {
|
|
566
|
+
ctx.meter.groundingWithheldBytes += unaccountedBytes(paid) -
|
|
567
|
+
unaccountedBytes(paidInFull);
|
|
568
|
+
}
|
|
569
|
+
const state: DerivationState = {
|
|
570
|
+
product: answer,
|
|
571
|
+
accounted: decided.accounted,
|
|
572
|
+
remainder: paid,
|
|
573
|
+
cost: decided.weight,
|
|
574
|
+
fixed: decided.complete,
|
|
575
|
+
used: decided.used,
|
|
576
|
+
};
|
|
577
|
+
const uncovered = state.remainder;
|
|
578
|
+
|
|
579
|
+
// ── Post-grounding, gated by the declaration and the remainder ────────
|
|
580
|
+
const preConsumed = declaredUsed ??
|
|
581
|
+
new Set(recognise(ctx, answer).sites.map((s) => s.payload));
|
|
515
582
|
// A grounding that DECLARED itself complete is not extended: the answer is
|
|
516
583
|
// already a trained form's own continuation, reached through an identity
|
|
517
584
|
// claim about the query, so a multi-hop pivot could only chain past the
|
|
@@ -540,11 +607,41 @@ export async function think(
|
|
|
540
607
|
// `preConsumed` is derived by re-recognising the answer — "everything in
|
|
541
608
|
// it", not "what it voiced" — and a containment rule over that would
|
|
542
609
|
// suppress every pivot the answer legitimately contains.
|
|
543
|
-
const voiced =
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
610
|
+
const voiced = declaredUsed === undefined ? [] : [...declaredUsed].flatMap(
|
|
611
|
+
(id) => ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n)),
|
|
612
|
+
);
|
|
613
|
+
// WHAT THIS BRANCH READ, published where it was read. Post-grounding decides
|
|
614
|
+
// by the DECLARATION (`decided.used`, which becomes `voiced`), by what the
|
|
615
|
+
// recognition already consumed (`preConsumed`) and by the derivation's own
|
|
616
|
+
// remainder — never by the provenance NAME, which is REPORTED throughout and
|
|
617
|
+
// compared nowhere (a stale comment here claimed otherwise; the register caught
|
|
618
|
+
// it, and this is the correction). The operands were invisible in the trace, so
|
|
619
|
+
// a change to the branching could not be shown equivalent or otherwise from
|
|
620
|
+
// outside — three separate investigations failed on exactly that gap. A gap in instrumentation is a defect IN the instrumentation
|
|
621
|
+
// (AGENTS.md §6): closed here, once, as counts only — never content.
|
|
622
|
+
ctx.trace?.step(
|
|
623
|
+
"postGrounding",
|
|
624
|
+
[rItem(answer, provenance)],
|
|
625
|
+
[],
|
|
626
|
+
`used=${decided.used !== undefined ? "declared" : "absent"} · ` +
|
|
627
|
+
`preConsumed=${preConsumed.size} · voiced=${voiced.length}`,
|
|
628
|
+
undefined,
|
|
629
|
+
{
|
|
630
|
+
version: 1,
|
|
631
|
+
provenance,
|
|
632
|
+
usedDeclared: decided.used !== undefined,
|
|
633
|
+
preConsumed: preConsumed.size,
|
|
634
|
+
voiced: voiced.length,
|
|
635
|
+
// THE STATE THE LAW GOVERNS, rendered where it is decided: what the asker
|
|
636
|
+
// said that no step has accounted for, in spans at or above one quantum,
|
|
637
|
+
// and whether the producer supplied a fixed point. Counts only, like
|
|
638
|
+
// every other operand here — and the spans are the state's, so a reader
|
|
639
|
+
// can check them against the meter's aggregate of the same remainder.
|
|
640
|
+
remainderSpans: state.remainder.length,
|
|
641
|
+
remainderBytes: unaccountedBytes(state.remainder),
|
|
642
|
+
fixed: state.fixed === true,
|
|
643
|
+
},
|
|
644
|
+
);
|
|
548
645
|
// REPORTABLE, NOT SILENT. A declared-complete grounding ends the derivation
|
|
549
646
|
// here, and that decision is part of the derivation's shape: the reader of a
|
|
550
647
|
// rationale must be able to see that the chain stopped because the mechanism
|
|
@@ -560,25 +657,25 @@ export async function think(
|
|
|
560
657
|
"post-grounding extension is skipped",
|
|
561
658
|
);
|
|
562
659
|
}
|
|
563
|
-
//
|
|
564
|
-
//
|
|
565
|
-
//
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const reasoned = decided.complete ? answer : meter
|
|
660
|
+
// PUBLISHED, NOT RECOMPUTED: the same `uncovered` the gates below read. A
|
|
661
|
+
// write-only accounting (meter contract 1), so the number that licenses an
|
|
662
|
+
// extension or a fusion stops being invisible.
|
|
663
|
+
if (meter) {
|
|
664
|
+
meter.postGroundingRemainderSpans += uncovered.length;
|
|
665
|
+
meter.postGroundingRemainderBytes += unaccountedBytes(uncovered);
|
|
666
|
+
}
|
|
667
|
+
// THE WALK CONSUMES AND RETURNS A STATE. It is handed the derivation's own —
|
|
668
|
+
// the grounding's product, accounting, remainder and cost — and hands back the
|
|
669
|
+
// state it advanced to, so what follows reads a state rather than bytes plus a
|
|
670
|
+
// tuple rebuilt here. A supplied fixed point is the one case where the walk
|
|
671
|
+
// does not run at all, and then the state is the grounding's own.
|
|
672
|
+
const extension = decided.complete ? undefined : meter
|
|
577
673
|
? await meter.time(
|
|
578
674
|
"reason",
|
|
579
|
-
() => reason(ctx, query,
|
|
675
|
+
() => reason(ctx, query, state, preConsumed, pre, voiced),
|
|
580
676
|
)
|
|
581
|
-
: await reason(ctx, query,
|
|
677
|
+
: await reason(ctx, query, state, preConsumed, pre, voiced);
|
|
678
|
+
const reasoned = extension ?? state;
|
|
582
679
|
|
|
583
680
|
// Fuse only when the query has a genuine REMAINDER no mechanism's
|
|
584
681
|
// structural evidence touched at all. `decided.accounted` alone
|
|
@@ -596,7 +693,15 @@ export async function think(
|
|
|
596
693
|
// observed: a single space between two fully-computed arithmetic spans
|
|
597
694
|
// ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
|
|
598
695
|
// corpus fact, corrupting "4 6" into "4 63".
|
|
599
|
-
|
|
696
|
+
// THE GATE ASKS THE LAW, and that is an OPTIMISATION, not a tidy-up: the state
|
|
697
|
+
// above ALREADY carries the remainder (`remainderOf`, per-span, with the W
|
|
698
|
+
// floor applied), so asking it costs nothing, while the total this line used to
|
|
699
|
+
// compute (`unaccounted(explained)`) was one more sum over the spans on every
|
|
700
|
+
// response. The two readings are the same condition, not two: the ACCOUNTING
|
|
701
|
+
// applies the same W floor the gate does, so a gap below one quantum never
|
|
702
|
+
// survives into `explained` and the total cannot reach W without some single
|
|
703
|
+
// gap reaching it. Measured over twelve constructions at W = 4 (test/136.3,
|
|
704
|
+
// which pins the equivalence and both sides of it).
|
|
600
705
|
// Whether the winning candidate's entire recognised substance is
|
|
601
706
|
// COMPUTED — every accounted span exactly a pre.computed span, nothing
|
|
602
707
|
// from a genuinely recognised/climbed site. fuseAttention's lone-root
|
|
@@ -606,8 +711,8 @@ export async function think(
|
|
|
606
711
|
// `unclimbed` parameter, gated there by Attention.breadth so a
|
|
607
712
|
// coincidental echo (which this flag alone cannot distinguish) is still
|
|
608
713
|
// rejected.
|
|
609
|
-
const unclimbed =
|
|
610
|
-
|
|
714
|
+
const unclimbed = state.accounted.length > 0 &&
|
|
715
|
+
state.accounted.every(([i, j]) =>
|
|
611
716
|
pre.computed.some((u) => u.i === i && u.j === j)
|
|
612
717
|
);
|
|
613
718
|
// Where the winning grounding stands in the query — fusion places primary
|
|
@@ -617,13 +722,10 @@ export async function think(
|
|
|
617
722
|
// Exactly the cost-ladder-vs-coverage distinction `explained` above draws,
|
|
618
723
|
// read here for POSITION instead of for coverage — and resolved here, where
|
|
619
724
|
// both readings are in hand, rather than inside fuseAttention.
|
|
620
|
-
const primarySpans: ReadonlyArray<
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
const fused = remainder < ctx.space.maxGroup
|
|
625
|
-
? reasoned
|
|
626
|
-
: meter
|
|
725
|
+
const primarySpans: ReadonlyArray<Span> = state.accounted.length > 0
|
|
726
|
+
? state.accounted
|
|
727
|
+
: pre.computed.map((u): [number, number] => [u.i, u.j]);
|
|
728
|
+
const fused = closed(state) ? reasoned : meter
|
|
627
729
|
? await meter.time(
|
|
628
730
|
"fuse",
|
|
629
731
|
() => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans),
|
|
@@ -638,8 +740,14 @@ export async function think(
|
|
|
638
740
|
);
|
|
639
741
|
|
|
640
742
|
done(
|
|
641
|
-
fused,
|
|
642
|
-
|
|
743
|
+
fused.product,
|
|
744
|
+
// NO CLAIM ABOUT FUSION HERE. `fuseAttention` is entered whenever a
|
|
745
|
+
// remainder ≥ W exists and returns early when there is nothing to bridge, so
|
|
746
|
+
// this note used to assert a fusion that frequently did not happen (measured:
|
|
747
|
+
// "What is the capital of France famous for" fuses 0 times). The fusion is
|
|
748
|
+
// reported by `fuseAttention`'s own `done` when it happens — the layer that
|
|
749
|
+
// did the work is the layer that says so.
|
|
750
|
+
"grounded, reasoned forward",
|
|
643
751
|
);
|
|
644
|
-
return { bytes: fused, provenance };
|
|
752
|
+
return { bytes: fused.product, provenance };
|
|
645
753
|
}
|
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
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
// fan-out / fan-in is visible in their lengths.
|
|
22
22
|
|
|
23
23
|
import type { Vec } from "../vec.js";
|
|
24
|
+
import { unexplainedSpans } from "./derivation.js";
|
|
24
25
|
|
|
25
26
|
/** One element of a step's input or output vector.
|
|
26
27
|
*
|
|
@@ -49,6 +50,17 @@ export interface RationaleItem {
|
|
|
49
50
|
* caller asked to carry it (off by default — a D-float array per item would
|
|
50
51
|
* bury the reasoning it is meant to explain). */
|
|
51
52
|
v?: Vec;
|
|
53
|
+
/** The element's OWN bytes, attached BY REFERENCE when the step was built from
|
|
54
|
+
* bytes (a `rationale.ts` item made from a node carries none: read it back
|
|
55
|
+
* through `node`). `text` is a RENDERING and cannot stand in for them — it
|
|
56
|
+
* decodes UTF-8 and DROPS NUL bytes, so a key containing one is unrecoverable
|
|
57
|
+
* from it, which is exactly how a join refusal (`deriveThroughMiss`) became
|
|
58
|
+
* impossible to test exactly without re-encoding. Treat as READ-ONLY: the
|
|
59
|
+
* array belongs to the caller (and may be a view into the query).
|
|
60
|
+
*
|
|
61
|
+
* Costs nothing when nothing inspects: items exist only while a rationale
|
|
62
|
+
* sink is attached, and this holds a reference rather than a copy. */
|
|
63
|
+
bytes?: Uint8Array;
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
/** A single completed act of inference — one mechanism, run once.
|
|
@@ -101,28 +113,11 @@ export function decodeText(bytes: Uint8Array): string {
|
|
|
101
113
|
return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
|
|
102
114
|
}
|
|
103
115
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
accounted: ReadonlyArray<[number, number]>,
|
|
110
|
-
): Array<[number, number]> {
|
|
111
|
-
const sorted = accounted
|
|
112
|
-
.map(([s, e]) =>
|
|
113
|
-
[Math.max(0, s), Math.min(queryLen, e)] as [number, number]
|
|
114
|
-
)
|
|
115
|
-
.filter(([s, e]) => e > s)
|
|
116
|
-
.sort((a, b) => a[0] - b[0]);
|
|
117
|
-
const gaps: Array<[number, number]> = [];
|
|
118
|
-
let reach = 0;
|
|
119
|
-
for (const [s, e] of sorted) {
|
|
120
|
-
if (s > reach) gaps.push([reach, s]);
|
|
121
|
-
if (e > reach) reach = e;
|
|
122
|
-
}
|
|
123
|
-
if (reach < queryLen) gaps.push([reach, queryLen]);
|
|
124
|
-
return gaps;
|
|
125
|
-
}
|
|
116
|
+
// THE SPAN ALGEBRA LIVES IN derivation.ts. `unaccountedBytes` and
|
|
117
|
+
// `unexplainedSpans` are the closure law's vocabulary — gap arithmetic over the
|
|
118
|
+
// asker's own bytes — and they moved to the layer that owns the law, so they
|
|
119
|
+
// have ONE home and are no longer asked of the tracer. This module keeps the
|
|
120
|
+
// inference TOLD as it happens, and reads the gaps only to render a label.
|
|
126
121
|
|
|
127
122
|
/** A human-readable label for the query bytes a mechanism's `accounted`
|
|
128
123
|
* spans leave unexplained — purely diagnostic (Task 2's negative evidence):
|
|
@@ -264,7 +259,16 @@ export class Rationale {
|
|
|
264
259
|
};
|
|
265
260
|
}
|
|
266
261
|
|
|
267
|
-
/**
|
|
262
|
+
/** WHY THIS NAME IS A FREE STRING, when the derivation's moves are a closed
|
|
263
|
+
* union: a mechanism name is WRITTEN and DISPLAYED, and it COMPOSES with
|
|
264
|
+
* the nesting — `mechanism` is the whole path (`["respond", "think",
|
|
265
|
+
* "recognise"]`), which no fixed union can express. Nothing branches on it:
|
|
266
|
+
* `nothing here drives the inference; it only WITNESSES it`. A vocabulary
|
|
267
|
+
* that is only witnessed needs no union; one that is read does
|
|
268
|
+
* (`DerivationMove`, in graph-search.ts). The asymmetry is the design, not
|
|
269
|
+
* a drift.
|
|
270
|
+
*
|
|
271
|
+
* Record a mechanism that has no sub-steps — its inputs and outputs are both
|
|
268
272
|
* known at the call site. Returns its index, for a later step to depend on. */
|
|
269
273
|
step(
|
|
270
274
|
name: string,
|