@hviana/sema 0.8.7 → 0.8.8

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.
@@ -103,6 +103,12 @@ export declare class Meter {
103
103
  recognisedBytes: number;
104
104
  /** `recognise` calls served from the memo. */
105
105
  recogniseHits: number;
106
+ /** Offsets the INTERIOR pass of `recognise` considered — the endpoints inside the gaps between consecutive
107
+ * content-defined cuts. A zero says that pass found nothing to offer; a non-zero with no new site says they
108
+ * were offered and refused. */
109
+ recogniseInteriorGaps: number;
110
+ /** Interior pairs that were actually probed (the pairs that passed the span bound and spent the budget). */
111
+ recogniseInteriorPairs: number;
106
112
  /** `resolve` — whole-span content-addressed identity requests. */
107
113
  resolves: number;
108
114
  /** `climbAttentionAll` calls that actually climbed. */
package/dist/src/meter.js CHANGED
@@ -97,6 +97,12 @@ export class Meter {
97
97
  recognisedBytes = 0;
98
98
  /** `recognise` calls served from the memo. */
99
99
  recogniseHits = 0;
100
+ /** Offsets the INTERIOR pass of `recognise` considered — the endpoints inside the gaps between consecutive
101
+ * content-defined cuts. A zero says that pass found nothing to offer; a non-zero with no new site says they
102
+ * were offered and refused. */
103
+ recogniseInteriorGaps = 0;
104
+ /** Interior pairs that were actually probed (the pairs that passed the span bound and spent the budget). */
105
+ recogniseInteriorPairs = 0;
100
106
  /** `resolve` — whole-span content-addressed identity requests. */
101
107
  resolves = 0;
102
108
  // ── Mind: the consensus climb ───────────────────────────────────────────
@@ -600,7 +600,14 @@ function recogniseImpl(ctx, bytes) {
600
600
  // keeps this off the quadratic path the budget note above describes (that
601
601
  // one had no span bound at all).
602
602
  {
603
- const reach = chainReach(W);
603
+ // The span bound is W^2, the chain's own limit, PLUS the slack the endpoint set already grants: every endpoint
604
+ // sits within `radius` of a cut, so a pair that names one form may straddle cuts and still be a single form's
605
+ // span. Measured on the composite fixture: W=4 (reach 16), radius 8, and the useful [7,32) is 25 bytes with its
606
+ // edges 2 bytes from cuts 5 and 30 — already IN `ordered`, and excluded only by the upper bound. Both terms are
607
+ // derived (W and the seat count); no new constant enters.
608
+ const reach = chainReach(W) + 2 * radius;
609
+ if (ctx.meter)
610
+ ctx.meter.recogniseInteriorGaps += ordered.length;
604
611
  for (const end of ordered) {
605
612
  for (const start of ordered) {
606
613
  if (start >= end)
@@ -608,6 +615,8 @@ function recogniseImpl(ctx, bytes) {
608
615
  const span = end - start;
609
616
  if (span < W || span > reach)
610
617
  continue;
618
+ if (ctx.meter)
619
+ ctx.meter.recogniseInteriorPairs++;
611
620
  spend(start, end);
612
621
  }
613
622
  }
package/dist/src/store.js CHANGED
@@ -822,10 +822,10 @@ export class AbstractStore {
822
822
  }
823
823
  // ── Content-addressed lookup ───────────────────────────────────────────
824
824
  findLeaf(bytes) {
825
- if (this.meter)
826
- this.meter.leafLookups++;
827
825
  if (bytes.length === 1)
828
826
  return -(bytes[0] + 1);
827
+ if (this.meter)
828
+ this.meter.leafLookups++;
829
829
  const key = keyOf(bytes);
830
830
  const cached = this._leafKey.get(key);
831
831
  if (cached !== undefined)
package/jsr.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://jsr.io/schema/config-file.v1.json",
3
3
  "name": "@hviana/sema",
4
- "version": "0.8.7",
4
+ "version": "0.8.8",
5
5
  "exports": "./src/index.ts"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hviana/sema",
3
- "version": "0.8.7",
3
+ "version": "0.8.8",
4
4
  "description": "Sema: a non-parametric, instance-based reasoning system.",
5
5
  "repository": {
6
6
  "type": "git",
package/src/meter.ts CHANGED
@@ -149,6 +149,12 @@ export class Meter {
149
149
  recognisedBytes = 0;
150
150
  /** `recognise` calls served from the memo. */
151
151
  recogniseHits = 0;
152
+ /** Offsets the INTERIOR pass of `recognise` considered — the endpoints inside the gaps between consecutive
153
+ * content-defined cuts. A zero says that pass found nothing to offer; a non-zero with no new site says they
154
+ * were offered and refused. */
155
+ recogniseInteriorGaps = 0;
156
+ /** Interior pairs that were actually probed (the pairs that passed the span bound and spent the budget). */
157
+ recogniseInteriorPairs = 0;
152
158
  /** `resolve` — whole-span content-addressed identity requests. */
153
159
  resolves = 0;
154
160
 
@@ -610,12 +610,19 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
610
610
  // keeps this off the quadratic path the budget note above describes (that
611
611
  // one had no span bound at all).
612
612
  {
613
- const reach = chainReach(W);
613
+ // The span bound is W^2, the chain's own limit, PLUS the slack the endpoint set already grants: every endpoint
614
+ // sits within `radius` of a cut, so a pair that names one form may straddle cuts and still be a single form's
615
+ // span. Measured on the composite fixture: W=4 (reach 16), radius 8, and the useful [7,32) is 25 bytes with its
616
+ // edges 2 bytes from cuts 5 and 30 — already IN `ordered`, and excluded only by the upper bound. Both terms are
617
+ // derived (W and the seat count); no new constant enters.
618
+ const reach = chainReach(W) + 2 * radius;
619
+ if (ctx.meter) ctx.meter.recogniseInteriorGaps += ordered.length;
614
620
  for (const end of ordered) {
615
621
  for (const start of ordered) {
616
622
  if (start >= end) continue;
617
623
  const span = end - start;
618
624
  if (span < W || span > reach) continue;
625
+ if (ctx.meter) ctx.meter.recogniseInteriorPairs++;
619
626
  spend(start, end);
620
627
  }
621
628
  }
package/src/store.ts CHANGED
@@ -1377,8 +1377,8 @@ export abstract class AbstractStore implements Store {
1377
1377
  // ── Content-addressed lookup ───────────────────────────────────────────
1378
1378
 
1379
1379
  findLeaf(bytes: Uint8Array): NodeId | null {
1380
- if (this.meter) this.meter.leafLookups++;
1381
1380
  if (bytes.length === 1) return -(bytes[0] + 1);
1381
+ if (this.meter) this.meter.leafLookups++;
1382
1382
  const key = keyOf(bytes);
1383
1383
  const cached = this._leafKey.get(key);
1384
1384
  if (cached !== undefined) return cached;
@@ -0,0 +1,103 @@
1
+ // 185-composite-continuation.test.mjs — the interior-offset probe of `recognise`.
2
+ //
3
+ // THE CAPABILITY. A question that names the PARTS of a stored composite reaches the fact whose SUBJECT is those parts
4
+ // and answers with that fact's own continuation. Before the probe the recogniser proposed only spans aligned to the
5
+ // question's own fold, so a stored form whose two edges are not fold cuts was never offered to the engine: measured, the
6
+ // two-part question below answered "Alderman is a settlement" — the second part's own fact — instead of the composite's
7
+ // continuation, which is the defect recorded in test/181's regime-B entry.
8
+ //
9
+ // HOW. The probe offers the offsets INTERIOR to the gaps between consecutive content-defined cuts, paired on both
10
+ // edges, gated by `findFlatBranch` (the allocation-free branch probe) and confirmed by `canonResolve`/`resolve`.
11
+ // It runs for the node that IS the whole byte stream, once per byte stream.
12
+ //
13
+ // WHAT THIS FILE PINS: the answers themselves, the determinism of the reading, and that the instrumented counters are
14
+ // wired — NOT their magnitudes, which are a cost matter with its own measurement.
15
+ import test from "node:test";
16
+ import assert from "node:assert/strict";
17
+ import { Mind } from "../dist/src/index.js";
18
+ import { SQliteStore } from "../dist/src/store-sqlite.js";
19
+
20
+ const A = "the founder was born in Alderman";
21
+ const B = "the capital is Brackwater";
22
+
23
+ async function mind() {
24
+ const m = new Mind({
25
+ seed: 7,
26
+ store: new SQliteStore({ path: ":memory:" }),
27
+ profile: true,
28
+ });
29
+ await m.ingest([
30
+ [A, "Alderman is a settlement"],
31
+ [B, "Brackwater is a province"],
32
+ [`${A} and ${B}`, "that settlement is Cordelia"],
33
+ ["the note is filed", "the note is in folder"],
34
+ ]);
35
+ return m;
36
+ }
37
+
38
+ test("recognise(): a question naming the parts of a composite answers with the composite's own continuation", async () => {
39
+ const m = await mind();
40
+ const got = String(await m.respondText(`I ask: ${B}, and ${A}.`, () => {}));
41
+ assert.match(
42
+ got,
43
+ /that settlement is Cordelia/,
44
+ `the composite's continuation must be reachable through its parts, got: ${
45
+ JSON.stringify(got)
46
+ }`,
47
+ );
48
+ assert.match(
49
+ got,
50
+ /Alderman is a settlement/,
51
+ `and the other part of the question stays explained too, got: ${
52
+ JSON.stringify(got)
53
+ }`,
54
+ );
55
+ await m.store.close();
56
+ });
57
+
58
+ test("recognise(): ONE part of the composite is enough to reach it", async () => {
59
+ const m = await mind();
60
+ const got = String(await m.respondText(`I ask: ${B}.`, () => {}));
61
+ assert.equal(got.trim(), "that settlement is Cordelia");
62
+ await m.store.close();
63
+ });
64
+
65
+ test("recognise(): the parts may be named in either order", async () => {
66
+ const m = await mind();
67
+ const got = String(await m.respondText(`I ask: ${A}, and ${B}.`, () => {}));
68
+ assert.match(
69
+ got,
70
+ /that settlement is Cordelia/,
71
+ `the store's order decides, not the question's, got: ${
72
+ JSON.stringify(got)
73
+ }`,
74
+ );
75
+ await m.store.close();
76
+ });
77
+
78
+ test("recognise(): the reading is deterministic across two answers", async () => {
79
+ const m = await mind();
80
+ const one = String(await m.respondText(`I ask: ${B}, and ${A}.`, () => {}));
81
+ const two = String(await m.respondText(`I ask: ${B}, and ${A}.`, () => {}));
82
+ assert.equal(two, one);
83
+ await m.store.close();
84
+ });
85
+
86
+ test("recognise(): the interior tier is instrumented, and the whole-stream guard is what runs it", async () => {
87
+ const m = await mind();
88
+ await m.respondText(`I ask: ${B}, and ${A}.`, () => {});
89
+ const c = m.lastCost?.counters ?? {};
90
+ for (const k of ["recogniseInteriorGaps", "recogniseInteriorPairs"]) {
91
+ assert.ok(
92
+ Object.hasOwn(c, k),
93
+ `the meter must carry ${k} — a cost claim with no counter is a claim with no evidence (got: ${
94
+ Object.keys(c).join(",")
95
+ })`,
96
+ );
97
+ }
98
+ assert.ok(
99
+ c.recogniseInteriorPairs > 0,
100
+ "the interior tier must have probed at least one gated pair",
101
+ );
102
+ await m.store.close();
103
+ });