@hviana/sema 0.8.2 → 0.8.3
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 +29 -29
- package/TRADEMARKS.md +0 -1
- package/dist/src/config.d.ts +11 -0
- package/dist/src/config.js +2 -0
- package/dist/src/geometry.d.ts +21 -0
- package/dist/src/geometry.js +21 -0
- package/dist/src/meter.d.ts +51 -0
- package/dist/src/meter.js +51 -0
- package/dist/src/mind/attention.d.ts +4 -0
- package/dist/src/mind/attention.js +165 -16
- package/dist/src/mind/canonical.d.ts +16 -0
- package/dist/src/mind/canonical.js +41 -0
- package/dist/src/mind/graph-search.js +33 -14
- package/dist/src/mind/match.d.ts +1 -1
- package/dist/src/mind/match.js +5 -3
- package/dist/src/mind/mechanisms/cast.js +1 -1
- package/dist/src/mind/mechanisms/confluence.js +24 -0
- package/dist/src/mind/mechanisms/recall.js +32 -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 -0
- package/dist/src/mind/pipeline.js +41 -14
- package/dist/src/mind/primitives.js +9 -1
- package/dist/src/mind/rationale.d.ts +28 -1
- package/dist/src/mind/rationale.js +22 -1
- package/dist/src/mind/reasoning.d.ts +21 -3
- package/dist/src/mind/reasoning.js +73 -21
- 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/docs/INVARIANTS.md +2 -2
- package/docs/architecture/bounded-reads.md +1 -1
- package/docs/architecture/commonality.md +2 -2
- package/docs/architecture/cost-model.md +2 -2
- package/docs/architecture/determinism.md +7 -7
- package/docs/architecture/match-project.md +2 -3
- package/docs/architecture/mechanism-market.md +10 -10
- package/docs/architecture/meter.md +5 -5
- package/docs/architecture/store.md +3 -3
- package/docs/failures/tempting-but-wrong.md +3 -4
- package/docs/harness/gates.md +2 -2
- package/docs/mechanisms/cast.md +2 -2
- package/docs/mechanisms/cover.md +2 -3
- package/docs/mechanisms/extraction.md +7 -7
- package/docs/mechanisms/recall.md +8 -9
- 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 -0
- package/src/meter.ts +51 -0
- package/src/mind/attention.ts +167 -16
- package/src/mind/canonical.ts +43 -0
- package/src/mind/graph-search.ts +39 -14
- package/src/mind/match.ts +5 -3
- package/src/mind/mechanisms/cast.ts +3 -1
- package/src/mind/mechanisms/confluence.ts +24 -0
- package/src/mind/mechanisms/recall.ts +32 -4
- package/src/mind/mind.ts +6 -4
- package/src/mind/pipeline-mechanism.ts +7 -0
- package/src/mind/pipeline.ts +49 -16
- package/src/mind/primitives.ts +9 -1
- package/src/mind/rationale.ts +35 -1
- package/src/mind/reasoning.ts +92 -15
- 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 +36 -13
- 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/32-confluence.test.mjs +68 -0
- package/test/38-reason-restate-guard.test.mjs +8 -2
- package/test/43-cast-analog-seat.test.mjs +10 -0
- package/test/55-cost-meter.test.mjs +859 -0
|
@@ -23,6 +23,22 @@ export declare function leafIdRun(ctx: MindContext, bytes: Uint8Array, from: num
|
|
|
23
23
|
* what a partial prefix means (deposit only interns the whole-stream flat
|
|
24
24
|
* branch when the prefix covers everything). */
|
|
25
25
|
export declare function leafIdPrefix(ctx: MindContext, bytes: Uint8Array): number[];
|
|
26
|
+
/** Which prefixes of `prefix ‖ tail` are STORED NODES — as lengths in the
|
|
27
|
+
* tail's own coordinates, ascending, excluding the empty one. This is the
|
|
28
|
+
* candidate set a rule needs to join an already-stored prefix to a suffix it
|
|
29
|
+
* has not stored: a key names a relation exactly when `prefix ‖ tail[0..p]` IS
|
|
30
|
+
* a node, and a key can end strictly inside the tail without sitting on any
|
|
31
|
+
* fold boundary (a stored member's end is the end of ITS OWN stream, and the
|
|
32
|
+
* fold never emits a cut at a stream's end). Measured: "stockholm mayor"
|
|
33
|
+
* exists, leads on, and its boundary 6 is in neither the tail's cuts nor the
|
|
34
|
+
* concatenation's.
|
|
35
|
+
*
|
|
36
|
+
* ONE cheap content-addressed probe per offset — `leafIdPrefix` walks the bytes
|
|
37
|
+
* once (a point probe each), `findBranch` hashes the growing kid run — and NO
|
|
38
|
+
* `resolve`, which is what keeps this off the O(suffix) vector folds the
|
|
39
|
+
* recognition path pays. It stops at the first byte that was never interned,
|
|
40
|
+
* which costs nothing real: a stored key's bytes are interned by construction. */
|
|
41
|
+
export declare function keyEnds(ctx: MindContext, prefix: Uint8Array, tail: Uint8Array): number[];
|
|
26
42
|
/** The canonical W-window node ids of a byte stream, offset → id — the
|
|
27
43
|
* CONTENT-ADDRESSED IDENTITY of every W-sized slice, under which any content
|
|
28
44
|
* two deposits share IS the same node (hash-consing paid the comparison at
|
|
@@ -70,6 +70,47 @@ export function leafIdPrefix(ctx, bytes) {
|
|
|
70
70
|
}
|
|
71
71
|
return ids;
|
|
72
72
|
}
|
|
73
|
+
/** Which prefixes of `prefix ‖ tail` are STORED NODES — as lengths in the
|
|
74
|
+
* tail's own coordinates, ascending, excluding the empty one. This is the
|
|
75
|
+
* candidate set a rule needs to join an already-stored prefix to a suffix it
|
|
76
|
+
* has not stored: a key names a relation exactly when `prefix ‖ tail[0..p]` IS
|
|
77
|
+
* a node, and a key can end strictly inside the tail without sitting on any
|
|
78
|
+
* fold boundary (a stored member's end is the end of ITS OWN stream, and the
|
|
79
|
+
* fold never emits a cut at a stream's end). Measured: "stockholm mayor"
|
|
80
|
+
* exists, leads on, and its boundary 6 is in neither the tail's cuts nor the
|
|
81
|
+
* concatenation's.
|
|
82
|
+
*
|
|
83
|
+
* ONE cheap content-addressed probe per offset — `leafIdPrefix` walks the bytes
|
|
84
|
+
* once (a point probe each), `findBranch` hashes the growing kid run — and NO
|
|
85
|
+
* `resolve`, which is what keeps this off the O(suffix) vector folds the
|
|
86
|
+
* recognition path pays. It stops at the first byte that was never interned,
|
|
87
|
+
* which costs nothing real: a stored key's bytes are interned by construction. */
|
|
88
|
+
export function keyEnds(ctx, prefix, tail) {
|
|
89
|
+
if (prefix.length === 0 || tail.length === 0)
|
|
90
|
+
return [];
|
|
91
|
+
const joined = new Uint8Array(prefix.length + tail.length);
|
|
92
|
+
joined.set(prefix, 0);
|
|
93
|
+
joined.set(tail, prefix.length);
|
|
94
|
+
const ids = leafIdPrefix(ctx, joined);
|
|
95
|
+
if (ids.length < prefix.length)
|
|
96
|
+
return [];
|
|
97
|
+
const ends = [];
|
|
98
|
+
// The kid run GROWS by one id per offset; `findBranch` wants an array, so the
|
|
99
|
+
// run is built once and pushed into, never re-sliced. Re-slicing
|
|
100
|
+
// `ids.slice(0, prefix.length + p)` per offset made this O(|tail| ·
|
|
101
|
+
// (|prefix| + |tail|)) — quadratic in the tail, where the learning path this
|
|
102
|
+
// follows slices a run that SHRINKS. Same ends, linear copying.
|
|
103
|
+
const run = ids.slice(0, prefix.length);
|
|
104
|
+
// The loop ENDS at the first byte that was never interned (`ids.length`):
|
|
105
|
+
// every later prefix contains it, so none of them can be a node either — this
|
|
106
|
+
// is where the scan stops, not a silent truncation of the answer.
|
|
107
|
+
for (let p = 1; prefix.length + p <= ids.length; p++) {
|
|
108
|
+
run.push(ids[prefix.length + p - 1]);
|
|
109
|
+
if (ctx.store.findBranch(run) !== null)
|
|
110
|
+
ends.push(p);
|
|
111
|
+
}
|
|
112
|
+
return ends;
|
|
113
|
+
}
|
|
73
114
|
/** The canonical W-window node ids of a byte stream, offset → id — the
|
|
74
115
|
* CONTENT-ADDRESSED IDENTITY of every W-sized slice, under which any content
|
|
75
116
|
* two deposits share IS the same node (hash-consing paid the comparison at
|
|
@@ -868,6 +868,8 @@ export class GraphSearch {
|
|
|
868
868
|
// concepts/connectors either (those need the caller's async
|
|
869
869
|
// pre-resolution) — the recursion follows edges and fusion, which is what
|
|
870
870
|
// a deeper rewrite chain is made of.
|
|
871
|
+
if (this.host.meter)
|
|
872
|
+
this.host.meter.recompletes++;
|
|
871
873
|
const rec = this.host.recogniseSpan(bytes);
|
|
872
874
|
const kids = new Set(nrec.kids);
|
|
873
875
|
// THE NODE'S OWN KIDS ARE SITES BY STRUCTURE — recognition cannot be the
|
|
@@ -1100,20 +1102,37 @@ export class GraphSearch {
|
|
|
1100
1102
|
// duplicate read and a branch that could never be taken.
|
|
1101
1103
|
let next = null;
|
|
1102
1104
|
let keyBytes = c.bytes;
|
|
1103
|
-
// THE
|
|
1104
|
-
//
|
|
1105
|
-
//
|
|
1106
|
-
//
|
|
1107
|
-
//
|
|
1108
|
-
//
|
|
1109
|
-
//
|
|
1110
|
-
//
|
|
1111
|
-
// boundary
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1105
|
+
// THE CANDIDATE ENDS ARE THE PREFIXES THAT ARE STORED NODES, ASCENDING.
|
|
1106
|
+
// The key is `entity + prefix`, and it names a relation exactly when that
|
|
1107
|
+
// concatenation IS a node — so the ends come from a content-addressed
|
|
1108
|
+
// probe per offset (the host's `contentKeyEnds`, the learning path's own
|
|
1109
|
+
// mechanism: one leaf walk plus one `findBranch` per offset, no `resolve`),
|
|
1110
|
+
// never from the fold's boundaries. A boundary is not a proxy: a stored
|
|
1111
|
+
// member's end is the end of ITS OWN stream, and the fold never cuts at a
|
|
1112
|
+
// stream's end — measured, "stockholm mayor" exists, leads on to the mayor
|
|
1113
|
+
// fact, and its boundary 6 sits in neither the tail's cuts ([4,7]) nor the
|
|
1114
|
+
// concatenation's. Filtering the scan by "is this a node?" cannot change
|
|
1115
|
+
// the winner: a position that is not a node cannot resolve, so skipping it
|
|
1116
|
+
// is invisible; and the order stays SHORTEST FIRST, which is a semantic
|
|
1117
|
+
// law, not an optimisation (test/106, test/108 pin it).
|
|
1118
|
+
//
|
|
1119
|
+
// A host that cannot answer falls back to every prefix: exact and
|
|
1120
|
+
// complete, at a `resolve` per offset. A host that CAN answer is
|
|
1121
|
+
// authoritative even when it answers "none" — if no prefix is a node then
|
|
1122
|
+
// no key exists to resolve, so enumerating would only pay nulls. (A key
|
|
1123
|
+
// reachable through the CANONICAL equivalence alone and ending off every
|
|
1124
|
+
// node end is therefore not tried here; that dimension is unreachable on
|
|
1125
|
+
// this path by construction and is not part of the exact-key law.)
|
|
1126
|
+
const ends = this.host.contentKeyEnds?.(c.bytes, tail);
|
|
1127
|
+
const candidateEnds = function* () {
|
|
1128
|
+
if (ends !== undefined) {
|
|
1129
|
+
yield* ends;
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
for (let p = 1; p <= tail.length; p++)
|
|
1133
|
+
yield p;
|
|
1134
|
+
};
|
|
1135
|
+
for (const len of candidateEnds()) {
|
|
1117
1136
|
keyBytes = concat2(c.bytes, tail.subarray(0, len));
|
|
1118
1137
|
const k = this.host.resolve(keyBytes) ??
|
|
1119
1138
|
this.host.canonResolve?.(keyBytes) ??
|
package/dist/src/mind/match.d.ts
CHANGED
|
@@ -200,7 +200,7 @@ export declare function frameSlots(ctx: MindContext, query: Uint8Array, cand: Ui
|
|
|
200
200
|
* ANCHOR that the query displaced. Neither implies the other, and the
|
|
201
201
|
* observed failures pass the restatement guard cleanly.
|
|
202
202
|
*
|
|
203
|
-
*
|
|
203
|
+
* Four conditions, all byte-exact and all necessary:
|
|
204
204
|
*
|
|
205
205
|
* 1. the query and the anchor must be ONE STRUCTURE — what they share has to
|
|
206
206
|
* dominate the query, or the query is not a variant of the anchor at all
|
package/dist/src/mind/match.js
CHANGED
|
@@ -546,7 +546,7 @@ export function frameSlots(ctx, query, cand, id) {
|
|
|
546
546
|
* ANCHOR that the query displaced. Neither implies the other, and the
|
|
547
547
|
* observed failures pass the restatement guard cleanly.
|
|
548
548
|
*
|
|
549
|
-
*
|
|
549
|
+
* Four conditions, all byte-exact and all necessary:
|
|
550
550
|
*
|
|
551
551
|
* 1. the query and the anchor must be ONE STRUCTURE — what they share has to
|
|
552
552
|
* dominate the query, or the query is not a variant of the anchor at all
|
|
@@ -631,8 +631,10 @@ export function substituteAll(hay, pairs) {
|
|
|
631
631
|
if (usable.length === 0)
|
|
632
632
|
return hay;
|
|
633
633
|
// Longest needle first, so a needle that is a prefix of another can never
|
|
634
|
-
// pre-empt it. Ties cannot arise:
|
|
635
|
-
// pairwise distinct
|
|
634
|
+
// pre-empt it. Ties cannot arise: a consumer that VOICES checks the
|
|
635
|
+
// fillers pairwise with `distinct` and refuses such an instance itself —
|
|
636
|
+
// `frameSlots` reports and does not judge (see its own doc), so the refusal
|
|
637
|
+
// lives with the mechanism that needs it, not here.
|
|
636
638
|
const order = [...usable].sort((a, b) => b.needle.length - a.needle.length);
|
|
637
639
|
const out = [];
|
|
638
640
|
let i = 0;
|
|
@@ -731,7 +731,7 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
731
731
|
// grounds") — fine for ORIENTING mechanisms, not for voicing learnt
|
|
732
732
|
// content the query never asked about. Computed once here; both the
|
|
733
733
|
// hub fallback below and the comparison gate consume it.
|
|
734
|
-
const rootTrusted = roots.some((r) => r.
|
|
734
|
+
const rootTrusted = roots.some((r) => r.idfVote >= consensusFloor(corpusN(ctx))); // the IDF sum: the bar's own quantity
|
|
735
735
|
// The context that ESTABLISHES a filler — the same reverse context, under
|
|
736
736
|
// the same naming test, `seatOfNode` uses to VOICE an analog (a predecessor
|
|
737
737
|
// whose bytes CONTAIN the node's: it names or describes it, rather than
|
|
@@ -98,6 +98,30 @@ export async function confluenceJoin(ctx, query, pre) {
|
|
|
98
98
|
// constraints). Shard-bound streams are no constraints, and their meets
|
|
99
99
|
// are connective debris (". Sure,", "ngul" — observed).
|
|
100
100
|
const bindsAConstituent = (cover) => cover.some(([cs, ce]) => ce - cs >= 2 * W);
|
|
101
|
+
// THE VOTE ENTERS AS ORDER, NEVER AS A BAR. This is the only one of the
|
|
102
|
+
// climb's four consumers (recall, fuseAttention, cast, here) that uses the
|
|
103
|
+
// evidence's MAGNITUDE without a floor, and it is legitimate by construction:
|
|
104
|
+
// `ranked` answers "which anchor is stronger" — a question about votes, so the
|
|
105
|
+
// comparison stays within one dimension — and the vote is otherwise only
|
|
106
|
+
// REPORTED (Stream.vote travels to the rationale's constraint nodes). What
|
|
107
|
+
// actually SELECTS a constraint is byte-structural and never the magnitude: a
|
|
108
|
+
// run of at least 2W (`bindsAConstituent`, with its accidental-sharing
|
|
109
|
+
// counter-examples above), disjoint covers (`disjoint`), and scaffolding never
|
|
110
|
+
// binds at all (`dominates(reachOf(…), N)`). The MEET such a stream may
|
|
111
|
+
// produce is selected the same way: a span shorter than 2W is rejected, and
|
|
112
|
+
// the winner is the one with the smallest `reach` (ties broken by the longer
|
|
113
|
+
// span) — a corpus quantity and bytes, never the vote, which appears only in
|
|
114
|
+
// the trace item.
|
|
115
|
+
// binds at all (`dominates(reachOf(…), N)`). The only cut in this loop is a
|
|
116
|
+
// BUDGET, and it is measured: stopping the scan at 2W anchors saves 50-70% of
|
|
117
|
+
// confluence's cost on non-conjunctive queries while preserving every genuinely
|
|
118
|
+
// conjunctive case, whose top anchors ARE its constraints.
|
|
119
|
+
// MEASURED (this goal, on THIS file's own conjunctive fixture): the two
|
|
120
|
+
// streams appear at ranks 1 and 4 against a budget of 2W = 8, on a query whose
|
|
121
|
+
// `ranked` is 9 — so the cut IS live (it would have returned null at the 8th
|
|
122
|
+
// anchor) and it does NOT prune the case it exists to protect. The other
|
|
123
|
+
// conjunctive fixture (the Leonardo one) finds them at ranks 0 and 1. Scope:
|
|
124
|
+
// these are the repo's conjunctive fixtures, and no more.
|
|
101
125
|
const streams = [];
|
|
102
126
|
const rankedCapped = ranked.length > pre.k ? ranked.slice(0, pre.k) : ranked;
|
|
103
127
|
// CONJUNCTIVITY EARLY-EXIT: a conjunctive query's top-ranked anchors
|
|
@@ -194,9 +194,36 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
194
194
|
// consensus", while breadth is the SCALE-INVARIANT reading — "a point whose
|
|
195
195
|
// breadth clears `dominates` (> half the query's regions corroborate it) is
|
|
196
196
|
// real consensus; one that does not is a coincidental single-region echo".
|
|
197
|
-
//
|
|
198
|
-
// comparing a POOLED SUM against a floor that prices ONE region's
|
|
199
|
-
// is a dimensional error.
|
|
197
|
+
// THIS USED TO CLAIM A DIMENSIONAL ERROR, AND THAT CLAIM WAS FALSE.
|
|
198
|
+
// It read: "comparing a POOLED SUM against a floor that prices ONE region's
|
|
199
|
+
// evidence is a dimensional error." `consensusFloor` is not priced for one
|
|
200
|
+
// region: thresholds.md §2 derives it as the POOLED-vote significance floor —
|
|
201
|
+
// "each region contributes at most ln(N/c) <= ln(N); ln(N)+1/2 demands ..." —
|
|
202
|
+
// and attention.ts says the same where it builds the vote ("the scale
|
|
203
|
+
// consensusFloor is derived for"). The comparison is in ONE dimension, and
|
|
204
|
+
// it is so because the climb WEIGHTS BY IDF: `wf` in voteRegions is
|
|
205
|
+
// `direct ? df : combined ? idf + df : idf`, and the engine only ever runs the
|
|
206
|
+
// last one (DFMode's default "inverse", the mode every non-test caller uses —
|
|
207
|
+
// `direct` and `combined` are exercised by test/24 and test/27 only, and
|
|
208
|
+
// test/24 pins that their votes DO differ). In those two the sum would leave
|
|
209
|
+
// the floor's dimension and the floor would need re-deriving.
|
|
210
|
+
//
|
|
211
|
+
// What the OR below is really for is SCALE, not dimension (the paragraph
|
|
212
|
+
// above says it): a vote that clears ln(N)+1/2 means "strong" on a small store
|
|
213
|
+
// and "weak" on a large one for the same genuine consensus, so the
|
|
214
|
+
// scale-invariant breadth reading is added beside it.
|
|
215
|
+
//
|
|
216
|
+
// AND THE PREMISE IS IDF. The deviation in the other two weighting modes is
|
|
217
|
+
// TWO-SIDED and DERIVED: `direct` DEFLATES a region (ln(1+c) < ln(N/c) for
|
|
218
|
+
// small c) and `combined` INFLATES it (ln N + ln(1+1/c)), both by at most
|
|
219
|
+
// `ln 2` — see `geometry.ts`'s `consensusFloor`, where the bound lives.
|
|
220
|
+
// MEASURED on 8 anchors across 5 queries, running the same climb in all
|
|
221
|
+
// three modes: ZERO gate inversions — every anchor's `vote >= floor` verdict
|
|
222
|
+
// is the same in `inverse`, `direct` and `combined`, even where the readings
|
|
223
|
+
// straddle the floor on opposite sides (#148: inverse 3.39, combined 4.71
|
|
224
|
+
// above it, direct 1.31 below). Pinned by test/55's test 20. The bar is not
|
|
225
|
+
// re-derived for those modes because nothing reachable needs it; the premise
|
|
226
|
+
// is IDF, and that is now written where the gate reads it.
|
|
200
227
|
//
|
|
201
228
|
// Measured on the 15.7M-node store (N=325,615, so the old floor was 13.19).
|
|
202
229
|
// The absolute vote cannot separate right from wrong at this scale, and the
|
|
@@ -265,7 +292,7 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
265
292
|
const minVote = consensusFloor(corpusN(ctx));
|
|
266
293
|
if (forest.length > 0 &&
|
|
267
294
|
!allWindowsAreScaffolding(ctx, query) &&
|
|
268
|
-
(forest[0].
|
|
295
|
+
(forest[0].idfVote >= minVote || // the IDF sum: the bar's own quantity
|
|
269
296
|
(dominates(forest[0].breadth, 1) && forest[0].peak > Math.LN2))) {
|
|
270
297
|
const g = await project(ctx, forest[0].anchor, queryGist);
|
|
271
298
|
// THE ANCHOR'S OCCUPANT IS NOT THE ASKER'S. This tier grounds an anchor
|
|
@@ -468,6 +495,7 @@ export const recallMechanism = {
|
|
|
468
495
|
moves: r.moves,
|
|
469
496
|
unexplained: r.unexplained,
|
|
470
497
|
provenance: r.echoed ? "recall-echo" : "recall",
|
|
498
|
+
used: new Set(),
|
|
471
499
|
...(r.complete ? { complete: true } : {}),
|
|
472
500
|
}];
|
|
473
501
|
},
|
package/dist/src/mind/mind.d.ts
CHANGED
|
@@ -75,6 +75,8 @@ export interface MindOptions {
|
|
|
75
75
|
seed?: number;
|
|
76
76
|
recallQueryK?: number;
|
|
77
77
|
haloQueryK?: number;
|
|
78
|
+
/** Branch nodes the pivot sweep may probe — see {@link MindConfig}. */
|
|
79
|
+
pivotProbeK?: number;
|
|
78
80
|
/** Items one rationale step may itemise — see {@link MindConfig}. */
|
|
79
81
|
rationaleSampleK?: number;
|
|
80
82
|
/** Corpus-reading capacities and budgets — see {@link MindConfig}. */
|
|
@@ -202,8 +204,8 @@ export declare class Mind implements MindContext {
|
|
|
202
204
|
* with the most distributional evidence (highest `prevOf` count — the
|
|
203
205
|
* structural manifestation of its halo). When evidence is equal the
|
|
204
206
|
* first-inserted edge wins. */
|
|
205
|
-
/** See {@link GraphSearchHost.
|
|
206
|
-
|
|
207
|
+
/** See {@link GraphSearchHost.contentKeyEnds}. */
|
|
208
|
+
contentKeyEnds(prefix: Uint8Array, tail: Uint8Array): readonly number[];
|
|
207
209
|
chooseNext(node: number): number | undefined;
|
|
208
210
|
constructor(opts?: MindOptions);
|
|
209
211
|
constructor(cfg: MindConfig, store: Store, _fromStore: true);
|
package/dist/src/mind/mind.js
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
import { makeKeyring, rng, setVecConfig } from "../vec.js";
|
|
12
12
|
import { sampleCorpus, searchCorpus } from "./corpus.js";
|
|
13
13
|
import { Alphabet } from "../alphabet.js";
|
|
14
|
-
import {
|
|
14
|
+
import { contentFoldIncremental, reachThreshold, } from "../geometry.js";
|
|
15
|
+
import { keyEnds } from "./canonical.js";
|
|
15
16
|
import { BoundedMap } from "../store.js";
|
|
16
17
|
import { SQliteStore } from "../store-sqlite.js";
|
|
17
18
|
import { resolveConfig } from "../config.js";
|
|
@@ -160,9 +161,9 @@ export class Mind {
|
|
|
160
161
|
* with the most distributional evidence (highest `prevOf` count — the
|
|
161
162
|
* structural manifestation of its halo). When evidence is equal the
|
|
162
163
|
* first-inserted edge wins. */
|
|
163
|
-
/** See {@link GraphSearchHost.
|
|
164
|
-
|
|
165
|
-
return
|
|
164
|
+
/** See {@link GraphSearchHost.contentKeyEnds}. */
|
|
165
|
+
contentKeyEnds(prefix, tail) {
|
|
166
|
+
return keyEnds(this, prefix, tail);
|
|
166
167
|
}
|
|
167
168
|
chooseNext(node) {
|
|
168
169
|
return chooseNext(this, node, this._edgeGuide);
|
|
@@ -151,6 +151,13 @@ export interface MechanismResult {
|
|
|
151
151
|
bytes: Uint8Array;
|
|
152
152
|
accounted: Array<[number, number]>;
|
|
153
153
|
moves: number;
|
|
154
|
+
/** WHAT THIS ANSWER SPEAKS FOR — the anchors it voices, and therefore the
|
|
155
|
+
* content the reasoner must not pivot back through. Declared by the
|
|
156
|
+
* mechanism about its OWN result, exactly like `accounted`/`unexplained`/
|
|
157
|
+
* `complete`: post-grounding honours the property and NEVER ASKS WHICH
|
|
158
|
+
* MECHANISM SET IT, so the market stays uniform. An EMPTY set is a real
|
|
159
|
+
* declaration — "this answer voices nothing" (recall) — and withholds
|
|
160
|
+
* nothing; omit the field and the pipeline re-recognises the answer. */
|
|
154
161
|
used?: ReadonlySet<number>;
|
|
155
162
|
unexplained: string;
|
|
156
163
|
/** Explicit weight override. When absent, weight = moves + PASS·unaccounted. */
|
|
@@ -12,7 +12,7 @@ import { PASS, STEP } from "./graph-search.js";
|
|
|
12
12
|
import { gistOf, read, resolve } from "./primitives.js";
|
|
13
13
|
import { recognise } from "./recognition.js";
|
|
14
14
|
import { fuseAttention, reason } from "./reasoning.js";
|
|
15
|
-
import { unexplainedSpans } from "./rationale.js";
|
|
15
|
+
import { unaccountedBytes, unexplainedSpans } from "./rationale.js";
|
|
16
16
|
import { rItem } from "./trace.js";
|
|
17
17
|
import { hubBound } from "./traverse.js";
|
|
18
18
|
import { Precomputed } from "./pipeline-mechanism.js";
|
|
@@ -124,8 +124,7 @@ export async function think(ctx, query, mechs) {
|
|
|
124
124
|
// (meter.md); the trace already represents structure.
|
|
125
125
|
const pre = new Precomputed(ctx, query, rec, computed, ctx._edgeGuide);
|
|
126
126
|
const grade = (w) => Math.floor(w / STEP);
|
|
127
|
-
const unaccounted = (spans) => unexplainedSpans(query.length, spans)
|
|
128
|
-
.reduce((sum, [s, e]) => sum + (e - s), 0);
|
|
127
|
+
const unaccounted = (spans) => unaccountedBytes(unexplainedSpans(query.length, spans));
|
|
129
128
|
const weigh = (accounted, moves) => moves + PASS * unaccounted(accounted);
|
|
130
129
|
const candidates = [];
|
|
131
130
|
let best = null;
|
|
@@ -317,13 +316,10 @@ export async function think(ctx, query, mechs) {
|
|
|
317
316
|
}
|
|
318
317
|
const answer = decided.bytes;
|
|
319
318
|
const provenance = decided.provenance;
|
|
320
|
-
const
|
|
319
|
+
const declaredUsed = decided.used;
|
|
321
320
|
// ── Post-grounding, gated by provenance ──────────────────────────────
|
|
322
|
-
const preConsumed =
|
|
323
|
-
|
|
324
|
-
: provenance === "recall" || provenance === "recall-echo"
|
|
325
|
-
? new Set()
|
|
326
|
-
: new Set(recognise(ctx, answer).sites.map((s) => s.payload));
|
|
321
|
+
const preConsumed = declaredUsed ??
|
|
322
|
+
new Set(recognise(ctx, answer).sites.map((s) => s.payload));
|
|
327
323
|
// A grounding that DECLARED itself complete is not extended: the answer is
|
|
328
324
|
// already a trained form's own continuation, reached through an identity
|
|
329
325
|
// claim about the query, so a multi-hop pivot could only chain past the
|
|
@@ -352,9 +348,21 @@ export async function think(ctx, query, mechs) {
|
|
|
352
348
|
// `preConsumed` is derived by re-recognising the answer — "everything in
|
|
353
349
|
// it", not "what it voiced" — and a containment rule over that would
|
|
354
350
|
// suppress every pivot the answer legitimately contains.
|
|
355
|
-
const voiced =
|
|
356
|
-
|
|
357
|
-
|
|
351
|
+
const voiced = declaredUsed === undefined ? [] : [...declaredUsed].flatMap((id) => ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n)));
|
|
352
|
+
// WHAT THIS BRANCH READ, published where it was read. Post-grounding decides
|
|
353
|
+
// by `decided.used` and by the provenance NAME; the operands were invisible in
|
|
354
|
+
// the trace, so a change to the branching could not be shown equivalent or
|
|
355
|
+
// otherwise from outside — three separate investigations failed on exactly
|
|
356
|
+
// that gap. A gap in instrumentation is a defect IN the instrumentation
|
|
357
|
+
// (AGENTS.md §6): closed here, once, as counts only — never content.
|
|
358
|
+
ctx.trace?.step("postGrounding", [rItem(answer, provenance)], [], `used=${decided.used !== undefined ? "declared" : "absent"} · ` +
|
|
359
|
+
`preConsumed=${preConsumed.size} · voiced=${voiced.length}`, undefined, {
|
|
360
|
+
version: 1,
|
|
361
|
+
provenance,
|
|
362
|
+
usedDeclared: decided.used !== undefined,
|
|
363
|
+
preConsumed: preConsumed.size,
|
|
364
|
+
voiced: voiced.length,
|
|
365
|
+
});
|
|
358
366
|
// REPORTABLE, NOT SILENT. A declared-complete grounding ends the derivation
|
|
359
367
|
// here, and that decision is part of the derivation's shape: the reader of a
|
|
360
368
|
// rationale must be able to see that the chain stopped because the mechanism
|
|
@@ -378,9 +386,21 @@ export async function think(ctx, query, mechs) {
|
|
|
378
386
|
];
|
|
379
387
|
const uncovered = unexplainedSpans(query.length, explained)
|
|
380
388
|
.filter(([a, b]) => b - a >= ctx.space.maxGroup);
|
|
381
|
-
|
|
389
|
+
// PUBLISHED, NOT RECOMPUTED: the same `uncovered` the gates below read. A
|
|
390
|
+
// write-only accounting (meter contract 1), so the number that licenses an
|
|
391
|
+
// extension or a fusion stops being invisible.
|
|
392
|
+
if (meter) {
|
|
393
|
+
meter.postGroundingRemainderSpans += uncovered.length;
|
|
394
|
+
meter.postGroundingRemainderBytes += unaccountedBytes(uncovered);
|
|
395
|
+
}
|
|
396
|
+
// The extension is kept as a WHOLE (bytes + what it carried + how many steps),
|
|
397
|
+
// not just its bytes: pricing it — `steps · STEP` against `PASS · unaccounted`
|
|
398
|
+
// — is the caller's job, one comparison away. `reasoned` stays the bytes so
|
|
399
|
+
// everything downstream is untouched.
|
|
400
|
+
const extension = decided.complete ? undefined : meter
|
|
382
401
|
? await meter.time("reason", () => reason(ctx, query, answer, preConsumed, pre, voiced, uncovered))
|
|
383
402
|
: await reason(ctx, query, answer, preConsumed, pre, voiced, uncovered);
|
|
403
|
+
const reasoned = extension?.bytes ?? answer;
|
|
384
404
|
// Fuse only when the query has a genuine REMAINDER no mechanism's
|
|
385
405
|
// structural evidence touched at all. `decided.accounted` alone
|
|
386
406
|
// undercounts this: it is a COST-LADDER quantity (cover.ts prices its
|
|
@@ -424,6 +444,13 @@ export async function think(ctx, query, mechs) {
|
|
|
424
444
|
: meter
|
|
425
445
|
? await meter.time("fuse", () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans))
|
|
426
446
|
: await fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans);
|
|
427
|
-
done(fused,
|
|
447
|
+
done(fused,
|
|
448
|
+
// NO CLAIM ABOUT FUSION HERE. `fuseAttention` is entered whenever a
|
|
449
|
+
// remainder ≥ W exists and returns early when there is nothing to bridge, so
|
|
450
|
+
// this note used to assert a fusion that frequently did not happen (measured:
|
|
451
|
+
// "What is the capital of France famous for" fuses 0 times). The fusion is
|
|
452
|
+
// reported by `fuseAttention`'s own `done` when it happens — the layer that
|
|
453
|
+
// did the work is the layer that says so.
|
|
454
|
+
"grounded, reasoned forward");
|
|
428
455
|
return { bytes: fused, provenance };
|
|
429
456
|
}
|
|
@@ -297,7 +297,15 @@ export function canonResolve(ctx, bytes) {
|
|
|
297
297
|
// on exactly the node the canonical-case query would have found.
|
|
298
298
|
const folded = foldTree(ctx, perceive(ctx, bytesOf), 0).node;
|
|
299
299
|
const use = folded ?? id;
|
|
300
|
-
|
|
300
|
+
// THE ADMISSION PREDICATE, by its own pair of probes: `traverse.ts`'s
|
|
301
|
+
// `leadsSomewhere` is edge-or-halo, and `hasHalo` is the one that carries
|
|
302
|
+
// the mass bar (`mass >= minHaloMass`). Asking `haloMass(use) > 0` instead
|
|
303
|
+
// is the same answer only while `minHaloMass <= 1` (its default): raise the
|
|
304
|
+
// bar and this site would rank a node as leading on evidence the law
|
|
305
|
+
// refuses. Calling `leadsSomewhere` here is not possible — `traverse.ts`
|
|
306
|
+
// imports THIS file, so it would be a cycle — which is why the pair is
|
|
307
|
+
// spelled out rather than named.
|
|
308
|
+
const leads = store.hasNext(use) || store.hasHalo(use);
|
|
301
309
|
if (best === null || (leads && !bestLeads) ||
|
|
302
310
|
(leads === bestLeads && use < best)) {
|
|
303
311
|
best = use;
|
|
@@ -26,6 +26,17 @@ export interface RationaleItem {
|
|
|
26
26
|
* caller asked to carry it (off by default — a D-float array per item would
|
|
27
27
|
* bury the reasoning it is meant to explain). */
|
|
28
28
|
v?: Vec;
|
|
29
|
+
/** The element's OWN bytes, attached BY REFERENCE when the step was built from
|
|
30
|
+
* bytes (a `rationale.ts` item made from a node carries none: read it back
|
|
31
|
+
* through `node`). `text` is a RENDERING and cannot stand in for them — it
|
|
32
|
+
* decodes UTF-8 and DROPS NUL bytes, so a key containing one is unrecoverable
|
|
33
|
+
* from it, which is exactly how a join refusal (`deriveThroughMiss`) became
|
|
34
|
+
* impossible to test exactly without re-encoding. Treat as READ-ONLY: the
|
|
35
|
+
* array belongs to the caller (and may be a view into the query).
|
|
36
|
+
*
|
|
37
|
+
* Costs nothing when nothing inspects: items exist only while a rationale
|
|
38
|
+
* sink is attached, and this holds a reference rather than a copy. */
|
|
39
|
+
bytes?: Uint8Array;
|
|
29
40
|
}
|
|
30
41
|
/** A single completed act of inference — one mechanism, run once.
|
|
31
42
|
*
|
|
@@ -72,6 +83,13 @@ export type InspectRationale = (step: RationaleStep) => void;
|
|
|
72
83
|
/** Decode bytes to text for display, dropping the NUL padding the encoder uses
|
|
73
84
|
* (the same cleanup {@link Mind.respondText} does for its result). */
|
|
74
85
|
export declare function decodeText(bytes: Uint8Array): string;
|
|
86
|
+
/** The BYTE COUNT of the complement — what the currency calls `unaccounted`
|
|
87
|
+
* in `weight = moves + PASS·unaccounted`. It lives here, beside the function
|
|
88
|
+
* that produces the gaps, because the price's second term has ONE definition:
|
|
89
|
+
* this was four copies of the same `reduce` (two in reasoning.ts, two in
|
|
90
|
+
* pipeline.ts) before the architecture audit of `../auditoria-arquitectura-sema.md`
|
|
91
|
+
* collapsed them. Same value at every site — the control diff is identical. */
|
|
92
|
+
export declare function unaccountedBytes(spans: ReadonlyArray<readonly [number, number]>): number;
|
|
75
93
|
/** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
|
|
76
94
|
* the same union-of-spans reading think's grounding decider prices at PASS
|
|
77
95
|
* per byte, exposed here so a mechanism can turn it into a human label. */
|
|
@@ -133,7 +151,16 @@ export declare class Rationale {
|
|
|
133
151
|
* now; the matching {@link Scope.done} supplies the outputs when it finishes.
|
|
134
152
|
* `deps` overrides the default data-flow edge (previous sibling / parent). */
|
|
135
153
|
enter(name: string, inputs: RationaleItem[], deps?: number[]): Scope;
|
|
136
|
-
/**
|
|
154
|
+
/** WHY THIS NAME IS A FREE STRING, when the derivation's moves are a closed
|
|
155
|
+
* union: a mechanism name is WRITTEN and DISPLAYED, and it COMPOSES with
|
|
156
|
+
* the nesting — `mechanism` is the whole path (`["respond", "think",
|
|
157
|
+
* "recognise"]`), which no fixed union can express. Nothing branches on it:
|
|
158
|
+
* `nothing here drives the inference; it only WITNESSES it`. A vocabulary
|
|
159
|
+
* that is only witnessed needs no union; one that is read does
|
|
160
|
+
* (`DerivationMove`, in graph-search.ts). The asymmetry is the design, not
|
|
161
|
+
* a drift.
|
|
162
|
+
*
|
|
163
|
+
* Record a mechanism that has no sub-steps — its inputs and outputs are both
|
|
137
164
|
* known at the call site. Returns its index, for a later step to depend on. */
|
|
138
165
|
step(name: string, inputs: RationaleItem[], outputs: RationaleItem[], note?: string, deps?: number[], data?: unknown): number;
|
|
139
166
|
}
|
|
@@ -24,6 +24,18 @@
|
|
|
24
24
|
export function decodeText(bytes) {
|
|
25
25
|
return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
|
|
26
26
|
}
|
|
27
|
+
/** The BYTE COUNT of the complement — what the currency calls `unaccounted`
|
|
28
|
+
* in `weight = moves + PASS·unaccounted`. It lives here, beside the function
|
|
29
|
+
* that produces the gaps, because the price's second term has ONE definition:
|
|
30
|
+
* this was four copies of the same `reduce` (two in reasoning.ts, two in
|
|
31
|
+
* pipeline.ts) before the architecture audit of `../auditoria-arquitectura-sema.md`
|
|
32
|
+
* collapsed them. Same value at every site — the control diff is identical. */
|
|
33
|
+
export function unaccountedBytes(spans) {
|
|
34
|
+
let total = 0;
|
|
35
|
+
for (const [a, b] of spans)
|
|
36
|
+
total += b - a;
|
|
37
|
+
return total;
|
|
38
|
+
}
|
|
27
39
|
/** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
|
|
28
40
|
* the same union-of-spans reading think's grounding decider prices at PASS
|
|
29
41
|
* per byte, exposed here so a mechanism can turn it into a human label. */
|
|
@@ -151,7 +163,16 @@ export class Rationale {
|
|
|
151
163
|
},
|
|
152
164
|
};
|
|
153
165
|
}
|
|
154
|
-
/**
|
|
166
|
+
/** WHY THIS NAME IS A FREE STRING, when the derivation's moves are a closed
|
|
167
|
+
* union: a mechanism name is WRITTEN and DISPLAYED, and it COMPOSES with
|
|
168
|
+
* the nesting — `mechanism` is the whole path (`["respond", "think",
|
|
169
|
+
* "recognise"]`), which no fixed union can express. Nothing branches on it:
|
|
170
|
+
* `nothing here drives the inference; it only WITNESSES it`. A vocabulary
|
|
171
|
+
* that is only witnessed needs no union; one that is read does
|
|
172
|
+
* (`DerivationMove`, in graph-search.ts). The asymmetry is the design, not
|
|
173
|
+
* a drift.
|
|
174
|
+
*
|
|
175
|
+
* Record a mechanism that has no sub-steps — its inputs and outputs are both
|
|
155
176
|
* known at the call site. Returns its index, for a later step to depend on. */
|
|
156
177
|
step(name, inputs, outputs, note, deps, data) {
|
|
157
178
|
const mechanism = this.path(name);
|
|
@@ -13,18 +13,36 @@ import type { Precomputed } from "./pipeline-mechanism.js";
|
|
|
13
13
|
export declare function restatesQuery(query: Uint8Array, bytes: Uint8Array): boolean;
|
|
14
14
|
/** Extend a grounded answer forward across facts (multi-hop reasoning).
|
|
15
15
|
* Pivots on the longest unconsumed learnt context each answer contains,
|
|
16
|
-
* then follows the pivot's continuation to the next fact.
|
|
17
|
-
*
|
|
16
|
+
* then follows the pivot's continuation to the next fact. **The chain ends
|
|
17
|
+
* when it STOPS, never when a count runs out**: every exit is a refusal (no
|
|
18
|
+
* pivot, no forward step, no question material carried) and the walk is bounded
|
|
19
|
+
* by the material and the graph — `consumed` refuses to revisit a node. There
|
|
20
|
+
* is no hop allowance, so this doc deliberately names no `cfg` capacity: the
|
|
21
|
+
* cover prices every hop at `STEP` and lets the search decide the depth, and a
|
|
22
|
+
* second count here would be a second decision about the same thing.
|
|
23
|
+
* `preConsumed` carries node ids already
|
|
18
24
|
* spoken for by the grounding stage (cover/extract/CAST). `voiced` carries
|
|
19
25
|
* the BYTES of the anchors a mechanism declared it voiced (its `used` set),
|
|
20
26
|
* when it declared one — see the pivot's own containment rule. `pre` is the
|
|
21
27
|
* response's shared pre-computation — the post-grounding stages read the
|
|
22
28
|
* same container the mechanisms did. */
|
|
29
|
+
/** What the multi-hop extension produced, and what it cost: the bytes (the
|
|
30
|
+
* answer), the spans of the grounding's UNCOVERED material that each step was
|
|
31
|
+
* justified by, and how many steps were taken. The last two exist so the
|
|
32
|
+
* caller can price the extension in the ladder's own currency — `steps · STEP`
|
|
33
|
+
* against `PASS · unaccounted` — instead of taking it unconditionally. Both
|
|
34
|
+
* are FACTS, not verdicts: nothing here says whether the extension was worth
|
|
35
|
+
* it; that is the comparison's job, one layer up. */
|
|
36
|
+
export interface ReasonedAnswer {
|
|
37
|
+
bytes: Uint8Array;
|
|
38
|
+
carried: Array<[number, number]>;
|
|
39
|
+
steps: number;
|
|
40
|
+
}
|
|
23
41
|
export declare function reason(ctx: MindContext, query: Uint8Array, answer: Uint8Array, preConsumed: ReadonlySet<number>, pre: Precomputed, voiced?: readonly Uint8Array[],
|
|
24
42
|
/** The query material the GROUNDING left uncovered — the cost ladder's own
|
|
25
43
|
* `unaccounted` spans. Only the reasoner's OWN extensions are judged
|
|
26
44
|
* against it; a mechanism carrying its own `used` set owns its shape. */
|
|
27
|
-
uncovered?: readonly (readonly [number, number])[]): Promise<
|
|
45
|
+
uncovered?: readonly (readonly [number, number])[]): Promise<ReasonedAnswer>;
|
|
28
46
|
/** Fuse independent points of attention into one answer (multi-topic).
|
|
29
47
|
* When the consensus climb finds more than one dominant point, each
|
|
30
48
|
* independent point grounds its own answer; they are bridged together
|