@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.
Files changed (86) hide show
  1. package/AGENTS.md +29 -29
  2. package/TRADEMARKS.md +0 -1
  3. package/dist/src/config.d.ts +11 -0
  4. package/dist/src/config.js +2 -0
  5. package/dist/src/geometry.d.ts +21 -0
  6. package/dist/src/geometry.js +21 -0
  7. package/dist/src/meter.d.ts +51 -0
  8. package/dist/src/meter.js +51 -0
  9. package/dist/src/mind/attention.d.ts +4 -0
  10. package/dist/src/mind/attention.js +165 -16
  11. package/dist/src/mind/canonical.d.ts +16 -0
  12. package/dist/src/mind/canonical.js +41 -0
  13. package/dist/src/mind/graph-search.js +33 -14
  14. package/dist/src/mind/match.d.ts +1 -1
  15. package/dist/src/mind/match.js +5 -3
  16. package/dist/src/mind/mechanisms/cast.js +1 -1
  17. package/dist/src/mind/mechanisms/confluence.js +24 -0
  18. package/dist/src/mind/mechanisms/recall.js +32 -4
  19. package/dist/src/mind/mind.d.ts +4 -2
  20. package/dist/src/mind/mind.js +5 -4
  21. package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
  22. package/dist/src/mind/pipeline.js +41 -14
  23. package/dist/src/mind/primitives.js +9 -1
  24. package/dist/src/mind/rationale.d.ts +28 -1
  25. package/dist/src/mind/rationale.js +22 -1
  26. package/dist/src/mind/reasoning.d.ts +21 -3
  27. package/dist/src/mind/reasoning.js +73 -21
  28. package/dist/src/mind/recognition.js +4 -8
  29. package/dist/src/mind/resonance.js +20 -1
  30. package/dist/src/mind/trace.js +1 -0
  31. package/dist/src/mind/traverse.js +6 -2
  32. package/dist/src/mind/types.d.ts +36 -13
  33. package/docs/INVARIANTS.md +2 -2
  34. package/docs/architecture/bounded-reads.md +1 -1
  35. package/docs/architecture/commonality.md +2 -2
  36. package/docs/architecture/cost-model.md +2 -2
  37. package/docs/architecture/determinism.md +7 -7
  38. package/docs/architecture/match-project.md +2 -3
  39. package/docs/architecture/mechanism-market.md +10 -10
  40. package/docs/architecture/meter.md +5 -5
  41. package/docs/architecture/store.md +3 -3
  42. package/docs/failures/tempting-but-wrong.md +3 -4
  43. package/docs/harness/gates.md +2 -2
  44. package/docs/mechanisms/cast.md +2 -2
  45. package/docs/mechanisms/cover.md +2 -3
  46. package/docs/mechanisms/extraction.md +7 -7
  47. package/docs/mechanisms/recall.md +8 -9
  48. package/jsr.json +1 -1
  49. package/package.json +1 -1
  50. package/src/alu/README.md +11 -12
  51. package/src/config.ts +13 -0
  52. package/src/geometry.ts +21 -0
  53. package/src/meter.ts +51 -0
  54. package/src/mind/attention.ts +167 -16
  55. package/src/mind/canonical.ts +43 -0
  56. package/src/mind/graph-search.ts +39 -14
  57. package/src/mind/match.ts +5 -3
  58. package/src/mind/mechanisms/cast.ts +3 -1
  59. package/src/mind/mechanisms/confluence.ts +24 -0
  60. package/src/mind/mechanisms/recall.ts +32 -4
  61. package/src/mind/mind.ts +6 -4
  62. package/src/mind/pipeline-mechanism.ts +7 -0
  63. package/src/mind/pipeline.ts +49 -16
  64. package/src/mind/primitives.ts +9 -1
  65. package/src/mind/rationale.ts +35 -1
  66. package/src/mind/reasoning.ts +92 -15
  67. package/src/mind/recognition.ts +4 -8
  68. package/src/mind/resonance.ts +19 -1
  69. package/src/mind/trace.ts +1 -0
  70. package/src/mind/traverse.ts +7 -5
  71. package/src/mind/types.ts +36 -13
  72. package/test/105-derive-through-reports-its-refusal.test.mjs +24 -0
  73. package/test/118-the-join-reaches-a-key-off-the-cut.test.mjs +74 -0
  74. package/test/119-the-work-does-not-grow-with-the-corpus.test.mjs +122 -0
  75. package/test/120-composition-is-consequence.test.mjs +132 -0
  76. package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +128 -0
  77. package/test/122-the-climb-search-does-not-grow-with-the-corpus.test.mjs +117 -0
  78. package/test/123-the-paired-formulas-agree.test.mjs +90 -0
  79. package/test/125-the-post-grounding-branch-publishes-its-operand.test.mjs +51 -0
  80. package/test/126-the-pipeline-does-not-name-mechanisms.test.mjs +42 -0
  81. package/test/128-the-leads-somewhere-pair-agrees.test.mjs +83 -0
  82. package/test/129-the-trace-payload-shape.test.mjs +164 -0
  83. package/test/32-confluence.test.mjs +68 -0
  84. package/test/38-reason-restate-guard.test.mjs +8 -2
  85. package/test/43-cast-analog-seat.test.mjs +10 -0
  86. package/test/55-cost-meter.test.mjs +859 -0
@@ -6,8 +6,9 @@ import { rItem, rNode } from "./trace.js";
6
6
  import { bytesEqual, indexOf } from "../bytes.js";
7
7
  import { resolve } from "./primitives.js";
8
8
  import { hubBound } from "./traverse.js";
9
- import { follow, haloSiblings, project } from "./match.js";
9
+ import { containsSpan, follow, haloSiblings, project } from "./match.js";
10
10
  import { joinWithBridge, pivotInto } from "./resonance.js";
11
+ import { unaccountedBytes } from "./rationale.js";
11
12
  /** Whether `bytes` is a proper byte-subspan of `query` — already present in
12
13
  * the question, so voicing it back only restates part of what was asked,
13
14
  * never answers it. The exact guard recallByResonance already applies to
@@ -21,15 +22,6 @@ import { joinWithBridge, pivotInto } from "./resonance.js";
21
22
  export function restatesQuery(query, bytes) {
22
23
  return bytes.length < query.length && indexOf(query, bytes, 0) >= 0;
23
24
  }
24
- /** Extend a grounded answer forward across facts (multi-hop reasoning).
25
- * Pivots on the longest unconsumed learnt context each answer contains,
26
- * then follows the pivot's continuation to the next fact. Repeats up
27
- * to `cfg.recallQueryK` hops. `preConsumed` carries node ids already
28
- * spoken for by the grounding stage (cover/extract/CAST). `voiced` carries
29
- * the BYTES of the anchors a mechanism declared it voiced (its `used` set),
30
- * when it declared one — see the pivot's own containment rule. `pre` is the
31
- * response's shared pre-computation — the post-grounding stages read the
32
- * same container the mechanisms did. */
33
25
  export async function reason(ctx, query, answer, preConsumed, pre, voiced = [],
34
26
  /** The query material the GROUNDING left uncovered — the cost ladder's own
35
27
  * `unaccounted` spans. Only the reasoner's OWN extensions are judged
@@ -41,8 +33,9 @@ uncovered = []) {
41
33
  // back. The grounded answer alone is the honest read-out. Deliberately a
42
34
  // broad structural gate; pinned by test/31-audit.
43
35
  const qId = pre.queryResolved;
44
- if (qId !== null && ctx.store.prevCount(qId) > 0)
45
- return answer;
36
+ if (qId !== null && ctx.store.prevCount(qId) > 0) {
37
+ return { bytes: answer, carried: [], steps: 0 };
38
+ }
46
39
  // Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
47
40
  // the hub bound, via the store's LIMITed edge reads: a common continuation's
48
41
  // reverse fan-in (and a hub context's forward fan-out) is corpus-sized, and
@@ -99,7 +92,7 @@ uncovered = []) {
99
92
  ? null
100
93
  : ctx.store.prevFirst(groundedId, bound);
101
94
  if (qId !== null && groundedPrev !== null && groundedPrev.includes(qId)) {
102
- return answer;
95
+ return { bytes: answer, carried: [], steps: 0 };
103
96
  }
104
97
  const consumed = new Set();
105
98
  /** `prev` lets a caller hand in an already-read reverse-edge list — hop 0
@@ -147,7 +140,27 @@ uncovered = []) {
147
140
  const qv = pre.guide; // the response-wide guide IS the query's gist
148
141
  let t;
149
142
  const startedFrom = answer;
150
- for (let hop = 0; hop < ctx.cfg.recallQueryK; hop++) {
143
+ // INSTRUMENTATION ONLY — the two facts the extension's own decision already
144
+ // used and threw away: the spans of uncovered material each step was
145
+ // JUSTIFIED by (the gate below computes which span carries it and kept only a
146
+ // boolean), and how many steps were taken. Nothing here decides anything:
147
+ // both are read after the loop, to bump counters and to let the caller compare
148
+ // the extension's cost against what it explains, in the ladder's own currency.
149
+ const carried = [];
150
+ let steps = 0;
151
+ // NO ALLOWANCE: THE CHAIN ENDS WHEN IT STOPS. Every exit below is the law —
152
+ // no pivot, no forward step, no question material carried — and the walk is
153
+ // bounded by the material and the graph rather than by a count: each taken
154
+ // step must carry a W-window of the uncovered material (finite), and
155
+ // `consumed` refuses to revisit a node. `recallQueryK` no longer bounds the
156
+ // reasoner here; it keeps its other roles (the bridge's candidate reads, the
157
+ // pivot's probe budget, the resonance limits).
158
+ //
159
+ // Measured before removing it: test/89 — the corpus-cost guard, the heaviest
160
+ // case in the suite — is green and no slower without the allowance (27 s
161
+ // against 30 s); and raising it from 12 to 200 changed neither the answer nor
162
+ // `pivotSteps` on the chain fixtures.
163
+ for (let hop = 0;; hop++) {
151
164
  // Hop 0's `cur` IS `answer`, so the guard above already resolved it and
152
165
  // read its reverse edges — reuse both rather than repeat them.
153
166
  const curId = hop === 0 ? groundedId : resolve(ctx, cur);
@@ -170,6 +183,7 @@ uncovered = []) {
170
183
  ]);
171
184
  ctx.trace?.step("absorbForward", [rItem(cur, "answer", curId)], [rItem(fwd, "answer", resolve(ctx, fwd) ?? undefined)], "the answer is itself a learnt fact — follow its continuation to the fixpoint");
172
185
  cur = fwd;
186
+ steps++;
173
187
  continue;
174
188
  }
175
189
  }
@@ -205,14 +219,19 @@ uncovered = []) {
205
219
  if (!producerOwnsShape && uncovered.length > 0) {
206
220
  const W = ctx.space.maxGroup;
207
221
  let progress = false;
222
+ let justified;
208
223
  for (const [a, b] of uncovered) {
209
224
  for (let i = a; i + W <= b && !progress; i++) {
210
- if (indexOf(fc, query.subarray(i, i + W), 0) >= 0)
225
+ if (indexOf(fc, query.subarray(i, i + W), 0) >= 0) {
211
226
  progress = true;
227
+ justified = [a, b];
228
+ }
212
229
  }
213
230
  if (progress)
214
231
  break;
215
232
  }
233
+ if (progress && justified !== undefined)
234
+ carried.push(justified);
216
235
  if (!progress) {
217
236
  // THE BRAKE, MADE VISIBLE. The reasoner declines a step that carries
218
237
  // none of the material the grounding left uncovered — the drift the
@@ -223,7 +242,25 @@ uncovered = []) {
223
242
  // the check disabled, test/110 and test/116 fail — so this brake is the
224
243
  // only thing keeping the extension honest until the pivot reports its
225
244
  // own accounted spans and the ladder can judge it instead.
226
- const left = uncovered.reduce((n, [a, b]) => n + (b - a), 0);
245
+ //
246
+ // THE PROMISE IS NOW KEPT, AND THE BRAKE TURNS OUT TO BE THE LADDER'S
247
+ // OWN CONSEQUENCE. The extension reports what it carried (`carried`,
248
+ // the span each step was justified by) and what it cost (`steps`), both
249
+ // counted in the meter (`reasonCarriedBytes`, `reasonSteps`), so the
250
+ // ladder CAN judge it: it accepts while
251
+ //
252
+ // steps · STEP < PASS · carried
253
+ //
254
+ // and this brake accepts whenever the step carries a `W`-window, i.e.
255
+ // whenever `carried ≥ W ≥ 1`. With `PASS/STEP = 1000` the two therefore
256
+ // agree on every extension with `steps ≤ 1000 · carried` — and every
257
+ // extension this repository produces takes 0 or 1 steps (measured on
258
+ // chains of 3, 8, 20 and 40 links). Above that bound the ladder would
259
+ // refuse what this brake accepts, which is the corner named in the
260
+ // closure report's limits: a chain of thousands of links explaining a
261
+ // handful of bytes. No guard is added for it — a limit without a
262
+ // derivation is exactly what the brake must not become.
263
+ const left = unaccountedBytes(uncovered);
227
264
  ctx.trace?.step("pivotRefused", [rItem(cur, "answer"), rItem(query, "query")], uncovered.map(([a, b]) => rItem(query.subarray(a, b), "uncovered")), `the step carries none of the question material the grounding left ` +
228
265
  `uncovered (${left} byte(s) in ${uncovered.length} span(s)) — refused`);
229
266
  break;
@@ -234,9 +271,23 @@ uncovered = []) {
234
271
  t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
235
272
  ctx.trace?.step("pivotStep", [rItem(cur, "answer"), rNode(ctx, pivot, "pivot")], [rItem(fc, "answer", resolve(ctx, fc) ?? undefined)], "pivot on the shared span this answer contains, then step forward across that fact");
236
273
  cur = fc;
274
+ steps++;
275
+ }
276
+ // INSTRUMENTATION ONLY — the extension's two facts, untraced (meter.ts
277
+ // contract 1: a counter never reaches a decision). They are what a caller
278
+ // needs to PRICE the extension instead of taking it unconditionally: the work
279
+ // it did (`steps · STEP`) and the uncovered material it carried.
280
+ if (ctx.meter) {
281
+ ctx.meter.reasonSteps += steps;
282
+ ctx.meter.reasonCarriedBytes += unaccountedBytes(carried);
237
283
  }
238
- t?.done([rItem(cur, "answer", resolve(ctx, cur) ?? undefined)], "the multi-hop chain's fixpoint");
239
- return cur;
284
+ t?.done([rItem(cur, "answer", resolve(ctx, cur) ?? undefined)],
285
+ // A FIXPOINT: no further step was possible. This note used to also cover an
286
+ // exhausted hop allowance — a different fact, and the reason F1 added a
287
+ // counter for it. The allowance is gone, so the only way out of the loop is
288
+ // a refusal, and the note is true again by construction.
289
+ "the multi-hop chain's fixpoint");
290
+ return { bytes: cur, carried, steps };
240
291
  }
241
292
  /** Fuse independent points of attention into one answer (multi-topic).
242
293
  * When the consensus climb finds more than one dominant point, each
@@ -405,8 +456,9 @@ primarySpans = []) {
405
456
  out = await joinWithBridge(ctx, out, pieces[i].bytes);
406
457
  }
407
458
  t?.done([rItem(out, "answer", resolve(ctx, out) ?? undefined)], `fused ${pieces.length} independent points of attention into one answer`);
459
+ // THE FACT IS THE FUSED ANSWER, not the call: every early return above hands
460
+ // back `primary` untouched. Untraced on purpose (meter.ts contract 1).
461
+ if (ctx.meter)
462
+ ctx.meter.fuseRuns++;
408
463
  return out;
409
464
  }
410
- // (resonance.js is already a static dependency above — `bridge` — so the old
411
- // dynamic import of pivotInto guarded against a cycle that does not exist.)
412
- import { containsSpan } from "./match.js";
@@ -519,8 +519,11 @@ function recogniseImpl(ctx, bytes) {
519
519
  if (end - start < W)
520
520
  return false;
521
521
  if (flatProbe(start, end) === null) {
522
- if (!canonBudget)
522
+ if (!canonBudget) {
523
+ if (ctx.meter)
524
+ ctx.meter.canonProbesDenied++;
523
525
  return false;
526
+ }
524
527
  if (!canonAdmits(start, end))
525
528
  return false;
526
529
  }
@@ -543,13 +546,6 @@ function recogniseImpl(ctx, bytes) {
543
546
  // endpoints in order and running out partway along the query. A form
544
547
  // longer than that is out of this tier's reach — but so is a form the
545
548
  // chain cannot span, and that is exactly the trade the budget prices.
546
- // The factor is chainReach(W), the same W² scale the chain already
547
- // trusts; no new constant.
548
- // The factor is chainReach(W) — the same W² scale the chain itself
549
- // trusts — so the cap is derived from the fold's geometry, never tuned.
550
- // (It was briefly an environment variable while the cost was being
551
- // measured; an env-read here would make inference non-reproducible,
552
- // which the determinism contract forbids outright.)
553
549
  // The factor is chainReach(W) — the same W² scale the chain itself
554
550
  // trusts — so the cap is derived from the fold's geometry, never tuned.
555
551
  // (It was briefly an environment variable while the cost was being
@@ -249,7 +249,14 @@ export async function joinWithBridge(ctx, left, right) {
249
249
  * overlap, so C3's genuine further hop — "Mona Lisa", a term inside the seat
250
250
  * sentence but part of NEITHER analog — still fires. */
251
251
  export async function pivotInto(ctx, answer, consumed, voiced = []) {
252
- const k = ctx.cfg.recallQueryK;
252
+ // The pivot's OWN shortlist capacity — not `recallQueryK`: they are different
253
+ // quantities (a mechanical sweep's probe budget vs the bridge's candidate-read
254
+ // allowance), and one number serving both means tightening either silently
255
+ // starves the other. The reason is the duplication, NOT a measured strangle:
256
+ // an earlier version of this comment claimed "at recallQueryK 1 the pivot finds
257
+ // no pivot", and that was probed and is FALSE on test/23's fixture — the
258
+ // strangle is fixture-specific, so it is not the evidence for this split.
259
+ const k = ctx.cfg.pivotProbeK;
253
260
  // ONE perception of the answer, shared by the probe budget and the walk —
254
261
  // this used to fold the same bytes twice, back to back, on every hop.
255
262
  const tree = perceive(ctx, answer);
@@ -288,6 +295,18 @@ export async function pivotInto(ctx, answer, consumed, voiced = []) {
288
295
  for (const c of n.kids)
289
296
  queue.push(c); // breadth-first: larger regions first
290
297
  }
298
+ // The sweep's two FACTS, untraced (meter.ts contract 1: a counter never
299
+ // reaches a decision). `probes` is the work done; the shortfall is the
300
+ // capacity the cap withheld — the branches the breadth-first order never got
301
+ // to. Whether that withheld anything that mattered is NOT said here: the
302
+ // order spends the largest regions first, and recognition below still
303
+ // contributes every exact containment candidate regardless of the budget.
304
+ if (ctx.meter) {
305
+ ctx.meter.pivotProbes += probes;
306
+ const unprobed = branchCount - probeCap;
307
+ if (unprobed > 0)
308
+ ctx.meter.pivotBranchesUnprobed += unprobed;
309
+ }
291
310
  // THE FULL recognition, memo-shared with every other reader of these bytes.
292
311
  // A "skip the edge trims here" variant was refuted (see recognise's own
293
312
  // note): those trims are what find a WHOLE trained form embedded at an
@@ -8,6 +8,7 @@ import { decodeText } from "./rationale.js";
8
8
  export function rItem(bytes, role, node, span) {
9
9
  return {
10
10
  text: decodeText(bytes),
11
+ bytes,
11
12
  role,
12
13
  node: node ?? undefined,
13
14
  span,
@@ -8,6 +8,12 @@
8
8
  import { cosine } from "../vec.js";
9
9
  import { gistOf, read } from "./primitives.js";
10
10
  import { canonicalWindows, leafIdPrefix, leafIdRun } from "./canonical.js";
11
+ // Imported at the TOP, where every other import is. They used to sit 800 lines
12
+ // down under a note claiming the position mattered ("before trace module is
13
+ // loaded") — it does not: an ES module's static imports are HOISTED, so the
14
+ // file's line order never decides load order. The note described an intention
15
+ // the runtime does not honour; the imports move and the claim goes.
16
+ import { decodeText } from "./rationale.js";
11
17
  //
12
18
  // Budgeted on the same terms as the reach memo below (caches.md): these three
13
19
  // maps are cleared on every write, but a long read-only session over a large
@@ -704,8 +710,6 @@ export function chooseAmong(ctx, candidates, guide) {
704
710
  ? { id: found.item, score: found.score }
705
711
  : { id: candidates[0], score: -Infinity };
706
712
  }
707
- // ── Trace shim (used by chooseNext before trace module is loaded) ────────
708
- import { decodeText } from "./rationale.js";
709
713
  function rItemShort(ctx, id, role, score) {
710
714
  return {
711
715
  text: decodeText(read(ctx, id)),
@@ -35,15 +35,21 @@ export interface GraphSearchHost {
35
35
  starts: ReadonlySet<number>;
36
36
  };
37
37
  chooseNext?(node: number): number | undefined;
38
- /** The boundary positions of `bytes` under the engine's ONE boundary rule
39
- * (geometry.ts's `contentBoundaries`), or undefined when the host has no
40
- * space to ask. The join's key is an entity plus a prefix of the tail, and
41
- * the prefix that names a stored relation ENDS on one of these boundaries —
42
- * measured, 5 of 5 accepted keys over four join-firing queries, where the
43
- * byte-by-byte scan spent 153 probes for 14 boundaries. Boundaries are
44
- * content-defined and STABLE under prefix extension, which is why a corpus
45
- * key's end is a boundary of the query's own fold of the same bytes. */
46
- contentCuts?(bytes: Uint8Array): readonly number[];
38
+ /** The lengths `p` for which `prefix ‖ tail[0..p]` IS A STORED NODE, ascending
39
+ * — the join's candidate set, in the tail's own coordinates. Optional: a host
40
+ * that cannot answer makes the join fall back to every prefix, which is exact
41
+ * and complete but pays a `resolve` per offset.
42
+ *
43
+ * WHY NOT THE FOLD'S CUTS. A key names a relation exactly when the
44
+ * concatenation is a node, and a node's end is the end of ITS OWN stream —
45
+ * where the fold never emits a cut (geometry's `emit` guards `at >= n`). So a
46
+ * key can end strictly inside the tail with no boundary anywhere near it:
47
+ * measured, "stockholm mayor" exists, leads on to the mayor fact, and its
48
+ * boundary 6 is in neither the tail's cuts ([4,7]) nor the concatenation's.
49
+ * The fold's boundaries are a SUBSET of the real ends, not a proxy for them,
50
+ * and using them skipped the shortest names first — which is a semantic law,
51
+ * not an optimisation (test/106, test/108 pin it). */
52
+ contentKeyEnds?(prefix: Uint8Array, tail: Uint8Array): readonly number[];
47
53
  /** The admission predicate — `traverse.ts`'s `leadsSomewhere`, its ONE
48
54
  * definition: does this node bear an edge or a halo? Optional, so a bare
49
55
  * host (a raw Store and nothing else) still works; when present, the search
@@ -104,11 +110,28 @@ export interface Attention {
104
110
  * strength and its place.
105
111
  * `vote` is a sum over every region that agreed, so it grows with how many
106
112
  * places corroborated; `peak` is what the strongest one of them said on its
107
- * own. A consumer holding this point to consensusFloor(N) — a bar that
108
- * prices ONE region's maximally-discriminative evidence — must read `peak`,
109
- * not `vote`: six scaffolding regions summing past the floor is not the
110
- * same claim as one region clearing it. */
113
+ * own. THIS USED TO PRESCRIBE THE WRONG OPERAND. It read: "a consumer
114
+ * holding this point to consensusFloor(N) — a bar that prices ONE region's
115
+ * maximally-discriminative evidence — must read `peak`, not `vote`." The
116
+ * engine reads the POOLED vote, and thresholds.md §2 derives the floor for
117
+ * exactly that ("Pooled-vote significance floor": one maximally-specific
118
+ * region contributes at most ln N, and ln(N)+1/2 demands corroboration
119
+ * BEYOND one region). MEASURED across 27 anchors on 6 queries: all 11
120
+ * admissions cleared the floor by the sum and NONE by `peak` alone — a gate
121
+ * reading `peak` would refuse every root the engine elects. `peak` remains
122
+ * what it is: the strongest SINGLE region's contribution. */
111
123
  peak: number;
124
+ /** The IDF-WEIGHTED sum behind this point — the quantity `consensusFloor` is
125
+ * derived for, and therefore the one the floor gates must read. It is
126
+ * MODE-INDEPENDENT by construction (its per-region weight is
127
+ * `mutual · idf / roots`, never the mode-dependent `wf`), so gating on it
128
+ * makes an anchor's admission the same in `inverse`, `direct` and `combined`.
129
+ * In `inverse` — the only mode the engine runs — it equals `vote` exactly
130
+ * (measured, test/55 test 17), so nothing about today's verdicts changes.
131
+ * MEASURED before this field existed: gating on `vote` DID flip a verdict,
132
+ * anchor 87 of test/55's query (inverse 2.682 admitted, direct 1.468
133
+ * refused, floor 2.292). */
134
+ idfVote: number;
112
135
  /** SCALE-INVARIANT confidence: the fraction of the query's OWN regions
113
136
  * whose evidence this point accounts for (Σ RegionVote.absorbed among
114
137
  * its contributors, over the query's total region count) — read PER-
@@ -11,9 +11,9 @@
11
11
  | 5 | Bounded reads | `src/store.ts:AbstractStore:nextFirst,parentsFirst,containersSlice,hasNext,bytesPrefix` `src/mind/traverse.ts:hubBound,hubCap` | `test/90` `test/14` | `bounded-reads.md` |
12
12
  | 6 | Fold contract | `src/geometry.ts:contentLevels` `src/mind/canonical.ts:canonicalWindows,chainReach` `src/canon.ts:canonicalizer` | `test/59` `test/63` | `fold-contract.md` |
13
13
  | 7 | Mechanism market | `src/mind/pipeline-mechanism.ts:PipelineMechanism,Precomputed` `src/mind/pipeline.ts:think,worthRunning` | `test/01` `test/04` | `mechanism-market.md` |
14
- | 8 | Two commonality measures | `src/mind/traverse.ts:reachOf,dominates,corpusN` (global) `src/mind/match.ts:depth[],MIN_WEAVE` (weave-local) | `test/17` `test/34` | `commonality.md` |
14
+ | 8 | Two commonality measures | `src/mind/traverse.ts:reachOf,dominates,corpusN` (global) `cast.ts:depth[],MIN_WEAVE` (weave-local) | `test/17` `test/34` | `commonality.md` |
15
15
  | 9 | Memoization idempotence | `src/mind/pipeline-mechanism.ts:Precomputed` `src/mind/mind.ts:beginResponse,endResponse,_resolvedSubtrees` | `test/42` | `memoization.md` |
16
16
  | 10 | Caches as budgets | `src/store.ts:BoundedMap` `src/config.ts:StoreConfig:bytesCacheMax,recCacheBytes,haloCacheBytes` | `test/96` `test/91` | `caches.md` |
17
17
  | 11 | Honest degradation | `src/mind/pipeline.ts:weight=moves+PASS*unaccounted` `src/store.ts:BoundedMap:miss→re-derive` | `test/28` `test/84` | `store.md`+`caches.md` |
18
18
  | 12 | Meter contracts | `src/meter.ts:Meter,PhaseCost,time` `src/mind/pipeline-mechanism.ts:Precomputed.shared` | `test/55` | `meter.md` |
19
- | 13 | Saturation | `src/mind/traverse.ts:edgeAncestors:SaturationReason` `src/mind/junction.ts:junctionContainersFrom` `src/mind/resonance.ts:pivotInto` | `test/27` `test/16` | `saturation.md` |
19
+ | 13 | Saturation | `traverse.ts:edgeAncestors,types.ts:SaturationReason` `src/mind/junction.ts:junctionContainersFrom` `src/mind/resonance.ts:pivotInto` | `test/27` `test/16` | `saturation.md` |
@@ -18,7 +18,7 @@ boundFor(n) = ceil(sqrt(max(2, n))) // ctx-free reading
18
18
  ```
19
19
 
20
20
  Defined once in `mind/traverse.ts` (`corpusN`, `hubBound`, `hubCap`,
21
- `boundFor`). Every consumer imports them; never spell `Math.sqrt` inline.
21
+ `boundFor`). Every consumer imports them; never re-derive them inline.
22
22
 
23
23
  ## Enforcement at the store level
24
24
 
@@ -19,8 +19,8 @@ scaffolding. Powers the consensus climb, edge following, and vote pooling.
19
19
 
20
20
  ## Weave-local — `depth[]` + `MIN_WEAVE` + `dominates`
21
21
 
22
- _Defined in `src/mind/match.ts` (`depth[]`, `MIN_WEAVE`, `frame`) and gated in
23
- `src/mind/match.ts:frame`; used by CAST._
22
+ _Defined and gated in `src/mind/mechanisms/cast.ts` (`depth[]` from the shared
23
+ weave, `MIN_WEAVE`); used by CAST._
24
24
 
25
25
  For an alignment weave, `depth[i]` counts how many aligned structures cover byte
26
26
  `i` of the query. `MIN_WEAVE = 2` requires agreement beyond a pair (pair columns
@@ -59,8 +59,8 @@ exceeds the true remaining cost.
59
59
  ## Policy is not cost
60
60
 
61
61
  "Computation always wins" is **not** priced into the ladder (a computed result
62
- costs `STEP`, same as a learned edge). It is enforced by masking: `pipeline.ts`
63
- removes recognised sites overlapped by a `ComputedResult` so the computation is
62
+ costs `STEP`, same as a learned edge). It is enforced by masking: `cover.ts`
63
+ removes recognised sites overlapped by a `ComputedResult`, so the computation is
64
64
  the sole completion there. Keep policy in callers; keep the engine neutral.
65
65
 
66
66
  ## Pins
@@ -20,7 +20,7 @@ flaky, the contract was broken, not the test.
20
20
  entropy root. Subsystems derive deterministically:
21
21
 
22
22
  - **Alphabet** — `Alphabet` (`src/alphabet.ts`) via `rng` (`src/vec.ts:rng`)
23
- seeded as `seed ^ seedMask`; builds 16→64→256 vectors by refinement.
23
+ seeded as `seed ^ seedMask`; builds 16→64→256 vectors.
24
24
  - **Keyring / Space** — `Space.seats` (`src/sema.ts:Space`) via `makeKeyring`
25
25
  (`src/vec.ts:makeKeyring`) and `rng` seeded from `seed` in `Mind`
26
26
  (`src/mind/mind.ts`); `fold`/`twoEndedSeat`/`companySignature` are pure over
@@ -34,8 +34,8 @@ derived from `D`/`W`/`N`, not sampled.
34
34
 
35
35
  ## Tie-breaks are corpus-determined
36
36
 
37
- Every choice among equals bottoms out in a fixed ordering — insertion order or
38
- lowest node id. The universal no-evidence fallback is **first-inserted**:
37
+ Every choice bottoms out in a fixed ordering — insertion order or lowest node id
38
+ — not interchangeable (`test/34`). The fallback is **first-inserted**:
39
39
 
40
40
  - `guidedFirst` (`src/mind/traverse.ts:guidedFirst`) — guided pick via
41
41
  `chooseNext` else first-inserted edge (`nextFirst` LIMIT 1).
@@ -46,7 +46,7 @@ lowest node id. The universal no-evidence fallback is **first-inserted**:
46
46
  - `companySignature` (`src/sema.ts:companySignature`) — `rng(id ^ 0x9e3779b9)`,
47
47
  i.e. seeded by node id, not observation order.
48
48
 
49
- Last-inserted was once used in one place; it was a bug. Never reintroduce it.
49
+ Never use last-inserted.
50
50
 
51
51
  ## Memoization and trace must not break identity
52
52
 
@@ -56,7 +56,7 @@ Per-response memos (`Precomputed`, `perceiveMemo`, `recogniseMemo`, `climbMemo`,
56
56
  `src/mind/primitives.ts`) are sound because asking never writes. Only
57
57
  `guidedNext`/`sharedReachMemo` are trace-bypassed;
58
58
  `perceiveMemo`/`recogniseMemo`/`climbMemo` are always consulted — `foldTree`'s
59
- subtree fast path skips `visit` (and thus site emission) for cached subtrees, so
59
+ subtree fast path skips `visit` (and site emission) for cached subtrees, so
60
60
  bypassing makes `recognise` non-idempotent.
61
61
 
62
62
  ## Follow it
@@ -69,5 +69,5 @@ call `Math.random`/`Date.now` on a behavioural path.
69
69
 
70
70
  - `test/42` pins recognition idempotence under trace — traced and untraced
71
71
  `recognise` must return the same cached object and site count.
72
- - Determinism suites — `test/03`, `test/04`, `test/08`, `test/20` and others
73
- assert same seed + same training ⇒ byte-identical answers and stores.
72
+ - Determinism suites — `test/03`, `test/04`, `test/08`, `test/20` — assert same
73
+ seed + same training ⇒ byte-identical answers and stores.
@@ -16,7 +16,7 @@ functions over bytes and the store — no mechanism owns a private copy.
16
16
  | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
17
17
  | **Match** (locate structure) | `locate` (exact → halo → gist ladder), `alignRuns` (literal W-gram weave), `alignGraded` (literal + halo gaps), `alignAround` / `frameSlots` (seeded frame with contracted gaps), `bestHaloMate` (in-list halo), `analogyStrength` / `sharedFrameStrength` (distributional + structural analogy) | Finds where a query sits in a learnt form. |
18
18
  | **Project** (direction) | `follow` (forward to fixpoint, first hop may `conceptHop`), `reverseContext` (reverse to context), `project` (forward else reverse), `conceptHop` (halo sibling with edge) | Moves along the store from the match — forward toward answers, reverse toward contexts. |
19
- | **Gate** (structural licence) | `isSpanShaped` (sparse subsequence — open reading), `carriesFillers` (substitution carriage — strict voicing licence) | Decides whether the shape licences voicing. |
19
+ | **Gate** (structural licence) | `isSpanShaped` (OPEN reading — sparse subsequence), `containsSpan` (STRICT reading — contiguous run or resolved node), `skillExemplar` (anchor → context + answer), `carriesFillers` (substitution carriage — strict voicing licence) | Two readings; not interchangeable. |
20
20
 
21
21
  Mechanisms declare only `(matcher, direction, gate)`. Thresholds behind gates
22
22
  live in `src/geometry.ts` — the match layer never invents a cutoff.
@@ -52,8 +52,7 @@ The shared layer never refuses on a consumer's behalf. Reference owns its four
52
52
  gates: frame dominates the query, each slot reaches `W` on both sides, no
53
53
  insertion/deletion, fillers pairwise distinct — plus `carriesFillers` on the
54
54
  chosen pair. CAST, recall, and cover each apply their own gate over the same
55
- shared inventory. Moving a consumer's gate into `match.ts` would hide who is
56
- responsible for the refusal.
55
+ shared inventory. Moving a gate into `match.ts` would hide who owns the refusal.
57
56
 
58
57
  ## Pins
59
58
 
@@ -19,6 +19,7 @@ interface MechanismResult {
19
19
  unexplained: string;
20
20
  scaffolding?: number;
21
21
  complete?: boolean;
22
+ used?: ReadonlySet<number>;
22
23
  }
23
24
  ```
24
25
 
@@ -30,7 +31,7 @@ interface MechanismResult {
30
31
 
31
32
  ## Decider
32
33
 
33
- `think` in `mind/pipeline.ts` iterates `defaultMechanisms` in list order:
34
+ `think` iterates `defaultMechanisms` in list order:
34
35
 
35
36
  ```
36
37
  defaultMechanisms = [cover, cast, confluence, extraction, reference, recall,
@@ -39,8 +40,7 @@ defaultMechanisms = [cover, cast, confluence, extraction, reference, recall,
39
40
 
40
41
  Weight is one currency: `weight = moves + PASS · unaccountedBytes` where
41
42
  `unaccountedBytes = unexplainedSpans(query.length, accounted)`. Comparison is at
42
- `STEP` grade (`grade = floor(weight/STEP)`); equal grade prefers fewer
43
- `scaffolding` bytes, then list order.
43
+ `STEP` grade ; equal grade prefers fewer `scaffolding` bytes, then list order.
44
44
 
45
45
  ## Four constraints
46
46
 
@@ -48,16 +48,16 @@ Weight is one currency: `weight = moves + PASS · unaccountedBytes` where
48
48
  never touches another; no mechanism asks what already decided.
49
49
  2. **Declared competence** — binary structural gates inside `floor`/`run` (query
50
50
  length, anchor shape, weave existence). Never a learned score; rationale
51
- states exactly why a mechanism abstained.
51
+ states why a mechanism abstained.
52
52
  3. **Visible budget** — every corpus-scale loop is capped at a named constant:
53
53
  `√N` via `hubBound`/`hubCap` and `k = 2·recallQueryK` (`Precomputed.k`).
54
- Enforced at the store level.
54
+ Enforced at the store.
55
55
  4. **Evidence travels** — every candidate carries `accounted` (query spans
56
56
  explained), `moves` (priced on `MICRO/STEP/CONCEPT/PASS`), `unexplained`
57
57
  (diagnostic label); optionally `scaffolding` (answer bytes from unrecognised
58
58
  spans — equal-grade tie-break) and `complete` (trained-form continuation
59
59
  reached via identity; post-grounding must not extend). The decider honours
60
- both without knowing who set them.
60
+ all three without knowing who set them.
61
61
 
62
62
  ## Two disciplines
63
63
 
@@ -70,8 +70,8 @@ Weight is one currency: `weight = moves + PASS · unaccountedBytes` where
70
70
  - **Investment discipline.** `worthRunning` is passed _into_ `floor`. A floor
71
71
  that would first-touch an expensive shared analysis (`pre.attention()` climb,
72
72
  `pre.weave()`, `pre.resonance()`) checks `worthRunning(cheapestBound)` first
73
- and returns the uninvested bound when it already loses. Never compute a shared
74
- analysis just to discard it. `cast.ts`/`extraction.ts` are the references.
73
+ and returns the uninvested bound if it loses. Never compute a shared analysis
74
+ just to discard it. `cast.ts`/`extraction.ts` are the references.
75
75
 
76
76
  ## Accounting
77
77
 
@@ -86,8 +86,8 @@ Weight is one currency: `weight = moves + PASS · unaccountedBytes` where
86
86
  same act is charged twice (`PASS`/byte dominates).
87
87
 
88
88
  `accounted` is a cost-ladder quantity; `cover.ts` leaves masked computed spans
89
- out of it so `PASS`-bridged bytes are still charged. `unexplained`,
90
- `narrowDecision`, `thinGrounding` are observational only.
89
+ out so `PASS`-bridged bytes are still charged. `unexplained`, `narrowDecision`,
90
+ `thinGrounding` are observational only.
91
91
 
92
92
  ## Pins
93
93
 
@@ -17,11 +17,11 @@ it. Harness: `bench/profile-inference.mjs`.
17
17
  non-deterministic hints reported separately — never use them to gate
18
18
  behaviour.
19
19
 
20
- 3. **Phases nest, they do not partition.** `think` contains every mechanism
21
- phase; a mechanism's `floor` contains whatever shared analysis it
22
- first-touched; `recall.run` contains `substitutionBridge`. Read a phase as
23
- inclusive wall-clock — never sum phases and expect the total.
24
- `CostReport.elapsedMs` is the only whole.
20
+ 3. **Phases nest, they do not partition.** Each phase is charged by the layer
21
+ doing the work (`recognise`, the climb's two, the bridge), and a mechanism's
22
+ `floor` contains whatever shared analysis it first-touched. Read a phase as
23
+ inclusive wall-clock; never sum phases. `CostReport.elapsedMs` is the only
24
+ whole.
25
25
 
26
26
  4. **Count once.** Off by default and free when off
27
27
  (`new Mind({ profile:
@@ -36,8 +36,8 @@ root never costs a full walk.
36
36
 
37
37
  ## Gist, halo, dedup
38
38
 
39
- On `put*`, content dedup (`hashOf`→probe→mint) gates first. `DedupKey` caches
40
- short keys (`DEDUP_KEY_MAX` bypass). Near-dedup merges by `mergeThreshold(D)` on
39
+ On `put*`, content dedup (`hashOf`→probe→mint) gates first. Short keys are
40
+ cached (`DEDUP_KEY_MAX` bypass). Near-dedup merges by `mergeThreshold(D)` on
41
41
  unit gist cosine. Gists sit in `_pendingGist` (byte-budgeted `BoundedMap`);
42
42
  `indexSubtree` & `pourHalo` promote via `_vecContentUpsert`/`_vecHaloUpsert` in
43
43
  `batchSize` batches. Buffers flush on cadence, `commit()`, and close. Halo mass
@@ -55,7 +55,7 @@ deferred transaction.
55
55
  Every in-memory cache is a `BoundedMap` with byte accounting and eviction (`lru`
56
56
  vs `smallest` + `clock`/`reorder` recency). ANN reads
57
57
  (`resonate`/`resonateHalo`) are content-addressed (`vecKey`) and dropped on any
58
- index mutation; `RESonate_CACHE_MAX=4096`.
58
+ index mutation; `RESONATE_CACHE_MAX=4096`.
59
59
 
60
60
  ## Maintenance (incremental)
61
61
 
@@ -30,8 +30,7 @@ not to do, why it fails, and what to do instead.
30
30
  - **WRONG:** Break equal-rank ties by picking the most recently inserted
31
31
  edge/node.
32
32
  - **WHY:** Tie-breaks must be corpus-determined and stable; last-inserted is
33
- recency-dependent and was fixed as a bug (`AGENTS §2` Invariant 1 —
34
- first-inserted fallback).
33
+ recency-dependent (`AGENTS §2` Invariant 1 — first-inserted fallback).
35
34
  - **CORRECT:** `guidedFirst`/`chooseNext`/`chooseAmong`: rank then
36
35
  first-inserted (lowest node id / `LIMIT 1` insertion order). Pinned by
37
36
  `test/03-recall.test.mjs` determinism suites.
@@ -55,8 +54,8 @@ not to do, why it fails, and what to do instead.
55
54
  policy is enforced by masking, not pricing (`AGENTS §2` Invariant 4 — One cost
56
55
  currency; `docs/architecture/cost-model.md` § Policy is not cost).
57
56
  - **CORRECT:** Keep `PASS` dominating; enforce precedence in the caller (e.g.
58
- `pipeline.ts` masks recognised sites overlapped by `ComputedResult`). Pinned
59
- by `test/04-think.test.mjs` and `test/55-cost-meter.test.mjs`.
57
+ `cover.ts` masks recognised sites overlapped by `ComputedResult`). Pinned by
58
+ `test/04-think.test.mjs` and `test/55-cost-meter.test.mjs`.
60
59
 
61
60
  ### 6. Reimplementing `locate`/`align` inside a mechanism
62
61
 
@@ -3,7 +3,7 @@
3
3
  Four executable gates. Each: run the command, check what it guards, follow its
4
4
  §.
5
5
 
6
- ## 1 — Correctness (all 90 suites)
6
+ ## 1 — Correctness (all suites)
7
7
 
8
8
  ```bash
9
9
  npm test
@@ -24,7 +24,7 @@ node bench/profile-inference.mjs --trace # trace is a debugging aid, not product
24
24
  ```
25
25
 
26
26
  Guards without trace: counters deterministic and diffable between runs; phases
27
- nest (not disjoint — `think` contains every mechanism phase); shared analyses
27
+ nest (not disjoint — each phase is charged by its own layer); shared analyses
28
28
  charged to themselves, not to the first toucher; millisecond fields are
29
29
  non-deterministic hints only. With `--trace`, recognition idempotence still
30
30
  holds (`test/42`). `src/meter.ts`, `docs/architecture/meter.md`, §55,
@@ -12,7 +12,7 @@ schema yields its own candidate and `think`'s single weight comparison picks.
12
12
  halo-matched `pre.rec.sites`. The product is `pre.weave()` — `points[]` (each
13
13
  with graded `runs[]`) and a per-query-byte `depth[]` (how many structures cover
14
14
  that byte). CAST's single-vs-multi test is measured from those runs: a second
15
- point must add ≥ one perception quantum of coverage the widest point does not.
15
+ point must add ≥ one perception quantum of coverage the widest does not.
16
16
 
17
17
  ## Gate — weave-local discriminative frame
18
18
 
@@ -76,5 +76,5 @@ redirection, or analogical comparison), not from a literal continuation.
76
76
  ## Source
77
77
 
78
78
  `src/mind/mechanisms/cast.ts` (`counterfactualTransfer`, `seatOfNode`,
79
- `MIN_WEAVE`), `src/mind/match.ts` (`alignGraded`, `project`, `depth`),
79
+ `MIN_WEAVE`, `weave.depth`), `src/mind/match.ts` (`alignGraded`, `project`),
80
80
  `src/geometry.ts` (`dominates`), `src/mind/graph-search.ts` (`STEP`).
@@ -15,9 +15,8 @@ consumes them directly; any site whose bytes overlap a computed span is masked
15
15
  - `formRules` follow continuation edges (`GraphSearch.formRules`): each hop
16
16
  costs `STEP` (1). Forks across all continuations up to the hub bound;
17
17
  disambiguation is distributional, not heuristic.
18
- - Edge-less forms may hop via a halo sibling (`conceptHop` / `resolveConcepts`
19
- in `src/mind/mechanisms/cover.ts`) at `CONCEPT` (10), borrowing a synonym's
20
- continuation.
18
+ - Edge-less forms may hop via a halo sibling (`conceptHop` / `resolveConcepts`)
19
+ at `CONCEPT` (10), borrowing a synonym's continuation.
21
20
 
22
21
  ## Gate — `leadsSomewhere` (`src/mind/traverse.ts`)
23
22