@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
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
// 137-the-law-lives-once-and-below.test.mjs — the adversarial checks that are
|
|
2
|
+
// STRUCTURAL: the law's layer, the single definition of each reading, and the
|
|
3
|
+
// determinism that makes it a function rather than a procedure.
|
|
4
|
+
//
|
|
5
|
+
// These are the quality points the goal names that no behavioural test can
|
|
6
|
+
// reach: a green suite would say nothing about where the law sits, whether a
|
|
7
|
+
// reading was re-spelled somewhere else, or whether a verdict depends on the
|
|
8
|
+
// corpus. So this file reads the SOURCE, and asserts:
|
|
9
|
+
//
|
|
10
|
+
// 137.1 THE LAYER, transitively. The law's entire import closure is itself
|
|
11
|
+
// plus `bytes.ts` — proved by walking the relative imports, not by
|
|
12
|
+
// reading its import line. That single fact carries three properties at
|
|
13
|
+
// once: it sits below every tier that asks it, no cycle passes through
|
|
14
|
+
// it, and it CANNOT reach the store, the corpus or a mechanism, so
|
|
15
|
+
// nothing it decides can grow with the corpus.
|
|
16
|
+
//
|
|
17
|
+
// 137.2 ONE DEFINITION EACH. Every reading the extraction unified has exactly
|
|
18
|
+
// one definition in `src/`, and the coverage scan exists once — the
|
|
19
|
+
// check that the five restatement spellings have not crept back, and
|
|
20
|
+
// that the label has one home and one call site.
|
|
21
|
+
//
|
|
22
|
+
// 137.3 IT IS A FUNCTION. The same state and continuation give the same
|
|
23
|
+
// verdict, every time; and the same response run twice gives byte-equal
|
|
24
|
+
// meter counters, so the extraction is deterministic end to end.
|
|
25
|
+
|
|
26
|
+
import { test } from "node:test";
|
|
27
|
+
import assert from "node:assert/strict";
|
|
28
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
29
|
+
import { dirname, join, relative } from "node:path";
|
|
30
|
+
import { fileURLToPath } from "node:url";
|
|
31
|
+
|
|
32
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const srcRoot = join(here, "..", "src");
|
|
34
|
+
const law = await import("../dist/src/mind/derivation.js");
|
|
35
|
+
const { Mind, SQliteStore } = await import("../dist/src/index.js");
|
|
36
|
+
|
|
37
|
+
/** The source with its comments removed, so a count of CALLS cannot be satisfied
|
|
38
|
+
* or broken by a mention in prose — the discipline this file is about. */
|
|
39
|
+
function stripComments(s) {
|
|
40
|
+
return s.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "")
|
|
41
|
+
.replace(/\/\/[^\n"']*$/gm, "");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Every `.ts` under src/, with its relative import targets resolved. */
|
|
45
|
+
async function graph() {
|
|
46
|
+
const files = [];
|
|
47
|
+
const walk = async (d) => {
|
|
48
|
+
for (const e of await readdir(d, { withFileTypes: true })) {
|
|
49
|
+
const p = join(d, e.name);
|
|
50
|
+
if (e.isDirectory()) await walk(p);
|
|
51
|
+
else if (e.name.endsWith(".ts")) files.push(p);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
await walk(srcRoot);
|
|
55
|
+
const g = new Map();
|
|
56
|
+
const text = new Map();
|
|
57
|
+
for (const f of files) {
|
|
58
|
+
const s = await readFile(f, "utf8");
|
|
59
|
+
// COUNTING uses the comment-free text; the import graph uses the raw text,
|
|
60
|
+
// where a path in prose cannot invent an edge but a path in code must not be
|
|
61
|
+
// missed.
|
|
62
|
+
text.set(relative(join(here, ".."), f), stripComments(s));
|
|
63
|
+
const targets = [];
|
|
64
|
+
for (const m of s.matchAll(/from\s+"(\.[^"]+)"/g)) {
|
|
65
|
+
targets.push(
|
|
66
|
+
relative(join(here, ".."), join(dirname(f), m[1])).replace(
|
|
67
|
+
/\.js$/,
|
|
68
|
+
".ts",
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
g.set(relative(join(here, ".."), f), targets);
|
|
73
|
+
}
|
|
74
|
+
return { g, text };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
test("137.1 the law's whole import closure is itself and bytes.ts", async () => {
|
|
78
|
+
const { g } = await graph();
|
|
79
|
+
const lawFile = "src/mind/derivation.ts";
|
|
80
|
+
assert.ok(g.has(lawFile), `${lawFile} must exist`);
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
const stack = [lawFile];
|
|
83
|
+
while (stack.length > 0) {
|
|
84
|
+
const f = stack.pop();
|
|
85
|
+
assert.ok(g.has(f), `unresolved import target ${f}`);
|
|
86
|
+
if (seen.has(f)) continue;
|
|
87
|
+
seen.add(f);
|
|
88
|
+
for (const t of g.get(f)) {
|
|
89
|
+
assert.notEqual(
|
|
90
|
+
t,
|
|
91
|
+
lawFile,
|
|
92
|
+
`an import cycle passes through ${lawFile}: ${f} imports it back`,
|
|
93
|
+
);
|
|
94
|
+
stack.push(t);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
assert.deepEqual(
|
|
98
|
+
[...seen].sort(),
|
|
99
|
+
["src/bytes.ts", "src/mind/derivation.ts"],
|
|
100
|
+
"the law must reach NOTHING else — a store, a corpus helper or a mechanism " +
|
|
101
|
+
"in its closure would let a verdict depend on the corpus, and would put the " +
|
|
102
|
+
"law above a layer instead of below every one",
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("137.2 every unified reading has exactly one definition", async () => {
|
|
107
|
+
const { text } = await graph();
|
|
108
|
+
const all = [...text.entries()];
|
|
109
|
+
const count = (re) =>
|
|
110
|
+
all.reduce((n, [, s]) => n + (s.match(re) ?? []).length, 0);
|
|
111
|
+
|
|
112
|
+
// the law's own functions, each defined once in all of src/
|
|
113
|
+
for (
|
|
114
|
+
const fn of [
|
|
115
|
+
"unaccountedBytes",
|
|
116
|
+
"unexplainedSpans",
|
|
117
|
+
"remainderOf",
|
|
118
|
+
"carries",
|
|
119
|
+
"closed",
|
|
120
|
+
"admissible",
|
|
121
|
+
"advance",
|
|
122
|
+
"closure",
|
|
123
|
+
"restates",
|
|
124
|
+
"insideAnsweredTurn",
|
|
125
|
+
]
|
|
126
|
+
) {
|
|
127
|
+
assert.equal(
|
|
128
|
+
count(new RegExp(`export (?:async )?function ${fn}\\(`, "g")),
|
|
129
|
+
1,
|
|
130
|
+
`${fn} must have exactly ONE definition in src/ — a second is a spelling ` +
|
|
131
|
+
`that will drift`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
// the coverage scan (the law's measure) — the one the brake used to duplicate
|
|
135
|
+
assert.equal(
|
|
136
|
+
count(/i \+ W <= b/g),
|
|
137
|
+
1,
|
|
138
|
+
"the coverage scan must exist exactly once in src/",
|
|
139
|
+
);
|
|
140
|
+
// the label: one definition, and exactly one call site outside its own file
|
|
141
|
+
assert.equal(count(/export function unexplainedLabel\(/g), 1);
|
|
142
|
+
let calls = 0;
|
|
143
|
+
for (const [f, s] of all) {
|
|
144
|
+
if (f.endsWith("rationale.ts")) continue;
|
|
145
|
+
calls += (s.match(/unexplainedLabel\(/g) ?? []).length;
|
|
146
|
+
}
|
|
147
|
+
assert.equal(
|
|
148
|
+
calls,
|
|
149
|
+
1,
|
|
150
|
+
`the label must be rendered in exactly one place outside its definition, ` +
|
|
151
|
+
`found ${calls}`,
|
|
152
|
+
);
|
|
153
|
+
// and the old spellings are gone by name
|
|
154
|
+
const stale = all.filter(([, s]) => /(?<!seg)restatesQuery/.test(s));
|
|
155
|
+
assert.deepEqual(
|
|
156
|
+
stale.map(([f]) => f),
|
|
157
|
+
[],
|
|
158
|
+
"the old restatement spelling is still in the tree",
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("137.3 the law is a function, end to end", async () => {
|
|
163
|
+
const query = new TextEncoder().encode(
|
|
164
|
+
"What is the capital of France famous for",
|
|
165
|
+
);
|
|
166
|
+
const W = 4;
|
|
167
|
+
const state = {
|
|
168
|
+
product: query,
|
|
169
|
+
accounted: [],
|
|
170
|
+
remainder: law.remainderOf(query.length, [], W),
|
|
171
|
+
cost: 0,
|
|
172
|
+
};
|
|
173
|
+
const conts = [
|
|
174
|
+
{ product: query.subarray(0, 3 * W), contains: true, cost: 1 },
|
|
175
|
+
{
|
|
176
|
+
product: new TextEncoder().encode("qqqqqqqqqqqq"),
|
|
177
|
+
contains: true,
|
|
178
|
+
cost: 1,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
product: new TextEncoder().encode("qqqqqqqqqqqq"),
|
|
182
|
+
contains: false,
|
|
183
|
+
cost: 1,
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
for (const t of conts) {
|
|
187
|
+
const a = law.admissible(state, t, query, W);
|
|
188
|
+
for (let i = 0; i < 5; i++) {
|
|
189
|
+
assert.deepEqual(
|
|
190
|
+
law.admissible(state, t, query, W),
|
|
191
|
+
a,
|
|
192
|
+
"the same state and continuation must give the same verdict",
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// AND THE SAME RESPONSE TWICE GIVES THE SAME COUNTERS.
|
|
198
|
+
const CHAIN = [
|
|
199
|
+
["What is the capital of France", "The capital of France is Paris"],
|
|
200
|
+
["Paris", "Paris is famous for the Eiffel Tower"],
|
|
201
|
+
["the Eiffel Tower", "the Eiffel Tower is in Paris"],
|
|
202
|
+
];
|
|
203
|
+
const run = async () => {
|
|
204
|
+
const store = new SQliteStore({ path: ":memory:" });
|
|
205
|
+
const mind = new Mind({ seed: 7, store, profile: true });
|
|
206
|
+
await mind.ingest(CHAIN);
|
|
207
|
+
const steps = [];
|
|
208
|
+
const answer = String(
|
|
209
|
+
await mind.respondText(
|
|
210
|
+
"What is the capital of France famous for",
|
|
211
|
+
(s) => steps.push(s),
|
|
212
|
+
),
|
|
213
|
+
).trim();
|
|
214
|
+
const out = {
|
|
215
|
+
answer,
|
|
216
|
+
reaches: steps.map((s) => s.mechanism.join("/")),
|
|
217
|
+
counters: mind.lastCost?.counters ?? {},
|
|
218
|
+
};
|
|
219
|
+
await store.close();
|
|
220
|
+
return out;
|
|
221
|
+
};
|
|
222
|
+
assert.deepEqual(await run(), await run(), "two identical runs must agree");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("137.4 a repeated query answers identically and does no more work", async () => {
|
|
226
|
+
// THE CONTRACT, and the measurement that settles what it means. AGENTS invariant
|
|
227
|
+
// #1 promises a byte-identical ANSWER for the same seed, deposit order and query —
|
|
228
|
+
// and that holds here while the COUNTERS of a repeated query DROP: the per-response
|
|
229
|
+
// memos (`perceiveMemo`, the node/edge caches, the ANN result cache) are warm, so
|
|
230
|
+
// the second run reads less. Measured on the prefix-refusal, hub and fuse
|
|
231
|
+
// fixtures: the answer and the whole step sequence identical, and every counter
|
|
232
|
+
// that appears in both runs lower or equal in the second — the largest drops being
|
|
233
|
+
// edgeProbes 75 -> 1 and nodeRecords 5 -> 1.
|
|
234
|
+
//
|
|
235
|
+
// So determinism is about WHAT is answered, never about what it COSTS the second
|
|
236
|
+
// time: asserting equal counters would assert that the caches never warm.
|
|
237
|
+
const { Mind, SQliteStore } = await import("../dist/src/index.js");
|
|
238
|
+
const cases = [
|
|
239
|
+
[[["The capital of France is", "The capital of France is Paris."], [
|
|
240
|
+
"The capital of France is",
|
|
241
|
+
"The capital of France is Lyon.",
|
|
242
|
+
]], "The capital of France is"],
|
|
243
|
+
[[["paris", "paris is the capital of france"], [
|
|
244
|
+
"paris",
|
|
245
|
+
"paris is famous for the eiffel tower",
|
|
246
|
+
]], "paris"],
|
|
247
|
+
[[["What is the capital of France", "The capital of France is Paris"], [
|
|
248
|
+
"2+2",
|
|
249
|
+
"2+2 equals 4",
|
|
250
|
+
]], "What is the capital of France and what is 2 + 2?"],
|
|
251
|
+
];
|
|
252
|
+
for (const [pairs, q] of cases) {
|
|
253
|
+
const store = new SQliteStore({ path: ":memory:" });
|
|
254
|
+
const mind = new Mind({ seed: 7, store, profile: true });
|
|
255
|
+
await mind.ingest(pairs);
|
|
256
|
+
const s1 = [];
|
|
257
|
+
const a1 = String(await mind.respondText(q, (s) => s1.push(s))).trim();
|
|
258
|
+
const c1 = mind.lastCost?.counters ?? {};
|
|
259
|
+
const s2 = [];
|
|
260
|
+
const a2 = String(await mind.respondText(q, (s) => s2.push(s))).trim();
|
|
261
|
+
const c2 = mind.lastCost?.counters ?? {};
|
|
262
|
+
assert.equal(
|
|
263
|
+
a1,
|
|
264
|
+
a2,
|
|
265
|
+
`q=${JSON.stringify(q)}: the repeated query answered differently`,
|
|
266
|
+
);
|
|
267
|
+
assert.deepEqual(
|
|
268
|
+
s2.map((s) => s.mechanism.join("/")),
|
|
269
|
+
s1.map((s) => s.mechanism.join("/")),
|
|
270
|
+
`q=${JSON.stringify(q)}: the repeated query took a different path`,
|
|
271
|
+
);
|
|
272
|
+
for (const k of Object.keys(c1)) {
|
|
273
|
+
if (c2[k] === undefined) continue;
|
|
274
|
+
assert.ok(
|
|
275
|
+
c2[k] <= c1[k],
|
|
276
|
+
`q=${JSON.stringify(q)}: the repeated query did MORE work on ${k} (${
|
|
277
|
+
c1[k]
|
|
278
|
+
} -> ${c2[k]}) — ` +
|
|
279
|
+
`a cache or memo is not warming, which is a cost regression, not determinism`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
await store.close();
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("137.5 the positional reading is the law's, and the offset caller asks it", async () => {
|
|
287
|
+
// The reading that says "the query already says this AT OR AFTER this point" is a
|
|
288
|
+
// closure reading like the others, so it lives with them. Pinned: that the witness
|
|
289
|
+
// behaves as a SEARCH START, and that the one caller which needs it asks the law
|
|
290
|
+
// instead of spelling the containment itself.
|
|
291
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
292
|
+
const query = enc("ab cd ef");
|
|
293
|
+
const tail = enc("cd ef");
|
|
294
|
+
assert.equal(
|
|
295
|
+
law.restates(query, tail, 0),
|
|
296
|
+
true,
|
|
297
|
+
"the plain reading finds it anywhere",
|
|
298
|
+
);
|
|
299
|
+
assert.equal(
|
|
300
|
+
law.restates(query, tail, 0, { from: 3 }),
|
|
301
|
+
true,
|
|
302
|
+
"and the positional reading finds it at or after the offset",
|
|
303
|
+
);
|
|
304
|
+
assert.equal(
|
|
305
|
+
law.restates(query, tail, 0, { from: 4 }),
|
|
306
|
+
false,
|
|
307
|
+
"one byte past the occurrence, it is not found",
|
|
308
|
+
);
|
|
309
|
+
assert.equal(
|
|
310
|
+
law.restates(query, tail, 0, { from: query.length }),
|
|
311
|
+
false,
|
|
312
|
+
"and from the end nothing is found",
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const { text } = await graph();
|
|
316
|
+
const reasoning = text.get("src/mind/reasoning.ts");
|
|
317
|
+
assert.ok(
|
|
318
|
+
reasoning !== undefined,
|
|
319
|
+
"reasoning.ts must be in the scanned tree",
|
|
320
|
+
);
|
|
321
|
+
assert.match(
|
|
322
|
+
reasoning,
|
|
323
|
+
/restates\(query, cont, 0, \{ from: root\.end \}\)/,
|
|
324
|
+
"the alreadyAnswered guard must ask the law's positional reading",
|
|
325
|
+
);
|
|
326
|
+
assert.ok(
|
|
327
|
+
!/indexOf\(query, cont, root\.end\)/.test(reasoning),
|
|
328
|
+
"the containment is spelled inline again: the offset reading belongs to the law",
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("137.6 no export is genuinely dead — the triage, as a guard", async () => {
|
|
333
|
+
// The scan that once reported "35 exports with no consumer" was fooled by TYPES used
|
|
334
|
+
// as declared shapes inside their own file. Asked as three questions — used in its
|
|
335
|
+
// own file, by other modules, by tests — exactly ONE export was genuinely dead, and
|
|
336
|
+
// it was removed. This pins that state, so a future dead export must be noticed
|
|
337
|
+
// rather than accumulate: a declaration appearing ONCE in its own file and nowhere
|
|
338
|
+
// in `src/` or in `test/` is dead by definition.
|
|
339
|
+
const { text } = await graph();
|
|
340
|
+
const entries = [...text.entries()];
|
|
341
|
+
// TWO EXEMPTIONS, both "deliberately offered" rather than forgotten:
|
|
342
|
+
// * PUBLIC SURFACE — a module `src/index.ts` re-exports by wildcard ships every
|
|
343
|
+
// one of its exports to callers, so an export nothing inside this repo calls
|
|
344
|
+
// (a library class, a derived bar) is API, not dead. `coverageBar` is exactly
|
|
345
|
+
// that: `docs/architecture/thresholds.md` lists it as "currently unused
|
|
346
|
+
// hot-path; batch compaction replaces it".
|
|
347
|
+
// * DOCUMENTED — a name the architecture docs describe is part of the contract
|
|
348
|
+
// even when only prose references it.
|
|
349
|
+
const indexSrc = text.get("src/index.ts") ?? "";
|
|
350
|
+
const publicModules = new Set(
|
|
351
|
+
[...indexSrc.matchAll(/export \* from "\.\/([^"]+)\.js"/g)].map((m) =>
|
|
352
|
+
`src/${m[1]}.ts`
|
|
353
|
+
),
|
|
354
|
+
);
|
|
355
|
+
const docsDir = join(here, "..", "docs");
|
|
356
|
+
const docsText = [];
|
|
357
|
+
const walkDocs = async (d) => {
|
|
358
|
+
for (const e of await readdir(d, { withFileTypes: true })) {
|
|
359
|
+
const q = join(d, e.name);
|
|
360
|
+
if (e.isDirectory()) await walkDocs(q);
|
|
361
|
+
else if (e.name.endsWith(".md")) docsText.push(await readFile(q, "utf8"));
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
await walkDocs(docsDir);
|
|
365
|
+
const docs = docsText.join("\n");
|
|
366
|
+
const testDir = join(here, "..", "test");
|
|
367
|
+
const testText = [];
|
|
368
|
+
for (const f of await readdir(testDir)) {
|
|
369
|
+
if (f.endsWith(".mjs")) {
|
|
370
|
+
testText.push(await readFile(join(testDir, f), "utf8"));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const tests = testText.join("\n");
|
|
374
|
+
const dead = [];
|
|
375
|
+
for (const [file, src] of entries) {
|
|
376
|
+
const others = entries.filter(([f]) => f !== file).map(([, t]) => t).join(
|
|
377
|
+
"\n",
|
|
378
|
+
);
|
|
379
|
+
for (
|
|
380
|
+
const m of src.matchAll(
|
|
381
|
+
/^export\s+(?:async\s+)?(?:abstract\s+)?(?:function|const|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/gm,
|
|
382
|
+
)
|
|
383
|
+
) {
|
|
384
|
+
const name = m[1];
|
|
385
|
+
const count = (hay) =>
|
|
386
|
+
(hay.match(new RegExp(`\\b${name}\\b`, "g")) ?? []).length;
|
|
387
|
+
if (count(src) > 1) continue;
|
|
388
|
+
if (count(others) > 0) continue;
|
|
389
|
+
if (count(tests) > 0) continue;
|
|
390
|
+
if (publicModules.has(file)) continue; // shipped to callers
|
|
391
|
+
if (count(docs) > 0) continue; // described by the architecture docs
|
|
392
|
+
dead.push(`${file}: ${name}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
assert.deepEqual(
|
|
396
|
+
dead,
|
|
397
|
+
[],
|
|
398
|
+
"genuinely dead exports found — remove them, or state where they are consumed",
|
|
399
|
+
);
|
|
400
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// 138 — the remainder drains only on a declared move.
|
|
2
|
+
//
|
|
3
|
+
// THE TWO MEASUREMENTS THIS PINS, both taken against the built law:
|
|
4
|
+
//
|
|
5
|
+
// a cycle re-offering the SAME product (no new structure, no declared move)
|
|
6
|
+
// carried the question's own bytes, step after step, and closed the
|
|
7
|
+
// derivation in TWO steps with `remainder: []` — a false closure, bought by
|
|
8
|
+
// repetition. Under the law as it stands the same cycle never closes, and its
|
|
9
|
+
// remainder is untouched.
|
|
10
|
+
//
|
|
11
|
+
// a step that MOVES (structure this derivation has not consumed) and declares
|
|
12
|
+
// the span it accounts for drains exactly that span, and closes the
|
|
13
|
+
// derivation when the question's material is spent — by the law, with no
|
|
14
|
+
// mechanism outside it.
|
|
15
|
+
|
|
16
|
+
import { test } from "node:test";
|
|
17
|
+
import assert from "node:assert/strict";
|
|
18
|
+
import { admissible, advance, closed } from "../dist/src/mind/derivation.js";
|
|
19
|
+
|
|
20
|
+
/** The question, in bytes: ten of them, one quantum being four. */
|
|
21
|
+
const QUERY = new TextEncoder().encode("ABCDEFGHIJ");
|
|
22
|
+
const W = 4;
|
|
23
|
+
|
|
24
|
+
const start = () => ({
|
|
25
|
+
product: new Uint8Array(0),
|
|
26
|
+
accounted: [],
|
|
27
|
+
remainder: [[0, 10]],
|
|
28
|
+
cost: 0,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("138.1 a cycle cannot close the derivation by carrying the question's bytes", () => {
|
|
32
|
+
let d = start();
|
|
33
|
+
const t = { product: QUERY, contains: true, cost: 1 };
|
|
34
|
+
for (let i = 0; i < 50 && !closed(d); i++) {
|
|
35
|
+
const witness = admissible(d, t, QUERY, W);
|
|
36
|
+
if (witness === null) break;
|
|
37
|
+
d = advance(d, t, witness);
|
|
38
|
+
}
|
|
39
|
+
assert.equal(
|
|
40
|
+
closed(d),
|
|
41
|
+
false,
|
|
42
|
+
"carrying question material is engagement, not progress",
|
|
43
|
+
);
|
|
44
|
+
assert.deepEqual(d.remainder, [[0, 10]]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("138.2 a declared move drains what it accounts for and closes the derivation", () => {
|
|
48
|
+
let d = start();
|
|
49
|
+
for (let i = 0; i < 50 && !closed(d); i++) {
|
|
50
|
+
const t = {
|
|
51
|
+
product: QUERY,
|
|
52
|
+
contains: true,
|
|
53
|
+
reaches: true,
|
|
54
|
+
explains: [[i * W, (i + 1) * W]],
|
|
55
|
+
cost: 1,
|
|
56
|
+
};
|
|
57
|
+
const witness = admissible(d, t, QUERY, W);
|
|
58
|
+
if (witness === null) break;
|
|
59
|
+
d = advance(d, t, witness);
|
|
60
|
+
}
|
|
61
|
+
assert.equal(closed(d), true, "the law closes it — no mechanism outside it");
|
|
62
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// 139 — the witness is ENGAGEMENT, not EXPLANATION.
|
|
2
|
+
//
|
|
3
|
+
// The law admits a transition on CARRIES when the product holds a window of the
|
|
4
|
+
// question's material. Both directions are measured here, because they are what
|
|
5
|
+
// settles the witness's role:
|
|
6
|
+
//
|
|
7
|
+
// a product that HOLDS the window answers nothing — "ZZZZABCDZZ" carries the
|
|
8
|
+
// question's bytes and says nothing about the question. The law admits it.
|
|
9
|
+
//
|
|
10
|
+
// a product that ANSWERS without holding the window — the answer's own words,
|
|
11
|
+
// none of them the question's — carries nothing, and the law refuses it on that
|
|
12
|
+
// ground. A step of that kind must arrive by MOVES, or not at all.
|
|
13
|
+
//
|
|
14
|
+
// That asymmetry is why the question's remainder cannot drain on CARRIES: a
|
|
15
|
+
// window of the question is held by any repetition, whether or not anything was
|
|
16
|
+
// ever answered, so consuming it would buy closure with repetition. It drains on
|
|
17
|
+
// a declared MOVE instead (test/138).
|
|
18
|
+
|
|
19
|
+
import { test } from "node:test";
|
|
20
|
+
import assert from "node:assert/strict";
|
|
21
|
+
import { advance, carries, closed } from "../dist/src/mind/derivation.js";
|
|
22
|
+
|
|
23
|
+
const QUERY = new TextEncoder().encode("ABCDEFGHIJ");
|
|
24
|
+
const W = 4;
|
|
25
|
+
const REMAINDER = [[0, 10]];
|
|
26
|
+
|
|
27
|
+
test("139.1 holding the question's window admits the step, and answers nothing", () => {
|
|
28
|
+
const product = new TextEncoder().encode("ZZZZABCDZZ");
|
|
29
|
+
const witness = carries(REMAINDER, product, QUERY, W);
|
|
30
|
+
assert.notEqual(witness, null, "the window is held");
|
|
31
|
+
assert.deepEqual(witness, [{ span: [0, 10], window: [0, 4] }]);
|
|
32
|
+
// The law takes the step — and the question is no better answered for it, so
|
|
33
|
+
// the remainder is untouched.
|
|
34
|
+
const after = advance(
|
|
35
|
+
{
|
|
36
|
+
product: new Uint8Array(0),
|
|
37
|
+
accounted: [],
|
|
38
|
+
remainder: REMAINDER,
|
|
39
|
+
cost: 0,
|
|
40
|
+
},
|
|
41
|
+
{ product, contains: true, cost: 1 },
|
|
42
|
+
witness,
|
|
43
|
+
);
|
|
44
|
+
assert.deepEqual(after.remainder, REMAINDER);
|
|
45
|
+
assert.equal(closed(after), false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("139.2 answering without holding the window carries nothing", () => {
|
|
49
|
+
const answer = new TextEncoder().encode("Paris");
|
|
50
|
+
assert.equal(carries(REMAINDER, answer, QUERY, W), null);
|
|
51
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// 140 — irrelevant supply does not change an answer already given.
|
|
2
|
+
//
|
|
3
|
+
// The sixth acceptance criterion of the derivation audit: adding facts that have
|
|
4
|
+
// nothing to do with the question must not move the answer, merely by enlarging
|
|
5
|
+
// what the market has to offer. Measured relationally against the fixture's own
|
|
6
|
+
// link, not against a string copied into the test.
|
|
7
|
+
|
|
8
|
+
import { test } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { Mind } from "../dist/src/index.js";
|
|
11
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
12
|
+
|
|
13
|
+
const QUERY = "What is the capital of France famous for";
|
|
14
|
+
|
|
15
|
+
const LINKS = [
|
|
16
|
+
["What is the capital of France", "The capital of France is Paris"],
|
|
17
|
+
["Paris", "Paris is famous for the Eiffel Tower"],
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/** Facts with no bearing on the question, and enough bytes to be real nodes. */
|
|
21
|
+
const IRRELEVANT = [
|
|
22
|
+
["Coffee", "Coffee is brewed from roasted beans"],
|
|
23
|
+
["The Moon", "The Moon orbits the Earth once a month"],
|
|
24
|
+
["Basalt", "Basalt is a fine-grained volcanic rock"],
|
|
25
|
+
["Trumpet", "The trumpet is a brass instrument"],
|
|
26
|
+
["Saffron", "Saffron is a spice from the crocus flower"],
|
|
27
|
+
["Tide", "The tide rises twice in a lunar day"],
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
async function corpus(facts) {
|
|
31
|
+
const store = new SQliteStore({ path: ":memory:" });
|
|
32
|
+
const mind = new Mind({ seed: 7, store, profile: true });
|
|
33
|
+
await mind.ingest(facts);
|
|
34
|
+
return mind;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const text = (resp) => new TextDecoder().decode(resp.bytes).replace(/\0+/g, "");
|
|
38
|
+
|
|
39
|
+
test("140.1 the answer survives a corpus enlarged with unrelated facts", async () => {
|
|
40
|
+
const lean = await corpus(LINKS);
|
|
41
|
+
const fat = await corpus([...LINKS, ...IRRELEVANT]);
|
|
42
|
+
const answer = text(await lean.respond(QUERY)).trim();
|
|
43
|
+
const withNoise = text(await fat.respond(QUERY)).trim();
|
|
44
|
+
assert.equal(answer, LINKS[1][1], "the fixture's own second link");
|
|
45
|
+
assert.equal(withNoise, answer, "irrelevant supply must not displace it");
|
|
46
|
+
await lean.store.close();
|
|
47
|
+
await fat.store.close();
|
|
48
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// 141 — what the construction owes, the transition that carries it pays.
|
|
2
|
+
//
|
|
3
|
+
// WHAT THIS PINS, measured on the three archetypes the engine has: a query the
|
|
4
|
+
// FUSION answers, a query the WALK extends, and a query the JOIN reaches through
|
|
5
|
+
// a subject the question never wrote.
|
|
6
|
+
//
|
|
7
|
+
// the FUSION pays: the grounding priced a span of the question its answer does
|
|
8
|
+
// not hold (the ALU's surface form is not in its result), so the derivation was
|
|
9
|
+
// born OWING it, and the fused step — offered to the law as a transition —
|
|
10
|
+
// carried a window of it and consumed it. Four bytes, one quantum.
|
|
11
|
+
//
|
|
12
|
+
// the WALK does not: its step follows the answer's own learnt continuation,
|
|
13
|
+
// structure the question never wrote, so it carries nothing to consume.
|
|
14
|
+
//
|
|
15
|
+
// So consumption is not the grounding's alone any more: it belongs to whichever
|
|
16
|
+
// transition CARRIES the owed material, read by the one reading (windowOf), and
|
|
17
|
+
// test/138 keeps a step from consuming what it does not carry.
|
|
18
|
+
|
|
19
|
+
import { test } from "node:test";
|
|
20
|
+
import assert from "node:assert/strict";
|
|
21
|
+
import { Mind } from "../dist/src/index.js";
|
|
22
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
23
|
+
|
|
24
|
+
const CHAIN = [
|
|
25
|
+
["What is the capital of France", "The capital of France is Paris"],
|
|
26
|
+
["Paris", "Paris is famous for the Eiffel Tower"],
|
|
27
|
+
["the Eiffel Tower", "the Eiffel Tower is in Paris"],
|
|
28
|
+
];
|
|
29
|
+
const FUSING = [
|
|
30
|
+
["2+2", "2+2 equals 4"],
|
|
31
|
+
[
|
|
32
|
+
"The tallest tower in Paris",
|
|
33
|
+
"The tallest tower in Paris is the Eiffel Tower",
|
|
34
|
+
],
|
|
35
|
+
];
|
|
36
|
+
const JOIN = [
|
|
37
|
+
["eva", "The director of Eva is Gustaf Molander."],
|
|
38
|
+
["eva director", "The director of Eva is Gustaf Molander."],
|
|
39
|
+
["gustaf molander", "The country of Gustaf Molander is Sweden."],
|
|
40
|
+
["gustaf molander country", "The country of Gustaf Molander is Sweden."],
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
async function counters(corpus, query) {
|
|
44
|
+
const store = new SQliteStore({ path: ":memory:" });
|
|
45
|
+
const mind = new Mind({ seed: 7, store, profile: true });
|
|
46
|
+
await mind.ingest(corpus);
|
|
47
|
+
const out = await mind.respond(query);
|
|
48
|
+
const c = mind.lastCost?.counters ?? {};
|
|
49
|
+
await store.close();
|
|
50
|
+
return {
|
|
51
|
+
text: new TextDecoder().decode(out.bytes).replace(/\0+/g, "").trim(),
|
|
52
|
+
c,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
test("141.1 the fusion answers a query that was already paid for", async () => {
|
|
57
|
+
const { text, c } = await counters(FUSING, "2+2 and the Eiffel Tower");
|
|
58
|
+
assert.ok((c.fuseRuns ?? 0) >= 1, "the fusion ran");
|
|
59
|
+
assert.equal(
|
|
60
|
+
c.remainderBytes ?? 0,
|
|
61
|
+
0,
|
|
62
|
+
"the question was paid at construction",
|
|
63
|
+
);
|
|
64
|
+
assert.ok(
|
|
65
|
+
(c.closureDrainedBytes ?? 0) > 0,
|
|
66
|
+
"the fusion carried a window of what the construction owed, and consumed it",
|
|
67
|
+
);
|
|
68
|
+
assert.match(text, /4/);
|
|
69
|
+
assert.match(text, /Eiffel Tower/);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("141.2 the walk extends over structure the question never wrote", async () => {
|
|
73
|
+
const { text, c } = await counters(
|
|
74
|
+
CHAIN,
|
|
75
|
+
"What is the capital of France famous for",
|
|
76
|
+
);
|
|
77
|
+
assert.ok((c.reasonSteps ?? 0) >= 1, "the walk took a step");
|
|
78
|
+
assert.equal(
|
|
79
|
+
c.closureDrainedBytes ?? 0,
|
|
80
|
+
0,
|
|
81
|
+
"the step carried no question material",
|
|
82
|
+
);
|
|
83
|
+
assert.equal(
|
|
84
|
+
text,
|
|
85
|
+
"Paris is famous for the Eiffel Tower",
|
|
86
|
+
"the chain answer",
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("141.3 the join reaches through a subject the question never wrote", async () => {
|
|
91
|
+
const { text, c } = await counters(JOIN, "eva director country");
|
|
92
|
+
assert.equal(
|
|
93
|
+
c.closureDrainedBytes ?? 0,
|
|
94
|
+
0,
|
|
95
|
+
"the join carries nothing to consume",
|
|
96
|
+
);
|
|
97
|
+
assert.equal(text, "The country of Gustaf Molander is Sweden.");
|
|
98
|
+
});
|