@hviana/sema 0.7.7 → 0.7.9
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/dist/src/mind/graph-search.d.ts +30 -22
- package/dist/src/mind/graph-search.js +38 -23
- package/dist/src/mind/mechanisms/alu.js +10 -8
- package/dist/src/mind/mind.d.ts +5 -0
- package/dist/src/mind/mind.js +11 -3
- package/dist/src/mind/pipeline-mechanism.d.ts +7 -1
- package/dist/src/mind/recognition.js +21 -19
- package/dist/src/mind/trace.js +1 -1
- package/dist/src/mind/types.d.ts +6 -0
- package/docs/mechanisms/alu.md +8 -2
- package/docs/mechanisms/cast.md +5 -0
- package/docs/mechanisms/confluence.md +7 -0
- package/docs/mechanisms/cover.md +5 -2
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/mind/graph-search.ts +47 -32
- package/src/mind/mechanisms/alu.ts +10 -8
- package/src/mind/mind.ts +12 -2
- package/src/mind/pipeline-mechanism.ts +7 -1
- package/src/mind/recognition.ts +21 -19
- package/src/mind/trace.ts +2 -2
- package/src/mind/types.ts +6 -0
- package/test/97-store-seed.test.mjs +3 -2
- package/test/99-fact-join.test.mjs +16 -14
|
@@ -73,12 +73,14 @@ export type GItem = {
|
|
|
73
73
|
* derivation actually CHOSE. Part of {@link key}, because it decides
|
|
74
74
|
* whether the span's final bytes may still change. */
|
|
75
75
|
fix?: boolean;
|
|
76
|
-
/** Set on the out
|
|
77
|
-
* (the subject the query never named) combined with the query's
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
|
|
76
|
+
/** Set on the out DERIVE-THROUGH produced: a produced fact's own contained
|
|
77
|
+
* entity (the subject the query never named) combined with the query's tail
|
|
78
|
+
* named a learned key, and that key's continuation is this out. Part of
|
|
79
|
+
* {@link key} so the derived reading is a distinct chart item from the plain
|
|
80
|
+
* concatenation of the same bytes. Named for what it does — derive THROUGH
|
|
81
|
+
* a produced fact — because `join` is already the confluence mechanism's
|
|
82
|
+
* `Provenance`, a different act at a different layer. */
|
|
83
|
+
throughFact?: boolean;
|
|
82
84
|
};
|
|
83
85
|
export declare const STEP = 1;
|
|
84
86
|
export declare const CONCEPT = 10;
|
|
@@ -142,7 +144,7 @@ export interface DerivationItem {
|
|
|
142
144
|
* {@link GraphSearch}'s rules fired, recovered from the rule's premise/
|
|
143
145
|
* conclusion shape (the rules carry no label, so this classifies by structure,
|
|
144
146
|
* the single place that maps rule geometry to a name). */
|
|
145
|
-
export type DerivationMove = "axiom" | "follow-edge" | "concept-hop" | "voice" | "ground" | "splice-connector" | "split" | "fuse" | "recompose" | "
|
|
147
|
+
export type DerivationMove = "axiom" | "follow-edge" | "concept-hop" | "voice" | "ground" | "splice-connector" | "split" | "fuse" | "recompose" | "derive-through" | "bridge" | "pool-vote" | "step";
|
|
146
148
|
/** The lightest-derivation search over the Sema graph. One instance binds the
|
|
147
149
|
* store, `maxGroup` (the fusible span ceiling), and the canonical
|
|
148
150
|
* {@link resolve} callback; {@link cover} then solves one query. */
|
|
@@ -186,11 +188,12 @@ export declare class GraphSearch {
|
|
|
186
188
|
* connector rule (see {@link outRules}), so the returned spans already carry
|
|
187
189
|
* it — there is no post-pass. */
|
|
188
190
|
cover(queryLen: number, sites: ReadonlyArray<Site>, conceptTarget: ReadonlyMap<number, number>, leaves: ReadonlyArray<Leaf>, splits: ReadonlySet<number>, starts: ReadonlySet<number>, substitutions?: ReadonlyMap<number, Uint8Array>, connectors?: ReadonlyMap<string, Uint8Array>, computedResults?: ReadonlyArray<ComputedResult>,
|
|
189
|
-
/** When given, receives
|
|
190
|
-
*
|
|
191
|
-
* cover
|
|
192
|
-
*
|
|
193
|
-
*
|
|
191
|
+
/** When given, receives each solved span's lightest derivation — the full
|
|
192
|
+
* adapted A*LD proof tree as classified {@link DerivationStep}s — for the
|
|
193
|
+
* TOP cover AND every nested completion the sink is threaded into (see
|
|
194
|
+
* {@link recompleteNode}), so a produced form's own recompositions reach
|
|
195
|
+
* the rationale instead of stopping at the first layer. Off by default, so
|
|
196
|
+
* the search pays nothing when no one inspects. */
|
|
194
197
|
onDerivation?: (steps: DerivationStep[]) => void): {
|
|
195
198
|
segs: Seg[];
|
|
196
199
|
cost: number;
|
|
@@ -318,25 +321,30 @@ export declare class GraphSearch {
|
|
|
318
321
|
* most once per cover. A Set, not a flag, because it states WHICH node is
|
|
319
322
|
* open — the invariant a reader needs to check the guard. */
|
|
320
323
|
private recompleteOpen;
|
|
321
|
-
/**
|
|
322
|
-
*
|
|
324
|
+
/** DERIVE-THROUGH — a RULE this module's DeductionSystem was missing. The
|
|
325
|
+
* A*LD library is untouched: this is one more `premises → conclusion + cost`
|
|
326
|
+
* rule in the system {@link buildSearch} hands to {@link lightestDerivation},
|
|
327
|
+
* the same kind of rule as `fuse`/`recompose` — not an extension of
|
|
328
|
+
* `src/derive`, and not a grounding mechanism. It derives the answer THROUGH
|
|
329
|
+
* a produced fact, without the intermediate key being named in the query.
|
|
323
330
|
*
|
|
324
331
|
* A produced fact (`fact.node`) carries the subject the query reached but
|
|
325
332
|
* never wrote; the query's remaining tail names the relation to follow from
|
|
326
333
|
* it. The pair IS a learned key — `"<entity><tail>"` — so the rule asks the
|
|
327
334
|
* store for that key's continuation and, when it exists, concludes with the
|
|
328
|
-
*
|
|
329
|
-
* following a literal continuation is. Deterministic and
|
|
330
|
-
* (`resolve` + `nextFirst`, no scan), so it adds no read that
|
|
331
|
-
* corpus. The move is visible in the rationale as its own act
|
|
332
|
-
* (`classifyMove` reports `
|
|
333
|
-
* `fuse`/`splice
|
|
334
|
-
|
|
335
|
+
* fact reached through it. On the ladder it is one STEP: a direct edge,
|
|
336
|
+
* exactly as following a literal continuation is. Deterministic and
|
|
337
|
+
* point-probed (`resolve` + `nextFirst`, no scan), so it adds no read that
|
|
338
|
+
* grows with the corpus. The move is visible in the rationale as its own act
|
|
339
|
+
* (`classifyMove` reports `derive-through`), distinct from the
|
|
340
|
+
* byte-concatenating `fuse`/`splice` — and named `derive-through` rather than
|
|
341
|
+
* `join` so it cannot be read as the confluence mechanism's `Provenance`. */
|
|
342
|
+
private deriveThrough;
|
|
335
343
|
/** out(i,j,bytes,…): index it for the binary rules, then offer splicing a
|
|
336
344
|
* learnt connector (the in-search bridge), splitting (at a sub-leaf form
|
|
337
345
|
* boundary), bridging (cover(i) ∧ this → cover(j)), fusing with an adjacent
|
|
338
346
|
* finalised out, and — for a produced fact — JOINING the entity it contains
|
|
339
|
-
* with the query's tail ({@link
|
|
347
|
+
* with the query's tail ({@link deriveThrough}). */
|
|
340
348
|
private outRules;
|
|
341
349
|
/** Whether the query span [from, to) is wholly covered by RECOGNISED outs —
|
|
342
350
|
* the test that lets a connector jump across INTERIOR answers (an N-ary whole)
|
|
@@ -104,7 +104,7 @@ function classifyMove(premises, conclusion, articulating) {
|
|
|
104
104
|
if (p.kind === "out" && conclusion.kind === "out") {
|
|
105
105
|
// A JOIN derives through a produced fact's own contained subject; a plain
|
|
106
106
|
// single-premise out→out is the byte-level split.
|
|
107
|
-
return conclusion.
|
|
107
|
+
return conclusion.throughFact ? "derive-through" : "split";
|
|
108
108
|
}
|
|
109
109
|
return "step";
|
|
110
110
|
}
|
|
@@ -222,11 +222,12 @@ export class GraphSearch {
|
|
|
222
222
|
* connector rule (see {@link outRules}), so the returned spans already carry
|
|
223
223
|
* it — there is no post-pass. */
|
|
224
224
|
cover(queryLen, sites, conceptTarget, leaves, splits, starts, substitutions, connectors, computedResults,
|
|
225
|
-
/** When given, receives
|
|
226
|
-
*
|
|
227
|
-
* cover
|
|
228
|
-
*
|
|
229
|
-
*
|
|
225
|
+
/** When given, receives each solved span's lightest derivation — the full
|
|
226
|
+
* adapted A*LD proof tree as classified {@link DerivationStep}s — for the
|
|
227
|
+
* TOP cover AND every nested completion the sink is threaded into (see
|
|
228
|
+
* {@link recompleteNode}), so a produced form's own recompositions reach
|
|
229
|
+
* the rationale instead of stopping at the first layer. Off by default, so
|
|
230
|
+
* the search pays nothing when no one inspects. */
|
|
230
231
|
onDerivation) {
|
|
231
232
|
// Top-level entry: reset the per-call recursion state, then run the one
|
|
232
233
|
// {@link solve} routine that both the query and any produced composite go
|
|
@@ -367,7 +368,7 @@ export class GraphSearch {
|
|
|
367
368
|
if (it.kind === "form") {
|
|
368
369
|
return `f${it.i}.${it.j}.${it.node}.${it.via ? 1 : 0}.${it.rcmp ? 1 : 0}`;
|
|
369
370
|
}
|
|
370
|
-
return `o${it.i}.${it.j}.${it.cover ? 1 : 0}.${it.rec ? 1 : 0}.${it.fix ? 1 : 0}.${it.
|
|
371
|
+
return `o${it.i}.${it.j}.${it.cover ? 1 : 0}.${it.rec ? 1 : 0}.${it.fix ? 1 : 0}.${it.throughFact ? 1 : 0}.${it.node ?? -1}.${latin1(it.bytes)}`;
|
|
371
372
|
},
|
|
372
373
|
*axioms() {
|
|
373
374
|
yield { item: { kind: "cover", p: 0 }, cost: 0 };
|
|
@@ -866,20 +867,25 @@ export class GraphSearch {
|
|
|
866
867
|
* most once per cover. A Set, not a flag, because it states WHICH node is
|
|
867
868
|
* open — the invariant a reader needs to check the guard. */
|
|
868
869
|
recompleteOpen = new Set();
|
|
869
|
-
/**
|
|
870
|
-
*
|
|
870
|
+
/** DERIVE-THROUGH — a RULE this module's DeductionSystem was missing. The
|
|
871
|
+
* A*LD library is untouched: this is one more `premises → conclusion + cost`
|
|
872
|
+
* rule in the system {@link buildSearch} hands to {@link lightestDerivation},
|
|
873
|
+
* the same kind of rule as `fuse`/`recompose` — not an extension of
|
|
874
|
+
* `src/derive`, and not a grounding mechanism. It derives the answer THROUGH
|
|
875
|
+
* a produced fact, without the intermediate key being named in the query.
|
|
871
876
|
*
|
|
872
877
|
* A produced fact (`fact.node`) carries the subject the query reached but
|
|
873
878
|
* never wrote; the query's remaining tail names the relation to follow from
|
|
874
879
|
* it. The pair IS a learned key — `"<entity><tail>"` — so the rule asks the
|
|
875
880
|
* store for that key's continuation and, when it exists, concludes with the
|
|
876
|
-
*
|
|
877
|
-
* following a literal continuation is. Deterministic and
|
|
878
|
-
* (`resolve` + `nextFirst`, no scan), so it adds no read that
|
|
879
|
-
* corpus. The move is visible in the rationale as its own act
|
|
880
|
-
* (`classifyMove` reports `
|
|
881
|
-
* `fuse`/`splice
|
|
882
|
-
|
|
881
|
+
* fact reached through it. On the ladder it is one STEP: a direct edge,
|
|
882
|
+
* exactly as following a literal continuation is. Deterministic and
|
|
883
|
+
* point-probed (`resolve` + `nextFirst`, no scan), so it adds no read that
|
|
884
|
+
* grows with the corpus. The move is visible in the rationale as its own act
|
|
885
|
+
* (`classifyMove` reports `derive-through`), distinct from the
|
|
886
|
+
* byte-concatenating `fuse`/`splice` — and named `derive-through` rather than
|
|
887
|
+
* `join` so it cannot be read as the confluence mechanism's `Provenance`. */
|
|
888
|
+
*deriveThrough(fact, queryBytes, queryLen) {
|
|
883
889
|
if (!this.host.recogniseSpan)
|
|
884
890
|
return;
|
|
885
891
|
const tail = queryBytes.subarray(fact.j, queryLen);
|
|
@@ -892,8 +898,16 @@ export class GraphSearch {
|
|
|
892
898
|
for (const site of this.host.recogniseSpan(fact.bytes).sites) {
|
|
893
899
|
if (site.payload < 0 || site.payload === fact.node)
|
|
894
900
|
continue;
|
|
895
|
-
|
|
896
|
-
|
|
901
|
+
// The admission predicate has ONE definition — `traverse.ts`'s
|
|
902
|
+
// `leadsSomewhere` (edge or halo). The host LENDS it when it can (Mind
|
|
903
|
+
// does, with the response-scoped struct cache); a bare host falls back to
|
|
904
|
+
// the raw-store probe, so the search stays host-based. Same semantics
|
|
905
|
+
// either way; the hook spares the repeated probe and keeps the predicate
|
|
906
|
+
// from being redefined here.
|
|
907
|
+
const leads = this.host.leadsSomewhere !== undefined
|
|
908
|
+
? this.host.leadsSomewhere(site.payload)
|
|
909
|
+
: this.store.hasNext(site.payload) || this.store.hasHalo(site.payload);
|
|
910
|
+
if (!leads)
|
|
897
911
|
continue;
|
|
898
912
|
const key = this.host.resolve(concat2(this.store.bytesPrefix(site.payload, ALL), tail));
|
|
899
913
|
if (key === null)
|
|
@@ -911,7 +925,7 @@ export class GraphSearch {
|
|
|
911
925
|
cover: true,
|
|
912
926
|
rec: true,
|
|
913
927
|
node: nx[0],
|
|
914
|
-
|
|
928
|
+
throughFact: true,
|
|
915
929
|
},
|
|
916
930
|
cost: STEP,
|
|
917
931
|
};
|
|
@@ -921,7 +935,7 @@ export class GraphSearch {
|
|
|
921
935
|
* learnt connector (the in-search bridge), splitting (at a sub-leaf form
|
|
922
936
|
* boundary), bridging (cover(i) ∧ this → cover(j)), fusing with an adjacent
|
|
923
937
|
* finalised out, and — for a produced fact — JOINING the entity it contains
|
|
924
|
-
* with the query's tail ({@link
|
|
938
|
+
* with the query's tail ({@link deriveThrough}). */
|
|
925
939
|
*outRules(it, ctx) {
|
|
926
940
|
const { splits, coversDone, outsByStart, outsByEnd, coverableByStart } = ctx;
|
|
927
941
|
const outsByNode = ctx.outsByNode;
|
|
@@ -1014,16 +1028,17 @@ export class GraphSearch {
|
|
|
1014
1028
|
yield* this.fuse(it, r, ctx);
|
|
1015
1029
|
for (const l of outsByEnd.get(it.i) ?? [])
|
|
1016
1030
|
yield* this.fuse(l, it, ctx);
|
|
1017
|
-
// ──
|
|
1031
|
+
// ── DERIVE-THROUGH (one more rule of the DeductionSystem this module builds)
|
|
1018
1032
|
// A produced fact may CONTAIN the subject the query never named; the query's
|
|
1019
1033
|
// remaining tail then names the relation to follow FROM that subject. The
|
|
1020
1034
|
// pair (contained entity, tail) is itself a learned key, and its
|
|
1021
|
-
// continuation is the derived answer — a genuine
|
|
1035
|
+
// continuation is the derived answer — a genuine relational join, distinct
|
|
1036
|
+
// from the confluence mechanism's `join` PROVENANCE — and not the
|
|
1022
1037
|
// juxtaposition the cover produces when the intermediate key IS named.
|
|
1023
1038
|
// Fired per finalized out with a node, so it is the search's own rule, on
|
|
1024
1039
|
// the ladder, memoised by {@link key}, and bounded by the fact's own length.
|
|
1025
1040
|
if (it.node !== undefined) {
|
|
1026
|
-
yield* this.
|
|
1041
|
+
yield* this.deriveThrough(it, ctx.queryBytes, ctx.queryLen);
|
|
1027
1042
|
}
|
|
1028
1043
|
}
|
|
1029
1044
|
/** Whether the query span [from, to) is wholly covered by RECOGNISED outs —
|
|
@@ -12,14 +12,16 @@ import { unexplainedLabel } from "../rationale.js";
|
|
|
12
12
|
export function aluToMechanism(alu) {
|
|
13
13
|
return {
|
|
14
14
|
name: "alu",
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
15
|
+
// The computation is GROUNDED BY COVER: this adapter's `parse` puts the
|
|
16
|
+
// authoritative span into `pre.computed`, cover masks it, and cover's
|
|
17
|
+
// derivation is what carries the answer out — measured, every computed
|
|
18
|
+
// probe reports provenance `cover`. So the adapter declares the core
|
|
19
|
+
// provenance the answer actually has. The ALU's own act is named where it
|
|
20
|
+
// belongs, in the TRACE (`evalComputation`, emitted by its `parse`), not in
|
|
21
|
+
// the provenance: a mechanism may not invent a label outside the pipeline's
|
|
22
|
+
// `Provenance` vocabulary, because post-grounding gates on that vocabulary
|
|
23
|
+
// (see the `provenance` contract in pipeline-mechanism.ts).
|
|
24
|
+
provenance: "cover",
|
|
23
25
|
parse: (query) => alu.parse(query),
|
|
24
26
|
async floor(_ctx, _query, pre, _worthRunning) {
|
|
25
27
|
return pre.computed.length > 0 ? 0 : null;
|
package/dist/src/mind/mind.d.ts
CHANGED
|
@@ -150,6 +150,11 @@ export declare class Mind implements MindContext {
|
|
|
150
150
|
private _conversations;
|
|
151
151
|
/** Canonical node id of a byte span. Required by GraphSearchHost & MindContext. */
|
|
152
152
|
resolve(bytes: Uint8Array): number | null;
|
|
153
|
+
/** Whether a node leads somewhere — the admission predicate, delegating to
|
|
154
|
+
* `traverse.ts`'s ONE definition (edge or halo, with its response-scoped
|
|
155
|
+
* cache). The search holds a bare Store and cannot reach that cache itself,
|
|
156
|
+
* so it asks through this hook; a bare host keeps its raw-store fallback. */
|
|
157
|
+
leadsSomewhere(id: number): boolean;
|
|
153
158
|
recogniseSpan(bytes: Uint8Array): {
|
|
154
159
|
sites: ReadonlyArray<Site>;
|
|
155
160
|
leaves: ReadonlyArray<Leaf>;
|
package/dist/src/mind/mind.js
CHANGED
|
@@ -20,7 +20,7 @@ import { GraphSearch, } from "./graph-search.js";
|
|
|
20
20
|
import { Alu } from "../alu/src/index.js";
|
|
21
21
|
import { decodeText, Rationale, } from "./rationale.js";
|
|
22
22
|
import { gistOf, inputBytes, perceive as perceiveImpl, perceiveKey, resolve as resolveImpl, } from "./primitives.js";
|
|
23
|
-
import { chooseNext, edgeAncestors as edgeAncestorsFn, invalidateStructuralCaches, } from "./traverse.js";
|
|
23
|
+
import { chooseNext, edgeAncestors as edgeAncestorsFn, invalidateStructuralCaches, leadsSomewhere, } from "./traverse.js";
|
|
24
24
|
import { invalidateJunctionCache } from "./junction.js";
|
|
25
25
|
import { follow } from "./match.js";
|
|
26
26
|
import { recognise, segment } from "./recognition.js";
|
|
@@ -112,6 +112,13 @@ export class Mind {
|
|
|
112
112
|
resolve(bytes) {
|
|
113
113
|
return resolveImpl(this, bytes);
|
|
114
114
|
}
|
|
115
|
+
/** Whether a node leads somewhere — the admission predicate, delegating to
|
|
116
|
+
* `traverse.ts`'s ONE definition (edge or halo, with its response-scoped
|
|
117
|
+
* cache). The search holds a bare Store and cannot reach that cache itself,
|
|
118
|
+
* so it asks through this hook; a bare host keeps its raw-store fallback. */
|
|
119
|
+
leadsSomewhere(id) {
|
|
120
|
+
return leadsSomewhere(this, id);
|
|
121
|
+
}
|
|
115
122
|
// recogniseSpan wraps recognise
|
|
116
123
|
recogniseSpan(bytes) {
|
|
117
124
|
const r = recognise(this, bytes);
|
|
@@ -155,8 +162,9 @@ export class Mind {
|
|
|
155
162
|
// `makeKeyring`, `Space.rand` and the `Alphabet` below, so folding a
|
|
156
163
|
// query under config.ts's default (42) against a store trained with
|
|
157
164
|
// another seed (e.g. 7) lands in a DIFFERENT vector space than the one
|
|
158
|
-
// the artifact's nodes were folded into: recognition and resonance
|
|
159
|
-
//
|
|
165
|
+
// the artifact's nodes were folded into: recognition and resonance read
|
|
166
|
+
// the wrong space, and answers silently diverge — pinned by test/97,
|
|
167
|
+
// where only adoption reproduces the artifact's own answer. An explicit
|
|
160
168
|
// caller seed still wins — this only replaces the unconfigured default.
|
|
161
169
|
if (explicitSeed === undefined && this.store.trainSeed !== null) {
|
|
162
170
|
this.cfg.seed = this.store.trainSeed;
|
|
@@ -185,7 +185,13 @@ export interface MechanismResult {
|
|
|
185
185
|
export interface PipelineMechanism {
|
|
186
186
|
/** Stable identifier for trace/debug. */
|
|
187
187
|
readonly name: string;
|
|
188
|
-
/** Which provenance tag the pipeline attaches to this mechanism's answers.
|
|
188
|
+
/** Which provenance tag the pipeline attaches to this mechanism's answers.
|
|
189
|
+
* DECLARED, not free-form: the pipeline narrows it to its own `Provenance`
|
|
190
|
+
* vocabulary (pipeline.ts), because post-grounding gates on that vocabulary
|
|
191
|
+
* — so an adapter declares one of those values, never a label of its own.
|
|
192
|
+
* The type is `string` only because a mechanism imports nothing from
|
|
193
|
+
* `pipeline.ts` (constraint 1, decoupling); the contract is semantic, and
|
|
194
|
+
* every shipped adapter honours it (see mechanisms/alu.ts). */
|
|
189
195
|
readonly provenance: string;
|
|
190
196
|
/** Parse authoritative spans BEFORE the grounding loop.
|
|
191
197
|
* Only needed by computational mechanisms (e.g. ALU). Results from ALL
|
|
@@ -503,18 +503,18 @@ function recogniseImpl(ctx, bytes) {
|
|
|
503
503
|
const probe = (start, end, canonBudget) => {
|
|
504
504
|
// Any span at least one river window wide is worth a probe. This used
|
|
505
505
|
// to stop at `chainReach(W)` — "the chain already covers anything that
|
|
506
|
-
// short" — and that premise
|
|
507
|
-
// single-byte leaf ids and
|
|
508
|
-
// is null
|
|
509
|
-
//
|
|
510
|
-
//
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
// pairs.
|
|
506
|
+
// short" — and that premise does not hold for every embedded form: the
|
|
507
|
+
// chain grows single-byte leaf ids and SKIPS a prefix whose
|
|
508
|
+
// `findBranch(ids)` is null, so a form the write side stored as a nested
|
|
509
|
+
// tree rather than as a flat leaf-id branch is never `resolveSpan`ned
|
|
510
|
+
// (measured: "Gustaf Molander" embedded in "The director of Eva is
|
|
511
|
+
// Gustaf Molander." gains no branch at any prefix), and its INTERIOR
|
|
512
|
+
// reach is one chunk plus W, which can be shorter than the form. The
|
|
513
|
+
// result was a dead zone: a form shorter than `chainReach` that neither
|
|
514
|
+
// starts on a fold cut nor ends on a node edge was unreachable by either
|
|
515
|
+
// tier — the exact site whose loss `tryChain`'s own note records as "the
|
|
516
|
+
// pivot dies with the site and multi-hop goes silent". The interior
|
|
517
|
+
// pass below spends the same budget on those pairs.
|
|
518
518
|
if (end - start < W)
|
|
519
519
|
return;
|
|
520
520
|
if (flatProbe(start, end) === null) {
|
|
@@ -579,13 +579,15 @@ function recogniseImpl(ctx, bytes) {
|
|
|
579
579
|
if (i < suffixes.length && !spend(suffixes[i], bytes.length))
|
|
580
580
|
break;
|
|
581
581
|
}
|
|
582
|
-
// INTERIOR pairs
|
|
583
|
-
// the dead zone the gate above used to leave: a form that neither
|
|
584
|
-
// on a fold cut nor ends on a node edge is exactly the one neither
|
|
585
|
-
// chain (nested, `findBranch` misses) nor the two edge scans reach.
|
|
586
|
-
//
|
|
587
|
-
// the W²
|
|
588
|
-
//
|
|
582
|
+
// INTERIOR pairs within the same `chainReach(W)` span bound the chain
|
|
583
|
+
// trusts — the dead zone the gate above used to leave: a form that neither
|
|
584
|
+
// starts on a fold cut nor ends on a node edge is exactly the one neither
|
|
585
|
+
// the chain (nested, `findBranch` misses) nor the two edge scans reach.
|
|
586
|
+
// Only pairs whose span lies in [W, chainReach(W)] are PROBED, which is
|
|
587
|
+
// the O(n · W²) work that matters; the enumeration itself is the all-pairs
|
|
588
|
+
// scan, each pair a constant-time span check — and the span bound is what
|
|
589
|
+
// keeps this off the quadratic path the budget note above describes (that
|
|
590
|
+
// one had no span bound at all).
|
|
589
591
|
{
|
|
590
592
|
const reach = chainReach(W);
|
|
591
593
|
for (const end of ordered) {
|
package/dist/src/mind/trace.js
CHANGED
|
@@ -50,7 +50,7 @@ export const MOVE_NOTE = {
|
|
|
50
50
|
"split": "cut a span at a sub-leaf form boundary so a form can be reached",
|
|
51
51
|
"fuse": "fuse adjacent fragments toward a deeper learned form",
|
|
52
52
|
"recompose": "recompose fused parts into a learned whole that leads on",
|
|
53
|
-
"
|
|
53
|
+
"derive-through": "derive the answer through a produced fact's own subject and the query's tail, not alongside it",
|
|
54
54
|
"bridge": "advance the cover frontier across this span",
|
|
55
55
|
"pool-vote": "pool independent regions' evidence for a shared anchor (sum, not shortest path)",
|
|
56
56
|
"axiom": "a seed: a perceived leaf, recognised form, or computed result",
|
package/dist/src/mind/types.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ export interface GraphSearchHost {
|
|
|
35
35
|
starts: ReadonlySet<number>;
|
|
36
36
|
};
|
|
37
37
|
chooseNext?(node: number): number | undefined;
|
|
38
|
+
/** The admission predicate — `traverse.ts`'s `leadsSomewhere`, its ONE
|
|
39
|
+
* definition: does this node bear an edge or a halo? Optional, so a bare
|
|
40
|
+
* host (a raw Store and nothing else) still works; when present, the search
|
|
41
|
+
* uses it rather than re-probing the store, which keeps the predicate
|
|
42
|
+
* single-defined AND memoised on the response-scoped struct cache. */
|
|
43
|
+
leadsSomewhere?(id: number): boolean;
|
|
38
44
|
}
|
|
39
45
|
export interface Recognition {
|
|
40
46
|
/** Forms that can lead somewhere — they have an edge or a halo. */
|
package/docs/mechanisms/alu.md
CHANGED
|
@@ -66,8 +66,14 @@ forms need no host; meaning-based paths do.
|
|
|
66
66
|
|
|
67
67
|
## Provenance
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
`cover`. A computation is grounded by cover: this adapter's `parse` puts the
|
|
70
|
+
authoritative span into `pre.computed`, cover masks it, and cover's derivation
|
|
71
|
+
carries the answer out (measured: 9 of 9 computed probes report `cover` —
|
|
72
|
+
`137*24`, `1000 - 421`, `15 * 7`, `3+3`, `5*5`). The adapter therefore declares
|
|
73
|
+
`cover` — a mechanism may not invent a label outside the pipeline's `Provenance`
|
|
74
|
+
vocabulary, because post-grounding gates on it (see `pipeline-mechanism.ts`).
|
|
75
|
+
The ALU's own act is named in the TRACE (`evalComputation`/`computeExtensions`),
|
|
76
|
+
not in the provenance.
|
|
71
77
|
|
|
72
78
|
## Pins
|
|
73
79
|
|
package/docs/mechanisms/cast.md
CHANGED
|
@@ -58,6 +58,11 @@ Floor is `2·STEP`. Before touching the shared expensive analyses
|
|
|
58
58
|
return the uninvested bound when it already loses. Never compute a shared
|
|
59
59
|
analysis just to discard it.
|
|
60
60
|
|
|
61
|
+
## Provenance
|
|
62
|
+
|
|
63
|
+
`cast` — the answer came from counterfactual transfer (substitution,
|
|
64
|
+
redirection, or analogical comparison), not from a literal continuation.
|
|
65
|
+
|
|
61
66
|
## Pins
|
|
62
67
|
|
|
63
68
|
- **test/17 intelligence** — reordered single-fact must not trigger
|
|
@@ -30,6 +30,13 @@ One currency (`mind/graph-search.ts`): `STEP=1`, `CONCEPT=10`, `PASS=1000`/byte.
|
|
|
30
30
|
`moves = STEP·slots + CONCEPT` (floor `3·STEP`: two constraints + meet). Weight
|
|
31
31
|
`moves + PASS·unaccounted` compared at `STEP` grade (`pipeline.ts:think`).
|
|
32
32
|
|
|
33
|
+
## Provenance
|
|
34
|
+
|
|
35
|
+
`join` — confluence is the mechanism that OWNS this provenance
|
|
36
|
+
(`src/mind/mechanisms/confluence.ts` sets it when the independent evidence
|
|
37
|
+
streams meet at one anchor). It is not cover's: cover reports `cover` for every
|
|
38
|
+
derivation it wins (see `docs/mechanisms/cover.md`).
|
|
39
|
+
|
|
33
40
|
## Pins
|
|
34
41
|
|
|
35
42
|
`test/32-confluence.test.mjs` — two-constraint intersection, order invariance,
|
package/docs/mechanisms/cover.md
CHANGED
|
@@ -45,8 +45,11 @@ by node pair. Bridges (`bridge`) splice connectors between rewrites.
|
|
|
45
45
|
|
|
46
46
|
## Provenance
|
|
47
47
|
|
|
48
|
-
`cover` for
|
|
49
|
-
learned form.
|
|
48
|
+
`cover` for every cover derivation — including the fusion/recomposition steps
|
|
49
|
+
(`fuse`/`recompose`) that name a deeper learned form. (`join` is NOT cover's:
|
|
50
|
+
that provenance belongs to the CONFLUENCE mechanism, which reports it when
|
|
51
|
+
independent evidence streams meet at one anchor — see
|
|
52
|
+
`docs/mechanisms/confluence.md`.)
|
|
50
53
|
|
|
51
54
|
## Pins
|
|
52
55
|
|
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/mind/graph-search.ts
CHANGED
|
@@ -115,12 +115,14 @@ export type GItem =
|
|
|
115
115
|
* derivation actually CHOSE. Part of {@link key}, because it decides
|
|
116
116
|
* whether the span's final bytes may still change. */
|
|
117
117
|
fix?: boolean;
|
|
118
|
-
/** Set on the out
|
|
119
|
-
* (the subject the query never named) combined with the query's
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
|
|
118
|
+
/** Set on the out DERIVE-THROUGH produced: a produced fact's own contained
|
|
119
|
+
* entity (the subject the query never named) combined with the query's tail
|
|
120
|
+
* named a learned key, and that key's continuation is this out. Part of
|
|
121
|
+
* {@link key} so the derived reading is a distinct chart item from the plain
|
|
122
|
+
* concatenation of the same bytes. Named for what it does — derive THROUGH
|
|
123
|
+
* a produced fact — because `join` is already the confluence mechanism's
|
|
124
|
+
* `Provenance`, a different act at a different layer. */
|
|
125
|
+
throughFact?: boolean;
|
|
124
126
|
};
|
|
125
127
|
type OutItem = Extract<GItem, { kind: "out" }>;
|
|
126
128
|
|
|
@@ -246,7 +248,7 @@ export type DerivationMove =
|
|
|
246
248
|
| "split" // out→out cut at a sub-leaf form boundary
|
|
247
249
|
| "fuse" // out+out→out: adjacent fragments recomposed toward a learned form
|
|
248
250
|
| "recompose" // out+out→form: a fused pair that names an edge-bearing node
|
|
249
|
-
| "
|
|
251
|
+
| "derive-through" // out→out: a produced fact's own contained subject + the query's tail names a learned key (derive the answer through the fact)
|
|
250
252
|
| "bridge" // cover+out→cover: the cover frontier advanced across a span
|
|
251
253
|
| "pool-vote" // N premises→conclusion, evidence pooled (combine:"sum" — see derive)
|
|
252
254
|
| "step"; // any other single-premise move (fallback)
|
|
@@ -286,7 +288,7 @@ function classifyMove(
|
|
|
286
288
|
if (p.kind === "out" && conclusion.kind === "out") {
|
|
287
289
|
// A JOIN derives through a produced fact's own contained subject; a plain
|
|
288
290
|
// single-premise out→out is the byte-level split.
|
|
289
|
-
return conclusion.
|
|
291
|
+
return conclusion.throughFact ? "derive-through" : "split";
|
|
290
292
|
}
|
|
291
293
|
return "step";
|
|
292
294
|
}
|
|
@@ -411,11 +413,12 @@ export class GraphSearch {
|
|
|
411
413
|
substitutions?: ReadonlyMap<number, Uint8Array>,
|
|
412
414
|
connectors?: ReadonlyMap<string, Uint8Array>,
|
|
413
415
|
computedResults?: ReadonlyArray<ComputedResult>,
|
|
414
|
-
/** When given, receives
|
|
415
|
-
*
|
|
416
|
-
* cover
|
|
417
|
-
*
|
|
418
|
-
*
|
|
416
|
+
/** When given, receives each solved span's lightest derivation — the full
|
|
417
|
+
* adapted A*LD proof tree as classified {@link DerivationStep}s — for the
|
|
418
|
+
* TOP cover AND every nested completion the sink is threaded into (see
|
|
419
|
+
* {@link recompleteNode}), so a produced form's own recompositions reach
|
|
420
|
+
* the rationale instead of stopping at the first layer. Off by default, so
|
|
421
|
+
* the search pays nothing when no one inspects. */
|
|
419
422
|
onDerivation?: (steps: DerivationStep[]) => void,
|
|
420
423
|
): { segs: Seg[]; cost: number } | null {
|
|
421
424
|
// Top-level entry: reset the per-call recursion state, then run the one
|
|
@@ -607,7 +610,7 @@ export class GraphSearch {
|
|
|
607
610
|
}
|
|
608
611
|
return `o${it.i}.${it.j}.${it.cover ? 1 : 0}.${it.rec ? 1 : 0}.${
|
|
609
612
|
it.fix ? 1 : 0
|
|
610
|
-
}.${it.
|
|
613
|
+
}.${it.throughFact ? 1 : 0}.${it.node ?? -1}.${latin1(it.bytes)}`;
|
|
611
614
|
},
|
|
612
615
|
*axioms() {
|
|
613
616
|
yield { item: { kind: "cover", p: 0 }, cost: 0 };
|
|
@@ -1133,20 +1136,25 @@ export class GraphSearch {
|
|
|
1133
1136
|
* open — the invariant a reader needs to check the guard. */
|
|
1134
1137
|
private recompleteOpen = new Set<number>();
|
|
1135
1138
|
|
|
1136
|
-
/**
|
|
1137
|
-
*
|
|
1139
|
+
/** DERIVE-THROUGH — a RULE this module's DeductionSystem was missing. The
|
|
1140
|
+
* A*LD library is untouched: this is one more `premises → conclusion + cost`
|
|
1141
|
+
* rule in the system {@link buildSearch} hands to {@link lightestDerivation},
|
|
1142
|
+
* the same kind of rule as `fuse`/`recompose` — not an extension of
|
|
1143
|
+
* `src/derive`, and not a grounding mechanism. It derives the answer THROUGH
|
|
1144
|
+
* a produced fact, without the intermediate key being named in the query.
|
|
1138
1145
|
*
|
|
1139
1146
|
* A produced fact (`fact.node`) carries the subject the query reached but
|
|
1140
1147
|
* never wrote; the query's remaining tail names the relation to follow from
|
|
1141
1148
|
* it. The pair IS a learned key — `"<entity><tail>"` — so the rule asks the
|
|
1142
1149
|
* store for that key's continuation and, when it exists, concludes with the
|
|
1143
|
-
*
|
|
1144
|
-
* following a literal continuation is. Deterministic and
|
|
1145
|
-
* (`resolve` + `nextFirst`, no scan), so it adds no read that
|
|
1146
|
-
* corpus. The move is visible in the rationale as its own act
|
|
1147
|
-
* (`classifyMove` reports `
|
|
1148
|
-
* `fuse`/`splice
|
|
1149
|
-
|
|
1150
|
+
* fact reached through it. On the ladder it is one STEP: a direct edge,
|
|
1151
|
+
* exactly as following a literal continuation is. Deterministic and
|
|
1152
|
+
* point-probed (`resolve` + `nextFirst`, no scan), so it adds no read that
|
|
1153
|
+
* grows with the corpus. The move is visible in the rationale as its own act
|
|
1154
|
+
* (`classifyMove` reports `derive-through`), distinct from the
|
|
1155
|
+
* byte-concatenating `fuse`/`splice` — and named `derive-through` rather than
|
|
1156
|
+
* `join` so it cannot be read as the confluence mechanism's `Provenance`. */
|
|
1157
|
+
private *deriveThrough(
|
|
1150
1158
|
fact: OutItem,
|
|
1151
1159
|
queryBytes: Uint8Array,
|
|
1152
1160
|
queryLen: number,
|
|
@@ -1160,10 +1168,16 @@ export class GraphSearch {
|
|
|
1160
1168
|
// itself, not an entity inside it.
|
|
1161
1169
|
for (const site of this.host.recogniseSpan(fact.bytes).sites) {
|
|
1162
1170
|
if (site.payload < 0 || site.payload === fact.node) continue;
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1171
|
+
// The admission predicate has ONE definition — `traverse.ts`'s
|
|
1172
|
+
// `leadsSomewhere` (edge or halo). The host LENDS it when it can (Mind
|
|
1173
|
+
// does, with the response-scoped struct cache); a bare host falls back to
|
|
1174
|
+
// the raw-store probe, so the search stays host-based. Same semantics
|
|
1175
|
+
// either way; the hook spares the repeated probe and keeps the predicate
|
|
1176
|
+
// from being redefined here.
|
|
1177
|
+
const leads = this.host.leadsSomewhere !== undefined
|
|
1178
|
+
? this.host.leadsSomewhere(site.payload)
|
|
1179
|
+
: this.store.hasNext(site.payload) || this.store.hasHalo(site.payload);
|
|
1180
|
+
if (!leads) continue;
|
|
1167
1181
|
const key = this.host.resolve(
|
|
1168
1182
|
concat2(this.store.bytesPrefix(site.payload, ALL), tail),
|
|
1169
1183
|
);
|
|
@@ -1180,7 +1194,7 @@ export class GraphSearch {
|
|
|
1180
1194
|
cover: true,
|
|
1181
1195
|
rec: true,
|
|
1182
1196
|
node: nx[0],
|
|
1183
|
-
|
|
1197
|
+
throughFact: true,
|
|
1184
1198
|
},
|
|
1185
1199
|
cost: STEP,
|
|
1186
1200
|
};
|
|
@@ -1191,7 +1205,7 @@ export class GraphSearch {
|
|
|
1191
1205
|
* learnt connector (the in-search bridge), splitting (at a sub-leaf form
|
|
1192
1206
|
* boundary), bridging (cover(i) ∧ this → cover(j)), fusing with an adjacent
|
|
1193
1207
|
* finalised out, and — for a produced fact — JOINING the entity it contains
|
|
1194
|
-
* with the query's tail ({@link
|
|
1208
|
+
* with the query's tail ({@link deriveThrough}). */
|
|
1195
1209
|
private *outRules(
|
|
1196
1210
|
it: OutItem,
|
|
1197
1211
|
ctx: {
|
|
@@ -1303,16 +1317,17 @@ export class GraphSearch {
|
|
|
1303
1317
|
for (const r of outsByStart.get(it.j) ?? []) yield* this.fuse(it, r, ctx);
|
|
1304
1318
|
for (const l of outsByEnd.get(it.i) ?? []) yield* this.fuse(l, it, ctx);
|
|
1305
1319
|
|
|
1306
|
-
// ──
|
|
1320
|
+
// ── DERIVE-THROUGH (one more rule of the DeductionSystem this module builds)
|
|
1307
1321
|
// A produced fact may CONTAIN the subject the query never named; the query's
|
|
1308
1322
|
// remaining tail then names the relation to follow FROM that subject. The
|
|
1309
1323
|
// pair (contained entity, tail) is itself a learned key, and its
|
|
1310
|
-
// continuation is the derived answer — a genuine
|
|
1324
|
+
// continuation is the derived answer — a genuine relational join, distinct
|
|
1325
|
+
// from the confluence mechanism's `join` PROVENANCE — and not the
|
|
1311
1326
|
// juxtaposition the cover produces when the intermediate key IS named.
|
|
1312
1327
|
// Fired per finalized out with a node, so it is the search's own rule, on
|
|
1313
1328
|
// the ladder, memoised by {@link key}, and bounded by the fact's own length.
|
|
1314
1329
|
if (it.node !== undefined) {
|
|
1315
|
-
yield* this.
|
|
1330
|
+
yield* this.deriveThrough(it, ctx.queryBytes, ctx.queryLen);
|
|
1316
1331
|
}
|
|
1317
1332
|
}
|
|
1318
1333
|
|
|
@@ -16,14 +16,16 @@ import type { PipelineMechanism } from "../pipeline-mechanism.js";
|
|
|
16
16
|
export function aluToMechanism(alu: Alu): PipelineMechanism {
|
|
17
17
|
return {
|
|
18
18
|
name: "alu",
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
19
|
+
// The computation is GROUNDED BY COVER: this adapter's `parse` puts the
|
|
20
|
+
// authoritative span into `pre.computed`, cover masks it, and cover's
|
|
21
|
+
// derivation is what carries the answer out — measured, every computed
|
|
22
|
+
// probe reports provenance `cover`. So the adapter declares the core
|
|
23
|
+
// provenance the answer actually has. The ALU's own act is named where it
|
|
24
|
+
// belongs, in the TRACE (`evalComputation`, emitted by its `parse`), not in
|
|
25
|
+
// the provenance: a mechanism may not invent a label outside the pipeline's
|
|
26
|
+
// `Provenance` vocabulary, because post-grounding gates on that vocabulary
|
|
27
|
+
// (see the `provenance` contract in pipeline-mechanism.ts).
|
|
28
|
+
provenance: "cover",
|
|
27
29
|
parse: (query) => alu.parse(query),
|
|
28
30
|
async floor(_ctx, _query, pre, _worthRunning) {
|
|
29
31
|
return pre.computed.length > 0 ? 0 : null;
|
package/src/mind/mind.ts
CHANGED
|
@@ -159,6 +159,7 @@ import {
|
|
|
159
159
|
chooseNext,
|
|
160
160
|
edgeAncestors as edgeAncestorsFn,
|
|
161
161
|
invalidateStructuralCaches,
|
|
162
|
+
leadsSomewhere,
|
|
162
163
|
} from "./traverse.js";
|
|
163
164
|
import { invalidateJunctionCache } from "./junction.js";
|
|
164
165
|
import { follow } from "./match.js";
|
|
@@ -343,6 +344,14 @@ export class Mind implements MindContext {
|
|
|
343
344
|
return resolveImpl(this, bytes);
|
|
344
345
|
}
|
|
345
346
|
|
|
347
|
+
/** Whether a node leads somewhere — the admission predicate, delegating to
|
|
348
|
+
* `traverse.ts`'s ONE definition (edge or halo, with its response-scoped
|
|
349
|
+
* cache). The search holds a bare Store and cannot reach that cache itself,
|
|
350
|
+
* so it asks through this hook; a bare host keeps its raw-store fallback. */
|
|
351
|
+
leadsSomewhere(id: number): boolean {
|
|
352
|
+
return leadsSomewhere(this, id);
|
|
353
|
+
}
|
|
354
|
+
|
|
346
355
|
// recogniseSpan wraps recognise
|
|
347
356
|
recogniseSpan(bytes: Uint8Array): {
|
|
348
357
|
sites: ReadonlyArray<Site>;
|
|
@@ -410,8 +419,9 @@ export class Mind implements MindContext {
|
|
|
410
419
|
// `makeKeyring`, `Space.rand` and the `Alphabet` below, so folding a
|
|
411
420
|
// query under config.ts's default (42) against a store trained with
|
|
412
421
|
// another seed (e.g. 7) lands in a DIFFERENT vector space than the one
|
|
413
|
-
// the artifact's nodes were folded into: recognition and resonance
|
|
414
|
-
//
|
|
422
|
+
// the artifact's nodes were folded into: recognition and resonance read
|
|
423
|
+
// the wrong space, and answers silently diverge — pinned by test/97,
|
|
424
|
+
// where only adoption reproduces the artifact's own answer. An explicit
|
|
415
425
|
// caller seed still wins — this only replaces the unconfigured default.
|
|
416
426
|
if (explicitSeed === undefined && this.store.trainSeed !== null) {
|
|
417
427
|
this.cfg.seed = this.store.trainSeed;
|
|
@@ -712,7 +712,13 @@ export interface PipelineMechanism {
|
|
|
712
712
|
/** Stable identifier for trace/debug. */
|
|
713
713
|
readonly name: string;
|
|
714
714
|
|
|
715
|
-
/** Which provenance tag the pipeline attaches to this mechanism's answers.
|
|
715
|
+
/** Which provenance tag the pipeline attaches to this mechanism's answers.
|
|
716
|
+
* DECLARED, not free-form: the pipeline narrows it to its own `Provenance`
|
|
717
|
+
* vocabulary (pipeline.ts), because post-grounding gates on that vocabulary
|
|
718
|
+
* — so an adapter declares one of those values, never a label of its own.
|
|
719
|
+
* The type is `string` only because a mechanism imports nothing from
|
|
720
|
+
* `pipeline.ts` (constraint 1, decoupling); the contract is semantic, and
|
|
721
|
+
* every shipped adapter honours it (see mechanisms/alu.ts). */
|
|
716
722
|
readonly provenance: string;
|
|
717
723
|
|
|
718
724
|
/** Parse authoritative spans BEFORE the grounding loop.
|
package/src/mind/recognition.ts
CHANGED
|
@@ -520,18 +520,18 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
|
|
|
520
520
|
): void => {
|
|
521
521
|
// Any span at least one river window wide is worth a probe. This used
|
|
522
522
|
// to stop at `chainReach(W)` — "the chain already covers anything that
|
|
523
|
-
// short" — and that premise
|
|
524
|
-
// single-byte leaf ids and
|
|
525
|
-
// is null
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
// pairs.
|
|
523
|
+
// short" — and that premise does not hold for every embedded form: the
|
|
524
|
+
// chain grows single-byte leaf ids and SKIPS a prefix whose
|
|
525
|
+
// `findBranch(ids)` is null, so a form the write side stored as a nested
|
|
526
|
+
// tree rather than as a flat leaf-id branch is never `resolveSpan`ned
|
|
527
|
+
// (measured: "Gustaf Molander" embedded in "The director of Eva is
|
|
528
|
+
// Gustaf Molander." gains no branch at any prefix), and its INTERIOR
|
|
529
|
+
// reach is one chunk plus W, which can be shorter than the form. The
|
|
530
|
+
// result was a dead zone: a form shorter than `chainReach` that neither
|
|
531
|
+
// starts on a fold cut nor ends on a node edge was unreachable by either
|
|
532
|
+
// tier — the exact site whose loss `tryChain`'s own note records as "the
|
|
533
|
+
// pivot dies with the site and multi-hop goes silent". The interior
|
|
534
|
+
// pass below spends the same budget on those pairs.
|
|
535
535
|
if (end - start < W) return;
|
|
536
536
|
if (flatProbe(start, end) === null) {
|
|
537
537
|
if (!canonBudget) return;
|
|
@@ -589,13 +589,15 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
|
|
|
589
589
|
if (i < prefixes.length && !spend(0, prefixes[i])) break;
|
|
590
590
|
if (i < suffixes.length && !spend(suffixes[i], bytes.length)) break;
|
|
591
591
|
}
|
|
592
|
-
// INTERIOR pairs
|
|
593
|
-
// the dead zone the gate above used to leave: a form that neither
|
|
594
|
-
// on a fold cut nor ends on a node edge is exactly the one neither
|
|
595
|
-
// chain (nested, `findBranch` misses) nor the two edge scans reach.
|
|
596
|
-
//
|
|
597
|
-
// the W²
|
|
598
|
-
//
|
|
592
|
+
// INTERIOR pairs within the same `chainReach(W)` span bound the chain
|
|
593
|
+
// trusts — the dead zone the gate above used to leave: a form that neither
|
|
594
|
+
// starts on a fold cut nor ends on a node edge is exactly the one neither
|
|
595
|
+
// the chain (nested, `findBranch` misses) nor the two edge scans reach.
|
|
596
|
+
// Only pairs whose span lies in [W, chainReach(W)] are PROBED, which is
|
|
597
|
+
// the O(n · W²) work that matters; the enumeration itself is the all-pairs
|
|
598
|
+
// scan, each pair a constant-time span check — and the span bound is what
|
|
599
|
+
// keeps this off the quadratic path the budget note above describes (that
|
|
600
|
+
// one had no span bound at all).
|
|
599
601
|
{
|
|
600
602
|
const reach = chainReach(W);
|
|
601
603
|
for (const end of ordered) {
|
package/src/mind/trace.ts
CHANGED
|
@@ -75,8 +75,8 @@ export const MOVE_NOTE: Record<string, string> = {
|
|
|
75
75
|
"split": "cut a span at a sub-leaf form boundary so a form can be reached",
|
|
76
76
|
"fuse": "fuse adjacent fragments toward a deeper learned form",
|
|
77
77
|
"recompose": "recompose fused parts into a learned whole that leads on",
|
|
78
|
-
"
|
|
79
|
-
"
|
|
78
|
+
"derive-through":
|
|
79
|
+
"derive the answer through a produced fact's own subject and the query's tail, not alongside it",
|
|
80
80
|
"bridge": "advance the cover frontier across this span",
|
|
81
81
|
"pool-vote":
|
|
82
82
|
"pool independent regions' evidence for a shared anchor (sum, not shortest path)",
|
package/src/mind/types.ts
CHANGED
|
@@ -65,6 +65,12 @@ export interface GraphSearchHost {
|
|
|
65
65
|
starts: ReadonlySet<number>;
|
|
66
66
|
};
|
|
67
67
|
chooseNext?(node: number): number | undefined;
|
|
68
|
+
/** The admission predicate — `traverse.ts`'s `leadsSomewhere`, its ONE
|
|
69
|
+
* definition: does this node bear an edge or a halo? Optional, so a bare
|
|
70
|
+
* host (a raw Store and nothing else) still works; when present, the search
|
|
71
|
+
* uses it rather than re-probing the store, which keeps the predicate
|
|
72
|
+
* single-defined AND memoised on the response-scoped struct cache. */
|
|
73
|
+
leadsSomewhere?(id: number): boolean;
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
// authoritative for the artifact. The seed feeds `makeKeyring`, `Space.rand`
|
|
7
7
|
// and the `Alphabet` in the Mind constructor: folding a query under any other
|
|
8
8
|
// seed lands in a DIFFERENT vector space than the one the artifact's nodes were
|
|
9
|
-
// folded into, so recognition and resonance read the wrong space and
|
|
10
|
-
//
|
|
9
|
+
// folded into, so recognition and resonance read the wrong space and answers
|
|
10
|
+
// silently diverge — this file pins that only adoption reproduces the
|
|
11
|
+
// artifact's own answer.
|
|
11
12
|
//
|
|
12
13
|
// The store recovers `train.D` and `geometry.maxGroup` from its own metadata at
|
|
13
14
|
// open; `train.seed` must be recovered the same way, and a Mind that did not
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
// 99-fact-join.test.mjs —
|
|
2
|
-
// whose subject the query never names.
|
|
1
|
+
// 99-fact-join.test.mjs — DERIVE-THROUGH: derive the answer THROUGH a produced
|
|
2
|
+
// fact whose subject the query never names.
|
|
3
3
|
//
|
|
4
4
|
// THE MOVE THIS PINS. A query like "Eiffel Tower country capital" names hop 1's
|
|
5
5
|
// key ("Eiffel Tower country") and hop 2's relation (" capital"), but never the
|
|
6
6
|
// intermediate subject ("France"). The cover alone can only juxtapose the two
|
|
7
7
|
// facts; the pivot needs the subject to surface as an unconsumed site inside the
|
|
8
|
-
// produced fact. The `
|
|
9
|
-
// entity the produced fact CONTAINS, combines it with the query's
|
|
10
|
-
// and asks the store for that key's continuation — a direct STEP
|
|
11
|
-
// reported as `
|
|
8
|
+
// produced fact. The `deriveThrough` rule in graph-search.ts closes the gap: it
|
|
9
|
+
// takes the entity the produced fact CONTAINS, combines it with the query's
|
|
10
|
+
// remaining tail, and asks the store for that key's continuation — a direct STEP
|
|
11
|
+
// on the ladder, reported as `derive-through` in the rationale. (The operation
|
|
12
|
+
// is a relational join; the MOVE is named `derive-through` so it cannot be read
|
|
13
|
+
// as the confluence mechanism's `join` PROVENANCE.)
|
|
12
14
|
//
|
|
13
15
|
// WHY THE FIXTURE CROSSES N=4096. Below the atomIsHub flip the interior subject
|
|
14
16
|
// surfaces anyway (small-store recognition) and the PIVOT alone reaches the
|
|
15
17
|
// chain, so a small fixture passes under the pre-change tree too — measured on a
|
|
16
18
|
// 3-deposit store, which answered "The father of Gustaf Molander is Harald
|
|
17
|
-
// Molander." with no
|
|
19
|
+
// Molander." with no derive-through move. That rule is what carries the chain at corpus scale,
|
|
18
20
|
// so the fixture must sit past the flip, exactly as test/78 does. The filler is
|
|
19
21
|
// lexically varied for the same reason test/78's is: a templated corpus folds to
|
|
20
22
|
// shared chunks and leaves the query uncontested.
|
|
@@ -55,7 +57,7 @@ async function pastTheFlip() {
|
|
|
55
57
|
return { store, mind };
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
test("
|
|
60
|
+
test("derive-through: a produced fact whose subject the query never names", async () => {
|
|
59
61
|
const { store, mind } = await pastTheFlip();
|
|
60
62
|
const query = "Eiffel Tower country capital";
|
|
61
63
|
assert.ok(
|
|
@@ -71,17 +73,17 @@ test("a join derives through a produced fact whose subject the query never names
|
|
|
71
73
|
|
|
72
74
|
assert.equal(out.trim(), "The capital of France is Paris.");
|
|
73
75
|
assert.ok(
|
|
74
|
-
moves.includes("
|
|
75
|
-
`expected
|
|
76
|
+
moves.includes("derive-through"),
|
|
77
|
+
`expected derive-through to be the move that reached the answer, got: ${
|
|
76
78
|
[...new Set(moves)].join(", ")
|
|
77
79
|
}`,
|
|
78
80
|
);
|
|
79
81
|
await store.close();
|
|
80
82
|
});
|
|
81
83
|
|
|
82
|
-
test("naming the intermediate does not need
|
|
84
|
+
test("naming the intermediate does not need derive-through — the cover reads it directly", async () => {
|
|
83
85
|
// The contrast that keeps the move honest: when the subject IS written, the
|
|
84
|
-
// cover already reaches the chain, so no
|
|
86
|
+
// cover already reaches the chain, so no derive-through is claimed.
|
|
85
87
|
const { store, mind } = await pastTheFlip();
|
|
86
88
|
const moves = [];
|
|
87
89
|
const out = await mind.respondText(
|
|
@@ -90,8 +92,8 @@ test("naming the intermediate does not need the join — the cover reads it dire
|
|
|
90
92
|
);
|
|
91
93
|
assert.ok(out.includes("The capital of France is Paris."));
|
|
92
94
|
assert.ok(
|
|
93
|
-
!moves.includes("
|
|
94
|
-
"a named intermediate must not be reported as
|
|
95
|
+
!moves.includes("derive-through"),
|
|
96
|
+
"a named intermediate must not be reported as derive-through",
|
|
95
97
|
);
|
|
96
98
|
await store.close();
|
|
97
99
|
});
|