@hviana/sema 0.8.3 → 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 (85) hide show
  1. package/AGENTS.md +11 -10
  2. package/README.md +17 -38
  3. package/dist/example/demo.js +85 -34
  4. package/dist/src/geometry.d.ts +0 -10
  5. package/dist/src/geometry.js +0 -12
  6. package/dist/src/meter.d.ts +11 -0
  7. package/dist/src/meter.js +11 -0
  8. package/dist/src/mind/articulation.js +1 -1
  9. package/dist/src/mind/attention.js +2 -1
  10. package/dist/src/mind/derivation.d.ts +201 -0
  11. package/dist/src/mind/derivation.js +327 -0
  12. package/dist/src/mind/graph-search.d.ts +2 -1
  13. package/dist/src/mind/graph-search.js +37 -15
  14. package/dist/src/mind/match.d.ts +2 -0
  15. package/dist/src/mind/match.js +2 -0
  16. package/dist/src/mind/mechanisms/alu.js +0 -2
  17. package/dist/src/mind/mechanisms/cast.d.ts +1 -5
  18. package/dist/src/mind/mechanisms/cast.js +15 -18
  19. package/dist/src/mind/mechanisms/confluence.d.ts +0 -3
  20. package/dist/src/mind/mechanisms/confluence.js +3 -9
  21. package/dist/src/mind/mechanisms/cover.js +17 -20
  22. package/dist/src/mind/mechanisms/extraction.d.ts +0 -1
  23. package/dist/src/mind/mechanisms/extraction.js +13 -8
  24. package/dist/src/mind/mechanisms/prefix-completion.js +0 -1
  25. package/dist/src/mind/mechanisms/recall.d.ts +0 -1
  26. package/dist/src/mind/mechanisms/recall.js +8 -9
  27. package/dist/src/mind/mechanisms/reference.js +3 -4
  28. package/dist/src/mind/pipeline-mechanism.d.ts +1 -4
  29. package/dist/src/mind/pipeline.js +106 -41
  30. package/dist/src/mind/rationale.d.ts +0 -11
  31. package/dist/src/mind/rationale.js +6 -32
  32. package/dist/src/mind/reasoning.d.ts +4 -30
  33. package/dist/src/mind/reasoning.js +183 -151
  34. package/dist/src/mind/types.js +6 -3
  35. package/docs/INDEX.md +23 -24
  36. package/docs/INVARIANTS.md +16 -17
  37. package/docs/architecture/bounded-reads.md +4 -4
  38. package/docs/architecture/closure.md +65 -0
  39. package/docs/architecture/commonality.md +27 -18
  40. package/docs/architecture/cost-model.md +5 -5
  41. package/docs/architecture/exact-vs-approximate.md +4 -4
  42. package/docs/architecture/factored-machinery.md +14 -14
  43. package/docs/architecture/mechanism-market.md +9 -9
  44. package/docs/architecture/meter.md +4 -5
  45. package/docs/architecture/store.md +2 -2
  46. package/docs/architecture/thresholds.md +1 -1
  47. package/docs/failures/tempting-but-wrong.md +11 -1
  48. package/docs/harness/gates.md +6 -6
  49. package/docs/mechanisms/cover.md +2 -2
  50. package/example/demo.ts +90 -37
  51. package/jsr.json +1 -1
  52. package/package.json +1 -1
  53. package/src/geometry.ts +0 -13
  54. package/src/meter.ts +11 -0
  55. package/src/mind/articulation.ts +0 -1
  56. package/src/mind/attention.ts +2 -1
  57. package/src/mind/derivation.ts +473 -0
  58. package/src/mind/graph-search.ts +37 -20
  59. package/src/mind/match.ts +2 -0
  60. package/src/mind/mechanisms/alu.ts +0 -2
  61. package/src/mind/mechanisms/cast.ts +17 -21
  62. package/src/mind/mechanisms/confluence.ts +3 -13
  63. package/src/mind/mechanisms/cover.ts +17 -20
  64. package/src/mind/mechanisms/extraction.ts +13 -9
  65. package/src/mind/mechanisms/prefix-completion.ts +0 -1
  66. package/src/mind/mechanisms/recall.ts +7 -9
  67. package/src/mind/mechanisms/reference.ts +2 -3
  68. package/src/mind/pipeline-mechanism.ts +1 -4
  69. package/src/mind/pipeline.ts +121 -46
  70. package/src/mind/rationale.ts +6 -36
  71. package/src/mind/reasoning.ts +208 -178
  72. package/src/mind/types.ts +5 -2
  73. package/test/133-the-decision-point-renders-the-state.test.mjs +204 -0
  74. package/test/134-the-law-explains-the-engines-own-refusal.test.mjs +237 -0
  75. package/test/135-one-law-any-producer.test.mjs +289 -0
  76. package/test/136-the-two-named-limits.test.mjs +205 -0
  77. package/test/137-the-law-lives-once-and-below.test.mjs +400 -0
  78. package/test/138-the-remainder-drains-only-what-a-move-declares.test.mjs +62 -0
  79. package/test/139-the-witness-is-engagement-not-explanation.test.mjs +51 -0
  80. package/test/140-irrelevant-supply-does-not-change-an-answer.test.mjs +48 -0
  81. package/test/141-the-question-is-paid-at-construction.test.mjs +98 -0
  82. package/test/36-already-answered-fusion.test.mjs +20 -2
  83. package/test/37-cluster-dispersion-fusion.test.mjs +30 -3
  84. package/test/38-reason-restate-guard.test.mjs +22 -2
  85. package/test/55-cost-meter.test.mjs +4 -1
@@ -9,7 +9,6 @@
9
9
 
10
10
  import type { Alu } from "../../alu/src/alu.js";
11
11
  import { STEP } from "../graph-search.js";
12
- import { unexplainedLabel } from "../rationale.js";
13
12
  import type { PipelineMechanism } from "../pipeline-mechanism.js";
14
13
 
15
14
  /** Wrap the ALU as a {@link PipelineMechanism}. */
@@ -35,7 +34,6 @@ export function aluToMechanism(alu: Alu): PipelineMechanism {
35
34
  bytes: u.bytes,
36
35
  accounted: [[u.i, u.j]],
37
36
  moves: STEP,
38
- unexplained: unexplainedLabel(query, [[u.i, u.j]]),
39
37
  }));
40
38
  },
41
39
  };
@@ -30,15 +30,11 @@ import {
30
30
  sharedFrameStrengthOf,
31
31
  } from "../match.js";
32
32
  import { joinWithBridge } from "../resonance.js";
33
- import { restatesQuery } from "../reasoning.js";
34
33
  import { CONCEPT, STEP } from "../graph-search.js";
35
34
  import { concat2, indexOf } from "../../bytes.js";
36
35
  import { consensusFloor, dominates } from "../../geometry.js";
37
- import {
38
- decodeText,
39
- unexplainedLabel,
40
- unexplainedSpans,
41
- } from "../rationale.js";
36
+ import { decodeText } from "../rationale.js";
37
+ import { restates, unexplainedSpans } from "../derivation.js";
42
38
  import { rItem, rNode } from "../trace.js";
43
39
  import { dismissedKnownContent } from "../bridge.js";
44
40
  import { leafIdRun } from "../canonical.js";
@@ -92,7 +88,6 @@ export interface CastResult {
92
88
  /** A human-readable label for the query bytes this schema left
93
89
  * unexplained — purely diagnostic, never priced (see the module's
94
90
  * Task 2 note in pipeline.ts's Candidate interface). */
95
- unexplained: string;
96
91
  }
97
92
 
98
93
  /** The seat that establishes a node's role in an analogical comparison:
@@ -129,7 +124,7 @@ export interface CastResult {
129
124
  * describes id ("...painted by Leonardo da Vinci." contains "Leonardo da
130
125
  * Vinci"). An incidental adjacency predecessor never does — it merely
131
126
  * preceded id in some unrelated document without ever mentioning it. No
132
- * new tuned constant: containment is the same primitive `restatesQuery`
127
+ * new tuned constant: containment is the same primitive `restates`
133
128
  * and `dominates`-style checks already use throughout this codebase.
134
129
  *
135
130
  * `allowForward` (default true) gates the FORWARD branch specifically —
@@ -499,7 +494,6 @@ export async function counterfactualTransfer(
499
494
  used: used ?? new Set(),
500
495
  accounted,
501
496
  moves,
502
- unexplained: unexplainedLabel(query, accounted),
503
497
  });
504
498
  };
505
499
  ctx.trace?.step(
@@ -613,7 +607,7 @@ export async function counterfactualTransfer(
613
607
  const fwd = await follow(ctx, proj.anchor, qv);
614
608
  if (
615
609
  fwd !== null && indexOf(answer, fwd, 0) < 0 &&
616
- !restatesQuery(query, fwd)
610
+ !restates(query, fwd, 0, { proper: true })
617
611
  ) {
618
612
  // THROUGH THE SHARED JOINER, not a bare concatenation.
619
613
  //
@@ -695,7 +689,7 @@ export async function counterfactualTransfer(
695
689
  // cap the test reads "none of the established continuations appears".
696
690
  const domNext = ctx.store.nextFirst(dominant.anchor, hubBound(ctx));
697
691
  const displaced = domNext
698
- .every((n) => indexOf(query, read(ctx, n), 0) < 0);
692
+ .every((n) => !restates(query, read(ctx, n), 0));
699
693
  // The SUBSTITUTE is what redirection speaks — the answer IS `project(last)`,
700
694
  // its own fact — so the same bar applies. The displaced structure is only
701
695
  // recognised as the slot being overridden and is never voiced, so it is
@@ -791,7 +785,7 @@ export async function counterfactualTransfer(
791
785
  queryScale(p.ctx.length) &&
792
786
  indexOf(dominant.ctx, p.ctx, 0) < 0 &&
793
787
  indexOf(p.ctx, dominant.ctx, 0) < 0 &&
794
- indexOf(query, p.ctx, 0) < 0
788
+ !restates(query, p.ctx, 0)
795
789
  ) {
796
790
  analogs.push({ anchor: p.anchor, point: p, src: p });
797
791
  }
@@ -810,7 +804,7 @@ export async function counterfactualTransfer(
810
804
  !queryScale(nctx.length) ||
811
805
  indexOf(dominant.ctx, nctx, 0) >= 0 ||
812
806
  indexOf(nctx, dominant.ctx, 0) >= 0 ||
813
- indexOf(query, nctx, 0) >= 0
807
+ restates(query, nctx, 0)
814
808
  ) continue;
815
809
  analogs.push({ anchor: nid, point: null, src: p });
816
810
  }
@@ -1286,13 +1280,16 @@ export async function counterfactualTransfer(
1286
1280
  // consulting it here is the same fallback `resolve` already makes when an
1287
1281
  // exact content lookup misses, and it keeps this mechanism from carrying
1288
1282
  // any idea of its own about what a character is.
1289
- const echoesQuery = (x: Uint8Array): boolean => {
1290
- if (restatesQuery(query, x)) return true;
1291
- const canon = ctx.canon;
1292
- if (canon === null) return false;
1293
- const cq = canon(query), cx = canon(x);
1294
- return cx.length < cq.length && indexOf(cq, cx, 0) >= 0;
1295
- };
1283
+ // TWO WITNESSES, ONE LAW: the byte reading, and — when the response carries
1284
+ // one — the same reading under the response's own equivalence. Asked twice
1285
+ // rather than branched on, because the equivalence is a property of the
1286
+ // injected canonicalizer (a substring-monotone function is the usual case,
1287
+ // not a guarantee), and the two readings are OR-ed here exactly as they were
1288
+ // before this became one definition.
1289
+ const echoesQuery = (x: Uint8Array): boolean =>
1290
+ restates(query, x, 0, { proper: true }) ||
1291
+ (ctx.canon !== null &&
1292
+ restates(query, x, 0, { equate: ctx.canon, proper: true }));
1296
1293
  if (echoesQuery(b)) {
1297
1294
  const fwd = await follow(ctx, bestAnalog.anchor, qv);
1298
1295
  if (fwd !== null && fwd.length > 0 && !echoesQuery(fwd)) b = fwd;
@@ -1406,7 +1403,6 @@ export const castMechanism: PipelineMechanism = {
1406
1403
  accounted: c.accounted,
1407
1404
  moves: c.moves,
1408
1405
  used: c.used,
1409
- unexplained: c.unexplained,
1410
1406
  }));
1411
1407
  },
1412
1408
  };
@@ -44,7 +44,7 @@ import { read } from "../primitives.js";
44
44
  import { corpusN, reachOf } from "../traverse.js";
45
45
  import { dominates } from "../../geometry.js";
46
46
  import { STEP } from "../graph-search.js";
47
- import { unexplainedLabel } from "../rationale.js";
47
+ import { insideAnsweredTurn } from "../derivation.js";
48
48
  import type { PipelineMechanism, Precomputed } from "../pipeline-mechanism.js";
49
49
  import { rItem, rNode } from "../trace.js";
50
50
 
@@ -58,9 +58,6 @@ export interface JoinResult {
58
58
  used: ReadonlySet<number>;
59
59
  accounted: Array<[number, number]>;
60
60
  moves: number;
61
- /** A human-readable label for the query bytes the meet left unexplained —
62
- * purely diagnostic, never priced. */
63
- unexplained: string;
64
61
  }
65
62
 
66
63
  /** The main confluence entry point. Given a query, detect whether it weaves
@@ -98,14 +95,9 @@ export async function confluenceJoin(
98
95
  // Recognition and attention still see the full transcript; only this
99
96
  // mechanism's constraint population excludes answered spans.
100
97
  const queryWin = new Map<number, number>();
101
- let answered = 0;
98
+ const answered = { at: 0 };
102
99
  for (const [off, id] of pre.queryWindows) {
103
- while (
104
- answered < ctx.answeredSpans.length &&
105
- ctx.answeredSpans[answered][1] <= off
106
- ) answered++;
107
- const span = ctx.answeredSpans[answered];
108
- if (span && span[0] <= off && off + W <= span[1]) continue;
100
+ if (insideAnsweredTurn(ctx.answeredSpans, answered, off, off + W)) continue;
109
101
  queryWin.set(off, id);
110
102
  }
111
103
  const queryIds = new Set(queryWin.values());
@@ -315,7 +307,6 @@ export async function confluenceJoin(
315
307
  used: new Set([met.a.anchor, met.b.anchor]),
316
308
  accounted,
317
309
  moves: 3 * STEP,
318
- unexplained: unexplainedLabel(query, accounted),
319
310
  };
320
311
  }
321
312
 
@@ -344,7 +335,6 @@ export const confluenceMechanism: PipelineMechanism = {
344
335
  accounted: met.accounted,
345
336
  moves: met.moves,
346
337
  used: met.used,
347
- unexplained: met.unexplained,
348
338
  }];
349
339
  },
350
340
  };
@@ -16,7 +16,8 @@ import { guidedFirst, hubBound } from "../traverse.js";
16
16
  import { conceptHop } from "../match.js";
17
17
  import { bridge } from "../resonance.js";
18
18
  import { liftAnswer, liftedScaffolding, segRestatesQuery } from "../types.js";
19
- import { decodeText, unexplainedLabel } from "../rationale.js";
19
+ import { decodeText } from "../rationale.js";
20
+ import { insideAnsweredTurn, restates } from "../derivation.js";
20
21
  import { indexOf } from "../../bytes.js";
21
22
  import type { RationaleItem } from "../rationale.js";
22
23
  import { rItem, rNode, traceDerivation } from "../trace.js";
@@ -62,16 +63,13 @@ export async function resolveConnectors(
62
63
  // discarded — a semantically neutral gate (it removes work whose product
63
64
  // liftAnswer throws away), and a cumulative (multi-turn) query is exactly
64
65
  // where such already-answered continuations recur.
65
- let answered = 0;
66
+ const answered = { at: 0 };
66
67
  const ordered = [...sites]
67
68
  .sort((a, b) => a.start - b.start)
68
69
  .filter((s) => {
69
- while (
70
- answered < ctx.answeredSpans.length &&
71
- ctx.answeredSpans[answered][1] <= s.start
72
- ) answered++;
73
- const span = ctx.answeredSpans[answered];
74
- if (span && span[0] <= s.start && s.end <= span[1]) return false;
70
+ if (insideAnsweredTurn(ctx.answeredSpans, answered, s.start, s.end)) {
71
+ return false;
72
+ }
75
73
  if (query === undefined || ctx.answeredSpans.length === 0) return true;
76
74
  const continuations = ctx.store.nextFirst(s.payload, hubBound(ctx));
77
75
  return !continuations.some((answer) => {
@@ -93,7 +91,11 @@ export async function resolveConnectors(
93
91
  // 3 bytes this reads 4 bytes per candidate instead of the ~231 it
94
92
  // averaged before.
95
93
  const bytes = read(ctx, answer, query.length + 1);
96
- return bytes.length <= query.length && indexOf(query, bytes, 0) >= 0;
94
+ // THE CONTENT READING of the same exclusion: the site's own
95
+ // continuation already occurs in the query, so voicing it back adds
96
+ // nothing. It is the restatement law with no `proper` flag — the
97
+ // containment reading that also admits the whole query.
98
+ return restates(query, bytes, 0);
97
99
  });
98
100
  });
99
101
  const bridgePair = async (l: number, r: number) => {
@@ -210,18 +212,11 @@ export const coverMechanism: PipelineMechanism = {
210
212
  )
211
213
  : await resolveConnectors(ctx, sites, query);
212
214
  let splits = rec.splits;
213
- let starts = rec.starts;
214
215
  if (computed.length > 0) {
215
216
  splits = new Set(rec.splits);
216
- starts = new Set(rec.starts);
217
217
  for (const u of computed) {
218
218
  splits.add(u.i);
219
219
  splits.add(u.j);
220
- // A computation's own boundaries carry the same fold-level evidence
221
- // a chunk boundary does — "computation always wins" (see the header
222
- // comment) extends to being trusted ground for cross-leaf recovery.
223
- starts.add(u.i);
224
- starts.add(u.j);
225
220
  }
226
221
  }
227
222
  const concepts = ctx.meter
@@ -262,7 +257,6 @@ export const coverMechanism: PipelineMechanism = {
262
257
  concepts,
263
258
  rec.leaves,
264
259
  splits,
265
- starts,
266
260
  undefined,
267
261
  connectors,
268
262
  computedResults,
@@ -330,9 +324,12 @@ export const coverMechanism: PipelineMechanism = {
330
324
  return [{
331
325
  bytes: composed,
332
326
  accounted,
333
- moves: 0,
334
- weight: solved!.cost, // A*LD derivation's g-value IS the weight
335
- unexplained: unexplainedLabel(query, accounted),
327
+ // The derivation's DISCRETE work. The bytes the chart could not
328
+ // recognise are NOT priced here: they are exactly the spans `accounted`
329
+ // leaves uncovered, and the pipeline's one formula charges them at PASS —
330
+ // the same formula that prices every other mechanism's candidate. No
331
+ // mechanism spells a cost of its own.
332
+ moves: solved!.moves,
336
333
  // How much of the composed answer is the asker's own unexplained words
337
334
  // (the spans the liftAnswer trace above labels "scaffolding"). Cover is
338
335
  // the mechanism that can carry them, because a PASS span still lands in
@@ -11,7 +11,7 @@ import type { MindContext } from "../types.js";
11
11
  import { read } from "../primitives.js";
12
12
  import { follow, isSpanShaped, locate, skillExemplar } from "../match.js";
13
13
  import { concatBytes, indexOf } from "../../bytes.js";
14
- import { decodeText, unexplainedLabel } from "../rationale.js";
14
+ import { decodeText } from "../rationale.js";
15
15
  import type {
16
16
  PipelineMechanism,
17
17
  Precomputed,
@@ -46,7 +46,6 @@ export async function extractBySkill(
46
46
  {
47
47
  bytes: Uint8Array;
48
48
  accounted: Array<[number, number]>;
49
- unexplained: string;
50
49
  } | null
51
50
  > {
52
51
  const t = ctx.trace?.enter("extractBySkill", [
@@ -102,6 +101,12 @@ export async function extractBySkill(
102
101
  const searched = ranked.slice(0, pre.k);
103
102
  let shapeMisses = 0;
104
103
  let subQuantum = 0;
104
+ // A SECOND refusal counter, because the note below used to call an UNANCHORED
105
+ // read "sub-quantum" — which is false, and it is the kind of instrumentation
106
+ // defect AGENTS §6 says to close where it lives: a reader could not tell from
107
+ // the trace which of the two gates refused. Neither counter reaches a
108
+ // decision (meter.ts contract 1); they exist so the refusal is legible.
109
+ let unanchored = 0;
105
110
  for (const cand of searched) {
106
111
  const exemplar = await pre.spanShapedOf(cand.anchor);
107
112
  if (!exemplar) {
@@ -137,22 +142,23 @@ export async function extractBySkill(
137
142
  // Here the field is this mechanism's own output and carries its documented
138
143
  // meaning, so the test is sound exactly where the convention does not reach.
139
144
  if (built.accounted.length === 0) {
140
- subQuantum++;
145
+ unanchored++;
141
146
  continue;
142
147
  }
143
- if (shapeMisses > 0 || subQuantum > 0) {
148
+ if (shapeMisses > 0 || subQuantum > 0 || unanchored > 0) {
144
149
  ctx.trace?.step(
145
150
  "trySkillAnchors",
146
151
  [
147
152
  rItem(
148
153
  query.subarray(0, 0),
149
- `skipped ${shapeMisses + subQuantum}`,
154
+ `skipped ${shapeMisses + subQuantum + unanchored}`,
150
155
  ),
151
156
  rNode(ctx, cand.anchor, "chosen"),
152
157
  ],
153
158
  [],
154
- `skipped ${shapeMisses} non-exemplar and ${subQuantum} sub-quantum ` +
155
- `anchor(s) before one yielded a usable extraction`,
159
+ `skipped ${shapeMisses} non-exemplar, ${subQuantum} sub-quantum and ` +
160
+ `${unanchored} unanchored anchor(s) before one yielded a usable ` +
161
+ `extraction`,
156
162
  );
157
163
  }
158
164
  t?.done(
@@ -170,7 +176,6 @@ export async function extractBySkill(
170
176
  return {
171
177
  bytes: built.bytes,
172
178
  accounted: built.accounted,
173
- unexplained: unexplainedLabel(query, built.accounted),
174
179
  };
175
180
  }
176
181
  if (shapeMisses === searched.length) {
@@ -403,7 +408,6 @@ export const extractionMechanism: PipelineMechanism = {
403
408
  bytes: ex.bytes,
404
409
  accounted: ex.accounted,
405
410
  moves: CONCEPT + STEP * ex.accounted.length,
406
- unexplained: ex.unexplained,
407
411
  }];
408
412
  },
409
413
  };
@@ -300,7 +300,6 @@ export const prefixMechanism: PipelineMechanism = {
300
300
  // IDENTITY bridge takes.
301
301
  accounted: [[0, query.length]],
302
302
  moves: STEP,
303
- unexplained: "",
304
303
  // NOT complete: the query is a proper PREFIX, so the form may carry more
305
304
  // past the remainder this voiced.
306
305
  }];
@@ -24,7 +24,7 @@ import {
24
24
  voicesDisplacedFiller,
25
25
  } from "../match.js";
26
26
  import { CONCEPT, STEP } from "../graph-search.js";
27
- import { unexplainedLabel } from "../rationale.js";
27
+ import { restates as lawRestates } from "../derivation.js";
28
28
  import type { PipelineMechanism, Precomputed } from "../pipeline-mechanism.js";
29
29
  import { rItem, rNode } from "../trace.js";
30
30
  import { substitutionBridge } from "../bridge.js";
@@ -35,7 +35,6 @@ export interface RecallResult {
35
35
  echoed: boolean;
36
36
  accounted: Array<[number, number]>;
37
37
  moves: number;
38
- unexplained: string;
39
38
  /** See {@link import("../pipeline-mechanism.js").MechanismResult.complete}
40
39
  * — set by the IDENTITY-bridge tier alone. */
41
40
  complete?: boolean;
@@ -71,7 +70,6 @@ export async function recallByResonance(
71
70
  echoed,
72
71
  accounted,
73
72
  moves,
74
- unexplained: unexplainedLabel(query, accounted),
75
73
  ...(complete ? { complete } : {}),
76
74
  };
77
75
  };
@@ -156,7 +154,7 @@ export async function recallByResonance(
156
154
  // conversation reads as if it were the next thing to say.
157
155
  if (
158
156
  g !== null && g.length > 0 &&
159
- !(g.length < query.length && indexOf(query, g, 0) >= 0)
157
+ !lawRestates(query, g, 0, { proper: true })
160
158
  ) {
161
159
  return ground(
162
160
  g,
@@ -196,10 +194,11 @@ export async function recallByResonance(
196
194
  // same principle that keeps cast from voicing stored questions), and
197
195
  // projecting them forward is reverse recall's containment failure in the
198
196
  // other direction — "whatever followed these bytes in some document".
199
- const qKey = ctx.canon ? ctx.canon(query) : query;
197
+ // THE EQUALITY READING of the restatement law: this tier rejects an answer
198
+ // that IS the question (an echo), and a proper fragment is handled by the
199
+ // tier's own subspan tests further down — so the law is asked with `whole`.
200
200
  const restates = (b: Uint8Array): boolean =>
201
- bytesEqual(b, query) ||
202
- (ctx.canon !== null && bytesEqual(ctx.canon(b), qKey));
201
+ lawRestates(query, b, 0, { equate: ctx.canon, whole: true });
203
202
  const idBar = identityBar(ctx.store.D, ctx.space.maxGroup, query.length);
204
203
  if (top.score >= idBar) {
205
204
  for (const h of whole) {
@@ -408,7 +407,7 @@ export async function recallByResonance(
408
407
  // — never an answer (the same principle as `restates` above, extended
409
408
  // to fragments). Genuine anchor groundings — longer than the query,
410
409
  // or disjoint from it — pass untouched.
411
- else if (g && !(g.length < query.length && indexOf(query, g, 0) >= 0)) {
410
+ else if (g && !lawRestates(query, g, 0, { proper: true })) {
412
411
  return ground(
413
412
  g,
414
413
  "scaffolding-dominated query — ground the consensus-climb anchor",
@@ -627,7 +626,6 @@ export const recallMechanism: PipelineMechanism = {
627
626
  bytes: r.bytes,
628
627
  accounted: r.accounted,
629
628
  moves: r.moves,
630
- unexplained: r.unexplained,
631
629
  provenance: r.echoed ? "recall-echo" : "recall",
632
630
  used: new Set<number>(),
633
631
  ...(r.complete ? { complete: true } : {}),
@@ -39,7 +39,7 @@ import type { FrameInstance } from "../match.js";
39
39
  import { carriesFillers, distinct, follow, substituteAll } from "../match.js";
40
40
  import { dominates } from "../../geometry.js";
41
41
  import { bytesEqual, indexOf } from "../../bytes.js";
42
- import { unexplainedLabel } from "../rationale.js";
42
+ import { restates } from "../derivation.js";
43
43
  import { STEP } from "../graph-search.js";
44
44
  import type {
45
45
  MechanismResult,
@@ -231,7 +231,7 @@ export async function bindReference(
231
231
  if (bytes.length === 0) return fail("the binding produced nothing");
232
232
  // Answering with the question is not answering — the same restated-fragment
233
233
  // guard every recall tier applies.
234
- if (bytes.length < query.length && indexOf(query, bytes, 0) >= 0) {
234
+ if (restates(query, bytes, 0, { proper: true })) {
235
235
  return fail("the binding restates part of the question");
236
236
  }
237
237
  const carried = !bytesEqual(bytes, first);
@@ -278,7 +278,6 @@ export async function bindReference(
278
278
  // binding claims strictly more than a one-slot binding, so where both are
279
279
  // licensed the smaller claim wins.
280
280
  moves: STEP * slots.length + STEP,
281
- unexplained: unexplainedLabel(query, accounted),
282
281
  // NOT scaffolding. That field counts answer bytes carried through BECAUSE
283
282
  // NOTHING EXPLAINED THEM; a referent is carried because the frame's slot
284
283
  // explains it, and it is accounted above. Reporting it would make every
@@ -677,15 +677,12 @@ export interface MechanismResult {
677
677
  moves: number;
678
678
  /** WHAT THIS ANSWER SPEAKS FOR — the anchors it voices, and therefore the
679
679
  * content the reasoner must not pivot back through. Declared by the
680
- * mechanism about its OWN result, exactly like `accounted`/`unexplained`/
680
+ * mechanism about its OWN result, exactly like `accounted`/`used`/
681
681
  * `complete`: post-grounding honours the property and NEVER ASKS WHICH
682
682
  * MECHANISM SET IT, so the market stays uniform. An EMPTY set is a real
683
683
  * declaration — "this answer voices nothing" (recall) — and withholds
684
684
  * nothing; omit the field and the pipeline re-recognises the answer. */
685
685
  used?: ReadonlySet<number>;
686
- unexplained: string;
687
- /** Explicit weight override. When absent, weight = moves + PASS·unaccounted. */
688
- weight?: number;
689
686
  /** Bytes of `bytes` that came from spans nothing recognised — the asker's
690
687
  * own words carried through verbatim rather than derived (see
691
688
  * {@link liftedScaffolding}). Reported, not priced: the ladder prices what