@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
@@ -7,13 +7,13 @@ what sits between them.
7
7
 
8
8
  ## Matcher — `skillExemplar` / `isSpanShaped` / `containsSpan` (`src/mind/match.ts`)
9
9
 
10
- An exemplar is span-shaped when its answer is an in-order embedding of its
11
- context. `isSpanShaped` is the open reading (sparse subsequence, any gaps) used
12
- to accept candidates; `answerRunsInContext` is the strong reading (greedy
13
- longest contiguous runs) used to decompose the answer for projection. Candidates
14
- are ranked anchors from `climbAttentionAll` (`Precomputed.spanShapedOf`), tried
15
- in order up to `pre.k`; sub-quantum (`< W = maxGroup`) or unanchored results are
16
- skipped.
10
+ An exemplar is span-shaped when its answer embeds in order. `isSpanShaped` is
11
+ the open reading (sparse subsequence, any gaps) for acceptance; `containsSpan`
12
+ is the strict reading (contiguous run, or a resolved node) that fusion gates on,
13
+ extraction decomposes with `answerRunsInContext` (greedy longest runs).
14
+ Candidates are ranked anchors from `climbAttentionAll`
15
+ (`Precomputed.spanShapedOf`), tried up to `pre.k`; sub-quantum (`< W`) or
16
+ unanchored results are skipped.
17
17
 
18
18
  ## Projection — read between located frames (`src/mind/mechanisms/extraction.ts`)
19
19
 
@@ -24,16 +24,15 @@ W = `maxGroup` (river window); bars from `src/geometry.ts`.
24
24
  ## Echo — the refusing tail
25
25
 
26
26
  If no tier grounded, the exact cosine of the top hit is re-folded (`gistOf` on
27
- its bytes). Decision uses that exact value in the same query-relative,
28
- chance-corrected fraction — never the RaBitQ estimate. Below `reach` → silence;
29
- restating → silence; otherwise the hit's own bytes are returned as an ungrounded
30
- echo.
27
+ its bytes). It uses that exact value in the same chance-corrected fraction —
28
+ never the RaBitQ estimate. Below `reach` → silence; restating → silence;
29
+ otherwise the hit's own bytes are returned as an ungrounded echo.
31
30
 
32
31
  ## Provenance
33
32
 
34
33
  Grounded answers carry `recall`; the echo carries `recall-echo` (`echoed: true`
35
- on `RecallResult`). Consumers distinguish a continuation through learned edges
36
- from a near-identity echo.
34
+ on `RecallResult`); it declares `used: ∅`. Consumers distinguish a continuation
35
+ through learned edges from a near-identity echo.
37
36
 
38
37
  ## Substitution bridge — refusal-path only (`src/mind/bridge.ts`)
39
38
 
@@ -47,13 +46,13 @@ unanimous, and the raw gap is length-balanced. Coverage must dominate the query
47
46
  and no dismissed gap may hide known content (`dismissedKnownContent` gate). Cost
48
47
  is `CONCEPT` per substitution plus `STEP`; accounted spans include matched and
49
48
  substituted ranges (so a 28/29-byte paraphrase is not charged `PASS` per
50
- substituted byte — observed double-charge that let `cast` outbid the bridge).
49
+ substituted byte — the double-charge that let `cast` outbid the bridge).
51
50
  Zero-substitution identity bridges carry `complete: true` (the whole read-out);
52
51
  substituted bridges do not.
53
52
 
54
- Scaffolding-only queries abstain: when every stored window that could anchor is
53
+ Scaffolding-only queries abstain: when every window that could anchor is
55
54
  saturated (corpus-global scaffolding, `allWindowsAreScaffolding`), the bridge
56
- returns nothing — a single substituted word cannot carry the semantic load.
55
+ returns nothing — one substituted word cannot carry the load.
57
56
 
58
57
  ## Cost
59
58
 
package/jsr.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://jsr.io/schema/config-file.v1.json",
3
3
  "name": "@hviana/sema",
4
- "version": "0.8.2",
4
+ "version": "0.8.3",
5
5
  "exports": "./src/index.ts"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hviana/sema",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "Sema: a non-parametric, instance-based reasoning system.",
5
5
  "repository": {
6
6
  "type": "git",
package/src/alu/README.md CHANGED
@@ -8,10 +8,10 @@ a truth value) are declared here once.
8
8
 
9
9
  It joins the mind as a `PipelineMechanism`
10
10
  ([`../mind/pipeline-mechanism.ts`](../mind/pipeline-mechanism.ts)) whose only
11
- special role is the optional `parse(query)` method every mechanism may
12
- implement. The mind knows nothing about what the ALU computes; it only knows
13
- that `parse` returns `ComputedSpan[]`, which enter the one lightest-derivation
14
- search as authoritative axioms (at `STEP` cost, like a learned edge).
11
+ special role is the optional `parse(query)` every mechanism may implement. The
12
+ mind knows nothing about what the ALU computes; it only knows that `parse`
13
+ returns `ComputedSpan[]`, which enter the one lightest-derivation search as
14
+ authoritative axioms (at `STEP`, like a learned edge).
15
15
 
16
16
  It has no dependency on the rest of the codebase except the pure byte helpers in
17
17
  `../bytes.ts`, and is intended to be reused as a self-contained sublibrary in
@@ -166,7 +166,7 @@ The ALU is completely decoupled from Sema. It joins the mind through
166
166
  re-exported from [`../mind/pipeline.ts`](../mind/pipeline.ts)), a thin adapter
167
167
  that wraps the ALU's `parse` in a `PipelineMechanism` — the same uniform
168
168
  interface every grounding mechanism (CAST, confluence, cover, extraction,
169
- recall) implements, so nothing about the ALU is special-cased in the pipeline.
169
+ recall) implements, so nothing about the ALU is special-cased.
170
170
 
171
171
  ### The contract
172
172
 
@@ -175,7 +175,7 @@ recall) implements, so nothing about the ALU is special-cased in the pipeline.
175
175
  ```ts
176
176
  interface PipelineMechanism {
177
177
  parse?(query: Uint8Array): Promise<ComputedSpan[]>;
178
- floor(ctx, query, pre): Promise<number | null>;
178
+ floor(ctx, query, pre, worthRunning): Promise<number | null>;
179
179
  run(ctx, query, pre): Promise<MechanismResult[]>;
180
180
  }
181
181
  ```
@@ -259,7 +259,7 @@ whose span overlaps a computed span is **masked** before the search. This is the
259
259
  the computed `4` is the cover's sole completion there. The search itself stays a
260
260
  neutral cost engine (a computed `Out` and a learned edge both cost `STEP`);
261
261
  precedence lives entirely in the masking step, which is in
262
- `src/mind/pipeline.ts`, not in the search and not in the ALU.
262
+ `src/mind/mechanisms/cover.ts`, not in the search and not in the ALU.
263
263
 
264
264
  A computation and an _unrelated_ rewrite still compose in one answer
265
265
  (`"ice 2+2"` → `"cold 4"`) because the masking is scoped to the colliding span
@@ -302,11 +302,10 @@ registry.derive("hypot", 2, ["hypot"], (args, ctx) =>
302
302
  ]));
303
303
  ```
304
304
 
305
- No kernel edit, no graph-search edit, no resonance edit — name it, list its
306
- surface forms, write the body in terms of existing ops. A scalar op broadcasts
307
- over `nd` automatically; pass `structural = true` (the trailing flag on
308
- `prim`/`derive`) only for an op that consumes a list _whole_, like the `nd`
309
- kernel's own.
305
+ No kernel, graph-search or resonance edit — name it, list its surface forms,
306
+ write the body from existing ops. A scalar op broadcasts over `nd`
307
+ automatically; pass `structural = true` (the trailing flag on `prim`/`derive`)
308
+ only for an op that consumes a list _whole_, like the `nd` kernel's own.
310
309
 
311
310
  ## Layout
312
311
 
package/src/config.ts CHANGED
@@ -116,6 +116,17 @@ export interface MindConfig {
116
116
  seed: number;
117
117
  recallQueryK: number;
118
118
  haloQueryK: number;
119
+ /** Branch nodes the pivot sweep may PROBE looking for the learnt context an
120
+ * answer contains — the pivot's own shortlist capacity, separate from
121
+ * `recallQueryK` because they are different quantities: this one bounds a
122
+ * MECHANICAL sweep over the answer's tree (breadth-first, largest regions
123
+ * first, so an exhausted allowance drops the far ones and never the near
124
+ * ones), while `recallQueryK` bounds the bridge's candidate reads. Sharing
125
+ * one number for both meant that tightening either silently starved the
126
+ * other — measured: at `recallQueryK: 1` the pivot cannot find a pivot at
127
+ * all. (`rationaleSampleK` was split out of `recallQueryK` for the same
128
+ * reason, found by an adversarial review.) */
129
+ pivotProbeK: number;
119
130
  /** Corpus reading (see src/mind/corpus.ts): results per call, resolved
120
131
  * nodes climbed from, contexts requested per climb, probes used to stride
121
132
  * the id space when browsing, bytes of each side a preview keeps, and the
@@ -148,6 +159,7 @@ export const DEFAULT_CONFIG: MindConfig = {
148
159
  seed: 42,
149
160
  recallQueryK: 12,
150
161
  haloQueryK: 12,
162
+ pivotProbeK: 12,
151
163
  rationaleSampleK: 12,
152
164
  corpusLimitMax: 24,
153
165
  corpusClimbs: 24,
@@ -196,6 +208,7 @@ export function resolveConfig(opts: Partial<MindConfig> = {}): MindConfig {
196
208
  seed: opts.seed ?? DEFAULT_CONFIG.seed,
197
209
  recallQueryK: opts.recallQueryK ?? DEFAULT_CONFIG.recallQueryK,
198
210
  haloQueryK: opts.haloQueryK ?? DEFAULT_CONFIG.haloQueryK,
211
+ pivotProbeK: opts.pivotProbeK ?? DEFAULT_CONFIG.pivotProbeK,
199
212
  rationaleSampleK: opts.rationaleSampleK ?? DEFAULT_CONFIG.rationaleSampleK,
200
213
  corpusLimitMax: opts.corpusLimitMax ?? DEFAULT_CONFIG.corpusLimitMax,
201
214
  corpusClimbs: opts.corpusClimbs ?? DEFAULT_CONFIG.corpusClimbs,
package/src/geometry.ts CHANGED
@@ -155,6 +155,27 @@ export function profileCapacity(D: number): number {
155
155
  return Math.max(1, Math.floor(Math.sqrt(D)));
156
156
  }
157
157
 
158
+ /**
159
+ * The POOLED-vote significance floor, and the derivation lives here because
160
+ * its PREMISE is a property of the caller's weighting.
161
+ *
162
+ * DERIVATION (docs/architecture/thresholds.md §2): a maximally-specific region
163
+ * contributes at most `ln N` to a pooled vote, so `ln(N) + 1/2` sits half a
164
+ * unit above ONE region's ceiling — it demands corroboration BEYOND a single
165
+ * region, which is what makes it a consensus bar rather than a resonance bar.
166
+ *
167
+ * PREMISE: that per-region ceiling is an IDF, `ln(N/c)` — attention.ts's
168
+ * `inverse` mode, the mode every non-test caller runs. The other two modes
169
+ * weight a region by `ln(1+c)` (`direct`) or `ln(N/c) + ln(1+c)` (`combined`),
170
+ * i.e. `ln N + ln(1 + 1/c)`, so they exceed the premise's ceiling by at most
171
+ * `ln 2` — a DERIVED bound, not a hole: the floor stays within `ln 2` of its
172
+ * own premise in every mode, and exactly on it in `inverse`.
173
+ *
174
+ * MEASURED: the floor is read on the pooled vote (`commitVotes`, `recall`,
175
+ * `cast`). Across 27 anchors on 6 queries, 11 cleared it by the sum and NONE
176
+ * by a single region's peak — gating on one region would refuse every elected
177
+ * root.
178
+ */
158
179
  export function consensusFloor(N: number): number {
159
180
  return Math.log(N) + 1 / 2;
160
181
  }
package/src/meter.ts CHANGED
@@ -223,6 +223,12 @@ export class Meter {
223
223
  joinNoKey = 0;
224
224
  /** Refused: the fact contains no entity that leads anywhere. */
225
225
  joinNoEntity = 0;
226
+ /** `recompleteNode` re-covered a produced form — the descent that decomposes
227
+ * a completion by ITS OWN kids. Without this the descent is invisible: a
228
+ * caller could see the chain's result but not whether the recomposition
229
+ * happened, so "the recursion stopped" and "the recursion never ran" were
230
+ * indistinguishable from the counters alone. */
231
+ recompletes = 0;
226
232
 
227
233
  // ── Mind: the multi-hop pivot (EXTENSION) ───────────────────────────────
228
234
  //
@@ -233,6 +239,51 @@ export class Meter {
233
239
  /** Times the reasoner pivoted on a span its answer contains and stepped
234
240
  * across that fact. */
235
241
  pivotSteps = 0;
242
+ /** Canon probes REFUSED because the canon budget ran out — the one thing the
243
+ * budget does that nothing could see. The budget itself is derived
244
+ * (`bytes.length · chainReach(W)²`, recognition.ts), and the cheap exact route
245
+ * is deliberately unbudgeted, so this counter says exactly when the expensive
246
+ * route was priced out. Counted where the fact happens (the `!canonBudget`
247
+ * refusal), not where the probe is called. */
248
+ canonProbesDenied = 0;
249
+ /** The pipeline's remainder AT THE DECISION POINT, in bytes: what the grounded
250
+ * answer plus the pre-computed spans left unexplained, after the same W floor
251
+ * the fuse gate uses. This is the quantity that licenses (or refuses) the
252
+ * post-grounding extension and the fusion — it was computed, used, and never
253
+ * published, so nothing could measure what a search had LEFT when it decided.
254
+ * Read with {@link postGroundingRemainderSpans}. */
255
+ postGroundingRemainderBytes = 0;
256
+ /** How many spans that remainder consists of (each at least one W window). */
257
+ postGroundingRemainderSpans = 0;
258
+ /** Times `fuseAttention` produced a FUSED answer — not times it was called.
259
+ * It is entered whenever the query has a remainder ≥ W and returns early when
260
+ * there is nothing to bridge (`containsSpan`, a lone root, an empty pass), so
261
+ * the call and the fact are different things and only the fact is counted.
262
+ * Its own rationale step reports the fusion; this is the untraced view, and
263
+ * its cost is one bridging edge: `fuseRuns · STEP`. */
264
+ fuseRuns = 0;
265
+ /** Steps the post-grounding EXTENSION took — pivots plus forward-absorbs.
266
+ * `pivotSteps` counts only the former, so before this the extension's COST was
267
+ * not computable at all. With it, the price of extending the answer is
268
+ * `reasonSteps · STEP`, the ladder's own value for following an edge. */
269
+ reasonSteps = 0;
270
+ /** Bytes of the grounding's UNCOVERED material the extension was justified by
271
+ * — the union of the spans each step carried a `W`-window of. The gate
272
+ * already computed WHICH span carried it per step and kept only a boolean;
273
+ * this is that fact, accumulated. Read with {@link reasonSteps}: one is the
274
+ * price, the other the explanation. */
275
+ reasonCarriedBytes = 0;
276
+ /** Branch-node probes the pivot sweep actually spent looking for the learnt
277
+ * context an answer contains (one `resonate` per probe). The untraced view
278
+ * of what the multi-hop's shortlist costs. */
279
+ pivotProbes = 0;
280
+ /** Branch nodes the pivot's probe cap withheld (`branchCount − probeCap`, over
281
+ * every call). A capacity fact, not a verdict: the sweep is breadth-first,
282
+ * so the probes it DOES spend are the largest regions, and recognition still
283
+ * contributes every exact containment candidate regardless of the budget.
284
+ * Read it with {@link pivotProbes} — one says the work, the other the
285
+ * shortfall. */
286
+ pivotBranchesUnprobed = 0;
236
287
 
237
288
  // ── Mind: the cover's connector assembly (LIMIT) ────────────────────────
238
289
  //
@@ -180,6 +180,10 @@ export interface ConsensusAnchorTrace {
180
180
 
181
181
  pooledVote: number;
182
182
  idfVote: number;
183
+ /** The LARGEST single-region contribution behind this anchor — the bar
184
+ * recall's own gate reads (mechanisms/recall.ts). Published so the one
185
+ * decision-making quantity the climb computes is not invisible. */
186
+ peak: number;
183
187
 
184
188
  candidateBreadth: number;
185
189
  contributingVotes: number;
@@ -1243,15 +1247,18 @@ export async function voteRegions(
1243
1247
  }
1244
1248
  contrastiveMargin = margin;
1245
1249
  // Scaled by what this region does NOT address — see `cov` above.
1246
- const noiseFloor = estimatorNoise(ctx.store.D) * (1 - cov);
1247
- if (margin <= noiseFloor) {
1250
+ // The bar THIS gate applies: the estimator's noise scaled by what the
1251
+ // region does NOT address (`cov`). ONE definition, used by the rejection
1252
+ // path below and by the voted payload — the trace reports the applied bar.
1253
+ const appliedFloor = estimatorNoise(ctx.store.D) * (1 - cov);
1254
+ if (margin <= appliedFloor) {
1248
1255
  recordRegion("contrastive-margin-rejection", {
1249
1256
  selected,
1250
1257
  reachNode: voterId,
1251
1258
  idf,
1252
1259
  dfWeight: wf,
1253
1260
  contrastiveMargin: margin,
1254
- contrastiveNoiseFloor: noiseFloor,
1261
+ contrastiveNoiseFloor: appliedFloor,
1255
1262
  ...(contrastiveRival ? { contrastiveRival } : {}),
1256
1263
  });
1257
1264
  continue;
@@ -1305,7 +1312,12 @@ export async function voteRegions(
1305
1312
  ...(contrastiveMargin !== undefined
1306
1313
  ? {
1307
1314
  contrastiveMargin,
1308
- contrastiveNoiseFloor: estimatorNoise(ctx.store.D),
1315
+ // THE BAR THE GATE ACTUALLY APPLIED — the same expression
1316
+ // the rejection path's `appliedFloor` defines, inline here because
1317
+ // this payload is built in a scope that does not carry that local.
1318
+ // Publishing the raw estimatorNoise(D) instead made a region that
1319
+ // PASSED look closer to its limit than it was.
1320
+ contrastiveNoiseFloor: estimatorNoise(ctx.store.D) * (1 - cov),
1309
1321
  ...(contrastiveRival ? { contrastiveRival } : {}),
1310
1322
  }
1311
1323
  : {}),
@@ -1438,7 +1450,15 @@ export function poolVotes(
1438
1450
  },
1439
1451
  pool,
1440
1452
  };
1441
- lightestDerivation(system);
1453
+ // THE SEARCH WAS THE ONE LAYER WITH NO TIME. The climb's phases are timed
1454
+ // (voteRegions, structuralResonance, crossRegion) but the pooled derivation
1455
+ // was not, so any cost or gain inside it stayed invisible.
1456
+ // `timeSync`, not `time`: the search is SYNCHRONOUS, and wrapping it in a
1457
+ // promise only to time it would make the profiled path wait where the
1458
+ // unprofiled one does not (meter.ts's own contract).
1459
+ if (ctx.meter) {
1460
+ ctx.meter.timeSync("climb.derivation", () => lightestDerivation(system));
1461
+ } else lightestDerivation(system);
1442
1462
 
1443
1463
  const votes = new Map<number, number>();
1444
1464
  const votesIdf = new Map<number, number>();
@@ -1476,12 +1496,24 @@ export function poolVotes(
1476
1496
  // The LARGEST single region's contribution to this anchor's pooled vote.
1477
1497
  // The pool is a SUM (deliberately — see the pooling note above), so it says
1478
1498
  // how much evidence there is in total, never whether any ONE place in the
1479
- // query carries evidence on its own. Consumers that hold an anchor to
1480
- // consensusFloor(N) = ln(N) + 1/2 need the latter: that bar prices ONE
1481
- // region's maximally-discriminative evidence (ln N is the IDF of content
1482
- // reaching a single context), so comparing a six-region sum against it is a
1483
- // dimensional error. Recorded here, beside the count, because this is the
1484
- // only place the per-region contributions are still separable.
1499
+ // query carries evidence on its own. Recorded here, beside the count,
1500
+ // because this is the only place the per-region contributions are still
1501
+ // separable.
1502
+ //
1503
+ // THE BAR IS THE POOLED FLOOR, AND IT WAS ONCE CLAIMED OTHERWISE HERE.
1504
+ // This comment used to say that holding an anchor to consensusFloor(N)
1505
+ // "prices ONE region's evidence", so comparing a six-region sum against it
1506
+ // was "a dimensional error". THAT WAS FALSE. `thresholds.md` §2 derives
1507
+ // `consensusFloor` as the POOLED-vote significance floor ("each region
1508
+ // contributes at most ln(N/c) <= ln(N); ln(N) + 1/2 demands ..."), and the
1509
+ // climb weights by IDF, so the sum and the floor are in ONE dimension —
1510
+ // which is exactly why `recall.ts` gates `forest[0].idfVote` against it and
1511
+ // why `commitVotes` does too. The other two weighting modes DO leave that
1512
+ // dimension (by at most ln 2, two-sided: `direct` deflates a region and
1513
+ // `combined` inflates it), and the gates therefore read the IDF sum, which
1514
+ // is mode-independent; `test/55` tests 19 and 20 pin both halves — the sum
1515
+ // as the reading the bar is derived for, and the absence of any gate
1516
+ // inversion across the three modes.
1485
1517
  const regionPeak = new Map<number, number>();
1486
1518
  const steps: DerivationStep[] = [];
1487
1519
  let order = 0;
@@ -1665,6 +1697,7 @@ export function commitVotes(
1665
1697
  anchor,
1666
1698
  vote,
1667
1699
  peak: regionPeak.get(anchor) ?? 0,
1700
+ idfVote: votesIdf.get(anchor) ?? 0,
1668
1701
  start: s.start,
1669
1702
  end: s.end,
1670
1703
  breadth: (regionSupport.get(anchor) ?? 0) / totalRegions,
@@ -1674,6 +1707,16 @@ export function commitVotes(
1674
1707
  ),
1675
1708
  };
1676
1709
  })
1710
+ // THE ORDER IS NOT A PREFERENCE: with equal evidence it decides ADMISSION,
1711
+ // through the stable sort and the first-come overlap absorption below.
1712
+ // Measured on test/34's corpus, query "red": the two candidates (`red
1713
+ // circle` and `red square`) carry IDENTICAL `vote` and IDENTICAL `idfVote`
1714
+ // (1.3863 each, three seeds), so this comparator leaves them tied and the
1715
+ // stable sort keeps the ENUMERATION order — which is corpus-determined and
1716
+ // admits `red square`, 60/60 seeds. Adding an id tie-break (`|| a.anchor -
1717
+ // b.anchor`) picks `red circle` instead and makes a single region reach the
1718
+ // JOINT context, which is the premise `test/34` exists to protect. The
1719
+ // gates read IDF; this line only decides who gets looked at first.
1677
1720
  .sort((a, b) => b.vote - a.vote);
1678
1721
  const overlaps = (a: Attention, b: Attention) =>
1679
1722
  a.start < b.end && b.start < a.end;
@@ -1719,6 +1762,13 @@ export function commitVotes(
1719
1762
  rank,
1720
1763
  pooledVote: point.vote,
1721
1764
  idfVote: votesIdf.get(point.anchor) ?? 0,
1765
+ // The LARGEST single-region contribution behind this anchor — the bar
1766
+ // recall's own gate reads (mechanisms/recall.ts: forest[0].peak > LN2),
1767
+ // and until now the only decision-making quantity the climb computed and
1768
+ // did not publish. `regionPeak` reached `ranked` (see its build below)
1769
+ // and stopped there. Published, not recomputed: the value is the one the
1770
+ // climb already carries.
1771
+ peak: point.peak,
1722
1772
  candidateBreadth: regions.length,
1723
1773
  contributingVotes: regionAxioms.get(point.anchor) ?? 0,
1724
1774
  contributingEvidence: regionSupport.get(point.anchor) ?? 0,
@@ -1748,6 +1798,18 @@ export function commitVotes(
1748
1798
  let passesConsensusFloor: boolean | undefined;
1749
1799
  let pastLeadingSaturation: boolean | undefined;
1750
1800
  let tiedWithDominant: boolean | undefined;
1801
+ // ── ONE OF THREE ADMISSIONS, AND THEY ARE NOT THE SAME READING ────────
1802
+ // This block admits by VOTES: per-region evidence pooled, gated on the
1803
+ // natural break and on consensusFloor, with the dominant allowed to bypass
1804
+ // both. `structuralResonance` admits by a MARGIN over the estimator's own
1805
+ // noise, and `crossRegionVotes` admits by STRUCTURE (which regions may pair
1806
+ // at all, with at least one side individually discriminative). Read
1807
+ // together they look like one policy written three times; they are three
1808
+ // different measurements of the same question ("is this evidence?"), and
1809
+ // unifying them would average three readings into one — the mistake
1810
+ // `extraction.ts` records as "do not unify the two into one machine".
1811
+ // What they DO share, and must keep sharing, is the discipline of deriving
1812
+ // every bar from D/W/N rather than choosing it (thresholds.md).
1751
1813
  const rejectionReasons: AnchorRejectionReason[] = [];
1752
1814
  if (absorbed) {
1753
1815
  status = "overlap";
@@ -1757,9 +1819,75 @@ export function commitVotes(
1757
1819
  pastLeadingSaturation = pastLeading;
1758
1820
  const vote = votesIdf.get(point.anchor) ?? 0;
1759
1821
  if (roots.length === 0) {
1760
- // The first non-overlapping root is DOMINANT and bypasses the two
1761
- // vote thresholds (it always grounds) — only the leading-saturation
1762
- // gate still applies to it.
1822
+ // THE DOMINANCE PRIVILEGE, AND THE TENSION IT CARRIES (measured).
1823
+ //
1824
+ // The first non-overlapping candidate is DOMINANT: it bypasses both
1825
+ // vote gates below and grounds on its own; only the leading-saturation
1826
+ // gate still applies to it. The privilege is load-bearing — analogies,
1827
+ // substitutions and composed contexts are precisely candidates the
1828
+ // query does NOT contain, and the engine loses them without it.
1829
+ //
1830
+ // WHICH candidate receives it, though, is decided by this loop's ORDER.
1831
+ // That order comes from `ranked`, and when two candidates carry equal
1832
+ // evidence the stable sort preserves the ENUMERATION order, so the
1833
+ // privilege is allocated by an ordering rather than by a rule.
1834
+ //
1835
+ // Measured on test/34's corpus, query "red":
1836
+ //
1837
+ // 0:#77 vote=1.3863 idf=1.3863 [0,3) | 1:#49 vote=1.3863 idf=1.3863 [0,3)
1838
+ //
1839
+ // Both candidates (`red square` #77, `red circle` #49) have IDENTICAL
1840
+ // `vote` AND IDENTICAL `idfVote` over the SAME support span, so the
1841
+ // comparator leaves them tied, the second is absorbed as "overlap", and
1842
+ // the first grounds. With this build's enumeration order that first is
1843
+ // `red square`, 60/60 seeds, and `red circle` — the JOINT context — is
1844
+ // never reached by "red" alone. That is the premise test/34 exists to
1845
+ // protect: no single region reaches the joint context, which is what
1846
+ // makes the binding query unreachable without direct region
1847
+ // interaction.
1848
+ //
1849
+ // THE TENSION: the premise therefore holds BY ENUMERATION ORDER, not by
1850
+ // a rule, so any change to this ordering can move the privilege onto the
1851
+ // joint context and let one region reach it. Measured: adding
1852
+ // `|| a.anchor - b.anchor` — the lowest-id tie-break that AGENTS.md §2
1853
+ // sanctions as an equivalent corpus-determined tie-break — does exactly
1854
+ // that: "red" then attends to `red circle`, test/34 fails 6/1, and the
1855
+ // canonical suite reports 1 failure.
1856
+ //
1857
+ // TWO ATTEMPTS TO MAKE IT A RULE, BOTH REFUTED BY MEASUREMENT:
1858
+ //
1859
+ // 1. EVIDENCE SEPARATION. Grant the privilege only when the first
1860
+ // candidate's evidence is separated from the next distinct
1861
+ // candidate's by more than the co-dominant band (sqrt(k) *
1862
+ // estimatorNoise(D)). Refuted: that band exists to ADMIT the
1863
+ // anchors the estimator cannot separate from the dominant — its own
1864
+ // documented purpose — so withholding the privilege on ties removes
1865
+ // the very case it was written for. Suite: 4 failures (the two
1866
+ // co-dominant band laws, breadth/scale invariance, test/29 D2).
1867
+ //
1868
+ // 2. QUERY-OWNED CONTENT. Grant the privilege only to a candidate
1869
+ // that IS a recognised region's identity (regions.some(r => r.id ===
1870
+ // point.anchor)). Measured: for "circle" that identity IS the
1871
+ // ranked candidate, so the privilege stays and `circle` grounds; for
1872
+ // "red" the identity is the `red` node itself while the candidates
1873
+ // are the conjunctions, so neither is privileged; for "red then
1874
+ // circle" the composed context carries idf 3.958 and clears both
1875
+ // gates on its own evidence. All four control queries came out
1876
+ // right — and the suite: 10 failures, six of them in the
1877
+ // analogy/counterfactual/CAST suites ("an analogy still transfers
1878
+ // from a structure the query never names"; "a substitute the query
1879
+ // NAMES may still be voiced"). Refuted: the privilege exists to
1880
+ // admit what the query does NOT contain, so identity is the wrong
1881
+ // axis.
1882
+ //
1883
+ // WHAT A FUTURE ATTEMPT MUST RESPECT: whatever allocates this privilege
1884
+ // has to (a) keep it available to candidates the query does not contain
1885
+ // — analogies, substitutions, compositions — and (b) not depend on the
1886
+ // estimator's ordering among anchors of equal evidence, because that
1887
+ // ordering is not a fact about the corpus. No lever satisfying both has
1888
+ // been found. Until one is, this premise rests on the enumeration order
1889
+ // recorded above, and test/34 is the only test that notices if it
1890
+ // moves.
1763
1891
  dominant = true;
1764
1892
  if (pastLeading) {
1765
1893
  status = "root";
@@ -1768,8 +1896,15 @@ export function commitVotes(
1768
1896
  rejectionReasons.push("leading-saturation");
1769
1897
  }
1770
1898
  } else {
1771
- passesNaturalBreak = vote >= rootCut;
1772
- passesConsensusFloor = vote >= floor;
1899
+ // THE FLOOR AND THE BREAK READ THE IDF WEIGHTING. `floor` is derived
1900
+ // for pooled IDF-weighted votes, and `rootCut` comes from the IDF
1901
+ // distribution (`idfDesc`), so gating the mode-dependent `vote` against
1902
+ // either let a weighting mode change an admission (measured: anchor 87,
1903
+ // inverse 2.682 admitted vs direct 1.468 refused). Reading the IDF sum
1904
+ // makes the verdict mode-independent, and changes nothing in the
1905
+ // engine's own mode, where the two readings coincide.
1906
+ passesNaturalBreak = point.idfVote >= rootCut;
1907
+ passesConsensusFloor = point.idfVote >= floor;
1773
1908
  // CO-DOMINANT — an anchor the estimator cannot separate from the
1774
1909
  // dominant inherits the dominant's exemption, because that exemption's
1775
1910
  // only warrant is being TOP, and "top" is not a fact about the corpus
@@ -2467,6 +2602,13 @@ export async function structuralResonance(
2467
2602
  });
2468
2603
  };
2469
2604
 
2605
+ // ── ADMISSION BY MARGIN, not by votes (see voteRegions' note) ─────────
2606
+ // What this site measures: how far the best ANN proposal's effective score
2607
+ // (score × semanticConfidence) stands above the runner-up's, against
2608
+ // `estimatorNoise(D)`. What it does NOT measure: how many regions voted,
2609
+ // or whether the query's regions agree — that is voteRegions' question, and
2610
+ // here a synthetic gist has already replaced them. The two bars are both
2611
+ // derived (thresholds.md), and neither is a tuning of the other.
2470
2612
  let selected: StructuralResonanceProposal | null = null;
2471
2613
  let selectedReach: AncestorReach | null = null;
2472
2614
  let selectedIdf = 0;
@@ -2582,6 +2724,15 @@ async function crossRegionVotes(
2582
2724
  // successfully reconstructed while probing one pair must not be read and
2583
2725
  // perceived again while probing another pair in the same climb.
2584
2726
  const siblingGistMemo = new Map<number, CachedSiblingGist>();
2727
+ // ── ADMISSION BY STRUCTURE, not by a bar (see voteRegions' note) ──────
2728
+ // What this site decides: WHICH regions may pair at all — a region that
2729
+ // already voted (individually discriminative), or a KNOWN non-voting one as
2730
+ // the weak side of a pair whose other side voted; never two non-voting
2731
+ // regions, and never a span contained in a maximal one whose reading is
2732
+ // exact. The bar (the container's idf) comes later, on the candidate. So
2733
+ // its "rejection reasons" name structural disqualifications — a different
2734
+ // vocabulary because it answers a different question, and the three
2735
+ // taxonomies stay separate for the same reason the readings do.
2585
2736
  const votedSpans = new Set<string>();
2586
2737
  for (const rv of rvs.votes) votedSpans.add(`${rv.start},${rv.end}`);
2587
2738
  const seen = new Set<string>();
@@ -88,6 +88,49 @@ export function leafIdPrefix(
88
88
  return ids;
89
89
  }
90
90
 
91
+ /** Which prefixes of `prefix ‖ tail` are STORED NODES — as lengths in the
92
+ * tail's own coordinates, ascending, excluding the empty one. This is the
93
+ * candidate set a rule needs to join an already-stored prefix to a suffix it
94
+ * has not stored: a key names a relation exactly when `prefix ‖ tail[0..p]` IS
95
+ * a node, and a key can end strictly inside the tail without sitting on any
96
+ * fold boundary (a stored member's end is the end of ITS OWN stream, and the
97
+ * fold never emits a cut at a stream's end). Measured: "stockholm mayor"
98
+ * exists, leads on, and its boundary 6 is in neither the tail's cuts nor the
99
+ * concatenation's.
100
+ *
101
+ * ONE cheap content-addressed probe per offset — `leafIdPrefix` walks the bytes
102
+ * once (a point probe each), `findBranch` hashes the growing kid run — and NO
103
+ * `resolve`, which is what keeps this off the O(suffix) vector folds the
104
+ * recognition path pays. It stops at the first byte that was never interned,
105
+ * which costs nothing real: a stored key's bytes are interned by construction. */
106
+ export function keyEnds(
107
+ ctx: MindContext,
108
+ prefix: Uint8Array,
109
+ tail: Uint8Array,
110
+ ): number[] {
111
+ if (prefix.length === 0 || tail.length === 0) return [];
112
+ const joined = new Uint8Array(prefix.length + tail.length);
113
+ joined.set(prefix, 0);
114
+ joined.set(tail, prefix.length);
115
+ const ids = leafIdPrefix(ctx, joined);
116
+ if (ids.length < prefix.length) return [];
117
+ const ends: number[] = [];
118
+ // The kid run GROWS by one id per offset; `findBranch` wants an array, so the
119
+ // run is built once and pushed into, never re-sliced. Re-slicing
120
+ // `ids.slice(0, prefix.length + p)` per offset made this O(|tail| ·
121
+ // (|prefix| + |tail|)) — quadratic in the tail, where the learning path this
122
+ // follows slices a run that SHRINKS. Same ends, linear copying.
123
+ const run = ids.slice(0, prefix.length);
124
+ // The loop ENDS at the first byte that was never interned (`ids.length`):
125
+ // every later prefix contains it, so none of them can be a node either — this
126
+ // is where the scan stops, not a silent truncation of the answer.
127
+ for (let p = 1; prefix.length + p <= ids.length; p++) {
128
+ run.push(ids[prefix.length + p - 1]);
129
+ if (ctx.store.findBranch(run) !== null) ends.push(p);
130
+ }
131
+ return ends;
132
+ }
133
+
91
134
  /** The canonical W-window node ids of a byte stream, offset → id — the
92
135
  * CONTENT-ADDRESSED IDENTITY of every W-sized slice, under which any content
93
136
  * two deposits share IS the same node (hash-consing paid the comparison at