@hviana/sema 0.8.3 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +11 -10
- package/README.md +17 -38
- package/dist/example/demo.js +85 -34
- package/dist/src/geometry.d.ts +0 -10
- package/dist/src/geometry.js +0 -12
- package/dist/src/meter.d.ts +31 -5
- package/dist/src/meter.js +31 -5
- package/dist/src/mind/articulation.js +1 -1
- package/dist/src/mind/attention.js +2 -1
- 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 +37 -15
- package/dist/src/mind/match.d.ts +2 -0
- package/dist/src/mind/match.js +2 -0
- 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 +15 -18
- package/dist/src/mind/mechanisms/confluence.d.ts +0 -3
- package/dist/src/mind/mechanisms/confluence.js +3 -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 +8 -9
- package/dist/src/mind/mechanisms/reference.js +3 -4
- package/dist/src/mind/pipeline-mechanism.d.ts +1 -4
- package/dist/src/mind/pipeline.js +106 -41
- package/dist/src/mind/rationale.d.ts +0 -11
- package/dist/src/mind/rationale.js +6 -32
- package/dist/src/mind/reasoning.d.ts +4 -30
- package/dist/src/mind/reasoning.js +191 -151
- 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 +4 -4
- package/docs/architecture/closure.md +78 -0
- package/docs/architecture/commonality.md +27 -18
- package/docs/architecture/cost-model.md +5 -5
- package/docs/architecture/exact-vs-approximate.md +4 -4
- package/docs/architecture/factored-machinery.md +14 -14
- package/docs/architecture/mechanism-market.md +9 -9
- package/docs/architecture/meter.md +4 -5
- package/docs/architecture/store.md +2 -2
- package/docs/architecture/thresholds.md +1 -1
- package/docs/failures/tempting-but-wrong.md +11 -1
- package/docs/harness/gates.md +6 -6
- package/docs/mechanisms/cover.md +2 -2
- package/example/demo.ts +90 -37
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/geometry.ts +0 -13
- package/src/meter.ts +31 -5
- package/src/mind/articulation.ts +0 -1
- package/src/mind/attention.ts +2 -1
- package/src/mind/derivation.ts +477 -0
- package/src/mind/graph-search.ts +37 -20
- package/src/mind/match.ts +2 -0
- package/src/mind/mechanisms/alu.ts +0 -2
- package/src/mind/mechanisms/cast.ts +17 -21
- package/src/mind/mechanisms/confluence.ts +3 -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 +7 -9
- package/src/mind/mechanisms/reference.ts +2 -3
- package/src/mind/pipeline-mechanism.ts +1 -4
- package/src/mind/pipeline.ts +121 -46
- package/src/mind/rationale.ts +6 -36
- package/src/mind/reasoning.ts +220 -178
- package/src/mind/types.ts +5 -2
- package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +3 -3
- 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/142-the-layer-offers-only-what-the-law-admits.test.mjs +93 -0
- package/test/143-cycles-terminate-and-are-not-closure.test.mjs +62 -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 +22 -2
- package/test/55-cost-meter.test.mjs +6 -3
|
@@ -42,7 +42,7 @@ import { read } from "../primitives.js";
|
|
|
42
42
|
import { corpusN, reachOf } from "../traverse.js";
|
|
43
43
|
import { dominates } from "../../geometry.js";
|
|
44
44
|
import { STEP } from "../graph-search.js";
|
|
45
|
-
import {
|
|
45
|
+
import { insideAnsweredTurn } from "../derivation.js";
|
|
46
46
|
import { rItem, rNode } from "../trace.js";
|
|
47
47
|
/** The main confluence entry point. Given a query, detect whether it weaves
|
|
48
48
|
* two or more INDEPENDENT constraints (ranked anchors supported by disjoint
|
|
@@ -75,13 +75,9 @@ export async function confluenceJoin(ctx, query, pre) {
|
|
|
75
75
|
// Recognition and attention still see the full transcript; only this
|
|
76
76
|
// mechanism's constraint population excludes answered spans.
|
|
77
77
|
const queryWin = new Map();
|
|
78
|
-
|
|
78
|
+
const answered = { at: 0 };
|
|
79
79
|
for (const [off, id] of pre.queryWindows) {
|
|
80
|
-
|
|
81
|
-
ctx.answeredSpans[answered][1] <= off)
|
|
82
|
-
answered++;
|
|
83
|
-
const span = ctx.answeredSpans[answered];
|
|
84
|
-
if (span && span[0] <= off && off + W <= span[1])
|
|
80
|
+
if (insideAnsweredTurn(ctx.answeredSpans, answered, off, off + W))
|
|
85
81
|
continue;
|
|
86
82
|
queryWin.set(off, id);
|
|
87
83
|
}
|
|
@@ -258,7 +254,6 @@ export async function confluenceJoin(ctx, query, pre) {
|
|
|
258
254
|
used: new Set([met.a.anchor, met.b.anchor]),
|
|
259
255
|
accounted,
|
|
260
256
|
moves: 3 * STEP,
|
|
261
|
-
unexplained: unexplainedLabel(query, accounted),
|
|
262
257
|
};
|
|
263
258
|
}
|
|
264
259
|
// ── Pipeline mechanism ──────────────────────────────────────────────────────
|
|
@@ -289,7 +284,6 @@ export const confluenceMechanism = {
|
|
|
289
284
|
accounted: met.accounted,
|
|
290
285
|
moves: met.moves,
|
|
291
286
|
used: met.used,
|
|
292
|
-
unexplained: met.unexplained,
|
|
293
287
|
}];
|
|
294
288
|
},
|
|
295
289
|
};
|
|
@@ -13,8 +13,8 @@ import { guidedFirst, hubBound } from "../traverse.js";
|
|
|
13
13
|
import { conceptHop } from "../match.js";
|
|
14
14
|
import { bridge } from "../resonance.js";
|
|
15
15
|
import { liftAnswer, liftedScaffolding, segRestatesQuery } from "../types.js";
|
|
16
|
-
import { decodeText
|
|
17
|
-
import {
|
|
16
|
+
import { decodeText } from "../rationale.js";
|
|
17
|
+
import { insideAnsweredTurn, restates } from "../derivation.js";
|
|
18
18
|
import { rItem, rNode, traceDerivation } from "../trace.js";
|
|
19
19
|
// ── Concept / connector pre-resolution ──────────────────────────────────────
|
|
20
20
|
export async function resolveConcepts(ctx, sites) {
|
|
@@ -45,16 +45,13 @@ export async function resolveConnectors(ctx, sites, query) {
|
|
|
45
45
|
// discarded — a semantically neutral gate (it removes work whose product
|
|
46
46
|
// liftAnswer throws away), and a cumulative (multi-turn) query is exactly
|
|
47
47
|
// where such already-answered continuations recur.
|
|
48
|
-
|
|
48
|
+
const answered = { at: 0 };
|
|
49
49
|
const ordered = [...sites]
|
|
50
50
|
.sort((a, b) => a.start - b.start)
|
|
51
51
|
.filter((s) => {
|
|
52
|
-
|
|
53
|
-
ctx.answeredSpans[answered][1] <= s.start)
|
|
54
|
-
answered++;
|
|
55
|
-
const span = ctx.answeredSpans[answered];
|
|
56
|
-
if (span && span[0] <= s.start && s.end <= span[1])
|
|
52
|
+
if (insideAnsweredTurn(ctx.answeredSpans, answered, s.start, s.end)) {
|
|
57
53
|
return false;
|
|
54
|
+
}
|
|
58
55
|
if (query === undefined || ctx.answeredSpans.length === 0)
|
|
59
56
|
return true;
|
|
60
57
|
const continuations = ctx.store.nextFirst(s.payload, hubBound(ctx));
|
|
@@ -77,7 +74,11 @@ export async function resolveConnectors(ctx, sites, query) {
|
|
|
77
74
|
// 3 bytes this reads 4 bytes per candidate instead of the ~231 it
|
|
78
75
|
// averaged before.
|
|
79
76
|
const bytes = read(ctx, answer, query.length + 1);
|
|
80
|
-
|
|
77
|
+
// THE CONTENT READING of the same exclusion: the site's own
|
|
78
|
+
// continuation already occurs in the query, so voicing it back adds
|
|
79
|
+
// nothing. It is the restatement law with no `proper` flag — the
|
|
80
|
+
// containment reading that also admits the whole query.
|
|
81
|
+
return restates(query, bytes, 0);
|
|
81
82
|
});
|
|
82
83
|
});
|
|
83
84
|
const bridgePair = async (l, r) => {
|
|
@@ -171,18 +172,11 @@ export const coverMechanism = {
|
|
|
171
172
|
? await ctx.meter.time("cover.resolveConnectors", () => resolveConnectors(ctx, sites, query))
|
|
172
173
|
: await resolveConnectors(ctx, sites, query);
|
|
173
174
|
let splits = rec.splits;
|
|
174
|
-
let starts = rec.starts;
|
|
175
175
|
if (computed.length > 0) {
|
|
176
176
|
splits = new Set(rec.splits);
|
|
177
|
-
starts = new Set(rec.starts);
|
|
178
177
|
for (const u of computed) {
|
|
179
178
|
splits.add(u.i);
|
|
180
179
|
splits.add(u.j);
|
|
181
|
-
// A computation's own boundaries carry the same fold-level evidence
|
|
182
|
-
// a chunk boundary does — "computation always wins" (see the header
|
|
183
|
-
// comment) extends to being trusted ground for cross-leaf recovery.
|
|
184
|
-
starts.add(u.i);
|
|
185
|
-
starts.add(u.j);
|
|
186
180
|
}
|
|
187
181
|
}
|
|
188
182
|
const concepts = ctx.meter
|
|
@@ -208,7 +202,7 @@ export const coverMechanism = {
|
|
|
208
202
|
])),
|
|
209
203
|
...computedResults.map((u) => rItem(u.bytes, "computed")),
|
|
210
204
|
], coverDeps.length ? coverDeps : undefined);
|
|
211
|
-
const solved = ctx.search.cover(query.length, sites, concepts, rec.leaves, splits,
|
|
205
|
+
const solved = ctx.search.cover(query.length, sites, concepts, rec.leaves, splits, undefined, connectors, computedResults, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
|
|
212
206
|
const segs = solved && solved.segs;
|
|
213
207
|
tCover?.done(segs === null
|
|
214
208
|
? []
|
|
@@ -247,9 +241,12 @@ export const coverMechanism = {
|
|
|
247
241
|
return [{
|
|
248
242
|
bytes: composed,
|
|
249
243
|
accounted,
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
244
|
+
// The derivation's DISCRETE work. The bytes the chart could not
|
|
245
|
+
// recognise are NOT priced here: they are exactly the spans `accounted`
|
|
246
|
+
// leaves uncovered, and the pipeline's one formula charges them at PASS —
|
|
247
|
+
// the same formula that prices every other mechanism's candidate. No
|
|
248
|
+
// mechanism spells a cost of its own.
|
|
249
|
+
moves: solved.moves,
|
|
253
250
|
// How much of the composed answer is the asker's own unexplained words
|
|
254
251
|
// (the spans the liftAnswer trace above labels "scaffolding"). Cover is
|
|
255
252
|
// the mechanism that can carry them, because a PASS span still lands in
|
|
@@ -19,7 +19,6 @@ import type { PipelineMechanism, Precomputed } from "../pipeline-mechanism.js";
|
|
|
19
19
|
export declare function extractBySkill(ctx: MindContext, query: Uint8Array, pre: Precomputed): Promise<{
|
|
20
20
|
bytes: Uint8Array;
|
|
21
21
|
accounted: Array<[number, number]>;
|
|
22
|
-
unexplained: string;
|
|
23
22
|
} | null>;
|
|
24
23
|
/** Decompose an answer into substrings of its surrounding context, in order —
|
|
25
24
|
* the STRONG span-shape reading (see the section note above). Returns null
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// that contains it.
|
|
9
9
|
import { locate } from "../match.js";
|
|
10
10
|
import { concatBytes, indexOf } from "../../bytes.js";
|
|
11
|
-
import { decodeText
|
|
11
|
+
import { decodeText } from "../rationale.js";
|
|
12
12
|
import { CONCEPT, STEP } from "../graph-search.js";
|
|
13
13
|
import { rItem, rNode, traceFail } from "../trace.js";
|
|
14
14
|
// ── Extraction ────────────────────────────────────────────────────────────
|
|
@@ -81,6 +81,12 @@ export async function extractBySkill(ctx, query, pre) {
|
|
|
81
81
|
const searched = ranked.slice(0, pre.k);
|
|
82
82
|
let shapeMisses = 0;
|
|
83
83
|
let subQuantum = 0;
|
|
84
|
+
// A SECOND refusal counter, because the note below used to call an UNANCHORED
|
|
85
|
+
// read "sub-quantum" — which is false, and it is the kind of instrumentation
|
|
86
|
+
// defect AGENTS §6 says to close where it lives: a reader could not tell from
|
|
87
|
+
// the trace which of the two gates refused. Neither counter reaches a
|
|
88
|
+
// decision (meter.ts contract 1); they exist so the refusal is legible.
|
|
89
|
+
let unanchored = 0;
|
|
84
90
|
for (const cand of searched) {
|
|
85
91
|
const exemplar = await pre.spanShapedOf(cand.anchor);
|
|
86
92
|
if (!exemplar) {
|
|
@@ -116,15 +122,16 @@ export async function extractBySkill(ctx, query, pre) {
|
|
|
116
122
|
// Here the field is this mechanism's own output and carries its documented
|
|
117
123
|
// meaning, so the test is sound exactly where the convention does not reach.
|
|
118
124
|
if (built.accounted.length === 0) {
|
|
119
|
-
|
|
125
|
+
unanchored++;
|
|
120
126
|
continue;
|
|
121
127
|
}
|
|
122
|
-
if (shapeMisses > 0 || subQuantum > 0) {
|
|
128
|
+
if (shapeMisses > 0 || subQuantum > 0 || unanchored > 0) {
|
|
123
129
|
ctx.trace?.step("trySkillAnchors", [
|
|
124
|
-
rItem(query.subarray(0, 0), `skipped ${shapeMisses + subQuantum}`),
|
|
130
|
+
rItem(query.subarray(0, 0), `skipped ${shapeMisses + subQuantum + unanchored}`),
|
|
125
131
|
rNode(ctx, cand.anchor, "chosen"),
|
|
126
|
-
], [], `skipped ${shapeMisses} non-exemplar
|
|
127
|
-
|
|
132
|
+
], [], `skipped ${shapeMisses} non-exemplar, ${subQuantum} sub-quantum and ` +
|
|
133
|
+
`${unanchored} unanchored anchor(s) before one yielded a usable ` +
|
|
134
|
+
`extraction`);
|
|
128
135
|
}
|
|
129
136
|
t?.done([rItem(built.bytes, "extracted")], built.pieces === 1
|
|
130
137
|
? `apply a learnt extraction skill — read the analogous span of the query` +
|
|
@@ -134,7 +141,6 @@ export async function extractBySkill(ctx, query, pre) {
|
|
|
134
141
|
return {
|
|
135
142
|
bytes: built.bytes,
|
|
136
143
|
accounted: built.accounted,
|
|
137
|
-
unexplained: unexplainedLabel(query, built.accounted),
|
|
138
144
|
};
|
|
139
145
|
}
|
|
140
146
|
if (shapeMisses === searched.length) {
|
|
@@ -321,7 +327,6 @@ export const extractionMechanism = {
|
|
|
321
327
|
bytes: ex.bytes,
|
|
322
328
|
accounted: ex.accounted,
|
|
323
329
|
moves: CONCEPT + STEP * ex.accounted.length,
|
|
324
|
-
unexplained: ex.unexplained,
|
|
325
330
|
}];
|
|
326
331
|
},
|
|
327
332
|
};
|
|
@@ -255,7 +255,6 @@ export const prefixMechanism = {
|
|
|
255
255
|
// IDENTITY bridge takes.
|
|
256
256
|
accounted: [[0, query.length]],
|
|
257
257
|
moves: STEP,
|
|
258
|
-
unexplained: "",
|
|
259
258
|
// NOT complete: the query is a proper PREFIX, so the form may carry more
|
|
260
259
|
// past the remainder this voiced.
|
|
261
260
|
}];
|
|
@@ -6,7 +6,6 @@ export interface RecallResult {
|
|
|
6
6
|
echoed: boolean;
|
|
7
7
|
accounted: Array<[number, number]>;
|
|
8
8
|
moves: number;
|
|
9
|
-
unexplained: string;
|
|
10
9
|
/** See {@link import("../pipeline-mechanism.js").MechanismResult.complete}
|
|
11
10
|
* — set by the IDENTITY-bridge tier alone. */
|
|
12
11
|
complete?: boolean;
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
import { cosine } from "../../vec.js";
|
|
7
7
|
import { consensusFloor, dominates, identityBar, reachThreshold, significanceBar, } from "../../geometry.js";
|
|
8
8
|
import { gistOf, read, resolve } from "../primitives.js";
|
|
9
|
-
import {
|
|
9
|
+
import { indexOf } from "../../bytes.js";
|
|
10
10
|
import { allWindowsAreScaffolding, corpusN, hubBound } from "../traverse.js";
|
|
11
11
|
import { follow, project, reverseContext, voicesDisplacedFiller, } from "../match.js";
|
|
12
12
|
import { CONCEPT, STEP } from "../graph-search.js";
|
|
13
|
-
import {
|
|
13
|
+
import { restates as lawRestates } from "../derivation.js";
|
|
14
14
|
import { rItem, rNode } from "../trace.js";
|
|
15
15
|
import { substitutionBridge } from "../bridge.js";
|
|
16
16
|
/** Recall the answer by resonating the whole query against the content index. */
|
|
@@ -29,7 +29,6 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
29
29
|
echoed,
|
|
30
30
|
accounted,
|
|
31
31
|
moves,
|
|
32
|
-
unexplained: unexplainedLabel(query, accounted),
|
|
33
32
|
...(complete ? { complete } : {}),
|
|
34
33
|
};
|
|
35
34
|
};
|
|
@@ -103,7 +102,7 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
103
102
|
// guard the argument's OWN later restatement in the same
|
|
104
103
|
// conversation reads as if it were the next thing to say.
|
|
105
104
|
if (g !== null && g.length > 0 &&
|
|
106
|
-
!(
|
|
105
|
+
!lawRestates(query, g, 0, { proper: true })) {
|
|
107
106
|
return ground(g, "argument binding — the query's sole edge-source constituent, continuation followed", [[arg.start, arg.end]], STEP);
|
|
108
107
|
}
|
|
109
108
|
}
|
|
@@ -130,9 +129,10 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
130
129
|
// same principle that keeps cast from voicing stored questions), and
|
|
131
130
|
// projecting them forward is reverse recall's containment failure in the
|
|
132
131
|
// other direction — "whatever followed these bytes in some document".
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
// THE EQUALITY READING of the restatement law: this tier rejects an answer
|
|
133
|
+
// that IS the question (an echo), and a proper fragment is handled by the
|
|
134
|
+
// tier's own subspan tests further down — so the law is asked with `whole`.
|
|
135
|
+
const restates = (b) => lawRestates(query, b, 0, { equate: ctx.canon, whole: true });
|
|
136
136
|
const idBar = identityBar(ctx.store.D, ctx.space.maxGroup, query.length);
|
|
137
137
|
if (top.score >= idBar) {
|
|
138
138
|
for (const h of whole) {
|
|
@@ -315,7 +315,7 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
315
315
|
// — never an answer (the same principle as `restates` above, extended
|
|
316
316
|
// to fragments). Genuine anchor groundings — longer than the query,
|
|
317
317
|
// or disjoint from it — pass untouched.
|
|
318
|
-
else if (g && !(
|
|
318
|
+
else if (g && !lawRestates(query, g, 0, { proper: true })) {
|
|
319
319
|
return ground(g, "scaffolding-dominated query — ground the consensus-climb anchor", [[forest[0].start, forest[0].end]], CONCEPT);
|
|
320
320
|
}
|
|
321
321
|
}
|
|
@@ -493,7 +493,6 @@ export const recallMechanism = {
|
|
|
493
493
|
bytes: r.bytes,
|
|
494
494
|
accounted: r.accounted,
|
|
495
495
|
moves: r.moves,
|
|
496
|
-
unexplained: r.unexplained,
|
|
497
496
|
provenance: r.echoed ? "recall-echo" : "recall",
|
|
498
497
|
used: new Set(),
|
|
499
498
|
...(r.complete ? { complete: true } : {}),
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
// made AVAILABLE, never imposed.
|
|
36
36
|
import { carriesFillers, distinct, follow, substituteAll } from "../match.js";
|
|
37
37
|
import { dominates } from "../../geometry.js";
|
|
38
|
-
import { bytesEqual
|
|
39
|
-
import {
|
|
38
|
+
import { bytesEqual } from "../../bytes.js";
|
|
39
|
+
import { restates } from "../derivation.js";
|
|
40
40
|
import { STEP } from "../graph-search.js";
|
|
41
41
|
import { rItem, rNode, traceFail } from "../trace.js";
|
|
42
42
|
/** The minimum number of instances that can establish a frame. One instance
|
|
@@ -196,7 +196,7 @@ export async function bindReference(ctx, query, pre) {
|
|
|
196
196
|
return fail("the binding produced nothing");
|
|
197
197
|
// Answering with the question is not answering — the same restated-fragment
|
|
198
198
|
// guard every recall tier applies.
|
|
199
|
-
if (
|
|
199
|
+
if (restates(query, bytes, 0, { proper: true })) {
|
|
200
200
|
return fail("the binding restates part of the question");
|
|
201
201
|
}
|
|
202
202
|
const carried = !bytesEqual(bytes, first);
|
|
@@ -230,7 +230,6 @@ export async function bindReference(ctx, query, pre) {
|
|
|
230
230
|
// binding claims strictly more than a one-slot binding, so where both are
|
|
231
231
|
// licensed the smaller claim wins.
|
|
232
232
|
moves: STEP * slots.length + STEP,
|
|
233
|
-
unexplained: unexplainedLabel(query, accounted),
|
|
234
233
|
// NOT scaffolding. That field counts answer bytes carried through BECAUSE
|
|
235
234
|
// NOTHING EXPLAINED THEM; a referent is carried because the frame's slot
|
|
236
235
|
// explains it, and it is accounted above. Reporting it would make every
|
|
@@ -153,15 +153,12 @@ export interface MechanismResult {
|
|
|
153
153
|
moves: number;
|
|
154
154
|
/** WHAT THIS ANSWER SPEAKS FOR — the anchors it voices, and therefore the
|
|
155
155
|
* content the reasoner must not pivot back through. Declared by the
|
|
156
|
-
* mechanism about its OWN result, exactly like `accounted`/`
|
|
156
|
+
* mechanism about its OWN result, exactly like `accounted`/`used`/
|
|
157
157
|
* `complete`: post-grounding honours the property and NEVER ASKS WHICH
|
|
158
158
|
* MECHANISM SET IT, so the market stays uniform. An EMPTY set is a real
|
|
159
159
|
* declaration — "this answer voices nothing" (recall) — and withholds
|
|
160
160
|
* nothing; omit the field and the pipeline re-recognises the answer. */
|
|
161
161
|
used?: ReadonlySet<number>;
|
|
162
|
-
unexplained: string;
|
|
163
|
-
/** Explicit weight override. When absent, weight = moves + PASS·unaccounted. */
|
|
164
|
-
weight?: number;
|
|
165
162
|
/** Bytes of `bytes` that came from spans nothing recognised — the asker's
|
|
166
163
|
* own words carried through verbatim rather than derived (see
|
|
167
164
|
* {@link liftedScaffolding}). Reported, not priced: the ladder prices what
|
|
@@ -12,8 +12,9 @@ import { PASS, STEP } from "./graph-search.js";
|
|
|
12
12
|
import { gistOf, read, resolve } from "./primitives.js";
|
|
13
13
|
import { recognise } from "./recognition.js";
|
|
14
14
|
import { fuseAttention, reason } from "./reasoning.js";
|
|
15
|
-
import { unaccountedBytes, unexplainedSpans } from "./
|
|
15
|
+
import { closed, remainderOf, unaccountedBytes, unexplainedSpans, windowOf, } from "./derivation.js";
|
|
16
16
|
import { rItem } from "./trace.js";
|
|
17
|
+
import { unexplainedLabel } from "./rationale.js";
|
|
17
18
|
import { hubBound } from "./traverse.js";
|
|
18
19
|
import { Precomputed } from "./pipeline-mechanism.js";
|
|
19
20
|
import { coverMechanism } from "./mechanisms/cover.js";
|
|
@@ -242,14 +243,16 @@ export async function think(ctx, query, mechs) {
|
|
|
242
243
|
? await meter.time(`${mech.name}.run`, () => mech.run(ctx, query, pre))
|
|
243
244
|
: await mech.run(ctx, query, pre);
|
|
244
245
|
for (const r of results) {
|
|
245
|
-
|
|
246
|
+
// ONE FORMULA, EVERY CANDIDATE: the chart's derivation reports how many
|
|
247
|
+
// discrete moves it made and which bytes it could not recognise; the
|
|
248
|
+
// currency prices both. No mechanism passes a price of its own.
|
|
249
|
+
const weight = weigh(r.accounted, r.moves);
|
|
246
250
|
consider({
|
|
247
251
|
bytes: r.bytes,
|
|
248
252
|
provenance: r.provenance ?? mech.provenance,
|
|
249
253
|
weight,
|
|
250
254
|
used: r.used,
|
|
251
255
|
accounted: r.accounted,
|
|
252
|
-
unexplained: r.unexplained,
|
|
253
256
|
complete: r.complete,
|
|
254
257
|
scaffolding: r.scaffolding,
|
|
255
258
|
});
|
|
@@ -280,7 +283,17 @@ export async function think(ctx, query, mechs) {
|
|
|
280
283
|
const margin = decided !== null && runnerUp !== null
|
|
281
284
|
? grade(runnerUp.weight) - grade(decided.weight)
|
|
282
285
|
: null;
|
|
283
|
-
ctx.trace?.step("decideGrounding",
|
|
286
|
+
ctx.trace?.step("decideGrounding",
|
|
287
|
+
// THE LABEL IS RENDERED WHERE IT IS SHOWN. It was a field on every
|
|
288
|
+
// mechanism's result, and at every one of them it was exactly
|
|
289
|
+
// `unexplainedLabel(query, accounted)` — a second representation of a
|
|
290
|
+
// quantity one pure function already yields, computed on every response
|
|
291
|
+
// whether or not anyone looked. Here it is computed only when a rationale
|
|
292
|
+
// is attached, because `trace?.step` short-circuits its arguments.
|
|
293
|
+
candidates.map((c) => {
|
|
294
|
+
const label = unexplainedLabel(query, c.accounted);
|
|
295
|
+
return rItem(c.bytes, `${c.provenance} (weight ${c.weight.toFixed(3)}${label ? `, unexplained: "${label}"` : ""})`);
|
|
296
|
+
}), decided ? [rItem(decided.bytes, decided.provenance)] : [], "the lightest grounding derivation wins — every mechanism weighed in the one cost ladder", undefined, {
|
|
284
297
|
version: 1,
|
|
285
298
|
candidates: candidates.map((c) => ({
|
|
286
299
|
provenance: c.provenance,
|
|
@@ -317,7 +330,54 @@ export async function think(ctx, query, mechs) {
|
|
|
317
330
|
const answer = decided.bytes;
|
|
318
331
|
const provenance = decided.provenance;
|
|
319
332
|
const declaredUsed = decided.used;
|
|
320
|
-
// ──
|
|
333
|
+
// ── THE DERIVATION STATE ─────────────────────────────────────────────
|
|
334
|
+
//
|
|
335
|
+
// THE REASONER JUDGES ITS OWN EXTENSIONS BY THE PIPELINE'S REMAINDER, not by
|
|
336
|
+
// the ladder's `accounted` — and by the SAME reading the fuse gate below uses,
|
|
337
|
+
// with the same W floor. `accounted` is a COST quantity (measured: a query
|
|
338
|
+
// fully explained by one computed span plus bridged connectors reports
|
|
339
|
+
// `accounted: []` while nothing is unexplained), and a remainder under one
|
|
340
|
+
// river-fold quantum is bridging punctuation, never a second topic — so it
|
|
341
|
+
// licenses no extension and blocks none.
|
|
342
|
+
//
|
|
343
|
+
// The state is built HERE, where these quantities are already computed, so it
|
|
344
|
+
// costs nothing new: `accounted` is what the winning transition priced,
|
|
345
|
+
// `remainder` is the coverage reading over `accounted ∪ the response's
|
|
346
|
+
// computed spans` (the union is what makes the two different quantities, and
|
|
347
|
+
// both are kept), `cost` is the ladder position, and the two declarations are
|
|
348
|
+
// the producer's own (`fixed`, `used`). What follows reads THIS state rather
|
|
349
|
+
// than a tuple rebuilt at each call site.
|
|
350
|
+
// A DERIVATION IS BORN OWING WHAT ITS ANSWER DOES NOT CARRY. The winning
|
|
351
|
+
// transition PRICED these spans, and pricing is not carrying: coverage claimed
|
|
352
|
+
// without evidence stays owed, and a later transition pays it only by carrying
|
|
353
|
+
// it (the law reads the window; see derivation.ts). Same reading, one
|
|
354
|
+
// definition — not a second spelling of it here.
|
|
355
|
+
const explained = [
|
|
356
|
+
...decided.accounted,
|
|
357
|
+
...pre.computed.map((u) => [u.i, u.j]),
|
|
358
|
+
].filter(([a, b]) => windowOf([a, b], answer, query, ctx.space.maxGroup) !== null);
|
|
359
|
+
// WHAT THE CONSTRUCTION WITHHOLDS, at or above one quantum: the difference between
|
|
360
|
+
// the remainder paid in full and the remainder paid by carrying. Both readings
|
|
361
|
+
// are the law's, so the floor is applied once and in one place.
|
|
362
|
+
const paidInFull = remainderOf(query.length, [
|
|
363
|
+
...decided.accounted,
|
|
364
|
+
...pre.computed.map((u) => [u.i, u.j]),
|
|
365
|
+
], ctx.space.maxGroup);
|
|
366
|
+
const paid = remainderOf(query.length, explained, ctx.space.maxGroup);
|
|
367
|
+
if (ctx.meter) {
|
|
368
|
+
ctx.meter.groundingWithheldBytes += unaccountedBytes(paid) -
|
|
369
|
+
unaccountedBytes(paidInFull);
|
|
370
|
+
}
|
|
371
|
+
const state = {
|
|
372
|
+
product: answer,
|
|
373
|
+
accounted: decided.accounted,
|
|
374
|
+
remainder: paid,
|
|
375
|
+
cost: decided.weight,
|
|
376
|
+
fixed: decided.complete,
|
|
377
|
+
used: decided.used,
|
|
378
|
+
};
|
|
379
|
+
const uncovered = state.remainder;
|
|
380
|
+
// ── Post-grounding, gated by the declaration and the remainder ────────
|
|
321
381
|
const preConsumed = declaredUsed ??
|
|
322
382
|
new Set(recognise(ctx, answer).sites.map((s) => s.payload));
|
|
323
383
|
// A grounding that DECLARED itself complete is not extended: the answer is
|
|
@@ -350,10 +410,13 @@ export async function think(ctx, query, mechs) {
|
|
|
350
410
|
// suppress every pivot the answer legitimately contains.
|
|
351
411
|
const voiced = declaredUsed === undefined ? [] : [...declaredUsed].flatMap((id) => ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n)));
|
|
352
412
|
// WHAT THIS BRANCH READ, published where it was read. Post-grounding decides
|
|
353
|
-
// by `decided.used
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
//
|
|
413
|
+
// by the DECLARATION (`decided.used`, which becomes `voiced`), by what the
|
|
414
|
+
// recognition already consumed (`preConsumed`) and by the derivation's own
|
|
415
|
+
// remainder — never by the provenance NAME, which is REPORTED throughout and
|
|
416
|
+
// compared nowhere (a stale comment here claimed otherwise; the register caught
|
|
417
|
+
// it, and this is the correction). The operands were invisible in the trace, so
|
|
418
|
+
// a change to the branching could not be shown equivalent or otherwise from
|
|
419
|
+
// outside — three separate investigations failed on exactly that gap. A gap in instrumentation is a defect IN the instrumentation
|
|
357
420
|
// (AGENTS.md §6): closed here, once, as counts only — never content.
|
|
358
421
|
ctx.trace?.step("postGrounding", [rItem(answer, provenance)], [], `used=${decided.used !== undefined ? "declared" : "absent"} · ` +
|
|
359
422
|
`preConsumed=${preConsumed.size} · voiced=${voiced.length}`, undefined, {
|
|
@@ -362,6 +425,14 @@ export async function think(ctx, query, mechs) {
|
|
|
362
425
|
usedDeclared: decided.used !== undefined,
|
|
363
426
|
preConsumed: preConsumed.size,
|
|
364
427
|
voiced: voiced.length,
|
|
428
|
+
// THE STATE THE LAW GOVERNS, rendered where it is decided: what the asker
|
|
429
|
+
// said that no step has accounted for, in spans at or above one quantum,
|
|
430
|
+
// and whether the producer supplied a fixed point. Counts only, like
|
|
431
|
+
// every other operand here — and the spans are the state's, so a reader
|
|
432
|
+
// can check them against the meter's aggregate of the same remainder.
|
|
433
|
+
remainderSpans: state.remainder.length,
|
|
434
|
+
remainderBytes: unaccountedBytes(state.remainder),
|
|
435
|
+
fixed: state.fixed === true,
|
|
365
436
|
});
|
|
366
437
|
// REPORTABLE, NOT SILENT. A declared-complete grounding ends the derivation
|
|
367
438
|
// here, and that decision is part of the derivation's shape: the reader of a
|
|
@@ -373,19 +444,6 @@ export async function think(ctx, query, mechs) {
|
|
|
373
444
|
ctx.trace?.step("completeGrounding", [rItem(answer, provenance)], [], "grounding declared complete — the query IS the context, so " +
|
|
374
445
|
"post-grounding extension is skipped");
|
|
375
446
|
}
|
|
376
|
-
// THE REASONER JUDGES ITS OWN EXTENSIONS BY THE PIPELINE'S REMAINDER, not by
|
|
377
|
-
// the ladder's `accounted` — and by the SAME reading the fuse gate below uses,
|
|
378
|
-
// with the same W floor. `accounted` is a COST quantity (measured: a query
|
|
379
|
-
// fully explained by one computed span plus bridged connectors reports
|
|
380
|
-
// `accounted: []` while nothing is unexplained), and a remainder under one
|
|
381
|
-
// river-fold quantum is bridging punctuation, never a second topic — so it
|
|
382
|
-
// licenses no extension and blocks none.
|
|
383
|
-
const explained = [
|
|
384
|
-
...decided.accounted,
|
|
385
|
-
...pre.computed.map((u) => [u.i, u.j]),
|
|
386
|
-
];
|
|
387
|
-
const uncovered = unexplainedSpans(query.length, explained)
|
|
388
|
-
.filter(([a, b]) => b - a >= ctx.space.maxGroup);
|
|
389
447
|
// PUBLISHED, NOT RECOMPUTED: the same `uncovered` the gates below read. A
|
|
390
448
|
// write-only accounting (meter contract 1), so the number that licenses an
|
|
391
449
|
// extension or a fusion stops being invisible.
|
|
@@ -393,14 +451,15 @@ export async function think(ctx, query, mechs) {
|
|
|
393
451
|
meter.postGroundingRemainderSpans += uncovered.length;
|
|
394
452
|
meter.postGroundingRemainderBytes += unaccountedBytes(uncovered);
|
|
395
453
|
}
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
//
|
|
454
|
+
// THE WALK CONSUMES AND RETURNS A STATE. It is handed the derivation's own —
|
|
455
|
+
// the grounding's product, accounting, remainder and cost — and hands back the
|
|
456
|
+
// state it advanced to, so what follows reads a state rather than bytes plus a
|
|
457
|
+
// tuple rebuilt here. A supplied fixed point is the one case where the walk
|
|
458
|
+
// does not run at all, and then the state is the grounding's own.
|
|
400
459
|
const extension = decided.complete ? undefined : meter
|
|
401
|
-
? await meter.time("reason", () => reason(ctx, query,
|
|
402
|
-
: await reason(ctx, query,
|
|
403
|
-
const reasoned = extension
|
|
460
|
+
? await meter.time("reason", () => reason(ctx, query, state, preConsumed, pre, voiced))
|
|
461
|
+
: await reason(ctx, query, state, preConsumed, pre, voiced);
|
|
462
|
+
const reasoned = extension ?? state;
|
|
404
463
|
// Fuse only when the query has a genuine REMAINDER no mechanism's
|
|
405
464
|
// structural evidence touched at all. `decided.accounted` alone
|
|
406
465
|
// undercounts this: it is a COST-LADDER quantity (cover.ts prices its
|
|
@@ -417,7 +476,15 @@ export async function think(ctx, query, mechs) {
|
|
|
417
476
|
// observed: a single space between two fully-computed arithmetic spans
|
|
418
477
|
// ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
|
|
419
478
|
// corpus fact, corrupting "4 6" into "4 63".
|
|
420
|
-
|
|
479
|
+
// THE GATE ASKS THE LAW, and that is an OPTIMISATION, not a tidy-up: the state
|
|
480
|
+
// above ALREADY carries the remainder (`remainderOf`, per-span, with the W
|
|
481
|
+
// floor applied), so asking it costs nothing, while the total this line used to
|
|
482
|
+
// compute (`unaccounted(explained)`) was one more sum over the spans on every
|
|
483
|
+
// response. The two readings are the same condition, not two: the ACCOUNTING
|
|
484
|
+
// applies the same W floor the gate does, so a gap below one quantum never
|
|
485
|
+
// survives into `explained` and the total cannot reach W without some single
|
|
486
|
+
// gap reaching it. Measured over twelve constructions at W = 4 (test/136.3,
|
|
487
|
+
// which pins the equivalence and both sides of it).
|
|
421
488
|
// Whether the winning candidate's entire recognised substance is
|
|
422
489
|
// COMPUTED — every accounted span exactly a pre.computed span, nothing
|
|
423
490
|
// from a genuinely recognised/climbed site. fuseAttention's lone-root
|
|
@@ -427,8 +494,8 @@ export async function think(ctx, query, mechs) {
|
|
|
427
494
|
// `unclimbed` parameter, gated there by Attention.breadth so a
|
|
428
495
|
// coincidental echo (which this flag alone cannot distinguish) is still
|
|
429
496
|
// rejected.
|
|
430
|
-
const unclimbed =
|
|
431
|
-
|
|
497
|
+
const unclimbed = state.accounted.length > 0 &&
|
|
498
|
+
state.accounted.every(([i, j]) => pre.computed.some((u) => u.i === i && u.j === j));
|
|
432
499
|
// Where the winning grounding stands in the query — fusion places primary
|
|
433
500
|
// by it (see fuseAttention's `primarySpans`). `accounted` is the
|
|
434
501
|
// cost-ladder read and is authoritative when non-empty; when it is empty
|
|
@@ -436,15 +503,13 @@ export async function think(ctx, query, mechs) {
|
|
|
436
503
|
// Exactly the cost-ladder-vs-coverage distinction `explained` above draws,
|
|
437
504
|
// read here for POSITION instead of for coverage — and resolved here, where
|
|
438
505
|
// both readings are in hand, rather than inside fuseAttention.
|
|
439
|
-
const primarySpans =
|
|
440
|
-
?
|
|
506
|
+
const primarySpans = state.accounted.length > 0
|
|
507
|
+
? state.accounted
|
|
441
508
|
: pre.computed.map((u) => [u.i, u.j]);
|
|
442
|
-
const fused =
|
|
443
|
-
? reasoned
|
|
444
|
-
:
|
|
445
|
-
|
|
446
|
-
: await fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans);
|
|
447
|
-
done(fused,
|
|
509
|
+
const fused = closed(state) ? reasoned : meter
|
|
510
|
+
? await meter.time("fuse", () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans))
|
|
511
|
+
: await fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans);
|
|
512
|
+
done(fused.product,
|
|
448
513
|
// NO CLAIM ABOUT FUSION HERE. `fuseAttention` is entered whenever a
|
|
449
514
|
// remainder ≥ W exists and returns early when there is nothing to bridge, so
|
|
450
515
|
// this note used to assert a fusion that frequently did not happen (measured:
|
|
@@ -452,5 +517,5 @@ export async function think(ctx, query, mechs) {
|
|
|
452
517
|
// reported by `fuseAttention`'s own `done` when it happens — the layer that
|
|
453
518
|
// did the work is the layer that says so.
|
|
454
519
|
"grounded, reasoned forward");
|
|
455
|
-
return { bytes: fused, provenance };
|
|
520
|
+
return { bytes: fused.product, provenance };
|
|
456
521
|
}
|
|
@@ -83,17 +83,6 @@ export type InspectRationale = (step: RationaleStep) => void;
|
|
|
83
83
|
/** Decode bytes to text for display, dropping the NUL padding the encoder uses
|
|
84
84
|
* (the same cleanup {@link Mind.respondText} does for its result). */
|
|
85
85
|
export declare function decodeText(bytes: Uint8Array): string;
|
|
86
|
-
/** The BYTE COUNT of the complement — what the currency calls `unaccounted`
|
|
87
|
-
* in `weight = moves + PASS·unaccounted`. It lives here, beside the function
|
|
88
|
-
* that produces the gaps, because the price's second term has ONE definition:
|
|
89
|
-
* this was four copies of the same `reduce` (two in reasoning.ts, two in
|
|
90
|
-
* pipeline.ts) before the architecture audit of `../auditoria-arquitectura-sema.md`
|
|
91
|
-
* collapsed them. Same value at every site — the control diff is identical. */
|
|
92
|
-
export declare function unaccountedBytes(spans: ReadonlyArray<readonly [number, number]>): number;
|
|
93
|
-
/** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
|
|
94
|
-
* the same union-of-spans reading think's grounding decider prices at PASS
|
|
95
|
-
* per byte, exposed here so a mechanism can turn it into a human label. */
|
|
96
|
-
export declare function unexplainedSpans(queryLen: number, accounted: ReadonlyArray<[number, number]>): Array<[number, number]>;
|
|
97
86
|
/** A human-readable label for the query bytes a mechanism's `accounted`
|
|
98
87
|
* spans leave unexplained — purely diagnostic (Task 2's negative evidence):
|
|
99
88
|
* it never changes a candidate's weight, only what the rationale trace
|
|
@@ -19,43 +19,17 @@
|
|
|
19
19
|
// out into a ranked list of hits. So a step's `inputs` and `outputs` are each a
|
|
20
20
|
// VECTOR — an ordered list of {@link RationaleItem}s, one per element — and the
|
|
21
21
|
// fan-out / fan-in is visible in their lengths.
|
|
22
|
+
import { unexplainedSpans } from "./derivation.js";
|
|
22
23
|
/** Decode bytes to text for display, dropping the NUL padding the encoder uses
|
|
23
24
|
* (the same cleanup {@link Mind.respondText} does for its result). */
|
|
24
25
|
export function decodeText(bytes) {
|
|
25
26
|
return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* collapsed them. Same value at every site — the control diff is identical. */
|
|
33
|
-
export function unaccountedBytes(spans) {
|
|
34
|
-
let total = 0;
|
|
35
|
-
for (const [a, b] of spans)
|
|
36
|
-
total += b - a;
|
|
37
|
-
return total;
|
|
38
|
-
}
|
|
39
|
-
/** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
|
|
40
|
-
* the same union-of-spans reading think's grounding decider prices at PASS
|
|
41
|
-
* per byte, exposed here so a mechanism can turn it into a human label. */
|
|
42
|
-
export function unexplainedSpans(queryLen, accounted) {
|
|
43
|
-
const sorted = accounted
|
|
44
|
-
.map(([s, e]) => [Math.max(0, s), Math.min(queryLen, e)])
|
|
45
|
-
.filter(([s, e]) => e > s)
|
|
46
|
-
.sort((a, b) => a[0] - b[0]);
|
|
47
|
-
const gaps = [];
|
|
48
|
-
let reach = 0;
|
|
49
|
-
for (const [s, e] of sorted) {
|
|
50
|
-
if (s > reach)
|
|
51
|
-
gaps.push([reach, s]);
|
|
52
|
-
if (e > reach)
|
|
53
|
-
reach = e;
|
|
54
|
-
}
|
|
55
|
-
if (reach < queryLen)
|
|
56
|
-
gaps.push([reach, queryLen]);
|
|
57
|
-
return gaps;
|
|
58
|
-
}
|
|
28
|
+
// THE SPAN ALGEBRA LIVES IN derivation.ts. `unaccountedBytes` and
|
|
29
|
+
// `unexplainedSpans` are the closure law's vocabulary — gap arithmetic over the
|
|
30
|
+
// asker's own bytes — and they moved to the layer that owns the law, so they
|
|
31
|
+
// have ONE home and are no longer asked of the tracer. This module keeps the
|
|
32
|
+
// inference TOLD as it happens, and reads the gaps only to render a label.
|
|
59
33
|
/** A human-readable label for the query bytes a mechanism's `accounted`
|
|
60
34
|
* spans leave unexplained — purely diagnostic (Task 2's negative evidence):
|
|
61
35
|
* it never changes a candidate's weight, only what the rationale trace
|