@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
package/AGENTS.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  The working manual for anyone (human or AI agent) changing Sema. For pattern
4
4
  detail, see `docs/INDEX.md` → `docs/architecture/*.md`. You should be able to
5
- develop against this document and docs/ alone; read the theory in
6
- `docs/architecture/` when you need why a pattern holds, not to get work done.
5
+ develop against this document and docs/ alone; read `docs/architecture/` for why
6
+ a pattern holds.
7
7
 
8
8
  ## 1. Orientation
9
9
 
@@ -14,7 +14,7 @@ accelerators), and a cost-based search that composes answers from stored facts
14
14
  the only runtime dependency.
15
15
 
16
16
  ```bash
17
- npm install # dev tooling + parquet reader used by one example
17
+ npm install # dev tooling + parquet reader for one example
18
18
  npm run build # tsc → dist/
19
19
  npm test # tsc && node --test test/**/*.test.mjs
20
20
  npm run demo # example/demo.ts — the four-note README demo
@@ -62,6 +62,7 @@ shared content-addressed ascent; `Precomputed` in
62
62
  `src/mind/pipeline-mechanism.ts` is the per-response lazy memo; `src/meter.ts`
63
63
  is the write-only work accounting surface. See `docs/INDEX.md` for the full
64
64
  contract table and `docs/architecture/factored-machinery.md` for ownership.
65
+ Tie-breaks are corpus-determined, but not interchangeable (`determinism.md`).
65
66
 
66
67
  ## 3. Where things live
67
68
 
@@ -89,7 +90,7 @@ contract table and `docs/architecture/factored-machinery.md` for ownership.
89
90
  | Sublibraries (own READMEs) | `src/derive/`, `src/alu/`, `src/rabitq-ivf/` |
90
91
 
91
92
  Mind functions are free functions over `MindContext` (`src/mind/types.ts`), not
92
- methods — `mind.ts` is a thin assembly that delegates.
93
+ methods; `mind.ts` is a thin assembly.
93
94
 
94
95
  ## 4. Recipes
95
96
 
@@ -97,7 +98,7 @@ methods — `mind.ts` is a thin assembly that delegates.
97
98
 
98
99
  Implement `PipelineMechanism` (`floor` → admissible bound or `null`; `run` →
99
100
  candidates with `bytes`/`accounted`/`moves`/`unexplained` + optional
100
- `scaffolding`/`complete`). Register via
101
+ `scaffolding`/`complete`/`used`). Register via
101
102
  `new Mind({ mechanismFactories: [host => yourMechanism(host)] })`. Verify the
102
103
  four market constraints (decoupled, declared competence, visible budget,
103
104
  evidence travels). → `docs/architecture/mechanism-market.md`
@@ -125,14 +126,13 @@ Run the full suite with your store substituted. → `docs/architecture/store.md`
125
126
  ## 5. Testing norms
126
127
 
127
128
  Tests are `node:test` suites in `test/*.test.mjs`, numbered by theme, run
128
- against built `dist/` (`npm test`; single suite:
129
- `node --test test/22-multihop.test.mjs` after `tsc`). New behaviour ⇒ test in
130
- the matching numbered suite or a new one. Many tests pin contracts that look
131
- like implementation details (ladder order, span-shape readings,
132
- `MechanismResult.complete`, fold invariance, recognition idempotence, honest
133
- silence). A simplification that fails an existing test is wrong until the test
134
- is proven wrong. Sublibraries test themselves in
135
- `src/{alu,derive,rabitq-ivf}/test/` with zero Sema dependency.
129
+ against built `dist/` (`npm test`; one suite:
130
+ `node --test test/22-multihop.test.mjs`). New behaviour ⇒ a test in the matching
131
+ numbered suite. Many tests pin contracts that look like implementation details
132
+ (ladder order, span-shape readings, `MechanismResult.complete`, fold invariance,
133
+ recognition idempotence, honest silence). A simplification that fails an
134
+ existing test is wrong until the test is proven wrong. Sublibraries test
135
+ themselves in `src/{alu,derive,rabitq-ivf}/test/` with zero Sema dependency.
136
136
 
137
137
  ## 6. Instrumentation — the meter and the rationale ARE the dev surface
138
138
 
@@ -143,17 +143,17 @@ instrumentation, and the only ones. Both are read through the public path —
143
143
  and the `inspectRationale` callback on `respond`/`respondText`/`respondTurn`.
144
144
 
145
145
  When a change needs to be seen, measured, or proved, EXTEND THEM: a counter in
146
- `meter.ts` (the one place a counter name exists — keep its four contracts true),
147
- a step or note where the mechanism emits it (`src/mind/trace.ts` holds the move
148
- vocabulary). A gap in instrumentation is a defect IN the instrumentation: close
149
- it there, once, so the next person sees it too. Never add a parallel channel for
150
- a single investigation — no ad-hoc logging or timing probes left in `src/`
151
- (`performance.now()` belongs in `meter.ts`, not at a call site), no private
152
- per-layer counter where a `meter.ts` field belongs, and no trace channel of your
153
- own: a callback threaded through a call chain must FEED the rationale, the way
154
- `GraphSearch`'s `onDerivation` feeds `traceDerivation`. (`store.ts`'s
155
- `danglingReads`/`compactFailures` and the `console.warn`s that report them
156
- predate this and stay: session-lifetime HEALTH counters, not per-response work.)
146
+ `meter.ts` (the one place a counter name exists), a step or note where the
147
+ mechanism emits it (`src/mind/trace.ts` holds the move vocabulary). A gap in
148
+ instrumentation is a defect IN the instrumentation: close it there, once, so the
149
+ next person sees it too. Never add a parallel channel for a single investigation
150
+ — no ad-hoc logging or timing probes left in `src/` (`performance.now()` belongs
151
+ in `meter.ts`, not at a call site), no private per-layer counter where a
152
+ `meter.ts` field belongs, and no trace channel of your own: a callback threaded
153
+ through a call chain must FEED the rationale, the way `GraphSearch`'s
154
+ `onDerivation` feeds `traceDerivation`. (`store.ts`'s
155
+ `danglingReads`/`compactFailures` and the `console.warn`s that report them stay:
156
+ session-lifetime HEALTH counters, not per-response work.)
157
157
 
158
158
  ## 7. Dependencies and licensing
159
159
 
@@ -161,8 +161,8 @@ PolyForm Noncommercial 1.0.0 with separate commercial licensing (see
161
161
  `LICENSE.md`, `COMMERCIAL-LICENSE.md`, `TRADEMARKS.md`). The library has **no
162
162
  runtime dependencies** — pinned by `test/88-dependency-footprint.test.mjs`
163
163
  (`dist/src` may import only `node:` builtins and relative paths; `package.json`
164
- has no `dependencies`). Examples may use dev dependencies lazily via dynamic
165
- import only on the code path that needs them (`example/train_base` + `hyparquet`
166
- is the reference). Training corpora: a store retains text verbatim, so upstream
167
- licences apply in full — NonCommercial and ShareAlike corpora cannot enter a
168
- trainer; see `DATASETS.md`.
164
+ has no `dependencies`). Examples may use dev dependencies via dynamic import
165
+ only where needed (`example/train_base` + `hyparquet` is the reference).
166
+ Training corpora: a store retains text verbatim, so upstream licences apply in
167
+ full — NonCommercial and ShareAlike corpora cannot enter a trainer; see
168
+ `DATASETS.md`.
package/TRADEMARKS.md CHANGED
@@ -6,7 +6,6 @@ The following are not licensed under the software license:
6
6
 
7
7
  - redistribute its algorithmic logic: that is, how algorithms and mathematical
8
8
  techniques are combined to develop its machinery;
9
- - visual identity;
10
9
  - project name;
11
10
  - logos;
12
11
  - icons;
@@ -100,6 +100,17 @@ export interface MindConfig {
100
100
  seed: number;
101
101
  recallQueryK: number;
102
102
  haloQueryK: number;
103
+ /** Branch nodes the pivot sweep may PROBE looking for the learnt context an
104
+ * answer contains — the pivot's own shortlist capacity, separate from
105
+ * `recallQueryK` because they are different quantities: this one bounds a
106
+ * MECHANICAL sweep over the answer's tree (breadth-first, largest regions
107
+ * first, so an exhausted allowance drops the far ones and never the near
108
+ * ones), while `recallQueryK` bounds the bridge's candidate reads. Sharing
109
+ * one number for both meant that tightening either silently starved the
110
+ * other — measured: at `recallQueryK: 1` the pivot cannot find a pivot at
111
+ * all. (`rationaleSampleK` was split out of `recallQueryK` for the same
112
+ * reason, found by an adversarial review.) */
113
+ pivotProbeK: number;
103
114
  /** Corpus reading (see src/mind/corpus.ts): results per call, resolved
104
115
  * nodes climbed from, contexts requested per climb, probes used to stride
105
116
  * the id space when browsing, bytes of each side a preview keeps, and the
@@ -5,6 +5,7 @@ export const DEFAULT_CONFIG = {
5
5
  seed: 42,
6
6
  recallQueryK: 12,
7
7
  haloQueryK: 12,
8
+ pivotProbeK: 12,
8
9
  rationaleSampleK: 12,
9
10
  corpusLimitMax: 24,
10
11
  corpusClimbs: 24,
@@ -51,6 +52,7 @@ export function resolveConfig(opts = {}) {
51
52
  seed: opts.seed ?? DEFAULT_CONFIG.seed,
52
53
  recallQueryK: opts.recallQueryK ?? DEFAULT_CONFIG.recallQueryK,
53
54
  haloQueryK: opts.haloQueryK ?? DEFAULT_CONFIG.haloQueryK,
55
+ pivotProbeK: opts.pivotProbeK ?? DEFAULT_CONFIG.pivotProbeK,
54
56
  rationaleSampleK: opts.rationaleSampleK ?? DEFAULT_CONFIG.rationaleSampleK,
55
57
  corpusLimitMax: opts.corpusLimitMax ?? DEFAULT_CONFIG.corpusLimitMax,
56
58
  corpusClimbs: opts.corpusClimbs ?? DEFAULT_CONFIG.corpusClimbs,
@@ -106,6 +106,27 @@ export declare function dominates(partLen: number, wholeLen: number): boolean;
106
106
  * Consumer: `companyProfile` (mind/learning.ts), which sizes its constituent
107
107
  * sketch at this capacity instead of a visit budget. */
108
108
  export declare function profileCapacity(D: number): number;
109
+ /**
110
+ * The POOLED-vote significance floor, and the derivation lives here because
111
+ * its PREMISE is a property of the caller's weighting.
112
+ *
113
+ * DERIVATION (docs/architecture/thresholds.md §2): a maximally-specific region
114
+ * contributes at most `ln N` to a pooled vote, so `ln(N) + 1/2` sits half a
115
+ * unit above ONE region's ceiling — it demands corroboration BEYOND a single
116
+ * region, which is what makes it a consensus bar rather than a resonance bar.
117
+ *
118
+ * PREMISE: that per-region ceiling is an IDF, `ln(N/c)` — attention.ts's
119
+ * `inverse` mode, the mode every non-test caller runs. The other two modes
120
+ * weight a region by `ln(1+c)` (`direct`) or `ln(N/c) + ln(1+c)` (`combined`),
121
+ * i.e. `ln N + ln(1 + 1/c)`, so they exceed the premise's ceiling by at most
122
+ * `ln 2` — a DERIVED bound, not a hole: the floor stays within `ln 2` of its
123
+ * own premise in every mode, and exactly on it in `inverse`.
124
+ *
125
+ * MEASURED: the floor is read on the pooled vote (`commitVotes`, `recall`,
126
+ * `cast`). Across 27 anchors on 6 queries, 11 cleared it by the sum and NONE
127
+ * by a single region's peak — gating on one region would refuse every elected
128
+ * root.
129
+ */
109
130
  export declare function consensusFloor(N: number): number;
110
131
  /** The coverage bar for the reach (interior) index, when vector-similarity
111
132
  * gating is used. Returns the concept threshold — the structural midpoint
@@ -143,6 +143,27 @@ export function dominates(partLen, wholeLen) {
143
143
  export function profileCapacity(D) {
144
144
  return Math.max(1, Math.floor(Math.sqrt(D)));
145
145
  }
146
+ /**
147
+ * The POOLED-vote significance floor, and the derivation lives here because
148
+ * its PREMISE is a property of the caller's weighting.
149
+ *
150
+ * DERIVATION (docs/architecture/thresholds.md §2): a maximally-specific region
151
+ * contributes at most `ln N` to a pooled vote, so `ln(N) + 1/2` sits half a
152
+ * unit above ONE region's ceiling — it demands corroboration BEYOND a single
153
+ * region, which is what makes it a consensus bar rather than a resonance bar.
154
+ *
155
+ * PREMISE: that per-region ceiling is an IDF, `ln(N/c)` — attention.ts's
156
+ * `inverse` mode, the mode every non-test caller runs. The other two modes
157
+ * weight a region by `ln(1+c)` (`direct`) or `ln(N/c) + ln(1+c)` (`combined`),
158
+ * i.e. `ln N + ln(1 + 1/c)`, so they exceed the premise's ceiling by at most
159
+ * `ln 2` — a DERIVED bound, not a hole: the floor stays within `ln 2` of its
160
+ * own premise in every mode, and exactly on it in `inverse`.
161
+ *
162
+ * MEASURED: the floor is read on the pooled vote (`commitVotes`, `recall`,
163
+ * `cast`). Across 27 anchors on 6 queries, 11 cleared it by the sum and NONE
164
+ * by a single region's peak — gating on one region would refuse every elected
165
+ * root.
166
+ */
146
167
  export function consensusFloor(N) {
147
168
  return Math.log(N) + 1 / 2;
148
169
  }
@@ -162,9 +162,60 @@ export declare class Meter {
162
162
  joinNoKey: number;
163
163
  /** Refused: the fact contains no entity that leads anywhere. */
164
164
  joinNoEntity: number;
165
+ /** `recompleteNode` re-covered a produced form — the descent that decomposes
166
+ * a completion by ITS OWN kids. Without this the descent is invisible: a
167
+ * caller could see the chain's result but not whether the recomposition
168
+ * happened, so "the recursion stopped" and "the recursion never ran" were
169
+ * indistinguishable from the counters alone. */
170
+ recompletes: number;
165
171
  /** Times the reasoner pivoted on a span its answer contains and stepped
166
172
  * across that fact. */
167
173
  pivotSteps: number;
174
+ /** Canon probes REFUSED because the canon budget ran out — the one thing the
175
+ * budget does that nothing could see. The budget itself is derived
176
+ * (`bytes.length · chainReach(W)²`, recognition.ts), and the cheap exact route
177
+ * is deliberately unbudgeted, so this counter says exactly when the expensive
178
+ * route was priced out. Counted where the fact happens (the `!canonBudget`
179
+ * refusal), not where the probe is called. */
180
+ canonProbesDenied: number;
181
+ /** The pipeline's remainder AT THE DECISION POINT, in bytes: what the grounded
182
+ * answer plus the pre-computed spans left unexplained, after the same W floor
183
+ * the fuse gate uses. This is the quantity that licenses (or refuses) the
184
+ * post-grounding extension and the fusion — it was computed, used, and never
185
+ * published, so nothing could measure what a search had LEFT when it decided.
186
+ * Read with {@link postGroundingRemainderSpans}. */
187
+ postGroundingRemainderBytes: number;
188
+ /** How many spans that remainder consists of (each at least one W window). */
189
+ postGroundingRemainderSpans: number;
190
+ /** Times `fuseAttention` produced a FUSED answer — not times it was called.
191
+ * It is entered whenever the query has a remainder ≥ W and returns early when
192
+ * there is nothing to bridge (`containsSpan`, a lone root, an empty pass), so
193
+ * the call and the fact are different things and only the fact is counted.
194
+ * Its own rationale step reports the fusion; this is the untraced view, and
195
+ * its cost is one bridging edge: `fuseRuns · STEP`. */
196
+ fuseRuns: number;
197
+ /** Steps the post-grounding EXTENSION took — pivots plus forward-absorbs.
198
+ * `pivotSteps` counts only the former, so before this the extension's COST was
199
+ * not computable at all. With it, the price of extending the answer is
200
+ * `reasonSteps · STEP`, the ladder's own value for following an edge. */
201
+ reasonSteps: number;
202
+ /** Bytes of the grounding's UNCOVERED material the extension was justified by
203
+ * — the union of the spans each step carried a `W`-window of. The gate
204
+ * already computed WHICH span carried it per step and kept only a boolean;
205
+ * this is that fact, accumulated. Read with {@link reasonSteps}: one is the
206
+ * price, the other the explanation. */
207
+ reasonCarriedBytes: number;
208
+ /** Branch-node probes the pivot sweep actually spent looking for the learnt
209
+ * context an answer contains (one `resonate` per probe). The untraced view
210
+ * of what the multi-hop's shortlist costs. */
211
+ pivotProbes: number;
212
+ /** Branch nodes the pivot's probe cap withheld (`branchCount − probeCap`, over
213
+ * every call). A capacity fact, not a verdict: the sweep is breadth-first,
214
+ * so the probes it DOES spend are the largest regions, and recognition still
215
+ * contributes every exact containment candidate regardless of the budget.
216
+ * Read it with {@link pivotProbes} — one says the work, the other the
217
+ * shortfall. */
218
+ pivotBranchesUnprobed: number;
168
219
  /** `bridge` calls the cover makes assembling connectors (pairwise + n-ary). */
169
220
  coverBridges: number;
170
221
  /** Continuations a CHAIN hop offered the search. Bounded by the question
package/dist/src/meter.js CHANGED
@@ -166,6 +166,12 @@ export class Meter {
166
166
  joinNoKey = 0;
167
167
  /** Refused: the fact contains no entity that leads anywhere. */
168
168
  joinNoEntity = 0;
169
+ /** `recompleteNode` re-covered a produced form — the descent that decomposes
170
+ * a completion by ITS OWN kids. Without this the descent is invisible: a
171
+ * caller could see the chain's result but not whether the recomposition
172
+ * happened, so "the recursion stopped" and "the recursion never ran" were
173
+ * indistinguishable from the counters alone. */
174
+ recompletes = 0;
169
175
  // ── Mind: the multi-hop pivot (EXTENSION) ───────────────────────────────
170
176
  //
171
177
  // `pivotStep` was observable only through the rationale, and the rationale
@@ -175,6 +181,51 @@ export class Meter {
175
181
  /** Times the reasoner pivoted on a span its answer contains and stepped
176
182
  * across that fact. */
177
183
  pivotSteps = 0;
184
+ /** Canon probes REFUSED because the canon budget ran out — the one thing the
185
+ * budget does that nothing could see. The budget itself is derived
186
+ * (`bytes.length · chainReach(W)²`, recognition.ts), and the cheap exact route
187
+ * is deliberately unbudgeted, so this counter says exactly when the expensive
188
+ * route was priced out. Counted where the fact happens (the `!canonBudget`
189
+ * refusal), not where the probe is called. */
190
+ canonProbesDenied = 0;
191
+ /** The pipeline's remainder AT THE DECISION POINT, in bytes: what the grounded
192
+ * answer plus the pre-computed spans left unexplained, after the same W floor
193
+ * the fuse gate uses. This is the quantity that licenses (or refuses) the
194
+ * post-grounding extension and the fusion — it was computed, used, and never
195
+ * published, so nothing could measure what a search had LEFT when it decided.
196
+ * Read with {@link postGroundingRemainderSpans}. */
197
+ postGroundingRemainderBytes = 0;
198
+ /** How many spans that remainder consists of (each at least one W window). */
199
+ postGroundingRemainderSpans = 0;
200
+ /** Times `fuseAttention` produced a FUSED answer — not times it was called.
201
+ * It is entered whenever the query has a remainder ≥ W and returns early when
202
+ * there is nothing to bridge (`containsSpan`, a lone root, an empty pass), so
203
+ * the call and the fact are different things and only the fact is counted.
204
+ * Its own rationale step reports the fusion; this is the untraced view, and
205
+ * its cost is one bridging edge: `fuseRuns · STEP`. */
206
+ fuseRuns = 0;
207
+ /** Steps the post-grounding EXTENSION took — pivots plus forward-absorbs.
208
+ * `pivotSteps` counts only the former, so before this the extension's COST was
209
+ * not computable at all. With it, the price of extending the answer is
210
+ * `reasonSteps · STEP`, the ladder's own value for following an edge. */
211
+ reasonSteps = 0;
212
+ /** Bytes of the grounding's UNCOVERED material the extension was justified by
213
+ * — the union of the spans each step carried a `W`-window of. The gate
214
+ * already computed WHICH span carried it per step and kept only a boolean;
215
+ * this is that fact, accumulated. Read with {@link reasonSteps}: one is the
216
+ * price, the other the explanation. */
217
+ reasonCarriedBytes = 0;
218
+ /** Branch-node probes the pivot sweep actually spent looking for the learnt
219
+ * context an answer contains (one `resonate` per probe). The untraced view
220
+ * of what the multi-hop's shortlist costs. */
221
+ pivotProbes = 0;
222
+ /** Branch nodes the pivot's probe cap withheld (`branchCount − probeCap`, over
223
+ * every call). A capacity fact, not a verdict: the sweep is breadth-first,
224
+ * so the probes it DOES spend are the largest regions, and recognition still
225
+ * contributes every exact containment candidate regardless of the budget.
226
+ * Read it with {@link pivotProbes} — one says the work, the other the
227
+ * shortfall. */
228
+ pivotBranchesUnprobed = 0;
178
229
  // ── Mind: the cover's connector assembly (LIMIT) ────────────────────────
179
230
  //
180
231
  // The cover's `run` is 91% of a hub query's time (`"Hello."`: 2.7 s of 3.0 s)
@@ -75,6 +75,10 @@ export interface ConsensusAnchorTrace {
75
75
  rank: number;
76
76
  pooledVote: number;
77
77
  idfVote: number;
78
+ /** The LARGEST single-region contribution behind this anchor — the bar
79
+ * recall's own gate reads (mechanisms/recall.ts). Published so the one
80
+ * decision-making quantity the climb computes is not invisible. */
81
+ peak: number;
78
82
  candidateBreadth: number;
79
83
  contributingVotes: number;
80
84
  contributingEvidence: number;
@@ -775,15 +775,18 @@ export async function voteRegions(ctx, query, regions, k, mode, N, reachMemo, td
775
775
  }
776
776
  contrastiveMargin = margin;
777
777
  // Scaled by what this region does NOT address — see `cov` above.
778
- const noiseFloor = estimatorNoise(ctx.store.D) * (1 - cov);
779
- if (margin <= noiseFloor) {
778
+ // The bar THIS gate applies: the estimator's noise scaled by what the
779
+ // region does NOT address (`cov`). ONE definition, used by the rejection
780
+ // path below and by the voted payload — the trace reports the applied bar.
781
+ const appliedFloor = estimatorNoise(ctx.store.D) * (1 - cov);
782
+ if (margin <= appliedFloor) {
780
783
  recordRegion("contrastive-margin-rejection", {
781
784
  selected,
782
785
  reachNode: voterId,
783
786
  idf,
784
787
  dfWeight: wf,
785
788
  contrastiveMargin: margin,
786
- contrastiveNoiseFloor: noiseFloor,
789
+ contrastiveNoiseFloor: appliedFloor,
787
790
  ...(contrastiveRival ? { contrastiveRival } : {}),
788
791
  });
789
792
  continue;
@@ -834,7 +837,12 @@ export async function voteRegions(ctx, query, regions, k, mode, N, reachMemo, td
834
837
  ...(contrastiveMargin !== undefined
835
838
  ? {
836
839
  contrastiveMargin,
837
- contrastiveNoiseFloor: estimatorNoise(ctx.store.D),
840
+ // THE BAR THE GATE ACTUALLY APPLIED — the same expression
841
+ // the rejection path's `appliedFloor` defines, inline here because
842
+ // this payload is built in a scope that does not carry that local.
843
+ // Publishing the raw estimatorNoise(D) instead made a region that
844
+ // PASSED look closer to its limit than it was.
845
+ contrastiveNoiseFloor: estimatorNoise(ctx.store.D) * (1 - cov),
838
846
  ...(contrastiveRival ? { contrastiveRival } : {}),
839
847
  }
840
848
  : {}),
@@ -939,7 +947,17 @@ export function poolVotes(ctx, regionVotes, sat, N, td) {
939
947
  },
940
948
  pool,
941
949
  };
942
- lightestDerivation(system);
950
+ // THE SEARCH WAS THE ONE LAYER WITH NO TIME. The climb's phases are timed
951
+ // (voteRegions, structuralResonance, crossRegion) but the pooled derivation
952
+ // was not, so any cost or gain inside it stayed invisible.
953
+ // `timeSync`, not `time`: the search is SYNCHRONOUS, and wrapping it in a
954
+ // promise only to time it would make the profiled path wait where the
955
+ // unprofiled one does not (meter.ts's own contract).
956
+ if (ctx.meter) {
957
+ ctx.meter.timeSync("climb.derivation", () => lightestDerivation(system));
958
+ }
959
+ else
960
+ lightestDerivation(system);
943
961
  const votes = new Map();
944
962
  const votesIdf = new Map();
945
963
  const support = new Map();
@@ -973,12 +991,24 @@ export function poolVotes(ctx, regionVotes, sat, N, td) {
973
991
  // The LARGEST single region's contribution to this anchor's pooled vote.
974
992
  // The pool is a SUM (deliberately — see the pooling note above), so it says
975
993
  // how much evidence there is in total, never whether any ONE place in the
976
- // query carries evidence on its own. Consumers that hold an anchor to
977
- // consensusFloor(N) = ln(N) + 1/2 need the latter: that bar prices ONE
978
- // region's maximally-discriminative evidence (ln N is the IDF of content
979
- // reaching a single context), so comparing a six-region sum against it is a
980
- // dimensional error. Recorded here, beside the count, because this is the
981
- // only place the per-region contributions are still separable.
994
+ // query carries evidence on its own. Recorded here, beside the count,
995
+ // because this is the only place the per-region contributions are still
996
+ // separable.
997
+ //
998
+ // THE BAR IS THE POOLED FLOOR, AND IT WAS ONCE CLAIMED OTHERWISE HERE.
999
+ // This comment used to say that holding an anchor to consensusFloor(N)
1000
+ // "prices ONE region's evidence", so comparing a six-region sum against it
1001
+ // was "a dimensional error". THAT WAS FALSE. `thresholds.md` §2 derives
1002
+ // `consensusFloor` as the POOLED-vote significance floor ("each region
1003
+ // contributes at most ln(N/c) <= ln(N); ln(N) + 1/2 demands ..."), and the
1004
+ // climb weights by IDF, so the sum and the floor are in ONE dimension —
1005
+ // which is exactly why `recall.ts` gates `forest[0].idfVote` against it and
1006
+ // why `commitVotes` does too. The other two weighting modes DO leave that
1007
+ // dimension (by at most ln 2, two-sided: `direct` deflates a region and
1008
+ // `combined` inflates it), and the gates therefore read the IDF sum, which
1009
+ // is mode-independent; `test/55` tests 19 and 20 pin both halves — the sum
1010
+ // as the reading the bar is derived for, and the absence of any gate
1011
+ // inversion across the three modes.
982
1012
  const regionPeak = new Map();
983
1013
  const steps = [];
984
1014
  let order = 0;
@@ -1136,12 +1166,23 @@ export function commitVotes(ctx, pooled, sat, regions, regionVoter, N, td, cfg)
1136
1166
  anchor,
1137
1167
  vote,
1138
1168
  peak: regionPeak.get(anchor) ?? 0,
1169
+ idfVote: votesIdf.get(anchor) ?? 0,
1139
1170
  start: s.start,
1140
1171
  end: s.end,
1141
1172
  breadth: (regionSupport.get(anchor) ?? 0) / totalRegions,
1142
1173
  clusters: countClusters(regionSpans.get(anchor) ?? [], ctx.space.maxGroup),
1143
1174
  };
1144
1175
  })
1176
+ // THE ORDER IS NOT A PREFERENCE: with equal evidence it decides ADMISSION,
1177
+ // through the stable sort and the first-come overlap absorption below.
1178
+ // Measured on test/34's corpus, query "red": the two candidates (`red
1179
+ // circle` and `red square`) carry IDENTICAL `vote` and IDENTICAL `idfVote`
1180
+ // (1.3863 each, three seeds), so this comparator leaves them tied and the
1181
+ // stable sort keeps the ENUMERATION order — which is corpus-determined and
1182
+ // admits `red square`, 60/60 seeds. Adding an id tie-break (`|| a.anchor -
1183
+ // b.anchor`) picks `red circle` instead and makes a single region reach the
1184
+ // JOINT context, which is the premise `test/34` exists to protect. The
1185
+ // gates read IDF; this line only decides who gets looked at first.
1145
1186
  .sort((a, b) => b.vote - a.vote);
1146
1187
  const overlaps = (a, b) => a.start < b.end && b.start < a.end;
1147
1188
  // Read the root cut from the anchors the QUERY pointed at. A vote standing
@@ -1177,6 +1218,13 @@ export function commitVotes(ctx, pooled, sat, regions, regionVoter, N, td, cfg)
1177
1218
  rank,
1178
1219
  pooledVote: point.vote,
1179
1220
  idfVote: votesIdf.get(point.anchor) ?? 0,
1221
+ // The LARGEST single-region contribution behind this anchor — the bar
1222
+ // recall's own gate reads (mechanisms/recall.ts: forest[0].peak > LN2),
1223
+ // and until now the only decision-making quantity the climb computed and
1224
+ // did not publish. `regionPeak` reached `ranked` (see its build below)
1225
+ // and stopped there. Published, not recomputed: the value is the one the
1226
+ // climb already carries.
1227
+ peak: point.peak,
1180
1228
  candidateBreadth: regions.length,
1181
1229
  contributingVotes: regionAxioms.get(point.anchor) ?? 0,
1182
1230
  contributingEvidence: regionSupport.get(point.anchor) ?? 0,
@@ -1206,6 +1254,18 @@ export function commitVotes(ctx, pooled, sat, regions, regionVoter, N, td, cfg)
1206
1254
  let passesConsensusFloor;
1207
1255
  let pastLeadingSaturation;
1208
1256
  let tiedWithDominant;
1257
+ // ── ONE OF THREE ADMISSIONS, AND THEY ARE NOT THE SAME READING ────────
1258
+ // This block admits by VOTES: per-region evidence pooled, gated on the
1259
+ // natural break and on consensusFloor, with the dominant allowed to bypass
1260
+ // both. `structuralResonance` admits by a MARGIN over the estimator's own
1261
+ // noise, and `crossRegionVotes` admits by STRUCTURE (which regions may pair
1262
+ // at all, with at least one side individually discriminative). Read
1263
+ // together they look like one policy written three times; they are three
1264
+ // different measurements of the same question ("is this evidence?"), and
1265
+ // unifying them would average three readings into one — the mistake
1266
+ // `extraction.ts` records as "do not unify the two into one machine".
1267
+ // What they DO share, and must keep sharing, is the discipline of deriving
1268
+ // every bar from D/W/N rather than choosing it (thresholds.md).
1209
1269
  const rejectionReasons = [];
1210
1270
  if (absorbed) {
1211
1271
  status = "overlap";
@@ -1216,9 +1276,75 @@ export function commitVotes(ctx, pooled, sat, regions, regionVoter, N, td, cfg)
1216
1276
  pastLeadingSaturation = pastLeading;
1217
1277
  const vote = votesIdf.get(point.anchor) ?? 0;
1218
1278
  if (roots.length === 0) {
1219
- // The first non-overlapping root is DOMINANT and bypasses the two
1220
- // vote thresholds (it always grounds) — only the leading-saturation
1221
- // gate still applies to it.
1279
+ // THE DOMINANCE PRIVILEGE, AND THE TENSION IT CARRIES (measured).
1280
+ //
1281
+ // The first non-overlapping candidate is DOMINANT: it bypasses both
1282
+ // vote gates below and grounds on its own; only the leading-saturation
1283
+ // gate still applies to it. The privilege is load-bearing — analogies,
1284
+ // substitutions and composed contexts are precisely candidates the
1285
+ // query does NOT contain, and the engine loses them without it.
1286
+ //
1287
+ // WHICH candidate receives it, though, is decided by this loop's ORDER.
1288
+ // That order comes from `ranked`, and when two candidates carry equal
1289
+ // evidence the stable sort preserves the ENUMERATION order, so the
1290
+ // privilege is allocated by an ordering rather than by a rule.
1291
+ //
1292
+ // Measured on test/34's corpus, query "red":
1293
+ //
1294
+ // 0:#77 vote=1.3863 idf=1.3863 [0,3) | 1:#49 vote=1.3863 idf=1.3863 [0,3)
1295
+ //
1296
+ // Both candidates (`red square` #77, `red circle` #49) have IDENTICAL
1297
+ // `vote` AND IDENTICAL `idfVote` over the SAME support span, so the
1298
+ // comparator leaves them tied, the second is absorbed as "overlap", and
1299
+ // the first grounds. With this build's enumeration order that first is
1300
+ // `red square`, 60/60 seeds, and `red circle` — the JOINT context — is
1301
+ // never reached by "red" alone. That is the premise test/34 exists to
1302
+ // protect: no single region reaches the joint context, which is what
1303
+ // makes the binding query unreachable without direct region
1304
+ // interaction.
1305
+ //
1306
+ // THE TENSION: the premise therefore holds BY ENUMERATION ORDER, not by
1307
+ // a rule, so any change to this ordering can move the privilege onto the
1308
+ // joint context and let one region reach it. Measured: adding
1309
+ // `|| a.anchor - b.anchor` — the lowest-id tie-break that AGENTS.md §2
1310
+ // sanctions as an equivalent corpus-determined tie-break — does exactly
1311
+ // that: "red" then attends to `red circle`, test/34 fails 6/1, and the
1312
+ // canonical suite reports 1 failure.
1313
+ //
1314
+ // TWO ATTEMPTS TO MAKE IT A RULE, BOTH REFUTED BY MEASUREMENT:
1315
+ //
1316
+ // 1. EVIDENCE SEPARATION. Grant the privilege only when the first
1317
+ // candidate's evidence is separated from the next distinct
1318
+ // candidate's by more than the co-dominant band (sqrt(k) *
1319
+ // estimatorNoise(D)). Refuted: that band exists to ADMIT the
1320
+ // anchors the estimator cannot separate from the dominant — its own
1321
+ // documented purpose — so withholding the privilege on ties removes
1322
+ // the very case it was written for. Suite: 4 failures (the two
1323
+ // co-dominant band laws, breadth/scale invariance, test/29 D2).
1324
+ //
1325
+ // 2. QUERY-OWNED CONTENT. Grant the privilege only to a candidate
1326
+ // that IS a recognised region's identity (regions.some(r => r.id ===
1327
+ // point.anchor)). Measured: for "circle" that identity IS the
1328
+ // ranked candidate, so the privilege stays and `circle` grounds; for
1329
+ // "red" the identity is the `red` node itself while the candidates
1330
+ // are the conjunctions, so neither is privileged; for "red then
1331
+ // circle" the composed context carries idf 3.958 and clears both
1332
+ // gates on its own evidence. All four control queries came out
1333
+ // right — and the suite: 10 failures, six of them in the
1334
+ // analogy/counterfactual/CAST suites ("an analogy still transfers
1335
+ // from a structure the query never names"; "a substitute the query
1336
+ // NAMES may still be voiced"). Refuted: the privilege exists to
1337
+ // admit what the query does NOT contain, so identity is the wrong
1338
+ // axis.
1339
+ //
1340
+ // WHAT A FUTURE ATTEMPT MUST RESPECT: whatever allocates this privilege
1341
+ // has to (a) keep it available to candidates the query does not contain
1342
+ // — analogies, substitutions, compositions — and (b) not depend on the
1343
+ // estimator's ordering among anchors of equal evidence, because that
1344
+ // ordering is not a fact about the corpus. No lever satisfying both has
1345
+ // been found. Until one is, this premise rests on the enumeration order
1346
+ // recorded above, and test/34 is the only test that notices if it
1347
+ // moves.
1222
1348
  dominant = true;
1223
1349
  if (pastLeading) {
1224
1350
  status = "root";
@@ -1229,8 +1355,15 @@ export function commitVotes(ctx, pooled, sat, regions, regionVoter, N, td, cfg)
1229
1355
  }
1230
1356
  }
1231
1357
  else {
1232
- passesNaturalBreak = vote >= rootCut;
1233
- passesConsensusFloor = vote >= floor;
1358
+ // THE FLOOR AND THE BREAK READ THE IDF WEIGHTING. `floor` is derived
1359
+ // for pooled IDF-weighted votes, and `rootCut` comes from the IDF
1360
+ // distribution (`idfDesc`), so gating the mode-dependent `vote` against
1361
+ // either let a weighting mode change an admission (measured: anchor 87,
1362
+ // inverse 2.682 admitted vs direct 1.468 refused). Reading the IDF sum
1363
+ // makes the verdict mode-independent, and changes nothing in the
1364
+ // engine's own mode, where the two readings coincide.
1365
+ passesNaturalBreak = point.idfVote >= rootCut;
1366
+ passesConsensusFloor = point.idfVote >= floor;
1234
1367
  // CO-DOMINANT — an anchor the estimator cannot separate from the
1235
1368
  // dominant inherits the dominant's exemption, because that exemption's
1236
1369
  // only warrant is being TOP, and "top" is not a fact about the corpus
@@ -1685,6 +1818,13 @@ ownRootsA, ownRootsB, trace) {
1685
1818
  outcome,
1686
1819
  });
1687
1820
  };
1821
+ // ── ADMISSION BY MARGIN, not by votes (see voteRegions' note) ─────────
1822
+ // What this site measures: how far the best ANN proposal's effective score
1823
+ // (score × semanticConfidence) stands above the runner-up's, against
1824
+ // `estimatorNoise(D)`. What it does NOT measure: how many regions voted,
1825
+ // or whether the query's regions agree — that is voteRegions' question, and
1826
+ // here a synthetic gist has already replaced them. The two bars are both
1827
+ // derived (thresholds.md), and neither is a tuning of the other.
1688
1828
  let selected = null;
1689
1829
  let selectedReach = null;
1690
1830
  let selectedIdf = 0;
@@ -1790,6 +1930,15 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
1790
1930
  // successfully reconstructed while probing one pair must not be read and
1791
1931
  // perceived again while probing another pair in the same climb.
1792
1932
  const siblingGistMemo = new Map();
1933
+ // ── ADMISSION BY STRUCTURE, not by a bar (see voteRegions' note) ──────
1934
+ // What this site decides: WHICH regions may pair at all — a region that
1935
+ // already voted (individually discriminative), or a KNOWN non-voting one as
1936
+ // the weak side of a pair whose other side voted; never two non-voting
1937
+ // regions, and never a span contained in a maximal one whose reading is
1938
+ // exact. The bar (the container's idf) comes later, on the candidate. So
1939
+ // its "rejection reasons" name structural disqualifications — a different
1940
+ // vocabulary because it answers a different question, and the three
1941
+ // taxonomies stay separate for the same reason the readings do.
1793
1942
  const votedSpans = new Set();
1794
1943
  for (const rv of rvs.votes)
1795
1944
  votedSpans.add(`${rv.start},${rv.end}`);