@systemfsoftware/vitest 2.0.0 → 4.1.2

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,5 +1,7 @@
1
1
 
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";
2
+ import { n as providedBudgetOf } from "./tiers-W0BWq1uh.mjs";
3
+ import { a as isCheck, 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-DelFkLBL.mjs";
4
+ import { l as noCheckRefusal, u as refusalOf, y as refuseUnyielded } from "./guard-Ci0TyBYv.mjs";
3
5
  import { TestRunner, inject } from "vitest";
4
6
  import * as Cause from "effect/Cause";
5
7
  import * as Effect$1 from "effect/Effect";
@@ -12,8 +14,7 @@ import * as Option$1 from "effect/Option";
12
14
  import * as Match$1 from "effect/Match";
13
15
  import * as Tracer from "effect/Tracer";
14
16
  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";
17
+ import * as Predicate from "effect/Predicate";
17
18
  import * as Arbitrary from "effect/Arbitrary";
18
19
  import * as Config from "effect/Config";
19
20
  import * as ConfigProvider from "effect/ConfigProvider";
@@ -25,8 +26,12 @@ import * as Path from "effect/Path";
25
26
  //#region src/internal/property/defaults.ts
26
27
  /** @internal The `test.provide` key the shared config fills with this run's tier's check options. */
27
28
  const checkDefaultsKey = "@systemfsoftware/vitest:property-check";
28
- /** @internal The configured property-check defaults, or `undefined` when the run provided none. */
29
- const providedCheckDefaults = () => inject(checkDefaultsKey);
29
+ const processEnvironment = () => ({ ...typeof process === "undefined" ? void 0 : process.env });
30
+ /**
31
+ * @internal The property-check defaults a run provided through `test.provide`, else the tier's budget for this
32
+ * process's environment, read from the same table the setup entry uses.
33
+ */
34
+ const providedCheckDefaults = () => inject("@systemfsoftware/vitest:property-check") ?? providedBudgetOf(processEnvironment());
30
35
  //#endregion
31
36
  //#region src/replay.schema.ts
32
37
  /**
@@ -40,15 +45,15 @@ const providedCheckDefaults = () => inject(checkDefaultsKey);
40
45
  * refuted run, so the engine rebuilds the generator's replay token. Several entries are joined by `|`, which no
41
46
  * entry can contain: every key is lowercase letters and every value is digits, commas or a failure tag.
42
47
  *
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
48
+ * A text that names neither form is not a replay. The seed and every decision are branded whole numbers, and
49
+ * the text is a codec rooted at `Schema.String`, so one declaration serves both the writer and the
45
50
  * reader instead of a pattern each.
46
51
  *
47
52
  * @since 4.0.0
48
53
  */
49
- /** The seed a replay names: the whole number a generator chose, never negative (CONST-D3). */
54
+ /** The seed a replay names: the whole number a generator chose, never negative (CONST-D1). */
50
55
  const ReplaySeed = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("ReplaySeed"));
51
- /** One decision along a replay path: the index the scheduler picked at that step (CONST-D3). */
56
+ /** One decision along a replay path: the index the scheduler picked at that step. */
52
57
  const ReplayStep = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("ReplayStep"));
53
58
  /** The replay a generator chose: its seed and the decisions it took, in order (R6). */
54
59
  const Replay = Schema.Struct({
@@ -57,7 +62,7 @@ const Replay = Schema.Struct({
57
62
  });
58
63
  /** A property's identity hash: the unsigned whole number `identityHash` derives from its identity (KTD4). */
59
64
  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). */
65
+ /** The count of runs a property entry names, never negative (CONST-D1). */
61
66
  const ReplayRunCount = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("ReplayRunCount"));
62
67
  /** The failure kind effect's falsification runner records in its replay token. */
63
68
  const ReplayFailureTag = Schema.Literals(["ReturnedFalse", "PropertyError"]);
@@ -317,7 +322,7 @@ const WorkspaceRoot = Schema.NonEmptyString.pipe(Schema.brand("WorkspaceRoot"));
317
322
  * line then drops its package filter, and paths print absolute (R6, KTD5).
318
323
  *
319
324
  * The values are branded (`provided.schema.ts`): a package name and a workspace root are not interchangeable
320
- * text (CONST-D3).
325
+ * text.
321
326
  *
322
327
  * @since 4.0.0
323
328
  */
@@ -832,11 +837,13 @@ const copyDiffFields = (error, failure) => {
832
837
  */
833
838
  const PROPERTY_FAILURE_TAGS = [
834
839
  "PropertyRefuted",
840
+ "PropertyExhausted",
835
841
  "NonBooleanVerdict",
836
842
  "CoverageBelowMinimum",
837
843
  "SelfModelLaw",
838
844
  "SeedStoreUnreadable",
839
845
  "ReplayUnreadable",
846
+ "ReplayNoLongerReproduces",
840
847
  "VacuousProperty"
841
848
  ];
842
849
  const isPropertyFailureLayer = (value) => {
@@ -911,6 +918,74 @@ const currentTask = () => TestRunner.getCurrentTest() ?? {};
911
918
  /** @internal */
912
919
  const testIdentityOf = (task) => identityOf(task ?? currentTask());
913
920
  //#endregion
921
+ //#region src/internal/driver.ts
922
+ /**
923
+ * The driver: one Effect that steps the body's generator in the fiber the test runs in.
924
+ *
925
+ * A `yield*` is a step, so the runner sees every one of them (KTD1):
926
+ * - a branded check is judged against this test's ledger, so it counts, and a failed judgement fails the check
927
+ * itself and stops the test right there (R8);
928
+ * - anything else opens a new observed state and then runs, which is what makes a second check on one state a
929
+ * second check in one window (R5), and a check inside a loop the same window as its siblings.
930
+ *
931
+ * The steps run in the same fiber as the test, so layers, virtual time, concurrency, the second run and scope
932
+ * finalizers keep working through the runner unchanged. A step that fails, and the test being interrupted, both
933
+ * return the generator: its `finally` blocks run and the steps after it never start (R8). When the body returns,
934
+ * the gate refuses what the body wrote and never judged, then a body that judged nothing at all (R4).
935
+ */
936
+ /**
937
+ * A value the body yielded, as the driver runs it. What a body needs of its environment belongs to the layer the
938
+ * runner provides around this whole Effect, so the driver erases the channels here, exactly as the runner erases
939
+ * a body's own (`outputIsEffect` in `runner.ts`): the layer block is where a requirement is checked, and a body
940
+ * that needs a service nothing provides is refused by the runner at run time (R9).
941
+ */
942
+ const isRunnable = (value) => Effect$1.isEffect(value);
943
+ /** A body that yielded something the driver cannot run ran nothing, so nothing in the test could fail. */
944
+ const refuseUnsteppable = "✗ the body yielded a value that is not an Effect. Yield the Effect itself, or a check: yield* expect(actual).toEqual(expected).";
945
+ /**
946
+ * A yielded Effect: a branded check judges itself, and anything else opens a new observed state before it runs,
947
+ * which is what resets the ledger's one-check window (R5).
948
+ */
949
+ const runEffect = (value, ledger) => isCheck(value) ? value : Effect$1.andThen(Effect$1.sync(() => ledger.asserted.step()), value);
950
+ /** One step: anything the driver cannot run is refused, because then no step of the test ran at all. */
951
+ const runStep = (value, ledger) => isRunnable(value) ? runEffect(value, ledger) : Effect$1.die(refusalOf(refuseUnsteppable));
952
+ /** Steps the iterator to its end, feeding each yielded Effect's result back into the body. */
953
+ const stepThrough = (steps, ledger, input) => Effect$1.suspend(() => {
954
+ const state = steps.next(input);
955
+ if (state.done === true) return Effect$1.void;
956
+ return Effect$1.flatMap(runStep(state.value, ledger), (value) => stepThrough(steps, ledger, value));
957
+ });
958
+ /**
959
+ * After the body returns: a check written and never yielded is refused first, then a body that judged none.
960
+ * Both carry the same text the refusal carries on its compile channel, branded by `refusalOf` so the runner's
961
+ * second run re-throws them as the refusals they are.
962
+ */
963
+ const gate = (ledger) => Effect$1.andThen(refusingUnyielded(ledger), judgedNone(ledger));
964
+ /** @internal */
965
+ const refusingUnyielded = (ledger) => Effect$1.suspend(() => ledger.unyielded() > 0 ? refuse(refuseUnyielded) : Effect$1.void);
966
+ const judgedNone = (ledger) => Effect$1.suspend(() => ledger.judged() === 0 ? refuse(noCheckRefusal) : Effect$1.void);
967
+ const refuse = (text) => Effect$1.die(refusalOf(text));
968
+ /**
969
+ * A failure or an interruption returns the generator, so its `finally` blocks run and the step after the failed
970
+ * check never does (R8).
971
+ */
972
+ const unwind = (steps) => Effect$1.sync(() => {
973
+ steps.return?.(void 0);
974
+ });
975
+ const driveBody = (body, args, ledger) => Effect$1.suspend(() => {
976
+ const steps = body(...args);
977
+ return Effect$1.onError(Effect$1.flatMap(stepThrough(steps, ledger, void 0), () => gate(ledger)), () => unwind(steps));
978
+ });
979
+ /** The body's steps, with this test's ledger provided as the `Asserted` every check in it reaches for. */
980
+ const driven = (body, args, ledger) => Effect$1.provideService(driveBody(body, args, ledger), Asserted, ledger.asserted);
981
+ /**
982
+ * Runs one test's body: `body` called with `args`, stepped against `ledger`, with the ledger's `asserted`
983
+ * provided as `Asserted` so every check in the body — including one yielded inside a forked fiber — reaches it.
984
+ *
985
+ * @internal
986
+ */
987
+ const drive = Function.dual(3, driven);
988
+ //#endregion
914
989
  //#region src/internal/property/coverage.ts
915
990
  /**
916
991
  * @internal The R14 coverage classes: labelled input predicates, each with a minimum share of runs.
@@ -1020,7 +1095,7 @@ const judgeCoverage = (options) => failedFailures(toppedUp(judgeOf(options.class
1020
1095
  //#region src/internal/property/error.schema.ts
1021
1096
  /**
1022
1097
  * 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
1098
+ * the vacuous impostor verdict, the seed store's read failure and a stale explicit replay. Every variant is a `Schema.TaggedError` carrying
1024
1099
  * its own data — the property it names, a rendered-and-projected witness, coverage classes, the law that refused
1025
1100
  * itself — and a zero-argument `message` getter computed only from those fields. Wording is free; nothing reads a
1026
1101
  * message back.
@@ -1028,13 +1103,13 @@ const judgeCoverage = (options) => failedFailures(toppedUp(judgeOf(options.class
1028
1103
  * @since 4.0.0
1029
1104
  */
1030
1105
  /**
1031
- * A property's seed: a whole number a generator chose, never negative (CONST-D3).
1106
+ * A property's seed: a whole number a generator chose, never negative (CONST-D1).
1032
1107
  *
1033
1108
  * @internal
1034
1109
  */
1035
1110
  const PropertySeed = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.brand("PropertySeed"));
1036
1111
  /**
1037
- * The count of runs a property's check was configured with, never negative (CONST-D3).
1112
+ * The count of runs a property's check was configured with, never negative (CONST-D1).
1038
1113
  *
1039
1114
  * @internal
1040
1115
  */
@@ -1093,6 +1168,21 @@ var PropertyRefuted = class extends Schema.TaggedError()("PropertyRefuted", {
1093
1168
  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}`;
1094
1169
  }
1095
1170
  };
1171
+ /** @internal */
1172
+ var PropertyExhausted = class extends Schema.TaggedError()("PropertyExhausted", {
1173
+ property: PropertyRun,
1174
+ discards: PropertyRunCount,
1175
+ budget: Schema.Struct({
1176
+ runs: PropertyRunCount,
1177
+ maxDiscards: Schema.NullOr(PropertyRunCount)
1178
+ }),
1179
+ replay: Schema.String
1180
+ }) {
1181
+ get message() {
1182
+ const cap = this.budget.maxDiscards === null ? "no configured discard cap" : `a discard cap of ${String(this.budget.maxDiscards)}`;
1183
+ return `${this.property.name}: the generator discarded ${String(this.discards)} draw(s) under ${cap} within ${String(this.budget.runs)} run(s), so the property was never evaluated. Narrow the generator so it draws admissible values, or raise \`maxDiscards\`.`;
1184
+ }
1185
+ };
1096
1186
  /**
1097
1187
  * A verdict that was not a literal boolean: the property that ran, the first drawn values whose verdict was not a
1098
1188
  * boolean, and every kind of value the property returned (R4).
@@ -1142,11 +1232,11 @@ var CoverageBelowMinimum = class extends Schema.TaggedError()("CoverageBelowMini
1142
1232
  * @internal
1143
1233
  */
1144
1234
  var SelfModelLaw = class extends Schema.TaggedError()("SelfModelLaw", {
1145
- name: Schema.String,
1235
+ law: Schema.String,
1146
1236
  site: Schema.NullOr(Schema.String)
1147
1237
  }) {
1148
1238
  get message() {
1149
- return `${this.name}: the model is the subject itself; a model law must compare against an independent oracle.`;
1239
+ return `${this.law}: the model is the subject itself; a model law must compare against an independent oracle.`;
1150
1240
  }
1151
1241
  };
1152
1242
  /**
@@ -1242,6 +1332,21 @@ var ReplayUnreadable = class extends Schema.TaggedError()("ReplayUnreadable", {
1242
1332
  }
1243
1333
  };
1244
1334
  /**
1335
+ * An explicit `CONFORMANCE_REPLAY` replay whose recorded failure no longer reproduces: after the root re-check,
1336
+ * the replay still mismatched, so no draw reproduces the failure the text names. The recorded seed-store path is
1337
+ * not this — there the entry is one candidate among the novel draws, and only a draw that still fails refutes.
1338
+ *
1339
+ * @internal
1340
+ */
1341
+ var ReplayNoLongerReproduces = class extends Schema.TaggedError()("ReplayNoLongerReproduces", {
1342
+ property: PropertyRun,
1343
+ replay: Schema.String
1344
+ }) {
1345
+ get message() {
1346
+ return `${this.property.name}: the CONFORMANCE_REPLAY replay ${this.replay} no longer reproduces a failure for this property; the case it named is gone or its generator changed.`;
1347
+ }
1348
+ };
1349
+ /**
1245
1350
  * Thrown when a law kind is given a record `of`, which cannot be spread into its subject.
1246
1351
  *
1247
1352
  * @internal
@@ -1271,15 +1376,6 @@ const ReplayToken = Schema$1.fromJsonString(Schema$1.Tuple([
1271
1376
  ]));
1272
1377
  //#endregion
1273
1378
  //#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
1379
  const NUMERIC_SEED = 0;
1284
1380
  /** @internal */
1285
1381
  const tokenOfReplay = (entry) => JSON.stringify([
@@ -1290,6 +1386,27 @@ const tokenOfReplay = (entry) => JSON.stringify([
1290
1386
  entry.path,
1291
1387
  entry.failure
1292
1388
  ]);
1389
+ const FAILURE_CLASSES = {
1390
+ ReturnedFalse: true,
1391
+ PropertyError: true
1392
+ };
1393
+ /**
1394
+ * The tokens that replay only the root draw of `token`, one per failure class: the recorded attempt with an empty
1395
+ * shrink path. Empty for an absent or unreadable token.
1396
+ *
1397
+ * @internal
1398
+ */
1399
+ const rootTokensOf = (token) => Option$1.match(Option$1.flatMap(Option$1.fromNullishOr(token), Schema$1.decodeOption(ReplayToken)), {
1400
+ onNone: () => [],
1401
+ onSome: (decoded) => Object.keys(FAILURE_CLASSES).map((failure) => JSON.stringify([
1402
+ decoded[0],
1403
+ `${decoded[1]}`,
1404
+ decoded[2],
1405
+ decoded[3],
1406
+ [],
1407
+ failure
1408
+ ]))
1409
+ });
1293
1410
  /** @internal */
1294
1411
  const refutedReplayTextOf = (identity) => Option$1.getOrUndefined(Option$1.map(Schema$1.decodeOption(ReplayToken)(identity.token), (decoded) => refutedReplayText$1({
1295
1412
  property: identity.property,
@@ -1757,14 +1874,30 @@ const verdictFor = (run, values, violations) => {
1757
1874
  };
1758
1875
  const tolerateInterruption = (cause) => Cause.hasInterrupts(cause) ? Effect$1.interrupt : Effect$1.fail(cause);
1759
1876
  const guardedVerdict = (run, violations) => (values) => Effect$1.catchCause(Effect$1.suspend(() => verdictFor(run, values, violations)), tolerateInterruption);
1877
+ const checkWith = (run, violations, options) => Arbitrary.checkEffect(run.arbitrary, guardedVerdict(run, violations), options);
1878
+ const isFalsified = Predicate.isTagged("Falsified");
1879
+ const isReplayMismatch = Predicate.isTagged("ReplayMismatch");
1880
+ /**
1881
+ * A replayed draw whose recorded shrink path no longer replays (`ReplayMismatch`) is re-checked at its root under
1882
+ * each failure class, so a root that still falsifies the property refutes it with the root as its counterexample.
1883
+ * The classes are tried in order and the check stops at the first that falsifies; when none does, the mismatch
1884
+ * stands: the recorded failure no longer reproduces.
1885
+ */
1886
+ const rootCheckOf = (run, mismatch) => rootTokensOf(run.options.replay).reduce((previous, replay) => Effect$1.filterOrElse(previous, (found) => isFalsified(found), () => checkWith({
1887
+ ...run,
1888
+ observe: noObserve
1889
+ }, newViolations(), {
1890
+ ...run.options,
1891
+ replay
1892
+ })), Effect$1.succeed(mismatch));
1760
1893
  const runCheck = (run) => {
1761
1894
  const violations = newViolations();
1762
- return Arbitrary.checkEffect(run.arbitrary, guardedVerdict(run, violations), run.options).pipe(Effect$1.map((result) => ({
1895
+ return checkWith(run, violations, run.options).pipe(Effect$1.filterOrElse((result) => !isReplayMismatch(result), (mismatch) => rootCheckOf(run, mismatch)), Effect$1.map((result) => ({
1763
1896
  violations,
1764
1897
  result
1765
1898
  })));
1766
1899
  };
1767
- const reportOf = (checked) => Arbitrary.formatCheckFailure(checked.result);
1900
+ const reportOf = (checked) => isReplayMismatch(checked.result) ? void 0 : Arbitrary.formatCheckFailure(checked.result);
1768
1901
  const seeded = (seed) => Option$1.getOrThrow(Schema$1.decodeOption(PropertySeed)(seed));
1769
1902
  const runCounted = (runs) => Option$1.getOrThrow(Schema$1.decodeOption(PropertyRunCount)(runs));
1770
1903
  const propertyRunOf = (run, budget) => ({
@@ -1829,20 +1962,23 @@ const violationOf = (run, budget, checked) => checked.violations.kinds.length ==
1829
1962
  const isRefuted = (checked) => checked.violations.kinds.length > 0 || reportOf(checked) !== void 0;
1830
1963
  const dieWithSite = (error, site) => Effect$1.die(withRaisingFrame(error, site));
1831
1964
  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({
1965
+ const countOrNull = (value) => Option$1.getOrNull(Option$1.flatMap(Option$1.fromUndefinedOr(value), (count) => Schema$1.decodeOption(PropertyRunCount)(count)));
1966
+ const refutedOf = (property, falsified, replay) => new PropertyRefuted({
1841
1967
  property,
1842
- ...falsificationOf(falsifiedOf(checked.result)),
1968
+ counterexample: witnessOf(falsified.shrunkInput),
1969
+ shrinks: shrinkCounted(falsified.shrinks),
1843
1970
  replay
1844
1971
  });
1845
- const dieReported = (run, budget, checked, site) => dieWithSite(refutedOf(propertyRunOf(run, budget), checked, refutedFailureReplay(run, budget, checked)), site);
1972
+ const dieRefuted = (run, budget, checked, falsified, site) => dieWithSite(refutedOf(propertyRunOf(run, budget), falsified, refutedFailureReplay(run, budget, checked)), site);
1973
+ const dieExhausted = (run, budget, exhausted, site) => dieWithSite(new PropertyExhausted({
1974
+ property: propertyRunOf(run, budget),
1975
+ discards: runCounted(exhausted.discards),
1976
+ budget: {
1977
+ runs: runCounted(budget.runs),
1978
+ maxDiscards: countOrNull(budget.options.maxDiscards)
1979
+ },
1980
+ replay: plainFailureReplay(run, budget)
1981
+ }), site);
1846
1982
  const dieUncovered = (run, budget, classes, site) => dieWithSite(new CoverageBelowMinimum({
1847
1983
  property: propertyRunOf(run, budget),
1848
1984
  classes,
@@ -1901,12 +2037,12 @@ const refutedCandidate = (store, budget, checked) => Option$1.flatMap(Option$1.f
1901
2037
  seed: budget.seed,
1902
2038
  token
1903
2039
  }));
1904
- const recordedReport = (registration, run, budget, checked) => reportOf(checked) === void 0 ? Effect$1.void : dieReported(run, budget, checked, registration.site);
2040
+ const recordedReport = (registration, run, budget, checked) => Match$1.value(checked.result).pipe(Match$1.tag("Falsified", (falsified) => dieRefuted(run, budget, checked, falsified, registration.site)), Match$1.tag("Exhausted", (exhausted) => dieExhausted(run, budget, exhausted, registration.site)), Match$1.orElse(() => Effect$1.void));
1905
2041
  const settleRecorded = (registration, run, budget, checked) => Option$1.match(Option$1.fromNullishOr(violationOf(run, budget, checked)), {
1906
2042
  onNone: () => recordedReport(registration, run, budget, checked),
1907
2043
  onSome: (violation) => dieWithSite(violation, registration.site)
1908
2044
  });
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));
2045
+ const settleRefuted = (registration, run, budget, coverage, checked, store) => Match$1.value(checked.result).pipe(Match$1.tag("Falsified", (falsified) => appendThenDie(store, refutedCandidate(store, budget, checked), dieRefuted(run, budget, checked, falsified, registration.site))), Match$1.tag("Exhausted", (exhausted) => appendThenDie(store, Option$1.none(), dieExhausted(run, budget, exhausted, registration.site))), Match$1.orElse(() => finishPassed(run, budget, coverage, registration.gate, [...store.recorded, budget])));
1910
2046
  const settle = (registration, run, budget, coverage, checked, store) => Option$1.match(Option$1.fromNullishOr(violationOf(run, budget, checked)), {
1911
2047
  onNone: () => settleRefuted(registration, run, budget, coverage, checked, store),
1912
2048
  onSome: (violation) => appendThenDie(store, nonBooleanEntryOf({
@@ -1947,11 +2083,14 @@ const runRecordedChecks = (registration, arbitrary, hash, recorded) => Effect$1.
1947
2083
  concurrency: 1,
1948
2084
  discard: true
1949
2085
  });
1950
- const replayRun = (registration, baseBudget, arbitrary, hash, entry) => {
2086
+ const replayRun = (registration, baseBudget, arbitrary, hash, entry, text) => {
1951
2087
  const budget = applyReplayEntry(baseBudget, entry);
1952
2088
  const coverage = makeCoverage(registration.spec.cover, arbitrary, budget.seed);
1953
2089
  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)));
2090
+ return Effect$1.flatMap(runCheck(run), (checked) => isReplayMismatch(checked.result) ? dieWithSite(new ReplayNoLongerReproduces({
2091
+ property: propertyRunOf(run, budget),
2092
+ replay: text
2093
+ }), registration.site) : settle(registration, run, budget, coverage, checked, noStore(registration.name)));
1955
2094
  };
1956
2095
  const storeRun = (registration, task, baseBudget, arbitrary, hash) => Effect$1.gen(function* () {
1957
2096
  const entries = yield* readTaskStore(task, registration.site);
@@ -1969,7 +2108,10 @@ const storeRun = (registration, task, baseBudget, arbitrary, hash) => Effect$1.g
1969
2108
  budget: task.budget
1970
2109
  });
1971
2110
  });
1972
- const propertyPlatform = Layer.merge(layer, layer$1);
2111
+ const propertyPlatform = Layer.unwrap(Effect$1.promise(async () => {
2112
+ const [fileSystem, path] = await Promise.all([import("@effect/platform-node/NodeFileSystem"), import("@effect/platform-node/NodePath")]);
2113
+ return Layer.merge(fileSystem.layer, path.layer);
2114
+ }));
1973
2115
  const program = (registration, task) => Effect$1.provide(Effect$1.gen(function* () {
1974
2116
  const fresh = (yield* Random.nextInt) >>> 0;
1975
2117
  const baseBudget = resolveBudget(registration.name, registration.spec, task, fresh);
@@ -1979,7 +2121,7 @@ const program = (registration, task) => Effect$1.provide(Effect$1.gen(function*
1979
2121
  const arbitrary = arbitraryOf(registration.spec.of);
1980
2122
  yield* Option$1.match(Option$1.fromNullishOr(entry), {
1981
2123
  onNone: () => storeRun(registration, task, baseBudget, arbitrary, hash),
1982
- onSome: (replay) => replayRun(registration, baseBudget, arbitrary, hash, replay)
2124
+ onSome: (replay) => replayRun(registration, baseBudget, arbitrary, hash, replay, Option$1.getOrThrow(replayText))
1983
2125
  });
1984
2126
  }), propertyPlatform);
1985
2127
  const programOf = (registration) => (task) => registration.runtime.provide(program(registration, task));
@@ -2005,7 +2147,7 @@ const makeProperty = (runtime) => {
2005
2147
  const refuseSelfModel = (name) => {
2006
2148
  const site = callFrame();
2007
2149
  runtime.register(name, (_task) => dieWithSite(new SelfModelLaw({
2008
- name,
2150
+ law: name,
2009
2151
  site: site ?? null
2010
2152
  }), site));
2011
2153
  };
@@ -2077,7 +2219,7 @@ const provideRun = (effect, ledger, ctx) => effect.pipe(Effect$1.provideService(
2077
2219
  const runRecorded = (ctx, program) => {
2078
2220
  const ledger = makeLedger(ctx);
2079
2221
  const recorder = createSpanRecorder();
2080
- return Effect$1.runPromiseExit(provideRun(program(checksFor(ledger)), ledger, ctx).pipe(Effect$1.withTracer(recorder.tracer))).then((exit) => outcomeOf(exit, recorder, testIdentityOf()));
2222
+ return Effect$1.runPromiseExit(provideRun(Effect$1.tap(program(checksFor(ledger)), () => refusingUnyielded(ledger)), ledger, ctx).pipe(Effect$1.withTracer(recorder.tracer))).then((exit) => outcomeOf(exit, recorder, testIdentityOf()));
2081
2223
  };
2082
2224
  /**
2083
2225
  * Runs one program as the runner runs a lane, with a fresh ledger and its own recorder, and resolves with the
@@ -2149,4 +2291,4 @@ const recordOfFile = Function.dual((args) => typeof args[0] === "function", (reg
2149
2291
  }));
2150
2292
  });
2151
2293
  //#endregion
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 };
2294
+ export { providedCheckDefaults as A, providedRoot as C, Replay as D, Slop as E, ReplayFromText as O, witnessOf as S, LeakedState as T, isFailureRecordError as _, makeProperty as a, testIdentityOf as b, NonBooleanVerdict as c, ReplayNoLongerReproduces as d, ReplayUnreadable as f, drive as g, VacuousProperty as h, createSpanRecorder as i, replayOfText as k, PropertyExhausted as l, SelfModelLaw as m, recordOfProperty as n, makeFileLedger as o, SeedStoreUnreadable as p, recordOfRun as r, CoverageBelowMinimum as s, recordOfFile as t, PropertyRefuted as u, renderFailureRecord as v, InvalidBudget as w, throwFailureRecord as x, summaryOf as y };
package/dist/failure.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
- import { A as VacuousProperty, C as witnessOf, M as Breach, S as throwFailureRecord, _ as ReplayValue, a as PropertySubject, b as summaryOf, d as ProvidedCheckDefaults, g as JsonValue, h as FailureRecordInput, i as PropertySpec, j as Witness, m as FailureRecord, n as Gens, o as Values, p as AttributeValue, r as PropApi, s as engine_d_exports, v as TestIdentity, x as testIdentityOf, y as renderFailureRecord } from "./engine-D7Q6-BxD.js";
3
- import { i as Checks, t as Asserted } from "./checks-DiuzHhQM.js";
2
+ import { A as VacuousProperty, M as Breach, _ as renderFailureRecord, a as PropertySubject, b as throwFailureRecord, d as AttributeValue, f as FailureRecord, g as TestIdentity, h as ReplayValue, i as PropertySpec, j as Witness, l as ProvidedCheckDefaults, m as JsonValue, n as Gens, o as Values, p as FailureRecordInput, r as PropApi, v as summaryOf, x as witnessOf, y as testIdentityOf } from "./engine-CYGdHvr8.js";
3
+ import { i as Checks, t as Asserted } from "./checks-BHOX6fGC.js";
4
4
  import * as Effect$1 from "effect/Effect";
5
5
  import * as Tracer from "effect/Tracer";
6
6
  import { Option, Schema } from "effect";
package/dist/failure.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
 
2
- import { E as replayOfText, T as ReplayFromText, _ as testIdentityOf, b as providedRoot, g as summaryOf, h as renderFailureRecord, i as createSpanRecorder, n as recordOfProperty, r as recordOfRun, t as recordOfFile, v as throwFailureRecord, w as Replay, y as witnessOf } from "./failure-nvq5TwWX.mjs";
3
- import { c as callFrameOutside, l as callSite, p as withRaisingFrame } from "./test-context-D_-acRSc.mjs";
2
+ import { C as providedRoot, D as Replay, O as ReplayFromText, S as witnessOf, b as testIdentityOf, i as createSpanRecorder, k as replayOfText, n as recordOfProperty, r as recordOfRun, t as recordOfFile, v as renderFailureRecord, x as throwFailureRecord, y as summaryOf } from "./failure-B094O43o.mjs";
3
+ import { c as callFrameOutside, l as callSite, p as withRaisingFrame } from "./test-context-DelFkLBL.mjs";
4
4
  export { Replay, ReplayFromText, callFrameOutside, callSite, createSpanRecorder, providedRoot as providedWorkspaceRoot, recordOfFile, recordOfProperty, recordOfRun, renderFailureRecord, replayOfText, summaryOf, testIdentityOf, throwFailureRecord, withRaisingFrame, witnessOf };
@@ -1,5 +1,6 @@
1
1
 
2
2
  import { chai } from "vitest";
3
+ import * as Predicate from "effect/Predicate";
3
4
  //#region src/internal/refusals.ts
4
5
  /**
5
6
  * Every refusal the fork prints, as the one text it uses on both channels: the `this` type (or parameter union)
@@ -108,9 +109,13 @@ const refuseRawIt = "✗ this test was registered with vitest's it; import it fr
108
109
  * `new Assertion(...)` on the same class (`chai/index.js:3367`, `:3394`).
109
110
  *
110
111
  * So this module wraps that prototype once, and every method, chainable method and property on it refuses
111
- * unless a check opened the window with {@link authorize} for the duration of its matcher call. Nothing
112
- * asynchronous may run inside that window, so a plain synchronous depth counter is enough; the counter is
113
- * nested-safe because chai's own matchers build further assertions inside the one call.
112
+ * unless a check opened the window with {@link authorize} for the duration of its matcher call. A matcher that
113
+ * returns a promise keeps its window open until that promise settles, so an async matcher such as
114
+ * `toMatchScreenshot` can build chai assertions after it awaits. Each call closes exactly the window it opened,
115
+ * so the depth counter stays nested-safe: chai's own matchers build further assertions inside the one call.
116
+ * The counter is global to the worker, not scoped to the test that opened it: while an async matcher is
117
+ * pending, a raw `expect` from a concurrently running test in the same worker is let through too. Once the
118
+ * promise settles the window closes and a raw `expect` is refused again.
114
119
  *
115
120
  * The window, the install flag and the wrapper marks live on `globalThis` under `Symbol.for` keys rather than
116
121
  * in module scope. A worker can hold two copies of this file at once — the setup file resolves through Node's
@@ -151,13 +156,20 @@ const authorized = () => state.depth > 0;
151
156
  const refuse = () => {
152
157
  throw refusalOf(refuseRawExpect);
153
158
  };
159
+ const close = () => {
160
+ state.depth -= 1;
161
+ };
162
+ const closeWhenSettled = (outcome) => Predicate.isPromiseLike(outcome) ? void Promise.resolve(outcome).then(close, close) : close();
154
163
  /** @internal */
155
164
  const authorize = (run) => {
156
165
  state.depth += 1;
157
166
  try {
158
- return run();
159
- } finally {
160
- state.depth -= 1;
167
+ const outcome = run();
168
+ closeWhenSettled(outcome);
169
+ return outcome;
170
+ } catch (failure) {
171
+ close();
172
+ throw failure;
161
173
  }
162
174
  };
163
175
  /** @internal */
package/dist/guard.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { h as refuseRawIt, n as installGuard, r as isMarked, u as refusalOf } from "./guard-BocOhXt2.mjs";
2
+ import { h as refuseRawIt, n as installGuard, r as isMarked, u as refusalOf } from "./guard-Ci0TyBYv.mjs";
3
3
  import { beforeEach } from "vitest";
4
4
  //#region src/guard.ts
5
5
  /**
@@ -14,6 +14,6 @@ import { beforeEach } from "vitest";
14
14
  installGuard();
15
15
  beforeEach((context) => {
16
16
  if (!isMarked(context.task)) throw refusalOf(refuseRawIt);
17
- });
17
+ }, 0);
18
18
  //#endregion
19
19
  export {};