@systemfsoftware/vitest 1.0.0 → 2.0.0

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.
@@ -1,19 +1,27 @@
1
1
 
2
2
  import { d as isUserFrame, f as siteOfFrame, i as checksFor, o as makeLedger, p as withRaisingFrame, r as Asserted, s as callFrame, t as VitestTestContext, u as framesOf$1 } from "./test-context-D_-acRSc.mjs";
3
- import * as V from "vitest";
4
3
  import { TestRunner, inject } from "vitest";
5
4
  import * as Cause from "effect/Cause";
6
5
  import * as Effect$1 from "effect/Effect";
7
6
  import * as Equal from "effect/Equal";
8
7
  import * as Exit from "effect/Exit";
9
8
  import * as Function from "effect/Function";
9
+ import * as Layer from "effect/Layer";
10
10
  import * as Schema$1 from "effect/Schema";
11
11
  import * as Option$1 from "effect/Option";
12
- import * as Match from "effect/Match";
12
+ import * as Match$1 from "effect/Match";
13
13
  import * as Tracer from "effect/Tracer";
14
- import { Effect, Option, Schema, SchemaGetter, SchemaIssue } from "effect";
14
+ import { Effect, Match, Option, Schema, SchemaGetter, SchemaIssue } from "effect";
15
+ import { layer } from "@effect/platform-node/NodeFileSystem";
16
+ import { layer as layer$1 } from "@effect/platform-node/NodePath";
15
17
  import * as Arbitrary from "effect/Arbitrary";
16
- import * as Data from "effect/Data";
18
+ import * as Config from "effect/Config";
19
+ import * as ConfigProvider from "effect/ConfigProvider";
20
+ import * as Random from "effect/Random";
21
+ import * as Ref from "effect/Ref";
22
+ import * as Result from "effect/Result";
23
+ import * as FileSystem from "effect/FileSystem";
24
+ import * as Path from "effect/Path";
17
25
  //#region src/internal/property/defaults.ts
18
26
  /** @internal The `test.provide` key the shared config fills with this run's tier's check options. */
19
27
  const checkDefaultsKey = "@systemfsoftware/vitest:property-check";
@@ -22,13 +30,19 @@ const providedCheckDefaults = () => inject(checkDefaultsKey);
22
30
  //#endregion
23
31
  //#region src/replay.schema.ts
24
32
  /**
25
- * The one replay grammar (KTD10, R6): the `CONFORMANCE_REPLAY` text a failure record's rerun line carries and
26
- * the spec runtime reads back to re-run the same schedule. The text names the seed a generator chose and the
27
- * decisions it took, in order — `seed=7;path=1,2,3` — and the path is empty when the run took no decision
28
- * (`seed=1;path=`). A text that names neither is not a replay.
33
+ * The one replay grammar (KTD10, KTD5, KD8, R6, R9-R12): the `CONFORMANCE_REPLAY` text a failure record's rerun
34
+ * line carries and the engine reads back to re-run the same verdict. Two forms share it:
29
35
  *
30
- * The seed and every decision are branded whole numbers (CONST-D3), and the text is a codec rooted at
31
- * `Schema.String`, so one declaration serves both the writer and the reader instead of a pattern each.
36
+ * - the kernel's schedule, `seed=<whole number>;path=<comma-separated whole numbers>` — the path is empty when the
37
+ * run took no decision (`seed=1;path=`);
38
+ * - a property entry, `property=<identity hash>;seed=<whole number>;runs=<whole number>` for a run that was not
39
+ * refuted, and the same entry plus `;attempt=<n>;size=<n>;path=<comma-separated decisions>;failure=<tag>` for a
40
+ * refuted run, so the engine rebuilds the generator's replay token. Several entries are joined by `|`, which no
41
+ * entry can contain: every key is lowercase letters and every value is digits, commas or a failure tag.
42
+ *
43
+ * A text that names neither form is not a replay. The seed and every decision are branded whole numbers
44
+ * (CONST-D3), and the text is a codec rooted at `Schema.String`, so one declaration serves both the writer and the
45
+ * reader instead of a pattern each.
32
46
  *
33
47
  * @since 4.0.0
34
48
  */
@@ -41,25 +55,70 @@ const Replay = Schema.Struct({
41
55
  seed: ReplaySeed,
42
56
  path: Schema.Array(ReplayStep)
43
57
  });
58
+ /** A property's identity hash: the unsigned whole number `identityHash` derives from its identity (KTD4). */
59
+ const PropertyHash = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("PropertyHash"));
60
+ /** The count of runs a property entry names, never negative (CONST-D3). */
61
+ const ReplayRunCount = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("ReplayRunCount"));
62
+ /** The failure kind effect's falsification runner records in its replay token. */
63
+ const ReplayFailureTag = Schema.Literals(["ReturnedFalse", "PropertyError"]);
64
+ /**
65
+ * A property entry for a run that was not refuted: the property's identity hash, the seed it ran with and its
66
+ * run count (R9, R11).
67
+ *
68
+ * @since 4.0.0
69
+ */
70
+ const PlainPropertyReplay = Schema.TaggedStruct("Plain", {
71
+ property: PropertyHash,
72
+ seed: ReplaySeed,
73
+ runs: ReplayRunCount
74
+ });
75
+ /**
76
+ * A property entry for a refuted run: the plain fields, plus the generator replay token's parts the engine needs
77
+ * to rebuild it — the attempt, the size, the shrink path and the failure tag (R9, R12, KTD5).
78
+ *
79
+ * @since 4.0.0
80
+ */
81
+ const RefutedPropertyReplay = Schema.TaggedStruct("Refuted", {
82
+ property: PropertyHash,
83
+ seed: ReplaySeed,
84
+ runs: ReplayRunCount,
85
+ attempt: ReplayRunCount,
86
+ size: ReplayRunCount,
87
+ path: Schema.Array(ReplayStep),
88
+ failure: ReplayFailureTag
89
+ });
90
+ /** One property entry: a plain run, or a refuted one (R9). */
91
+ const PropertyReplay = Schema.Union([RefutedPropertyReplay, PlainPropertyReplay]);
92
+ /** What a `CONFORMANCE_REPLAY` text names: the kernel's replay, or one or more property entries (KTD5). */
93
+ const ReplayChannel = Schema.Union([Replay, Schema.Array(PropertyReplay)]);
44
94
  const SEED_PREFIX = "seed=";
45
95
  const PATH_PREFIX = "path=";
96
+ const PROPERTY_PREFIX = "property=";
97
+ const RUNS_PREFIX = "runs=";
98
+ const ATTEMPT_PREFIX = "attempt=";
99
+ const SIZE_PREFIX = "size=";
100
+ const FAILURE_PREFIX = "failure=";
46
101
  const PAIR_SEPARATOR = ";";
47
- const ENTRY_SEPARATOR = ",";
102
+ const LIST_SEPARATOR = ",";
103
+ const ENTRY_SEPARATOR = "|";
48
104
  const WHOLE_NUMBER = /^\d+$/u;
105
+ const ASSIGNMENT = /^([a-z]+)=([\s\S]*)$/u;
49
106
  const NOT_A_REPLAY = "a replay reads \"seed=<whole number>;path=<comma-separated whole numbers>\"";
107
+ const NOT_A_CHANNEL = "a replay reads \"seed=<whole number>;path=<whole numbers>\", or one or more \"property=<hash>;seed=<n>;runs=<n>\" entries joined by \"|\"";
50
108
  const wholeNumberOf = (text) => WHOLE_NUMBER.test(text) ? Number(text) : void 0;
51
109
  const seedDigitsOf = (text) => text.startsWith(SEED_PREFIX) ? text.slice(5) : void 0;
52
110
  const pathDigitsOf = (text) => text.startsWith(PATH_PREFIX) ? text.slice(5) : void 0;
53
- const entriesOf$1 = (text) => text.length === 0 ? [] : text.split(ENTRY_SEPARATOR);
111
+ const entriesOf$1 = (text) => text.length === 0 ? [] : text.split(LIST_SEPARATOR);
54
112
  const wholeNumbersOf = (entries) => {
55
113
  const numbers = entries.map(wholeNumberOf);
56
114
  return numbers.every((value) => value !== void 0) ? numbers : void 0;
57
115
  };
58
116
  const seedIn = (part) => Option.flatMap(Option.fromUndefinedOr(seedDigitsOf(part)), (digits) => Option.fromUndefinedOr(wholeNumberOf(digits)));
59
117
  const decisionsIn = (part) => Option.flatMap(Option.fromUndefinedOr(pathDigitsOf(part)), (entries) => Option.fromUndefinedOr(wholeNumbersOf(entriesOf$1(entries))));
118
+ const exactlyTwoParts = (parts) => parts.length === 2;
60
119
  const pairPartsOf = (text) => {
61
- const [seedPart, pathPart, ...rest] = text.split(PAIR_SEPARATOR);
62
- return rest.length === 0 ? Option.all([Option.fromUndefinedOr(seedPart), Option.fromUndefinedOr(pathPart)]) : Option.none();
120
+ const parts = text.split(PAIR_SEPARATOR);
121
+ return exactlyTwoParts(parts) ? Option.all([Option.fromUndefinedOr(parts[0]), Option.fromUndefinedOr(parts[1])]) : Option.none();
63
122
  };
64
123
  /** The seed and decisions a pair names, or nothing when the pair is not `seed=…;path=…`. */
65
124
  const namedPairOf = (text) => Option.flatMap(pairPartsOf(text), ([seedPart, pathPart]) => Option.all([seedIn(seedPart), decisionsIn(pathPart)]));
@@ -69,7 +128,7 @@ const replayOfName = (text) => Option.flatMap(namedPairOf(text), ([seed, path])
69
128
  seed,
70
129
  path
71
130
  }));
72
- const textOfEncodedReplay = (replay) => `${SEED_PREFIX}${replay.seed}${PAIR_SEPARATOR}${PATH_PREFIX}${replay.path.join(ENTRY_SEPARATOR)}`;
131
+ const textOfEncodedReplay = (replay) => `${SEED_PREFIX}${replay.seed}${PAIR_SEPARATOR}${PATH_PREFIX}${replay.path.join(LIST_SEPARATOR)}`;
73
132
  const replayRefusal = (text) => /* @__PURE__ */ new Error(`CONFORMANCE_REPLAY names neither a seed nor a decision path: ${text}`);
74
133
  /**
75
134
  * The `CONFORMANCE_REPLAY` text as the replay it names. A text a seed and a path cannot be read out of is refused
@@ -88,9 +147,79 @@ const replayOfText = (text) => Option.match(replayOfName(text), {
88
147
  * @since 4.0.0
89
148
  */
90
149
  const replayTextOf = (replay) => textOfEncodedReplay(replay);
150
+ const fieldOfPart = (part) => Option.flatMap(Option.fromNullishOr(ASSIGNMENT.exec(part)), (matched) => Option.map(Option.all([Option.fromUndefinedOr(matched[1]), Option.fromUndefinedOr(matched[2])]), ([key, value]) => [key, value]));
151
+ const fieldsOf$1 = (text) => Option.all(text.split(PAIR_SEPARATOR).map(fieldOfPart));
152
+ const textIn = (fields, key) => Option.map(Option.fromNullishOr(fields.find(([name]) => name === key)), (field) => field[1]);
153
+ /** The entry's keys name exactly `keys`, in order — so a reordered or repeated field is refused (KD8). */
154
+ const keysAre = (fields, keys) => fields.length === keys.length && fields.every(([name], at) => name === keys[at]);
155
+ const naturalIn = (fields, key) => Option.flatMap(textIn(fields, key), (text) => Option.fromUndefinedOr(wholeNumberOf(text)));
156
+ const pathIn = (fields, key) => Option.flatMap(textIn(fields, key), (text) => Option.fromUndefinedOr(wholeNumbersOf(entriesOf$1(text))));
157
+ const failureIn = (fields, key) => Option.flatMap(textIn(fields, key), (text) => Schema.decodeUnknownOption(ReplayFailureTag)(text));
158
+ const PLAIN_KEYS = [
159
+ "property",
160
+ "seed",
161
+ "runs"
162
+ ];
163
+ const REFUTED_KEYS = [
164
+ "property",
165
+ "seed",
166
+ "runs",
167
+ "attempt",
168
+ "size",
169
+ "path",
170
+ "failure"
171
+ ];
172
+ const plainEncodedOf = (fields) => Option.map(Option.all([
173
+ naturalIn(fields, "property"),
174
+ naturalIn(fields, "seed"),
175
+ naturalIn(fields, "runs")
176
+ ]), ([property, seed, runs]) => ({
177
+ _tag: "Plain",
178
+ property,
179
+ seed,
180
+ runs
181
+ }));
182
+ const refutedEncodedOf = (fields) => Option.map(Option.all([
183
+ naturalIn(fields, "property"),
184
+ naturalIn(fields, "seed"),
185
+ naturalIn(fields, "runs"),
186
+ naturalIn(fields, "attempt"),
187
+ naturalIn(fields, "size"),
188
+ pathIn(fields, "path"),
189
+ failureIn(fields, "failure")
190
+ ]), ([property, seed, runs, attempt, size, path, failure]) => ({
191
+ _tag: "Refuted",
192
+ property,
193
+ seed,
194
+ runs,
195
+ attempt,
196
+ size,
197
+ path,
198
+ failure
199
+ }));
200
+ const plainEntryOf = (fields) => Option.flatMap(plainEncodedOf(fields), (encoded) => Schema.decodeOption(PlainPropertyReplay)(encoded));
201
+ const refutedEntryOf$1 = (fields) => Option.flatMap(refutedEncodedOf(fields), (encoded) => Schema.decodeOption(RefutedPropertyReplay)(encoded));
202
+ const plainWhenKeysOf = (fields) => keysAre(fields, PLAIN_KEYS) ? plainEntryOf(fields) : Option.none();
203
+ const refutedWhenKeysOf = (fields) => keysAre(fields, REFUTED_KEYS) ? refutedEntryOf$1(fields) : Option.none();
204
+ const propertyEntryOf = (text) => Option.flatMap(fieldsOf$1(text), (fields) => Option.orElse(plainWhenKeysOf(fields), () => refutedWhenKeysOf(fields)));
205
+ const propertyChannelOf = (text) => text.length === 0 ? Option.none() : Option.all(text.split(ENTRY_SEPARATOR).map(propertyEntryOf));
206
+ const channelOfName = (text) => Option.match(replayOfName(text), {
207
+ onNone: () => propertyChannelOf(text),
208
+ onSome: (replay) => Option.some(replay)
209
+ });
210
+ const isEntries$1 = (channel) => Array.isArray(channel);
211
+ /** The text a plain property entry is written as (R9, R11). */
212
+ const plainReplayText = (fields) => `${PROPERTY_PREFIX}${fields.property}${PAIR_SEPARATOR}${SEED_PREFIX}${fields.seed}${PAIR_SEPARATOR}${RUNS_PREFIX}${fields.runs}`;
213
+ /** The text several plain entries are written as, joined by `|` (R11). */
214
+ const plainReplayTexts = (entries) => entries.map(plainReplayText).join(ENTRY_SEPARATOR);
215
+ /** The text a refuted property entry is written as (R9, R12). */
216
+ const refutedReplayText$1 = (fields) => `${plainReplayText(fields)}${PAIR_SEPARATOR}${ATTEMPT_PREFIX}${fields.attempt}${PAIR_SEPARATOR}${SIZE_PREFIX}${fields.size}${PAIR_SEPARATOR}${PATH_PREFIX}${fields.path.join(LIST_SEPARATOR)}${PAIR_SEPARATOR}${FAILURE_PREFIX}${fields.failure}`;
217
+ const entryTextOf = (entry) => Match.value(entry).pipe(Match.tag("Refuted", (refuted) => refutedReplayText$1(refuted)), Match.tag("Plain", (plain) => plainReplayText(plain)), Match.exhaustive);
218
+ /** The text a channel is written as: the kernel's replay, or the entries joined by `|` (KTD5). */
219
+ const replayChannelTextOf = (channel) => isEntries$1(channel) ? channel.map(entryTextOf).join(ENTRY_SEPARATOR) : textOfEncodedReplay(channel);
91
220
  /**
92
- * The replay codec: `Schema.String` in, a replay out. A text that names no seed and path fails the decode, and
93
- * every replay encodes back to the text it was read from.
221
+ * The kernel's replay codec: `Schema.String` in, a replay out. A text that names no seed and path fails the decode,
222
+ * and every replay encodes back to the text it was read from.
94
223
  *
95
224
  * @since 4.0.0
96
225
  */
@@ -98,6 +227,16 @@ const ReplayFromText = Schema.String.pipe(Schema.decodeTo(Replay, {
98
227
  decode: SchemaGetter.transformEffect((text, options) => Effect.fromOption(replayOfName(text), () => new SchemaIssue.InvalidValue({ message: NOT_A_REPLAY }, text, options))),
99
228
  encode: SchemaGetter.transform(textOfEncodedReplay)
100
229
  }));
230
+ /**
231
+ * The channel codec: `Schema.String` in, the kernel's replay or the property entries out. A text that names neither
232
+ * form fails the decode, and every channel encodes back to the text it was read from.
233
+ *
234
+ * @since 4.0.0
235
+ */
236
+ const ReplayChannelFromText = Schema.String.pipe(Schema.decodeTo(ReplayChannel, {
237
+ decode: SchemaGetter.transformEffect((text, options) => Effect.fromOption(channelOfName(text), () => new SchemaIssue.InvalidValue({ message: NOT_A_CHANNEL }, text, options))),
238
+ encode: SchemaGetter.transform((channel) => replayChannelTextOf(channel))
239
+ }));
101
240
  //#endregion
102
241
  //#region src/internal/errors.schema.ts
103
242
  /**
@@ -129,12 +268,6 @@ const REFUSED_BREACH_TEXT = {
129
268
  */
130
269
  const refusedRecordDetail = (breaches) => `✗ refused a failure record: ${breaches.map((breach) => REFUSED_BREACH_TEXT[breach]).join(", and ")}`;
131
270
  /** @internal */
132
- var NonBooleanVerdict = class extends Schema.TaggedError()("NonBooleanVerdict", { detail: Schema.String }) {
133
- get message() {
134
- return this.detail;
135
- }
136
- };
137
- /** @internal */
138
271
  var LeakedState = class extends Schema.TaggedError()("LeakedState", { detail: Schema.String }) {
139
272
  get message() {
140
273
  return this.detail;
@@ -256,7 +389,7 @@ const MARK = {
256
389
  failed: "✗",
257
390
  unfinished: "✗"
258
391
  };
259
- const not = (value) => !value;
392
+ const not$1 = (value) => !value;
260
393
  const and = (left, right) => left && right;
261
394
  const or = (left, right) => left || right;
262
395
  const firstPresent = (values) => values.find((value) => value !== void 0);
@@ -289,16 +422,85 @@ const pairTextsOf = (pairs) => pairs.map(pairTextOf);
289
422
  const hasPair = (pairs, name) => pairs.some((pair) => pair.name === name);
290
423
  const pairsOf = (value) => isObject(value) ? Object.keys(value).map((key) => pairOf(key, renderValue(fieldOf(value, key)))) : [];
291
424
  const fieldsOf = (value) => pairsOf(value).filter((pair) => isFieldName(pair.name));
292
- const renderValue = (value) => isObject(value) ? renderObject(value) : renderScalar(value);
425
+ const renderValue = (value) => witnessOf(value).rendered;
293
426
  const renderScalar = (value) => isText(value) ? JSON.stringify(value) : renderPrimitive(value);
294
427
  const renderPrimitive = (value) => isBigInt(value) ? `${value}n` : renderPlain(value);
295
428
  const renderPlain = (value) => isPrimitive(value) ? `${value}` : renderOther(value);
296
429
  const renderOther = (value) => isNull(value) ? "null" : renderUndefined(value);
297
430
  const renderUndefined = (value) => value === void 0 ? "undefined" : renderContainer(value);
298
431
  const renderContainer = (value) => isArray(value) ? renderList(value) : typeof value;
299
- const renderList = (value) => `[${value.map(renderValue).join(",")}]`;
300
- const renderObject = (value) => tagFieldOf(value) ?? renderRecord$1(value);
301
- const renderRecord$1 = (value) => `{${pairTextsOf(pairsOf(value)).join(",")}}`;
432
+ const listTextOf = (children) => `[${children.join(",")}]`;
433
+ const recordTextOf = (pairs) => `{${pairTextsOf(pairs).join(",")}}`;
434
+ const renderList = (value) => listTextOf(value.map(renderValue));
435
+ const renderRecord$1 = (value) => recordTextOf(pairsOf(value));
436
+ const KIND_FIELD = "_kind";
437
+ const VALUE_FIELD = "value";
438
+ const DESCRIPTION_FIELD = "description";
439
+ const UNDEFINED_JSON = { [KIND_FIELD]: "undefined" };
440
+ const CIRCULAR_WITNESS = {
441
+ rendered: "[Circular]",
442
+ value: { [KIND_FIELD]: "circular" }
443
+ };
444
+ const markOf = (kind, field, text) => ({
445
+ [KIND_FIELD]: kind,
446
+ [field]: text
447
+ });
448
+ const isSymbol = (value) => typeof value === "symbol";
449
+ const isCallable = (value) => typeof value === "function";
450
+ const callableNameOf = (value) => {
451
+ const name = fieldOf(value, NAME_FIELD);
452
+ return isText(name) ? name : "";
453
+ };
454
+ const numberJsonOf = (value) => Number.isFinite(value) ? value : markOf("number", VALUE_FIELD, `${value}`);
455
+ const bigintJsonOf = (value) => markOf("bigint", VALUE_FIELD, `${value}`);
456
+ const functionJsonOf = (value) => markOf("function", NAME_FIELD, callableNameOf(value));
457
+ const symbolJsonOf = (value) => markOf("symbol", DESCRIPTION_FIELD, value.description ?? "");
458
+ const scalarJsonOf = (value) => isText(value) ? value : nonTextJsonOf(value);
459
+ const nonTextJsonOf = (value) => isBoolean(value) ? value : nullOrOtherJsonOf(value);
460
+ const nullOrOtherJsonOf = (value) => isNull(value) ? value : numberOrOtherJsonOf(value);
461
+ const numberOrOtherJsonOf = (value) => isNumber(value) ? numberJsonOf(value) : bigintOrOtherJsonOf(value);
462
+ const bigintOrOtherJsonOf = (value) => isBigInt(value) ? bigintJsonOf(value) : callableOrUndefinedJsonOf(value);
463
+ const callableOrUndefinedJsonOf = (value) => isCallable(value) ? functionJsonOf(value) : symbolOrUndefinedJsonOf(value);
464
+ const symbolOrUndefinedJsonOf = (value) => isSymbol(value) ? symbolJsonOf(value) : UNDEFINED_JSON;
465
+ /**
466
+ * The one traversal a value's rendered text and its JSON projection come from (R3, KD7): `rendered` is exactly
467
+ * `renderValue`'s text, `value` is the same walk with every value JSON cannot carry marked by kind, and a cycle
468
+ * marks `circular` rather than recursing forever.
469
+ *
470
+ * @internal
471
+ */
472
+ const witnessOf = (value) => witnessAt(value, []);
473
+ const witnessAt = (value, seen) => isObject(value) ? witnessedStructure(value, seen) : witnessedScalar(value);
474
+ const witnessedStructure = (value, seen) => seen.includes(value) ? CIRCULAR_WITNESS : witnessedObject(value, seen);
475
+ const witnessedObject = (value, seen) => isArray(value) ? witnessedList(value, seen) : witnessedRecord(value, seen);
476
+ const witnessedScalar = (value) => ({
477
+ rendered: renderScalar(value),
478
+ value: scalarJsonOf(value)
479
+ });
480
+ const witnessedList = (value, seen) => {
481
+ const children = value.map((item) => witnessAt(item, seenWith(seen, value)));
482
+ return {
483
+ rendered: listTextOf(children.map(renderedOf)),
484
+ value: children.map(witnessValueOf)
485
+ };
486
+ };
487
+ const witnessedRecord = (value, seen) => {
488
+ const entries = entriesAt(value, seenWith(seen, value));
489
+ return {
490
+ rendered: tagFieldOf(value) ?? recordTextOf(entryPairsOf(entries)),
491
+ value: entriesJsonOf(entries)
492
+ };
493
+ };
494
+ const seenWith = (seen, value) => [...seen, value];
495
+ const entriesAt = (value, seen) => Object.keys(value).map((key) => witnessEntryOf(key, witnessAt(fieldOf(value, key), seen)));
496
+ const witnessEntryOf = (name, witness) => ({
497
+ name,
498
+ witness
499
+ });
500
+ const entryPairsOf = (entries) => entries.map((entry) => pairOf(entry.name, entry.witness.rendered));
501
+ const entriesJsonOf = (entries) => Object.fromEntries(entries.map((entry) => [entry.name, entry.witness.value]));
502
+ const renderedOf = (witness) => witness.rendered;
503
+ const witnessValueOf = (witness) => witness.value;
302
504
  const bracesOf = (pairs) => ` {${pairTextsOf(pairs).join(",")}}`;
303
505
  const renderFields = (value) => bracesOf(fieldsOf(value));
304
506
  const textSummaryOf = (text) => text.length === 0 ? text : JSON.stringify(text);
@@ -359,7 +561,7 @@ const textAttrOf = (span, key) => {
359
561
  const ancestorsIn = (span) => span instanceof Tracer.NativeSpan ? [span, ...ancestorsOf(span.parent)] : [];
360
562
  const ancestorsOf = (parent) => Option$1.isNone(parent) ? [] : ancestorsIn(parent.value);
361
563
  const stepAncestorOf = (span) => ancestorsOf(span.parent).find((ancestor) => ancestor.name === STEP_SPAN);
362
- const stateOf = (span) => Match.value(span.status).pipe(Match.when({ _tag: "Started" }, () => "unfinished"), Match.when({ _tag: "Ended" }, (ended) => endedStateOf(ended.exit)), Match.exhaustive);
564
+ const stateOf = (span) => Match$1.value(span.status).pipe(Match$1.when({ _tag: "Started" }, () => "unfinished"), Match$1.when({ _tag: "Ended" }, (ended) => endedStateOf(ended.exit)), Match$1.exhaustive);
363
565
  const endedStateOf = (exit) => Exit.isSuccess(exit) ? "passed" : "failed";
364
566
  const stepSpansOf = (spans) => spans.filter(isTopStepSpan);
365
567
  const isTopStepSpan = (span) => and(span.name === STEP_SPAN, stepAncestorOf(span) === void 0);
@@ -376,7 +578,7 @@ const nestedOf = (cause, depth) => cause === void 0 ? [] : layersOfCause(cause,
376
578
  const nestedCauseOf = (value, depth) => depth === 0 ? [] : nestedOf(causeFieldOf(value), depth);
377
579
  const causeChainOf = (value) => [value, ...nestedCauseOf(value, CAUSE_DEPTH)];
378
580
  const interruptTextOf = (reason) => reason.fiberId === void 0 ? "interrupted" : `interrupted by fiber #${reason.fiberId}`;
379
- const reasonPayloadOf = (reason) => Match.value(reason).pipe(Match.when({ _tag: "Fail" }, (fail) => fail.error), Match.when({ _tag: "Die" }, (die) => die.defect), Match.when({ _tag: "Interrupt" }, (interrupt) => interruptTextOf(interrupt)), Match.exhaustive);
581
+ const reasonPayloadOf = (reason) => Match$1.value(reason).pipe(Match$1.when({ _tag: "Fail" }, (fail) => fail.error), Match$1.when({ _tag: "Die" }, (die) => die.defect), Match$1.when({ _tag: "Interrupt" }, (interrupt) => interruptTextOf(interrupt)), Match$1.exhaustive);
380
582
  const reasonsOf = (cause) => cause.reasons.flatMap((reason) => causeChainOf(reasonPayloadOf(reason)));
381
583
  const layersOf = (failure) => Cause.isCause(failure) ? reasonsOf(failure) : causeChainOf(failure);
382
584
  const declineOf = (span) => stateOf(span) === "unfinished" ? NEVER_FINISHED : "";
@@ -462,17 +664,23 @@ const chainDetailLines = (layer) => isObject(layer) ? messageTailLines(layer) :
462
664
  const chainLayerLines = (layer, chosen) => [chainLineOf(layer, chosen), ...chainDetailLines(layer)];
463
665
  const chainLinesOf = (layers, chosen) => layers.slice(1).flatMap((layer) => chainLayerLines(layer, chosen));
464
666
  const quoted = (text) => `"${text.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
667
+ const REPLAY_FIELD = "replay";
668
+ const propertyReplayTextOf = (failure) => {
669
+ const layer = propertyFailureOf(failure);
670
+ return layer === void 0 ? void 0 : textFieldOf(layer, REPLAY_FIELD);
671
+ };
465
672
  const rerunReplayTextOf = (replay) => Option$1.getOrUndefined(Option$1.flatMap(Option$1.fromUndefinedOr(replay.seed), (seed) => Option$1.map(replayOfParts({
466
673
  seed,
467
674
  path: replay.path
468
675
  }), replayTextOf)));
676
+ const kernelReplayTextOf = (replay) => replay === void 0 ? void 0 : rerunReplayTextOf(replay);
469
677
  const replayPrefixText = (text) => text === void 0 ? "" : `CONFORMANCE_REPLAY="${text}" `;
470
- const replayPrefixOf = (replay) => replayPrefixText(replay === void 0 ? void 0 : rerunReplayTextOf(replay));
678
+ const replayPrefixOf = (replayText, replay) => replayPrefixText(replayText ?? kernelReplayTextOf(replay));
471
679
  const isCompleteIdentity = (identity) => and(identity.file.length > 0, identity.name.length > 0);
472
680
  const filterPrefixOf = (identity) => identity.package.length === 0 ? "" : `pnpm --filter ${identity.package} exec `;
473
- const rerunLineOf = (identity, replay) => isCompleteIdentity(identity) ? ` ${replayPrefixOf(replay)}${filterPrefixOf(identity)}vitest run ${identity.file} -t ` + quoted(identity.name) : void 0;
474
- const rerunLinesOf = (identity, replay) => {
475
- const line = rerunLineOf(identity, replay);
681
+ const rerunLineOf = (identity, replay, replayText) => isCompleteIdentity(identity) ? ` ${replayPrefixOf(replayText, replay)}${filterPrefixOf(identity)}vitest run ${identity.file} -t ` + quoted(identity.name) : void 0;
682
+ const rerunLinesOf = (identity, replay, replayText) => {
683
+ const line = rerunLineOf(identity, replay, replayText);
476
684
  return line === void 0 ? [] : ["Rerun only this scenario:", line];
477
685
  };
478
686
  const baselineReplay = (replay) => and(replay.seed === void 0, replay.path.length > 0);
@@ -501,7 +709,7 @@ const recordPartsOf = (input, layers) => {
501
709
  chain: chainLinesOf(layers, chosen),
502
710
  trail: trailLinesOf(input.spans),
503
711
  schedule: scheduleLinesOf(input.schedule),
504
- rerun: rerunLinesOf(input.identity, input.replay)
712
+ rerun: rerunLinesOf(input.identity, input.replay, propertyReplayTextOf(input.failure))
505
713
  };
506
714
  };
507
715
  const maybeLine = (line) => line === void 0 ? [] : [line];
@@ -510,7 +718,7 @@ const sectionLines = (header, lines) => lines.length === 0 ? [] : [
510
718
  ...lines,
511
719
  ""
512
720
  ];
513
- const linesOf = (parts) => [
721
+ const linesOf$1 = (parts) => [
514
722
  parts.headline,
515
723
  ...maybeLine(parts.raisedAt),
516
724
  parts.failingStep,
@@ -525,7 +733,7 @@ const breachIf = (holds, breach) => holds ? [breach] : [];
525
733
  const r6Broken = (parts, input) => or(parts.rerun.length === 0, replayOnBaseline(input.replay));
526
734
  const breachesOf = (parts, input, record) => [
527
735
  ...breachIf(parts.headline.length === 0, "R1"),
528
- ...breachIf(not(LOCATION.test(record)), "R2"),
736
+ ...breachIf(not$1(LOCATION.test(record)), "R2"),
529
737
  ...breachIf(r6Broken(parts, input), "R6")
530
738
  ];
531
739
  const hasRoot = (root) => root !== void 0 && root.length > 0;
@@ -543,11 +751,13 @@ const relativize = (record, root) => hasRoot(root) ? record.replaceAll(`${root}/
543
751
  const renderFailureRecord = (input) => {
544
752
  const layers = layersOf(input.failure);
545
753
  const parts = recordPartsOf(input, layers);
546
- const record = relativize(linesOf(parts).join("\n").trimEnd(), input.root);
754
+ const record = relativize(linesOf$1(parts).join("\n").trimEnd(), input.root);
755
+ const property = propertyFailureOf(input.failure);
547
756
  return {
548
757
  name: nameOf(layers),
549
758
  record,
550
- breaches: breachesOf(parts, input, record)
759
+ breaches: breachesOf(parts, input, record),
760
+ ...property === void 0 ? {} : { failure: property }
551
761
  };
552
762
  };
553
763
  const RECORD = Symbol.for("@systemfsoftware/vitest/FailureRecord");
@@ -617,6 +827,36 @@ const copyDiffFields = (error, failure) => {
617
827
  return carrier === void 0 ? error : copyFields(error, carrier);
618
828
  };
619
829
  /**
830
+ * The property-channel failure tags: a failure whose own fields ride onto the printed error and stay readable from
831
+ * the corpus record (R1, R2, R8). Tags, not the classes, so this module never imports the property engine.
832
+ */
833
+ const PROPERTY_FAILURE_TAGS = [
834
+ "PropertyRefuted",
835
+ "NonBooleanVerdict",
836
+ "CoverageBelowMinimum",
837
+ "SelfModelLaw",
838
+ "SeedStoreUnreadable",
839
+ "ReplayUnreadable",
840
+ "VacuousProperty"
841
+ ];
842
+ const isPropertyFailureLayer = (value) => {
843
+ const tag = tagFieldOf(value);
844
+ return tag !== void 0 && PROPERTY_FAILURE_TAGS.includes(tag);
845
+ };
846
+ const propertyFailureOf = (failure) => layerChainOf(failure).map(objectOrUndefined).filter(isObject).find(isPropertyFailureLayer);
847
+ /**
848
+ * Copies the property failure's `_tag` and every own field onto the printed error, so a reporter reading the raw
849
+ * error sees them. The message and stack stay untouched.
850
+ */
851
+ const copyOwnFields = (error, layer) => {
852
+ for (const field of Object.keys(layer)) copyField(error, layer, field);
853
+ return error;
854
+ };
855
+ const copyPropertyFields = (error, failure) => {
856
+ const layer = propertyFailureOf(failure);
857
+ return layer === void 0 ? error : copyOwnFields(error, layer);
858
+ };
859
+ /**
620
860
  * The `Error` Vitest prints for one record (R2, R7, R8): its `name` is the record's failure tag, its `message` is
621
861
  * the record, and its `stack` leads with that message — Vitest's JSON reporter hands a consumer `stack || message`,
622
862
  * so the record has to ride there too — followed by the record's first location, so no `effect` or library frame
@@ -654,7 +894,8 @@ const isFailureRecordError = (value) => isObject(value) && fieldOf(value, RECORD
654
894
  const throwFailureRecord = (input) => {
655
895
  if (isRefusalFailure(input.failure)) throw input.failure;
656
896
  const record = renderFailureRecord(input);
657
- throw copyDiffFields(printedError(input.failure, record), input.failure);
897
+ const printed = copyDiffFields(printedError(input.failure, record), input.failure);
898
+ throw copyPropertyFields(printed, input.failure);
658
899
  };
659
900
  const LEVEL_SEPARATOR = " > ";
660
901
  const EMPTY_TEXT = "";
@@ -684,7 +925,6 @@ const TOLERANCE = .9;
684
925
  const FIRST_BATCH = 64;
685
926
  const BATCH_CAP = 4096;
686
927
  const DRAW_CAP = 1e5;
687
- const FAILURE_HEAD = "coverage";
688
928
  /** The certainty's two-sided tail probability: `alpha / 2` for `alpha = 1 / CERTAINTY`. */
689
929
  const TAIL_PROBABILITY = 1 / (2 * CERTAINTY);
690
930
  const C1 = -.007784894002430293;
@@ -722,12 +962,6 @@ const wilson = (hits, runs, z) => {
722
962
  const zz = z * z;
723
963
  return (share + zz / (2 * runs) + z * Math.sqrt(share * (1 - share) / runs + zz / (4 * runs * runs))) / (1 + zz / runs);
724
964
  };
725
- /**
726
- * Thrown when a coverage class is confidently below its minimum share of runs.
727
- *
728
- * @internal
729
- */
730
- var CoverageBelowMinimum = class extends Data.Error {};
731
965
  const wilsonLower = (entry, runs) => wilson(entry.hits, runs, -TAIL_Z);
732
966
  const wilsonUpper = (entry, runs) => wilson(entry.hits, runs, TAIL_Z);
733
967
  const covered = (entry, runs) => wilsonLower(entry, runs) >= TOLERANCE * entry.minimum;
@@ -746,9 +980,12 @@ const judgeOf = (classes, runs) => ({
746
980
  });
747
981
  const openLabels = (judge) => [...judge.classes].filter(([, entry]) => !settled(entry, judge.runs)).map(([label]) => label);
748
982
  const failedEntries = (judge) => [...judge.classes].filter(([, entry]) => uncovered(entry, judge.runs));
749
- const describeShare = (hits, runs) => `${String(hits)}/${String(runs)}`;
750
- const describeFailure = (label, entry, runs) => `${FAILURE_HEAD} ${label}: ${describeShare(entry.hits, runs)} of runs, below the required ${String(entry.minimum)}`;
751
- const reportFailures = (judge) => failedEntries(judge).map(([label, entry]) => describeFailure(label, entry, judge.runs)).join("\n");
983
+ const failedFailures = (judge) => failedEntries(judge).map(([label, entry]) => ({
984
+ label,
985
+ hits: entry.hits,
986
+ runs: judge.runs,
987
+ minimum: entry.minimum
988
+ }));
752
989
  /** @internal */
753
990
  const countHit = Function.dual(2, (classes, label) => {
754
991
  const prior = classes.get(label);
@@ -771,48 +1008,237 @@ const sizeOf = (judge, batch) => Math.min(batch, DRAW_CAP - judge.runs);
771
1008
  const grown = (batch) => Math.min(batch * 2, BATCH_CAP);
772
1009
  const keepDrawing = (judge) => openLabels(judge).length > 0 && judge.runs < DRAW_CAP;
773
1010
  const toppedUp = (judge, draw, batch) => keepDrawing(judge) ? toppedUp(drawBatch(judge, draw, batch), draw, grown(batch)) : judge;
774
- const reportOf$1 = (judge) => failedEntries(judge).length === 0 ? void 0 : reportFailures(judge);
775
1011
  /**
776
1012
  * Runs R14's sequential draw: tops up the undecided classes until every class is decided or the draw cap
777
- * is reached, then reports the confidently under-covered classes, naming each label and its observed share.
1013
+ * is reached, then hands back the confidently under-covered classes as data, each with its label, hits, the runs
1014
+ * counted and the required share.
778
1015
  *
779
1016
  * @internal
780
1017
  */
781
- const judgeCoverage = (options) => reportOf$1(toppedUp(judgeOf(options.classes, options.runs), options.draw, FIRST_BATCH));
1018
+ const judgeCoverage = (options) => failedFailures(toppedUp(judgeOf(options.classes, options.runs), options.draw, FIRST_BATCH));
782
1019
  //#endregion
783
1020
  //#region src/internal/property/error.schema.ts
784
1021
  /**
785
- * The generator's choice that refuted a property, as data: the seed and the shrink path fast-check recorded. The
786
- * renderer reads it off the failure value instead of parsing the message (KTD5).
1022
+ * The closed set of property-channel failures (KTD1, R1): refuted, non-boolean, under-covered and self-model, plus
1023
+ * the vacuous impostor verdict and the seed store's read failure. Every variant is a `Schema.TaggedError` carrying
1024
+ * its own data — the property it names, a rendered-and-projected witness, coverage classes, the law that refused
1025
+ * itself — and a zero-argument `message` getter computed only from those fields. Wording is free; nothing reads a
1026
+ * message back.
1027
+ *
1028
+ * @since 4.0.0
1029
+ */
1030
+ /**
1031
+ * A property's seed: a whole number a generator chose, never negative (CONST-D3).
1032
+ *
1033
+ * @internal
1034
+ */
1035
+ const PropertySeed = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("PropertySeed"));
1036
+ /**
1037
+ * The count of runs a property's check was configured with, never negative (CONST-D3).
1038
+ *
1039
+ * @internal
1040
+ */
1041
+ const PropertyRunCount = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("PropertyRunCount"));
1042
+ /** @internal */
1043
+ const PropertyShrinkCount = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("PropertyShrinkCount"));
1044
+ /**
1045
+ * One witness: the text the renderer prints for a value, and a JSON-safe projection of the same traversal (R3, KD7).
787
1046
  *
788
1047
  * @internal
789
1048
  */
790
- const PropertyReplay = Schema$1.Struct({
791
- seed: Schema$1.Finite,
792
- path: Schema$1.Array(Schema$1.Finite)
1049
+ const Witness = Schema.Struct({
1050
+ rendered: Schema.String,
1051
+ value: Schema.Json
793
1052
  });
794
1053
  /**
795
- * A falsified property: the message the failure carries, and the generator's choice when the check result recorded
796
- * one, so the rerun line can replay it (KTD5).
1054
+ * One property involved in a failure: its full name, its declaration site, its seed and its run count (R2).
797
1055
  *
798
1056
  * @internal
799
1057
  */
800
- var PropertyRefuted = class extends Schema$1.TaggedError()("PropertyRefuted", {
801
- detail: Schema$1.String,
802
- replay: Schema$1.optional(PropertyReplay)
1058
+ const PropertyRun = Schema.Struct({
1059
+ name: Schema.String,
1060
+ site: Schema.NullOr(Schema.String),
1061
+ seed: PropertySeed,
1062
+ runs: PropertyRunCount
1063
+ });
1064
+ /**
1065
+ * The kinds a non-boolean verdict can be: every `typeof` result, plus an `Effect` on the sync lane (R4).
1066
+ *
1067
+ * @internal
1068
+ */
1069
+ const VerdictKind = Schema.Literals([
1070
+ "undefined",
1071
+ "object",
1072
+ "boolean",
1073
+ "number",
1074
+ "bigint",
1075
+ "string",
1076
+ "symbol",
1077
+ "function",
1078
+ "effect"
1079
+ ]);
1080
+ /**
1081
+ * A falsified property: the property that ran, the shrunk counterexample the generator settled on, and how many
1082
+ * shrink steps it took to reach it (R2-R4).
1083
+ *
1084
+ * @internal
1085
+ */
1086
+ var PropertyRefuted = class extends Schema.TaggedError()("PropertyRefuted", {
1087
+ property: PropertyRun,
1088
+ counterexample: Witness,
1089
+ shrinks: PropertyShrinkCount,
1090
+ replay: Schema.String
803
1091
  }) {
804
1092
  get message() {
805
- return this.detail;
1093
+ return `${this.property.name}: the property was falsified within ${String(this.property.runs)} run(s), and ${String(this.shrinks)} shrink(s) settled on its input. Shrunk input: ${this.counterexample.rendered}`;
806
1094
  }
807
1095
  };
808
1096
  /**
809
- * Thrown at file end when no property in the file refuted a subject's constant impostor (R12).
1097
+ * A verdict that was not a literal boolean: the property that ran, the first drawn values whose verdict was not a
1098
+ * boolean, and every kind of value the property returned (R4).
810
1099
  *
811
1100
  * @internal
812
1101
  */
813
- var VacuousProperty = class extends Schema$1.TaggedError()("VacuousProperty", { detail: Schema$1.String }) {
1102
+ var NonBooleanVerdict = class extends Schema.TaggedError()("NonBooleanVerdict", {
1103
+ property: PropertyRun,
1104
+ drawn: Witness,
1105
+ returned: Schema.Array(VerdictKind),
1106
+ replay: Schema.String
1107
+ }) {
814
1108
  get message() {
815
- return this.detail;
1109
+ return `${this.property.name}: the property returned no boolean; it returned ${this.returned.join(", ")}. A property must return a literal boolean verdict — asserting with expect(), or returning an Option, Result or object, is not a verdict.`;
1110
+ }
1111
+ };
1112
+ /**
1113
+ * One coverage class left under its minimum: its label, hits, runs and required share (R7).
1114
+ *
1115
+ * @internal
1116
+ */
1117
+ const CoverageClassFailure = Schema.Struct({
1118
+ label: Schema.String,
1119
+ hits: Schema.Int,
1120
+ runs: Schema.Int,
1121
+ minimum: Schema.Finite
1122
+ });
1123
+ /**
1124
+ * A property whose declared coverage classes stayed under their minima: the property and the failed classes, in
1125
+ * declaration order (R7).
1126
+ *
1127
+ * @internal
1128
+ */
1129
+ var CoverageBelowMinimum = class extends Schema.TaggedError()("CoverageBelowMinimum", {
1130
+ property: PropertyRun,
1131
+ classes: Schema.Array(CoverageClassFailure),
1132
+ replay: Schema.String
1133
+ }) {
1134
+ get message() {
1135
+ const lines = this.classes.map((entry) => `coverage ${entry.label}: ${entry.hits}/${entry.runs} of runs, below the required ${entry.minimum}`);
1136
+ return `${this.property.name}: ${lines.join("\n")}`;
1137
+ }
1138
+ };
1139
+ /**
1140
+ * A model law given the subject itself as its oracle, so it compares nothing (R13).
1141
+ *
1142
+ * @internal
1143
+ */
1144
+ var SelfModelLaw = class extends Schema.TaggedError()("SelfModelLaw", {
1145
+ name: Schema.String,
1146
+ site: Schema.NullOr(Schema.String)
1147
+ }) {
1148
+ get message() {
1149
+ return `${this.name}: the model is the subject itself; a model law must compare against an independent oracle.`;
1150
+ }
1151
+ };
1152
+ /**
1153
+ * A seed-store line that could not be decoded (R16, KTD6).
1154
+ *
1155
+ * @internal
1156
+ */
1157
+ var SeedStoreUnreadable = class extends Schema.TaggedError()("SeedStoreUnreadable", {
1158
+ file: Schema.String,
1159
+ line: Schema.Int,
1160
+ detail: Schema.String
1161
+ }) {
1162
+ get message() {
1163
+ return `the seed store ${this.file} line ${this.line} could not be read: ${this.detail}`;
1164
+ }
1165
+ };
1166
+ /**
1167
+ * What a subject's constant impostor froze for one property: each frozen key — the subject itself, or a record
1168
+ * member — with the value its first call returned (R4, R6). Empty when the property never called the subject.
1169
+ *
1170
+ * @internal
1171
+ */
1172
+ const FrozenOutput = Schema.Struct({
1173
+ member: Schema.String,
1174
+ output: Witness
1175
+ });
1176
+ /**
1177
+ * The impostor's frozen outputs (R4).
1178
+ *
1179
+ * @internal
1180
+ */
1181
+ const Frozen = Schema.TaggedStruct("Frozen", { outputs: Schema.Array(FrozenOutput) });
1182
+ /**
1183
+ * A property whose body never called the subject during the vacuity check: nothing was frozen (R6).
1184
+ *
1185
+ * @internal
1186
+ */
1187
+ const NeverCalled = Schema.TaggedStruct("NeverCalled", {});
1188
+ /**
1189
+ * One non-refuting property of a vacuous subject: the property it ran, and whether the impostor was called (R5).
1190
+ *
1191
+ * @internal
1192
+ */
1193
+ const VacuousPropertyRun = Schema.Struct({
1194
+ property: PropertyRun,
1195
+ frozen: Schema.Union([Frozen, NeverCalled])
1196
+ });
1197
+ /**
1198
+ * One vacuous subject: what it is, and every non-refuting property in the file that ran over it (R5).
1199
+ *
1200
+ * @internal
1201
+ */
1202
+ const VacuousSubject = Schema.Struct({
1203
+ label: Schema.String,
1204
+ properties: Schema.Array(VacuousPropertyRun)
1205
+ });
1206
+ /**
1207
+ * A law kind exempt from the impostor gate in a refused file: its name and the kind that exempted it (R6).
1208
+ *
1209
+ * @internal
1210
+ */
1211
+ const ExemptLaw = Schema.Struct({
1212
+ name: Schema.String,
1213
+ kind: Schema.String
1214
+ });
1215
+ const REPAIR_ADVICE = "Laws that only relate outputs to each other (additivity, idempotence, commutativity, round trips through the subject) hold for such constants. Pin the output to the input: compare against an independent model (`subject(x)` equals a straightforward reimplementation), or conjoin a base case (`subject([one])` equals its known value). Also check that the body calls the `subject` it was given, not the imported implementation.";
1216
+ const fakeTextOf = (run) => Match.value(run.frozen).pipe(Match.tag("Frozen", (frozen) => frozen.outputs.map((entry) => `${entry.member} always returned ${entry.output.rendered}`)), Match.tag("NeverCalled", () => []), Match.exhaustive);
1217
+ const subjectMessageOf = (subject) => {
1218
+ const names = subject.properties.map((run) => run.property.name).join(", ");
1219
+ const runs = subject.properties.reduce((total, run) => total + run.property.runs, 0);
1220
+ const fakes = subject.properties.flatMap(fakeTextOf);
1221
+ const fake = fakes.length === 0 ? "was never called" : fakes.join("; ");
1222
+ return `${names}: no property in this file refuted the constant impostor of this subject (${subject.label}), so nothing here pins it down. It held for ${String(runs)} run(s) against a fake that ${fake}, whatever the input. ${REPAIR_ADVICE}`;
1223
+ };
1224
+ /**
1225
+ * Thrown at file end when no property in the file refuted a subject's constant impostor (R12, R5, R6).
1226
+ *
1227
+ * @internal
1228
+ */
1229
+ var VacuousProperty = class extends Schema.TaggedError()("VacuousProperty", {
1230
+ subjects: Schema.Array(VacuousSubject),
1231
+ exempt: Schema.Array(ExemptLaw),
1232
+ replay: Schema.String
1233
+ }) {
1234
+ get message() {
1235
+ return this.subjects.map(subjectMessageOf).join("\n\n");
1236
+ }
1237
+ };
1238
+ /** @internal */
1239
+ var ReplayUnreadable = class extends Schema.TaggedError()("ReplayUnreadable", { text: Schema.String }) {
1240
+ get message() {
1241
+ return `CONFORMANCE_REPLAY names neither a seed and path nor a property entry: ${this.text}`;
816
1242
  }
817
1243
  };
818
1244
  /**
@@ -820,19 +1246,82 @@ var VacuousProperty = class extends Schema$1.TaggedError()("VacuousProperty", {
820
1246
  *
821
1247
  * @internal
822
1248
  */
823
- var NonTupleOf = class extends Schema$1.TaggedError()("NonTupleOf", { detail: Schema$1.String }) {
1249
+ var NonTupleOf = class extends Schema.TaggedError()("NonTupleOf", { detail: Schema.String }) {
824
1250
  get message() {
825
1251
  return this.detail;
826
1252
  }
827
1253
  };
828
1254
  //#endregion
1255
+ //#region src/internal/property/replay.schema.ts
1256
+ /**
1257
+ * The token effect's falsification runner records, as the Schema that decodes it (KTD5). One falsification's
1258
+ * coordinates ride in a JSON array: a leading `0` naming a numeric seed, the seed text, the attempt, the size,
1259
+ * the shrink path, and the failure tag.
1260
+ *
1261
+ * @since 4.0.0
1262
+ */
1263
+ /** @internal */
1264
+ const ReplayToken = Schema$1.fromJsonString(Schema$1.Tuple([
1265
+ Schema$1.Finite,
1266
+ Schema$1.Union([Schema$1.Finite, Schema$1.String]),
1267
+ Schema$1.Finite,
1268
+ Schema$1.Finite,
1269
+ Schema$1.Array(Schema$1.Finite),
1270
+ Schema$1.String
1271
+ ]));
1272
+ //#endregion
1273
+ //#region src/internal/property/replay.ts
1274
+ /**
1275
+ * The property replay channel (KTD5, R9-R12): the text a property failure carries, the generator replay token a
1276
+ * refuted entry rebuilds, and the entry the engine selects for a property's identity hash.
1277
+ *
1278
+ * The token `effect`'s falsification runner writes is decoded here through its codec — never hand-parsed — into the
1279
+ * parts a refuted entry names, and rebuilt from them the way `makeReplay` reads them back.
1280
+ *
1281
+ * @since 4.0.0
1282
+ */
1283
+ const NUMERIC_SEED = 0;
1284
+ /** @internal */
1285
+ const tokenOfReplay = (entry) => JSON.stringify([
1286
+ NUMERIC_SEED,
1287
+ `${entry.seed}`,
1288
+ entry.attempt,
1289
+ entry.size,
1290
+ entry.path,
1291
+ entry.failure
1292
+ ]);
1293
+ /** @internal */
1294
+ const refutedReplayTextOf = (identity) => Option$1.getOrUndefined(Option$1.map(Schema$1.decodeOption(ReplayToken)(identity.token), (decoded) => refutedReplayText$1({
1295
+ property: identity.property,
1296
+ seed: identity.seed,
1297
+ runs: identity.runs,
1298
+ attempt: decoded[2],
1299
+ size: decoded[3],
1300
+ path: decoded[4],
1301
+ failure: decoded[5]
1302
+ })));
1303
+ /** @internal */
1304
+ const plainReplayTextOf = (identity) => plainReplayText(identity);
1305
+ /** @internal */
1306
+ const plainReplayEntriesText = (entries) => plainReplayTexts(entries);
1307
+ const isEntries = (channel) => Array.isArray(channel);
1308
+ /** @internal */
1309
+ const selectReplayEntry = (selection) => isEntries(selection.channel) ? selection.channel.find((entry) => entry.property === selection.hash) : void 0;
1310
+ //#endregion
829
1311
  //#region src/internal/property/impostor.ts
1312
+ /**
1313
+ * @internal The constant impostor of R12: a fake of the subject under test that
1314
+ * returns the first output it ever produced, for every later call, whatever the
1315
+ * input it was given.
1316
+ *
1317
+ * A property that still holds for the impostor does not constrain the subject.
1318
+ * Whether that is fatal is a per-file question — a subject's impostor must be
1319
+ * refuted by some property in its file — so this module builds the fake and
1320
+ * collects the verdicts; the engine decides them once, when the file finishes
1321
+ * (KTD10).
1322
+ */
830
1323
  const SUBJECT_KEY = "the subject";
831
- const NO_VALUE = "";
832
- const isCallable = (value) => typeof value === "function";
833
1324
  const isCallableSubject = (target) => typeof target === "function";
834
- const describeValue = (value) => isCallable(value) ? "a function" : String(value);
835
- const describeEntry = (key, value) => `${key} always returned ${describeValue(value)}`;
836
1325
  const emptyState = () => ({ outputs: /* @__PURE__ */ new Map() });
837
1326
  const frozenAt = (state, key, compute) => state.outputs.has(key) ? state.outputs.get(key) : remember(state, key, compute());
838
1327
  const remember = (state, key, value) => {
@@ -850,7 +1339,10 @@ const impostorHandler = (state) => ({
850
1339
  apply: (target, thisArg, args) => appliedImpostor(state, target, thisArg, args),
851
1340
  get: (target, property) => readImpostor(state, target, property)
852
1341
  });
853
- const describeFrozen = (state) => [...state.outputs].map(([key, value]) => describeEntry(key, value)).join("; ");
1342
+ const frozenOutputs = (state) => [...state.outputs].map(([member, output]) => ({
1343
+ member,
1344
+ output
1345
+ }));
854
1346
  /**
855
1347
  * A constant impostor of `subject`: a function returns its first output forever, and so does each member
856
1348
  * of a record.
@@ -861,48 +1353,75 @@ const impostorOf = (subject) => {
861
1353
  const state = emptyState();
862
1354
  return {
863
1355
  impostor: new Proxy(subject, impostorHandler(state)),
864
- frozen: () => describeFrozen(state)
1356
+ frozen: () => frozenOutputs(state)
865
1357
  };
866
1358
  };
867
1359
  /** @internal */
868
1360
  const subjectLabel = (subject) => isCallableSubject(subject) ? `a function of ${String(subject.length)} argument(s)` : `a record of ${Object.keys(subject).sort().join(", ")}`;
869
1361
  const judgeFor = (subject) => ({
870
1362
  label: subjectLabel(subject),
871
- properties: [],
872
- refuters: [],
873
- frozen: NO_VALUE,
874
- runs: 0
1363
+ verdicts: [],
1364
+ refuters: []
875
1365
  });
876
- const trackProperty = (judge, property) => judge.properties.includes(property) ? judge : {
877
- ...judge,
878
- properties: [...judge.properties, property]
879
- };
880
- const trackRefuter = (judge, property) => judge.refuters.includes(property) ? judge : {
881
- ...judge,
882
- refuters: [...judge.refuters, property]
883
- };
884
- const trackVerdict = (judge, property, verdict) => {
885
- const named = trackProperty(judge, property);
886
- return verdict.refuted ? trackRefuter(named, property) : {
887
- ...named,
888
- frozen: verdict.frozen,
889
- runs: verdict.runs
1366
+ const addName = (names, name) => names.includes(name) ? names : [...names, name];
1367
+ const trackVerdict = (judge, verdict) => {
1368
+ const named = {
1369
+ ...judge,
1370
+ refuters: addName(judge.refuters, verdict.property.name)
1371
+ };
1372
+ return verdict.refuted ? named : {
1373
+ ...judge,
1374
+ verdicts: [...judge.verdicts, {
1375
+ property: verdict.property,
1376
+ identity: verdict.identity,
1377
+ frozen: verdict.frozen
1378
+ }]
890
1379
  };
891
1380
  };
892
- const recordJudge = (state, subject, property, verdict) => {
1381
+ const recordJudge = (state, subject, verdict) => {
893
1382
  const prior = state.judges.get(subject) ?? judgeFor(subject);
894
- state.judges.set(subject, trackVerdict(prior, property, verdict));
1383
+ state.judges.set(subject, trackVerdict(prior, verdict));
895
1384
  };
896
1385
  const isVacuous = (judge) => judge.refuters.length === 0;
897
- const describeVacuous = (judge) => `${judge.properties.join(", ")}: no property in this file refuted the constant impostor of this subject (${judge.label}), so nothing here pins it down. It held for ${judge.runs} run(s) against a fake that ${judge.frozen === NO_VALUE ? "was never called" : judge.frozen}, whatever the input. Laws that only relate outputs to each other (additivity, idempotence, commutativity, round trips through the subject) hold for such constants. Pin the output to the input: compare against an independent model (\`subject(x)\` equals a straightforward reimplementation), or conjoin a base case (\`subject([one])\` equals its known value). Also check that the body calls the \`subject\` it was given, not the imported implementation.`;
898
- const finaliseJudges = (state, onProblems) => {
899
- const vacuous = [...state.judges.values()].filter(isVacuous).map(describeVacuous);
900
- if (vacuous.length === 0) return;
901
- throw onProblems(vacuous.join("\n\n"));
902
- };
903
- const ledgerOf = (state, onProblems) => ({
904
- record: (subject, property, verdict) => recordJudge(state, subject, property, verdict),
905
- finalise: () => finaliseJudges(state, onProblems)
1386
+ const frozenOf = (frozen) => frozen.length === 0 ? { _tag: "NeverCalled" } : {
1387
+ _tag: "Frozen",
1388
+ outputs: frozen.map(({ member, output }) => ({
1389
+ member,
1390
+ output: witnessOf(output)
1391
+ }))
1392
+ };
1393
+ const propertyRunOf$1 = (verdict) => ({
1394
+ property: verdict.property,
1395
+ frozen: frozenOf(verdict.frozen)
1396
+ });
1397
+ const subjectOf = (judge) => ({
1398
+ label: judge.label,
1399
+ properties: judge.verdicts.map(propertyRunOf$1)
1400
+ });
1401
+ const vacuousReplayTextOf = (judges) => plainReplayEntriesText(judges.flatMap((judge) => judge.verdicts.map((verdict) => ({
1402
+ property: verdict.identity,
1403
+ seed: verdict.property.seed,
1404
+ runs: verdict.property.runs
1405
+ }))));
1406
+ const refuseVacuous = (judges, exempt) => {
1407
+ const vacuous = judges.filter(isVacuous);
1408
+ return vacuous.length === 0 ? void 0 : new VacuousProperty({
1409
+ subjects: vacuous.map(subjectOf),
1410
+ exempt: [...exempt],
1411
+ replay: vacuousReplayTextOf(vacuous)
1412
+ });
1413
+ };
1414
+ const finaliseJudges = (state) => refuseVacuous([...state.judges.values()], state.exempts);
1415
+ const ledgerOf = (state) => ({
1416
+ record: (subject, verdict) => recordJudge(state, subject, verdict),
1417
+ recordExempt: (name, kind) => {
1418
+ if (state.exempts.some((exempt) => exempt.name === name)) return;
1419
+ state.exempts.push({
1420
+ name,
1421
+ kind
1422
+ });
1423
+ },
1424
+ finalise: () => finaliseJudges(state)
906
1425
  });
907
1426
  /**
908
1427
  * Collects the impostor verdicts of every property in the file and decides, at file end, which subjects
@@ -910,7 +1429,10 @@ const ledgerOf = (state, onProblems) => ({
910
1429
  *
911
1430
  * @internal
912
1431
  */
913
- const makeFileLedger = (onProblems) => ledgerOf({ judges: /* @__PURE__ */ new Map() }, onProblems);
1432
+ const makeFileLedger = () => ledgerOf({
1433
+ judges: /* @__PURE__ */ new Map(),
1434
+ exempts: []
1435
+ });
914
1436
  //#endregion
915
1437
  //#region src/internal/property/kinds.ts
916
1438
  /**
@@ -947,46 +1469,140 @@ const idempotentHolds = () => (subject, values) => sameTwice(subject, firstOf(sp
947
1469
  /** @internal */
948
1470
  const deterministicHolds = () => (subject, values) => Equal.equals(call(subject, values), call(subject, values));
949
1471
  //#endregion
950
- //#region src/internal/property/replay.schema.ts
1472
+ //#region src/internal/property/seed-store.schema.ts
951
1473
  /**
952
- * The token effect's falsification runner records, as the Schema that decodes it (KTD5). One falsification's
953
- * coordinates ride in a JSON array: a leading `0` naming a numeric seed, the seed text, the attempt, the size,
954
- * the shrink path, and the failure tag.
1474
+ * @internal The seed store's line grammar (KTD6, R16, R18): one JSON Lines entry per failing run, decoded as a
1475
+ * tagged union. A refuted run carries the property's full name plus the parts a generator replay token is rebuilt
1476
+ * from; a non-boolean run carries the name, the seed and the run count. The codec a line is read through lives
1477
+ * here; the decisions over an entry live in `seed-record.ts`.
955
1478
  *
956
1479
  * @since 4.0.0
957
1480
  */
958
1481
  /** @internal */
959
- const ReplayToken = Schema$1.fromJsonString(Schema$1.Tuple([
960
- Schema$1.Finite,
961
- Schema$1.Union([Schema$1.Finite, Schema$1.String]),
962
- Schema$1.Finite,
963
- Schema$1.Finite,
964
- Schema$1.Array(Schema$1.Finite),
965
- Schema$1.String
966
- ]));
1482
+ const SeedStoreRefuted = Schema$1.TaggedStruct("Refuted", {
1483
+ property: Schema$1.String,
1484
+ seed: PropertySeed,
1485
+ attempt: PropertyRunCount,
1486
+ size: PropertyRunCount,
1487
+ path: Schema$1.Array(ReplayStep),
1488
+ failure: ReplayFailureTag
1489
+ });
1490
+ /** @internal */
1491
+ const SeedStoreNonBoolean = Schema$1.TaggedStruct("NonBoolean", {
1492
+ property: Schema$1.String,
1493
+ seed: PropertySeed,
1494
+ runs: PropertyRunCount
1495
+ });
1496
+ /** @internal */
1497
+ const SeedStoreEntry = Schema$1.Union([SeedStoreRefuted, SeedStoreNonBoolean]);
1498
+ /** @internal */
1499
+ const SeedStoreLine = Schema$1.fromJsonString(SeedStoreEntry);
967
1500
  //#endregion
968
- //#region src/internal/property/replay.ts
1501
+ //#region src/internal/property/seed-record.ts
969
1502
  /**
970
- * The replay value a property counterexample carries (KTD5): the seed and the shrink path the generator recorded,
971
- * read off the failure value and out of the check result as data. Nothing here reads a failure message.
972
- *
973
- * The token `effect`'s falsification runner writes is decoded here — never hand-parsed — into the seed and path R6
974
- * prints.
1503
+ * @internal The seed store's pure decisions (KTD6, R16-R19): the codec a line is read through, the entries a
1504
+ * property claims, whether a failure is worth appending, and the replay a stored entry names. No file access
1505
+ * lives here — the shell `seed-store.ts` reads and appends lines, and the line grammar is `seed-store.schema.ts`.
975
1506
  *
976
1507
  * @since 4.0.0
977
1508
  */
978
- /** The token's leading kind that names a numeric seed. */
979
- const NUMERIC_SEED = 0;
980
- const numericSeedOf = (token) => token[0] === NUMERIC_SEED ? Number(token[1]) : void 0;
981
- const replayOf = (token) => Option$1.map(Option$1.fromNullishOr(numericSeedOf(token)), (seed) => ({
982
- seed,
983
- path: token[4]
1509
+ const decodeLine = (file, index, text) => Result.mapError(Schema$1.decodeResult(SeedStoreLine)(text), (error) => new SeedStoreUnreadable({
1510
+ file,
1511
+ line: index + 1,
1512
+ detail: error.message
984
1513
  }));
985
1514
  /** @internal */
986
- const replayOfToken = (replay) => Option$1.getOrUndefined(Option$1.flatMap(Schema$1.decodeOption(ReplayToken)(replay), replayOf));
987
- const refutationOf = (value) => Schema$1.is(PropertyRefuted)(value) ? value : void 0;
1515
+ const decodeStoreLines = Function.dual(2, (file, lines) => Result.all(lines.map((text, index) => decodeLine(file, index, text))));
988
1516
  /** @internal */
989
- const replayOfFailure = (value) => Option$1.getOrUndefined(Option$1.flatMap(Option$1.fromNullishOr(refutationOf(value)), (failure) => Option$1.fromNullishOr(failure.replay)));
1517
+ const entriesForProperty = Function.dual(2, (entries, property) => entries.filter((entry) => entry.property === property));
1518
+ const sameEntry = (left, right) => JSON.stringify(left) === JSON.stringify(right);
1519
+ const derandomized = (budget) => budget?.seed !== void 0;
1520
+ const recordingOptedOut = (budget) => budget?.record === false;
1521
+ const alreadyRecorded = (existing, candidate) => existing.some((entry) => sameEntry(entry, candidate));
1522
+ const not = (value) => value === false;
1523
+ /** @internal */
1524
+ const shouldAppendEntry = (input) => [
1525
+ derandomized(input.budget),
1526
+ recordingOptedOut(input.budget),
1527
+ alreadyRecorded(input.existing, input.candidate)
1528
+ ].every(not);
1529
+ /** @internal */
1530
+ const refutedEntryOf = (input) => Option$1.flatMap(Schema$1.decodeOption(ReplayToken)(input.token), (parts) => Schema$1.decodeUnknownOption(SeedStoreEntry)({
1531
+ _tag: "Refuted",
1532
+ property: input.property,
1533
+ seed: input.seed,
1534
+ attempt: parts[2],
1535
+ size: parts[3],
1536
+ path: parts[4],
1537
+ failure: parts[5]
1538
+ }));
1539
+ /** @internal */
1540
+ const nonBooleanEntryOf = (input) => Schema$1.decodeOption(SeedStoreEntry)({
1541
+ _tag: "NonBoolean",
1542
+ property: input.property,
1543
+ seed: input.seed,
1544
+ runs: input.runs
1545
+ });
1546
+ /** @internal */
1547
+ const replayEntryOf$1 = Function.dual(2, (entry, hash) => Match$1.value(entry).pipe(Match$1.tag("Refuted", (refuted) => Schema$1.decodeOption(RefutedPropertyReplay)({
1548
+ _tag: "Refuted",
1549
+ property: hash,
1550
+ seed: refuted.seed,
1551
+ runs: 1,
1552
+ attempt: refuted.attempt,
1553
+ size: refuted.size,
1554
+ path: refuted.path,
1555
+ failure: refuted.failure
1556
+ })), Match$1.tag("NonBoolean", (plain) => Schema$1.decodeOption(PlainPropertyReplay)({
1557
+ _tag: "Plain",
1558
+ property: hash,
1559
+ seed: plain.seed,
1560
+ runs: plain.runs
1561
+ })), Match$1.exhaustive));
1562
+ //#endregion
1563
+ //#region src/internal/property/seed-store.ts
1564
+ /**
1565
+ * @internal The seed store's shell (KTD6, R16): the asynchronous file access the engine uses — read a test file's
1566
+ * store lines beside it, and append one failing entry's line. The line grammar is `seed-store.schema.ts` and the
1567
+ * decisions are `seed-record.ts`; nothing reads or writes a file synchronously here.
1568
+ *
1569
+ * @since 4.0.0
1570
+ */
1571
+ const STORE_DIRECTORY = "__property_seeds__";
1572
+ /** @internal */
1573
+ const storeFileOf = (filepath) => Effect$1.map(Effect$1.service(Path.Path), (path) => path.join(path.dirname(filepath), STORE_DIRECTORY, `${path.basename(filepath)}.jsonl`));
1574
+ const linesOf = (text) => text.length === 0 ? [] : text.replace(/\n$/u, "").split("\n");
1575
+ const readText = (file) => Effect$1.flatMap(Effect$1.service(FileSystem.FileSystem), (fs) => fs.readFileString(file).pipe(Effect$1.catchReason("PlatformError", "NotFound", () => Effect$1.succeed(""))));
1576
+ /** @internal */
1577
+ const readStoreLines = (file) => Effect$1.map(readText(file).pipe(Effect$1.orDie), linesOf);
1578
+ const appendLine = (file, line) => Effect$1.flatMap(Effect$1.service(FileSystem.FileSystem), (fs) => Effect$1.flatMap(Effect$1.service(Path.Path), (path) => Effect$1.flatMap(fs.makeDirectory(path.dirname(file), { recursive: true }).pipe(Effect$1.orDie), () => fs.writeFileString(file, `${line}\n`, { flag: "a" }).pipe(Effect$1.orDie))));
1579
+ const appendEntry = (filepath, entry) => Effect$1.flatMap(storeFileOf(filepath), (file) => Effect$1.flatMap(Schema$1.encodeEffect(SeedStoreLine)(entry).pipe(Effect$1.orDie), (line) => appendLine(file, line)));
1580
+ /** @internal */
1581
+ const appendStoreEntry = Function.dual(2, appendEntry);
1582
+ //#endregion
1583
+ //#region src/internal/property/seed.ts
1584
+ const FNV_OFFSET_BASIS = 2166136261;
1585
+ const FNV_PRIME = 16777619;
1586
+ const utf8 = new TextEncoder();
1587
+ const PART_SEPARATOR = "\0";
1588
+ /** @internal */
1589
+ const fnv1a32 = (text) => utf8.encode(text).reduce((hash, byte) => Math.imul(hash ^ byte, FNV_PRIME), FNV_OFFSET_BASIS) >>> 0;
1590
+ const identityParts = (identity) => [
1591
+ identity.package,
1592
+ identity.file,
1593
+ identity.name
1594
+ ];
1595
+ /** @internal */
1596
+ const identitySeed = Function.dual(2, (salt, identity) => fnv1a32([String(salt), ...identityParts(identity)].join(PART_SEPARATOR)));
1597
+ /** @internal */
1598
+ const identityHash = (identity) => fnv1a32(identityParts(identity).join(PART_SEPARATOR));
1599
+ /** @internal */
1600
+ const topUpSeed = Function.dual(2, (seed, drawn) => fnv1a32(`${seed}${PART_SEPARATOR}${drawn}`));
1601
+ const ownSeed = (seed) => Match$1.value(seed).pipe(Match$1.when(Match$1.number, (literal) => literal), Match$1.when(Match$1.string, fnv1a32), Match$1.exhaustive);
1602
+ const providedSeed = (input) => Option$1.map(Option$1.fromNullishOr(input.provided), identitySeed(input.identity));
1603
+ const saltOrProvided = (input) => Option$1.orElse(Option$1.map(Option$1.fromNullishOr(input.own), ownSeed), () => providedSeed(input));
1604
+ /** @internal */
1605
+ const resolveSeed = (input) => Option$1.getOrElse(saltOrProvided(input), () => input.fresh);
990
1606
  //#endregion
991
1607
  //#region src/internal/property/engine.ts
992
1608
  /**
@@ -999,11 +1615,24 @@ const replayOfFailure = (value) => Option$1.getOrUndefined(Option$1.flatMap(Opti
999
1615
  * receives the generated values exactly as drawn, typed by the gens that produced them.
1000
1616
  */
1001
1617
  const REWRITE = "it.prop(name, { of, subject, runs }, holds)";
1618
+ const newViolations = () => ({
1619
+ kinds: [],
1620
+ drawn: void 0,
1621
+ seen: false
1622
+ });
1002
1623
  const DEFAULT_RUNS = 100;
1003
1624
  const isPositiveInteger = (runs) => Number.isInteger(runs) && runs > 0;
1004
1625
  const describeRuns = (runs) => Number.isInteger(runs) ? `${String(runs)}` : "a non-integer";
1005
1626
  const invalidPropertyBudget = (name, runs) => `${name}: pass a positive integer \`runs\`; received ${describeRuns(runs)}. Write ${REWRITE}.`;
1006
1627
  const invalidProvidedBudget = (runs) => `the configured property budget (${checkDefaultsKey}) supplies \`runs\`: ${describeRuns(runs)}; it must be a positive integer.`;
1628
+ const invalidPropertySeed = (name, seed) => `${name}: pass a non-negative integer \`seed\`; received ${describeRuns(seed)}. Write ${REWRITE}.`;
1629
+ const isNonInteger = (value) => Number.isInteger(value) === false;
1630
+ const isNegative = (value) => value < 0;
1631
+ const isInvalidSeedNumber = (value) => isNonInteger(value) || isNegative(value);
1632
+ const isInvalidSeed = (seed) => typeof seed === "number" && isInvalidSeedNumber(seed);
1633
+ const requireOwnSeed = (name, seed) => {
1634
+ if (isInvalidSeed(seed)) throw new InvalidBudget({ detail: invalidPropertySeed(name, seed) });
1635
+ };
1007
1636
  const requirePositiveRuns = (message, runs) => {
1008
1637
  if (isInvalidRuns(runs)) throw new InvalidBudget({ detail: message(runs) });
1009
1638
  };
@@ -1022,15 +1651,30 @@ const resolvedRuns = (options) => options.runs ?? DEFAULT_RUNS;
1022
1651
  /**
1023
1652
  * The effective check options: the property's own fields over the configured default over `runs: 100`,
1024
1653
  * merged field by field so a property that sets only `runs` still inherits the configured size and caps.
1654
+ * The resolved seed replaces the provided and explicit `seed` fields, which are inputs to the derivation
1655
+ * rather than a shared stream (KTD4).
1025
1656
  */
1026
- const resolveBudget = (name, spec, provided) => {
1657
+ const EMPTY_PROVIDED_BUDGET = {};
1658
+ const resolveBudget = (name, spec, task, fresh) => {
1027
1659
  const explicit = explicitOptions(spec);
1660
+ const provided = task.budget ?? EMPTY_PROVIDED_BUDGET;
1028
1661
  requirePositiveRuns((runs) => invalidPropertyBudget(name, runs), explicit.runs);
1029
- requirePositiveRuns(invalidProvidedBudget, provided?.runs);
1030
- const options = mergedOptions(provided, explicit);
1662
+ requirePositiveRuns(invalidProvidedBudget, provided.runs);
1663
+ requireOwnSeed(name, explicit.seed);
1664
+ const options = mergedOptions(task.budget, explicit);
1665
+ const seed = resolveSeed({
1666
+ own: explicit.seed,
1667
+ provided: provided.seed,
1668
+ identity: task.identity,
1669
+ fresh
1670
+ });
1031
1671
  return {
1032
1672
  runs: resolvedRuns(options),
1033
- options
1673
+ options: {
1674
+ ...options,
1675
+ seed
1676
+ },
1677
+ seed
1034
1678
  };
1035
1679
  };
1036
1680
  const toArbitrary = (input) => Schema$1.isSchema(input) ? Arbitrary.schema(input) : input;
@@ -1061,12 +1705,21 @@ const observeRun = (classes, counter, entries, values) => {
1061
1705
  counter.runs = counter.runs + 1;
1062
1706
  for (const label of satisfiedLabels(entries, values)) countHit(classes, label);
1063
1707
  };
1064
- const drawFor = (entries, arbitrary) => ({ more: (count) => Effect$1.runSync(Arbitrary.sampleEffect(arbitrary, { count }).pipe(Effect$1.orDie)).map((values) => satisfiedLabels(entries, values)) });
1708
+ const drawFor = (entries, arbitrary, seed) => {
1709
+ const drawn = Ref.makeUnsafe(0);
1710
+ return { more: (count) => {
1711
+ const soFar = Effect$1.runSync(Ref.getAndUpdate(drawn, (total) => total + count));
1712
+ return Effect$1.runSync(Arbitrary.sampleEffect(arbitrary, {
1713
+ count,
1714
+ seed: topUpSeed(seed, soFar)
1715
+ }).pipe(Effect$1.orDie)).map((values) => satisfiedLabels(entries, values));
1716
+ } };
1717
+ };
1065
1718
  const noCoverage = () => ({
1066
1719
  observe: () => void 0,
1067
- judge: () => void 0
1720
+ judge: () => []
1068
1721
  });
1069
- const liveCoverage = (cover, arbitrary) => {
1722
+ const liveCoverage = (cover, arbitrary, seed) => {
1070
1723
  const entries = Object.entries(cover);
1071
1724
  const classes = seedClasses(entries);
1072
1725
  const counter = { runs: 0 };
@@ -1075,78 +1728,206 @@ const liveCoverage = (cover, arbitrary) => {
1075
1728
  judge: () => judgeCoverage({
1076
1729
  classes: Object.fromEntries(classes),
1077
1730
  runs: counter.runs,
1078
- draw: drawFor(entries, arbitrary)
1731
+ draw: drawFor(entries, arbitrary, seed)
1079
1732
  })
1080
1733
  };
1081
1734
  };
1082
- const makeCoverage = (cover, arbitrary) => cover === void 0 ? noCoverage() : liveCoverage(cover, arbitrary);
1083
- const describeVerdict = (verdict) => Effect$1.isEffect(verdict) ? "an Effect, which this lane never runs" : `a ${typeof verdict}`;
1084
- const nonBoolean = (verdict, violations) => {
1085
- violations.add(describeVerdict(verdict));
1735
+ const makeCoverage = (cover, arbitrary, seed) => cover === void 0 ? noCoverage() : liveCoverage(cover, arbitrary, seed);
1736
+ const verdictKindOf = (verdict) => Match$1.value(verdict).pipe(Match$1.when(Effect$1.isEffect, () => "effect"), Match$1.orElse((value) => typeof value));
1737
+ const noteKind = (violations, kind) => {
1738
+ if (violations.kinds.includes(kind) === false) violations.kinds.push(kind);
1739
+ };
1740
+ const noteDrawn = (violations, values) => {
1741
+ if (violations.seen === false) {
1742
+ violations.seen = true;
1743
+ violations.drawn = values;
1744
+ }
1745
+ };
1746
+ const noteVerdict = (violations, verdict, values) => {
1747
+ noteKind(violations, verdictKindOf(verdict));
1748
+ noteDrawn(violations, values);
1086
1749
  return true;
1087
1750
  };
1088
- const literalVerdict = (verdict, violations) => typeof verdict === "boolean" ? verdict : nonBoolean(verdict, violations);
1089
- const effectVerdict = (lane, verdict, violations) => lane === "sync" ? Effect$1.succeed(nonBoolean(verdict, violations)) : Effect$1.map(verdict, (value) => literalVerdict(value, violations));
1751
+ const literalVerdict = (verdict, violations, values) => typeof verdict === "boolean" ? verdict : noteVerdict(violations, verdict, values);
1752
+ const effectVerdict = (lane, verdict, violations, values) => lane === "sync" ? Effect$1.succeed(noteVerdict(violations, verdict, values)) : Effect$1.map(verdict, (value) => literalVerdict(value, violations, values));
1090
1753
  const verdictFor = (run, values, violations) => {
1091
1754
  run.observe(values);
1092
1755
  const verdict = run.holds(run.subject, values);
1093
- return Effect$1.isEffect(verdict) ? effectVerdict(run.lane, verdict, violations) : Effect$1.succeed(literalVerdict(verdict, violations));
1756
+ return Effect$1.isEffect(verdict) ? effectVerdict(run.lane, verdict, violations, values) : Effect$1.succeed(literalVerdict(verdict, violations, values));
1094
1757
  };
1095
1758
  const tolerateInterruption = (cause) => Cause.hasInterrupts(cause) ? Effect$1.interrupt : Effect$1.fail(cause);
1096
1759
  const guardedVerdict = (run, violations) => (values) => Effect$1.catchCause(Effect$1.suspend(() => verdictFor(run, values, violations)), tolerateInterruption);
1097
- const violationMessage = (name, violations) => `${name}: the property returned no boolean; it returned ${violations.join(", ")}. A property must return a literal boolean verdict — asserting with expect(), or returning an Option, Result or object, is not a verdict; write ${REWRITE}.`;
1098
1760
  const runCheck = (run) => {
1099
- const violations = /* @__PURE__ */ new Set();
1761
+ const violations = newViolations();
1100
1762
  return Arbitrary.checkEffect(run.arbitrary, guardedVerdict(run, violations), run.options).pipe(Effect$1.map((result) => ({
1101
1763
  violations,
1102
1764
  result
1103
1765
  })));
1104
1766
  };
1105
1767
  const reportOf = (checked) => Arbitrary.formatCheckFailure(checked.result);
1106
- const violationOf = (name, checked) => checked.violations.size === 0 ? void 0 : violationMessage(name, [...checked.violations]);
1107
- const isRefuted = (checked) => checked.violations.size > 0 || reportOf(checked) !== void 0;
1108
- const dieWithSite = (error, site) => Effect$1.die(withRaisingFrame(error, site));
1109
- const dieViolation = (detail, site) => dieWithSite(new NonBooleanVerdict({ detail }), site);
1110
- const dieReported = (name, report, replay, site) => {
1111
- const detail = `${name}: the property was falsified. ${report}`;
1112
- return dieWithSite(new PropertyRefuted(replay === void 0 ? { detail } : {
1113
- detail,
1114
- replay
1115
- }), site);
1116
- };
1117
- const dieUncovered = (failure, site) => dieWithSite(new CoverageBelowMinimum({ message: failure }), site);
1118
- const impostorHolds = (holds, impostor) => (_subject, values) => holds(impostor.impostor, values);
1119
- const recordImpostor = (run, budget, impostor, checked) => {
1120
- const verdict = {
1121
- refuted: isRefuted(checked),
1122
- frozen: impostor.frozen(),
1123
- runs: budget.runs
1768
+ const seeded = (seed) => Option$1.getOrThrow(Schema$1.decodeOption(PropertySeed)(seed));
1769
+ const runCounted = (runs) => Option$1.getOrThrow(Schema$1.decodeOption(PropertyRunCount)(runs));
1770
+ const propertyRunOf = (run, budget) => ({
1771
+ name: run.name,
1772
+ site: run.site ?? null,
1773
+ seed: seeded(budget.seed),
1774
+ runs: runCounted(budget.runs)
1775
+ });
1776
+ const REPLAY_VARIABLE = "CONFORMANCE_REPLAY";
1777
+ const replayEntryOf = (text, hash, site) => Option$1.match(text, {
1778
+ onNone: () => Effect$1.succeed(Option$1.none()),
1779
+ onSome: (value) => Option$1.match(Schema$1.decodeOption(ReplayChannelFromText)(value), {
1780
+ onNone: () => dieWithSite(new ReplayUnreadable({ text: value }), site),
1781
+ onSome: (channel) => Effect$1.succeed(Option$1.fromNullishOr(selectReplayEntry({
1782
+ channel,
1783
+ hash
1784
+ })))
1785
+ })
1786
+ });
1787
+ const replayedBudget = (budget, runs, seed, replay) => {
1788
+ return {
1789
+ runs,
1790
+ seed,
1791
+ options: replay === void 0 ? {
1792
+ ...budget.options,
1793
+ runs,
1794
+ seed
1795
+ } : {
1796
+ ...budget.options,
1797
+ runs,
1798
+ seed,
1799
+ replay
1800
+ }
1124
1801
  };
1125
- gatedLedger.record(run.subject, run.name, verdict);
1126
1802
  };
1127
- const impostorRun = (run, budget) => {
1803
+ const applyReplayEntry = (budget, entry) => entry === void 0 ? budget : Match$1.value(entry).pipe(Match$1.tag("Refuted", (refuted) => replayedBudget(budget, refuted.runs, refuted.seed, tokenOfReplay(refuted))), Match$1.tag("Plain", (plain) => replayedBudget(budget, plain.runs, plain.seed, void 0)), Match$1.exhaustive);
1804
+ const plainFailureReplay = (run, budget) => plainReplayTextOf({
1805
+ property: run.identityHash,
1806
+ seed: budget.seed,
1807
+ runs: budget.runs
1808
+ });
1809
+ const falsifiedReplayOf = (checked) => {
1810
+ const falsified = falsifiedOf(checked.result);
1811
+ return falsified === void 0 ? void 0 : falsified.replay;
1812
+ };
1813
+ const refutedReplayText = (run, budget, token) => Option$1.getOrElse(Option$1.fromUndefinedOr(refutedReplayTextOf({
1814
+ property: run.identityHash,
1815
+ seed: budget.seed,
1816
+ runs: budget.runs,
1817
+ token
1818
+ })), () => plainFailureReplay(run, budget));
1819
+ const refutedFailureReplay = (run, budget, checked) => {
1820
+ const token = falsifiedReplayOf(checked);
1821
+ return token === void 0 ? plainFailureReplay(run, budget) : refutedReplayText(run, budget, token);
1822
+ };
1823
+ const violationOf = (run, budget, checked) => checked.violations.kinds.length === 0 ? void 0 : new NonBooleanVerdict({
1824
+ property: propertyRunOf(run, budget),
1825
+ drawn: witnessOf(checked.violations.drawn),
1826
+ returned: [...checked.violations.kinds],
1827
+ replay: plainFailureReplay(run, budget)
1828
+ });
1829
+ const isRefuted = (checked) => checked.violations.kinds.length > 0 || reportOf(checked) !== void 0;
1830
+ const dieWithSite = (error, site) => Effect$1.die(withRaisingFrame(error, site));
1831
+ const shrinkCounted = (shrinks) => Option$1.getOrThrow(Schema$1.decodeOption(PropertyShrinkCount)(shrinks));
1832
+ const NO_FALSIFICATION = {
1833
+ counterexample: witnessOf(void 0),
1834
+ shrinks: shrinkCounted(0)
1835
+ };
1836
+ const falsificationOf = (falsified) => falsified === void 0 ? NO_FALSIFICATION : {
1837
+ counterexample: witnessOf(falsified.shrunkInput),
1838
+ shrinks: shrinkCounted(falsified.shrinks)
1839
+ };
1840
+ const refutedOf = (property, checked, replay) => new PropertyRefuted({
1841
+ property,
1842
+ ...falsificationOf(falsifiedOf(checked.result)),
1843
+ replay
1844
+ });
1845
+ const dieReported = (run, budget, checked, site) => dieWithSite(refutedOf(propertyRunOf(run, budget), checked, refutedFailureReplay(run, budget, checked)), site);
1846
+ const dieUncovered = (run, budget, classes, site) => dieWithSite(new CoverageBelowMinimum({
1847
+ property: propertyRunOf(run, budget),
1848
+ classes,
1849
+ replay: plainFailureReplay(run, budget)
1850
+ }), site);
1851
+ const impostorHolds = (holds, impostor) => (_subject, values) => holds(impostor.impostor, values);
1852
+ const noObserve = () => void 0;
1853
+ const impostorRun = (run, propertyBudget, budgets) => {
1128
1854
  const impostor = impostorOf(run.subject);
1129
1855
  const retry = {
1130
1856
  ...run,
1131
1857
  holds: impostorHolds(run.holds, impostor),
1132
1858
  observe: noObserve
1133
1859
  };
1134
- return runCheck(retry).pipe(Effect$1.map((checked) => recordImpostor(retry, budget, impostor, checked)));
1860
+ return Effect$1.map(Effect$1.forEach(budgets, (budget) => runCheck({
1861
+ ...retry,
1862
+ options: budget.options,
1863
+ seed: budget.seed
1864
+ }), { concurrency: 1 }), (checked) => {
1865
+ const verdict = {
1866
+ refuted: checked.some(isRefuted),
1867
+ property: propertyRunOf(retry, propertyBudget),
1868
+ identity: run.identityHash,
1869
+ frozen: impostor.frozen()
1870
+ };
1871
+ run.ledger.record(run.subject, verdict);
1872
+ });
1135
1873
  };
1136
- const noObserve = () => void 0;
1137
- const gateRun = (run, budget, gate) => gate ? impostorRun(run, budget) : Effect$1.void;
1138
- const finishPassed = (run, budget, coverage, gate) => {
1139
- const failure = coverage.judge();
1140
- return failure === void 0 ? gateRun(run, budget, gate) : dieUncovered(`${run.name}: ${failure}`, run.site);
1874
+ const gateRun = (run, budget, gate, budgets) => gate ? impostorRun(run, budget, budgets) : Effect$1.void;
1875
+ const finishPassed = (run, budget, coverage, gate, budgets) => {
1876
+ const failures = coverage.judge();
1877
+ return failures.length === 0 ? gateRun(run, budget, gate, budgets) : dieUncovered(run, budget, failures, run.site);
1141
1878
  };
1142
1879
  const falsifiedOf = (result) => "replay" in result ? result : void 0;
1143
- const replayOfChecked = (checked) => Option$1.getOrUndefined(Option$1.flatMap(Option$1.fromNullishOr(falsifiedOf(checked.result)), (falsified) => Option$1.fromNullishOr(replayOfToken(falsified.replay))));
1144
- const settleReport = (run, budget, coverage, gate, report, replay) => report === void 0 ? finishPassed(run, budget, coverage, gate) : dieReported(run.name, report, replay, run.site);
1145
- const settle = (registration, run, budget, coverage, checked) => {
1146
- const violation = violationOf(registration.name, checked);
1147
- return violation === void 0 ? settleReport(run, budget, coverage, registration.gate, reportOf(checked), replayOfChecked(checked)) : dieViolation(violation, registration.site);
1148
- };
1149
- const checkOf = (registration, arbitrary, budget, observe) => ({
1880
+ const noStore = (name) => ({
1881
+ filepath: null,
1882
+ name,
1883
+ existing: [],
1884
+ recorded: [],
1885
+ budget: void 0
1886
+ });
1887
+ const appendWhenRecorded = (store, candidate) => Option$1.match(Option$1.fromNullishOr(store.filepath), {
1888
+ onNone: () => Effect$1.void,
1889
+ onSome: (filepath) => shouldAppendEntry({
1890
+ existing: store.existing,
1891
+ candidate,
1892
+ budget: store.budget
1893
+ }) ? appendStoreEntry(filepath, candidate) : Effect$1.void
1894
+ });
1895
+ const appendThenDie = (store, candidate, die) => Effect$1.flatMap(Option$1.match(candidate, {
1896
+ onNone: () => Effect$1.void,
1897
+ onSome: (entry) => appendWhenRecorded(store, entry)
1898
+ }), () => die);
1899
+ const refutedCandidate = (store, budget, checked) => Option$1.flatMap(Option$1.fromNullishOr(falsifiedReplayOf(checked)), (token) => refutedEntryOf({
1900
+ property: store.name,
1901
+ seed: budget.seed,
1902
+ token
1903
+ }));
1904
+ const recordedReport = (registration, run, budget, checked) => reportOf(checked) === void 0 ? Effect$1.void : dieReported(run, budget, checked, registration.site);
1905
+ const settleRecorded = (registration, run, budget, checked) => Option$1.match(Option$1.fromNullishOr(violationOf(run, budget, checked)), {
1906
+ onNone: () => recordedReport(registration, run, budget, checked),
1907
+ onSome: (violation) => dieWithSite(violation, registration.site)
1908
+ });
1909
+ const settleRefuted = (registration, run, budget, coverage, checked, store) => reportOf(checked) === void 0 ? finishPassed(run, budget, coverage, registration.gate, [...store.recorded, budget]) : appendThenDie(store, refutedCandidate(store, budget, checked), dieReported(run, budget, checked, registration.site));
1910
+ const settle = (registration, run, budget, coverage, checked, store) => Option$1.match(Option$1.fromNullishOr(violationOf(run, budget, checked)), {
1911
+ onNone: () => settleRefuted(registration, run, budget, coverage, checked, store),
1912
+ onSome: (violation) => appendThenDie(store, nonBooleanEntryOf({
1913
+ property: store.name,
1914
+ seed: budget.seed,
1915
+ runs: budget.runs
1916
+ }), dieWithSite(violation, registration.site))
1917
+ });
1918
+ const readStoreEntries = (file, site) => Effect$1.flatMap(readStoreLines(file), (lines) => {
1919
+ const decoded = decodeStoreLines(file, lines);
1920
+ return Result.isSuccess(decoded) ? Effect$1.succeed(decoded.success) : dieWithSite(decoded.failure, site);
1921
+ });
1922
+ const readTaskStore = (task, site) => Option$1.match(Option$1.fromNullishOr(task.filepath), {
1923
+ onNone: () => Effect$1.succeed([]),
1924
+ onSome: (testFile) => Effect$1.flatMap(storeFileOf(testFile), (file) => readStoreEntries(file, site))
1925
+ });
1926
+ const recordedBudgets = (entries, baseBudget, hash) => entries.flatMap((stored) => Option$1.match(replayEntryOf$1(stored, hash), {
1927
+ onNone: () => [],
1928
+ onSome: (entry) => [applyReplayEntry(baseBudget, entry)]
1929
+ }));
1930
+ const checkOf = (registration, arbitrary, budget, observe, identityHash) => ({
1150
1931
  name: registration.name,
1151
1932
  lane: registration.lane,
1152
1933
  subject: registration.spec.subject,
@@ -1154,23 +1935,54 @@ const checkOf = (registration, arbitrary, budget, observe) => ({
1154
1935
  arbitrary,
1155
1936
  observe,
1156
1937
  options: budget.options,
1157
- site: registration.site
1938
+ seed: budget.seed,
1939
+ identityHash,
1940
+ site: registration.site,
1941
+ ledger: registration.runtime.ledger
1158
1942
  });
1159
- const program = (registration) => Effect$1.gen(function* () {
1160
- const budget = resolveBudget(registration.name, registration.spec, providedCheckDefaults());
1161
- const arbitrary = arbitraryOf(registration.spec.of);
1162
- const coverage = makeCoverage(registration.spec.cover, arbitrary);
1163
- const run = checkOf(registration, arbitrary, budget, coverage.observe);
1164
- const checked = yield* runCheck(run);
1165
- yield* settle(registration, run, budget, coverage, checked);
1943
+ const runRecordedChecks = (registration, arbitrary, hash, recorded) => Effect$1.forEach(recorded, (budget) => {
1944
+ const run = checkOf(registration, arbitrary, budget, noObserve, hash);
1945
+ return Effect$1.flatMap(runCheck(run), (checked) => settleRecorded(registration, run, budget, checked));
1946
+ }, {
1947
+ concurrency: 1,
1948
+ discard: true
1166
1949
  });
1167
- const programOf = (registration) => () => registration.runtime.provide(program(registration));
1168
- const selfModelMessage = (name) => `${name}: the model is the subject itself; a model law must compare against an independent oracle.`;
1169
- const gatedLedger = makeFileLedger((message) => new VacuousProperty({ detail: message }));
1170
- V.afterAll(() => {
1171
- gatedLedger.finalise();
1950
+ const replayRun = (registration, baseBudget, arbitrary, hash, entry) => {
1951
+ const budget = applyReplayEntry(baseBudget, entry);
1952
+ const coverage = makeCoverage(registration.spec.cover, arbitrary, budget.seed);
1953
+ const run = checkOf(registration, arbitrary, budget, coverage.observe, hash);
1954
+ return Effect$1.flatMap(runCheck(run), (checked) => settle(registration, run, budget, coverage, checked, noStore(registration.name)));
1955
+ };
1956
+ const storeRun = (registration, task, baseBudget, arbitrary, hash) => Effect$1.gen(function* () {
1957
+ const entries = yield* readTaskStore(task, registration.site);
1958
+ const existing = entriesForProperty(entries, task.identity.name);
1959
+ const recorded = recordedBudgets(existing, baseBudget, hash);
1960
+ yield* runRecordedChecks(registration, arbitrary, hash, recorded);
1961
+ const coverage = makeCoverage(registration.spec.cover, arbitrary, baseBudget.seed);
1962
+ const run = checkOf(registration, arbitrary, baseBudget, coverage.observe, hash);
1963
+ const checked = yield* runCheck(run);
1964
+ yield* settle(registration, run, baseBudget, coverage, checked, {
1965
+ filepath: task.filepath,
1966
+ name: task.identity.name,
1967
+ existing,
1968
+ recorded,
1969
+ budget: task.budget
1970
+ });
1172
1971
  });
1173
- const registeredName = (name, kind, exempt) => exempt ? `${name} [exempt: ${kind}]` : name;
1972
+ const propertyPlatform = Layer.merge(layer, layer$1);
1973
+ const program = (registration, task) => Effect$1.provide(Effect$1.gen(function* () {
1974
+ const fresh = (yield* Random.nextInt) >>> 0;
1975
+ const baseBudget = resolveBudget(registration.name, registration.spec, task, fresh);
1976
+ const hash = identityHash(task.identity);
1977
+ const replayText = yield* Config.option(Config.String(REPLAY_VARIABLE)).pipe(Effect$1.provideService(ConfigProvider.ConfigProvider, ConfigProvider.fromEnv()), Effect$1.orDie);
1978
+ const entry = Option$1.getOrUndefined(yield* replayEntryOf(replayText, hash, registration.site));
1979
+ const arbitrary = arbitraryOf(registration.spec.of);
1980
+ yield* Option$1.match(Option$1.fromNullishOr(entry), {
1981
+ onNone: () => storeRun(registration, task, baseBudget, arbitrary, hash),
1982
+ onSome: (replay) => replayRun(registration, baseBudget, arbitrary, hash, replay)
1983
+ });
1984
+ }), propertyPlatform);
1985
+ const programOf = (registration) => (task) => registration.runtime.provide(program(registration, task));
1174
1986
  const isSelfModel = (subject, oracle) => Object.is(subject, oracle);
1175
1987
  /**
1176
1988
  * The property surface over one runtime: `prop`, `effectProp`, and the R13 law kinds routed through the gate.
@@ -1192,20 +2004,27 @@ const makeProperty = (runtime) => {
1192
2004
  };
1193
2005
  const refuseSelfModel = (name) => {
1194
2006
  const site = callFrame();
1195
- runtime.register(name, () => dieWithSite(new VacuousProperty({ detail: selfModelMessage(name) }), site));
2007
+ runtime.register(name, (_task) => dieWithSite(new SelfModelLaw({
2008
+ name,
2009
+ site: site ?? null
2010
+ }), site));
2011
+ };
2012
+ const judged = (name, spec, holds) => body(name, spec, holds, "sync", true);
2013
+ const exempted = (name, spec, holds, kind) => {
2014
+ runtime.ledger.recordExempt(name, kind);
2015
+ body(`${name} [exempt: ${kind}]`, spec, holds, "sync", false);
1196
2016
  };
1197
- const gated = (name, spec, holds, kind, exempt) => body(registeredName(name, kind, exempt), spec, holds, "sync", exempt === false);
1198
- const model = (name, spec, oracle) => isSelfModel(spec.subject, oracle) ? refuseSelfModel(name) : gated(name, spec, modelHolds(oracle), "model", false);
1199
- const idempotent = (name, spec) => gated(name, spec, idempotentHolds(), "idempotent", true);
1200
- const deterministic = (name, spec) => gated(name, spec, deterministicHolds(), "deterministic", true);
2017
+ const model = (name, spec, oracle) => isSelfModel(spec.subject, oracle) ? refuseSelfModel(name) : judged(name, spec, modelHolds(oracle));
2018
+ const idempotent = (name, spec) => exempted(name, spec, idempotentHolds(), "idempotent");
2019
+ const deterministic = (name, spec) => exempted(name, spec, deterministicHolds(), "deterministic");
1201
2020
  return {
1202
2021
  prop: (name, spec, holds) => body(name, spec, holds, "sync", true),
1203
2022
  effectProp: (name, spec, holds) => body(name, spec, holds, "effect", true),
1204
2023
  law: {
1205
2024
  model,
1206
- metamorphic: (name, spec, relation) => gated(name, spec, metamorphicHolds(relation), "metamorphic", false),
1207
- roundTrip: (name, spec, decode) => gated(name, spec, roundTripHolds(decode), "roundTrip", false),
1208
- invariant: (name, spec, holds) => gated(name, spec, invariantHolds(holds), "invariant", false),
2025
+ metamorphic: (name, spec, relation) => judged(name, spec, metamorphicHolds(relation)),
2026
+ roundTrip: (name, spec, decode) => judged(name, spec, roundTripHolds(decode)),
2027
+ invariant: (name, spec, holds) => judged(name, spec, invariantHolds(holds)),
1209
2028
  idempotent,
1210
2029
  deterministic
1211
2030
  }
@@ -1250,7 +2069,7 @@ const renderRecord = (failure, spans, identity) => renderFailureRecord({
1250
2069
  failure,
1251
2070
  spans,
1252
2071
  identity,
1253
- replay: replayOfFailure(failure),
2072
+ replay: void 0,
1254
2073
  root: providedRoot()
1255
2074
  });
1256
2075
  const outcomeOf = (exit, recorder, identity) => Exit.isSuccess(exit) ? void 0 : renderRecord(Cause.squash(exit.cause), recorder.spans, identity);
@@ -1271,23 +2090,63 @@ const recordOfRun = (program) => {
1271
2090
  if (task === void 0) return Promise.resolve(void 0);
1272
2091
  return runRecorded(task.context, program);
1273
2092
  };
1274
- const capturingRuntime = (programs) => ({
1275
- register: (_name, program) => {
1276
- programs.push(program);
2093
+ const capturingRuntime = (captured, ledger) => ({
2094
+ register: (name, program) => {
2095
+ captured.push({
2096
+ name,
2097
+ program
2098
+ });
1277
2099
  },
1278
- provide: (effect) => effect
2100
+ provide: (effect) => effect,
2101
+ ledger
1279
2102
  });
1280
- const capturedProgram = (programs) => Option$1.getOrThrow(Option$1.fromNullishOr(programs[0]));
2103
+ const taskOfRecorded = (name, budget, store) => {
2104
+ const current = TestRunner.getCurrentTest();
2105
+ return {
2106
+ identity: {
2107
+ ...testIdentityOf(current),
2108
+ name
2109
+ },
2110
+ filepath: Option$1.getOrNull(Option$1.fromNullishOr(store)),
2111
+ budget: budget ?? providedCheckDefaults()
2112
+ };
2113
+ };
2114
+ /** Runs every captured program in registration order, each as `recordOfProperty` runs one. */
2115
+ const runCaptured = (captured, budget, store) => captured.reduce((previous, entry) => previous.then((records) => {
2116
+ const task = taskOfRecorded(entry.name, budget, store);
2117
+ return recordOfRun(() => entry.program(task)).then((record) => [...records, record]);
2118
+ }), Promise.resolve([]));
2119
+ /** Registers a whole file's properties on a capturing runtime with a fresh ledger, and runs them in order. */
2120
+ const capturedRun = (register, budget, store) => {
2121
+ const captured = [];
2122
+ const ledger = makeFileLedger();
2123
+ register(makeProperty(capturingRuntime(captured, ledger)));
2124
+ return runCaptured(captured, budget, store).then((records) => ({
2125
+ records,
2126
+ ledger
2127
+ }));
2128
+ };
1281
2129
  /**
1282
2130
  * Runs one property the way the runner runs `it.prop`, with a fresh ledger and its own recorder, and resolves with
1283
2131
  * the record its falsification rendered — or `undefined` when it held.
1284
2132
  *
1285
2133
  * @internal
1286
2134
  */
1287
- const recordOfProperty = (input) => {
1288
- const programs = [];
1289
- makeProperty(capturingRuntime(programs)).prop(input.name, input.spec, input.holds);
1290
- return recordOfRun(capturedProgram(programs));
1291
- };
2135
+ const recordOfProperty = (input) => capturedRun((api) => api.prop(input.name, input.spec, input.holds), input.budget, input.store).then(({ records }) => records[0]);
2136
+ const EMPTY_FILE_OPTIONS = {};
2137
+ /**
2138
+ * Runs every property and law a file registers, in registration order, then finalises the file's one ledger:
2139
+ * resolves with each property's rendered failure and the file's single vacuous verdict, or `undefined` when the
2140
+ * file holds. The name a property is recorded under is the name it was registered with.
2141
+ *
2142
+ * @internal
2143
+ */
2144
+ const recordOfFile = Function.dual((args) => typeof args[0] === "function", (register, options) => {
2145
+ const resolved = options ?? EMPTY_FILE_OPTIONS;
2146
+ return capturedRun(register, resolved.budget, resolved.store).then(({ records, ledger }) => ({
2147
+ records,
2148
+ vacuous: ledger.finalise()
2149
+ }));
2150
+ });
1292
2151
  //#endregion
1293
- export { ReplayFromText as _, replayOfFailure as a, summaryOf as c, providedRoot as d, InvalidBudget as f, Replay as g, Slop as h, makeProperty as i, testIdentityOf as l, NonBooleanVerdict as m, recordOfRun as n, isFailureRecordError as o, LeakedState as p, createSpanRecorder as r, renderFailureRecord as s, recordOfProperty as t, throwFailureRecord as u, replayOfText as v };
2152
+ export { Slop as C, providedCheckDefaults as D, replayOfText as E, LeakedState as S, ReplayFromText as T, testIdentityOf as _, makeProperty as a, providedRoot as b, NonBooleanVerdict as c, SeedStoreUnreadable as d, SelfModelLaw as f, summaryOf as g, renderFailureRecord as h, createSpanRecorder as i, PropertyRefuted as l, isFailureRecordError as m, recordOfProperty as n, makeFileLedger as o, VacuousProperty as p, recordOfRun as r, CoverageBelowMinimum as s, recordOfFile as t, ReplayUnreadable as u, throwFailureRecord as v, Replay as w, InvalidBudget as x, witnessOf as y };