@rehearsal-db/core 0.1.0-beta.13 → 0.1.0-beta.15

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.
Files changed (169) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +4 -1
  3. package/dist/package.json +100 -0
  4. package/dist/scripts/install/create_root_config.d.mts +3 -0
  5. package/dist/scripts/install/create_root_config.mjs +13 -0
  6. package/dist/scripts/runtime/manage_database.d.mts +6 -0
  7. package/{scripts → dist/scripts}/runtime/manage_database.mjs +6 -8
  8. package/dist/src/application/session.d.mts +96 -0
  9. package/dist/src/application/session.mjs +380 -0
  10. package/dist/src/baseline/artifact.d.mts +119 -0
  11. package/dist/src/baseline/artifact.mjs +522 -0
  12. package/dist/src/baseline/builder.d.mts +19 -0
  13. package/dist/src/baseline/builder.mjs +142 -0
  14. package/dist/src/baseline/input_discovery.d.mts +13 -0
  15. package/dist/src/baseline/input_discovery.mjs +149 -0
  16. package/dist/src/baseline/policy_review.d.mts +56 -0
  17. package/dist/src/baseline/policy_review.mjs +73 -0
  18. package/dist/src/baseline/preparation.d.mts +63 -0
  19. package/dist/src/baseline/preparation.mjs +295 -0
  20. package/dist/src/baseline/privacy_engine.d.mts +162 -0
  21. package/dist/src/baseline/privacy_engine.mjs +1162 -0
  22. package/dist/src/baseline/privacy_structured_json.d.mts +66 -0
  23. package/dist/src/baseline/privacy_structured_json.mjs +464 -0
  24. package/dist/src/baseline/sanitization_policy.d.mts +69 -0
  25. package/dist/src/baseline/sanitization_policy.mjs +227 -0
  26. package/dist/src/baseline/schema_snapshot.d.mts +19 -0
  27. package/dist/src/baseline/schema_snapshot.mjs +79 -0
  28. package/dist/src/cli/arguments.d.mts +43 -0
  29. package/dist/src/cli/arguments.mjs +135 -0
  30. package/dist/src/cli/guided.d.mts +51 -0
  31. package/dist/src/cli/guided.mjs +713 -0
  32. package/dist/src/cli/init_command.d.mts +21 -0
  33. package/dist/src/cli/init_command.mjs +90 -0
  34. package/dist/src/cli/project_commands.d.mts +157 -0
  35. package/dist/src/cli/project_commands.mjs +183 -0
  36. package/dist/src/cli/rehearsal.d.mts +7 -0
  37. package/dist/src/cli/rehearsal.mjs +706 -0
  38. package/dist/src/cli/renderers.d.mts +299 -0
  39. package/dist/src/cli/renderers.mjs +554 -0
  40. package/dist/src/cli/runtime_commands.d.mts +265 -0
  41. package/dist/src/cli/runtime_commands.mjs +599 -0
  42. package/dist/src/cli/source_commands.d.mts +432 -0
  43. package/dist/src/cli/source_commands.mjs +299 -0
  44. package/dist/src/cli/terminal.d.mts +59 -0
  45. package/dist/src/cli/terminal.mjs +230 -0
  46. package/dist/src/identity/claim.d.mts +144 -0
  47. package/dist/src/identity/claim.mjs +947 -0
  48. package/dist/src/identity/provider_configuration.d.mts +37 -0
  49. package/dist/src/identity/provider_configuration.mjs +132 -0
  50. package/dist/src/identity/storage.d.mts +21 -0
  51. package/dist/src/identity/storage.mjs +122 -0
  52. package/dist/src/index.d.mts +4 -0
  53. package/dist/src/index.mjs +3 -0
  54. package/dist/src/project/configuration.d.mts +181 -0
  55. package/dist/src/project/configuration.mjs +958 -0
  56. package/dist/src/project/install_scaffold.d.mts +42 -0
  57. package/dist/src/project/install_scaffold.mjs +263 -0
  58. package/dist/src/project/setup.d.mts +87 -0
  59. package/dist/src/project/setup.mjs +414 -0
  60. package/dist/src/project/support_report.d.mts +88 -0
  61. package/dist/src/project/support_report.mjs +83 -0
  62. package/dist/src/runtime/cleanup.d.mts +85 -0
  63. package/dist/src/runtime/cleanup.mjs +311 -0
  64. package/dist/src/runtime/migration_history.d.mts +51 -0
  65. package/dist/src/runtime/migration_history.mjs +170 -0
  66. package/dist/src/runtime/plan.d.mts +134 -0
  67. package/dist/src/runtime/plan.mjs +636 -0
  68. package/dist/src/runtime/policy.d.mts +66 -0
  69. package/dist/src/runtime/policy.mjs +369 -0
  70. package/dist/src/runtime/restore.d.mts +22 -0
  71. package/dist/src/runtime/restore.mjs +276 -0
  72. package/dist/src/runtime/service_environment.d.mts +11 -0
  73. package/dist/src/runtime/service_environment.mjs +58 -0
  74. package/dist/src/runtime/topology.d.mts +51 -0
  75. package/dist/src/runtime/topology.mjs +128 -0
  76. package/dist/src/shared/diagnostics.d.mts +97 -0
  77. package/dist/src/shared/diagnostics.mjs +170 -0
  78. package/dist/src/shared/human_output.d.mts +2 -0
  79. package/{src → dist/src}/shared/human_output.mjs +1 -3
  80. package/dist/src/shared/node_runtime.d.mts +14 -0
  81. package/dist/src/shared/node_runtime.mjs +25 -0
  82. package/dist/src/shared/operation_guard.d.mts +22 -0
  83. package/dist/src/shared/operation_guard.mjs +150 -0
  84. package/dist/src/shared/process_environment.d.mts +15 -0
  85. package/dist/src/shared/process_environment.mjs +50 -0
  86. package/dist/src/source/access.d.mts +165 -0
  87. package/dist/src/source/access.mjs +602 -0
  88. package/dist/src/source/asset_transfer.d.mts +27 -0
  89. package/dist/src/source/asset_transfer.mjs +202 -0
  90. package/dist/src/source/baseline.d.mts +66 -0
  91. package/dist/src/source/baseline.mjs +372 -0
  92. package/dist/src/source/external_reader.d.mts +42 -0
  93. package/dist/src/source/external_reader.mjs +278 -0
  94. package/dist/src/source/postgresql_access.d.mts +86 -0
  95. package/dist/src/source/postgresql_access.mjs +454 -0
  96. package/dist/src/source/privacy_paths.d.mts +10 -0
  97. package/dist/src/source/privacy_paths.mjs +27 -0
  98. package/dist/src/targets/driver.d.mts +2 -0
  99. package/dist/src/targets/driver.mjs +13 -0
  100. package/dist/src/targets/postgresql.d.mts +5 -0
  101. package/dist/src/targets/postgresql.mjs +641 -0
  102. package/dist/src/targets/supabase.d.mts +8 -0
  103. package/dist/src/targets/supabase.mjs +659 -0
  104. package/dist/src/targets/supabase_environment.d.mts +49 -0
  105. package/dist/src/targets/supabase_environment.mjs +162 -0
  106. package/dist/src/targets/target.d.mts +33 -0
  107. package/dist/src/targets/target.mjs +39 -0
  108. package/docs/README.md +1 -0
  109. package/docs/architecture.md +17 -9
  110. package/docs/configuration.md +49 -8
  111. package/docs/getting-started.md +28 -0
  112. package/docs/index.md +1 -0
  113. package/docs/production-source.md +43 -7
  114. package/docs/releasing.md +5 -3
  115. package/docs/roadmap.md +3 -2
  116. package/docs/sanitization.md +415 -14
  117. package/docs/standalone-workflow.md +12 -6
  118. package/docs/troubleshooting.md +5 -1
  119. package/docs/tutorial.md +5 -0
  120. package/docs/typescript.md +87 -0
  121. package/package.json +35 -29
  122. package/scripts/install/create_root_config.mjs +0 -36
  123. package/src/README.md +0 -17
  124. package/src/application/session.mjs +0 -435
  125. package/src/baseline/artifact.mjs +0 -684
  126. package/src/baseline/builder.mjs +0 -164
  127. package/src/baseline/input_discovery.mjs +0 -155
  128. package/src/baseline/policy_review.mjs +0 -92
  129. package/src/baseline/preparation.mjs +0 -323
  130. package/src/baseline/privacy_engine.mjs +0 -952
  131. package/src/baseline/sanitization_policy.mjs +0 -299
  132. package/src/baseline/schema_snapshot.mjs +0 -113
  133. package/src/cli/arguments.mjs +0 -126
  134. package/src/cli/guided.mjs +0 -814
  135. package/src/cli/init_command.mjs +0 -94
  136. package/src/cli/rehearsal.mjs +0 -970
  137. package/src/cli/renderers.mjs +0 -626
  138. package/src/cli/runtime_commands.mjs +0 -707
  139. package/src/cli/source_commands.mjs +0 -347
  140. package/src/cli/terminal.mjs +0 -275
  141. package/src/identity/claim.mjs +0 -1163
  142. package/src/identity/provider_configuration.mjs +0 -177
  143. package/src/identity/storage.mjs +0 -165
  144. package/src/project/configuration.mjs +0 -1337
  145. package/src/project/install_scaffold.mjs +0 -291
  146. package/src/project/setup.mjs +0 -466
  147. package/src/project/support_report.mjs +0 -105
  148. package/src/runtime/cleanup.mjs +0 -381
  149. package/src/runtime/migration_history.mjs +0 -220
  150. package/src/runtime/plan.mjs +0 -722
  151. package/src/runtime/policy.mjs +0 -438
  152. package/src/runtime/restore.mjs +0 -337
  153. package/src/runtime/service_environment.mjs +0 -82
  154. package/src/runtime/topology.mjs +0 -177
  155. package/src/shared/diagnostics.mjs +0 -207
  156. package/src/shared/node_runtime.mjs +0 -31
  157. package/src/shared/operation_guard.mjs +0 -162
  158. package/src/shared/process_environment.mjs +0 -68
  159. package/src/source/access.mjs +0 -753
  160. package/src/source/asset_transfer.mjs +0 -207
  161. package/src/source/baseline.mjs +0 -482
  162. package/src/source/external_reader.mjs +0 -255
  163. package/src/source/postgresql_access.mjs +0 -581
  164. package/src/source/privacy_paths.mjs +0 -38
  165. package/src/targets/postgresql.mjs +0 -808
  166. package/src/targets/supabase.mjs +0 -840
  167. package/src/targets/supabase_environment.mjs +0 -229
  168. package/src/targets/target.mjs +0 -66
  169. /package/{src/project → types}/configuration.d.mts +0 -0
@@ -1,707 +0,0 @@
1
- /**
2
- * CLI orchestration for one or more isolated database runtimes and their
3
- * project-owned proof commands.
4
- */
5
-
6
- import { spawnSync } from "node:child_process";
7
- import { access } from "node:fs/promises";
8
- import { join } from "node:path";
9
- import { performance } from "node:perf_hooks";
10
- import * as prompts from "@clack/prompts";
11
- import {
12
- holdApplicationSession,
13
- startApplicationSession,
14
- summarizeProjectCommandFailure,
15
- } from "../application/session.mjs";
16
- import { loadRehearsalConfig } from "../project/configuration.mjs";
17
- import { buildRehearsalPlan, runRehearsalDoctor } from "../runtime/plan.mjs";
18
- import {
19
- inspectRuntimeTopologyCandidates,
20
- loadRuntimeTopology,
21
- } from "../runtime/topology.mjs";
22
- import {
23
- RehearsalError,
24
- normalizeRehearsalError,
25
- redactDiagnosticValue,
26
- } from "../shared/diagnostics.mjs";
27
- import { assertInstalledPackageFingerprint } from "../shared/operation_guard.mjs";
28
- import { buildRuntimeManagerArguments } from "../targets/target.mjs";
29
- import {
30
- formatDuration,
31
- isHumanTerminal,
32
- promptForConfirmation,
33
- terminalStyle,
34
- useStyledPrompts,
35
- } from "./terminal.mjs";
36
-
37
- export const buildRuntimeStackPlan = async (planOptions) => {
38
- const topology = await loadRuntimeTopology(planOptions);
39
- if (topology.targets.length === 1) return buildRehearsalPlan(planOptions);
40
- const targets = [];
41
- for (const target of topology.targets) {
42
- targets.push({
43
- name: target.name,
44
- declaration: target.declaration,
45
- plan: await buildRehearsalPlan({
46
- projectRoot: topology.projectRoot,
47
- configPath: target.configPath,
48
- }),
49
- });
50
- }
51
- const candidates = await inspectRuntimeTopologyCandidates(planOptions);
52
- return {
53
- targets,
54
- migrations: {
55
- candidateCount: candidates.candidateCount,
56
- candidateSha256: candidates.candidateSha256,
57
- },
58
- execution: [
59
- "verify every immutable baseline and migration prefix",
60
- "restore and migrate the primary runtime",
61
- "restore and migrate each dependent runtime in declared order",
62
- ...topology.dependents
63
- .filter((target) => target.declaration.prepareCommand)
64
- .map(
65
- (target) =>
66
- `run ${target.name} preparation: ${target.declaration.prepareCommand}`,
67
- ),
68
- ...topology.dependents.map(
69
- (target) =>
70
- `run ${target.name} proof: ${target.config.application.proofCommand}`,
71
- ),
72
- `run primary proof: ${topology.primary.config.application.proofCommand}`,
73
- ],
74
- guarantee:
75
- "Every database target is isolated by its own project ID, ports, artifact directory, and local-only safety policy. No production resources will be contacted.",
76
- };
77
- };
78
-
79
- export const runRuntimeStackDoctor = async (planOptions) => {
80
- let topology;
81
- try {
82
- topology = await loadRuntimeTopology(planOptions);
83
- } catch (error) {
84
- if (
85
- String(error?.message ?? error).startsWith(
86
- "No Rehearsal configuration was found",
87
- )
88
- ) {
89
- return runRehearsalDoctor(planOptions);
90
- }
91
- return {
92
- state: "NOT READY",
93
- checks: [
94
- {
95
- id: "runtime-topology",
96
- label: "Dependent runtime isolation",
97
- status: "fail",
98
- detail: redactDiagnosticValue(String(error?.message ?? error)),
99
- remediation:
100
- "Give every target a readable config with unique ports, project ID, environment file, and .rehearsal artifact directory.",
101
- },
102
- ],
103
- ambientHostedVariables: {
104
- presentButQuarantined: [],
105
- note: "Topology validation stopped before any runtime action.",
106
- },
107
- };
108
- }
109
- if (topology.targets.length === 1) return runRehearsalDoctor(planOptions);
110
- const reports = [];
111
- for (const target of topology.targets) {
112
- reports.push({
113
- target: target.name,
114
- report: await runRehearsalDoctor({
115
- projectRoot: topology.projectRoot,
116
- configPath: target.configPath,
117
- }),
118
- });
119
- }
120
- return {
121
- state: reports.every(({ report }) => report.state === "READY")
122
- ? "READY"
123
- : "NOT READY",
124
- checks: reports.flatMap(({ target, report }) =>
125
- report.checks.map((check) => ({
126
- ...check,
127
- id: `${target}:${check.id}`,
128
- label: `${target}: ${check.label}`,
129
- })),
130
- ),
131
- ambientHostedVariables: {
132
- presentButQuarantined: [
133
- ...new Set(
134
- reports.flatMap(
135
- ({ report }) =>
136
- report.ambientHostedVariables?.presentButQuarantined ?? [],
137
- ),
138
- ),
139
- ],
140
- note: "Blocked ambient values are not inherited by Rehearsal child processes.",
141
- },
142
- };
143
- };
144
-
145
- export const topologyCandidateSummary = async (planOptions) => {
146
- const inspection = await inspectRuntimeTopologyCandidates(planOptions);
147
- const targets = inspection.targets.map((target) => ({
148
- name: target.name,
149
- configPath: target.configPath,
150
- baselineGenerationId: target.summary.baselineGenerationId,
151
- candidateSha256: target.summary.candidateSha256,
152
- candidateCount: target.summary.candidateCount,
153
- candidates: target.summary.candidates,
154
- }));
155
- return {
156
- baselineGenerationId: targets[0].baselineGenerationId,
157
- candidateSha256: inspection.candidateSha256,
158
- candidateCount: inspection.candidateCount,
159
- candidates: targets.flatMap((target) =>
160
- target.candidates.map((candidate) => ({
161
- ...candidate,
162
- target: target.name,
163
- })),
164
- ),
165
- targets,
166
- topology: inspection.topology,
167
- };
168
- };
169
-
170
- const parseCommand = (source) => {
171
- const values = [];
172
- let current = "";
173
- let quote = null;
174
- for (const character of source) {
175
- if (quote) {
176
- if (character === quote) quote = null;
177
- else current += character;
178
- } else if (character === '"' || character === "'") quote = character;
179
- else if (/\s/u.test(character)) {
180
- if (current) values.push(current);
181
- current = "";
182
- } else current += character;
183
- }
184
- if (quote)
185
- throw new Error("Application proof command contains an unmatched quote.");
186
- if (current) values.push(current);
187
- if (values.length === 0)
188
- throw new Error("Application proof command is empty.");
189
- return values;
190
- };
191
-
192
- const projectCommandEnvironment = (additional = {}) => ({
193
- ...Object.fromEntries(
194
- [
195
- "CI",
196
- "COLORTERM",
197
- "FORCE_COLOR",
198
- "HOME",
199
- "LANG",
200
- "LC_ALL",
201
- "NO_COLOR",
202
- "PATH",
203
- "SHELL",
204
- "TERM",
205
- "TMPDIR",
206
- "USER",
207
- ].flatMap((key) =>
208
- process.env[key] === undefined ? [] : [[key, process.env[key]]],
209
- ),
210
- ),
211
- ...additional,
212
- });
213
-
214
- const RUNTIME_STARTUP_FAILURE_PATTERN =
215
- /container\b.*\b(?:is not ready|starting|unhealthy)|health(?:check)?\b.*\b(?:failed|starting|unhealthy)|out of memory|\boom\b|no space left|resource temporarily unavailable/iu;
216
-
217
- export const classifyManagerFailure = ({
218
- action,
219
- output,
220
- inferredCategory,
221
- }) => {
222
- if (RUNTIME_STARTUP_FAILURE_PATTERN.test(output)) {
223
- return "runtime_dependency_failure";
224
- }
225
- if (
226
- action === "migrate" ||
227
- (action === "run" &&
228
- /migrateRuntime|candidate migration|migration up/iu.test(output))
229
- ) {
230
- return "migration_candidate_failure";
231
- }
232
- return inferredCategory;
233
- };
234
-
235
- export const parseDockerCapacity = (output) => {
236
- const [cpuText, memoryText, runningText, totalText] = output
237
- .trim()
238
- .split(/\s+/u);
239
- const values = [cpuText, memoryText, runningText, totalText].map(Number);
240
- if (!values.every(Number.isFinite)) return null;
241
- const [cpus, memoryBytes, runningContainers, containers] = values;
242
- return { cpus, memoryBytes, runningContainers, containers };
243
- };
244
-
245
- export const parseColimaMemory = (output) => {
246
- const line = output
247
- .split("\n")
248
- .map((entry) => entry.trim())
249
- .find((entry) => entry.startsWith("Mem:"));
250
- if (!line) return null;
251
- const columns = line.split(/\s+/u);
252
- const totalBytes = Number(columns[1]);
253
- const availableBytes = Number(columns[6]);
254
- if (![totalBytes, availableBytes].every(Number.isFinite)) return null;
255
- return { totalBytes, availableBytes };
256
- };
257
-
258
- const diagnosticCommand = (command, args) =>
259
- spawnSync(command, args, {
260
- encoding: "utf8",
261
- env: projectCommandEnvironment(),
262
- stdio: ["ignore", "pipe", "ignore"],
263
- timeout: 5_000,
264
- });
265
-
266
- const inspectContainerCapacity = () => {
267
- const docker = diagnosticCommand("docker", [
268
- "info",
269
- "--format",
270
- "{{.NCPU}} {{.MemTotal}} {{.ContainersRunning}} {{.Containers}}",
271
- ]);
272
- const capacity =
273
- docker.status === 0 ? parseDockerCapacity(docker.stdout) : null;
274
- if (!capacity) return undefined;
275
-
276
- const context = diagnosticCommand("docker", ["context", "show"]);
277
- if (context.status !== 0 || context.stdout.trim() !== "colima") {
278
- return { engine: "docker-compatible", ...capacity };
279
- }
280
- const memory = diagnosticCommand("colima", ["ssh", "--", "free", "-b"]);
281
- return {
282
- engine: "colima",
283
- ...capacity,
284
- ...(memory.status === 0
285
- ? { runtimeMemory: parseColimaMemory(memory.stdout) ?? undefined }
286
- : {}),
287
- };
288
- };
289
-
290
- export const topologyCommandEnvironment = (topology) =>
291
- Object.fromEntries(
292
- topology.targets.map((target) => [
293
- target.primary
294
- ? "REHEARSAL_PRIMARY_ENV_FILE"
295
- : `REHEARSAL_DEPENDENT_${target.name.toUpperCase().replaceAll("-", "_")}_ENV_FILE`,
296
- target.paths.applicationEnvironment,
297
- ]),
298
- );
299
-
300
- export const topologyEnvironmentFiles = (topology) =>
301
- Object.fromEntries(
302
- topology.targets.map((target) => [
303
- target.primary ? "primary" : target.name,
304
- target.paths.applicationEnvironment,
305
- ]),
306
- );
307
-
308
- const runProjectCommand = ({
309
- source,
310
- configuredRoot,
311
- label,
312
- project,
313
- environment = {},
314
- }) => {
315
- const [command, ...args] = parseCommand(source);
316
- const result = spawnSync(command, args, {
317
- cwd: configuredRoot,
318
- encoding: "utf8",
319
- env: projectCommandEnvironment(environment),
320
- stdio: ["ignore", "pipe", "pipe"],
321
- maxBuffer: 16 * 1024 * 1024,
322
- });
323
- if (result.error || result.status !== 0) {
324
- throw new RehearsalError({
325
- category: "application_proof_failure",
326
- code: "PROJECT_COMMAND_FAILED",
327
- message: `The configured ${label} did not pass.`,
328
- expected: `${source} exits successfully`,
329
- actual: summarizeProjectCommandFailure(result),
330
- context: { project, label },
331
- refused:
332
- "Rehearsal did not report the local runtime stack as fully verified.",
333
- suggestions: [
334
- "Run the command directly in the same project and correct the failure.",
335
- ],
336
- cause: result.error,
337
- });
338
- }
339
- return {
340
- command: source,
341
- output: redactDiagnosticValue(String(result.stdout ?? "").trim()),
342
- };
343
- };
344
-
345
- export const createRuntimeCommands = ({
346
- packageRoot,
347
- projectRoot,
348
- managerPath,
349
- getActivePackageFingerprint,
350
- }) => {
351
- const runManager = ({
352
- action,
353
- flags,
354
- configPath = flags.configPath,
355
- confirmation = flags.confirmation,
356
- targetName,
357
- }) => {
358
- const fingerprint = getActivePackageFingerprint();
359
- if (fingerprint) {
360
- assertInstalledPackageFingerprint({
361
- packageRoot,
362
- expected: fingerprint,
363
- });
364
- }
365
- const args = buildRuntimeManagerArguments({
366
- managerPath,
367
- action,
368
- configPath,
369
- confirmation,
370
- });
371
- const runtimeStartedMs = performance.now();
372
- const actionDescription = {
373
- run: "Restoring the baseline and applying reviewed migrations",
374
- start: "Starting the disposable local runtime",
375
- migrate: "Applying reviewed migrations to the current runtime",
376
- reset: "Restoring the immutable baseline",
377
- status: "Inspecting the disposable local runtime",
378
- stop: "Stopping the disposable local runtime",
379
- discard: "Removing the disposable local runtime",
380
- verify: "Verifying the current local runtime",
381
- }[action];
382
- const describedAction = targetName
383
- ? `${actionDescription} for ${targetName}`
384
- : actionDescription;
385
- const spinner = useStyledPrompts(flags) ? prompts.spinner() : null;
386
- if (spinner) {
387
- spinner.start(`${describedAction}. This can take a moment.`);
388
- } else if (isHumanTerminal(flags)) {
389
- console.log(
390
- `\n${terminalStyle(flags, "1;36", action === "run" ? "REHEARSING" : "REHEARSAL")}\n${terminalStyle(flags, "36", "→")} ${describedAction}. This can take a moment.`,
391
- );
392
- }
393
- const result = spawnSync(process.execPath, args, {
394
- cwd: projectRoot,
395
- encoding: "utf8",
396
- env: projectCommandEnvironment(),
397
- stdio: ["inherit", "pipe", "pipe"],
398
- maxBuffer: 16 * 1024 * 1024,
399
- });
400
- const currentFingerprint = getActivePackageFingerprint();
401
- if (currentFingerprint) {
402
- assertInstalledPackageFingerprint({
403
- packageRoot,
404
- expected: currentFingerprint,
405
- });
406
- }
407
- if (result.error) {
408
- if (spinner) spinner.stop(`${describedAction} did not complete.`);
409
- throw result.error;
410
- }
411
- if (result.status !== 0) {
412
- if (spinner) spinner.stop(`${describedAction} did not complete.`);
413
- const completeOutput = [
414
- String(result.stdout ?? ""),
415
- String(result.stderr ?? ""),
416
- ]
417
- .filter(Boolean)
418
- .join("\n")
419
- .trim();
420
- const inferred = normalizeRehearsalError(
421
- new Error(completeOutput || `Rehearsal ${action} failed.`),
422
- );
423
- const category = classifyManagerFailure({
424
- action,
425
- output: completeOutput,
426
- inferredCategory: inferred.category,
427
- });
428
- const startupFailure = category === "runtime_dependency_failure";
429
- const engineCapacity = startupFailure
430
- ? inspectContainerCapacity()
431
- : undefined;
432
- const conciseOutput = completeOutput
433
- .replaceAll(String.fromCodePoint(27), "")
434
- .split("\n")
435
- .map((line) => line.trim())
436
- .filter(Boolean)
437
- .slice(-8)
438
- .join("\n")
439
- .slice(-2_000);
440
- throw new RehearsalError({
441
- category,
442
- code: `MANAGER_${action.toUpperCase()}_FAILED`,
443
- message: `The local Rehearsal ${action} operation did not complete.`,
444
- expected: "the isolated runtime operation to finish and verify",
445
- actual: conciseOutput || `exit ${result.status}`,
446
- context: { action, ...(engineCapacity ? { engineCapacity } : {}) },
447
- refused: "The runtime was not reported as trusted.",
448
- suggestions: [
449
- "Review the concise failure above, then rerun rehearsal doctor before retrying.",
450
- ...(startupFailure
451
- ? [
452
- "Stop unrelated local stacks or increase Docker/Colima capacity, then retry. Rehearsal will not change engine settings or bypass service health checks.",
453
- "Run docker stats --no-stream to identify active containers using the shared engine.",
454
- ]
455
- : []),
456
- "Use --debug only when the safe diagnostic detail is needed.",
457
- ],
458
- cause: new Error(completeOutput || `exit ${result.status}`),
459
- });
460
- }
461
- const durationMs = performance.now() - runtimeStartedMs;
462
- if (spinner) {
463
- spinner.stop(
464
- `${describedAction} completed in ${formatDuration(durationMs)}.`,
465
- );
466
- } else if (isHumanTerminal(flags)) {
467
- console.log(
468
- `${terminalStyle(flags, "32", "✓")} Local runtime step completed in ${formatDuration(durationMs)}.`,
469
- );
470
- }
471
- return {
472
- action,
473
- target: targetName ?? "primary",
474
- durationMs: Math.round(durationMs * 100) / 100,
475
- output: redactDiagnosticValue(String(result.stdout ?? "").trim()),
476
- };
477
- };
478
-
479
- const runRuntimeStack = async ({ command, flags, planOptions }) => {
480
- const topology =
481
- flags.runtimeTopology ?? (await loadRuntimeTopology(planOptions));
482
- const reverse = ["stop", "discard"].includes(command);
483
- const targets = reverse
484
- ? [...topology.targets].reverse()
485
- : topology.targets;
486
- const results = targets.map((target) =>
487
- runManager({
488
- action: command,
489
- flags,
490
- configPath: target.configPath,
491
- confirmation: flags.targetConfirmations?.get(target.configPath),
492
- targetName: topology.targets.length > 1 ? target.name : undefined,
493
- }),
494
- );
495
- if (results.length === 1) return { topology, runtime: results[0] };
496
- return {
497
- topology,
498
- runtime: {
499
- action: command,
500
- durationMs:
501
- Math.round(
502
- results.reduce((total, result) => total + result.durationMs, 0) *
503
- 100,
504
- ) / 100,
505
- output: results
506
- .map((result) => `[${result.target}]\n${result.output}`)
507
- .join("\n\n"),
508
- targets: results,
509
- },
510
- };
511
- };
512
-
513
- const runApplicationProof = async (planOptions, options = {}) => {
514
- const { config, projectRoot: configuredRoot } =
515
- await loadRehearsalConfig(planOptions);
516
- return runProjectCommand({
517
- source: config.application.proofCommand,
518
- configuredRoot,
519
- label: options.targetName
520
- ? `application proof for ${options.targetName}`
521
- : "application proof",
522
- project: config.project.name,
523
- environment: options.environment,
524
- });
525
- };
526
-
527
- const prepareDependentTargets = ({ topology, environment }) =>
528
- topology.dependents.flatMap((target) => {
529
- if (!target.declaration.prepareCommand) return [];
530
- return [
531
- {
532
- target: target.name,
533
- ...runProjectCommand({
534
- source: target.declaration.prepareCommand,
535
- configuredRoot: topology.projectRoot,
536
- label: `preparation command for ${target.name}`,
537
- project: topology.primary.config.project.name,
538
- environment,
539
- }),
540
- },
541
- ];
542
- });
543
-
544
- const proveRuntimeStack = async ({ topology, planOptions, environment }) => {
545
- const dependentProofs = [];
546
- for (const target of topology.dependents) {
547
- dependentProofs.push({
548
- target: target.name,
549
- ...(await runApplicationProof(
550
- { ...planOptions, configPath: target.configPath },
551
- { targetName: target.name, environment },
552
- )),
553
- });
554
- }
555
- const applicationProof = await runApplicationProof(planOptions, {
556
- targetName: topology.targets.length > 1 ? "primary" : undefined,
557
- environment,
558
- });
559
- return { applicationProof, dependentProofs };
560
- };
561
-
562
- const openRuntimeApplication = async ({
563
- flags,
564
- planOptions,
565
- onReady = () => undefined,
566
- }) => {
567
- const topology = await loadRuntimeTopology(planOptions);
568
- const application = topology.primary.config.application;
569
- if (!application.readiness) {
570
- throw new Error(
571
- "rehearsal open requires config.application.readiness so it can confirm the local application is usable.",
572
- );
573
- }
574
- const runtimeReceipts = await Promise.all(
575
- topology.targets.map((target) =>
576
- access(join(target.paths.runtimeWorkdir, "baseline.json"))
577
- .then(() => true)
578
- .catch((error) => {
579
- if (error?.code === "ENOENT") return false;
580
- throw error;
581
- }),
582
- ),
583
- );
584
- if (runtimeReceipts.some((present) => !present)) {
585
- throw new Error(
586
- "rehearsal open requires an existing verified runtime. Run rehearsal run first.",
587
- );
588
- }
589
- flags.runtimeTopology = topology;
590
- const started = await runRuntimeStack({
591
- command: "start",
592
- flags,
593
- planOptions,
594
- });
595
- const verified = await runRuntimeStack({
596
- command: "verify",
597
- flags,
598
- planOptions,
599
- });
600
- const preparations = prepareDependentTargets({
601
- topology,
602
- environment: topologyCommandEnvironment(topology),
603
- });
604
- const session = await startApplicationSession({
605
- command: application.startCommand,
606
- cwd: topology.projectRoot,
607
- files: topologyEnvironmentFiles(topology),
608
- mappings: application.environmentVariables,
609
- readiness: application.readiness,
610
- });
611
- const closed = await holdApplicationSession({ session, onReady });
612
- return {
613
- action: "open",
614
- started: started.runtime,
615
- verified: verified.runtime,
616
- preparations,
617
- application: {
618
- command: application.startCommand,
619
- readiness: session.ready,
620
- stoppedBy: closed.signal,
621
- diagnostics: closed.diagnostics,
622
- },
623
- };
624
- };
625
-
626
- const prepareCandidateConfirmation = async ({
627
- command,
628
- flags,
629
- planOptions,
630
- }) => {
631
- if (!["run", "migrate"].includes(command) || flags.dryRun) return true;
632
- const summary = await topologyCandidateSummary(planOptions);
633
- flags.runtimeTopology = summary.topology;
634
- flags.targetConfirmations = new Map(
635
- summary.targets.map((target) => [
636
- target.configPath,
637
- target.candidateSha256,
638
- ]),
639
- );
640
- if (summary.candidateCount === 0) return true;
641
- if (flags.confirmation === summary.candidateSha256) return true;
642
- const interactive =
643
- !flags.json && process.stdin.isTTY && process.stdout.isTTY;
644
- if (!interactive || flags.confirmation) {
645
- throw new RehearsalError({
646
- category: "migration_candidate_failure",
647
- code: "CANDIDATE_CONFIRMATION_REQUIRED",
648
- message: flags.confirmation
649
- ? "The candidate migration confirmation does not match the current candidate set."
650
- : "Candidate migration confirmation is required in noninteractive use.",
651
- expected: summary.candidateSha256,
652
- actual: flags.confirmation ?? "missing",
653
- context: {
654
- command,
655
- candidateCount: summary.candidateCount,
656
- candidates: summary.candidates.map(
657
- (migration) => `${migration.target}: ${migration.filename}`,
658
- ),
659
- },
660
- refused:
661
- "Rehearsal did not reset, start, or modify the disposable runtime.",
662
- suggestions: [
663
- "Run rehearsal candidates, review every file, then pass the current digest with --confirm-candidates=.",
664
- ],
665
- });
666
- }
667
- console.log(
668
- [
669
- "",
670
- terminalStyle(
671
- flags,
672
- "1",
673
- `${summary.candidateCount} candidate migration${summary.candidateCount === 1 ? "" : "s"}`,
674
- ),
675
- "",
676
- ...summary.candidates.map((migration) =>
677
- summary.targets.length > 1
678
- ? ` → ${migration.target}: ${migration.filename}`
679
- : ` → ${migration.filename}`,
680
- ),
681
- "",
682
- `Candidate set: ${summary.candidateSha256.slice(0, 12)}`,
683
- "",
684
- `Rehearsal will apply only this exact set to ${summary.targets.length === 1 ? "the disposable local runtime" : "the isolated local runtime stack"}.`,
685
- ].join("\n"),
686
- );
687
- const accepted = await promptForConfirmation(
688
- `Apply exactly ${summary.candidateCount === 1 ? "this migration" : "these migrations"}?`,
689
- flags,
690
- );
691
- if (!accepted) {
692
- console.log("No changes made.");
693
- return false;
694
- }
695
- flags.confirmation = summary.candidateSha256;
696
- return true;
697
- };
698
-
699
- return {
700
- openRuntimeApplication,
701
- prepareCandidateConfirmation,
702
- prepareDependentTargets,
703
- proveRuntimeStack,
704
- runManager,
705
- runRuntimeStack,
706
- };
707
- };