1/**
2 * Repro rig for the "green destroy but leaked state" race (leaked
3 * rpchttpteststack-* / rpcworkerbindingstack-* workers in the 2026-08-05
4 * full-suite run): a `destroy(Stack)` whose plan printed "no changes"
5 * even though the localState stage dir had committed rows on disk.
6 *
7 * Mirrors the Test/Alchemy harness topology exactly: each simulated "file"
8 * loops deploy -> destroy of its own `Alchemy.Stack` with `localState()`,
9 * through Core.deploy / Core.destroy / Core.toEffect with a shared scope,
10 * with K files running concurrently to simulate full-suite load.
11 *
12 * An anomaly is any iteration where, after a successful destroy, the
13 * stage directory still exists with state rows in it.
14 *
15 * Run from packages/alchemy:
16 * bun test/State/destroy-consistency-repro.ts [files] [iterations]
17 */
18import * as Alchemy from "@/index.ts";
19import * as Core from "@/Test/Core.ts";
20import * as Effect from "effect/Effect";
21import * as Exit from "effect/Exit";
22import * as Scope from "effect/Scope";
23import { existsSync, readdirSync } from "node:fs";
24import * as path from "node:path";
25import { Bucket, TestLayers } from "../test.resources.ts";
26
27const FILES = Number(process.argv[2] ?? 16);
28const ITERATIONS = Number(process.argv[3] ?? 50);
29
30const stateRoot = path.join(process.cwd(), ".alchemy", "state");
31
32let anomalies = 0;
33let completed = 0;
34
35const simulateFile = async (fileIdx: number) => {
36 const stackName = `DestroyRaceRepro${fileIdx}`;
37 const stack = Alchemy.Stack(
38 stackName,
39 {
40 providers: TestLayers(),
41 state: Alchemy.localState(),
42 },
43 Effect.gen(function* () {
44 const bucket = yield* Bucket(`bucket`, { name: `repro-${fileIdx}` });
45 return { arn: bucket.bucketArn.as<string>() };
46 }),
47 );
48 const options: Core.MakeOptions = { providers: TestLayers() };
49
50 for (let i = 0; i < ITERATIONS; i++) {
51 // fresh scope per iteration, like a fresh test file
52 const scope = Scope.makeUnsafe("sequential");
53 const run = <A>(eff: Core.TestEffect<A>) =>
54 Effect.runPromise(Core.toEffect(eff, options, scope) as Effect.Effect<A>);
55 try {
56 await run(Core.deploy(options, stack as any, { scope }));
57 const stageDir = path.join(stateRoot, stackName, "test");
58 if (!existsSync(stageDir) || readdirSync(stageDir).length === 0) {
59 anomalies++;
60 console.error(
61 `ANOMALY[deploy] iter=${i} file=${fileIdx}: state dir missing/empty after deploy`,
62 );
63 }
64 await run(Core.destroy(options, stack as any, { scope }));
65 if (existsSync(stageDir)) {
66 const rows = readdirSync(stageDir);
67 anomalies++;
68 console.error(
69 `ANOMALY[destroy] iter=${i} file=${fileIdx}: stage dir survived destroy: ${JSON.stringify(rows)}`,
70 );
71 }
72 } finally {
73 await Effect.runPromise(
74 Effect.suspend(() => Scope.close(scope, Exit.void)).pipe(Effect.ignore),
75 );
76 }
77 completed++;
78 if (completed % 100 === 0) {
79 console.log(
80 `progress: ${completed}/${FILES * ITERATIONS} anomalies=${anomalies}`,
81 );
82 }
83 }
84};
85
86const t0 = Date.now();
87await Promise.all(Array.from({ length: FILES }, (_, i) => simulateFile(i)));
88console.log(
89 `done: ${FILES * ITERATIONS} deploy/destroy cycles in ${((Date.now() - t0) / 1000).toFixed(1)}s, anomalies=${anomalies}`,
90);
91process.exit(anomalies > 0 ? 1 : 0);