@semiont/core 0.5.23 → 0.5.25

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.
@@ -0,0 +1,180 @@
1
+ import { Observable } from 'rxjs';
2
+ import { StateUnit } from '@semiont/core';
3
+ import * as fc from 'fast-check';
4
+ import { FaultyTransport, FaultAction } from '@semiont/core/testing';
5
+
6
+ /**
7
+ * Executable enforcement of the StateUnit pattern — the runtime twin of
8
+ * `packages/sdk/docs/STATE-UNITS.md` and the ledger in
9
+ * `.plans/STATE-UNIT-AXIOMS.md`. The `StateUnit` interface's own comment notes
10
+ * the pattern is convention; this file makes it executable.
11
+ *
12
+ * `assertStateUnitAxioms(spec)` runs every applicable axiom against a factory in
13
+ * one shot, throwing a labeled Error on the first violation (the axiom id is in
14
+ * the message). It is framework-agnostic on purpose — only `rxjs` + `fast-check`,
15
+ * no `vitest` — so it ships through `@semiont/core/testing` and any package's test
16
+ * runner can invoke it from a single `it(...)` per state unit. It lives in core
17
+ * (not sdk) so even packages below sdk (e.g. `http-transport`) can use it without
18
+ * a dependency cycle.
19
+ *
20
+ * Axioms (random-input dimension; fast-check):
21
+ * A5 dispose() is idempotent and total (n ∈ [1,20] calls never throw)
22
+ * A5b post-dispose inertness — every public method is a no-op after dispose
23
+ * A6 every pre-dispose subscriber (k ∈ [1,10]) sees `complete` on dispose
24
+ * X3-runtime instance isolation — driving one instance never moves another's surfaces
25
+ * Structural assertions (single-shot):
26
+ * A1 plain-object identity (no class instance)
27
+ * X1 no raw Subject on the public surface
28
+ * A7-passed disposing the unit must NOT dispose an injected dependency
29
+ * A7-owned disposing the unit MUST dispose its internally-constructed children
30
+ */
31
+
32
+ /**
33
+ * A disposable stand-in for an injected dependency. Pass one as a unit's
34
+ * constructor arg, then list it in `setup().passedIn` so A7-passed can assert
35
+ * the unit never disposed it. Counts calls so A7-passed also holds under the
36
+ * repeated-dispose stress of A5.
37
+ */
38
+ interface DisposeProbe extends StateUnit {
39
+ readonly disposeCount: number;
40
+ }
41
+ declare function disposeProbe(): DisposeProbe;
42
+ type SetupResult<T extends StateUnit> = T | {
43
+ unit: T;
44
+ passedIn?: readonly DisposeProbe[];
45
+ teardown?: () => void;
46
+ };
47
+ interface StateUnitAxiomSpec<T extends StateUnit> {
48
+ /**
49
+ * Build a FRESH unit. Called many times (fast-check re-runs), so it must
50
+ * return an independent instance each call. Return the bare unit, or an object
51
+ * carrying the injected `passedIn` probes (A7-passed) and a `teardown` to
52
+ * release per-instance resources (e.g. a mock bus).
53
+ */
54
+ setup: () => SetupResult<T>;
55
+ /** Owned public Observables — Subjects the unit completes on dispose (A6, X3, post-dispose inertness). */
56
+ surfaces?: (unit: T) => readonly Observable<unknown>[];
57
+ /** Public input methods as zero-arg callers (A5b post-dispose, X3 drive). */
58
+ invocations?: (unit: T) => readonly (() => unknown)[];
59
+ /** Surfaces of internally-constructed children — must complete when the outer disposes (A7-owned). */
60
+ ownedChildSurfaces?: (unit: T) => readonly Observable<unknown>[];
61
+ /** fast-check run budget per property (default 30). */
62
+ numRuns?: number;
63
+ }
64
+ /**
65
+ * Run every applicable axiom against `spec`. Throws a labeled Error on the first
66
+ * violation. Axioms whose accessors are omitted are skipped (e.g. A7-passed
67
+ * runs only when `setup` returns `passedIn`; A6 only when `surfaces` is given).
68
+ */
69
+ declare function assertStateUnitAxioms<T extends StateUnit>(spec: StateUnitAxiomSpec<T>): void;
70
+
71
+ /**
72
+ * Executable enforcement of the liveness axioms — the runtime twin of
73
+ * `.plans/LIVENESS-AXIOMS.md`, and the composition-level sibling of
74
+ * `assertStateUnitAxioms` (state-unit-axioms.ts). Where the StateUnit axioms
75
+ * make *per-unit* wrongness mechanically detectable, these make *silence*
76
+ * detectable: every existing enforcement tier is safety (nothing wrong is
77
+ * delivered); these assert liveness (something is eventually delivered).
78
+ *
79
+ * Axioms (fault-schedule dimension; fast-check):
80
+ * L1 Subscriber liveness — every output emits next|error within the bound,
81
+ * under any fault schedule. Error is a permitted outcome; the forbidden
82
+ * fourth state is pending-forever.
83
+ * L2 Request settlement — every awaited path settles within the bound;
84
+ * re-issues per logical request stay within the retry budget (B14: one);
85
+ * a faulted request must be re-issued or surfaced, never swallowed.
86
+ * L3 Delivery across lifecycle transitions — every event written to a live
87
+ * connection reaches the output exactly once, wherever a client-initiated
88
+ * transition (handover / reconnect / scope change) lands. Retirement is
89
+ * by drain, never by abort (TRANSPORT-HTTP.md, Abort discipline).
90
+ *
91
+ * Framework-agnostic on purpose — only `rxjs` + `fast-check`, no `vitest` — so
92
+ * it ships through `@semiont/core/testing` and any package's test runner can
93
+ * invoke it. Deterministic virtual time: properties pass a small explicit
94
+ * `timeoutMs` to `busRequest`; no `Date.now`, no 30 s real waits.
95
+ */
96
+
97
+ /** What one fresh run of the composition exposes to the axioms. */
98
+ interface LivenessScenario {
99
+ /**
100
+ * Live-query-shaped outputs. The harness subscribes each one; every
101
+ * subscription must see `next` or `error` within the bound (L1).
102
+ */
103
+ outputs: readonly Observable<unknown>[];
104
+ /**
105
+ * Awaited paths. Each promise must settle — resolve or reject — within the
106
+ * bound (L2). Rejections are fine; pending-forever is the violation.
107
+ */
108
+ settlements?: readonly Promise<unknown>[];
109
+ teardown?: () => void;
110
+ }
111
+ interface LivenessAxiomSpec {
112
+ /** Build a FRESH composition wired to the given transport. Called per run. */
113
+ setup: (transport: FaultyTransport) => LivenessScenario | Promise<LivenessScenario>;
114
+ /**
115
+ * The timeoutMs the scenario passes to `busRequest` — the bound is derived
116
+ * from it: (timeoutMs × (1 + retryBudget) + Σdelays) × slackFactor.
117
+ */
118
+ timeoutMs: number;
119
+ /** Max sanctioned re-issues per logical request (B14 budget). Default 1. */
120
+ retryBudget?: number;
121
+ /** Override the generated fault schedules (teeth tests pin one). */
122
+ scheduleArb?: fc.Arbitrary<readonly FaultAction[]>;
123
+ /** Passed through to FaultyTransport (reply synthesis). */
124
+ makeResponse?: (operation: string, payload: Record<string, unknown>) => unknown;
125
+ /** fast-check run budget (default 25 — CI-fast; crank locally). */
126
+ numRuns?: number;
127
+ /** Real-scheduler jitter headroom on the bound (default 4×). */
128
+ slackFactor?: number;
129
+ }
130
+ /** The five wire behaviors, uniformly weighted; delays stay small (≤5 ms). */
131
+ declare function arbFaultAction(): fc.Arbitrary<FaultAction>;
132
+ declare function arbFaultSchedule(maxLength?: number): fc.Arbitrary<readonly FaultAction[]>;
133
+ /**
134
+ * Run L1 + L2 against `spec` across generated fault schedules. Throws a
135
+ * labeled Error (`L1: …` / `L2: …`) on the first violation.
136
+ */
137
+ declare function assertLivenessAxioms(spec: LivenessAxiomSpec): Promise<void>;
138
+ /**
139
+ * A connection-stream-shaped subject: something that accepts writes to the
140
+ * live connection, can be told to transition (handover / reconnect / scope
141
+ * change), and exposes the subscriber-facing output. P3 adapts the real
142
+ * actor's mock-connection harness to this shape; the teeth tests drive
143
+ * reconstructed pre-fix doubles.
144
+ */
145
+ interface DeliverySubject {
146
+ /** Write the event with this id to the currently-live connection. */
147
+ write: (eventId: string) => void;
148
+ /** Client-initiated lifecycle transition. */
149
+ transition: () => void | Promise<void>;
150
+ /** Subscriber-facing output; each emission is a delivered event id. */
151
+ output$: Observable<string>;
152
+ /**
153
+ * Drain pending asynchronous delivery at end of sequence (a live connection
154
+ * eventually flushes). Default: one macrotask tick.
155
+ */
156
+ settle?: () => Promise<void>;
157
+ teardown?: () => void;
158
+ }
159
+ type DeliveryOp = 'write' | 'transition';
160
+ interface DeliveryAxiomSpec {
161
+ /** Build a FRESH subject. Called per run. */
162
+ setup: () => DeliverySubject;
163
+ /** Override the generated op sequences (teeth tests pin one). */
164
+ opsArb?: fc.Arbitrary<readonly DeliveryOp[]>;
165
+ /** Max generated sequence length (default 12). */
166
+ maxOps?: number;
167
+ /** fast-check run budget (default 50 — these runs are cheap). */
168
+ numRuns?: number;
169
+ }
170
+ declare function arbDeliveryOps(maxOps?: number): fc.Arbitrary<readonly DeliveryOp[]>;
171
+ /**
172
+ * Run L3 against `spec` across generated write/transition interleavings.
173
+ * Throws a labeled Error (`L3: …`) on the first violation: an event written
174
+ * to a live connection delivered zero times (lost — retired by abort instead
175
+ * of drain) or more than once (duplicate).
176
+ */
177
+ declare function assertExactlyOnceDelivery(spec: DeliveryAxiomSpec): Promise<void>;
178
+
179
+ export { arbDeliveryOps, arbFaultAction, arbFaultSchedule, assertExactlyOnceDelivery, assertLivenessAxioms, assertStateUnitAxioms, disposeProbe };
180
+ export type { DeliveryAxiomSpec, DeliveryOp, DeliverySubject, DisposeProbe, LivenessAxiomSpec, LivenessScenario, StateUnitAxiomSpec };
@@ -0,0 +1,365 @@
1
+ import { FaultyTransport } from '../chunk-I3ZOWCTH.js';
2
+ import '../chunk-FMFOBVTE.js';
3
+ import * as fc2 from 'fast-check';
4
+ import { Subject } from 'rxjs';
5
+
6
+ function disposeProbe() {
7
+ let n = 0;
8
+ return {
9
+ dispose() {
10
+ n += 1;
11
+ },
12
+ get disposeCount() {
13
+ return n;
14
+ }
15
+ };
16
+ }
17
+ function normalize(r) {
18
+ if (r && typeof r === "object" && "unit" in r) {
19
+ return { unit: r.unit, passedIn: r.passedIn ?? [], teardown: r.teardown ?? (() => {
20
+ }) };
21
+ }
22
+ return { unit: r, passedIn: [], teardown: () => {
23
+ } };
24
+ }
25
+ function swallowAsync(value) {
26
+ if (value && typeof value === "object" && typeof value.then === "function") {
27
+ value.then(void 0, () => {
28
+ });
29
+ }
30
+ }
31
+ function assertStateUnitAxioms(spec) {
32
+ const numRuns = spec.numRuns ?? 30;
33
+ const fresh = () => normalize(spec.setup());
34
+ const run = (axiom, prop) => {
35
+ try {
36
+ fc2.assert(prop, { numRuns });
37
+ } catch (e) {
38
+ throw new Error(`${axiom}: ${e instanceof Error ? e.message : String(e)}`);
39
+ }
40
+ };
41
+ {
42
+ const { unit, teardown } = fresh();
43
+ const proto = Object.getPrototypeOf(unit);
44
+ if (proto !== Object.prototype && proto !== null) {
45
+ throw new Error("A1: state unit is not a plain object (has a class prototype)");
46
+ }
47
+ unit.dispose();
48
+ teardown();
49
+ }
50
+ {
51
+ const { unit, teardown } = fresh();
52
+ for (const [key, value] of Object.entries(unit)) {
53
+ if (value instanceof Subject && value.source === void 0) {
54
+ throw new Error(`X1: public field "${key}" is a raw Subject \u2014 expose it via .asObservable()`);
55
+ }
56
+ }
57
+ unit.dispose();
58
+ teardown();
59
+ }
60
+ {
61
+ const { unit, passedIn, teardown } = fresh();
62
+ unit.dispose();
63
+ passedIn.forEach((probe, i) => {
64
+ if (probe.disposeCount > 0) {
65
+ throw new Error(`A7-passed: the unit disposed injected dependency #${i} (it doesn't own it)`);
66
+ }
67
+ });
68
+ teardown();
69
+ }
70
+ run("A5", fc2.property(fc2.integer({ min: 1, max: 20 }), (n) => {
71
+ const { unit, passedIn, teardown } = fresh();
72
+ for (let i = 0; i < n; i++) unit.dispose();
73
+ passedIn.forEach((probe, i) => {
74
+ if (probe.disposeCount > 0) {
75
+ throw new Error(`injected dependency #${i} disposed under ${n} dispose() calls`);
76
+ }
77
+ });
78
+ teardown();
79
+ }));
80
+ if (spec.surfaces) {
81
+ const surfaces = spec.surfaces;
82
+ run("A6", fc2.property(fc2.integer({ min: 1, max: 10 }), (k) => {
83
+ const { unit, teardown } = fresh();
84
+ const completed = [];
85
+ const subs = surfaces(unit).flatMap(
86
+ (o) => Array.from({ length: k }, () => {
87
+ const idx = completed.push(false) - 1;
88
+ return o.subscribe({ complete: () => {
89
+ completed[idx] = true;
90
+ } });
91
+ })
92
+ );
93
+ unit.dispose();
94
+ completed.forEach((seen, idx) => {
95
+ if (!seen) throw new Error(`a subscriber did not see complete on dispose (k=${k}, sub #${idx})`);
96
+ });
97
+ subs.forEach((s) => s.unsubscribe());
98
+ teardown();
99
+ }));
100
+ }
101
+ if (spec.invocations) {
102
+ const invocations = spec.invocations;
103
+ run("A5b", fc2.property(fc2.array(fc2.nat(), { maxLength: 12 }), (seq) => {
104
+ const { unit, teardown } = fresh();
105
+ const callers = invocations(unit);
106
+ unit.dispose();
107
+ for (const raw of seq) {
108
+ if (callers.length === 0) break;
109
+ swallowAsync(callers[raw % callers.length]());
110
+ }
111
+ teardown();
112
+ }));
113
+ }
114
+ if (spec.surfaces && spec.invocations) {
115
+ const surfaces = spec.surfaces;
116
+ const invocations = spec.invocations;
117
+ run("X3-runtime", fc2.property(fc2.array(fc2.nat(), { minLength: 1, maxLength: 8 }), (seq) => {
118
+ const a = fresh();
119
+ const b = fresh();
120
+ const bSurfaces = surfaces(b.unit);
121
+ const bCounts = bSurfaces.map(() => 0);
122
+ const bSubs = bSurfaces.map((o, i) => o.subscribe(() => {
123
+ bCounts[i] += 1;
124
+ }));
125
+ const baseline = bCounts.slice();
126
+ const aCallers = invocations(a.unit);
127
+ for (const raw of seq) {
128
+ if (aCallers.length === 0) break;
129
+ swallowAsync(aCallers[raw % aCallers.length]());
130
+ }
131
+ bCounts.forEach((count, i) => {
132
+ if (count !== baseline[i]) {
133
+ throw new Error(`driving instance A perturbed instance B's surface #${i}`);
134
+ }
135
+ });
136
+ bSubs.forEach((s) => s.unsubscribe());
137
+ a.unit.dispose();
138
+ b.unit.dispose();
139
+ a.teardown();
140
+ b.teardown();
141
+ }));
142
+ }
143
+ if (spec.ownedChildSurfaces) {
144
+ const { unit, teardown } = fresh();
145
+ const childSurfaces = spec.ownedChildSurfaces(unit);
146
+ const completed = childSurfaces.map(() => false);
147
+ const subs = childSurfaces.map((o, i) => o.subscribe({ complete: () => {
148
+ completed[i] = true;
149
+ } }));
150
+ unit.dispose();
151
+ completed.forEach((seen, i) => {
152
+ if (!seen) throw new Error(`A7-owned: owned child surface #${i} did not complete on outer dispose`);
153
+ });
154
+ subs.forEach((s) => s.unsubscribe());
155
+ teardown();
156
+ }
157
+ if (spec.surfaces) {
158
+ const { unit, teardown } = fresh();
159
+ unit.dispose();
160
+ spec.surfaces(unit).forEach((o, i) => {
161
+ let nexts = 0;
162
+ let done = false;
163
+ o.subscribe({ next: () => {
164
+ nexts += 1;
165
+ }, complete: () => {
166
+ done = true;
167
+ } }).unsubscribe();
168
+ if (nexts > 0) throw new Error(`A5b/inert: owned surface #${i} emitted a value after dispose`);
169
+ if (!done) throw new Error(`A5b/inert: owned surface #${i} did not complete after dispose`);
170
+ });
171
+ teardown();
172
+ }
173
+ }
174
+ function arbFaultAction() {
175
+ return fc2.oneof(
176
+ fc2.constant({ kind: "deliver" }),
177
+ fc2.constant({ kind: "drop-reply" }),
178
+ fc2.integer({ min: 0, max: 5 }).map((ms) => ({ kind: "delay", ms })),
179
+ fc2.constant({ kind: "duplicate-reply" }),
180
+ fc2.constant({ kind: "reject-emit" })
181
+ );
182
+ }
183
+ function arbFaultSchedule(maxLength = 8) {
184
+ return fc2.array(arbFaultAction(), { minLength: 1, maxLength });
185
+ }
186
+ function describeSchedule(schedule) {
187
+ return schedule.map((a) => a.kind === "delay" ? `delay(${a.ms})` : a.kind).join(",");
188
+ }
189
+ function sleep(ms) {
190
+ return new Promise((resolve) => setTimeout(resolve, ms));
191
+ }
192
+ async function runLabeled(fallback, slot, assertion) {
193
+ try {
194
+ await assertion();
195
+ } catch (e) {
196
+ const detail = e instanceof Error ? e.message : String(e);
197
+ if (slot.violation) throw new Error(`${slot.violation.message}
198
+ ${detail}`);
199
+ throw new Error(`${fallback}: ${detail}`);
200
+ }
201
+ }
202
+ function capturing(slot, block) {
203
+ try {
204
+ block();
205
+ } catch (e) {
206
+ slot.violation = e instanceof Error ? e : new Error(String(e));
207
+ throw e;
208
+ }
209
+ }
210
+ async function assertLivenessAxioms(spec) {
211
+ const retryBudget = spec.retryBudget ?? 1;
212
+ const slack = spec.slackFactor ?? 4;
213
+ const scheduleArb = spec.scheduleArb ?? arbFaultSchedule();
214
+ const slot = { violation: null };
215
+ await runLabeled(
216
+ "liveness",
217
+ slot,
218
+ () => fc2.assert(
219
+ fc2.asyncProperty(scheduleArb, async (schedule) => {
220
+ const delayTotal = schedule.reduce((n, a) => n + (a.kind === "delay" ? a.ms : 0), 0);
221
+ const bound = (spec.timeoutMs * (1 + retryBudget) + delayTotal) * slack;
222
+ const transport = new FaultyTransport({
223
+ schedule,
224
+ ...spec.makeResponse ? { makeResponse: spec.makeResponse } : {}
225
+ });
226
+ const scenario = await spec.setup(transport);
227
+ const outputs = scenario.outputs;
228
+ const settlements = scenario.settlements ?? [];
229
+ const notified = outputs.map(() => false);
230
+ const settled = settlements.map(() => false);
231
+ let doneResolve = () => {
232
+ };
233
+ const allDone = new Promise((resolve) => {
234
+ doneResolve = resolve;
235
+ });
236
+ const check = () => {
237
+ if (notified.every(Boolean) && settled.every(Boolean)) doneResolve();
238
+ };
239
+ const subs = outputs.map(
240
+ (o, i) => o.subscribe({
241
+ next: () => {
242
+ notified[i] = true;
243
+ check();
244
+ },
245
+ error: () => {
246
+ notified[i] = true;
247
+ check();
248
+ }
249
+ })
250
+ );
251
+ settlements.forEach((p, i) => {
252
+ p.then(
253
+ () => {
254
+ settled[i] = true;
255
+ check();
256
+ },
257
+ () => {
258
+ settled[i] = true;
259
+ check();
260
+ }
261
+ );
262
+ });
263
+ check();
264
+ await Promise.race([allDone, sleep(bound)]);
265
+ try {
266
+ capturing(slot, () => {
267
+ settled.forEach((seen, i) => {
268
+ if (!seen) {
269
+ throw new Error(
270
+ `L2: settlement #${i} did not settle within ${bound}ms under schedule \u27E8${describeSchedule(schedule)}\u27E9 \u2014 the forbidden fourth state`
271
+ );
272
+ }
273
+ });
274
+ const issues = /* @__PURE__ */ new Map();
275
+ for (const entry of transport.requestLog) {
276
+ const prior = issues.get(entry.retryKey) ?? { count: 0, lastFaulted: false };
277
+ issues.set(entry.retryKey, {
278
+ count: prior.count + 1,
279
+ lastFaulted: entry.action.kind === "drop-reply" || entry.action.kind === "reject-emit"
280
+ });
281
+ }
282
+ for (const [key, { count, lastFaulted }] of issues) {
283
+ if (count > 1 + retryBudget) {
284
+ throw new Error(
285
+ `L2: request \u27E8${key}\u27E9 issued ${count} times \u2014 exceeds the retry budget (1 + ${retryBudget}) under schedule \u27E8${describeSchedule(schedule)}\u27E9`
286
+ );
287
+ }
288
+ if (lastFaulted && count <= retryBudget && outputs.length > 0 && !notified.some(Boolean)) {
289
+ throw new Error(
290
+ `L2: faulted request \u27E8${key}\u27E9 was neither re-issued nor surfaced under schedule \u27E8${describeSchedule(schedule)}\u27E9 \u2014 rejection swallowed`
291
+ );
292
+ }
293
+ }
294
+ notified.forEach((seen, i) => {
295
+ if (!seen) {
296
+ throw new Error(
297
+ `L1: output #${i} received no next/error within ${bound}ms under schedule \u27E8${describeSchedule(schedule)}\u27E9 \u2014 silently pending`
298
+ );
299
+ }
300
+ });
301
+ });
302
+ } finally {
303
+ subs.forEach((s) => s.unsubscribe());
304
+ scenario.teardown?.();
305
+ transport.dispose();
306
+ }
307
+ }),
308
+ { numRuns: spec.numRuns ?? 25 }
309
+ )
310
+ );
311
+ }
312
+ function arbDeliveryOps(maxOps = 12) {
313
+ return fc2.array(fc2.constantFrom("write", "transition"), { minLength: 1, maxLength: maxOps }).map((ops) => ops.includes("write") ? ops : [...ops, "write"]);
314
+ }
315
+ async function assertExactlyOnceDelivery(spec) {
316
+ const opsArb = spec.opsArb ?? arbDeliveryOps(spec.maxOps ?? 12);
317
+ const slot = { violation: null };
318
+ await runLabeled(
319
+ "L3",
320
+ slot,
321
+ () => fc2.assert(
322
+ fc2.asyncProperty(opsArb, async (ops) => {
323
+ const subject = spec.setup();
324
+ const delivered = [];
325
+ const sub = subject.output$.subscribe((id) => delivered.push(id));
326
+ const written = [];
327
+ try {
328
+ for (let i = 0; i < ops.length; i++) {
329
+ if (ops[i] === "write") {
330
+ const id = `e${i}`;
331
+ written.push(id);
332
+ subject.write(id);
333
+ } else {
334
+ await subject.transition();
335
+ }
336
+ }
337
+ await (subject.settle?.() ?? sleep(0));
338
+ capturing(slot, () => {
339
+ for (const id of written) {
340
+ const n = delivered.filter((d) => d === id).length;
341
+ if (n === 0) {
342
+ throw new Error(
343
+ `L3: event \u27E8${id}\u27E9 delivered 0 times under \u27E8${ops.join(",")}\u27E9 \u2014 lost across a transition (retire by drain, never by abort)`
344
+ );
345
+ }
346
+ if (n > 1) {
347
+ throw new Error(
348
+ `L3: event \u27E8${id}\u27E9 delivered ${n} times under \u27E8${ops.join(",")}\u27E9 \u2014 duplicate delivery`
349
+ );
350
+ }
351
+ }
352
+ });
353
+ } finally {
354
+ sub.unsubscribe();
355
+ subject.teardown?.();
356
+ }
357
+ }),
358
+ { numRuns: spec.numRuns ?? 50 }
359
+ )
360
+ );
361
+ }
362
+
363
+ export { arbDeliveryOps, arbFaultAction, arbFaultSchedule, assertExactlyOnceDelivery, assertLivenessAxioms, assertStateUnitAxioms, disposeProbe };
364
+ //# sourceMappingURL=axioms.js.map
365
+ //# sourceMappingURL=axioms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/state-unit-axioms.ts","../../src/liveness-axioms.ts"],"names":["fc"],"mappings":"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","file":"axioms.js","sourcesContent":["/**\n * Executable enforcement of the StateUnit pattern — the runtime twin of\n * `packages/sdk/docs/STATE-UNITS.md` and the ledger in\n * `.plans/STATE-UNIT-AXIOMS.md`. The `StateUnit` interface's own comment notes\n * the pattern is convention; this file makes it executable.\n *\n * `assertStateUnitAxioms(spec)` runs every applicable axiom against a factory in\n * one shot, throwing a labeled Error on the first violation (the axiom id is in\n * the message). It is framework-agnostic on purpose — only `rxjs` + `fast-check`,\n * no `vitest` — so it ships through `@semiont/core/testing` and any package's test\n * runner can invoke it from a single `it(...)` per state unit. It lives in core\n * (not sdk) so even packages below sdk (e.g. `http-transport`) can use it without\n * a dependency cycle.\n *\n * Axioms (random-input dimension; fast-check):\n * A5 dispose() is idempotent and total (n ∈ [1,20] calls never throw)\n * A5b post-dispose inertness — every public method is a no-op after dispose\n * A6 every pre-dispose subscriber (k ∈ [1,10]) sees `complete` on dispose\n * X3-runtime instance isolation — driving one instance never moves another's surfaces\n * Structural assertions (single-shot):\n * A1 plain-object identity (no class instance)\n * X1 no raw Subject on the public surface\n * A7-passed disposing the unit must NOT dispose an injected dependency\n * A7-owned disposing the unit MUST dispose its internally-constructed children\n */\n\nimport * as fc from 'fast-check';\nimport { Subject, type Observable } from 'rxjs';\nimport type { StateUnit } from './state-unit';\n\n/**\n * A disposable stand-in for an injected dependency. Pass one as a unit's\n * constructor arg, then list it in `setup().passedIn` so A7-passed can assert\n * the unit never disposed it. Counts calls so A7-passed also holds under the\n * repeated-dispose stress of A5.\n */\nexport interface DisposeProbe extends StateUnit {\n readonly disposeCount: number;\n}\n\nexport function disposeProbe(): DisposeProbe {\n let n = 0;\n return {\n dispose() { n += 1; },\n get disposeCount() { return n; },\n };\n}\n\ntype SetupResult<T extends StateUnit> =\n | T\n | { unit: T; passedIn?: readonly DisposeProbe[]; teardown?: () => void };\n\nexport interface StateUnitAxiomSpec<T extends StateUnit> {\n /**\n * Build a FRESH unit. Called many times (fast-check re-runs), so it must\n * return an independent instance each call. Return the bare unit, or an object\n * carrying the injected `passedIn` probes (A7-passed) and a `teardown` to\n * release per-instance resources (e.g. a mock bus).\n */\n setup: () => SetupResult<T>;\n /** Owned public Observables — Subjects the unit completes on dispose (A6, X3, post-dispose inertness). */\n surfaces?: (unit: T) => readonly Observable<unknown>[];\n /** Public input methods as zero-arg callers (A5b post-dispose, X3 drive). */\n invocations?: (unit: T) => readonly (() => unknown)[];\n /** Surfaces of internally-constructed children — must complete when the outer disposes (A7-owned). */\n ownedChildSurfaces?: (unit: T) => readonly Observable<unknown>[];\n /** fast-check run budget per property (default 30). */\n numRuns?: number;\n}\n\ninterface Normalized<T extends StateUnit> {\n unit: T;\n passedIn: readonly DisposeProbe[];\n teardown: () => void;\n}\n\nfunction normalize<T extends StateUnit>(r: SetupResult<T>): Normalized<T> {\n if (r && typeof r === 'object' && 'unit' in r) {\n return { unit: r.unit, passedIn: r.passedIn ?? [], teardown: r.teardown ?? (() => {}) };\n }\n return { unit: r as T, passedIn: [], teardown: () => {} };\n}\n\nfunction swallowAsync(value: unknown): void {\n if (value && typeof value === 'object' && typeof (value as { then?: unknown }).then === 'function') {\n (value as Promise<unknown>).then(undefined, () => {});\n }\n}\n\n/**\n * Run every applicable axiom against `spec`. Throws a labeled Error on the first\n * violation. Axioms whose accessors are omitted are skipped (e.g. A7-passed\n * runs only when `setup` returns `passedIn`; A6 only when `surfaces` is given).\n */\nexport function assertStateUnitAxioms<T extends StateUnit>(spec: StateUnitAxiomSpec<T>): void {\n const numRuns = spec.numRuns ?? 30;\n const fresh = (): Normalized<T> => normalize(spec.setup());\n\n // fast-check's default falsification message is just \"Property failed after N\n // tests {seed}\" — it drops the thrown axiom id. Prepend the axiom so a real\n // failure names itself; the seed/shrink detail is preserved.\n const run = (axiom: string, prop: Parameters<typeof fc.assert>[0]): void => {\n try {\n fc.assert(prop, { numRuns });\n } catch (e) {\n throw new Error(`${axiom}: ${e instanceof Error ? e.message : String(e)}`);\n }\n };\n\n // A1 — plain-object identity (no class instance).\n {\n const { unit, teardown } = fresh();\n const proto = Object.getPrototypeOf(unit);\n if (proto !== Object.prototype && proto !== null) {\n throw new Error('A1: state unit is not a plain object (has a class prototype)');\n }\n unit.dispose();\n teardown();\n }\n\n // X1 — no raw *origin* Subject on the public surface (expose `.asObservable()`).\n // Flag only origin Subjects — `new Subject` / `new BehaviorSubject`, the unit's own\n // state, where `.source` is undefined. Derived `AnonymousSubject`s from `Subject.lift`\n // (`shareReplay().pipe(...)`) carry a `.source` and are inert sinks (`.next()` does\n // nothing useful) — idiomatic, not the forgotten-internal-Subject smell — so exclude them.\n {\n const { unit, teardown } = fresh();\n for (const [key, value] of Object.entries(unit)) {\n if (value instanceof Subject && (value as { source?: unknown }).source === undefined) {\n throw new Error(`X1: public field \"${key}\" is a raw Subject — expose it via .asObservable()`);\n }\n }\n unit.dispose();\n teardown();\n }\n\n // A7-passed — disposing the unit must NOT dispose an injected dependency.\n {\n const { unit, passedIn, teardown } = fresh();\n unit.dispose();\n passedIn.forEach((probe, i) => {\n if (probe.disposeCount > 0) {\n throw new Error(`A7-passed: the unit disposed injected dependency #${i} (it doesn't own it)`);\n }\n });\n teardown();\n }\n\n // A5 — dispose() idempotent & total: n calls never throw; injected deps stay untouched.\n run('A5', fc.property(fc.integer({ min: 1, max: 20 }), (n) => {\n const { unit, passedIn, teardown } = fresh();\n for (let i = 0; i < n; i++) unit.dispose();\n passedIn.forEach((probe, i) => {\n if (probe.disposeCount > 0) {\n throw new Error(`injected dependency #${i} disposed under ${n} dispose() calls`);\n }\n });\n teardown();\n }));\n\n // A6 — k pre-dispose subscribers on each owned surface all see `complete` on dispose.\n if (spec.surfaces) {\n const surfaces = spec.surfaces;\n run('A6', fc.property(fc.integer({ min: 1, max: 10 }), (k) => {\n const { unit, teardown } = fresh();\n const completed: boolean[] = [];\n const subs = surfaces(unit).flatMap((o) =>\n Array.from({ length: k }, () => {\n const idx = completed.push(false) - 1;\n return o.subscribe({ complete: () => { completed[idx] = true; } });\n }),\n );\n unit.dispose();\n completed.forEach((seen, idx) => {\n if (!seen) throw new Error(`a subscriber did not see complete on dispose (k=${k}, sub #${idx})`);\n });\n subs.forEach((s) => s.unsubscribe());\n teardown();\n }));\n }\n\n // A5b — post-dispose inertness: every public method is a no-op (no throw) after dispose.\n if (spec.invocations) {\n const invocations = spec.invocations;\n run('A5b', fc.property(fc.array(fc.nat(), { maxLength: 12 }), (seq) => {\n const { unit, teardown } = fresh();\n const callers = invocations(unit);\n unit.dispose();\n for (const raw of seq) {\n if (callers.length === 0) break;\n swallowAsync(callers[raw % callers.length]());\n }\n teardown();\n }));\n }\n\n // X3-runtime — instance isolation: driving instance A never moves instance B's surfaces.\n if (spec.surfaces && spec.invocations) {\n const surfaces = spec.surfaces;\n const invocations = spec.invocations;\n run('X3-runtime', fc.property(fc.array(fc.nat(), { minLength: 1, maxLength: 8 }), (seq) => {\n const a = fresh();\n const b = fresh();\n const bSurfaces = surfaces(b.unit);\n const bCounts = bSurfaces.map(() => 0);\n const bSubs = bSurfaces.map((o, i) => o.subscribe(() => { bCounts[i] += 1; }));\n const baseline = bCounts.slice(); // initial replay captured synchronously above\n const aCallers = invocations(a.unit);\n for (const raw of seq) {\n if (aCallers.length === 0) break;\n swallowAsync(aCallers[raw % aCallers.length]());\n }\n bCounts.forEach((count, i) => {\n if (count !== baseline[i]) {\n throw new Error(`driving instance A perturbed instance B's surface #${i}`);\n }\n });\n bSubs.forEach((s) => s.unsubscribe());\n a.unit.dispose();\n b.unit.dispose();\n a.teardown();\n b.teardown();\n }));\n }\n\n // A7-owned — internally-constructed children are disposed (their surfaces complete) on outer dispose.\n if (spec.ownedChildSurfaces) {\n const { unit, teardown } = fresh();\n const childSurfaces = spec.ownedChildSurfaces(unit);\n const completed = childSurfaces.map(() => false);\n const subs = childSurfaces.map((o, i) => o.subscribe({ complete: () => { completed[i] = true; } }));\n unit.dispose();\n completed.forEach((seen, i) => {\n if (!seen) throw new Error(`A7-owned: owned child surface #${i} did not complete on outer dispose`);\n });\n subs.forEach((s) => s.unsubscribe());\n teardown();\n }\n\n // Post-dispose surface inertness: a NEW subscription after dispose completes with no `next`.\n if (spec.surfaces) {\n const { unit, teardown } = fresh();\n unit.dispose();\n spec.surfaces(unit).forEach((o, i) => {\n let nexts = 0;\n let done = false;\n o.subscribe({ next: () => { nexts += 1; }, complete: () => { done = true; } }).unsubscribe();\n if (nexts > 0) throw new Error(`A5b/inert: owned surface #${i} emitted a value after dispose`);\n if (!done) throw new Error(`A5b/inert: owned surface #${i} did not complete after dispose`);\n });\n teardown();\n }\n}\n","/**\n * Executable enforcement of the liveness axioms — the runtime twin of\n * `.plans/LIVENESS-AXIOMS.md`, and the composition-level sibling of\n * `assertStateUnitAxioms` (state-unit-axioms.ts). Where the StateUnit axioms\n * make *per-unit* wrongness mechanically detectable, these make *silence*\n * detectable: every existing enforcement tier is safety (nothing wrong is\n * delivered); these assert liveness (something is eventually delivered).\n *\n * Axioms (fault-schedule dimension; fast-check):\n * L1 Subscriber liveness — every output emits next|error within the bound,\n * under any fault schedule. Error is a permitted outcome; the forbidden\n * fourth state is pending-forever.\n * L2 Request settlement — every awaited path settles within the bound;\n * re-issues per logical request stay within the retry budget (B14: one);\n * a faulted request must be re-issued or surfaced, never swallowed.\n * L3 Delivery across lifecycle transitions — every event written to a live\n * connection reaches the output exactly once, wherever a client-initiated\n * transition (handover / reconnect / scope change) lands. Retirement is\n * by drain, never by abort (TRANSPORT-HTTP.md, Abort discipline).\n *\n * Framework-agnostic on purpose — only `rxjs` + `fast-check`, no `vitest` — so\n * it ships through `@semiont/core/testing` and any package's test runner can\n * invoke it. Deterministic virtual time: properties pass a small explicit\n * `timeoutMs` to `busRequest`; no `Date.now`, no 30 s real waits.\n */\n\nimport * as fc from 'fast-check';\nimport type { Observable, Subscription } from 'rxjs';\nimport { FaultyTransport, type FaultAction } from './faulty-transport';\n\n// ── L1/L2: liveness over a composition on FaultyTransport ────────────────\n\n/** What one fresh run of the composition exposes to the axioms. */\nexport interface LivenessScenario {\n /**\n * Live-query-shaped outputs. The harness subscribes each one; every\n * subscription must see `next` or `error` within the bound (L1).\n */\n outputs: readonly Observable<unknown>[];\n /**\n * Awaited paths. Each promise must settle — resolve or reject — within the\n * bound (L2). Rejections are fine; pending-forever is the violation.\n */\n settlements?: readonly Promise<unknown>[];\n teardown?: () => void;\n}\n\nexport interface LivenessAxiomSpec {\n /** Build a FRESH composition wired to the given transport. Called per run. */\n setup: (transport: FaultyTransport) => LivenessScenario | Promise<LivenessScenario>;\n /**\n * The timeoutMs the scenario passes to `busRequest` — the bound is derived\n * from it: (timeoutMs × (1 + retryBudget) + Σdelays) × slackFactor.\n */\n timeoutMs: number;\n /** Max sanctioned re-issues per logical request (B14 budget). Default 1. */\n retryBudget?: number;\n /** Override the generated fault schedules (teeth tests pin one). */\n scheduleArb?: fc.Arbitrary<readonly FaultAction[]>;\n /** Passed through to FaultyTransport (reply synthesis). */\n makeResponse?: (operation: string, payload: Record<string, unknown>) => unknown;\n /** fast-check run budget (default 25 — CI-fast; crank locally). */\n numRuns?: number;\n /** Real-scheduler jitter headroom on the bound (default 4×). */\n slackFactor?: number;\n}\n\n/** The five wire behaviors, uniformly weighted; delays stay small (≤5 ms). */\nexport function arbFaultAction(): fc.Arbitrary<FaultAction> {\n return fc.oneof(\n fc.constant<FaultAction>({ kind: 'deliver' }),\n fc.constant<FaultAction>({ kind: 'drop-reply' }),\n fc.integer({ min: 0, max: 5 }).map((ms): FaultAction => ({ kind: 'delay', ms })),\n fc.constant<FaultAction>({ kind: 'duplicate-reply' }),\n fc.constant<FaultAction>({ kind: 'reject-emit' }),\n );\n}\n\nexport function arbFaultSchedule(maxLength = 8): fc.Arbitrary<readonly FaultAction[]> {\n return fc.array(arbFaultAction(), { minLength: 1, maxLength });\n}\n\nfunction describeSchedule(schedule: readonly FaultAction[]): string {\n return schedule.map((a) => (a.kind === 'delay' ? `delay(${a.ms})` : a.kind)).join(',');\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Sibling of state-unit-axioms' `run`, adapted for properties that check more\n * than one axiom: fast-check's falsification message carries the seed and\n * counterexample but not the thrown axiom id (fc v4 moves the inner error to\n * `cause`), so the property captures its own labeled violation into `slot`\n * and the wrapper re-throws with the axiom id leading and fc's detail after.\n */\nasync function runLabeled(\n fallback: string,\n slot: { violation: Error | null },\n assertion: () => Promise<void>,\n): Promise<void> {\n try {\n await assertion();\n } catch (e) {\n const detail = e instanceof Error ? e.message : String(e);\n if (slot.violation) throw new Error(`${slot.violation.message}\\n${detail}`);\n throw new Error(`${fallback}: ${detail}`);\n }\n}\n\n/** Wrap a check block so its labeled throw lands in `slot` before falsifying. */\nfunction capturing(slot: { violation: Error | null }, block: () => void): void {\n try {\n block();\n } catch (e) {\n slot.violation = e instanceof Error ? e : new Error(String(e));\n throw e;\n }\n}\n\n/**\n * Run L1 + L2 against `spec` across generated fault schedules. Throws a\n * labeled Error (`L1: …` / `L2: …`) on the first violation.\n */\nexport async function assertLivenessAxioms(spec: LivenessAxiomSpec): Promise<void> {\n const retryBudget = spec.retryBudget ?? 1;\n const slack = spec.slackFactor ?? 4;\n const scheduleArb = spec.scheduleArb ?? arbFaultSchedule();\n\n const slot = { violation: null as Error | null };\n await runLabeled('liveness', slot, () =>\n fc.assert(\n fc.asyncProperty(scheduleArb, async (schedule) => {\n const delayTotal = schedule.reduce((n, a) => n + (a.kind === 'delay' ? a.ms : 0), 0);\n const bound = (spec.timeoutMs * (1 + retryBudget) + delayTotal) * slack;\n const transport = new FaultyTransport({\n schedule,\n ...(spec.makeResponse ? { makeResponse: spec.makeResponse } : {}),\n });\n const scenario = await spec.setup(transport);\n const outputs = scenario.outputs;\n const settlements = scenario.settlements ?? [];\n\n // Subscribe every output (that's what makes a live query live) and\n // track notifications; track settlement of every awaited path.\n const notified: boolean[] = outputs.map(() => false);\n const settled: boolean[] = settlements.map(() => false);\n let doneResolve: () => void = () => {};\n const allDone = new Promise<void>((resolve) => { doneResolve = resolve; });\n const check = (): void => {\n if (notified.every(Boolean) && settled.every(Boolean)) doneResolve();\n };\n const subs: Subscription[] = outputs.map((o, i) =>\n o.subscribe({\n next: () => { notified[i] = true; check(); },\n error: () => { notified[i] = true; check(); },\n }),\n );\n settlements.forEach((p, i) => {\n p.then(() => { settled[i] = true; check(); },\n () => { settled[i] = true; check(); });\n });\n check();\n\n // Wait for full liveness or the bound, whichever first.\n await Promise.race([allDone, sleep(bound)]);\n\n try {\n // L2 first: when it applies it names the mechanism (swallow, budget,\n // unsettled await); L1 below is the broader every-output net. All-\n // outputs-silent is a subset of any-output-silent, so checking L1\n // first would shadow the sharper L2 diagnoses entirely.\n capturing(slot, () => {\n // L2 — settlement: every awaited path settled.\n settled.forEach((seen, i) => {\n if (!seen) {\n throw new Error(\n `L2: settlement #${i} did not settle within ${bound}ms ` +\n `under schedule ⟨${describeSchedule(schedule)}⟩ — the forbidden fourth state`,\n );\n }\n });\n\n // L2 — retry accounting over the transport's request log.\n const issues = new Map<string, { count: number; lastFaulted: boolean }>();\n for (const entry of transport.requestLog) {\n const prior = issues.get(entry.retryKey) ?? { count: 0, lastFaulted: false };\n issues.set(entry.retryKey, {\n count: prior.count + 1,\n lastFaulted: entry.action.kind === 'drop-reply' || entry.action.kind === 'reject-emit',\n });\n }\n for (const [key, { count, lastFaulted }] of issues) {\n if (count > 1 + retryBudget) {\n throw new Error(\n `L2: request ⟨${key}⟩ issued ${count} times — exceeds the retry budget ` +\n `(1 + ${retryBudget}) under schedule ⟨${describeSchedule(schedule)}⟩`,\n );\n }\n // Swallow detection: the final issue of a logical request was\n // faulted, the composition had retry budget left but didn't use\n // it, and no output surfaced anything — the rejection went into\n // a void (the pre-B14 `catch(() => {})`).\n if (lastFaulted && count <= retryBudget && outputs.length > 0 && !notified.some(Boolean)) {\n throw new Error(\n `L2: faulted request ⟨${key}⟩ was neither re-issued nor surfaced ` +\n `under schedule ⟨${describeSchedule(schedule)}⟩ — rejection swallowed`,\n );\n }\n }\n\n // L1 — every subscription saw next|error; pending-forever is the bug.\n notified.forEach((seen, i) => {\n if (!seen) {\n throw new Error(\n `L1: output #${i} received no next/error within ${bound}ms ` +\n `under schedule ⟨${describeSchedule(schedule)}⟩ — silently pending`,\n );\n }\n });\n });\n } finally {\n subs.forEach((s) => s.unsubscribe());\n scenario.teardown?.();\n transport.dispose();\n }\n }),\n { numRuns: spec.numRuns ?? 25 },\n ),\n );\n}\n\n// ── L3: exactly-once delivery across client-initiated transitions ────────\n\n/**\n * A connection-stream-shaped subject: something that accepts writes to the\n * live connection, can be told to transition (handover / reconnect / scope\n * change), and exposes the subscriber-facing output. P3 adapts the real\n * actor's mock-connection harness to this shape; the teeth tests drive\n * reconstructed pre-fix doubles.\n */\nexport interface DeliverySubject {\n /** Write the event with this id to the currently-live connection. */\n write: (eventId: string) => void;\n /** Client-initiated lifecycle transition. */\n transition: () => void | Promise<void>;\n /** Subscriber-facing output; each emission is a delivered event id. */\n output$: Observable<string>;\n /**\n * Drain pending asynchronous delivery at end of sequence (a live connection\n * eventually flushes). Default: one macrotask tick.\n */\n settle?: () => Promise<void>;\n teardown?: () => void;\n}\n\nexport type DeliveryOp = 'write' | 'transition';\n\nexport interface DeliveryAxiomSpec {\n /** Build a FRESH subject. Called per run. */\n setup: () => DeliverySubject;\n /** Override the generated op sequences (teeth tests pin one). */\n opsArb?: fc.Arbitrary<readonly DeliveryOp[]>;\n /** Max generated sequence length (default 12). */\n maxOps?: number;\n /** fast-check run budget (default 50 — these runs are cheap). */\n numRuns?: number;\n}\n\nexport function arbDeliveryOps(maxOps = 12): fc.Arbitrary<readonly DeliveryOp[]> {\n return fc\n .array(fc.constantFrom<DeliveryOp>('write', 'transition'), { minLength: 1, maxLength: maxOps })\n // A sequence with no write asserts nothing — always exercise delivery.\n .map((ops) => (ops.includes('write') ? ops : ([...ops, 'write'] as const)));\n}\n\n/**\n * Run L3 against `spec` across generated write/transition interleavings.\n * Throws a labeled Error (`L3: …`) on the first violation: an event written\n * to a live connection delivered zero times (lost — retired by abort instead\n * of drain) or more than once (duplicate).\n */\nexport async function assertExactlyOnceDelivery(spec: DeliveryAxiomSpec): Promise<void> {\n const opsArb = spec.opsArb ?? arbDeliveryOps(spec.maxOps ?? 12);\n\n const slot = { violation: null as Error | null };\n await runLabeled('L3', slot, () =>\n fc.assert(\n fc.asyncProperty(opsArb, async (ops) => {\n const subject = spec.setup();\n const delivered: string[] = [];\n const sub = subject.output$.subscribe((id) => delivered.push(id));\n const written: string[] = [];\n try {\n for (let i = 0; i < ops.length; i++) {\n if (ops[i] === 'write') {\n const id = `e${i}`;\n written.push(id);\n subject.write(id);\n } else {\n await subject.transition();\n }\n // Deliberately no settling between ops: the interesting races are\n // transitions landing before a write's asynchronous delivery.\n }\n await (subject.settle?.() ?? sleep(0));\n\n capturing(slot, () => {\n for (const id of written) {\n const n = delivered.filter((d) => d === id).length;\n if (n === 0) {\n throw new Error(\n `L3: event ⟨${id}⟩ delivered 0 times under ⟨${ops.join(',')}⟩ — ` +\n `lost across a transition (retire by drain, never by abort)`,\n );\n }\n if (n > 1) {\n throw new Error(\n `L3: event ⟨${id}⟩ delivered ${n} times under ⟨${ops.join(',')}⟩ — duplicate delivery`,\n );\n }\n }\n });\n } finally {\n sub.unsubscribe();\n subject.teardown?.();\n }\n }),\n { numRuns: spec.numRuns ?? 50 },\n ),\n );\n}\n"]}