@hviana/sema 0.4.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/AGENTS.md +290 -77
  2. package/HOW_IT_WORKS.md +2170 -735
  3. package/dist/example/train_base.d.ts +9 -3
  4. package/dist/example/train_base.js +21 -4
  5. package/dist/src/canon.d.ts +19 -0
  6. package/dist/src/canon.js +28 -0
  7. package/dist/src/geometry.d.ts +52 -0
  8. package/dist/src/geometry.js +87 -1
  9. package/dist/src/mind/bridge.js +27 -1
  10. package/dist/src/mind/frame-filler.d.ts +15 -0
  11. package/dist/src/mind/frame-filler.js +535 -0
  12. package/dist/src/mind/learning.js +6 -11
  13. package/dist/src/mind/mechanisms/cast.js +72 -2
  14. package/dist/src/mind/mechanisms/cover.js +6 -1
  15. package/dist/src/mind/mechanisms/extraction.js +27 -0
  16. package/dist/src/mind/mechanisms/recall.js +214 -34
  17. package/dist/src/mind/mind.d.ts +49 -1
  18. package/dist/src/mind/mind.js +137 -10
  19. package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
  20. package/dist/src/mind/pipeline.js +29 -1
  21. package/dist/src/mind/prefix-completion.d.ts +59 -0
  22. package/dist/src/mind/prefix-completion.js +270 -0
  23. package/dist/src/mind/primitives.d.ts +29 -10
  24. package/dist/src/mind/primitives.js +52 -61
  25. package/dist/src/mind/recognition.js +119 -9
  26. package/dist/src/mind/traverse.d.ts +32 -0
  27. package/dist/src/mind/traverse.js +52 -0
  28. package/dist/src/mind/types.d.ts +55 -16
  29. package/dist/src/mind/types.js +68 -19
  30. package/dist/src/rabitq-ivf/src/rabitq.js +31 -1
  31. package/dist/src/store.d.ts +21 -0
  32. package/dist/src/store.js +21 -0
  33. package/example/train_base.ts +21 -4
  34. package/package.json +1 -1
  35. package/src/canon.ts +28 -0
  36. package/src/geometry.ts +100 -1
  37. package/src/mind/bridge.ts +34 -0
  38. package/src/mind/frame-filler.ts +604 -0
  39. package/src/mind/learning.ts +5 -9
  40. package/src/mind/mechanisms/cast.ts +70 -2
  41. package/src/mind/mechanisms/cover.ts +6 -1
  42. package/src/mind/mechanisms/extraction.ts +27 -0
  43. package/src/mind/mechanisms/recall.ts +236 -37
  44. package/src/mind/mind.ts +154 -14
  45. package/src/mind/pipeline-mechanism.ts +7 -0
  46. package/src/mind/pipeline.ts +33 -1
  47. package/src/mind/prefix-completion.ts +314 -0
  48. package/src/mind/primitives.ts +59 -70
  49. package/src/mind/recognition.ts +117 -6
  50. package/src/mind/traverse.ts +52 -0
  51. package/src/mind/types.ts +98 -42
  52. package/src/rabitq-ivf/src/rabitq.ts +31 -1
  53. package/src/store.ts +25 -0
  54. package/test/13-conversation.test.mjs +13 -0
  55. package/test/57-fusion-order.test.mjs +65 -0
  56. package/test/65-ann-recall.test.mjs +331 -0
  57. package/test/66-query-edge-whitespace.test.mjs +99 -0
  58. package/test/67-climb-anchor-breadth.test.mjs +113 -0
  59. package/test/68-extraction-unanchored.test.mjs +79 -0
  60. package/test/69-frame-filler.test.mjs +115 -0
  61. package/test/70-prefix-completion.test.mjs +170 -0
  62. package/test/71-embedded-canon-equivalence.test.mjs +121 -0
  63. package/test/72-prefix-candidate-supply.test.mjs +114 -0
  64. package/test/73-scaffolding-only-bridge-abstains.test.mjs +178 -0
  65. package/test/74-prefix-trap-not-sprung-early.test.mjs +114 -0
  66. package/test/75-multiturn-context-optimisation.test.mjs +1082 -0
package/AGENTS.md CHANGED
@@ -32,6 +32,10 @@ Hard facts you must not fight:
32
32
  in new files.
33
33
  - **Determinism is the product.** Same seed + same deposit order + same query ⇒
34
34
  byte-identical answer. Tests pin it.
35
+ - **Perception is a pure function of the bytes**, and the deposit and inference
36
+ paths must compute the same tree for the same input (2.15). Anything that
37
+ makes one side impose structure the other does not is a correctness bug, not a
38
+ tuning choice.
35
39
 
36
40
  The mental model, top to bottom:
37
41
 
@@ -39,11 +43,15 @@ The mental model, top to bottom:
39
43
  mind/pipeline.ts the grounding decider: mechanisms compete on one cost scale
40
44
  mind/mechanisms/* cover · cast · confluence · extraction · recall · alu
41
45
  mind/* shared machinery: match/project, attention, recognition,
42
- junction ascent, graph search, learning, rationale
46
+ junction ascent, graph search, learning, rationale;
47
+ recall's refusal-path tiers (bridge, prefix-completion,
48
+ frame-filler) live beside them, not inside recall.ts
43
49
  store.ts AbstractStore: ALL domain logic of the DAG store
44
50
  store-sqlite.ts the one concrete backend (thin SQL wrappers)
45
- geometry.ts + vec/alphabet/sema
46
- vectors, the fold, and every derived threshold
51
+ geometry.ts + vec/alphabet/sema/canon
52
+ vectors, the fold (content-defined cuts + two-ended
53
+ seats), every derived threshold, and the injected
54
+ content canonicalizer
47
55
  derive/ · alu/ · rabitq-ivf/
48
56
  firewalled sublibraries with their own READMEs and tests
49
57
  ```
@@ -79,32 +87,55 @@ Every similarity/decision threshold is a **formula** over the vector dimension
79
87
  D, the perception window W, or the corpus size N, defined once in
80
88
  `src/geometry.ts` (`mergeThreshold`, `identityBar`, `reachThreshold`,
81
89
  `significanceBar`, `estimatorNoise`, `conceptThreshold`, `consensusFloor`,
82
- `dominates`, …). `src/config.ts` holds **capacities and budgets only** (cache
83
- byte budgets, batch sizes, vector-index parameters, query k, ALU precision, the
84
- seed).
90
+ `dominates`, …), with the corpus-scale readings beside them in
91
+ `mind/traverse.ts` (`corpusN`, `hubBound`, `hubCap`, `atomReach`, `atomIsHub`).
92
+ `src/config.ts` holds **capacities and budgets only** (cache byte budgets, batch
93
+ sizes, vector-index parameters, query k, ALU precision, the seed).
85
94
 
86
95
  _Follow it:_ if you are about to add a tunable cutoff to config, derive it in
87
96
  `geometry.ts` instead. A threshold knob is a design bug here — one was already
88
97
  removed once. When a decision needs a scale, express it in D, W, or N.
89
98
 
99
+ Two derivations bite in practice and are worth knowing before you reach for a
100
+ bar:
101
+
102
+ - `identityBar(D, W, len)` is the SCALE-AWARE identity claim (`1 − W/len`,
103
+ floored at `mergeThreshold`). Reuse `mergeThreshold` for a whole-span claim
104
+ and long spans silently tolerate whole windows of foreign content.
105
+ - A bar calibrated for one quantity does not transfer to another. `chooseNext`
106
+ carries a comment recording exactly this: the consensus floor is priced for
107
+ pooled, N-scaled climb votes, and gating an N-invariant support count against
108
+ it fails once N is large enough (test/40 pins it).
109
+
90
110
  ### 2.3 Exact decides, approximate proposes
91
111
 
92
112
  Every score from the vector indexes (`resonate`, `resonateHalo`) is a RaBitQ
93
113
  _estimate_. Identity is decided only by content-addressed lookup (`resolve`,
94
- `findLeaf`, `findBranch`), never by `score >= threshold`. Scores rank candidates
95
- and gate broad regions; bytes make decisions.
114
+ `findLeaf`, `findBranch`, and the canonical fallback `canonResolve`), never by
115
+ `score >= threshold`. Scores rank candidates and gate broad regions; bytes make
116
+ decisions. Even the echo decision in `recall.ts` re-folds the top hit's bytes
117
+ rather than trusting the estimate it already has.
96
118
 
97
119
  The same principle appears as **graded evidence ladders** — exact tier first,
98
- distributional second, geometric last — in three places built on one shape:
120
+ distributional second, geometric last — in five places built on one shape:
99
121
 
122
+ - `resolve` in `mind/primitives.ts`: exact content-addressed fold →
123
+ `canonResolve` (equivalence class, hash-then-verify).
100
124
  - `locate` in `mind/match.ts`: exact bytes → halo role → gist.
101
- - `alignGraded` in `mind/match.ts`: literal W-gram runs → halo-matched sites.
125
+ - `alignGraded` in `mind/match.ts`: literal W-gram runs → halo-matched sites
126
+ (the weave adds a third pass from the climb's own proposals — see
127
+ `pipeline-mechanism.ts`).
102
128
  - `bridge` in `mind/resonance.ts`: junction containers by identity → edge
103
129
  junctions → synonym junctions → whole-gist resonance as last resort.
130
+ - `crossRegionVotes` in `mind/attention.ts`: exact containers → single synonym →
131
+ double synonym → `structuralResonance` (a synthetic gist; the one tier with no
132
+ byte containment behind it, and gated hardest because of it).
104
133
 
105
134
  _Follow it:_ never reorder a ladder's tiers, and never let an approximate tier
106
- override an exact one. If you need a new matcher, add a tier to the shared
107
- family (2.5), not a private score check.
135
+ override an exact one. Two asymmetries in `attention.ts` encode that rule and
136
+ must not be flattened: only the EXACT tier may explain ordinary votes away, and
137
+ only container-backed evidence may consume its endpoints. If you need a new
138
+ matcher, add a tier to the shared family (2.5), not a private score check.
108
139
 
109
140
  ### 2.4 One cost currency
110
141
 
@@ -112,15 +143,27 @@ The graph search's cost ladder (`mind/graph-search.ts`, exported constants) is
112
143
  the single pricing scheme of the whole mind:
113
144
 
114
145
  ```
115
- MICRO (1e-3) advance over recognised material; per-byte unit of the A* heuristic
116
- STEP (1) follow one learned edge; one computed result; one projection
117
- CONCEPT (10) a halo-mediated act (synonym hop, consensus climb)
146
+ MICRO (1e-3) advance over recognised material; per-byte unit of the A* heuristic;
147
+ a RECOMPOSED form's onward edge
148
+ STEP (1) follow one learned edge (EVERY hop, first or fifth); one computed
149
+ result; one projection
150
+ CONCEPT (10) a halo-mediated act (synonym hop, consensus climb); also the price
151
+ of ABANDONING an edge chain early (graph-search's stop-here rule)
118
152
  PASS (1000/byte) carry a byte nothing explains
119
153
  ```
120
154
 
121
155
  Only the _ordering_ matters. The pipeline weighs whole mechanisms in the same
122
156
  units: `weight = moves + PASS · unaccounted-bytes`, so a mechanism-level choice
123
- and a byte-level choice are the same kind of decision.
157
+ and a byte-level choice are the same kind of decision. Weights are compared at
158
+ STEP resolution (`grade = ⌊w/STEP⌋`); at equal grade the candidate reporting
159
+ fewer `scaffolding` bytes wins, and only then does the mechanism list's order
160
+ decide.
161
+
162
+ Two pricings inside `graph-search.ts` are easy to "simplify" and are not free to
163
+ change: charging every edge hop STEP is what makes the lightest derivation the
164
+ SHORTEST chain (charging later hops nothing made every stopping depth tie), and
165
+ the stop-here rule at CONCEPT-above-chain-cost is what keeps a genuine fixpoint
166
+ preferable to giving up at the same depth.
124
167
 
125
168
  _Follow it:_ place any new cost deliberately in the ordering; never make the A\*
126
169
  heuristic exceed a real per-byte cost (admissibility breaks silently — answers
@@ -132,10 +175,13 @@ policy in callers, the engine neutral.
132
175
 
133
176
  `mind/match.ts` is the shared family every generalising mechanism configures:
134
177
  matchers (`locate`, `alignRuns`, `alignGraded`, `bestHaloMate`, `haloSiblings`,
135
- `analogyStrength`) and projections (`follow`, `reverseContext`, `project`,
136
- `conceptHop`). `mind/traverse.ts` owns the graph readings (`edgeAncestors`,
137
- `reachOf`, `chooseNext`/`chooseAmong`, `guidedFirst`, `leadsSomewhere`) and the
138
- corpus scale (`corpusN`, `hubBound`, `hubCap`).
178
+ `analogyStrength` and its structural tier `sharedFrameStrength`, `spanHalo`,
179
+ `spanSynonymStrength`), projections (`follow`, `reverseContext`, `project`,
180
+ `conceptHop`), and the span-shape family (`skillExemplar`, `isSpanShaped`,
181
+ `containsSpan`). `mind/traverse.ts` owns the graph readings (`edgeAncestors`,
182
+ `reachOf`, `chooseNext`/`chooseAmong`, `guidedFirst`, `leadsSomewhere`,
183
+ `allWindowsAreScaffolding`) and the corpus scale (`corpusN`, `hubBound`,
184
+ `hubCap`, `atomReach`).
139
185
 
140
186
  _Follow it:_ before writing a new generalising mechanism, express it as a
141
187
  (matcher, direction, gate) triple. If those already exist, the mechanism is a
@@ -155,21 +201,33 @@ without knowing extraction exists.
155
201
 
156
202
  Related single-definition contracts (define once, import everywhere):
157
203
 
204
+ - `contentLevels` (`geometry.ts`) — the ONE boundary rule: where a stream
205
+ segments and at what level. `contentBoundaries` is a projection of it, not a
206
+ second copy; it used to carry its own rolling-hash loop, which is exactly how
207
+ a write side and a read side drift apart without a type error.
158
208
  - `canonical.ts` — the write/read contract for canonical segmentation
159
209
  (`canonicalWindows`, `chainReach`, `leafIdRun`, `leafIdPrefix`, `windowIds`).
160
- Learning writes through it; recognition and attention read through it.
161
- Changing one side means changing this file — drift between sides breaks
162
- canonical recognition with **no type error**.
210
+ Learning writes through it; recognition, attention, confluence, the bridge and
211
+ prefix-completion read through it. Changing one side means changing this file
212
+ — drift between sides breaks canonical recognition with **no type error**.
163
213
  - `junction.ts` — the content-addressed "which learnt whole contains these two
164
214
  forms?" ascent, shared by the bridge and cross-region attention, with its
165
215
  per-response `WalkCache` and once-per-candidate seed computation.
166
216
  - `joinWithBridge` (`resonance.ts`) — the one out-of-search way to join two
167
217
  answer spans; it emits a `bridgeMiss` trace step on a bare join.
218
+ - `dismissedKnownContent` (`bridge.ts`) — the one IGNORED-KNOWN test ("does the
219
+ unaccounted remainder contain a STORED window?"), shared by the substitution
220
+ bridge's own acceptance and CAST's frame-tier comparison gate.
221
+ - `sharedReachMemo` (`traverse.ts`) — the one definition of the ancestor-reach
222
+ memo's lifetime (session-scoped between writes, cold under a trace). There
223
+ used to be two memos that never met.
168
224
  - `guidedFirst` (`traverse.ts`) — the one answer-shaped "what does this lead
169
225
  to?" read (guided pick merged with the first-inserted fallback).
170
226
  - `leadsSomewhere` (`traverse.ts`) — the one admission predicate for recognition
171
227
  sites (edge-or-halo, via existence probes).
172
228
  - `isChunk` (`sema.ts`) — the one "children are all leaves" predicate.
229
+ - `twoEndedSeat` (`sema.ts`) — the one positional-coordinate algebra, shared by
230
+ perception, `fold`, and every synthetic/canonical fold.
173
231
 
174
232
  ### 2.6 The mechanism market (the free-will architecture)
175
233
 
@@ -195,7 +253,12 @@ Four constraints make the market honest — verify all four for anything you add
195
253
  (2.8).
196
254
  4. **Evidence travels.** Every candidate carries `accounted` (query spans its
197
255
  structural evidence explains), `moves` (its acts, priced on the ladder), and
198
- `unexplained` (a diagnostic label). The decider sees only weights.
256
+ `unexplained` (a diagnostic label). Two optional fields let a mechanism state
257
+ things only it can know: `scaffolding` (answer bytes lifted from spans
258
+ nothing recognised — the equal-grade tie-break) and `complete` (this answer
259
+ is a trained form's own continuation reached through an identity claim about
260
+ the query, so post-grounding must not extend it). The decider honours both
261
+ without ever asking which mechanism set them. It sees only weights.
199
262
 
200
263
  Two disciplines inside the loop:
201
264
 
@@ -218,6 +281,16 @@ Evidence accounting rules that bite:
218
281
  - **Reverse reading is not derivation.** A `reverseContext` projection produces
219
282
  bytes but explains nothing forward: `accounted = []`, weight ≈ PASS·|query|.
220
283
  It is the designated last resort by arithmetic, not by rule.
284
+ - **An act you PAID for is accounted.** The mirror of the rule above: the
285
+ bridge's corroborated substitutions cost a CONCEPT each in `moves`, so leaving
286
+ their spans unaccounted charges the same act twice — and the PASS-per-byte
287
+ charge is far the larger (measured: a bridge matching 28 of 29 bytes declared
288
+ the whole query unexplained and lost).
289
+ - `accounted` is a COST-LADDER quantity, not a coverage one. `cover.ts`
290
+ deliberately leaves masked computed spans out of it so PASS-bridged bytes are
291
+ still charged, so a fully-explained query can report `accounted: []`. The
292
+ post-grounding fusion gate therefore reads `accounted ∪ pre.computed`, not
293
+ `accounted` alone.
221
294
  - `unexplained`, `narrowDecision`, and `thinGrounding` are **observational
222
295
  only** — they appear in the trace and never alter the decision.
223
296
 
@@ -231,9 +304,16 @@ using the wrong one is a semantic bug the type system cannot catch:
231
304
  filler/scaffolding gate. Answers "does this discriminate anything in the
232
305
  store?"
233
306
  - **Weave-local** — reference set: the structures aligned with _this query_.
234
- Tooling: the `depth[]` array from `alignGraded` + `MIN_WEAVE` + `dominates`.
235
- Used by CAST's frame gate. Answers "does this discriminate among the
236
- structures this query activates?"
307
+ Tooling: the `depth[]` array built in `computeWeave` + `MIN_WEAVE` +
308
+ `dominates`. Used by CAST's frame gate and by the frame filler's constituency
309
+ reading. Answers "does this discriminate among the structures this query
310
+ activates?"
311
+
312
+ `depth[]` counts **distinct covering structures**, not accumulated alignment
313
+ weight: the frame test compares it against a COUNT of aligned points, so
314
+ accumulating weight there compares weight-mass against a cardinality. It reads
315
+ like a harmless refinement and inverts the frame verdict (measured: 29 of 42
316
+ bytes reading FRAME against 6 of 42, with nothing else changed).
237
317
 
238
318
  _Follow it:_ when adding a gate on "shared vs. discriminative", write down which
239
319
  population your question is about before choosing the tool. Substituting one for
@@ -252,10 +332,21 @@ Crucially, the cap is enforced **at the store level**:
252
332
  `prevCount` — indexed point probes that never decode vectors or unpack blobs.
253
333
  Use them for every "does this lead anywhere?" question instead of
254
334
  `next(id).length > 0`.
335
+ - Prefix-capped reads: `bytesPrefix(id, cap)` and `contentLen(id, cap)`. A
336
+ candidate that exceeds the cap is rejected without reconstructing it — the
337
+ weave, the junction walks and the bridge all read this way, and uncapped reads
338
+ there cost seconds per query on a large store.
255
339
  - `chainRun` climbs transparent scaffolding chains in one bounded read.
256
340
  - The full materialising reads (`next`, `prev`, `parents`, `containers`) exist
257
341
  for maintenance and inspection only. Keep them off hot paths.
258
342
 
343
+ `edgeAncestors` is the reference consumer: it decides saturation from LIMITed
344
+ reads alone, by five named stops (predecessor fan-in, distinct-context limit,
345
+ parent fan-out, the cumulative **lateral-cone** bound, and **byte-atom**
346
+ commonality). The last two are the ones a new walk forgets. An atom carries no
347
+ kid/contain rows by construction, so its commonality is unmeasurable and must
348
+ not default to "maximally rare" — `atomReach`/`atomIsHub` are the honest floor.
349
+
259
350
  _Follow it:_ any new fan-out walk uses `hubBound`/`hubCap` — do not invent a
260
351
  second convention, and do not call `edgeSourceCount()` or
261
352
  `Math.ceil(Math.sqrt(...))` inline.
@@ -282,8 +373,16 @@ respect:
282
373
  copy first.
283
374
  - `contentLen(id, cap?)` reads a node's byte length; pass `cap` when exact
284
375
  length beyond a bound doesn't matter.
285
- - Maintenance entry points (`compactContentIndex`, `repairContentIndex`) are
286
- batch operations for checkpoints, never the hot path.
376
+ - The **canon index** (`canonAdd`/`canonFind`/`canonCount`/`eachContent`) is an
377
+ OPTIONAL capability: a backend may omit all four, and resolution then simply
378
+ has no equivalence fallback. The store never learns what the equivalence IS —
379
+ the canonicalizer is injected by the caller and every candidate is verified by
380
+ re-canonicalizing its bytes, so a hash collision costs a read, never a wrong
381
+ id.
382
+ - Maintenance entry points (`compactContentIndex`, `repairContentIndex`,
383
+ `Mind.buildCanonIndex`) are batch operations for checkpoints, never the hot
384
+ path. `buildCanonIndex` is incremental — it remembers the last indexed id in
385
+ store meta — and must be run under the SAME canonicalizer queries will carry.
287
386
 
288
387
  ### 2.10 The async/sync seam and pre-resolution
289
388
 
@@ -298,19 +397,40 @@ Do not try to make the search async.
298
397
 
299
398
  Asking never writes, which is the only reason per-response memos are sound.
300
399
  `Precomputed` (`pipeline-mechanism.ts`) is the shared response-scoped container:
301
- eager fields (recognition, computed spans, guide) plus **lazily-cached methods**
302
- for expensive analyses (`attention()` — the consensus climb, `weave()`,
303
- `spanShapedOf`, `queryWindows`, `windowsOf`, `reachMemo`) — each computed at
304
- most once, shared by mechanisms and post-grounding stages, and never computed if
305
- nobody asks.
400
+ eager fields (recognition, computed spans, guide, the evidence-breadth constant
401
+ `k`) plus **lazily-cached methods** for expensive analyses (`attention()` — the
402
+ consensus climb, `weave()`, `spanShapedOf`/`spanShapedAll`, `queryWindows`,
403
+ `queryResolved`, `windowsOf`, `reachMemo`) each computed at most once, shared
404
+ by mechanisms and post-grounding stages, and never computed if nobody asks. The
405
+ async ones are cached **by promise**, so a second caller awaits the first
406
+ computation rather than starting another.
407
+
408
+ Mind-level memos (`climbMemo`, `recogniseMemo`, `perceiveMemo`, `canonMemo`,
409
+ `_resolvedSubtrees`, `_edgeChoice`, `_gistCache`) are created in
410
+ `beginResponse()` and torn down in `endResponse()` — a new memo must be added to
411
+ both. A conversation supplies its own maps for the first four, so they persist
412
+ across turns.
413
+
414
+ **Which memos a trace bypasses, and why the answer is "almost none".** Only
415
+ `_edgeChoice` (via `guidedNext`) and `sharedReachMemo` are trace-bypassed —
416
+ there, a memo hit would swallow a repeat's `disambiguate` step or black out
417
+ reach detail the trace serialises. `perceiveMemo`, `recogniseMemo` and
418
+ `climbMemo` are **always** consulted, tracing or not, and that is a correctness
419
+ contract rather than a speed choice: `recogniseImpl` walks the query tree
420
+ through `foldTree`, whose subtree-resolution fast path skips `visit` — and
421
+ therefore skips EMITTING SITES — for any subtree already cached. A second call
422
+ on identical bytes is not idempotent; it finds strictly fewer sites (observed:
423
+ 31 → 5). Bypassing these under trace made every traced turn re-run recognition
424
+ at each of the many call sites that recognise the same query, each call more
425
+ incomplete than the last, measurably changing which mechanism grounded the
426
+ answer (test/42 pins this). Trace steps still fire on a cache hit, so a hit is
427
+ never silent.
306
428
 
307
429
  _Follow it:_ an expensive analysis a new mechanism needs goes on `Precomputed`
308
- as a lazy method, not inside the mechanism. Mind-level memos (`climbMemo`,
309
- `recogniseMemo`, `perceiveMemo`, `_edgeChoice`, `_gistCache`) are created in
310
- `beginResponse()` and torn down in `endResponse()`a new memo must be added to
311
- both. `climbMemo`/`recogniseMemo` are bypassed while a rationale trace is
312
- attached (every mechanism must emit its steps); `perceiveMemo` is not.
313
- Consequence: **never benchmark with a trace attached.**
430
+ as a lazy method, not inside the mechanism. Do not add a `if (!ctx.trace)` guard
431
+ to a memo without checking whether the memoised function is idempotent. And
432
+ still **never benchmark with a trace attached** the two bypassed memos, plus
433
+ the trace's own allocation, measure a different machine.
314
434
 
315
435
  ### 2.12 Caches are budgets, not correctness
316
436
 
@@ -320,6 +440,16 @@ under pressure is always speed or reach (a re-perception, a duplicate probe,
320
440
  reduced resonance until repair), never identity, reconstruction, or a learned
321
441
  relation.
322
442
 
443
+ The deposit-path caches (`_depositTrees`/`_depositLens` for folded segments,
444
+ `_internIds` for already-interned tree nodes, `_resolvedSubtrees` for resolved
445
+ ones) obey the same rule, with one extra obligation: `contentFoldIncremental`
446
+ reuses segments keyed on their **offsets**, which cannot witness that the
447
+ underlying bytes agree. The caller must discharge that structurally — the
448
+ deposit cache is keyed by the prefix's own bytes, and a conversation's fold
449
+ advances only by append. A caller that cannot make the same argument passes no
450
+ `prev` at all; the cold path is always correct. Handing it a mismatched `prev`
451
+ produced a wrong tree on 336 of 400 random streams.
452
+
323
453
  _Follow it:_ new caches get budgets and a re-derivation path. If memory grows,
324
454
  look for something bypassing a budget — not for a leak in the DAG (nodes are
325
455
  meant to accumulate).
@@ -327,9 +457,11 @@ meant to accumulate).
327
457
  ### 2.13 Honest degradation, visible failure
328
458
 
329
459
  Nothing degrades silently: counters (`danglingReads`, `compactFailures`), trace
330
- steps (`bridgeMiss`, `narrowDecision`, `thinGrounding`), the `echoed` flag on
331
- recall's last tier, `recall-echo` provenance. Empty results are legitimate
332
- outputs (silence), not errors.
460
+ steps (`bridgeMiss`, `narrowDecision`, `thinGrounding`, `skipMechanism` with the
461
+ reason it skipped, `anchorFallback`), the `echoed` flag on recall's last tier,
462
+ `recall-echo` provenance, and the per-region/per-anchor rejection reasons in the
463
+ climb's structured payload. Empty results are legitimate outputs (silence), not
464
+ errors — and several suites assert exactly that (5).
333
465
 
334
466
  _Follow it:_ when your code can degrade, emit a counter or trace step. And mind
335
467
  the classic trap: **empty bytes are truthy** — `Uint8Array(0)` passes
@@ -374,10 +506,41 @@ _Follow it:_ a new layer that wants to be visible bumps a field in `meter.ts`
374
506
  never a private counter. (`danglingReads`/`compactFailures` in `store.ts` stay:
375
507
  those are session-lifetime HEALTH counters, not per-response work.) Add the
376
508
  counter to the report the same way, and remember the classic trap: **profile
377
- without a trace attached** — a trace bypasses the memos (2.11) and measures a
378
- different machine.
379
-
380
- ### 2.15 Comment style
509
+ without a trace attached** (2.11).
510
+
511
+ ### 2.15 The fold contract: train and infer must agree
512
+
513
+ `perceiveDeposit` and `perceive` must produce the SAME tree for the same bytes.
514
+ That is not a nicety — it is what makes a trained context node and the node
515
+ `resolve(query)` reaches the same node. The deposit path therefore imposes
516
+ nothing: no boundaries, no turn convention, nothing read out of the bytes. When
517
+ the two sides disagreed, the alignment family went quadratic (measured: 5.2M
518
+ cells on a 476-byte context, against 0 when they agree) and cumulative contexts
519
+ stopped resolving to what they were trained as.
520
+
521
+ Three consequences you will meet:
522
+
523
+ - **Boundaries are a separate feature from reuse.** `contentFoldIncremental`
524
+ (segment reuse, transparent, imposes nothing) and `stablePrefixFold`
525
+ (caller-supplied cuts, left-nested, buys prefix-ROOT identity) solve different
526
+ problems. Conflating them is what once put an imposed boundary set on the
527
+ inference path.
528
+ - **A conversation's turn offsets are API metadata**, not a fold instruction.
529
+ They feed `ConversationState`, `answeredSpans` and `currentTurnStart`; the
530
+ geometry never sees them.
531
+ - **Identity must not depend on W**, or on absolute offset. If you are adding
532
+ anything that groups by index — a stride, a tile, a fixed-arity row — you are
533
+ reintroducing the bug content-defined cuts exist to remove. Two such attempts
534
+ are recorded as refuted at `collectRegions` and `contentLevels`; test/59 and
535
+ test/63 pin the invariance floors.
536
+
537
+ _Follow it:_ changes to `contentLevels` — the cut rate, which bits are read, the
538
+ minimum/maximum segment length, the forced cut — are changes to the segment
539
+ DISTRIBUTION every downstream mechanism is fitted to. Each of those four has
540
+ been altered experimentally and cost 5–21 tests. Re-measure the whole suite, not
541
+ the one query that motivated the change.
542
+
543
+ ### 2.16 Comment style
381
544
 
382
545
  Comments state _constraints and failure modes_ — "this guard exists because X
383
546
  breaks without it", often naming the test that pins the behaviour — never
@@ -391,28 +554,32 @@ story of the fix.
391
554
 
392
555
  ## 3. Where things live
393
556
 
394
- | Concept | File(s) |
395
- | :-------------------------------------------- | :--------------------------------------------------------------------- |
396
- | Public surface / assembly | `src/index.ts`, `src/mind/mind.ts` |
397
- | Config (capacities, budgets, seed) | `src/config.ts` |
398
- | Derived thresholds, river fold, Hilbert | `src/geometry.ts` |
399
- | Vector primitives, alphabet, node/fold types | `src/vec.ts`, `src/alphabet.ts`, `src/sema.ts` |
400
- | Store domain logic / SQLite adapter | `src/store.ts`, `src/store-sqlite.ts` |
401
- | Mechanism contract + shared `Precomputed` | `src/mind/pipeline-mechanism.ts` |
402
- | The grounding decider (`think`) | `src/mind/pipeline.ts` |
403
- | Grounding mechanisms (one file each) | `src/mind/mechanisms/{cover,cast,confluence,extraction,recall,alu}.ts` |
404
- | Weighted deduction system + cost ladder | `src/mind/graph-search.ts` (engine in `src/derive/`) |
405
- | Match/project family | `src/mind/match.ts` |
406
- | Graph traversal, corpus scale, disambiguators | `src/mind/traverse.ts` |
407
- | Consensus climb + cross-region attention | `src/mind/attention.ts` |
408
- | Recognition / canonical contract | `src/mind/recognition.ts`, `src/mind/canonical.ts` |
409
- | Junction ascent (bridge + attention share) | `src/mind/junction.ts`, `src/mind/resonance.ts` |
410
- | Learning / ingestion / training cache | `src/mind/learning.ts`, `src/ingest-cache.ts` |
411
- | Post-grounding (reason, fuse, articulate) | `src/mind/reasoning.ts`, `src/mind/articulation.ts` |
412
- | Rationale / trace | `src/mind/rationale.ts`, `src/mind/trace.ts` |
413
- | Computational-usage meter | `src/meter.ts` (harness: `bench/profile-inference.mjs`) |
414
- | Extension host types | `src/extension.ts` |
415
- | Sublibraries (own READMEs, own tests) | `src/derive/`, `src/alu/`, `src/rabitq-ivf/` |
557
+ | Concept | File(s) |
558
+ | :-------------------------------------------------- | :-------------------------------------------------------------------------------- |
559
+ | Public surface / assembly | `src/index.ts`, `src/mind/mind.ts` |
560
+ | Conversation API (turns, state, answered spans) | `src/mind/mind.ts` |
561
+ | Config (capacities, budgets, seed) | `src/config.ts` |
562
+ | Derived thresholds, the fold, Hilbert | `src/geometry.ts` |
563
+ | Content canonicalizer (injected, modality-specific) | `src/canon.ts` |
564
+ | Vector primitives, alphabet, node/fold types, seats | `src/vec.ts`, `src/alphabet.ts`, `src/sema.ts` |
565
+ | Perceive / resolve / read primitives | `src/mind/primitives.ts` |
566
+ | Store domain logic / SQLite adapter | `src/store.ts`, `src/store-sqlite.ts` |
567
+ | Mechanism contract + shared `Precomputed` | `src/mind/pipeline-mechanism.ts` |
568
+ | The grounding decider (`think`) | `src/mind/pipeline.ts` |
569
+ | Grounding mechanisms (one file each) | `src/mind/mechanisms/{cover,cast,confluence,extraction,recall,alu}.ts` |
570
+ | Weighted deduction system + cost ladder | `src/mind/graph-search.ts` (engine in `src/derive/`) |
571
+ | Match/project family | `src/mind/match.ts` |
572
+ | Graph traversal, corpus scale, disambiguators | `src/mind/traverse.ts` |
573
+ | Consensus climb + cross-region attention | `src/mind/attention.ts` |
574
+ | Recall's refusal-path tiers | `src/mind/bridge.ts`, `src/mind/prefix-completion.ts`, `src/mind/frame-filler.ts` |
575
+ | Recognition / canonical contract | `src/mind/recognition.ts`, `src/mind/canonical.ts` |
576
+ | Junction ascent (bridge + attention share) | `src/mind/junction.ts`, `src/mind/resonance.ts` |
577
+ | Learning / ingestion / training cache | `src/mind/learning.ts`, `src/ingest-cache.ts` |
578
+ | Post-grounding (reason, fuse, articulate) | `src/mind/reasoning.ts`, `src/mind/articulation.ts` |
579
+ | Rationale / trace | `src/mind/rationale.ts`, `src/mind/trace.ts` |
580
+ | Computational-usage meter | `src/meter.ts` (harness: `bench/profile-inference.mjs`) |
581
+ | Extension host types | `src/extension.ts` |
582
+ | Sublibraries (own READMEs, own tests) | `src/derive/`, `src/alu/`, `src/rabitq-ivf/` |
416
583
 
417
584
  Mind functions are **free functions over `MindContext`** (`mind/types.ts`), not
418
585
  methods — `mind.ts` is a thin assembly that implements the context and
@@ -427,13 +594,19 @@ with no hidden `this` state.
427
594
 
428
595
  Implement `PipelineMechanism`: `floor` returns an admissible bound or `null`
429
596
  (structurally can't fire); `run` returns candidates with `bytes`, `accounted`,
430
- `moves`, `unexplained`; add `parse` only if you compute authoritative spans (the
431
- ALU's `aluToMechanism` in `mechanisms/alu.ts` is the reference). Register with
597
+ `moves`, `unexplained` (plus `scaffolding`/`complete` when your mechanism can
598
+ state them); add `parse` only if you compute authoritative spans (the ALU's
599
+ `aluToMechanism` in `mechanisms/alu.ts` is the reference). Register with
432
600
  `new Mind({ mechanismFactories: [host => yourMechanism(host)] })` (or
433
601
  `mechanisms: [...]` if no host is needed); reach meaning only through the
434
602
  `ExtensionHost`. Verify the four market constraints (2.6). You never touch
435
603
  `think()` or another mechanism's file.
436
604
 
605
+ If your `floor` needs an expensive shared analysis to be tight, check
606
+ `worthRunning(cheapestBound)` FIRST and return the uninvested bound when it
607
+ already fails — that is the investment discipline (2.6), and `cast.ts` /
608
+ `extraction.ts` are the two reference implementations.
609
+
437
610
  ### Add an ALU operation
438
611
 
439
612
  One declarative `registry.derive(name, arity, surfaceForms, body)` in the
@@ -464,6 +637,29 @@ Hilbert-linearizes it; `Grid[]` stacks frames. Anything else: produce a
464
637
  `Uint8Array` with a deterministic, locality-preserving ordering. Nothing
465
638
  downstream changes.
466
639
 
640
+ A modality may also supply its own **canonicalizer** (`Canon`) and its own
641
+ reading of "edge" — that is the one place presentation rules belong. Nothing in
642
+ the store or the mind's core knows what case or whitespace is; the text entry
643
+ points inject `textCanon`/`textEdgeTrim`, byte and grid inputs inject neither
644
+ (for them `0x20` is content). If you find yourself adding a character class
645
+ inside a mechanism, it belongs here instead.
646
+
647
+ ### Hold a conversation
648
+
649
+ ```ts
650
+ const conv = mind.beginConversation(savedState); // state optional
651
+ const { response, state } = await mind.respondTurnText(conv, "…");
652
+ mind.addTurn(conv, "…"); // a turn to hear but not answer
653
+ mind.endConversation(conv);
654
+ ```
655
+
656
+ Turns append raw bytes plus an offset — never a separator. To replay a corpus
657
+ that joins turns with `"\n"`, pass `"\n" + turnText` as the turn; the separator
658
+ rides inside the turn bytes, where it belongs. `ConversationState` (context,
659
+ boundaries, answered spans) is serialisable and restores exactly. One
660
+ `respondTurn` may be in flight per Mind: the conversation's memos are swapped
661
+ into the response-scoped slots for the turn's duration.
662
+
467
663
  ### Debug an answer
468
664
 
469
665
  ```ts
@@ -476,7 +672,18 @@ console.log(r.provenance); // cast | join | cover | extract | recall | recall-ec
476
672
  Read top-down: which mechanism fired (and why the others abstained), what
477
673
  recognition found, how the climb voted, which edges were followed
478
674
  (`disambiguate` steps carry the evidence). `recall-echo` means "nearest stored
479
- form, not a derived fact". Remember traced responses bypass memos (2.11).
675
+ form, not a derived fact".
676
+
677
+ Three steps carry **structured data**, so tooling need not parse notes:
678
+ `decideGrounding` (every candidate's provenance, exact weight, discrete grade,
679
+ unexplained bytes, which won, plus the runner-up margin), `climbConsensus` (per
680
+ region: source, span, selected anchor, IDF, contrastive margin and its rival,
681
+ mutual weight, outcome; per anchor: pooled vote, peak, breadth, clusters, and
682
+ the live commit verdict with its rejection reasons; plus every cross-region
683
+ probe and its tier), and `narrowDecision`. When an answer is wrong, the fastest
684
+ route is usually `decideGrounding` first (was the right mechanism outbid, or did
685
+ it never produce a candidate?), then the per-region climb detail (did the
686
+ evidence vote at all?).
480
687
 
481
688
  ### Profile an answer
482
689
 
@@ -497,7 +704,8 @@ Use `CachedIngest` (`ingest-cache.ts`) as a drop-in for `mind.ingest` — it
497
704
  memoises perceive+intern of repeated inputs and routes through the same
498
705
  `dispatchIngest` as the direct path (shape detection can't drift). Call
499
706
  `store.commit()` at checkpoints; run `compactContentIndex` /
500
- `repairContentIndex` post-training if eviction was heavy. See
707
+ `repairContentIndex` post-training if eviction was heavy, and
708
+ `mind.buildCanonIndex()` if queries will carry a canonicalizer (2.9). See
501
709
  `example/train_base.ts`. Profiling note: the first `resonate` after a big ingest
502
710
  pays the pending index flush; the dominant query-side ANN cost is connector
503
711
  pre-resolution (bounded by recognised-site count — don't add another loop over
@@ -516,10 +724,15 @@ against the built `dist/` (`npm test`; one suite:
516
724
  - Many tests pin **contracts that look like implementation details** (the bridge
517
725
  tier order, the bridge's identity admission vs. its prefix trap and the
518
726
  scaffolding reading behind it, the two span-shape readings (`match.ts`),
519
- `MechanismResult.complete`, determinism, honest silence). A "simplification"
520
- that fails an existing test is wrong until you can argue the _test_ is wrong —
521
- several guards exist precisely because a plausible simplification once failed
522
- a dozen suites.
727
+ `MechanismResult.complete`, fold invariance under shifts (test/59, test/63),
728
+ recognition's idempotence under trace (test/42), the cross-region tier ladder
729
+ (test/51), the instrumentation payloads (test/52–55), determinism, honest
730
+ silence). A "simplification" that fails an existing test is wrong until you
731
+ can argue the _test_ is wrong — several guards exist precisely because a
732
+ plausible simplification once failed a dozen suites.
733
+ - **Honest silence is a tested behaviour, not an absence of one.** Several
734
+ suites assert that a query grounds NOTHING; a change that makes the engine
735
+ more forthcoming fails them, and that is the suite working.
523
736
  - Sublibraries test themselves (`src/{alu,derive,rabitq-ivf}/test/`) with zero
524
737
  Sema dependency. Keep it so.
525
738
  - Performance claims are tested (the rabitq-ivf benchmark asserts sub-linear