@hviana/sema 0.8.3 → 0.8.6

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 (88) 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 +31 -5
  7. package/dist/src/meter.js +31 -5
  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 +191 -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 +78 -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 +31 -5
  55. package/src/mind/articulation.ts +0 -1
  56. package/src/mind/attention.ts +2 -1
  57. package/src/mind/derivation.ts +477 -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 +220 -178
  72. package/src/mind/types.ts +5 -2
  73. package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +3 -3
  74. package/test/133-the-decision-point-renders-the-state.test.mjs +204 -0
  75. package/test/134-the-law-explains-the-engines-own-refusal.test.mjs +237 -0
  76. package/test/135-one-law-any-producer.test.mjs +289 -0
  77. package/test/136-the-two-named-limits.test.mjs +205 -0
  78. package/test/137-the-law-lives-once-and-below.test.mjs +400 -0
  79. package/test/138-the-remainder-drains-only-what-a-move-declares.test.mjs +62 -0
  80. package/test/139-the-witness-is-engagement-not-explanation.test.mjs +51 -0
  81. package/test/140-irrelevant-supply-does-not-change-an-answer.test.mjs +48 -0
  82. package/test/141-the-question-is-paid-at-construction.test.mjs +98 -0
  83. package/test/142-the-layer-offers-only-what-the-law-admits.test.mjs +93 -0
  84. package/test/143-cycles-terminate-and-are-not-closure.test.mjs +62 -0
  85. package/test/36-already-answered-fusion.test.mjs +20 -2
  86. package/test/37-cluster-dispersion-fusion.test.mjs +30 -3
  87. package/test/38-reason-restate-guard.test.mjs +22 -2
  88. package/test/55-cost-meter.test.mjs +6 -3
@@ -177,6 +177,30 @@ export interface Seg {
177
177
  /** Read the chosen spans back off a derivation: the goal is a chain of bridge
178
178
  * steps, each whose second premise is the `out` it crossed. Walk the chain to
179
179
  * the axiom and reverse into left-to-right order. */
180
+ /** The BYTE TERM of a derivation's cost: how many bytes its bridge rule charged
181
+ * at PASS each — read back off the rule that charged them (`bridgeRule`:
182
+ * `o.rec ? MICRO : PASS * (o.j - o.i)`), so the split exists in ONE place and
183
+ * the engine's generic accumulator is left alone.
184
+ *
185
+ * `derivation.cost - PASS * readBridgedBytes(derivation)` is therefore the
186
+ * derivation's DISCRETE work — the number a mechanism reports as `moves`, which
187
+ * the pipeline's one formula then prices together with `PASS * unaccounted`.
188
+ * The two readings of the byte term coincide by construction: the spans this
189
+ * sums are exactly the ones the chart could not recognise (`rec === false`),
190
+ * which are the spans the candidate leaves unaccounted. */
191
+ function readBridgedBytes(derivation: Derivation<GItem>): number {
192
+ let bytes = 0;
193
+ let node: Derivation<GItem> | undefined = derivation;
194
+ while (node && node.rule) {
195
+ const o = node.premises[1]?.item;
196
+ if (o !== undefined && o.kind === "out" && o.rec === false) {
197
+ bytes += o.j - o.i;
198
+ }
199
+ node = node.premises[0];
200
+ }
201
+ return bytes;
202
+ }
203
+
180
204
  function readCover(derivation: Derivation<GItem>): Seg[] {
181
205
  const segs: Seg[] = [];
182
206
  let node: Derivation<GItem> | undefined = derivation;
@@ -426,7 +450,6 @@ export class GraphSearch {
426
450
  conceptTarget: ReadonlyMap<number, number>,
427
451
  leaves: ReadonlyArray<Leaf>,
428
452
  splits: ReadonlySet<number>,
429
- starts: ReadonlySet<number>,
430
453
  substitutions?: ReadonlyMap<number, Uint8Array>,
431
454
  connectors?: ReadonlyMap<string, Uint8Array>,
432
455
  computedResults?: ReadonlyArray<ComputedResult>,
@@ -437,7 +460,7 @@ export class GraphSearch {
437
460
  * the rationale instead of stopping at the first layer. Off by default, so
438
461
  * the search pays nothing when no one inspects. */
439
462
  onDerivation?: (steps: DerivationStep[]) => void,
440
- ): { segs: Seg[]; cost: number } | null {
463
+ ): { segs: Seg[]; cost: number; moves: number } | null {
441
464
  // Top-level entry: reset the per-call recursion state, then run the one
442
465
  // {@link solve} routine that both the query and any produced composite go
443
466
  // through (completion is cover, recursively — see {@link recompleteNode}).
@@ -449,12 +472,7 @@ export class GraphSearch {
449
472
  this.derivationSink = onDerivation;
450
473
  const solved = this.solve(
451
474
  queryLen,
452
- {
453
- sites,
454
- leaves,
455
- splits,
456
- starts,
457
- },
475
+ { sites, leaves, splits },
458
476
  conceptTarget,
459
477
  substitutions,
460
478
  connectors,
@@ -468,9 +486,11 @@ export class GraphSearch {
468
486
  // continuations instead of following the chain the answer itself licensed.
469
487
  // With deepening only at the top, `recompleteNode` walks the accepted chain
470
488
  // one link at a time (its own memo and stack), so the work is the answer's.
471
- return solved === null
472
- ? null
473
- : { segs: this.deepen(solved.segs), cost: solved.cost };
489
+ return solved === null ? null : {
490
+ segs: this.deepen(solved.segs),
491
+ cost: solved.cost,
492
+ moves: solved.moves,
493
+ };
474
494
  }
475
495
  /** Build the deduction system for one span and return its lightest cover's
476
496
  * chosen spans — the SINGLE routine the query and every produced composite
@@ -491,21 +511,19 @@ export class GraphSearch {
491
511
  sites: ReadonlyArray<Site>;
492
512
  leaves: ReadonlyArray<Leaf>;
493
513
  splits: ReadonlySet<number>;
494
- starts: ReadonlySet<number>;
495
514
  },
496
515
  conceptTarget: ReadonlyMap<number, number>,
497
516
  substitutions?: ReadonlyMap<number, Uint8Array>,
498
517
  connectors?: ReadonlyMap<string, Uint8Array>,
499
518
  computedResults?: ReadonlyArray<ComputedResult>,
500
519
  onDerivation?: (steps: DerivationStep[]) => void,
501
- ): { segs: Seg[]; cost: number } | null {
520
+ ): { segs: Seg[]; cost: number; moves: number } | null {
502
521
  const system = this.buildSearch(
503
522
  spanLen,
504
523
  recognition.sites,
505
524
  conceptTarget,
506
525
  recognition.leaves,
507
526
  recognition.splits,
508
- recognition.starts,
509
527
  substitutions,
510
528
  connectors,
511
529
  computedResults,
@@ -528,7 +546,11 @@ export class GraphSearch {
528
546
  onDerivation(readDerivation(derivation, substitutions !== undefined));
529
547
  }
530
548
  return derivation
531
- ? { segs: readCover(derivation), cost: derivation.cost }
549
+ ? {
550
+ segs: readCover(derivation),
551
+ cost: derivation.cost,
552
+ moves: derivation.cost - PASS * readBridgedBytes(derivation),
553
+ }
532
554
  : null;
533
555
  }
534
556
 
@@ -568,7 +590,6 @@ export class GraphSearch {
568
590
  conceptTarget: ReadonlyMap<number, number>,
569
591
  leaves: ReadonlyArray<Leaf>,
570
592
  splits: ReadonlySet<number>,
571
- starts: ReadonlySet<number>,
572
593
  substitutions?: ReadonlyMap<number, Uint8Array>,
573
594
  connectors?: ReadonlyMap<string, Uint8Array>,
574
595
  computedResults?: ReadonlyArray<ComputedResult>,
@@ -730,7 +751,6 @@ export class GraphSearch {
730
751
  return this.outRules(it, {
731
752
  W,
732
753
  splits,
733
- starts,
734
754
  atomsAreHubs,
735
755
  coversDone,
736
756
  outsByStart,
@@ -1190,7 +1210,6 @@ export class GraphSearch {
1190
1210
  sites: [...recognised, ...structural],
1191
1211
  leaves: rec.leaves,
1192
1212
  splits: rec.splits,
1193
- starts: rec.starts,
1194
1213
  },
1195
1214
  new Map(),
1196
1215
  undefined,
@@ -1484,7 +1503,6 @@ export class GraphSearch {
1484
1503
  ctx: {
1485
1504
  W: number;
1486
1505
  splits: ReadonlySet<number>;
1487
- starts: ReadonlySet<number>;
1488
1506
  atomsAreHubs: boolean;
1489
1507
  coversDone: Set<number>;
1490
1508
  outsByStart: Map<number, OutItem[]>;
@@ -1647,7 +1665,6 @@ export class GraphSearch {
1647
1665
  r: OutItem,
1648
1666
  ctx: {
1649
1667
  W: number;
1650
- starts: ReadonlySet<number>;
1651
1668
  atomsAreHubs: boolean;
1652
1669
  findLeafU: (b: Uint8Array) => number | undefined;
1653
1670
  findBranchU: (k: number[]) => number | undefined;
package/src/mind/match.ts CHANGED
@@ -1004,6 +1004,8 @@ export function spanHalo(
1004
1004
  return found ? normalize(out) : null;
1005
1005
  }
1006
1006
 
1007
+ /** A TEST SURFACE: exported for the tests that pin the synonym strength ladder
1008
+ * (they are its only consumers); nothing in `src/` calls it. */
1007
1009
  /** Distributional synonym evidence between arbitrary byte spans. Whole words
1008
1010
  * need not be independently interned: their stored W-window occurrences are
1009
1011
  * lifted to episode halos, bundled, and compared. The caller chooses the
@@ -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