@ultimat3/testing 16.0.0 → 17.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.
package/CLAUDE.md CHANGED
@@ -92,6 +92,31 @@ is its own entry point and not part of the barrel.
92
92
  | Globals install all-or-nothing | `installGlobals` saves DESCRIPTORS, not values — a saved value cannot tell "no such global" from "a global holding `undefined`", and the teardown deleted both — and rolls the whole install back if one assignment throws. That rollback is the half with teeth: the install runs BEFORE `mountIsland`'s own `try`, so a getter-only own global among the caller's `globals` used to leave the fake `document` installed for the rest of the process |
93
93
  | Which command shards | `bun test` is one process on one database, and that is still what a scaffolded app's `test` script runs. `x verify` DOES shard its parallel test steps, over `ULTIMATE_TEST_WORKER` and one database per worker; `live` and `e2e` stay serial because a replication slot is cluster-scoped and `e2e` has one built `dist/`. Say which command a claim is about |
94
94
 
95
+ ## The frozen instant is screened, and so is the seed — `As of 2026-08-26`
96
+
97
+ `installDeterminism({ now })`, `setFrozenClock`, `frozenClock` and `advanceClock` all write ONE
98
+ module-level number, and `bun test` is one process: `new Date('yesterday').getTime()` is `NaN`, so
99
+ one unreadable instant makes `Date.now()` answer `NaN` and `new Date()` answer `Invalid Date` for
100
+ every file after it, where every `expiresAt > Date.now()` in the framework reads false and no
101
+ assertion anywhere names the clock. Nothing repairs it either — `NaN + ms` is `NaN`, so
102
+ `advanceClock` only carries it forward, which is why that required parameter is screened too. All
103
+ four go through one `instantMs`, and `defineIslandStates` already refused an unpinned `now` at
104
+ declaration (`isPinnedInstant`) — this is that rule where the clock is actually set.
105
+
106
+ **The seed is NOT a bound, and it is screened anyway — measured before deciding.**
107
+ `seededRandom` starts at `seed >>> 0`, so `NaN`, `±Infinity`, `0.5`, `-1` and `2 ** 32` all produce
108
+ the SAME sequence as `seed: 0`. A non-finite seed therefore does not make a run non-deterministic;
109
+ it silently makes it the seed-0 run, and the record of which seed produced it is false — in the
110
+ package whose promise is reproducibility. `preload.ts` already screened its own environment read by
111
+ hand (`Number.isFinite(seed) ? { seed } : {}`) while `harness.ts` passed an app's `seedValue`
112
+ straight through, which is the repair-in-another-file shape. What the screen does NOT claim: `>>>`
113
+ is modulo `2 ** 32`, so a seed above that still wraps onto another one.
114
+
115
+ `finiteOption`/`finiteCount` from `@ultimat3/core` are the one form; `determinism-bounds.test.ts`
116
+ holds both sides, restores the captured instant after EVERY case, and pins seed `0` as legal.
117
+ `retry.ts`'s budget was already screened, and `matcher-visible.ts` passes its `timeout`/`interval`
118
+ into that screen rather than growing a second one.
119
+
95
120
  Commands: `bun test`, `bunx tsc --noEmit -p tsconfig.json`.
96
121
 
97
122
  Entry points: `.` (the API), `./preload` (side effects for bunfig) and `./registry-isolation`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/testing",
3
- "version": "16.0.0",
3
+ "version": "17.0.0",
4
4
  "description": "Test harness: cloned template DBs per worker, frozen clock, sealed network, 6 test types",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -33,16 +33,16 @@
33
33
  "test": "bun test"
34
34
  },
35
35
  "dependencies": {
36
- "@ultimat3/cache": "16.0.0",
37
- "@ultimat3/core": "16.0.0",
38
- "@ultimat3/db": "16.0.0",
39
- "@ultimat3/entity": "16.0.0",
40
- "@ultimat3/i18n": "16.0.0",
41
- "@ultimat3/jobs": "16.0.0",
42
- "@ultimat3/mail": "16.0.0",
43
- "@ultimat3/policy": "16.0.0",
44
- "@ultimat3/query": "16.0.0",
45
- "@ultimat3/realtime": "16.0.0",
46
- "@ultimat3/time": "16.0.0"
36
+ "@ultimat3/cache": "17.0.0",
37
+ "@ultimat3/core": "17.0.0",
38
+ "@ultimat3/db": "17.0.0",
39
+ "@ultimat3/entity": "17.0.0",
40
+ "@ultimat3/i18n": "17.0.0",
41
+ "@ultimat3/jobs": "17.0.0",
42
+ "@ultimat3/mail": "17.0.0",
43
+ "@ultimat3/policy": "17.0.0",
44
+ "@ultimat3/query": "17.0.0",
45
+ "@ultimat3/realtime": "17.0.0",
46
+ "@ultimat3/time": "17.0.0"
47
47
  }
48
48
  }
@@ -2,6 +2,7 @@
2
2
  // bug: a frozen clock means "advance it", a seeded RNG means "same values every run", and both are
3
3
  // restorable so a test that genuinely needs real time can opt out explicitly.
4
4
 
5
+ import { finiteCount, finiteOption } from '@ultimat3/core';
5
6
  import { NondeterministicError } from './errors';
6
7
 
7
8
  export const DEFAULT_SEED = 20260101;
@@ -16,6 +17,22 @@ const dateGetTime = RealDate.prototype.getTime;
16
17
  let frozenAt = new RealDate(DEFAULT_NOW).getTime();
17
18
  let installed = false;
18
19
 
20
+ /**
21
+ * The epoch milliseconds `value` names, or a refusal — the ONE writer's screen for `frozenAt`.
22
+ *
23
+ * `new Date('yesterday').getTime()` is `NaN`, and `frozenAt` is process state the preload installs
24
+ * for the whole run: one unreadable instant makes `Date.now()` answer `NaN` and `new Date()`
25
+ * answer `Invalid Date` in every test file after it, where every `expiresAt > Date.now()` reads
26
+ * false and no assertion anywhere names the clock. Nothing can repair it either — `NaN + ms` is
27
+ * `NaN`, so `advanceClock` only carries it forward.
28
+ *
29
+ * `finiteOption` and not `finiteCount`: an instant before 1970 is negative, and `Date.getTime()`
30
+ * is already integral. `defineIslandStates` refuses an unpinned `now` at declaration for the same
31
+ * reason one hop away (`isPinnedInstant`); this is that rule where the clock is actually set.
32
+ */
33
+ const instantMs = (subject: string, value: string | number): number =>
34
+ finiteOption(subject, 'now', new RealDate(value).getTime());
35
+
19
36
  /** mulberry32: 32 bits of state, uniform enough for tests, identical across platforms and runs. */
20
37
  export function seededRandom(seed: number): () => number {
21
38
  let state = seed >>> 0;
@@ -81,8 +98,20 @@ export interface DeterminismOptions {
81
98
 
82
99
  /** Install the frozen clock and the seeded RNG globally. Idempotent. */
83
100
  export function installDeterminism(options: DeterminismOptions = {}): void {
84
- frozenAt = new RealDate(options.now ?? DEFAULT_NOW).getTime();
85
- const next = seededRandom(options.seed ?? DEFAULT_SEED);
101
+ // Both screens run BEFORE anything is installed, so a refusal leaves the process on whatever
102
+ // clock and RNG it already had rather than half-way onto a new one.
103
+ const at = instantMs('installDeterminism', options.now ?? DEFAULT_NOW);
104
+ // A seed is not a bound and nothing compares against it — MEASURED, and the answer is why it is
105
+ // screened anyway: `seededRandom` starts at `seed >>> 0`, so `NaN`, `±Infinity`, `0.5`, `-1` and
106
+ // `2 ** 32` all produce the SAME sequence as `seed: 0`. Determinism survives; the record of which
107
+ // seed produced the run does not, and that record is this package's whole promise. The one caller
108
+ // that reads a seed from the environment (`preload.ts`) already screens it by hand, which is the
109
+ // repair-in-another-file shape — `harness.ts` passes an app's `seedValue` through unscreened.
110
+ // What this does NOT claim: `>>>` is modulo 2**32, so a seed above that still wraps onto another.
111
+ const next = seededRandom(
112
+ finiteCount('installDeterminism', 'seed', options.seed ?? DEFAULT_SEED),
113
+ );
114
+ frozenAt = at;
86
115
  globalThis.Date = FrozenDate as unknown as DateConstructor;
87
116
  Math.random = next;
88
117
  installed = true;
@@ -127,20 +156,27 @@ export const isDeterminismInstalled = (): boolean => installed;
127
156
 
128
157
  /** Move the frozen clock forward. The only legal way for time to pass inside a test. */
129
158
  export function advanceClock(ms: number): Date {
130
- frozenAt += ms;
159
+ // A required parameter with no default, so no `??` and no ratchet can see it — and it writes the
160
+ // same single number `installDeterminism` does. `frozenAt += NaN` is `NaN` for the rest of the
161
+ // process, which no later `advance` and no later `set` undoes. Negative is legal: a test may
162
+ // move the clock backwards.
163
+ frozenAt += finiteOption('advanceClock', 'ms', ms);
131
164
  return new RealDate(frozenAt);
132
165
  }
133
166
 
134
167
  export const frozenNow = (): Date => new RealDate(frozenAt);
135
168
 
136
169
  export function setFrozenClock(now: string | number): void {
137
- frozenAt = new RealDate(now).getTime();
170
+ frozenAt = instantMs('setFrozenClock', now);
138
171
  }
139
172
 
140
173
  /** Run `body` with the clock frozen at `now`, then restore whatever was there before. */
141
174
  export async function frozenClock<T>(now: string, body: () => T | Promise<T>): Promise<T> {
175
+ // Resolved before `previous` is spent: a refusal here must not run the body and must not leave
176
+ // the outer instant behind a `finally` that restores something already overwritten.
177
+ const at = instantMs('frozenClock', now);
142
178
  const previous = frozenAt;
143
- frozenAt = new RealDate(now).getTime();
179
+ frozenAt = at;
144
180
  try {
145
181
  return await body();
146
182
  } finally {