@hviana/sema 0.4.7 → 0.5.0
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 +290 -77
- package/HOW_IT_WORKS.md +2170 -735
- package/dist/example/train_base.d.ts +9 -3
- package/dist/example/train_base.js +21 -4
- package/dist/src/canon.d.ts +19 -0
- package/dist/src/canon.js +28 -0
- package/dist/src/geometry.d.ts +52 -0
- package/dist/src/geometry.js +87 -1
- package/dist/src/mind/bridge.js +27 -1
- package/dist/src/mind/frame-filler.d.ts +15 -0
- package/dist/src/mind/frame-filler.js +535 -0
- package/dist/src/mind/learning.js +6 -11
- package/dist/src/mind/mechanisms/cast.js +72 -2
- package/dist/src/mind/mechanisms/cover.js +6 -1
- package/dist/src/mind/mechanisms/extraction.js +27 -0
- package/dist/src/mind/mechanisms/recall.js +214 -34
- package/dist/src/mind/mind.d.ts +49 -1
- package/dist/src/mind/mind.js +137 -10
- package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
- package/dist/src/mind/pipeline.js +29 -1
- package/dist/src/mind/prefix-completion.d.ts +59 -0
- package/dist/src/mind/prefix-completion.js +270 -0
- package/dist/src/mind/primitives.d.ts +29 -10
- package/dist/src/mind/primitives.js +52 -61
- package/dist/src/mind/recognition.js +119 -9
- package/dist/src/mind/traverse.d.ts +32 -0
- package/dist/src/mind/traverse.js +52 -0
- package/dist/src/mind/types.d.ts +55 -16
- package/dist/src/mind/types.js +68 -19
- package/dist/src/store.d.ts +21 -0
- package/dist/src/store.js +21 -0
- package/example/train_base.ts +21 -4
- package/package.json +1 -1
- package/src/canon.ts +28 -0
- package/src/geometry.ts +100 -1
- package/src/mind/bridge.ts +34 -0
- package/src/mind/frame-filler.ts +604 -0
- package/src/mind/learning.ts +5 -9
- package/src/mind/mechanisms/cast.ts +70 -2
- package/src/mind/mechanisms/cover.ts +6 -1
- package/src/mind/mechanisms/extraction.ts +27 -0
- package/src/mind/mechanisms/recall.ts +236 -37
- package/src/mind/mind.ts +154 -14
- package/src/mind/pipeline-mechanism.ts +7 -0
- package/src/mind/pipeline.ts +33 -1
- package/src/mind/prefix-completion.ts +314 -0
- package/src/mind/primitives.ts +59 -70
- package/src/mind/recognition.ts +117 -6
- package/src/mind/traverse.ts +52 -0
- package/src/mind/types.ts +98 -42
- package/src/store.ts +25 -0
- package/test/13-conversation.test.mjs +13 -0
- package/test/57-fusion-order.test.mjs +65 -0
- package/test/66-query-edge-whitespace.test.mjs +99 -0
- package/test/67-climb-anchor-breadth.test.mjs +113 -0
- package/test/68-extraction-unanchored.test.mjs +79 -0
- package/test/69-frame-filler.test.mjs +115 -0
- package/test/70-prefix-completion.test.mjs +170 -0
- package/test/71-embedded-canon-equivalence.test.mjs +121 -0
- package/test/72-prefix-candidate-supply.test.mjs +114 -0
- package/test/73-scaffolding-only-bridge-abstains.test.mjs +178 -0
- package/test/74-prefix-trap-not-sprung-early.test.mjs +114 -0
- package/test/75-multiturn-context-optimisation.test.mjs +1082 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// 71-embedded-canon-equivalence.test.mjs — an EMBEDDED trained form must be
|
|
2
|
+
// recognised under the response's canonical equivalence, not only byte-exactly.
|
|
3
|
+
//
|
|
4
|
+
// The contract: `recognise` decomposes a query into stored forms that lead
|
|
5
|
+
// somewhere. A trained form sitting at an interior offset of a larger query is
|
|
6
|
+
// found by the scale-gated "exact query-edge forms" tier in recognition.ts.
|
|
7
|
+
// That tier admits candidates with a BYTE-EXACT `store.findBranch` probe over
|
|
8
|
+
// the query's leaf-id run, then calls the canon-capable `resolveSpan`. The
|
|
9
|
+
// prefilter is therefore strictly narrower than its own resolver: a form whose
|
|
10
|
+
// deposit differs from the query only by the response canonicalizer's
|
|
11
|
+
// equivalence (case, width) can never reach `resolveSpan`, because a
|
|
12
|
+
// differently-cased deposit's branch kid-ids are not the query's leaf-id run
|
|
13
|
+
// under ANY canonicalization of the query. Measured on a 15.7M-node store:
|
|
14
|
+
// "Hey, What is the process of photosynthesis?" recognises the trained form,
|
|
15
|
+
// "Hey, what is …" recognises nothing, though the lowercased form resolves
|
|
16
|
+
// exactly at offset 0.
|
|
17
|
+
//
|
|
18
|
+
// WHY THE 4.3k-FACT FIXTURE IS NOT OPTIONAL: the tier runs only when
|
|
19
|
+
// `atomIsHub(ctx, corpusN)` is true, i.e. N > ~4096 contexts at maxGroup=4.
|
|
20
|
+
// Every small-store suite exercises the OTHER branch of recognition, so a
|
|
21
|
+
// conventional fixture would pass while the defect is fully present. The
|
|
22
|
+
// atomIsHub assertion below fails loudly if that crossover ever moves, so this
|
|
23
|
+
// test can never silently stop covering the branch it exists for.
|
|
24
|
+
import { test } from "node:test";
|
|
25
|
+
import assert from "node:assert/strict";
|
|
26
|
+
import { Mind } from "../dist/src/index.js";
|
|
27
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
28
|
+
import { recognise } from "../dist/src/mind/recognition.js";
|
|
29
|
+
import { resolve } from "../dist/src/mind/primitives.js";
|
|
30
|
+
import { atomIsHub, corpusN } from "../dist/src/mind/traverse.js";
|
|
31
|
+
import { textCanon } from "../dist/src/canon.js";
|
|
32
|
+
|
|
33
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
34
|
+
|
|
35
|
+
// Longer than chainReach(W)=W²=16 bytes, or the tier's own size gate skips it.
|
|
36
|
+
const FORM = "Madam Your Glasses Are Fogged";
|
|
37
|
+
const PREFIX = "Hey, ";
|
|
38
|
+
|
|
39
|
+
/** Recognise with the canonicalizer a TEXT response would carry. `ctx.canon`
|
|
40
|
+
* is per-response state that `respond()` injects; calling `recognise`
|
|
41
|
+
* directly would otherwise run with canon disabled and test nothing. */
|
|
42
|
+
function recogniseAsText(mind, text) {
|
|
43
|
+
mind.canon = textCanon;
|
|
44
|
+
mind.canonMemo = new Map();
|
|
45
|
+
try {
|
|
46
|
+
return recognise(mind, enc(text));
|
|
47
|
+
} finally {
|
|
48
|
+
mind.canon = null;
|
|
49
|
+
mind.canonMemo = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const sitesFor = (rec, id) => rec.sites.filter((s) => s.payload === id);
|
|
54
|
+
|
|
55
|
+
test("an embedded trained form is recognised under canonical equivalence", async () => {
|
|
56
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
57
|
+
|
|
58
|
+
const filler = [];
|
|
59
|
+
for (let i = 0; i < 4300; i++) filler.push([`filler-${i}`, `f${i}`]);
|
|
60
|
+
await m.ingest(filler);
|
|
61
|
+
await m.ingest([[FORM, "a stored continuation for the form"]]);
|
|
62
|
+
// Canonical resolution reads a store-side index that training builds
|
|
63
|
+
// explicitly (meta `canon.upto`); without it `canonResolve` has no
|
|
64
|
+
// candidates and the equivalence under test does not exist at all.
|
|
65
|
+
await m.buildCanonIndex();
|
|
66
|
+
|
|
67
|
+
const N = corpusN(m);
|
|
68
|
+
assert.ok(
|
|
69
|
+
atomIsHub(m, N),
|
|
70
|
+
`fixture must cross atomIsHub (N=${N}); the tier under test is ` +
|
|
71
|
+
`scale-gated and this suite would otherwise assert nothing`,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const formId = resolve(m, enc(FORM));
|
|
75
|
+
assert.ok(formId !== null, "the trained form must resolve exactly");
|
|
76
|
+
|
|
77
|
+
// Control 1 — the form standalone, in its deposited case.
|
|
78
|
+
assert.ok(
|
|
79
|
+
sitesFor(recogniseAsText(m, FORM), formId).length > 0,
|
|
80
|
+
"standalone deposited-case form must be recognised",
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Control 2 — the form standalone, lowercased. This is what proves the
|
|
84
|
+
// canonical equivalence is REAL and reachable, so the embedded assertion
|
|
85
|
+
// below is about placement, not about the canonicalizer.
|
|
86
|
+
assert.ok(
|
|
87
|
+
sitesFor(recogniseAsText(m, FORM.toLowerCase()), formId).length > 0,
|
|
88
|
+
"lowercased form must resolve to the same node at offset 0",
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Control 3 — embedded, deposited case. The tier's byte-exact route.
|
|
92
|
+
assert.ok(
|
|
93
|
+
sitesFor(recogniseAsText(m, PREFIX + FORM), formId).length > 0,
|
|
94
|
+
"embedded deposited-case form must be recognised",
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// THE CONTRACT — embedded AND canonically equivalent. Controls 2 and 3 each
|
|
98
|
+
// hold, so anything that fails here is the prefilter being narrower than its
|
|
99
|
+
// resolver, which is the defect this test exists to prevent.
|
|
100
|
+
const rec = recogniseAsText(m, PREFIX + FORM.toLowerCase());
|
|
101
|
+
const hit = sitesFor(rec, formId);
|
|
102
|
+
assert.ok(
|
|
103
|
+
hit.length > 0,
|
|
104
|
+
`embedded lowercased form must be recognised: standalone-lowercased and ` +
|
|
105
|
+
`embedded-exact both are, so the byte-exact admission gate is the only ` +
|
|
106
|
+
`thing rejecting it (sites found: ${
|
|
107
|
+
rec.sites.map((s) => `${s.start}-${s.end}`).join(",") || "none"
|
|
108
|
+
})`,
|
|
109
|
+
);
|
|
110
|
+
// It must be found AT its true offset, not as some other coincidental span.
|
|
111
|
+
assert.ok(
|
|
112
|
+
hit.some((s) =>
|
|
113
|
+
s.start === PREFIX.length && s.end === PREFIX.length + FORM.length
|
|
114
|
+
),
|
|
115
|
+
`the form must be recognised at its own span [${PREFIX.length},${
|
|
116
|
+
PREFIX.length + FORM.length
|
|
117
|
+
}], got ${hit.map((s) => `${s.start}-${s.end}`).join(",")}`,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
await m.store.close();
|
|
121
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// 72-prefix-candidate-supply.test.mjs — a query that is a proper PREFIX of a
|
|
2
|
+
// trained form must be able to reach that form even when resonance cannot rank
|
|
3
|
+
// it.
|
|
4
|
+
//
|
|
5
|
+
// THE GAP THIS PINS. `prefixCompletion` consumes a ranked list the caller
|
|
6
|
+
// already fetched, and that list comes from resonance. Resonance cannot rank a
|
|
7
|
+
// proper prefix: measured on a 15.7M-node store, cos(prefix, form) falls from
|
|
8
|
+
// 0.9629 at a one-byte truncation to 0.6206 at three bytes, against a
|
|
9
|
+
// reachThreshold of 0.8750. The mechanism's guards were therefore never
|
|
10
|
+
// reached — the trace read `candidates: 24, opened: 0` — and the query answered
|
|
11
|
+
// nothing. `prefixCandidates` is the second SUPPLY that closes it, reading the
|
|
12
|
+
// leaf-id WINDOW index `indexSubSpans` already writes at deposit time. No
|
|
13
|
+
// ingestion, storage or fold change is involved: this test would pass on a
|
|
14
|
+
// store trained before the supply existed.
|
|
15
|
+
//
|
|
16
|
+
// WHY THE ASSERTIONS ARE SHAPED THIS WAY. On a small fixture resonance may
|
|
17
|
+
// well return the form by luck, and then an end-to-end "does it answer?" test
|
|
18
|
+
// would pass with the supply deleted — pinning nothing. So the contract is
|
|
19
|
+
// asserted on `prefixCandidates` DIRECTLY, and the resonance list is asserted
|
|
20
|
+
// to lack the form, which is what makes the supply load-bearing rather than
|
|
21
|
+
// redundant.
|
|
22
|
+
//
|
|
23
|
+
// The 4.3k-fact fixture is not decoration: window containment is judged against
|
|
24
|
+
// `hubBound` = √N, so a toy store makes every window look saturated and the
|
|
25
|
+
// supply would correctly return nothing, testing nothing.
|
|
26
|
+
import { test } from "node:test";
|
|
27
|
+
import assert from "node:assert/strict";
|
|
28
|
+
import { Mind } from "../dist/src/index.js";
|
|
29
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
30
|
+
import {
|
|
31
|
+
prefixCandidates,
|
|
32
|
+
prefixCompletion,
|
|
33
|
+
} from "../dist/src/mind/prefix-completion.js";
|
|
34
|
+
import { gistOf, resolve } from "../dist/src/mind/primitives.js";
|
|
35
|
+
|
|
36
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
37
|
+
const dec = new TextDecoder();
|
|
38
|
+
|
|
39
|
+
const FORM = "The chief export of the northern province is powdered basalt.";
|
|
40
|
+
|
|
41
|
+
test("a proper prefix reaches its trained form through the window supply", async () => {
|
|
42
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
43
|
+
|
|
44
|
+
const filler = [];
|
|
45
|
+
for (let i = 0; i < 4300; i++) filler.push([`filler-${i}`, `f${i}`]);
|
|
46
|
+
await m.ingest(filler);
|
|
47
|
+
await m.ingest([[FORM, "acknowledged"]]);
|
|
48
|
+
|
|
49
|
+
const formId = resolve(m, enc(FORM));
|
|
50
|
+
assert.ok(formId !== null, "the trained form must resolve exactly");
|
|
51
|
+
|
|
52
|
+
// Truncate by more than one grouping window, so the continuation clears
|
|
53
|
+
// prefixCompletion's sub-quantum guard and the tier can actually fire.
|
|
54
|
+
const W = m.space.maxGroup;
|
|
55
|
+
const query = enc(FORM.slice(0, FORM.length - 3 * W));
|
|
56
|
+
|
|
57
|
+
// Premise 1 — the exact tiers genuinely cannot serve this query.
|
|
58
|
+
assert.equal(
|
|
59
|
+
resolve(m, query),
|
|
60
|
+
null,
|
|
61
|
+
"a proper prefix must have no branch of its own, or the gap is not real",
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Premise 2 — resonance does not supply the form, so anything that works
|
|
65
|
+
// below is the new supply and not the ranked list in disguise.
|
|
66
|
+
// Premise 2 — the ranked list is NOT what is under test. At fixture scale
|
|
67
|
+
// resonance does rank the form (it is the only content among 4,300 fillers
|
|
68
|
+
// that resembles the query), and that is measured, not assumed: the
|
|
69
|
+
// assertion below records it, so if fixture geometry ever changes the reader
|
|
70
|
+
// is told rather than misled. Resonance's real-world inability to rank a
|
|
71
|
+
// prefix is a LARGE-CORPUS property — cos falls to 0.6206 at a three-byte
|
|
72
|
+
// truncation against a 0.8750 bar, on a 15.7M-node store — and cannot be
|
|
73
|
+
// reproduced at this scale. That is why the contract below is asserted on
|
|
74
|
+
// `prefixCandidates` DIRECTLY: deleting or emptying the supply fails this
|
|
75
|
+
// test regardless of what resonance happens to return.
|
|
76
|
+
const ranked = (await m.store.resonate(gistOf(m, query), 64)).map((h) =>
|
|
77
|
+
h.id
|
|
78
|
+
);
|
|
79
|
+
assert.ok(
|
|
80
|
+
ranked.includes(formId),
|
|
81
|
+
"fixture note: at this scale resonance is expected to rank the form; " +
|
|
82
|
+
"if it no longer does, the end-to-end path below became the load-" +
|
|
83
|
+
"bearing assertion and this comment must be revisited",
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// THE CONTRACT — the write side's own window index proposes the form.
|
|
87
|
+
const proposed = prefixCandidates(m, query);
|
|
88
|
+
assert.ok(
|
|
89
|
+
proposed.includes(formId),
|
|
90
|
+
`the window supply must propose the trained form the query opens ` +
|
|
91
|
+
`(proposed ${proposed.length} candidate(s))`,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// …and the mechanism, unchanged, grounds it whole through that supply.
|
|
95
|
+
const completed = prefixCompletion(m, query, proposed);
|
|
96
|
+
assert.ok(completed !== null, "the supplied form must complete the query");
|
|
97
|
+
assert.equal(
|
|
98
|
+
dec.decode(completed.form),
|
|
99
|
+
FORM,
|
|
100
|
+
"a FORM is grounded whole, never a slice cut at the query's end",
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// HONEST DEGRADATION (§2.13). A query with no discriminative window must
|
|
104
|
+
// propose nothing rather than guess — silence is the correct answer, and a
|
|
105
|
+
// supply that widened until it found something would be the real defect.
|
|
106
|
+
const hub = enc("The ");
|
|
107
|
+
assert.equal(
|
|
108
|
+
prefixCompletion(m, hub, prefixCandidates(m, hub)),
|
|
109
|
+
null,
|
|
110
|
+
"a query carrying no discriminative window must stay silent",
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
await m.store.close();
|
|
114
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// 73-scaffolding-only-bridge-abstains.test.mjs — the substitution bridge must
|
|
2
|
+
// ABSTAIN when every literal span it did not substitute is corpus-global
|
|
3
|
+
// scaffolding.
|
|
4
|
+
//
|
|
5
|
+
// THE DEFECT THIS PINS. A bridge grounds through the literal spans it did NOT
|
|
6
|
+
// substitute; those anchors are the whole of its evidence. The anchor scan
|
|
7
|
+
// ranked them by containment but rejected only the ones with ZERO containers,
|
|
8
|
+
// so a query made entirely of scaffolding still bridged — the single
|
|
9
|
+
// substituted span carried the whole semantic load, and the answer was voiced
|
|
10
|
+
// with confidence. Measured on the trained store (hubBound 571): "What is the
|
|
11
|
+
// capital of" has 19 anchors, ALL saturated ("What":572, "hat ":572, "at i":572
|
|
12
|
+
// …), and answered with an unrelated trained context about an integral. That
|
|
13
|
+
// breaks honest silence (§2.13), which is worse than a gap: a gap is visible, a
|
|
14
|
+
// fabrication is not.
|
|
15
|
+
//
|
|
16
|
+
// WHY THIS IS NOT A PROBE-SHAPED PATCH. The gate was falsified against the
|
|
17
|
+
// queries the bridge answers CORRECTLY before it was written, and every one of
|
|
18
|
+
// them has an unsaturated anchor with no near miss: "Who is the author of
|
|
19
|
+
// Hamlet?" → "let?":12, "How do you say 'thank you' in French?" → "y 't":3,
|
|
20
|
+
// "…largest planet…" → "tem?":31, "What is the capital of France?" →
|
|
21
|
+
// "f Fr":114. The honest-silence probes sit on the same side as the correct
|
|
22
|
+
// ones ("Zamu":3), so this gate is not what makes them silent and cannot be
|
|
23
|
+
// credited for them. The separation is categorical, not marginal.
|
|
24
|
+
//
|
|
25
|
+
// WHY A TEST DOUBLE AND NOT A CORPUS. Saturation is a LARGE-CORPUS
|
|
26
|
+
// phenomenon and cannot be manufactured at fixture scale — this was measured,
|
|
27
|
+
// not assumed, across three fixture designs:
|
|
28
|
+
// • one repeated template → content addressing dedups the identical chunks,
|
|
29
|
+
// so its windows have 1–5 containers, not thousands;
|
|
30
|
+
// • template with varied surroundings → the leading windows saturate (67 at
|
|
31
|
+
// bound 66) but interior ones stay at 1, because "is the " is always the
|
|
32
|
+
// same deduped chunk whatever surrounds it;
|
|
33
|
+
// • randomised small vocabulary → most windows saturate, but cross-boundary
|
|
34
|
+
// windows ("fa b":13) cannot, since they touch only the two adjacent chunk
|
|
35
|
+
// types.
|
|
36
|
+
// Every window saturating at once needs the real store's scale AND lexical
|
|
37
|
+
// diversity. So the CONDITION is supplied by a store double and the BEHAVIOUR
|
|
38
|
+
// under it is asserted — the honest way to pin a rule whose trigger a fixture
|
|
39
|
+
// cannot reach. The double inflates containment only; it invents no node, and
|
|
40
|
+
// the padding is a real container id, so nothing downstream reads a fiction.
|
|
41
|
+
import { test } from "node:test";
|
|
42
|
+
import assert from "node:assert/strict";
|
|
43
|
+
import { Mind } from "../dist/src/index.js";
|
|
44
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
45
|
+
import { substitutionBridge } from "../dist/src/mind/bridge.js";
|
|
46
|
+
import { gistOf } from "../dist/src/mind/primitives.js";
|
|
47
|
+
import {
|
|
48
|
+
allWindowsAreScaffolding,
|
|
49
|
+
hubBound,
|
|
50
|
+
} from "../dist/src/mind/traverse.js";
|
|
51
|
+
|
|
52
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
53
|
+
|
|
54
|
+
/** Report every window as corpus-global: the real containers, padded with an
|
|
55
|
+
* id that is genuinely among them, up to past the hub bound. Only `.length`
|
|
56
|
+
* drives the gate, and padding with a real container keeps every other reader
|
|
57
|
+
* truthful. */
|
|
58
|
+
function saturateContainment(mind) {
|
|
59
|
+
const store = mind.store;
|
|
60
|
+
const real = store.containersSlice.bind(store);
|
|
61
|
+
const bound = hubBound(mind);
|
|
62
|
+
store.containersSlice = (child, off, limit) => {
|
|
63
|
+
const got = real(child, off, limit);
|
|
64
|
+
if (got.length === 0) return got;
|
|
65
|
+
const out = got.slice();
|
|
66
|
+
while (out.length < Math.min(limit, bound + 1)) out.push(got[0]);
|
|
67
|
+
return out;
|
|
68
|
+
};
|
|
69
|
+
return () => {
|
|
70
|
+
store.containersSlice = real;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
test("a bridge with only scaffolding anchors stays silent", async () => {
|
|
75
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
76
|
+
|
|
77
|
+
const facts = [];
|
|
78
|
+
for (let i = 0; i < 600; i++) {
|
|
79
|
+
facts.push([
|
|
80
|
+
`case ${i}: what is the qty${i} of item${i}?`,
|
|
81
|
+
`it is ${i * 2}`,
|
|
82
|
+
]);
|
|
83
|
+
}
|
|
84
|
+
await m.ingest(facts);
|
|
85
|
+
|
|
86
|
+
const query = enc("case 7: what is the qty7 of item7?");
|
|
87
|
+
const proposed = async () =>
|
|
88
|
+
(await m.store.resonate(gistOf(m, query), 256)).map((h) => h.id);
|
|
89
|
+
|
|
90
|
+
// CONTROL — with real containment the query HAS discriminating anchors, so
|
|
91
|
+
// the gate must not apply. Establishing this first is what makes the
|
|
92
|
+
// assertion below about saturation rather than about this query being
|
|
93
|
+
// unbridgeable for some unrelated reason.
|
|
94
|
+
const ordinary = await substitutionBridge(m, query, proposed);
|
|
95
|
+
|
|
96
|
+
// THE CONTRACT — the identical query, with every anchor now corpus-global.
|
|
97
|
+
// Nothing corroborates a substitution, so the only honest result is none.
|
|
98
|
+
const restore = saturateContainment(m);
|
|
99
|
+
try {
|
|
100
|
+
const bridged = await substitutionBridge(m, query, proposed);
|
|
101
|
+
assert.equal(
|
|
102
|
+
bridged,
|
|
103
|
+
null,
|
|
104
|
+
"a bridge whose every anchor is corpus-global scaffolding must abstain",
|
|
105
|
+
);
|
|
106
|
+
} finally {
|
|
107
|
+
restore();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// …and the gate is SELECTIVE: it changed the outcome only because of
|
|
111
|
+
// saturation. If the bridge declined this query anyway, the assertion above
|
|
112
|
+
// would be vacuous, and this is what says it is not.
|
|
113
|
+
assert.notEqual(
|
|
114
|
+
ordinary,
|
|
115
|
+
null,
|
|
116
|
+
"fixture invalid: the bridge declines this query even with real " +
|
|
117
|
+
"containment, so the saturated assertion above pins nothing",
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
await m.store.close();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// The predicate itself, and its SECOND consumer. The scaffolding-dominated
|
|
124
|
+
// tier in mechanisms/recall.ts grounds the consensus-climb anchor, and it
|
|
125
|
+
// fabricated for the same reason the bridge did: measured on the trained
|
|
126
|
+
// store, "What is the capital " answered "Colombo is the commercial capital of
|
|
127
|
+
// Sri Lanka…" on breadth 0.667 / clusters 1, every window a hub ("What":572).
|
|
128
|
+
//
|
|
129
|
+
// DISPERSION WAS TRIED THERE FIRST AND FALSIFIED — recorded here because the
|
|
130
|
+
// falsification is the reason this predicate is shared rather than local: the
|
|
131
|
+
// fabrication and the no-punctuation robustness probe "what is the capital of
|
|
132
|
+
// france" have the IDENTICAL profile (breadth 0.667, clusters 1), so requiring
|
|
133
|
+
// clusters >= 2 silenced the good probe too and cost the battery a probe.
|
|
134
|
+
// Window saturation separates them cleanly where dispersion cannot
|
|
135
|
+
// ("f fr":248 vs all-saturated).
|
|
136
|
+
test("scaffolding-only is a property of the query, not of one mechanism", async () => {
|
|
137
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
138
|
+
|
|
139
|
+
const facts = [];
|
|
140
|
+
for (let i = 0; i < 600; i++) {
|
|
141
|
+
facts.push([
|
|
142
|
+
`case ${i}: what is the qty${i} of item${i}?`,
|
|
143
|
+
`it is ${i * 2}`,
|
|
144
|
+
]);
|
|
145
|
+
}
|
|
146
|
+
await m.ingest(facts);
|
|
147
|
+
|
|
148
|
+
const query = enc("case 7: what is the qty7 of item7?");
|
|
149
|
+
|
|
150
|
+
// With real containment the query HAS discriminating windows.
|
|
151
|
+
assert.equal(
|
|
152
|
+
allWindowsAreScaffolding(m, query),
|
|
153
|
+
false,
|
|
154
|
+
"a query with a discriminating window must not read as scaffolding-only",
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// With every window corpus-global it does not.
|
|
158
|
+
const restore = saturateContainment(m);
|
|
159
|
+
try {
|
|
160
|
+
assert.equal(
|
|
161
|
+
allWindowsAreScaffolding(m, query),
|
|
162
|
+
true,
|
|
163
|
+
"a query whose every stored window is a hub must read as scaffolding-only",
|
|
164
|
+
);
|
|
165
|
+
} finally {
|
|
166
|
+
restore();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// A query the store has never seen has no evidence EITHER WAY, and must not
|
|
170
|
+
// be mistaken for scaffolding — its callers refuse it on their own terms.
|
|
171
|
+
assert.equal(
|
|
172
|
+
allWindowsAreScaffolding(m, enc("zzqx vvwy jjkl")),
|
|
173
|
+
false,
|
|
174
|
+
"a query with no stored window at all is not scaffolding-only",
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
await m.store.close();
|
|
178
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// 74-prefix-trap-not-sprung-early.test.mjs — a query that is the OPENING of
|
|
2
|
+
// many trained forms must stay silent, at BOTH points where that decision can
|
|
3
|
+
// be made wrongly.
|
|
4
|
+
//
|
|
5
|
+
// THE SHAPE. A corpus of templated facts ("what is the value of <i>?") holds
|
|
6
|
+
// thousands of forms sharing one long opening. Asked the opening alone, the
|
|
7
|
+
// corpus does not say which one is meant, so the only honest answer is none.
|
|
8
|
+
// Two independent defects each voiced one anyway, and each is pinned here by a
|
|
9
|
+
// fixture the OTHER fix cannot rescue.
|
|
10
|
+
//
|
|
11
|
+
// 1. THE IDENTITY BRIDGE SPRANG THE PREFIX TRAP ITSELF. With zero
|
|
12
|
+
// substitutions the bridge claims "a trained context IS this query, up to
|
|
13
|
+
// filler". When the query is a strict byte PREFIX of that context, the
|
|
14
|
+
// dismissed tail is exactly the discriminating part, and grounding through
|
|
15
|
+
// it asserts a specification the asker never made. It also PREEMPTED
|
|
16
|
+
// prefixCompletion, which runs later and owns this decision. Fixture 1
|
|
17
|
+
// keeps every continuation at or above one grouping window, so guard 2b
|
|
18
|
+
// below cannot fire and only the deferral can produce silence.
|
|
19
|
+
//
|
|
20
|
+
// 2. A SUB-QUANTUM CONTINUATION WAS DROPPED FROM THE UNIQUENESS TALLY.
|
|
21
|
+
// prefixCompletion refuses to VOICE a below-window continuation, but it
|
|
22
|
+
// also removed those candidates before counting, turning "many readings,
|
|
23
|
+
// most unvoiceable" into "exactly one" — guard 3 then passed vacuously.
|
|
24
|
+
// Fixture 2 makes the competing continuations sub-quantum and the survivor
|
|
25
|
+
// voiceable, so ONLY guard 2b can produce silence.
|
|
26
|
+
//
|
|
27
|
+
// Both fixtures were measured before the fixes: fixture 1 answered "the value
|
|
28
|
+
// of 0 is 0", fixture 2 answered "the value of 10 is 20" — each an arbitrary
|
|
29
|
+
// pick from thousands of equally-good readings.
|
|
30
|
+
import { test } from "node:test";
|
|
31
|
+
import assert from "node:assert/strict";
|
|
32
|
+
import { Mind } from "../dist/src/index.js";
|
|
33
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
34
|
+
import { prefixCompletion } from "../dist/src/mind/prefix-completion.js";
|
|
35
|
+
import { resolve } from "../dist/src/mind/primitives.js";
|
|
36
|
+
|
|
37
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
38
|
+
|
|
39
|
+
const dec = new TextDecoder();
|
|
40
|
+
const say = async (m, q) => {
|
|
41
|
+
const r = await m.respond(q);
|
|
42
|
+
return dec.decode((r?.bytes ?? new Uint8Array()).filter((b) => b !== 0));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
test("sub-quantum continuations still count as competing readings", async () => {
|
|
46
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
47
|
+
|
|
48
|
+
// Continuations are " 0?", " 4?", " 8?" (3 bytes, sub-quantum at W=4) and
|
|
49
|
+
// " 10?" (4 bytes, voiceable). Dropping the short ones leaves exactly one
|
|
50
|
+
// survivor — the vacuous uniqueness this test exists to prevent.
|
|
51
|
+
const facts = [];
|
|
52
|
+
for (let i = 0; i < 4300; i++) {
|
|
53
|
+
facts.push([`what is the value of ${i}?`, `the value of ${i} is ${i * 2}`]);
|
|
54
|
+
}
|
|
55
|
+
await m.ingest(facts);
|
|
56
|
+
|
|
57
|
+
const answer = await say(m, "what is the value of");
|
|
58
|
+
assert.equal(
|
|
59
|
+
answer,
|
|
60
|
+
"",
|
|
61
|
+
`forms opening with this query continue below one grouping window — ` +
|
|
62
|
+
`they are competing readings, not absent ones; got ${
|
|
63
|
+
JSON.stringify(answer.slice(0, 60))
|
|
64
|
+
}`,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
await m.store.close();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// GUARD 2b IN ISOLATION. The end-to-end test above needs BOTH fixes (with the
|
|
71
|
+
// identity-bridge deferral disabled it fabricates before prefixCompletion is
|
|
72
|
+
// ever consulted), so it cannot attribute the silence to guard 2b alone. This
|
|
73
|
+
// one calls the mechanism directly with a candidate list of exactly the shape
|
|
74
|
+
// that fooled it: several forms continuing below one grouping window and ONE
|
|
75
|
+
// continuing above it. Dropping the short ones leaves a lone survivor and
|
|
76
|
+
// guard 3 passes vacuously — the defect — so a mechanism that answers here is
|
|
77
|
+
// counting evidence it discarded.
|
|
78
|
+
//
|
|
79
|
+
// Isolating the DEFERRAL the same way was attempted and is not achievable
|
|
80
|
+
// end-to-end: in every fixture whose continuations are all voiceable the
|
|
81
|
+
// bridge does not ground at all, so the deferral is unreachable and such a
|
|
82
|
+
// test asserts nothing. It is pinned by the test above instead.
|
|
83
|
+
test("prefixCompletion refuses when dropped candidates were the disagreement", async () => {
|
|
84
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
85
|
+
|
|
86
|
+
// W = 4. " 0?"/" 4?"/" 8?" are 3 bytes — sub-quantum; " 10?" is 4.
|
|
87
|
+
await m.ingest([
|
|
88
|
+
["what is the value of 0?", "zero"],
|
|
89
|
+
["what is the value of 4?", "four"],
|
|
90
|
+
["what is the value of 8?", "eight"],
|
|
91
|
+
["what is the value of 10?", "ten"],
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
const query = enc("what is the value of");
|
|
95
|
+
const ranked = ["0?", "4?", "8?", "10?"].map((v) =>
|
|
96
|
+
m.store.findLeaf(enc(`what is the value of ${v}`)) ??
|
|
97
|
+
resolveForm(m, `what is the value of ${v}`)
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const hit = prefixCompletion(m, query, ranked.filter((x) => x !== null));
|
|
101
|
+
assert.equal(
|
|
102
|
+
hit,
|
|
103
|
+
null,
|
|
104
|
+
`three forms open with this query and continue sub-quantum while one ` +
|
|
105
|
+
`continues perceivably; the corpus offers competing readings, so no ` +
|
|
106
|
+
`completion is licensed`,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
await m.store.close();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
function resolveForm(mind, text) {
|
|
113
|
+
return resolve(mind, enc(text));
|
|
114
|
+
}
|