@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
@@ -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;
@@ -238,6 +262,14 @@ export interface DerivationItem {
238
262
  * {@link GraphSearch}'s rules fired, recovered from the rule's premise/
239
263
  * conclusion shape (the rules carry no label, so this classifies by structure,
240
264
  * the single place that maps rule geometry to a name). */
265
+ // CLOSED ON PURPOSE — AND ONLY THIS ONE IS. A derivation move is BRANCHED ON
266
+ // (`classifyMove`, the rationale's readers, MOVE_NOTE's fallback), so it is a
267
+ // closed union: adding one without teaching every reader is a compile error,
268
+ // which is what a closed vocabulary buys. The MECHANISM names in
269
+ // `rationale.ts` are the opposite case — written and displayed, never
270
+ // branched on — and they stay free strings that COMPOSE with the nesting
271
+ // (`["respond", "think", "recognise"]`). That asymmetry is deliberate; do not
272
+ // "fix" it by uniting the two (see test/126 for the pipeline half of it).
241
273
  export type DerivationMove =
242
274
  | "axiom" // a seed: a perceived leaf, a recognised form, or a computed result
243
275
  | "follow-edge" // form→form via a continuation edge (STEP) — the core "what follows what"
@@ -418,7 +450,6 @@ export class GraphSearch {
418
450
  conceptTarget: ReadonlyMap<number, number>,
419
451
  leaves: ReadonlyArray<Leaf>,
420
452
  splits: ReadonlySet<number>,
421
- starts: ReadonlySet<number>,
422
453
  substitutions?: ReadonlyMap<number, Uint8Array>,
423
454
  connectors?: ReadonlyMap<string, Uint8Array>,
424
455
  computedResults?: ReadonlyArray<ComputedResult>,
@@ -429,7 +460,7 @@ export class GraphSearch {
429
460
  * the rationale instead of stopping at the first layer. Off by default, so
430
461
  * the search pays nothing when no one inspects. */
431
462
  onDerivation?: (steps: DerivationStep[]) => void,
432
- ): { segs: Seg[]; cost: number } | null {
463
+ ): { segs: Seg[]; cost: number; moves: number } | null {
433
464
  // Top-level entry: reset the per-call recursion state, then run the one
434
465
  // {@link solve} routine that both the query and any produced composite go
435
466
  // through (completion is cover, recursively — see {@link recompleteNode}).
@@ -441,12 +472,7 @@ export class GraphSearch {
441
472
  this.derivationSink = onDerivation;
442
473
  const solved = this.solve(
443
474
  queryLen,
444
- {
445
- sites,
446
- leaves,
447
- splits,
448
- starts,
449
- },
475
+ { sites, leaves, splits },
450
476
  conceptTarget,
451
477
  substitutions,
452
478
  connectors,
@@ -460,9 +486,11 @@ export class GraphSearch {
460
486
  // continuations instead of following the chain the answer itself licensed.
461
487
  // With deepening only at the top, `recompleteNode` walks the accepted chain
462
488
  // one link at a time (its own memo and stack), so the work is the answer's.
463
- return solved === null
464
- ? null
465
- : { 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
+ };
466
494
  }
467
495
  /** Build the deduction system for one span and return its lightest cover's
468
496
  * chosen spans — the SINGLE routine the query and every produced composite
@@ -483,21 +511,19 @@ export class GraphSearch {
483
511
  sites: ReadonlyArray<Site>;
484
512
  leaves: ReadonlyArray<Leaf>;
485
513
  splits: ReadonlySet<number>;
486
- starts: ReadonlySet<number>;
487
514
  },
488
515
  conceptTarget: ReadonlyMap<number, number>,
489
516
  substitutions?: ReadonlyMap<number, Uint8Array>,
490
517
  connectors?: ReadonlyMap<string, Uint8Array>,
491
518
  computedResults?: ReadonlyArray<ComputedResult>,
492
519
  onDerivation?: (steps: DerivationStep[]) => void,
493
- ): { segs: Seg[]; cost: number } | null {
520
+ ): { segs: Seg[]; cost: number; moves: number } | null {
494
521
  const system = this.buildSearch(
495
522
  spanLen,
496
523
  recognition.sites,
497
524
  conceptTarget,
498
525
  recognition.leaves,
499
526
  recognition.splits,
500
- recognition.starts,
501
527
  substitutions,
502
528
  connectors,
503
529
  computedResults,
@@ -520,7 +546,11 @@ export class GraphSearch {
520
546
  onDerivation(readDerivation(derivation, substitutions !== undefined));
521
547
  }
522
548
  return derivation
523
- ? { segs: readCover(derivation), cost: derivation.cost }
549
+ ? {
550
+ segs: readCover(derivation),
551
+ cost: derivation.cost,
552
+ moves: derivation.cost - PASS * readBridgedBytes(derivation),
553
+ }
524
554
  : null;
525
555
  }
526
556
 
@@ -560,7 +590,6 @@ export class GraphSearch {
560
590
  conceptTarget: ReadonlyMap<number, number>,
561
591
  leaves: ReadonlyArray<Leaf>,
562
592
  splits: ReadonlySet<number>,
563
- starts: ReadonlySet<number>,
564
593
  substitutions?: ReadonlyMap<number, Uint8Array>,
565
594
  connectors?: ReadonlyMap<string, Uint8Array>,
566
595
  computedResults?: ReadonlyArray<ComputedResult>,
@@ -722,7 +751,6 @@ export class GraphSearch {
722
751
  return this.outRules(it, {
723
752
  W,
724
753
  splits,
725
- starts,
726
754
  atomsAreHubs,
727
755
  coversDone,
728
756
  outsByStart,
@@ -1135,6 +1163,7 @@ export class GraphSearch {
1135
1163
  // concepts/connectors either (those need the caller's async
1136
1164
  // pre-resolution) — the recursion follows edges and fusion, which is what
1137
1165
  // a deeper rewrite chain is made of.
1166
+ if (this.host.meter) this.host.meter.recompletes++;
1138
1167
  const rec = this.host.recogniseSpan(bytes);
1139
1168
  const kids = new Set(nrec.kids);
1140
1169
  // THE NODE'S OWN KIDS ARE SITES BY STRUCTURE — recognition cannot be the
@@ -1181,7 +1210,6 @@ export class GraphSearch {
1181
1210
  sites: [...recognised, ...structural],
1182
1211
  leaves: rec.leaves,
1183
1212
  splits: rec.splits,
1184
- starts: rec.starts,
1185
1213
  },
1186
1214
  new Map(),
1187
1215
  undefined,
@@ -1391,20 +1419,36 @@ export class GraphSearch {
1391
1419
  // duplicate read and a branch that could never be taken.
1392
1420
  let next: number | null = null;
1393
1421
  let keyBytes = c.bytes;
1394
- // THE PREFIX ENDS ARE THE TAIL'S OWN FOLD BOUNDARIES, not every byte
1395
- // length. The key is `entity + prefix`, and the prefix that names a
1396
- // stored relation ends where the fold cuts: measured over four join-firing
1397
- // queries, 5 of 5 accepted keys ended on a boundary (or the tail's end)
1398
- // while the byte-by-byte scan spent 153 probes where 14 boundaries would
1399
- // do. Same criterion — resolves AND leads — same shortest-first order, so
1400
- // the answer is the same one the enumeration found; only the candidates
1401
- // come from the structure instead of from the byte count. A host with no
1402
- // boundary rule falls back to the enumeration.
1403
- const cuts = this.host.contentCuts?.(tail);
1404
- const ends = cuts && cuts.length > 0
1405
- ? [...cuts.filter((c) => c > 0 && c < tail.length), tail.length]
1406
- : Array.from({ length: tail.length }, (_, i) => i + 1);
1407
- for (const len of ends) {
1422
+ // THE CANDIDATE ENDS ARE THE PREFIXES THAT ARE STORED NODES, ASCENDING.
1423
+ // The key is `entity + prefix`, and it names a relation exactly when that
1424
+ // concatenation IS a node — so the ends come from a content-addressed
1425
+ // probe per offset (the host's `contentKeyEnds`, the learning path's own
1426
+ // mechanism: one leaf walk plus one `findBranch` per offset, no `resolve`),
1427
+ // never from the fold's boundaries. A boundary is not a proxy: a stored
1428
+ // member's end is the end of ITS OWN stream, and the fold never cuts at a
1429
+ // stream's end — measured, "stockholm mayor" exists, leads on to the mayor
1430
+ // fact, and its boundary 6 sits in neither the tail's cuts ([4,7]) nor the
1431
+ // concatenation's. Filtering the scan by "is this a node?" cannot change
1432
+ // the winner: a position that is not a node cannot resolve, so skipping it
1433
+ // is invisible; and the order stays SHORTEST FIRST, which is a semantic
1434
+ // law, not an optimisation (test/106, test/108 pin it).
1435
+ //
1436
+ // A host that cannot answer falls back to every prefix: exact and
1437
+ // complete, at a `resolve` per offset. A host that CAN answer is
1438
+ // authoritative even when it answers "none" — if no prefix is a node then
1439
+ // no key exists to resolve, so enumerating would only pay nulls. (A key
1440
+ // reachable through the CANONICAL equivalence alone and ending off every
1441
+ // node end is therefore not tried here; that dimension is unreachable on
1442
+ // this path by construction and is not part of the exact-key law.)
1443
+ const ends = this.host.contentKeyEnds?.(c.bytes, tail);
1444
+ const candidateEnds = function* (): Generator<number> {
1445
+ if (ends !== undefined) {
1446
+ yield* ends;
1447
+ return;
1448
+ }
1449
+ for (let p = 1; p <= tail.length; p++) yield p;
1450
+ };
1451
+ for (const len of candidateEnds()) {
1408
1452
  keyBytes = concat2(c.bytes, tail.subarray(0, len));
1409
1453
  const k = this.host.resolve(keyBytes) ??
1410
1454
  this.host.canonResolve?.(keyBytes) ??
@@ -1459,7 +1503,6 @@ export class GraphSearch {
1459
1503
  ctx: {
1460
1504
  W: number;
1461
1505
  splits: ReadonlySet<number>;
1462
- starts: ReadonlySet<number>;
1463
1506
  atomsAreHubs: boolean;
1464
1507
  coversDone: Set<number>;
1465
1508
  outsByStart: Map<number, OutItem[]>;
@@ -1622,7 +1665,6 @@ export class GraphSearch {
1622
1665
  r: OutItem,
1623
1666
  ctx: {
1624
1667
  W: number;
1625
- starts: ReadonlySet<number>;
1626
1668
  atomsAreHubs: boolean;
1627
1669
  findLeafU: (b: Uint8Array) => number | undefined;
1628
1670
  findBranchU: (k: number[]) => number | undefined;
package/src/mind/match.ts CHANGED
@@ -723,7 +723,7 @@ export function frameSlots(
723
723
  * ANCHOR that the query displaced. Neither implies the other, and the
724
724
  * observed failures pass the restatement guard cleanly.
725
725
  *
726
- * Three conditions, all byte-exact and all necessary:
726
+ * Four conditions, all byte-exact and all necessary:
727
727
  *
728
728
  * 1. the query and the anchor must be ONE STRUCTURE — what they share has to
729
729
  * dominate the query, or the query is not a variant of the anchor at all
@@ -811,8 +811,10 @@ export function substituteAll(
811
811
  const usable = pairs.filter((p) => p.needle.length > 0);
812
812
  if (usable.length === 0) return hay;
813
813
  // Longest needle first, so a needle that is a prefix of another can never
814
- // pre-empt it. Ties cannot arise: an instance whose fillers are not
815
- // pairwise distinct is refused by frameSlots.
814
+ // pre-empt it. Ties cannot arise: a consumer that VOICES checks the
815
+ // fillers pairwise with `distinct` and refuses such an instance itself —
816
+ // `frameSlots` reports and does not judge (see its own doc), so the refusal
817
+ // lives with the mechanism that needs it, not here.
816
818
  const order = [...usable].sort((a, b) => b.needle.length - a.needle.length);
817
819
  const out: number[] = [];
818
820
  let i = 0;
@@ -1002,6 +1004,8 @@ export function spanHalo(
1002
1004
  return found ? normalize(out) : null;
1003
1005
  }
1004
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. */
1005
1009
  /** Distributional synonym evidence between arbitrary byte spans. Whole words
1006
1010
  * need not be independently interned: their stored W-window occurrences are
1007
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
  }
@@ -867,7 +861,9 @@ export async function counterfactualTransfer(
867
861
  // grounds") — fine for ORIENTING mechanisms, not for voicing learnt
868
862
  // content the query never asked about. Computed once here; both the
869
863
  // hub fallback below and the comparison gate consume it.
870
- const rootTrusted = roots.some((r) => r.vote >= consensusFloor(corpusN(ctx)));
864
+ const rootTrusted = roots.some((r) =>
865
+ r.idfVote >= consensusFloor(corpusN(ctx))
866
+ ); // the IDF sum: the bar's own quantity
871
867
  // The context that ESTABLISHES a filler — the same reverse context, under
872
868
  // the same naming test, `seatOfNode` uses to VOICE an analog (a predecessor
873
869
  // whose bytes CONTAIN the node's: it names or describes it, rather than
@@ -1284,13 +1280,16 @@ export async function counterfactualTransfer(
1284
1280
  // consulting it here is the same fallback `resolve` already makes when an
1285
1281
  // exact content lookup misses, and it keeps this mechanism from carrying
1286
1282
  // any idea of its own about what a character is.
1287
- const echoesQuery = (x: Uint8Array): boolean => {
1288
- if (restatesQuery(query, x)) return true;
1289
- const canon = ctx.canon;
1290
- if (canon === null) return false;
1291
- const cq = canon(query), cx = canon(x);
1292
- return cx.length < cq.length && indexOf(cq, cx, 0) >= 0;
1293
- };
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 }));
1294
1293
  if (echoesQuery(b)) {
1295
1294
  const fwd = await follow(ctx, bestAnalog.anchor, qv);
1296
1295
  if (fwd !== null && fwd.length > 0 && !echoesQuery(fwd)) b = fwd;
@@ -1404,7 +1403,6 @@ export const castMechanism: PipelineMechanism = {
1404
1403
  accounted: c.accounted,
1405
1404
  moves: c.moves,
1406
1405
  used: c.used,
1407
- unexplained: c.unexplained,
1408
1406
  }));
1409
1407
  },
1410
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());
@@ -146,6 +138,30 @@ export async function confluenceJoin(
146
138
  const bindsAConstituent = (cover: Array<[number, number]>): boolean =>
147
139
  cover.some(([cs, ce]) => ce - cs >= 2 * W);
148
140
 
141
+ // THE VOTE ENTERS AS ORDER, NEVER AS A BAR. This is the only one of the
142
+ // climb's four consumers (recall, fuseAttention, cast, here) that uses the
143
+ // evidence's MAGNITUDE without a floor, and it is legitimate by construction:
144
+ // `ranked` answers "which anchor is stronger" — a question about votes, so the
145
+ // comparison stays within one dimension — and the vote is otherwise only
146
+ // REPORTED (Stream.vote travels to the rationale's constraint nodes). What
147
+ // actually SELECTS a constraint is byte-structural and never the magnitude: a
148
+ // run of at least 2W (`bindsAConstituent`, with its accidental-sharing
149
+ // counter-examples above), disjoint covers (`disjoint`), and scaffolding never
150
+ // binds at all (`dominates(reachOf(…), N)`). The MEET such a stream may
151
+ // produce is selected the same way: a span shorter than 2W is rejected, and
152
+ // the winner is the one with the smallest `reach` (ties broken by the longer
153
+ // span) — a corpus quantity and bytes, never the vote, which appears only in
154
+ // the trace item.
155
+ // binds at all (`dominates(reachOf(…), N)`). The only cut in this loop is a
156
+ // BUDGET, and it is measured: stopping the scan at 2W anchors saves 50-70% of
157
+ // confluence's cost on non-conjunctive queries while preserving every genuinely
158
+ // conjunctive case, whose top anchors ARE its constraints.
159
+ // MEASURED (this goal, on THIS file's own conjunctive fixture): the two
160
+ // streams appear at ranks 1 and 4 against a budget of 2W = 8, on a query whose
161
+ // `ranked` is 9 — so the cut IS live (it would have returned null at the 8th
162
+ // anchor) and it does NOT prune the case it exists to protect. The other
163
+ // conjunctive fixture (the Leonardo one) finds them at ranks 0 and 1. Scope:
164
+ // these are the repo's conjunctive fixtures, and no more.
149
165
  const streams: Stream[] = [];
150
166
  const rankedCapped = ranked.length > pre.k ? ranked.slice(0, pre.k) : ranked;
151
167
  // CONJUNCTIVITY EARLY-EXIT: a conjunctive query's top-ranked anchors
@@ -291,7 +307,6 @@ export async function confluenceJoin(
291
307
  used: new Set([met.a.anchor, met.b.anchor]),
292
308
  accounted,
293
309
  moves: 3 * STEP,
294
- unexplained: unexplainedLabel(query, accounted),
295
310
  };
296
311
  }
297
312
 
@@ -320,7 +335,6 @@ export const confluenceMechanism: PipelineMechanism = {
320
335
  accounted: met.accounted,
321
336
  moves: met.moves,
322
337
  used: met.used,
323
- unexplained: met.unexplained,
324
338
  }];
325
339
  },
326
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
  }];