@hviana/sema 0.8.2 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +29 -29
- package/TRADEMARKS.md +0 -1
- package/dist/src/config.d.ts +11 -0
- package/dist/src/config.js +2 -0
- package/dist/src/geometry.d.ts +21 -0
- package/dist/src/geometry.js +21 -0
- package/dist/src/meter.d.ts +51 -0
- package/dist/src/meter.js +51 -0
- package/dist/src/mind/attention.d.ts +4 -0
- package/dist/src/mind/attention.js +165 -16
- package/dist/src/mind/canonical.d.ts +16 -0
- package/dist/src/mind/canonical.js +41 -0
- package/dist/src/mind/graph-search.js +33 -14
- package/dist/src/mind/match.d.ts +1 -1
- package/dist/src/mind/match.js +5 -3
- package/dist/src/mind/mechanisms/cast.js +1 -1
- package/dist/src/mind/mechanisms/confluence.js +24 -0
- package/dist/src/mind/mechanisms/recall.js +32 -4
- package/dist/src/mind/mind.d.ts +4 -2
- package/dist/src/mind/mind.js +5 -4
- package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
- package/dist/src/mind/pipeline.js +41 -14
- package/dist/src/mind/primitives.js +9 -1
- package/dist/src/mind/rationale.d.ts +28 -1
- package/dist/src/mind/rationale.js +22 -1
- package/dist/src/mind/reasoning.d.ts +21 -3
- package/dist/src/mind/reasoning.js +73 -21
- package/dist/src/mind/recognition.js +4 -8
- package/dist/src/mind/resonance.js +20 -1
- package/dist/src/mind/trace.js +1 -0
- package/dist/src/mind/traverse.js +6 -2
- package/dist/src/mind/types.d.ts +36 -13
- package/docs/INVARIANTS.md +2 -2
- package/docs/architecture/bounded-reads.md +1 -1
- package/docs/architecture/commonality.md +2 -2
- package/docs/architecture/cost-model.md +2 -2
- package/docs/architecture/determinism.md +7 -7
- package/docs/architecture/match-project.md +2 -3
- package/docs/architecture/mechanism-market.md +10 -10
- package/docs/architecture/meter.md +5 -5
- package/docs/architecture/store.md +3 -3
- package/docs/failures/tempting-but-wrong.md +3 -4
- package/docs/harness/gates.md +2 -2
- package/docs/mechanisms/cast.md +2 -2
- package/docs/mechanisms/cover.md +2 -3
- package/docs/mechanisms/extraction.md +7 -7
- package/docs/mechanisms/recall.md +8 -9
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/alu/README.md +11 -12
- package/src/config.ts +13 -0
- package/src/geometry.ts +21 -0
- package/src/meter.ts +51 -0
- package/src/mind/attention.ts +167 -16
- package/src/mind/canonical.ts +43 -0
- package/src/mind/graph-search.ts +39 -14
- package/src/mind/match.ts +5 -3
- package/src/mind/mechanisms/cast.ts +3 -1
- package/src/mind/mechanisms/confluence.ts +24 -0
- package/src/mind/mechanisms/recall.ts +32 -4
- package/src/mind/mind.ts +6 -4
- package/src/mind/pipeline-mechanism.ts +7 -0
- package/src/mind/pipeline.ts +49 -16
- package/src/mind/primitives.ts +9 -1
- package/src/mind/rationale.ts +35 -1
- package/src/mind/reasoning.ts +92 -15
- package/src/mind/recognition.ts +4 -8
- package/src/mind/resonance.ts +19 -1
- package/src/mind/trace.ts +1 -0
- package/src/mind/traverse.ts +7 -5
- package/src/mind/types.ts +36 -13
- package/test/105-derive-through-reports-its-refusal.test.mjs +24 -0
- package/test/118-the-join-reaches-a-key-off-the-cut.test.mjs +74 -0
- package/test/119-the-work-does-not-grow-with-the-corpus.test.mjs +122 -0
- package/test/120-composition-is-consequence.test.mjs +132 -0
- package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +128 -0
- package/test/122-the-climb-search-does-not-grow-with-the-corpus.test.mjs +117 -0
- package/test/123-the-paired-formulas-agree.test.mjs +90 -0
- package/test/125-the-post-grounding-branch-publishes-its-operand.test.mjs +51 -0
- package/test/126-the-pipeline-does-not-name-mechanisms.test.mjs +42 -0
- package/test/128-the-leads-somewhere-pair-agrees.test.mjs +83 -0
- package/test/129-the-trace-payload-shape.test.mjs +164 -0
- package/test/32-confluence.test.mjs +68 -0
- package/test/38-reason-restate-guard.test.mjs +8 -2
- package/test/43-cast-analog-seat.test.mjs +10 -0
- package/test/55-cost-meter.test.mjs +859 -0
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
|
|
6
|
-
|
|
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
|
|
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
|
|
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`;
|
|
129
|
-
`node --test test/22-multihop.test.mjs`
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
`
|
|
155
|
-
`danglingReads`/`compactFailures` and the `console.warn`s that report them
|
|
156
|
-
|
|
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
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
package/dist/src/config.d.ts
CHANGED
|
@@ -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
|
package/dist/src/config.js
CHANGED
|
@@ -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,
|
package/dist/src/geometry.d.ts
CHANGED
|
@@ -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
|
package/dist/src/geometry.js
CHANGED
|
@@ -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
|
}
|
package/dist/src/meter.d.ts
CHANGED
|
@@ -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
|
-
|
|
779
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
977
|
-
//
|
|
978
|
-
//
|
|
979
|
-
//
|
|
980
|
-
//
|
|
981
|
-
//
|
|
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
|
-
//
|
|
1220
|
-
//
|
|
1221
|
-
//
|
|
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
|
-
|
|
1233
|
-
|
|
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}`);
|