@hviana/sema 0.8.5 → 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.
- package/dist/src/meter.d.ts +21 -6
- package/dist/src/meter.js +21 -6
- package/dist/src/mind/derivation.d.ts +1 -1
- package/dist/src/mind/derivation.js +1 -1
- package/dist/src/mind/reasoning.js +10 -2
- package/docs/INDEX.md +1 -1
- package/docs/INVARIANTS.md +1 -1
- package/docs/architecture/closure.md +15 -2
- package/docs/harness/gates.md +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/meter.ts +21 -6
- package/src/mind/derivation.ts +6 -2
- package/src/mind/reasoning.ts +14 -2
- package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +3 -3
- package/test/136-the-two-named-limits.test.mjs +1 -1
- package/test/142-the-layer-offers-only-what-the-law-admits.test.mjs +93 -0
- package/test/143-cycles-terminate-and-are-not-closure.test.mjs +62 -0
- package/test/55-cost-meter.test.mjs +2 -2
package/dist/src/meter.d.ts
CHANGED
|
@@ -199,15 +199,30 @@ export declare class Meter {
|
|
|
199
199
|
* not computable at all. With it, the price of extending the answer is
|
|
200
200
|
* `reasonSteps · STEP`, the ladder's own value for following an edge. */
|
|
201
201
|
reasonSteps: number;
|
|
202
|
-
/** Bytes
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
202
|
+
/** Bytes the admitted steps ACCOUNTED for — the witnesses' spans, summed by the
|
|
203
|
+
* law's own function over the tail of the state's accounting. It is NOT what
|
|
204
|
+
* they carried: a step admitted by `reaches` may declare spans it holds no
|
|
205
|
+
* window of, and then it accounts without carrying and consumes nothing (see
|
|
206
|
+
* {@link closureDrainedBytes}). Read with {@link reasonSteps}: one is the
|
|
207
|
+
* price, the other the accounting. */
|
|
208
|
+
reasonAccountedBytes: number;
|
|
209
|
+
/** Bytes of the QUESTION's own material an admitted step CARRIED — the windows
|
|
210
|
+
* of the remainder its product holds, read by the law's one reading. The
|
|
211
|
+
* accounting may be larger (a `reaches` step declares spans it holds no window
|
|
212
|
+
* of) and the consumption is the window of a step that also reaches, so the
|
|
213
|
+
* three form a lattice: consumed ⊆ carried ⊆ accounted. */
|
|
207
214
|
reasonCarriedBytes: number;
|
|
215
|
+
/** Times the layer OFFERED a continuation to the law. Read with
|
|
216
|
+
* {@link lawRejects}: the offers the law refused are the Model X contract being
|
|
217
|
+
* exercised, and `offerRuns - lawRejects` is the accepted transitions. */
|
|
218
|
+
offerRuns: number;
|
|
219
|
+
/** Times the law REFUSED the continuation the layer offered. A refusal is
|
|
220
|
+
* terminal by the layer's contract, so this counts the moments the contract
|
|
221
|
+
* mattered — zero means the walk never needed it. */
|
|
222
|
+
lawRejects: number;
|
|
208
223
|
/** Bytes of the question's REMAINDER a step CONSUMED — the drop the law's own
|
|
209
224
|
* `advance` makes when a declared move carries the material it accounts for.
|
|
210
|
-
* Read with {@link reasonSteps} and {@link
|
|
225
|
+
* Read with {@link reasonSteps} and {@link reasonAccountedBytes}: carrying is
|
|
211
226
|
* the engagement, this is the consumption, and before it the second was
|
|
212
227
|
* invisible. */
|
|
213
228
|
closureDrainedBytes: number;
|
package/dist/src/meter.js
CHANGED
|
@@ -209,15 +209,30 @@ export class Meter {
|
|
|
209
209
|
* not computable at all. With it, the price of extending the answer is
|
|
210
210
|
* `reasonSteps · STEP`, the ladder's own value for following an edge. */
|
|
211
211
|
reasonSteps = 0;
|
|
212
|
-
/** Bytes
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
212
|
+
/** Bytes the admitted steps ACCOUNTED for — the witnesses' spans, summed by the
|
|
213
|
+
* law's own function over the tail of the state's accounting. It is NOT what
|
|
214
|
+
* they carried: a step admitted by `reaches` may declare spans it holds no
|
|
215
|
+
* window of, and then it accounts without carrying and consumes nothing (see
|
|
216
|
+
* {@link closureDrainedBytes}). Read with {@link reasonSteps}: one is the
|
|
217
|
+
* price, the other the accounting. */
|
|
218
|
+
reasonAccountedBytes = 0;
|
|
219
|
+
/** Bytes of the QUESTION's own material an admitted step CARRIED — the windows
|
|
220
|
+
* of the remainder its product holds, read by the law's one reading. The
|
|
221
|
+
* accounting may be larger (a `reaches` step declares spans it holds no window
|
|
222
|
+
* of) and the consumption is the window of a step that also reaches, so the
|
|
223
|
+
* three form a lattice: consumed ⊆ carried ⊆ accounted. */
|
|
217
224
|
reasonCarriedBytes = 0;
|
|
225
|
+
/** Times the layer OFFERED a continuation to the law. Read with
|
|
226
|
+
* {@link lawRejects}: the offers the law refused are the Model X contract being
|
|
227
|
+
* exercised, and `offerRuns - lawRejects` is the accepted transitions. */
|
|
228
|
+
offerRuns = 0;
|
|
229
|
+
/** Times the law REFUSED the continuation the layer offered. A refusal is
|
|
230
|
+
* terminal by the layer's contract, so this counts the moments the contract
|
|
231
|
+
* mattered — zero means the walk never needed it. */
|
|
232
|
+
lawRejects = 0;
|
|
218
233
|
/** Bytes of the question's REMAINDER a step CONSUMED — the drop the law's own
|
|
219
234
|
* `advance` makes when a declared move carries the material it accounts for.
|
|
220
|
-
* Read with {@link reasonSteps} and {@link
|
|
235
|
+
* Read with {@link reasonSteps} and {@link reasonAccountedBytes}: carrying is
|
|
221
236
|
* the engagement, this is the consumption, and before it the second was
|
|
222
237
|
* invisible. */
|
|
223
238
|
closureDrainedBytes = 0;
|
|
@@ -196,6 +196,6 @@ export type Offer = (d: DerivationState) => Promise<Continuation | null>;
|
|
|
196
196
|
* Nothing here counts steps, and nothing here decides admissibility. */
|
|
197
197
|
export declare function closure(d: DerivationState, query: Uint8Array, W: number, offer: Offer,
|
|
198
198
|
/** Called for each step the law admits, with the state before and after. */
|
|
199
|
-
onTaken?: (before: DerivationState, after: DerivationState) => void,
|
|
199
|
+
onTaken?: (before: DerivationState, after: DerivationState, witnesses: ReadonlyArray<Witness>) => void,
|
|
200
200
|
/** Called when the law refused the continuation the layer offered. */
|
|
201
201
|
onRefused?: (at: DerivationState) => void): Promise<DerivationState>;
|
|
@@ -183,6 +183,8 @@ export async function reason(ctx, query, d0, preConsumed, pre, voiced = []) {
|
|
|
183
183
|
// admitted.
|
|
184
184
|
let pending = null;
|
|
185
185
|
const offer = async (d) => {
|
|
186
|
+
if (ctx.meter)
|
|
187
|
+
ctx.meter.offerRuns += 1;
|
|
186
188
|
const cur = d.product;
|
|
187
189
|
// The first step's `cur` IS the grounding's product, so the guard above
|
|
188
190
|
// already resolved it and read its reverse edges — reuse both.
|
|
@@ -232,8 +234,9 @@ export async function reason(ctx, query, d0, preConsumed, pre, voiced = []) {
|
|
|
232
234
|
cost: STEP,
|
|
233
235
|
};
|
|
234
236
|
};
|
|
235
|
-
const closed_ = await closure(d0, query, W, offer, (before, after) => {
|
|
237
|
+
const closed_ = await closure(d0, query, W, offer, (before, after, witnesses) => {
|
|
236
238
|
if (ctx.meter) {
|
|
239
|
+
ctx.meter.reasonCarriedBytes += witnesses.reduce((n, w) => n + (w.window ? w.window[1] - w.window[0] : 0), 0);
|
|
237
240
|
ctx.meter.closureDrainedBytes += unaccountedBytes(before.remainder) -
|
|
238
241
|
unaccountedBytes(after.remainder);
|
|
239
242
|
}
|
|
@@ -251,6 +254,10 @@ export async function reason(ctx, query, d0, preConsumed, pre, voiced = []) {
|
|
|
251
254
|
ctx.trace?.step("pivotStep", [rItem(p.cur, "answer"), rNode(ctx, p.pivot, "pivot")], [rItem(p.fc, "answer", resolve(ctx, p.fc) ?? undefined)], "pivot on the shared span this answer contains, then step forward across that fact");
|
|
252
255
|
}
|
|
253
256
|
}, (at) => {
|
|
257
|
+
// THE LAW REFUSED — counted where it happened, before the bookkeeping
|
|
258
|
+
// below decides whether there is a step to report.
|
|
259
|
+
if (ctx.meter)
|
|
260
|
+
ctx.meter.lawRejects += 1;
|
|
254
261
|
const p = pending;
|
|
255
262
|
pending = null;
|
|
256
263
|
if (p === null || p.kind !== "pivot")
|
|
@@ -279,7 +286,7 @@ export async function reason(ctx, query, d0, preConsumed, pre, voiced = []) {
|
|
|
279
286
|
// over the tail of the state's list — and computed ONLY when a meter is
|
|
280
287
|
// attached: this used to slice and MAP a fresh array on every response, for a
|
|
281
288
|
// counter that usually does not exist. One allocation, under the meter.
|
|
282
|
-
ctx.meter.
|
|
289
|
+
ctx.meter.reasonAccountedBytes += unaccountedBytes(closed_.accounted.slice(d0.accounted.length));
|
|
283
290
|
}
|
|
284
291
|
t?.done([rItem(closed_.product, "answer", resolve(ctx, closed_.product) ?? undefined)],
|
|
285
292
|
// A FIXPOINT: no further step was offered, or the law refused the one that
|
|
@@ -489,6 +496,7 @@ primarySpans = []) {
|
|
|
489
496
|
return { ...state, product: out };
|
|
490
497
|
const fused = advance(state, fusedT, fusedWitness);
|
|
491
498
|
if (ctx.meter) {
|
|
499
|
+
ctx.meter.reasonCarriedBytes += fusedWitness.reduce((n, w) => n + (w.window ? w.window[1] - w.window[0] : 0), 0);
|
|
492
500
|
ctx.meter.closureDrainedBytes += unaccountedBytes(state.remainder) -
|
|
493
501
|
unaccountedBytes(fused.remainder);
|
|
494
502
|
}
|
package/docs/INDEX.md
CHANGED
|
@@ -36,7 +36,7 @@ proof in `test/` (pins that fail when the law is broken).
|
|
|
36
36
|
| 11 | `docs/architecture/memoization.md` | `Precomputed` is per-response lazy cache (promise-cached async); `beginResponse`/`endResponse` lifecycle | `test/42` |
|
|
37
37
|
| 12 | `docs/architecture/saturation.md` | Every walk names a deciding saturation beside its cap; cap is safety net, not decision | `test/27`, `test/16` |
|
|
38
38
|
| 13 | `docs/architecture/meter.md` | `meter.ts` is write-only work accounting; counts are exact, phases nest | `test/55` |
|
|
39
|
-
| 14 | `docs/architecture/closure.md` | A derivation is closed when its structure accounts for the question's remainder; every transition asks that law | `test/133`–`
|
|
39
|
+
| 14 | `docs/architecture/closure.md` | A derivation is closed when its structure accounts for the question's remainder; every transition asks that law | `test/133`–`143` |
|
|
40
40
|
|
|
41
41
|
## Mechanisms (8)
|
|
42
42
|
|
package/docs/INVARIANTS.md
CHANGED
|
@@ -15,4 +15,4 @@
|
|
|
15
15
|
| 11 | Honest degradation | `src/mind/pipeline.ts:weight=moves+PASS*unaccounted` `src/store.ts:BoundedMap:miss→re-derive` | `test/28` `test/84` | `store.md`+`caches.md` |
|
|
16
16
|
| 12 | Meter contracts | `src/meter.ts:Meter,PhaseCost,time` `src/mind/pipeline-mechanism.ts:Precomputed.shared` | `test/55` | `meter.md` |
|
|
17
17
|
| 13 | Saturation | `traverse.ts:edgeAncestors,types.ts:SaturationReason` `src/mind/junction.ts:junctionContainersFrom` `src/mind/resonance.ts:pivotInto` | `test/27` `test/16` | `saturation.md` |
|
|
18
|
-
| 14 | Closure | `src/mind/derivation.ts:closed,admissible,advance` | `test/133`–`
|
|
18
|
+
| 14 | Closure | `src/mind/derivation.ts:closed,admissible,advance` | `test/133`–`143` | `closure.md` |
|
|
@@ -23,6 +23,14 @@ field, no producer field, and no count of any kind. The witnesses `contains` and
|
|
|
23
23
|
`moves` are the LAYER's: it holds the structure and hands them in, so the law
|
|
24
24
|
never probes the store.
|
|
25
25
|
|
|
26
|
+
## The boundary
|
|
27
|
+
|
|
28
|
+
`product` (what it stands on, its identity being `resolve(product)`),
|
|
29
|
+
`accounted` (the price its steps summed) and `remainder` (the question's debt,
|
|
30
|
+
at one quantum) are the derivation's own. What it REACHED and what it SPENT are
|
|
31
|
+
the layer's (`reaches`, `consumed`), never a field here: the law decides
|
|
32
|
+
admission and consumption, the layer decides frontier and termination.
|
|
33
|
+
|
|
26
34
|
## The transition
|
|
27
35
|
|
|
28
36
|
`admissible(state, continuation, query, W)` returns the witnesses the step pays
|
|
@@ -33,6 +41,10 @@ consumes what its window proves. Whole-span draining was refuted by `test/110`,
|
|
|
33
41
|
the window alone by `test/138`. The state is BORN owing what its product does
|
|
34
42
|
not carry.
|
|
35
43
|
|
|
44
|
+
The walk ends when the layer stops offering or the law refuses; owning no
|
|
45
|
+
search, the law cannot prevent a cycle — that is the layer's, over the structure
|
|
46
|
+
it holds.
|
|
47
|
+
|
|
36
48
|
## One cost home
|
|
37
49
|
|
|
38
50
|
`moves + PASS · unaccountedBytes` is computed in ONE place (`pipeline.ts`,
|
|
@@ -60,6 +72,7 @@ vocabulary, not the tracer's.
|
|
|
60
72
|
## Pins
|
|
61
73
|
|
|
62
74
|
- `test/133`–`137` — the law's home, readings, refusal, limits.
|
|
63
|
-
- `test/138` — a cycle cannot close it
|
|
75
|
+
- `test/138` — a cycle cannot close it, a carried move can.
|
|
64
76
|
- `test/139` — carrying is ENGAGEMENT, not explanation.
|
|
65
|
-
- `test/140` — irrelevant supply changes
|
|
77
|
+
- `test/140` — irrelevant supply changes nothing.
|
|
78
|
+
- `test/142`, `test/143` — the offer's contract, and the cycles.
|
package/docs/harness/gates.md
CHANGED
|
@@ -13,7 +13,7 @@ Guards honest silence, determinism, and every pinned contract. Silence:
|
|
|
13
13
|
unrelated queries ground to nothing (`test/28`, `50`, `56`, `67`, `76`, `84`).
|
|
14
14
|
Determinism: same seed + deposit order + query gives byte-identical answer
|
|
15
15
|
(`test/20`). Every invariant is pinned, the closure law included
|
|
16
|
-
(`test/133`–`
|
|
16
|
+
(`test/133`–`143`). §14–25 (pipeline), §64 (derived thresholds), AGENTS.md §2
|
|
17
17
|
invariants 1–5.
|
|
18
18
|
|
|
19
19
|
## 2 — Work accounting (profiler)
|
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/meter.ts
CHANGED
|
@@ -267,15 +267,30 @@ export class Meter {
|
|
|
267
267
|
* not computable at all. With it, the price of extending the answer is
|
|
268
268
|
* `reasonSteps · STEP`, the ladder's own value for following an edge. */
|
|
269
269
|
reasonSteps = 0;
|
|
270
|
-
/** Bytes
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
270
|
+
/** Bytes the admitted steps ACCOUNTED for — the witnesses' spans, summed by the
|
|
271
|
+
* law's own function over the tail of the state's accounting. It is NOT what
|
|
272
|
+
* they carried: a step admitted by `reaches` may declare spans it holds no
|
|
273
|
+
* window of, and then it accounts without carrying and consumes nothing (see
|
|
274
|
+
* {@link closureDrainedBytes}). Read with {@link reasonSteps}: one is the
|
|
275
|
+
* price, the other the accounting. */
|
|
276
|
+
reasonAccountedBytes = 0;
|
|
277
|
+
/** Bytes of the QUESTION's own material an admitted step CARRIED — the windows
|
|
278
|
+
* of the remainder its product holds, read by the law's one reading. The
|
|
279
|
+
* accounting may be larger (a `reaches` step declares spans it holds no window
|
|
280
|
+
* of) and the consumption is the window of a step that also reaches, so the
|
|
281
|
+
* three form a lattice: consumed ⊆ carried ⊆ accounted. */
|
|
275
282
|
reasonCarriedBytes = 0;
|
|
283
|
+
/** Times the layer OFFERED a continuation to the law. Read with
|
|
284
|
+
* {@link lawRejects}: the offers the law refused are the Model X contract being
|
|
285
|
+
* exercised, and `offerRuns - lawRejects` is the accepted transitions. */
|
|
286
|
+
offerRuns = 0;
|
|
287
|
+
/** Times the law REFUSED the continuation the layer offered. A refusal is
|
|
288
|
+
* terminal by the layer's contract, so this counts the moments the contract
|
|
289
|
+
* mattered — zero means the walk never needed it. */
|
|
290
|
+
lawRejects = 0;
|
|
276
291
|
/** Bytes of the question's REMAINDER a step CONSUMED — the drop the law's own
|
|
277
292
|
* `advance` makes when a declared move carries the material it accounts for.
|
|
278
|
-
* Read with {@link reasonSteps} and {@link
|
|
293
|
+
* Read with {@link reasonSteps} and {@link reasonAccountedBytes}: carrying is
|
|
279
294
|
* the engagement, this is the consumption, and before it the second was
|
|
280
295
|
* invisible. */
|
|
281
296
|
closureDrainedBytes = 0;
|
package/src/mind/derivation.ts
CHANGED
|
@@ -454,7 +454,11 @@ export async function closure(
|
|
|
454
454
|
W: number,
|
|
455
455
|
offer: Offer,
|
|
456
456
|
/** Called for each step the law admits, with the state before and after. */
|
|
457
|
-
onTaken?: (
|
|
457
|
+
onTaken?: (
|
|
458
|
+
before: DerivationState,
|
|
459
|
+
after: DerivationState,
|
|
460
|
+
witnesses: ReadonlyArray<Witness>,
|
|
461
|
+
) => void,
|
|
458
462
|
/** Called when the law refused the continuation the layer offered. */
|
|
459
463
|
onRefused?: (at: DerivationState) => void,
|
|
460
464
|
): Promise<DerivationState> {
|
|
@@ -467,7 +471,7 @@ export async function closure(
|
|
|
467
471
|
return d;
|
|
468
472
|
}
|
|
469
473
|
const next = advance(d, t, explains);
|
|
470
|
-
onTaken?.(d, next);
|
|
474
|
+
onTaken?.(d, next, explains);
|
|
471
475
|
d = next;
|
|
472
476
|
}
|
|
473
477
|
}
|
package/src/mind/reasoning.ts
CHANGED
|
@@ -206,6 +206,7 @@ export async function reason(
|
|
|
206
206
|
| null = null;
|
|
207
207
|
|
|
208
208
|
const offer: Offer = async (d) => {
|
|
209
|
+
if (ctx.meter) ctx.meter.offerRuns += 1;
|
|
209
210
|
const cur = d.product;
|
|
210
211
|
// The first step's `cur` IS the grounding's product, so the guard above
|
|
211
212
|
// already resolved it and read its reverse edges — reuse both.
|
|
@@ -268,8 +269,12 @@ export async function reason(
|
|
|
268
269
|
query,
|
|
269
270
|
W,
|
|
270
271
|
offer,
|
|
271
|
-
(before, after) => {
|
|
272
|
+
(before, after, witnesses) => {
|
|
272
273
|
if (ctx.meter) {
|
|
274
|
+
ctx.meter.reasonCarriedBytes += witnesses.reduce(
|
|
275
|
+
(n, w) => n + (w.window ? w.window[1] - w.window[0] : 0),
|
|
276
|
+
0,
|
|
277
|
+
);
|
|
273
278
|
ctx.meter.closureDrainedBytes += unaccountedBytes(before.remainder) -
|
|
274
279
|
unaccountedBytes(after.remainder);
|
|
275
280
|
}
|
|
@@ -295,6 +300,9 @@ export async function reason(
|
|
|
295
300
|
}
|
|
296
301
|
},
|
|
297
302
|
(at) => {
|
|
303
|
+
// THE LAW REFUSED — counted where it happened, before the bookkeeping
|
|
304
|
+
// below decides whether there is a step to report.
|
|
305
|
+
if (ctx.meter) ctx.meter.lawRejects += 1;
|
|
298
306
|
const p = pending;
|
|
299
307
|
pending = null;
|
|
300
308
|
if (p === null || p.kind !== "pivot") return;
|
|
@@ -329,7 +337,7 @@ export async function reason(
|
|
|
329
337
|
// over the tail of the state's list — and computed ONLY when a meter is
|
|
330
338
|
// attached: this used to slice and MAP a fresh array on every response, for a
|
|
331
339
|
// counter that usually does not exist. One allocation, under the meter.
|
|
332
|
-
ctx.meter.
|
|
340
|
+
ctx.meter.reasonAccountedBytes += unaccountedBytes(
|
|
333
341
|
closed_.accounted.slice(d0.accounted.length),
|
|
334
342
|
);
|
|
335
343
|
}
|
|
@@ -570,6 +578,10 @@ export async function fuseAttention(
|
|
|
570
578
|
if (fusedWitness === null) return { ...state, product: out };
|
|
571
579
|
const fused = advance(state, fusedT, fusedWitness);
|
|
572
580
|
if (ctx.meter) {
|
|
581
|
+
ctx.meter.reasonCarriedBytes += fusedWitness.reduce(
|
|
582
|
+
(n, w) => n + (w.window ? w.window[1] - w.window[0] : 0),
|
|
583
|
+
0,
|
|
584
|
+
);
|
|
573
585
|
ctx.meter.closureDrainedBytes += unaccountedBytes(state.remainder) -
|
|
574
586
|
unaccountedBytes(fused.remainder);
|
|
575
587
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
//
|
|
10
10
|
// MEASURED BEFORE THIS TEST EXISTED, one call per size:
|
|
11
11
|
//
|
|
12
|
-
// N= 250 / 500 / 1000 / 2000 ⇒ reasonSteps 1,
|
|
12
|
+
// N= 250 / 500 / 1000 / 2000 ⇒ reasonSteps 1, reasonAccountedBytes 11,
|
|
13
13
|
// pops 338 (k = 0.000), same answer
|
|
14
14
|
//
|
|
15
15
|
// THE BARS ARE THE REPO'S OWN (trap 4 — no new numbers): test/89 asserts k < 1 for
|
|
@@ -72,7 +72,7 @@ async function measure(n) {
|
|
|
72
72
|
return {
|
|
73
73
|
answer,
|
|
74
74
|
steps: c.reasonSteps ?? 0,
|
|
75
|
-
carried: c.
|
|
75
|
+
carried: c.reasonAccountedBytes ?? 0,
|
|
76
76
|
pops: c.searchPops ?? 0,
|
|
77
77
|
};
|
|
78
78
|
}
|
|
@@ -91,7 +91,7 @@ test("a bigger corpus does not buy more extension work for the same answer", asy
|
|
|
91
91
|
steps.push(r.steps);
|
|
92
92
|
answers.push(r.answer);
|
|
93
93
|
console.log(
|
|
94
|
-
` N=${n} → reasonSteps ${r.steps},
|
|
94
|
+
` N=${n} → reasonSteps ${r.steps}, reasonAccountedBytes ${r.carried}, ` +
|
|
95
95
|
`pops ${r.pops}`,
|
|
96
96
|
);
|
|
97
97
|
}
|
|
@@ -124,7 +124,7 @@ test("136.2 the extension is not priced — and it pays for itself", async () =>
|
|
|
124
124
|
// would refuse the IDENTITY steps the law admits, which is the documented
|
|
125
125
|
// failure of the (c) remedy (material lost: "hot" for "cold hot").
|
|
126
126
|
assert.ok(
|
|
127
|
-
(counters.
|
|
127
|
+
(counters.reasonAccountedBytes ?? 0) * PASS >
|
|
128
128
|
(counters.reasonSteps ?? 0) * STEP,
|
|
129
129
|
"the extension is carrying less than its steps are worth — measured 11 bytes " +
|
|
130
130
|
"against 1 step here. If this ever fails, the absent price comparison HAS " +
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// 142 — the layer offers only what the law admits, which is what makes a refusal final.
|
|
2
|
+
//
|
|
3
|
+
// THE CONTRACT THIS PINS. `closure` stops when the law refuses the continuation the layer
|
|
4
|
+
// offered, and reads that refusal as "no continuation exists". That is valid only under
|
|
5
|
+
// ONE of two architectures: the layer searches for an admissible continuation and offers
|
|
6
|
+
// only a valid one (X), rather than offering candidates for the law to filter (Y). Sema is
|
|
7
|
+
// X — the producer tries the forward absorb first and reaches for the pivot only when the
|
|
8
|
+
// absorb's guards fail, returning null when neither exists — so the one offer the law can
|
|
9
|
+
// still refuse is the pivot without ownership, and it is the layer's last.
|
|
10
|
+
//
|
|
11
|
+
// 142.1 pins the premise: the law CAN refuse (a continuation that neither carries question
|
|
12
|
+
// material nor declares a move), and admits the two species. Without a refusal the contract
|
|
13
|
+
// would be vacuous. 142.2 pins the consequence in the engine: the walk ends at that refusal
|
|
14
|
+
// and the satisfying answer is untouched.
|
|
15
|
+
|
|
16
|
+
import { test } from "node:test";
|
|
17
|
+
import assert from "node:assert/strict";
|
|
18
|
+
import { Mind } from "../dist/src/index.js";
|
|
19
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
20
|
+
import { admissible } from "../dist/src/mind/derivation.js";
|
|
21
|
+
|
|
22
|
+
const QUERY = new TextEncoder().encode("ABCDEFGHIJ");
|
|
23
|
+
const W = 4;
|
|
24
|
+
const state = () => ({
|
|
25
|
+
product: new Uint8Array(0),
|
|
26
|
+
accounted: [],
|
|
27
|
+
remainder: [[0, 10]],
|
|
28
|
+
cost: 0,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("142.1 the law refuses what neither carries nor reaches, and admits both species", () => {
|
|
32
|
+
// neither: no window held, no move declared
|
|
33
|
+
assert.equal(
|
|
34
|
+
admissible(
|
|
35
|
+
state(),
|
|
36
|
+
{ product: new TextEncoder().encode("Paris"), contains: true, cost: 1 },
|
|
37
|
+
QUERY,
|
|
38
|
+
W,
|
|
39
|
+
),
|
|
40
|
+
null,
|
|
41
|
+
"an offer that neither carries nor reaches is refused — so offering one is not free",
|
|
42
|
+
);
|
|
43
|
+
// carries: the product holds a window of the remainder
|
|
44
|
+
assert.notEqual(
|
|
45
|
+
admissible(
|
|
46
|
+
state(),
|
|
47
|
+
{
|
|
48
|
+
product: new TextEncoder().encode("ZZZZABCDZZ"),
|
|
49
|
+
contains: true,
|
|
50
|
+
cost: 1,
|
|
51
|
+
},
|
|
52
|
+
QUERY,
|
|
53
|
+
W,
|
|
54
|
+
),
|
|
55
|
+
null,
|
|
56
|
+
"carrying admits",
|
|
57
|
+
);
|
|
58
|
+
// reaches: a declared move, which needs no question material
|
|
59
|
+
assert.notEqual(
|
|
60
|
+
admissible(
|
|
61
|
+
state(),
|
|
62
|
+
{
|
|
63
|
+
product: new TextEncoder().encode("Paris"),
|
|
64
|
+
contains: true,
|
|
65
|
+
reaches: true,
|
|
66
|
+
cost: 1,
|
|
67
|
+
},
|
|
68
|
+
QUERY,
|
|
69
|
+
W,
|
|
70
|
+
),
|
|
71
|
+
null,
|
|
72
|
+
"a declared move admits on its own ground",
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("142.2 the walk ends at the refusal, with the satisfying answer untouched", async () => {
|
|
77
|
+
const LINKS = [
|
|
78
|
+
["What is the capital of France", "The capital of France is Paris"],
|
|
79
|
+
["Paris", "Paris is famous for the Eiffel Tower"],
|
|
80
|
+
["the Eiffel Tower", "the Eiffel Tower is in Paris"],
|
|
81
|
+
];
|
|
82
|
+
const store = new SQliteStore({ path: ":memory:" });
|
|
83
|
+
const mind = new Mind({ seed: 7, store, profile: true });
|
|
84
|
+
await mind.ingest(LINKS);
|
|
85
|
+
const out = await mind.respond("What is the capital of France famous for");
|
|
86
|
+
const text = new TextDecoder().decode(out.bytes).replace(/\0+/g, "").trim();
|
|
87
|
+
await store.close();
|
|
88
|
+
assert.equal(
|
|
89
|
+
text,
|
|
90
|
+
LINKS[1][1],
|
|
91
|
+
"the layer's last offer was refused, and the answer stands",
|
|
92
|
+
);
|
|
93
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// 143 — cycles, in the engine: three topologies, and what must hold for each.
|
|
2
|
+
//
|
|
3
|
+
// The law holds no `visited`, no `depth`, no `hop`, no `once` and no `cap` — cycle
|
|
4
|
+
// prevention belongs to the layer that controls states and consumption, and a cycle is not
|
|
5
|
+
// closure. So the engine must terminate on each topology anyway, and the answer must be a
|
|
6
|
+
// fact of the corpus rather than a composition typed around the loop.
|
|
7
|
+
//
|
|
8
|
+
// A→B→A · A→B→C→A · A→B, B→C, C→B (a loop with a tail, where the refusal is not trivial).
|
|
9
|
+
// Each fact is filed under both of its ends, the way the store files one, so the walk has
|
|
10
|
+
// every edge the loop needs.
|
|
11
|
+
|
|
12
|
+
import { test } from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { Mind } from "../dist/src/index.js";
|
|
15
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
16
|
+
|
|
17
|
+
const TOPOLOGIES = {
|
|
18
|
+
"A→B→A": [
|
|
19
|
+
["Alice knows Bob", "Alice knows Bob"],
|
|
20
|
+
["Bob knows Alice", "Bob knows Alice"],
|
|
21
|
+
],
|
|
22
|
+
"A→B→C→A": [
|
|
23
|
+
["Alice knows Bob", "Alice knows Bob"],
|
|
24
|
+
["Bob knows Carol", "Bob knows Carol"],
|
|
25
|
+
["Carol knows Alice", "Carol knows Alice"],
|
|
26
|
+
],
|
|
27
|
+
"A→B, B→C, C→B": [
|
|
28
|
+
["Alice knows Bob", "Alice knows Bob"],
|
|
29
|
+
["Bob knows Carol", "Bob knows Carol"],
|
|
30
|
+
["Carol knows Bob", "Carol knows Bob"],
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
async function ask(corpus, query) {
|
|
35
|
+
const store = new SQliteStore({ path: ":memory:" });
|
|
36
|
+
const mind = new Mind({ seed: 7, store, profile: true });
|
|
37
|
+
await mind.ingest(corpus);
|
|
38
|
+
const out = await mind.respond(query);
|
|
39
|
+
const text = new TextDecoder().decode(out.bytes).replace(/\0+/g, "").trim();
|
|
40
|
+
await store.close();
|
|
41
|
+
return text;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const [name, corpus] of Object.entries(TOPOLOGIES)) {
|
|
45
|
+
test(`143 ${name} terminates and answers from the corpus`, async () => {
|
|
46
|
+
const first = await ask(corpus, "Alice knows");
|
|
47
|
+
const second = await ask(corpus, "Alice knows");
|
|
48
|
+
assert.equal(
|
|
49
|
+
second,
|
|
50
|
+
first,
|
|
51
|
+
"two runs agree: no order-dependent wander around the loop",
|
|
52
|
+
);
|
|
53
|
+
if (first.length > 0) {
|
|
54
|
+
assert.ok(
|
|
55
|
+
corpus.some(([, fact]) => first.includes(fact) || fact.includes(first)),
|
|
56
|
+
`the answer is one of the corpus's own facts, not a composition typed around the loop: ${
|
|
57
|
+
JSON.stringify(first)
|
|
58
|
+
}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -419,7 +419,7 @@ test("12. the extension's cost obeys the ladder's own inequality", async () => {
|
|
|
419
419
|
await store.close();
|
|
420
420
|
|
|
421
421
|
const steps = c.reasonSteps ?? 0;
|
|
422
|
-
const carried = c.
|
|
422
|
+
const carried = c.reasonAccountedBytes ?? 0;
|
|
423
423
|
assert.ok(
|
|
424
424
|
steps >= 1,
|
|
425
425
|
`the extension must run for this to mean anything (got ${steps})`,
|
|
@@ -765,7 +765,7 @@ test("18. the remainder the pipeline decides on is visible", async () => {
|
|
|
765
765
|
};
|
|
766
766
|
|
|
767
767
|
// The pivoting query: the reasoner runs, so the remainder is non-empty
|
|
768
|
-
// (measured: reasonSteps 1,
|
|
768
|
+
// (measured: reasonSteps 1, reasonAccountedBytes 11).
|
|
769
769
|
const pivot = await run("What is the capital of France famous for");
|
|
770
770
|
assert.ok(
|
|
771
771
|
(pivot.steps ?? 0) >= 1,
|