@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
@@ -12,8 +12,9 @@ import { PASS, STEP } from "./graph-search.js";
12
12
  import { gistOf, read, resolve } from "./primitives.js";
13
13
  import { recognise } from "./recognition.js";
14
14
  import { fuseAttention, reason } from "./reasoning.js";
15
- import { unexplainedSpans } from "./rationale.js";
15
+ import { closed, remainderOf, unaccountedBytes, unexplainedSpans, windowOf, } from "./derivation.js";
16
16
  import { rItem } from "./trace.js";
17
+ import { unexplainedLabel } from "./rationale.js";
17
18
  import { hubBound } from "./traverse.js";
18
19
  import { Precomputed } from "./pipeline-mechanism.js";
19
20
  import { coverMechanism } from "./mechanisms/cover.js";
@@ -124,8 +125,7 @@ export async function think(ctx, query, mechs) {
124
125
  // (meter.md); the trace already represents structure.
125
126
  const pre = new Precomputed(ctx, query, rec, computed, ctx._edgeGuide);
126
127
  const grade = (w) => Math.floor(w / STEP);
127
- const unaccounted = (spans) => unexplainedSpans(query.length, spans)
128
- .reduce((sum, [s, e]) => sum + (e - s), 0);
128
+ const unaccounted = (spans) => unaccountedBytes(unexplainedSpans(query.length, spans));
129
129
  const weigh = (accounted, moves) => moves + PASS * unaccounted(accounted);
130
130
  const candidates = [];
131
131
  let best = null;
@@ -243,14 +243,16 @@ export async function think(ctx, query, mechs) {
243
243
  ? await meter.time(`${mech.name}.run`, () => mech.run(ctx, query, pre))
244
244
  : await mech.run(ctx, query, pre);
245
245
  for (const r of results) {
246
- const weight = r.weight ?? weigh(r.accounted, r.moves);
246
+ // ONE FORMULA, EVERY CANDIDATE: the chart's derivation reports how many
247
+ // discrete moves it made and which bytes it could not recognise; the
248
+ // currency prices both. No mechanism passes a price of its own.
249
+ const weight = weigh(r.accounted, r.moves);
247
250
  consider({
248
251
  bytes: r.bytes,
249
252
  provenance: r.provenance ?? mech.provenance,
250
253
  weight,
251
254
  used: r.used,
252
255
  accounted: r.accounted,
253
- unexplained: r.unexplained,
254
256
  complete: r.complete,
255
257
  scaffolding: r.scaffolding,
256
258
  });
@@ -281,7 +283,17 @@ export async function think(ctx, query, mechs) {
281
283
  const margin = decided !== null && runnerUp !== null
282
284
  ? grade(runnerUp.weight) - grade(decided.weight)
283
285
  : null;
284
- ctx.trace?.step("decideGrounding", candidates.map((c) => rItem(c.bytes, `${c.provenance} (weight ${c.weight.toFixed(3)}${c.unexplained ? `, unexplained: "${c.unexplained}"` : ""})`)), decided ? [rItem(decided.bytes, decided.provenance)] : [], "the lightest grounding derivation wins — every mechanism weighed in the one cost ladder", undefined, {
286
+ ctx.trace?.step("decideGrounding",
287
+ // THE LABEL IS RENDERED WHERE IT IS SHOWN. It was a field on every
288
+ // mechanism's result, and at every one of them it was exactly
289
+ // `unexplainedLabel(query, accounted)` — a second representation of a
290
+ // quantity one pure function already yields, computed on every response
291
+ // whether or not anyone looked. Here it is computed only when a rationale
292
+ // is attached, because `trace?.step` short-circuits its arguments.
293
+ candidates.map((c) => {
294
+ const label = unexplainedLabel(query, c.accounted);
295
+ return rItem(c.bytes, `${c.provenance} (weight ${c.weight.toFixed(3)}${label ? `, unexplained: "${label}"` : ""})`);
296
+ }), decided ? [rItem(decided.bytes, decided.provenance)] : [], "the lightest grounding derivation wins — every mechanism weighed in the one cost ladder", undefined, {
285
297
  version: 1,
286
298
  candidates: candidates.map((c) => ({
287
299
  provenance: c.provenance,
@@ -317,13 +329,57 @@ export async function think(ctx, query, mechs) {
317
329
  }
318
330
  const answer = decided.bytes;
319
331
  const provenance = decided.provenance;
320
- const castUsed = decided.used ?? new Set();
321
- // ── Post-grounding, gated by provenance ──────────────────────────────
322
- const preConsumed = provenance === "cast" || provenance === "join"
323
- ? castUsed
324
- : provenance === "recall" || provenance === "recall-echo"
325
- ? new Set()
326
- : new Set(recognise(ctx, answer).sites.map((s) => s.payload));
332
+ const declaredUsed = decided.used;
333
+ // ── THE DERIVATION STATE ─────────────────────────────────────────────
334
+ //
335
+ // THE REASONER JUDGES ITS OWN EXTENSIONS BY THE PIPELINE'S REMAINDER, not by
336
+ // the ladder's `accounted` — and by the SAME reading the fuse gate below uses,
337
+ // with the same W floor. `accounted` is a COST quantity (measured: a query
338
+ // fully explained by one computed span plus bridged connectors reports
339
+ // `accounted: []` while nothing is unexplained), and a remainder under one
340
+ // river-fold quantum is bridging punctuation, never a second topic — so it
341
+ // licenses no extension and blocks none.
342
+ //
343
+ // The state is built HERE, where these quantities are already computed, so it
344
+ // costs nothing new: `accounted` is what the winning transition priced,
345
+ // `remainder` is the coverage reading over `accounted ∪ the response's
346
+ // computed spans` (the union is what makes the two different quantities, and
347
+ // both are kept), `cost` is the ladder position, and the two declarations are
348
+ // the producer's own (`fixed`, `used`). What follows reads THIS state rather
349
+ // than a tuple rebuilt at each call site.
350
+ // A DERIVATION IS BORN OWING WHAT ITS ANSWER DOES NOT CARRY. The winning
351
+ // transition PRICED these spans, and pricing is not carrying: coverage claimed
352
+ // without evidence stays owed, and a later transition pays it only by carrying
353
+ // it (the law reads the window; see derivation.ts). Same reading, one
354
+ // definition — not a second spelling of it here.
355
+ const explained = [
356
+ ...decided.accounted,
357
+ ...pre.computed.map((u) => [u.i, u.j]),
358
+ ].filter(([a, b]) => windowOf([a, b], answer, query, ctx.space.maxGroup) !== null);
359
+ // WHAT THE CONSTRUCTION WITHHOLDS, at or above one quantum: the difference between
360
+ // the remainder paid in full and the remainder paid by carrying. Both readings
361
+ // are the law's, so the floor is applied once and in one place.
362
+ const paidInFull = remainderOf(query.length, [
363
+ ...decided.accounted,
364
+ ...pre.computed.map((u) => [u.i, u.j]),
365
+ ], ctx.space.maxGroup);
366
+ const paid = remainderOf(query.length, explained, ctx.space.maxGroup);
367
+ if (ctx.meter) {
368
+ ctx.meter.groundingWithheldBytes += unaccountedBytes(paid) -
369
+ unaccountedBytes(paidInFull);
370
+ }
371
+ const state = {
372
+ product: answer,
373
+ accounted: decided.accounted,
374
+ remainder: paid,
375
+ cost: decided.weight,
376
+ fixed: decided.complete,
377
+ used: decided.used,
378
+ };
379
+ const uncovered = state.remainder;
380
+ // ── Post-grounding, gated by the declaration and the remainder ────────
381
+ const preConsumed = declaredUsed ??
382
+ new Set(recognise(ctx, answer).sites.map((s) => s.payload));
327
383
  // A grounding that DECLARED itself complete is not extended: the answer is
328
384
  // already a trained form's own continuation, reached through an identity
329
385
  // claim about the query, so a multi-hop pivot could only chain past the
@@ -352,9 +408,32 @@ export async function think(ctx, query, mechs) {
352
408
  // `preConsumed` is derived by re-recognising the answer — "everything in
353
409
  // it", not "what it voiced" — and a containment rule over that would
354
410
  // suppress every pivot the answer legitimately contains.
355
- const voiced = (provenance === "cast" || provenance === "join")
356
- ? [...castUsed].flatMap((id) => ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n)))
357
- : [];
411
+ const voiced = declaredUsed === undefined ? [] : [...declaredUsed].flatMap((id) => ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n)));
412
+ // WHAT THIS BRANCH READ, published where it was read. Post-grounding decides
413
+ // by the DECLARATION (`decided.used`, which becomes `voiced`), by what the
414
+ // recognition already consumed (`preConsumed`) and by the derivation's own
415
+ // remainder — never by the provenance NAME, which is REPORTED throughout and
416
+ // compared nowhere (a stale comment here claimed otherwise; the register caught
417
+ // it, and this is the correction). The operands were invisible in the trace, so
418
+ // a change to the branching could not be shown equivalent or otherwise from
419
+ // outside — three separate investigations failed on exactly that gap. A gap in instrumentation is a defect IN the instrumentation
420
+ // (AGENTS.md §6): closed here, once, as counts only — never content.
421
+ ctx.trace?.step("postGrounding", [rItem(answer, provenance)], [], `used=${decided.used !== undefined ? "declared" : "absent"} · ` +
422
+ `preConsumed=${preConsumed.size} · voiced=${voiced.length}`, undefined, {
423
+ version: 1,
424
+ provenance,
425
+ usedDeclared: decided.used !== undefined,
426
+ preConsumed: preConsumed.size,
427
+ voiced: voiced.length,
428
+ // THE STATE THE LAW GOVERNS, rendered where it is decided: what the asker
429
+ // said that no step has accounted for, in spans at or above one quantum,
430
+ // and whether the producer supplied a fixed point. Counts only, like
431
+ // every other operand here — and the spans are the state's, so a reader
432
+ // can check them against the meter's aggregate of the same remainder.
433
+ remainderSpans: state.remainder.length,
434
+ remainderBytes: unaccountedBytes(state.remainder),
435
+ fixed: state.fixed === true,
436
+ });
358
437
  // REPORTABLE, NOT SILENT. A declared-complete grounding ends the derivation
359
438
  // here, and that decision is part of the derivation's shape: the reader of a
360
439
  // rationale must be able to see that the chain stopped because the mechanism
@@ -365,22 +444,22 @@ export async function think(ctx, query, mechs) {
365
444
  ctx.trace?.step("completeGrounding", [rItem(answer, provenance)], [], "grounding declared complete — the query IS the context, so " +
366
445
  "post-grounding extension is skipped");
367
446
  }
368
- // THE REASONER JUDGES ITS OWN EXTENSIONS BY THE PIPELINE'S REMAINDER, not by
369
- // the ladder's `accounted` — and by the SAME reading the fuse gate below uses,
370
- // with the same W floor. `accounted` is a COST quantity (measured: a query
371
- // fully explained by one computed span plus bridged connectors reports
372
- // `accounted: []` while nothing is unexplained), and a remainder under one
373
- // river-fold quantum is bridging punctuation, never a second topic — so it
374
- // licenses no extension and blocks none.
375
- const explained = [
376
- ...decided.accounted,
377
- ...pre.computed.map((u) => [u.i, u.j]),
378
- ];
379
- const uncovered = unexplainedSpans(query.length, explained)
380
- .filter(([a, b]) => b - a >= ctx.space.maxGroup);
381
- const reasoned = decided.complete ? answer : meter
382
- ? await meter.time("reason", () => reason(ctx, query, answer, preConsumed, pre, voiced, uncovered))
383
- : await reason(ctx, query, answer, preConsumed, pre, voiced, uncovered);
447
+ // PUBLISHED, NOT RECOMPUTED: the same `uncovered` the gates below read. A
448
+ // write-only accounting (meter contract 1), so the number that licenses an
449
+ // extension or a fusion stops being invisible.
450
+ if (meter) {
451
+ meter.postGroundingRemainderSpans += uncovered.length;
452
+ meter.postGroundingRemainderBytes += unaccountedBytes(uncovered);
453
+ }
454
+ // THE WALK CONSUMES AND RETURNS A STATE. It is handed the derivation's own —
455
+ // the grounding's product, accounting, remainder and cost — and hands back the
456
+ // state it advanced to, so what follows reads a state rather than bytes plus a
457
+ // tuple rebuilt here. A supplied fixed point is the one case where the walk
458
+ // does not run at all, and then the state is the grounding's own.
459
+ const extension = decided.complete ? undefined : meter
460
+ ? await meter.time("reason", () => reason(ctx, query, state, preConsumed, pre, voiced))
461
+ : await reason(ctx, query, state, preConsumed, pre, voiced);
462
+ const reasoned = extension ?? state;
384
463
  // Fuse only when the query has a genuine REMAINDER no mechanism's
385
464
  // structural evidence touched at all. `decided.accounted` alone
386
465
  // undercounts this: it is a COST-LADDER quantity (cover.ts prices its
@@ -397,7 +476,15 @@ export async function think(ctx, query, mechs) {
397
476
  // observed: a single space between two fully-computed arithmetic spans
398
477
  // ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
399
478
  // corpus fact, corrupting "4 6" into "4 63".
400
- const remainder = unaccounted(explained);
479
+ // THE GATE ASKS THE LAW, and that is an OPTIMISATION, not a tidy-up: the state
480
+ // above ALREADY carries the remainder (`remainderOf`, per-span, with the W
481
+ // floor applied), so asking it costs nothing, while the total this line used to
482
+ // compute (`unaccounted(explained)`) was one more sum over the spans on every
483
+ // response. The two readings are the same condition, not two: the ACCOUNTING
484
+ // applies the same W floor the gate does, so a gap below one quantum never
485
+ // survives into `explained` and the total cannot reach W without some single
486
+ // gap reaching it. Measured over twelve constructions at W = 4 (test/136.3,
487
+ // which pins the equivalence and both sides of it).
401
488
  // Whether the winning candidate's entire recognised substance is
402
489
  // COMPUTED — every accounted span exactly a pre.computed span, nothing
403
490
  // from a genuinely recognised/climbed site. fuseAttention's lone-root
@@ -407,8 +494,8 @@ export async function think(ctx, query, mechs) {
407
494
  // `unclimbed` parameter, gated there by Attention.breadth so a
408
495
  // coincidental echo (which this flag alone cannot distinguish) is still
409
496
  // rejected.
410
- const unclimbed = decided.accounted.length > 0 &&
411
- decided.accounted.every(([i, j]) => pre.computed.some((u) => u.i === i && u.j === j));
497
+ const unclimbed = state.accounted.length > 0 &&
498
+ state.accounted.every(([i, j]) => pre.computed.some((u) => u.i === i && u.j === j));
412
499
  // Where the winning grounding stands in the query — fusion places primary
413
500
  // by it (see fuseAttention's `primarySpans`). `accounted` is the
414
501
  // cost-ladder read and is authoritative when non-empty; when it is empty
@@ -416,14 +503,19 @@ export async function think(ctx, query, mechs) {
416
503
  // Exactly the cost-ladder-vs-coverage distinction `explained` above draws,
417
504
  // read here for POSITION instead of for coverage — and resolved here, where
418
505
  // both readings are in hand, rather than inside fuseAttention.
419
- const primarySpans = decided.accounted.length > 0
420
- ? decided.accounted
506
+ const primarySpans = state.accounted.length > 0
507
+ ? state.accounted
421
508
  : pre.computed.map((u) => [u.i, u.j]);
422
- const fused = remainder < ctx.space.maxGroup
423
- ? reasoned
424
- : meter
425
- ? await meter.time("fuse", () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans))
426
- : await fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans);
427
- done(fused, "grounded, reasoned forward, fused across points of attention");
428
- return { bytes: fused, provenance };
509
+ const fused = closed(state) ? reasoned : meter
510
+ ? await meter.time("fuse", () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans))
511
+ : await fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans);
512
+ done(fused.product,
513
+ // NO CLAIM ABOUT FUSION HERE. `fuseAttention` is entered whenever a
514
+ // remainder ≥ W exists and returns early when there is nothing to bridge, so
515
+ // this note used to assert a fusion that frequently did not happen (measured:
516
+ // "What is the capital of France famous for" fuses 0 times). The fusion is
517
+ // reported by `fuseAttention`'s own `done` when it happens — the layer that
518
+ // did the work is the layer that says so.
519
+ "grounded, reasoned forward");
520
+ return { bytes: fused.product, provenance };
429
521
  }
@@ -297,7 +297,15 @@ export function canonResolve(ctx, bytes) {
297
297
  // on exactly the node the canonical-case query would have found.
298
298
  const folded = foldTree(ctx, perceive(ctx, bytesOf), 0).node;
299
299
  const use = folded ?? id;
300
- const leads = store.hasNext(use) || store.haloMass(use) > 0;
300
+ // THE ADMISSION PREDICATE, by its own pair of probes: `traverse.ts`'s
301
+ // `leadsSomewhere` is edge-or-halo, and `hasHalo` is the one that carries
302
+ // the mass bar (`mass >= minHaloMass`). Asking `haloMass(use) > 0` instead
303
+ // is the same answer only while `minHaloMass <= 1` (its default): raise the
304
+ // bar and this site would rank a node as leading on evidence the law
305
+ // refuses. Calling `leadsSomewhere` here is not possible — `traverse.ts`
306
+ // imports THIS file, so it would be a cycle — which is why the pair is
307
+ // spelled out rather than named.
308
+ const leads = store.hasNext(use) || store.hasHalo(use);
301
309
  if (best === null || (leads && !bestLeads) ||
302
310
  (leads === bestLeads && use < best)) {
303
311
  best = use;
@@ -26,6 +26,17 @@ export interface RationaleItem {
26
26
  * caller asked to carry it (off by default — a D-float array per item would
27
27
  * bury the reasoning it is meant to explain). */
28
28
  v?: Vec;
29
+ /** The element's OWN bytes, attached BY REFERENCE when the step was built from
30
+ * bytes (a `rationale.ts` item made from a node carries none: read it back
31
+ * through `node`). `text` is a RENDERING and cannot stand in for them — it
32
+ * decodes UTF-8 and DROPS NUL bytes, so a key containing one is unrecoverable
33
+ * from it, which is exactly how a join refusal (`deriveThroughMiss`) became
34
+ * impossible to test exactly without re-encoding. Treat as READ-ONLY: the
35
+ * array belongs to the caller (and may be a view into the query).
36
+ *
37
+ * Costs nothing when nothing inspects: items exist only while a rationale
38
+ * sink is attached, and this holds a reference rather than a copy. */
39
+ bytes?: Uint8Array;
29
40
  }
30
41
  /** A single completed act of inference — one mechanism, run once.
31
42
  *
@@ -72,10 +83,6 @@ export type InspectRationale = (step: RationaleStep) => void;
72
83
  /** Decode bytes to text for display, dropping the NUL padding the encoder uses
73
84
  * (the same cleanup {@link Mind.respondText} does for its result). */
74
85
  export declare function decodeText(bytes: Uint8Array): string;
75
- /** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
76
- * the same union-of-spans reading think's grounding decider prices at PASS
77
- * per byte, exposed here so a mechanism can turn it into a human label. */
78
- export declare function unexplainedSpans(queryLen: number, accounted: ReadonlyArray<[number, number]>): Array<[number, number]>;
79
86
  /** A human-readable label for the query bytes a mechanism's `accounted`
80
87
  * spans leave unexplained — purely diagnostic (Task 2's negative evidence):
81
88
  * it never changes a candidate's weight, only what the rationale trace
@@ -133,7 +140,16 @@ export declare class Rationale {
133
140
  * now; the matching {@link Scope.done} supplies the outputs when it finishes.
134
141
  * `deps` overrides the default data-flow edge (previous sibling / parent). */
135
142
  enter(name: string, inputs: RationaleItem[], deps?: number[]): Scope;
136
- /** Record a mechanism that has no sub-steps — its inputs and outputs are both
143
+ /** WHY THIS NAME IS A FREE STRING, when the derivation's moves are a closed
144
+ * union: a mechanism name is WRITTEN and DISPLAYED, and it COMPOSES with
145
+ * the nesting — `mechanism` is the whole path (`["respond", "think",
146
+ * "recognise"]`), which no fixed union can express. Nothing branches on it:
147
+ * `nothing here drives the inference; it only WITNESSES it`. A vocabulary
148
+ * that is only witnessed needs no union; one that is read does
149
+ * (`DerivationMove`, in graph-search.ts). The asymmetry is the design, not
150
+ * a drift.
151
+ *
152
+ * Record a mechanism that has no sub-steps — its inputs and outputs are both
137
153
  * known at the call site. Returns its index, for a later step to depend on. */
138
154
  step(name: string, inputs: RationaleItem[], outputs: RationaleItem[], note?: string, deps?: number[], data?: unknown): number;
139
155
  }
@@ -19,31 +19,17 @@
19
19
  // out into a ranked list of hits. So a step's `inputs` and `outputs` are each a
20
20
  // VECTOR — an ordered list of {@link RationaleItem}s, one per element — and the
21
21
  // fan-out / fan-in is visible in their lengths.
22
+ import { unexplainedSpans } from "./derivation.js";
22
23
  /** Decode bytes to text for display, dropping the NUL padding the encoder uses
23
24
  * (the same cleanup {@link Mind.respondText} does for its result). */
24
25
  export function decodeText(bytes) {
25
26
  return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
26
27
  }
27
- /** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
28
- * the same union-of-spans reading think's grounding decider prices at PASS
29
- * per byte, exposed here so a mechanism can turn it into a human label. */
30
- export function unexplainedSpans(queryLen, accounted) {
31
- const sorted = accounted
32
- .map(([s, e]) => [Math.max(0, s), Math.min(queryLen, e)])
33
- .filter(([s, e]) => e > s)
34
- .sort((a, b) => a[0] - b[0]);
35
- const gaps = [];
36
- let reach = 0;
37
- for (const [s, e] of sorted) {
38
- if (s > reach)
39
- gaps.push([reach, s]);
40
- if (e > reach)
41
- reach = e;
42
- }
43
- if (reach < queryLen)
44
- gaps.push([reach, queryLen]);
45
- return gaps;
46
- }
28
+ // THE SPAN ALGEBRA LIVES IN derivation.ts. `unaccountedBytes` and
29
+ // `unexplainedSpans` are the closure law's vocabulary — gap arithmetic over the
30
+ // asker's own bytes — and they moved to the layer that owns the law, so they
31
+ // have ONE home and are no longer asked of the tracer. This module keeps the
32
+ // inference TOLD as it happens, and reads the gaps only to render a label.
47
33
  /** A human-readable label for the query bytes a mechanism's `accounted`
48
34
  * spans leave unexplained — purely diagnostic (Task 2's negative evidence):
49
35
  * it never changes a candidate's weight, only what the rationale trace
@@ -151,7 +137,16 @@ export class Rationale {
151
137
  },
152
138
  };
153
139
  }
154
- /** Record a mechanism that has no sub-steps — its inputs and outputs are both
140
+ /** WHY THIS NAME IS A FREE STRING, when the derivation's moves are a closed
141
+ * union: a mechanism name is WRITTEN and DISPLAYED, and it COMPOSES with
142
+ * the nesting — `mechanism` is the whole path (`["respond", "think",
143
+ * "recognise"]`), which no fixed union can express. Nothing branches on it:
144
+ * `nothing here drives the inference; it only WITNESSES it`. A vocabulary
145
+ * that is only witnessed needs no union; one that is read does
146
+ * (`DerivationMove`, in graph-search.ts). The asymmetry is the design, not
147
+ * a drift.
148
+ *
149
+ * Record a mechanism that has no sub-steps — its inputs and outputs are both
155
150
  * known at the call site. Returns its index, for a later step to depend on. */
156
151
  step(name, inputs, outputs, note, deps, data) {
157
152
  const mechanism = this.path(name);
@@ -1,35 +1,27 @@
1
1
  import type { MindContext } from "./types.js";
2
2
  import type { Precomputed } from "./pipeline-mechanism.js";
3
- /** Whether `bytes` is a proper byte-subspan of `query` — already present in
4
- * the question, so voicing it back only restates part of what was asked,
5
- * never answers it. The exact guard recallByResonance already applies to
6
- * its OWN grounding candidates (tier 1's `restates`, tier 2's subspan
7
- * check, tier 0b's argument-binding subspan check) — every mechanism that
8
- * walks a LEARNT CONTINUATION EDGE past an already-vetted grounding
9
- * (reason()'s own hops below, and CAST's `projectCounterfactual` seat
10
- * substitution — see cast.ts) needs the same guard applied to what the
11
- * walk turns up, since `follow()`/`chooseNext`/`pivotInto` know nothing of
12
- * the query at all — only of what structurally continues what. */
13
- export declare function restatesQuery(query: Uint8Array, bytes: Uint8Array): boolean;
3
+ import { type DerivationState, type Span } from "./derivation.js";
14
4
  /** Extend a grounded answer forward across facts (multi-hop reasoning).
15
5
  * Pivots on the longest unconsumed learnt context each answer contains,
16
- * then follows the pivot's continuation to the next fact. Repeats up
17
- * to `cfg.recallQueryK` hops. `preConsumed` carries node ids already
6
+ * then follows the pivot's continuation to the next fact. **The chain ends
7
+ * when it STOPS, never when a count runs out**: every exit is a refusal (no
8
+ * pivot, no forward step, no question material carried) and the walk is bounded
9
+ * by the material and the graph — `consumed` refuses to revisit a node. There
10
+ * is no hop allowance, so this doc deliberately names no `cfg` capacity: the
11
+ * cover prices every hop at `STEP` and lets the search decide the depth, and a
12
+ * second count here would be a second decision about the same thing.
13
+ * `preConsumed` carries node ids already
18
14
  * spoken for by the grounding stage (cover/extract/CAST). `voiced` carries
19
15
  * the BYTES of the anchors a mechanism declared it voiced (its `used` set),
20
16
  * when it declared one — see the pivot's own containment rule. `pre` is the
21
17
  * response's shared pre-computation — the post-grounding stages read the
22
18
  * same container the mechanisms did. */
23
- export declare function reason(ctx: MindContext, query: Uint8Array, answer: Uint8Array, preConsumed: ReadonlySet<number>, pre: Precomputed, voiced?: readonly Uint8Array[],
24
- /** The query material the GROUNDING left uncovered — the cost ladder's own
25
- * `unaccounted` spans. Only the reasoner's OWN extensions are judged
26
- * against it; a mechanism carrying its own `used` set owns its shape. */
27
- uncovered?: readonly (readonly [number, number])[]): Promise<Uint8Array>;
19
+ export declare function reason(ctx: MindContext, query: Uint8Array, d0: DerivationState, preConsumed: ReadonlySet<number>, pre: Precomputed, voiced?: readonly Uint8Array[]): Promise<DerivationState>;
28
20
  /** Fuse independent points of attention into one answer (multi-topic).
29
21
  * When the consensus climb finds more than one dominant point, each
30
22
  * independent point grounds its own answer; they are bridged together
31
23
  * by any learnt connector the graph holds between them. */
32
- export declare function fuseAttention(ctx: MindContext, query: Uint8Array, primary: Uint8Array, pre: Precomputed,
24
+ export declare function fuseAttention(ctx: MindContext, query: Uint8Array, state: DerivationState, pre: Precomputed,
33
25
  /** True when `primary` never touched the consensus climb at all — e.g. a
34
26
  * pure ALU computation, which has no anchor of its own. commitVotes
35
27
  * ALWAYS admits the dominant root regardless of its vote (attention.ts:
@@ -43,4 +35,4 @@ unclimbed?: boolean,
43
35
  * which is the layer that knows how a given grounding records its evidence;
44
36
  * fuseAttention just reads a position from it. Empty or absent preserves
45
37
  * the original behaviour exactly. */
46
- primarySpans?: ReadonlyArray<readonly [number, number]>): Promise<Uint8Array>;
38
+ primarySpans?: ReadonlyArray<Span>): Promise<DerivationState>;