@hviana/sema 0.8.2 → 0.8.5

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 (126) hide show
  1. package/AGENTS.md +38 -37
  2. package/README.md +17 -38
  3. package/TRADEMARKS.md +0 -1
  4. package/dist/example/demo.js +85 -34
  5. package/dist/src/config.d.ts +11 -0
  6. package/dist/src/config.js +2 -0
  7. package/dist/src/geometry.d.ts +21 -10
  8. package/dist/src/geometry.js +21 -12
  9. package/dist/src/meter.d.ts +62 -0
  10. package/dist/src/meter.js +62 -0
  11. package/dist/src/mind/articulation.js +1 -1
  12. package/dist/src/mind/attention.d.ts +4 -0
  13. package/dist/src/mind/attention.js +167 -17
  14. package/dist/src/mind/canonical.d.ts +16 -0
  15. package/dist/src/mind/canonical.js +41 -0
  16. package/dist/src/mind/derivation.d.ts +201 -0
  17. package/dist/src/mind/derivation.js +327 -0
  18. package/dist/src/mind/graph-search.d.ts +2 -1
  19. package/dist/src/mind/graph-search.js +70 -29
  20. package/dist/src/mind/match.d.ts +3 -1
  21. package/dist/src/mind/match.js +7 -3
  22. package/dist/src/mind/mechanisms/alu.js +0 -2
  23. package/dist/src/mind/mechanisms/cast.d.ts +1 -5
  24. package/dist/src/mind/mechanisms/cast.js +16 -19
  25. package/dist/src/mind/mechanisms/confluence.d.ts +0 -3
  26. package/dist/src/mind/mechanisms/confluence.js +27 -9
  27. package/dist/src/mind/mechanisms/cover.js +17 -20
  28. package/dist/src/mind/mechanisms/extraction.d.ts +0 -1
  29. package/dist/src/mind/mechanisms/extraction.js +13 -8
  30. package/dist/src/mind/mechanisms/prefix-completion.js +0 -1
  31. package/dist/src/mind/mechanisms/recall.d.ts +0 -1
  32. package/dist/src/mind/mechanisms/recall.js +40 -13
  33. package/dist/src/mind/mechanisms/reference.js +3 -4
  34. package/dist/src/mind/mind.d.ts +4 -2
  35. package/dist/src/mind/mind.js +5 -4
  36. package/dist/src/mind/pipeline-mechanism.d.ts +7 -3
  37. package/dist/src/mind/pipeline.js +136 -44
  38. package/dist/src/mind/primitives.js +9 -1
  39. package/dist/src/mind/rationale.d.ts +21 -5
  40. package/dist/src/mind/rationale.js +16 -21
  41. package/dist/src/mind/reasoning.d.ts +12 -20
  42. package/dist/src/mind/reasoning.js +190 -106
  43. package/dist/src/mind/recognition.js +4 -8
  44. package/dist/src/mind/resonance.js +20 -1
  45. package/dist/src/mind/trace.js +1 -0
  46. package/dist/src/mind/traverse.js +6 -2
  47. package/dist/src/mind/types.d.ts +36 -13
  48. package/dist/src/mind/types.js +6 -3
  49. package/docs/INDEX.md +23 -24
  50. package/docs/INVARIANTS.md +16 -17
  51. package/docs/architecture/bounded-reads.md +5 -5
  52. package/docs/architecture/closure.md +65 -0
  53. package/docs/architecture/commonality.md +29 -20
  54. package/docs/architecture/cost-model.md +7 -7
  55. package/docs/architecture/determinism.md +7 -7
  56. package/docs/architecture/exact-vs-approximate.md +4 -4
  57. package/docs/architecture/factored-machinery.md +14 -14
  58. package/docs/architecture/match-project.md +2 -3
  59. package/docs/architecture/mechanism-market.md +16 -16
  60. package/docs/architecture/meter.md +10 -11
  61. package/docs/architecture/store.md +4 -4
  62. package/docs/architecture/thresholds.md +1 -1
  63. package/docs/failures/tempting-but-wrong.md +14 -5
  64. package/docs/harness/gates.md +7 -7
  65. package/docs/mechanisms/cast.md +2 -2
  66. package/docs/mechanisms/cover.md +4 -5
  67. package/docs/mechanisms/extraction.md +7 -7
  68. package/docs/mechanisms/recall.md +8 -9
  69. package/example/demo.ts +90 -37
  70. package/jsr.json +1 -1
  71. package/package.json +1 -1
  72. package/src/alu/README.md +11 -12
  73. package/src/config.ts +13 -0
  74. package/src/geometry.ts +21 -13
  75. package/src/meter.ts +62 -0
  76. package/src/mind/articulation.ts +0 -1
  77. package/src/mind/attention.ts +169 -17
  78. package/src/mind/canonical.ts +43 -0
  79. package/src/mind/derivation.ts +473 -0
  80. package/src/mind/graph-search.ts +76 -34
  81. package/src/mind/match.ts +7 -3
  82. package/src/mind/mechanisms/alu.ts +0 -2
  83. package/src/mind/mechanisms/cast.ts +20 -22
  84. package/src/mind/mechanisms/confluence.ts +27 -13
  85. package/src/mind/mechanisms/cover.ts +17 -20
  86. package/src/mind/mechanisms/extraction.ts +13 -9
  87. package/src/mind/mechanisms/prefix-completion.ts +0 -1
  88. package/src/mind/mechanisms/recall.ts +39 -13
  89. package/src/mind/mechanisms/reference.ts +2 -3
  90. package/src/mind/mind.ts +6 -4
  91. package/src/mind/pipeline-mechanism.ts +7 -3
  92. package/src/mind/pipeline.ts +160 -52
  93. package/src/mind/primitives.ts +9 -1
  94. package/src/mind/rationale.ts +27 -23
  95. package/src/mind/reasoning.ts +227 -120
  96. package/src/mind/recognition.ts +4 -8
  97. package/src/mind/resonance.ts +19 -1
  98. package/src/mind/trace.ts +1 -0
  99. package/src/mind/traverse.ts +7 -5
  100. package/src/mind/types.ts +41 -15
  101. package/test/105-derive-through-reports-its-refusal.test.mjs +24 -0
  102. package/test/118-the-join-reaches-a-key-off-the-cut.test.mjs +74 -0
  103. package/test/119-the-work-does-not-grow-with-the-corpus.test.mjs +122 -0
  104. package/test/120-composition-is-consequence.test.mjs +132 -0
  105. package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +128 -0
  106. package/test/122-the-climb-search-does-not-grow-with-the-corpus.test.mjs +117 -0
  107. package/test/123-the-paired-formulas-agree.test.mjs +90 -0
  108. package/test/125-the-post-grounding-branch-publishes-its-operand.test.mjs +51 -0
  109. package/test/126-the-pipeline-does-not-name-mechanisms.test.mjs +42 -0
  110. package/test/128-the-leads-somewhere-pair-agrees.test.mjs +83 -0
  111. package/test/129-the-trace-payload-shape.test.mjs +164 -0
  112. package/test/133-the-decision-point-renders-the-state.test.mjs +204 -0
  113. package/test/134-the-law-explains-the-engines-own-refusal.test.mjs +237 -0
  114. package/test/135-one-law-any-producer.test.mjs +289 -0
  115. package/test/136-the-two-named-limits.test.mjs +205 -0
  116. package/test/137-the-law-lives-once-and-below.test.mjs +400 -0
  117. package/test/138-the-remainder-drains-only-what-a-move-declares.test.mjs +62 -0
  118. package/test/139-the-witness-is-engagement-not-explanation.test.mjs +51 -0
  119. package/test/140-irrelevant-supply-does-not-change-an-answer.test.mjs +48 -0
  120. package/test/141-the-question-is-paid-at-construction.test.mjs +98 -0
  121. package/test/32-confluence.test.mjs +68 -0
  122. package/test/36-already-answered-fusion.test.mjs +20 -2
  123. package/test/37-cluster-dispersion-fusion.test.mjs +30 -3
  124. package/test/38-reason-restate-guard.test.mjs +28 -2
  125. package/test/43-cast-analog-seat.test.mjs +10 -0
  126. package/test/55-cost-meter.test.mjs +862 -0
@@ -0,0 +1,327 @@
1
+ // derivation.ts — the derivation unit and the one closure law it is governed by.
2
+ //
3
+ // THE LAW, in the quantities this engine already has:
4
+ //
5
+ // A derivation is CLOSED for a query when the structure it built accounts for
6
+ // the query's REMAINDER under the engine's own identity and admission rules,
7
+ // and every step is priced on the one ladder.
8
+ //
9
+ // It is written ONCE, here, because that is where a rule has to live — in the
10
+ // behaviour of the code and at the sites it governs (this repository's own
11
+ // history records the same conclusion: a law written as a document was deleted,
12
+ // and the reason given was that the rule belongs in the code). The tiers that
13
+ // can evaluate it ask it; the tiers that cannot say so and are named below.
14
+ //
15
+ // WHAT THE LAW READS, AND WHAT IT NEVER READS. It reads the state and the
16
+ // proposed continuation, and nothing else: no mechanism, no store, no context,
17
+ // no producer. Its whole purpose is that a product may be consumed by ANY
18
+ // transition the law admits, so that composition is the next transition of the
19
+ // same state rather than an operation with its own dispatch.
20
+ //
21
+ // THE TWO WITNESSES ARE THE LAYER'S. `contains` (does the transition's
22
+ // structure hold the product? a node in its tree, or one contiguous byte run)
23
+ // and `moves` (does it reach structure this derivation has not consumed?) are
24
+ // resolved by the layer that knows the structure, and handed in. The law never
25
+ // re-derives them, and never probes the store to ask: a predicate that would
26
+ // scan the store for every candidate would raise the cost of a step, which is
27
+ // why `traverse.ts`'s own admission predicate is LENT to the chart rather than
28
+ // called through it.
29
+ //
30
+ // THE UNIT: product, accounted, remainder, cost, and the two declarations a
31
+ // producer makes about its own result (`fixed`, `used`). No identity field
32
+ // (the product is content-addressed, so its identity is `resolve(product)` — a
33
+ // pure function, and a cache is not a field); no structure field (that is what
34
+ // identity is computed over); no frontier field (a continuation is computed by
35
+ // the layer that can offer one); no producer field (docs/architecture's own
36
+ // contract: `provenance` is observability and nothing compares it); and no
37
+ // count of any kind — no depth, hop, visited, once or cap.
38
+ //
39
+ // FIVE QUESTIONS, FIVE OWNERS — and no two of them may be answered by one fact:
40
+ //
41
+ // ENGAGEMENT does the step touch the question's material? `carries`: admits,
42
+ // consumes nothing
43
+ // PROGRESS does it reach structure not yet consumed? `reaches`: admits
44
+ // AND consumes what the
45
+ // step's window carries
46
+ // ACCOUNTING what does the step price? the span, into
47
+ // `accounted`
48
+ // CLOSURE is the question's material all accounted for? `closed` — the law
49
+ // TERMINATION why does the walk stop at all? the LAYER: its offer
50
+ // ends, or the law refuses
51
+ // one; never a depth
52
+ // CYCLES why is a loop not a walk? the LAYER's consumed
53
+ // set; the law only sees
54
+ // that a cycle never
55
+ // closes it (test/138.1)
56
+ //
57
+ // A step can progress without closing the derivation; a derivation can be closed
58
+ // with no further transition (the grounding built it closed); and neither of those
59
+ // is termination.
60
+ //
61
+ // THE REMAINDER TRAVELS UNCHANGED, and this was measured, not assumed: letting
62
+ // `advance` drain the witness made a step that engaged the gap CLOSE the
63
+ // derivation, the next step was admitted unconstrained, and test/110's
64
+ // three-link chain drifted one hop past its satisfying answer. A step ENGAGES
65
+ // the question's leftover; it does not consume it. Termination is therefore not
66
+ // the law's: it is the walker's cycle protection over a finite graph.
67
+ //
68
+ // TWO LIMITS ARE PROVED AND LEFT OUT (test/136 pins both, with the measurements):
69
+ // 1. the CHART cannot evaluate accounting — its interface has no parameter for
70
+ // it, and carrying it per chart item was measured and rejected. The chart
71
+ // evaluates the same law read off an item: identity is the item's `key`,
72
+ // continuation is the rule's existence, progress is the frontier advancing,
73
+ // closure is the goal test, and `fix` is `fixed`.
74
+ // 2. closure by the QUERY'S POSITION in the graph is not a term of the unit:
75
+ // `reason`'s echo guards stop a derivation although the remainder is not
76
+ // empty, and recall's reverse-recall tiers close it with an empty
77
+ // accounting. That is a fact about the asker's material in the store,
78
+ // available only to the layer holding it — recorded as a limit, not patched
79
+ // into the unit as a foreign key.
80
+ //
81
+ // LAYERING: this module imports `../bytes.js` only. It sits BELOW
82
+ // `graph-search.ts`, `match.ts`, `rationale.ts` and `pipeline.ts`, so each may
83
+ // ask it and none may be asked by it. The span algebra lives here too: it is
84
+ // the law's vocabulary — gap arithmetic over the asker's bytes — and not the
85
+ // tracer's, which is why it moved out of `rationale.ts`.
86
+ import { indexOf } from "../bytes.js";
87
+ // ── The span algebra: the law's vocabulary ──────────────────────────────────
88
+ /** The BYTE COUNT of a span list — what the currency calls `unaccounted` in
89
+ * `weight = moves + PASS·unaccounted`. ONE definition: this was four copies of
90
+ * the same `reduce` before the architecture audit collapsed them. */
91
+ export function unaccountedBytes(spans) {
92
+ let total = 0;
93
+ for (const [a, b] of spans)
94
+ total += b - a;
95
+ return total;
96
+ }
97
+ /** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` — the
98
+ * union-of-spans reading the ladder prices at PASS per byte, and the raw
99
+ * material every closure decision measures. Clipped to the question, sorted
100
+ * and merged, so two overlapping spans account for their union once. */
101
+ export function unexplainedSpans(queryLen, accounted) {
102
+ const sorted = accounted
103
+ .map(([s, e]) => [Math.max(0, s), Math.min(queryLen, e)])
104
+ .filter(([s, e]) => e > s)
105
+ .sort((a, b) => a[0] - b[0]);
106
+ const gaps = [];
107
+ let reach = 0;
108
+ for (const [s, e] of sorted) {
109
+ if (s > reach)
110
+ gaps.push([reach, s]);
111
+ if (e > reach)
112
+ reach = e;
113
+ }
114
+ if (reach < queryLen)
115
+ gaps.push([reach, queryLen]);
116
+ return gaps;
117
+ }
118
+ /** THE REMAINDER: what no step has accounted for, with every span below one
119
+ * river-fold quantum dropped. `W` is the mind's own line between bridging
120
+ * punctuation and a substantive phrase — the same floor `liftedScaffolding`
121
+ * and the honesty-density bar use — so a remainder under it licenses nothing
122
+ * and blocks nothing. This is the law's measure. */
123
+ export function remainderOf(queryLen, explained, W) {
124
+ return unexplainedSpans(queryLen, explained).filter(([a, b]) => b - a >= W);
125
+ }
126
+ /** The window of `span` that `product` holds, or null: the ONE reading of
127
+ * coverage — used by {@link carries}, by the move branch, and by the GROUNDING
128
+ * when it decides what its answer has actually paid for. */
129
+ export function windowOf(span, product, query, W) {
130
+ const [a, b] = span;
131
+ for (let i = a; i + W <= b; i++) {
132
+ if (indexOf(product, query.subarray(i, i + W), 0) >= 0)
133
+ return [i, i + W];
134
+ }
135
+ return null;
136
+ }
137
+ /** PROGRESS, by coverage: the first member of `remainder` that `product` carries
138
+ * a whole quantum of, or null when it carries none. The window is taken from
139
+ * `query`, the asker's own bytes, so the test is "this product restates a
140
+ * quantum of what was left unaccounted", never a similarity score.
141
+ *
142
+ * One witness per step, deterministically the FIRST in remainder order: the
143
+ * measure stays a single member of a finite list, which is what makes the walk
144
+ * reviewable — and, because the remainder is not drained, it is the walker's
145
+ * cycle protection (not this) that terminates a chain. */
146
+ export function carries(remainder, product, query, W) {
147
+ for (const span of remainder) {
148
+ const window = windowOf(span, product, query, W);
149
+ if (window !== null)
150
+ return [{ span, window }];
151
+ }
152
+ return null;
153
+ }
154
+ // ── The restatement reading ─────────────────────────────────────────────────
155
+ /** Whether `bytes` RESTATES the question — says nothing the asker did not just
156
+ * say — and is therefore not an answer. This is a closure condition: a
157
+ * derivation whose product is already the question has added nothing, and the
158
+ * engine asks it in five places. ONE definition, asked everywhere, with the
159
+ * DIFFERENCES between those places supplied as WITNESSES by the caller — never
160
+ * as a mechanism or a producer. If this function ever needs to know who
161
+ * produced the bytes to decide, the right conclusion is that a witness is
162
+ * missing, not that it should dispatch.
163
+ *
164
+ * `floor` is one river-fold quantum: below it, byte overlap is chance, not
165
+ * evidence — the same line `identityBar`, the bridge's `attestedQ` and
166
+ * recognition's site floor all draw. `0` disables the floor, which is the
167
+ * reading the callers that ask before any structure exists use.
168
+ *
169
+ * THE THREE READINGS the callers need, and why each is a witness rather than a
170
+ * branch here:
171
+ *
172
+ * • `proper` — a PROPER part of the question (strictly shorter). This is the
173
+ * reading every tier that rejects a fragment uses.
174
+ * • `whole` — the EQUALITY reading: only "the answer IS the question" counts,
175
+ * because the caller has already handled a proper fragment elsewhere (a
176
+ * recall tier's own subspan tests).
177
+ * • `equate` — the response's own notion of "the same text" (whatever
178
+ * `src/canon.ts` equates: case, width, whitespace). A caller that has one
179
+ * passes it; a caller that does not gets the byte-exact reading. It is the
180
+ * same fallback `resolve` already makes when an exact lookup misses.
181
+ *
182
+ * The LITERAL EXEMPTION is deliberately NOT here: whether a span is the site's
183
+ * own bytes at its own position is the CALLER's knowledge, and a caller states
184
+ * it by not asking (see `segRestatesQuery` in types.ts, which returns false for
185
+ * a literal span before reaching this). */
186
+ export function restates(query, bytes, floor = 0, witnesses = {}) {
187
+ const equate = witnesses.equate ?? null;
188
+ const from = witnesses.from ?? 0;
189
+ const q = equate === null ? query : equate(query);
190
+ const b = equate === null ? bytes : equate(bytes);
191
+ if (b.length > q.length || b.length < floor)
192
+ return false;
193
+ if (witnesses.whole === true) {
194
+ return b.length === q.length && indexOf(q, b, from) >= 0;
195
+ }
196
+ if (witnesses.proper === true && b.length === q.length)
197
+ return false;
198
+ return indexOf(q, b, from) >= 0;
199
+ }
200
+ /** Whether the query span `[from, to)` lies inside a COMPLETED ASSISTANT TURN —
201
+ * material the engine has already produced, so it is context rather than
202
+ * something the asker is asserting. Recognition and attention still see the
203
+ * full transcript; what excludes these spans is the closure reading "this was
204
+ * already answered", and it is a closure reading rather than a budget: a window
205
+ * inside a prior reply is not a fresh constraint.
206
+ *
207
+ * ONE definition of it. `cursor` is the CALLER's own progress through `turns`
208
+ * (they are ascending and each caller scans its candidates in ascending order),
209
+ * so the amortised search is preserved exactly and a caller passes the same
210
+ * holder for a whole scan: extracting the reading must not cost the scan. */
211
+ export function insideAnsweredTurn(turns, cursor, from, to) {
212
+ while (cursor.at < turns.length && turns[cursor.at][1] <= from)
213
+ cursor.at++;
214
+ const turn = turns[cursor.at];
215
+ return turn !== undefined && turn[0] <= from && to <= turn[1];
216
+ }
217
+ /** CLOSED — nothing of the asker's material is left unaccounted. */
218
+ export function closed(d) {
219
+ return d.remainder.length === 0;
220
+ }
221
+ /** THE LAW, evaluated once.
222
+ *
223
+ * Returns the spans the transition accounts for — the witness that lets it be
224
+ * taken — or `null` when it is inadmissible. Evaluating it once and advancing
225
+ * the state with {@link advance} is the whole of a transition; asking twice for
226
+ * the same pair would repeat the scan, which this module must not make anyone
227
+ * do.
228
+ *
229
+ * ¬FIXED ∧ CONTAINS ∧ ( CLOSED ∨ CARRIES ∨ MOVES )
230
+ *
231
+ * Cost is not a term: it is the lattice order the market minimises over, and
232
+ * neither is any budget — a cap decides with a number of work, this decides
233
+ * with the remainder. */
234
+ export function admissible(d, t, query, W) {
235
+ if (d.fixed)
236
+ return null;
237
+ if (!t.contains)
238
+ return null;
239
+ if (closed(d))
240
+ return (t.explains ?? []).map((span) => ({ span }));
241
+ // MOVES BEFORE CARRIES, and that order is not arbitrary: a transition that
242
+ // DECLARES it moves (the walker's forward-absorb, and a pivot whose producing
243
+ // mechanism declared the anchors it speaks for) is admitted on that ground
244
+ // alone and records no coverage witness — which is exactly how the code it
245
+ // replaces behaved, where the brake was skipped whenever the producer owned
246
+ // the shape of its answer. Reading coverage first would attribute to such a
247
+ // step material it was never asked to account for.
248
+ if (t.reaches) {
249
+ // A MOVE IS ITS OWN GROUND — it needs no question material to be admitted.
250
+ // What it CARRIES is nonetheless the one thing the question's remainder may
251
+ // be consumed by, and it is read here by the SAME reading the carries
252
+ // admission uses: one definition, so the law cannot be told a span it does
253
+ // not hold. A move that carries nothing consumes nothing.
254
+ const carried = carries(d.remainder, t.product, query, W);
255
+ if (carried !== null)
256
+ return carried;
257
+ return (t.explains ?? []).map((span) => ({ span }));
258
+ }
259
+ return carries(d.remainder, t.product, query, W);
260
+ }
261
+ /** ADVANCE — the transition itself: the state that results from consuming `t`
262
+ * with the witness {@link admissible} returned. The product moves, the
263
+ * accounting accumulates what the step engaged, and the cost is added.
264
+ *
265
+ * THE REMAINDER DRAINS ONLY ON A DECLARED MOVE, and that is measured, not
266
+ * assumed: a step admitted by CARRIES holds question material, which any
267
+ * repetition also holds — draining on it let a cycle empty the remainder and
268
+ * close a derivation whose product never changed. A MOVE (structure this
269
+ * derivation has not consumed) cannot be produced by repetition, so it is the
270
+ * one evidence on which the question's leftover may be consumed. The result is
271
+ * no longer a supplied fixed point. */
272
+ function drain(remainder, explains) {
273
+ return remainder.flatMap(([a, b]) => {
274
+ const cut = explains.find(([x, y]) => x >= a && y <= b);
275
+ if (!cut)
276
+ return [[a, b]];
277
+ const [x, y] = cut;
278
+ const w = y - x;
279
+ const out = [];
280
+ if (x - a >= w)
281
+ out.push([a, x]);
282
+ if (b - y >= w)
283
+ out.push([y, b]);
284
+ return out;
285
+ });
286
+ }
287
+ export function advance(d, t, explains) {
288
+ const spans = explains.map((w) => w.span);
289
+ const carried = t.reaches === true
290
+ ? explains.flatMap((w) => (w.window ? [w.window] : []))
291
+ : [];
292
+ return {
293
+ product: t.product,
294
+ accounted: spans.length === 0 ? d.accounted : [...d.accounted, ...spans],
295
+ remainder: carried.length === 0 ? d.remainder : drain(d.remainder, carried),
296
+ cost: d.cost + t.cost,
297
+ used: d.used,
298
+ };
299
+ }
300
+ /** THE CLOSURE — the walk of {@link advance} over the continuations `offer`
301
+ * proposes, run until the layer has nothing further to offer or the law refuses
302
+ * the one it offered.
303
+ *
304
+ * ADMISSION is entirely the law's; TERMINATION is the layer's, and deliberately
305
+ * so. The remainder does not descend (draining it was implemented and refuted
306
+ * — see the module note), so a walk cannot run forever only because the layer
307
+ * offering continuations keeps its own cycle protection over a finite graph.
308
+ * Nothing here counts steps, and nothing here decides admissibility. */
309
+ export async function closure(d, query, W, offer,
310
+ /** Called for each step the law admits, with the state before and after. */
311
+ onTaken,
312
+ /** Called when the law refused the continuation the layer offered. */
313
+ onRefused) {
314
+ for (;;) {
315
+ const t = await offer(d);
316
+ if (t === null)
317
+ return d;
318
+ const explains = admissible(d, t, query, W);
319
+ if (explains === null) {
320
+ onRefused?.(d);
321
+ return d;
322
+ }
323
+ const next = advance(d, t, explains);
324
+ onTaken?.(d, next);
325
+ d = next;
326
+ }
327
+ }
@@ -186,7 +186,7 @@ export declare class GraphSearch {
186
186
  * Any learnt connector between two rewrites is spliced IN by the in-search
187
187
  * connector rule (see {@link outRules}), so the returned spans already carry
188
188
  * it — there is no post-pass. */
189
- cover(queryLen: number, sites: ReadonlyArray<Site>, conceptTarget: ReadonlyMap<number, number>, leaves: ReadonlyArray<Leaf>, splits: ReadonlySet<number>, starts: ReadonlySet<number>, substitutions?: ReadonlyMap<number, Uint8Array>, connectors?: ReadonlyMap<string, Uint8Array>, computedResults?: ReadonlyArray<ComputedResult>,
189
+ cover(queryLen: number, sites: ReadonlyArray<Site>, conceptTarget: ReadonlyMap<number, number>, leaves: ReadonlyArray<Leaf>, splits: ReadonlySet<number>, substitutions?: ReadonlyMap<number, Uint8Array>, connectors?: ReadonlyMap<string, Uint8Array>, computedResults?: ReadonlyArray<ComputedResult>,
190
190
  /** When given, receives each solved span's lightest derivation — the full
191
191
  * adapted A*LD proof tree as classified {@link DerivationStep}s — for the
192
192
  * TOP cover AND every nested completion the sink is threaded into (see
@@ -196,6 +196,7 @@ export declare class GraphSearch {
196
196
  onDerivation?: (steps: DerivationStep[]) => void): {
197
197
  segs: Seg[];
198
198
  cost: number;
199
+ moves: number;
199
200
  } | null;
200
201
  /** Build the deduction system for one span and return its lightest cover's
201
202
  * chosen spans — the SINGLE routine the query and every produced composite
@@ -51,6 +51,29 @@ function pushInto(mp, k, v) {
51
51
  /** Read the chosen spans back off a derivation: the goal is a chain of bridge
52
52
  * steps, each whose second premise is the `out` it crossed. Walk the chain to
53
53
  * the axiom and reverse into left-to-right order. */
54
+ /** The BYTE TERM of a derivation's cost: how many bytes its bridge rule charged
55
+ * at PASS each — read back off the rule that charged them (`bridgeRule`:
56
+ * `o.rec ? MICRO : PASS * (o.j - o.i)`), so the split exists in ONE place and
57
+ * the engine's generic accumulator is left alone.
58
+ *
59
+ * `derivation.cost - PASS * readBridgedBytes(derivation)` is therefore the
60
+ * derivation's DISCRETE work — the number a mechanism reports as `moves`, which
61
+ * the pipeline's one formula then prices together with `PASS * unaccounted`.
62
+ * The two readings of the byte term coincide by construction: the spans this
63
+ * sums are exactly the ones the chart could not recognise (`rec === false`),
64
+ * which are the spans the candidate leaves unaccounted. */
65
+ function readBridgedBytes(derivation) {
66
+ let bytes = 0;
67
+ let node = derivation;
68
+ while (node && node.rule) {
69
+ const o = node.premises[1]?.item;
70
+ if (o !== undefined && o.kind === "out" && o.rec === false) {
71
+ bytes += o.j - o.i;
72
+ }
73
+ node = node.premises[0];
74
+ }
75
+ return bytes;
76
+ }
54
77
  function readCover(derivation) {
55
78
  const segs = [];
56
79
  let node = derivation;
@@ -229,7 +252,7 @@ export class GraphSearch {
229
252
  * Any learnt connector between two rewrites is spliced IN by the in-search
230
253
  * connector rule (see {@link outRules}), so the returned spans already carry
231
254
  * it — there is no post-pass. */
232
- cover(queryLen, sites, conceptTarget, leaves, splits, starts, substitutions, connectors, computedResults,
255
+ cover(queryLen, sites, conceptTarget, leaves, splits, substitutions, connectors, computedResults,
233
256
  /** When given, receives each solved span's lightest derivation — the full
234
257
  * adapted A*LD proof tree as classified {@link DerivationStep}s — for the
235
258
  * TOP cover AND every nested completion the sink is threaded into (see
@@ -246,12 +269,7 @@ export class GraphSearch {
246
269
  // so the recompositions a produced form needs are reported in the same
247
270
  // trace instead of vanishing after the first layer.
248
271
  this.derivationSink = onDerivation;
249
- const solved = this.solve(queryLen, {
250
- sites,
251
- leaves,
252
- splits,
253
- starts,
254
- }, conceptTarget, substitutions, connectors, computedResults, onDerivation);
272
+ const solved = this.solve(queryLen, { sites, leaves, splits }, conceptTarget, substitutions, connectors, computedResults, onDerivation);
255
273
  // Deepening runs HERE, once, on the derivation the top cover CHOSE — never
256
274
  // inside the nested solve a completion runs. Nesting the deepening is what
257
275
  // made per-query cost track how densely the corpus interconnects the forms
@@ -259,9 +277,11 @@ export class GraphSearch {
259
277
  // continuations instead of following the chain the answer itself licensed.
260
278
  // With deepening only at the top, `recompleteNode` walks the accepted chain
261
279
  // one link at a time (its own memo and stack), so the work is the answer's.
262
- return solved === null
263
- ? null
264
- : { segs: this.deepen(solved.segs), cost: solved.cost };
280
+ return solved === null ? null : {
281
+ segs: this.deepen(solved.segs),
282
+ cost: solved.cost,
283
+ moves: solved.moves,
284
+ };
265
285
  }
266
286
  /** Build the deduction system for one span and return its lightest cover's
267
287
  * chosen spans — the SINGLE routine the query and every produced composite
@@ -277,7 +297,7 @@ export class GraphSearch {
277
297
  * decomposes, two recomposes, any mix — and stops only when it reaches a node
278
298
  * that leads nowhere new, never at an arbitrary count. */
279
299
  solve(spanLen, recognition, conceptTarget, substitutions, connectors, computedResults, onDerivation) {
280
- const system = this.buildSearch(spanLen, recognition.sites, conceptTarget, recognition.leaves, recognition.splits, recognition.starts, substitutions, connectors, computedResults);
300
+ const system = this.buildSearch(spanLen, recognition.sites, conceptTarget, recognition.leaves, recognition.splits, substitutions, connectors, computedResults);
281
301
  // Search-effort accounting (src/meter.ts): the chart's pops/pushes are
282
302
  // the cover's real cost, and a heuristic that stops being admissible
283
303
  // shows up as a pop count that explodes while the answer stays the same.
@@ -296,7 +316,11 @@ export class GraphSearch {
296
316
  onDerivation(readDerivation(derivation, substitutions !== undefined));
297
317
  }
298
318
  return derivation
299
- ? { segs: readCover(derivation), cost: derivation.cost }
319
+ ? {
320
+ segs: readCover(derivation),
321
+ cost: derivation.cost,
322
+ moves: derivation.cost - PASS * readBridgedBytes(derivation),
323
+ }
300
324
  : null;
301
325
  }
302
326
  /** Re-cover the CHOSEN fixpoint spans, in place.
@@ -330,7 +354,7 @@ export class GraphSearch {
330
354
  * adjacent fragments toward a known leaf (findLeaf) or branch (findBranch);
331
355
  * a completion fused with its neighbour may spell a deeper learned form the
332
356
  * flat probes can't name, recovered canonically by {@link resolve}. */
333
- buildSearch(queryLen, sites, conceptTarget, leaves, splits, starts, substitutions, connectors, computedResults) {
357
+ buildSearch(queryLen, sites, conceptTarget, leaves, splits, substitutions, connectors, computedResults) {
334
358
  const W = this.maxGroup; // fusible span ceiling (shortest composite bound)
335
359
  // Same corpus-scale hub floor {@link atomIsHub}/{@link atomReach} (traverse.ts)
336
360
  // derive for byte atoms — see {@link hubBound} below for why this module
@@ -473,7 +497,6 @@ export class GraphSearch {
473
497
  return this.outRules(it, {
474
498
  W,
475
499
  splits,
476
- starts,
477
500
  atomsAreHubs,
478
501
  coversDone,
479
502
  outsByStart,
@@ -868,6 +891,8 @@ export class GraphSearch {
868
891
  // concepts/connectors either (those need the caller's async
869
892
  // pre-resolution) — the recursion follows edges and fusion, which is what
870
893
  // a deeper rewrite chain is made of.
894
+ if (this.host.meter)
895
+ this.host.meter.recompletes++;
871
896
  const rec = this.host.recogniseSpan(bytes);
872
897
  const kids = new Set(nrec.kids);
873
898
  // THE NODE'S OWN KIDS ARE SITES BY STRUCTURE — recognition cannot be the
@@ -912,7 +937,6 @@ export class GraphSearch {
912
937
  sites: [...recognised, ...structural],
913
938
  leaves: rec.leaves,
914
939
  splits: rec.splits,
915
- starts: rec.starts,
916
940
  }, new Map(), undefined, undefined, undefined, this.derivationSink);
917
941
  const answer = solved && concatBytes(solved.segs.map((s) => s.bytes));
918
942
  // ACCEPT, then CONTINUE THE CHAIN — but only along an accepted
@@ -1100,20 +1124,37 @@ export class GraphSearch {
1100
1124
  // duplicate read and a branch that could never be taken.
1101
1125
  let next = null;
1102
1126
  let keyBytes = c.bytes;
1103
- // THE PREFIX ENDS ARE THE TAIL'S OWN FOLD BOUNDARIES, not every byte
1104
- // length. The key is `entity + prefix`, and the prefix that names a
1105
- // stored relation ends where the fold cuts: measured over four join-firing
1106
- // queries, 5 of 5 accepted keys ended on a boundary (or the tail's end)
1107
- // while the byte-by-byte scan spent 153 probes where 14 boundaries would
1108
- // do. Same criterion — resolves AND leads — same shortest-first order, so
1109
- // the answer is the same one the enumeration found; only the candidates
1110
- // come from the structure instead of from the byte count. A host with no
1111
- // boundary rule falls back to the enumeration.
1112
- const cuts = this.host.contentCuts?.(tail);
1113
- const ends = cuts && cuts.length > 0
1114
- ? [...cuts.filter((c) => c > 0 && c < tail.length), tail.length]
1115
- : Array.from({ length: tail.length }, (_, i) => i + 1);
1116
- for (const len of ends) {
1127
+ // THE CANDIDATE ENDS ARE THE PREFIXES THAT ARE STORED NODES, ASCENDING.
1128
+ // The key is `entity + prefix`, and it names a relation exactly when that
1129
+ // concatenation IS a node — so the ends come from a content-addressed
1130
+ // probe per offset (the host's `contentKeyEnds`, the learning path's own
1131
+ // mechanism: one leaf walk plus one `findBranch` per offset, no `resolve`),
1132
+ // never from the fold's boundaries. A boundary is not a proxy: a stored
1133
+ // member's end is the end of ITS OWN stream, and the fold never cuts at a
1134
+ // stream's end — measured, "stockholm mayor" exists, leads on to the mayor
1135
+ // fact, and its boundary 6 sits in neither the tail's cuts ([4,7]) nor the
1136
+ // concatenation's. Filtering the scan by "is this a node?" cannot change
1137
+ // the winner: a position that is not a node cannot resolve, so skipping it
1138
+ // is invisible; and the order stays SHORTEST FIRST, which is a semantic
1139
+ // law, not an optimisation (test/106, test/108 pin it).
1140
+ //
1141
+ // A host that cannot answer falls back to every prefix: exact and
1142
+ // complete, at a `resolve` per offset. A host that CAN answer is
1143
+ // authoritative even when it answers "none" — if no prefix is a node then
1144
+ // no key exists to resolve, so enumerating would only pay nulls. (A key
1145
+ // reachable through the CANONICAL equivalence alone and ending off every
1146
+ // node end is therefore not tried here; that dimension is unreachable on
1147
+ // this path by construction and is not part of the exact-key law.)
1148
+ const ends = this.host.contentKeyEnds?.(c.bytes, tail);
1149
+ const candidateEnds = function* () {
1150
+ if (ends !== undefined) {
1151
+ yield* ends;
1152
+ return;
1153
+ }
1154
+ for (let p = 1; p <= tail.length; p++)
1155
+ yield p;
1156
+ };
1157
+ for (const len of candidateEnds()) {
1117
1158
  keyBytes = concat2(c.bytes, tail.subarray(0, len));
1118
1159
  const k = this.host.resolve(keyBytes) ??
1119
1160
  this.host.canonResolve?.(keyBytes) ??
@@ -200,7 +200,7 @@ export declare function frameSlots(ctx: MindContext, query: Uint8Array, cand: Ui
200
200
  * ANCHOR that the query displaced. Neither implies the other, and the
201
201
  * observed failures pass the restatement guard cleanly.
202
202
  *
203
- * Three conditions, all byte-exact and all necessary:
203
+ * Four conditions, all byte-exact and all necessary:
204
204
  *
205
205
  * 1. the query and the anchor must be ONE STRUCTURE — what they share has to
206
206
  * dominate the query, or the query is not a variant of the anchor at all
@@ -294,6 +294,8 @@ export declare function haloSiblings(ctx: MindContext, id: number, halo?: Vec |
294
294
  * inside the composed phrase. Returns null when the corpus provides no
295
295
  * distributional evidence for the span. */
296
296
  export declare function spanHalo(ctx: MindContext, bytes: Uint8Array, from?: number, to?: number): Vec | null;
297
+ /** A TEST SURFACE: exported for the tests that pin the synonym strength ladder
298
+ * (they are its only consumers); nothing in `src/` calls it. */
297
299
  /** Distributional synonym evidence between arbitrary byte spans. Whole words
298
300
  * need not be independently interned: their stored W-window occurrences are
299
301
  * lifted to episode halos, bundled, and compared. The caller chooses the
@@ -546,7 +546,7 @@ export function frameSlots(ctx, query, cand, id) {
546
546
  * ANCHOR that the query displaced. Neither implies the other, and the
547
547
  * observed failures pass the restatement guard cleanly.
548
548
  *
549
- * Three conditions, all byte-exact and all necessary:
549
+ * Four conditions, all byte-exact and all necessary:
550
550
  *
551
551
  * 1. the query and the anchor must be ONE STRUCTURE — what they share has to
552
552
  * dominate the query, or the query is not a variant of the anchor at all
@@ -631,8 +631,10 @@ export function substituteAll(hay, pairs) {
631
631
  if (usable.length === 0)
632
632
  return hay;
633
633
  // Longest needle first, so a needle that is a prefix of another can never
634
- // pre-empt it. Ties cannot arise: an instance whose fillers are not
635
- // pairwise distinct is refused by frameSlots.
634
+ // pre-empt it. Ties cannot arise: a consumer that VOICES checks the
635
+ // fillers pairwise with `distinct` and refuses such an instance itself —
636
+ // `frameSlots` reports and does not judge (see its own doc), so the refusal
637
+ // lives with the mechanism that needs it, not here.
636
638
  const order = [...usable].sort((a, b) => b.needle.length - a.needle.length);
637
639
  const out = [];
638
640
  let i = 0;
@@ -812,6 +814,8 @@ export function spanHalo(ctx, bytes, from = 0, to = bytes.length) {
812
814
  }
813
815
  return found ? normalize(out) : null;
814
816
  }
817
+ /** A TEST SURFACE: exported for the tests that pin the synonym strength ladder
818
+ * (they are its only consumers); nothing in `src/` calls it. */
815
819
  /** Distributional synonym evidence between arbitrary byte spans. Whole words
816
820
  * need not be independently interned: their stored W-window occurrences are
817
821
  * lifted to episode halos, bundled, and compared. The caller chooses the
@@ -7,7 +7,6 @@
7
7
  // (`evalComputation`) are emitted inside its `parse()`. A user extension
8
8
  // joins the same way — see MindOptions.mechanismFactories.
9
9
  import { STEP } from "../graph-search.js";
10
- import { unexplainedLabel } from "../rationale.js";
11
10
  /** Wrap the ALU as a {@link PipelineMechanism}. */
12
11
  export function aluToMechanism(alu) {
13
12
  return {
@@ -31,7 +30,6 @@ export function aluToMechanism(alu) {
31
30
  bytes: u.bytes,
32
31
  accounted: [[u.i, u.j]],
33
32
  moves: STEP,
34
- unexplained: unexplainedLabel(query, [[u.i, u.j]]),
35
33
  }));
36
34
  },
37
35
  };
@@ -9,10 +9,6 @@ export interface CastResult {
9
9
  used: ReadonlySet<number>;
10
10
  accounted: Array<[number, number]>;
11
11
  moves: number;
12
- /** A human-readable label for the query bytes this schema left
13
- * unexplained — purely diagnostic, never priced (see the module's
14
- * Task 2 note in pipeline.ts's Candidate interface). */
15
- unexplained: string;
16
12
  }
17
13
  /** The seat that establishes a node's role in an analogical comparison:
18
14
  * the REVERSE context (what leads to it) when a predecessor genuinely
@@ -48,7 +44,7 @@ export interface CastResult {
48
44
  * describes id ("...painted by Leonardo da Vinci." contains "Leonardo da
49
45
  * Vinci"). An incidental adjacency predecessor never does — it merely
50
46
  * preceded id in some unrelated document without ever mentioning it. No
51
- * new tuned constant: containment is the same primitive `restatesQuery`
47
+ * new tuned constant: containment is the same primitive `restates`
52
48
  * and `dominates`-style checks already use throughout this codebase.
53
49
  *
54
50
  * `allowForward` (default true) gates the FORWARD branch specifically —