@rehearsal-db/core 1.0.0 → 1.0.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +46 -1
  2. package/COMPATIBILITY.md +43 -0
  3. package/README.md +31 -12
  4. package/dist/package.json +2 -2
  5. package/dist/scripts/install/create_root_config.mjs +9 -2
  6. package/dist/src/cli/failure_output.d.mts +3 -0
  7. package/dist/src/cli/failure_output.mjs +48 -0
  8. package/dist/src/cli/rehearsal.mjs +15 -4
  9. package/dist/src/cli/runtime_action_handlers.mjs +11 -4
  10. package/dist/src/cli/runtime_commands.mjs +23 -24
  11. package/dist/src/cli/runtime_failure_summary.d.mts +1 -0
  12. package/dist/src/cli/runtime_failure_summary.mjs +30 -4
  13. package/dist/src/cli/runtime_planning.mjs +6 -0
  14. package/dist/src/project/install_scaffold.mjs +7 -0
  15. package/dist/src/project/project_discovery_metadata.mjs +9 -11
  16. package/dist/src/runtime/migration_failure.d.mts +28 -0
  17. package/dist/src/runtime/migration_failure.mjs +197 -0
  18. package/dist/src/runtime/migration_failure_source.d.mts +16 -0
  19. package/dist/src/runtime/migration_failure_source.mjs +68 -0
  20. package/dist/src/runtime/plan.mjs +26 -15
  21. package/dist/src/shared/diagnostics.mjs +5 -0
  22. package/dist/src/shared/host_platform.d.mts +16 -0
  23. package/dist/src/shared/host_platform.mjs +109 -0
  24. package/dist/src/shared/operation_guard.d.mts +4 -1
  25. package/dist/src/shared/operation_guard.mjs +18 -13
  26. package/dist/src/shared/operation_progress.mjs +35 -1
  27. package/dist/src/shared/operation_progress_storage.d.mts +27 -6
  28. package/dist/src/shared/operation_progress_storage.mjs +65 -9
  29. package/dist/src/shared/process_environment.d.mts +2 -1
  30. package/dist/src/shared/process_environment.mjs +47 -5
  31. package/dist/src/targets/port_start_failure.d.mts +15 -0
  32. package/dist/src/targets/port_start_failure.mjs +46 -0
  33. package/dist/src/targets/postgresql.mjs +8 -7
  34. package/dist/src/targets/postgresql_process.d.mts +8 -1
  35. package/dist/src/targets/postgresql_process.mjs +46 -2
  36. package/dist/src/targets/supabase_process.mjs +2 -1
  37. package/dist/src/targets/supabase_start_failure.mjs +5 -1
  38. package/docs/architecture.md +32 -2
  39. package/docs/commands.md +35 -0
  40. package/docs/getting-started.md +48 -0
  41. package/docs/index.md +15 -9
  42. package/docs/releasing.md +28 -24
  43. package/docs/roadmap.md +8 -3
  44. package/docs/stability.md +8 -4
  45. package/docs/troubleshooting.md +54 -2
  46. package/docs/tutorial.md +93 -8
  47. package/docs/vscode.md +220 -37
  48. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,49 @@ a Changelog, and versions will follow Semantic Versioning after the package exis
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [1.0.2] - 2026-10-20
9
+
10
+ ### Fixed
11
+
12
+ - Explain recognized PostgreSQL/Supabase migration failures with bounded native
13
+ SQLSTATE evidence and safe syntax, schema, constraint, permission, connectivity
14
+ and timeout guidance. Unsupported or ambiguous evidence remains unknown; raw
15
+ SQL, provider details, credentials and private rows are not exposed.
16
+ - Preserve the original diagnosis across cleanup and bounded operation history so
17
+ the CLI and editor can show the same failure after refresh. Failed operations
18
+ never claim verification or unconditional rollback success.
19
+ - Optionally identify the exact approved PostgreSQL input file by hash, without
20
+ inventing a failing SQL origin, line or column. Reject invalid UTF-8 before
21
+ execution rather than submitting replacement-decoded bytes.
22
+ - Point recognized bind failures at the configured local ports, including safe
23
+ Windows/WSL exclusion-inspection guidance. Do not infer a reservation from every
24
+ denial or suggest terminating unknown listeners.
25
+ - Keep database-only command guidance separate from optional application launch.
26
+
27
+ ### Documentation and distribution
28
+
29
+ - Add a main-tutorial existing-row constraint failure, reviewed backfill/fix and
30
+ verification sequence, plus a same-project VS Code handoff and safe local-copy
31
+ recovery for unsupported external npm links.
32
+ - Pair with Starter 0.1.22 for actionable persistent failures, safe exact-byte
33
+ source opening, distinct command/database states and truthful recorded proof.
34
+ - Refresh the separate macOS, Linux and Windows/WSL 2 setup bundles to exact core
35
+ 1.0.2. Native Windows database execution remains unsupported.
36
+
37
+ ## [1.0.1] - 2026-10-19
38
+
39
+ ### Platform compatibility
40
+
41
+ - Keep macOS Intel/Apple Silicon and Linux execution distinct from Windows desktop
42
+ access through WSL 2. Require the WSL project's Linux filesystem; document the
43
+ preview's actual component and runtime limits rather than claiming every OS works.
44
+ - Verify installed package version/discovery on real desktop OS runners and exercise
45
+ PostgreSQL/dependent targets in an owned Windows 2025 WSL2 Ubuntu distribution.
46
+ Provision checksum-verified official Node and signed Docker Engine packages without
47
+ Docker Desktop, a daemon TCP endpoint or new branch-protection requirements.
48
+ - Document native Windows runtime refusal and the Linux/Remote-WSL setup path; version,
49
+ help and project discovery remain distinct from database-runtime support.
50
+
8
51
  ## [1.0.0] - 2026-10-17
9
52
 
10
53
  ### Stable compatibility
@@ -772,7 +815,9 @@ not unattended cleanup, and container age is not proof of user inactivity.
772
815
  publication uses short-lived trusted OIDC, and every release tag must already exist on
773
816
  protected `main`.
774
817
 
775
- [Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.0...HEAD
818
+ [Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.2...HEAD
819
+ [1.0.2]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.1...v1.0.2
820
+ [1.0.1]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.0...v1.0.1
776
821
  [1.0.0]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.10...v1.0.0
777
822
  [0.1.0-rc.10]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.9...v0.1.0-rc.10
778
823
  [0.1.0-rc.9]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.8...v0.1.0-rc.9
package/COMPATIBILITY.md CHANGED
@@ -32,6 +32,49 @@ default and the release-build runtime. End-of-life and odd-numbered Node.js rele
32
32
  not supported. A future release may remove a Node.js line after its upstream end-of-life
33
33
  date, with that change called out in the changelog.
34
34
 
35
+ ## Operating systems and execution location
36
+
37
+ Core 1.0.1's platform boundary distinguishes the computer running VS Code from the
38
+ operating system actually executing Node, Docker and Rehearsal. The corresponding editor
39
+ changes are delivered with extension 0.1.21. A Windows desktop connected to WSL is not native
40
+ Windows execution.
41
+
42
+ | Execution environment | Runtime boundary |
43
+ | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
44
+ | macOS, Intel or Apple Silicon | POSIX runtime; supported Node and a local Docker-compatible engine required |
45
+ | Linux | POSIX runtime; Ubuntu 24.04 is the primary PostgreSQL/Supabase fixture environment |
46
+ | Windows with WSL 2 | Run Linux Node and Docker inside the distribution, with the project on its Linux filesystem; compatibility preview |
47
+ | Native Windows Node, PowerShell or Command Prompt | Version, help and read-only project discovery remain available; runtime/setup/database operations refuse with WSL guidance |
48
+ | WSL 1, a WSL project on `/mnt/c` or another Windows-mounted filesystem | Not a supported database-runtime environment |
49
+ | SSH, Dev Containers, Codespaces or browser-only VS Code | No end-to-end support claim; different host, filesystem and port-forwarding boundaries need their own verification |
50
+ | iOS, iPadOS, Android, BSD and other unlisted systems | Not supported; installing a JavaScript package does not establish a compatible database engine or desktop extension host |
51
+
52
+ Do not bypass native Windows refusal with permission bits or a shell wrapper. Native
53
+ Windows ACLs, process-tree retirement and baseline filesystem behavior are not certified
54
+ by Linux tests. Keep keys, baselines and working files in the WSL Linux home filesystem,
55
+ not a Windows share. The package does not move an existing project automatically.
56
+ See [Windows and WSL setup](docs/getting-started.md#windows-use-a-wsl-2-linux-workspace).
57
+
58
+ ### What platform verification proves
59
+
60
+ The separate `Platform compatibility` workflow preserves existing release/check names.
61
+ Desktop jobs install the packed package and exercise version/discovery from a path with
62
+ spaces and Unicode on real Linux, macOS and Windows runners. Windows also checks the
63
+ intentional runtime refusal; a passing Windows job is not a working Windows database.
64
+ The Windows 2025 WSL job provisions its own Ubuntu 24.04 distribution, verifies Linux
65
+ execution and filesystem location, then runs the existing real PostgreSQL/dependent-target
66
+ fixtures against a local Unix-socket Docker Engine. It does not install Docker Desktop
67
+ or expose the daemon over TCP. Node uses the official release checksum; Docker packages
68
+ use its signed apt repository. These integrity checks are not independent publisher
69
+ authentication.
70
+
71
+ A configured workflow is not passing evidence. Reports identify actual OS, architecture,
72
+ installed artifact and exercised boundary. The WSL fixture does not certify native
73
+ Windows runtime, WSL Supabase/Auth/Storage, a native Remote-WSL VS Code session, all Linux
74
+ distributions, assistive technology or every architecture. Editor Node/controller tests
75
+ are separate from real extension-host and browser walkthroughs. Intel/ARM labels and
76
+ Windows/WSL are never interchangeable evidence.
77
+
35
78
  The npm install hook may create project scaffolding only when it proves one application
36
79
  root. Ordinary npm application installs are covered. A workspace is automatic only when
37
80
  one declaring consumer is already visible in its manifest or lockfile; npm can save a
package/README.md CHANGED
@@ -16,6 +16,7 @@
16
16
  <p align="center">
17
17
  <a href="https://ddupasquier.github.io/rehearsal-db/">Read the documentation</a> ·
18
18
  <a href="https://www.npmjs.com/package/@rehearsal-db/core">View on npm</a> ·
19
+ <a href="https://ddupasquier.github.io/rehearsal-db/vscode/">Free VS Code Starter</a> ·
19
20
  <a href="https://github.com/Ddupasquier/rehearsal-db">Source on GitHub</a> ·
20
21
  <a href="https://github.com/Ddupasquier/rehearsal-db/issues">Report a problem</a>
21
22
  </p>
@@ -45,8 +46,14 @@ it through a reviewed privacy policy, and retire the access again. It is never p
45
46
  `run`, `reset`, `migrate`, or application launch. Rehearsal is not a backup system or a
46
47
  production deployment tool.
47
48
 
48
- > **Pre-1.0 safety note:** use Rehearsal on a branch and keep a working backup of your
49
- > project.
49
+ The CLI is free and MIT-licensed, with stable releases on
50
+ [npm](https://www.npmjs.com/package/@rehearsal-db/core). This guide targets **1.0.2**;
51
+ install only a version actually published there. Prefer a visual workflow?
52
+ [Install the free VS Code Starter preview](docs/vscode.md). Its included features are
53
+ free indefinitely for personal or commercial development—no account, card, or trial.
54
+
55
+ Work on a branch and keep a working backup of your project. Rehearsal's local safety
56
+ boundaries do not replace version control or backups.
50
57
 
51
58
  ## What you need
52
59
 
@@ -62,6 +69,12 @@ your migration is intended to change.
62
69
 
63
70
  ## Quick start
64
71
 
72
+ Prefer OS-specific files? Use the [Windows / WSL 2](installers/windows/README.md),
73
+ [macOS](installers/macos/README.md) or [Linux](installers/linux/README.md) setup folder.
74
+ These readable helpers require existing Node.js 24 or 26 and check prerequisites by
75
+ default; installation is an explicit option. They do not install OS tools or start a
76
+ database. See [their exact behavior and release availability](installers/README.md).
77
+
65
78
  From your project directory:
66
79
 
67
80
  ```bash
@@ -157,18 +170,24 @@ loads.
157
170
 
158
171
  ## Supported today
159
172
 
160
- | Environment | Support |
161
- | ------------------------------------------------ | ---------------------- |
162
- | Supabase CLI projects | Supported |
163
- | Ordinary PostgreSQL in local Docker | Supported |
164
- | macOS and Linux | Supported |
165
- | WSL | Experimental |
166
- | Native Windows | Not yet supported |
167
- | Hosted database URLs | Intentionally rejected |
168
- | Optional read-only PostgreSQL source preparation | Explicit opt-in beta |
169
- | MySQL, MongoDB, and other database families | Not yet supported |
173
+ | Environment | Support |
174
+ | ------------------------------------------------ | ---------------------------- |
175
+ | Supabase CLI projects | Supported |
176
+ | Ordinary PostgreSQL in local Docker | Supported |
177
+ | macOS and Linux | Supported |
178
+ | Windows via WSL 2 on its Linux filesystem | Compatibility preview |
179
+ | Native Windows runtime | Not yet supported; use WSL 2 |
180
+ | Hosted database URLs | Intentionally rejected |
181
+ | Optional read-only PostgreSQL source preparation | Explicit opt-in beta |
182
+ | MySQL, MongoDB, and other database families | Not yet supported |
170
183
 
171
184
  See [COMPATIBILITY.md](COMPATIBILITY.md) for the exact support contract.
185
+ Native Windows metadata commands are not database-runtime support. For Windows, follow
186
+ the [WSL 2 setup path](docs/getting-started.md#windows-use-a-wsl-2-linux-workspace):
187
+ Linux Node, project files and Docker must live in the Linux execution environment.
188
+ Core 1.0.1 and editor 0.1.21 add explicit platform guidance/refusal; older immutable
189
+ releases retain their original behavior. Mobile, browser-only and unlisted systems are
190
+ not implied by desktop or Linux compatibility.
172
191
 
173
192
  ## Documentation
174
193
 
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rehearsal-db/core",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "private": false,
5
5
  "description": "Catch PostgreSQL and Supabase migrations that pass on empty databases but fail on real data—before deploy.",
6
6
  "repository": {
@@ -78,7 +78,7 @@
78
78
  "test:fixture:resources": "npm run build && node scripts/verification/run_scenario.mjs resources",
79
79
  "release:verify": "npm run build && node scripts/verification/release_gate.mjs",
80
80
  "check": "npm run check:syntax && npm run typecheck && npm run test && npm run docs:build && npm run format:check && npm run package:audit",
81
- "check:syntax": "find scripts tests -type f -name '*.mjs' -exec node --check {} +",
81
+ "check:syntax": "node scripts/verification/check_syntax.mjs",
82
82
  "typecheck": "npm run build && tsc --project tsconfig.json",
83
83
  "format": "prettier --write .",
84
84
  "format:check": "prettier --check .",
@@ -2,10 +2,17 @@
2
2
  /** Create safe project-root Rehearsal scaffolding during a local install. */
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { createInstalledRehearsalScaffold, renderInstalledRehearsalScaffold, } from "../../src/project/install_scaffold.mjs";
5
+ import { inspectHostPlatform } from "../../src/shared/host_platform.mjs";
5
6
  const packageRoot = fileURLToPath(new URL("../../..", import.meta.url));
6
7
  try {
7
- const result = await createInstalledRehearsalScaffold({ packageRoot });
8
- console.log(renderInstalledRehearsalScaffold(result));
8
+ const host = await inspectHostPlatform();
9
+ if (!host.supported) {
10
+ console.log(`Rehearsal installed without creating project files. ${host.remediation}`);
11
+ }
12
+ else {
13
+ const result = await createInstalledRehearsalScaffold({ packageRoot });
14
+ console.log(renderInstalledRehearsalScaffold(result));
15
+ }
9
16
  }
10
17
  catch (error) {
11
18
  const message = error instanceof Error ? error.message : String(error);
@@ -0,0 +1,3 @@
1
+ /** Preserve fixed original migration guidance without masking aggregate cleanup failure. */
2
+ import { RehearsalError, type RehearsalErrorOverrides } from "../shared/diagnostics.mjs";
3
+ export declare const normalizeCliFailure: (error: unknown, overrides?: RehearsalErrorOverrides) => RehearsalError;
@@ -0,0 +1,48 @@
1
+ /** Preserve fixed original migration guidance without masking aggregate cleanup failure. */
2
+ import { RehearsalError, normalizeRehearsalError, } from "../shared/diagnostics.mjs";
3
+ import { parseMigrationFailureDiagnostic, } from "../runtime/migration_failure.mjs";
4
+ const aggregateMigrationFailure = (error) => {
5
+ try {
6
+ let original = error;
7
+ for (let depth = 0; depth <= 4; depth += 1) {
8
+ if (original instanceof AggregateError) {
9
+ if (depth === 4 || !Array.isArray(original.errors))
10
+ return undefined;
11
+ original = original.errors[0];
12
+ continue;
13
+ }
14
+ if (original instanceof RehearsalError &&
15
+ original.category !== "operation_cancelled" &&
16
+ original.context !== null &&
17
+ typeof original.context === "object" &&
18
+ !Array.isArray(original.context) &&
19
+ "migrationFailure" in original.context)
20
+ return parseMigrationFailureDiagnostic(original.context.migrationFailure);
21
+ return undefined;
22
+ }
23
+ }
24
+ catch {
25
+ /* Untrusted metadata is not evidence; retain the aggregate's existing failure. */
26
+ }
27
+ return undefined;
28
+ };
29
+ export const normalizeCliFailure = (error, overrides = {}) => {
30
+ const failure = normalizeRehearsalError(error, overrides);
31
+ if (!(error instanceof AggregateError))
32
+ return failure;
33
+ const migrationFailure = aggregateMigrationFailure(error);
34
+ if (migrationFailure === undefined)
35
+ return failure;
36
+ // Keep outer category/exit and cleanup warning. Project no original/sibling text.
37
+ return new RehearsalError({
38
+ category: failure.category,
39
+ code: failure.code,
40
+ message: failure.message,
41
+ expected: failure.expected,
42
+ actual: failure.actual,
43
+ context: { migrationFailure },
44
+ ...(failure.refused === undefined ? {} : { refused: failure.refused }),
45
+ suggestions: [...migrationFailure.suggestions, ...failure.suggestions],
46
+ cause: failure.cause,
47
+ });
48
+ };
@@ -8,13 +8,14 @@ import { join } from "node:path";
8
8
  import { performance } from "node:perf_hooks";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import * as prompts from "@clack/prompts";
11
- import { RehearsalError, normalizeRehearsalError, renderHumanError, serializeRehearsalError, } from "../shared/diagnostics.mjs";
11
+ import { RehearsalError, renderHumanError, serializeRehearsalError, } from "../shared/diagnostics.mjs";
12
12
  import { acquireProjectOperation, assertInstalledPackageFingerprint, createInstalledPackageFingerprint, } from "../shared/operation_guard.mjs";
13
13
  import { commandMutatesProject, parseArguments, resetGuidedFlags, } from "./arguments.mjs";
14
14
  import { usage } from "./basic_command_handlers.mjs";
15
15
  import { createCliCommandRegistry } from "./cli_commands.mjs";
16
16
  import { createCliSessionState, resetCommandSessionState, } from "./command_contract.mjs";
17
17
  import { createCommandEmitter } from "./command_output.mjs";
18
+ import { normalizeCliFailure } from "./failure_output.mjs";
18
19
  import { createGuidedHome } from "./guided.mjs";
19
20
  import { createProjectCommands } from "./project_commands.mjs";
20
21
  import { buildRuntimeStackPlan, createRuntimeCommands, runRuntimeStackDoctor, topologyCandidateSummary, topologyCommandEnvironment, } from "./runtime_commands.mjs";
@@ -22,6 +23,7 @@ import { buildRefreshWorkflowPlan, createRefreshWorkflow, } from "./source_comma
22
23
  import { installGuidedExitShortcut, useStyledPrompts } from "./terminal.mjs";
23
24
  import { runWithOperationCancellation, supportsOperationCancellation, } from "../shared/cancellation.mjs";
24
25
  import { withOperationProgress } from "../shared/operation_progress.mjs";
26
+ import { assertSupportedHostPlatform } from "../shared/host_platform.mjs";
25
27
  const packageRoot = fileURLToPath(new URL("../../..", import.meta.url));
26
28
  const projectRoot = process.cwd();
27
29
  const managerPath = join(packageRoot, "dist/scripts/runtime/manage_database.mjs");
@@ -94,8 +96,17 @@ const main = async () => {
94
96
  !flags.json &&
95
97
  process.stdin.isTTY &&
96
98
  process.stdout.isTTY;
97
- const guided = command === "guide" || wantsAutomaticGuide;
98
- const mutating = commandMutatesProject({ command, flags, guided });
99
+ const guided = !flags.help &&
100
+ !flags.version &&
101
+ (command === "guide" || wantsAutomaticGuide);
102
+ if (!flags.help &&
103
+ !flags.version &&
104
+ (guided || !["help", "version", "discover", "doctor"].includes(command))) {
105
+ await assertSupportedHostPlatform({ projectRoot });
106
+ }
107
+ const mutating = !flags.help &&
108
+ !flags.version &&
109
+ commandMutatesProject({ command, flags, guided });
99
110
  const operation = mutating
100
111
  ? await acquireProjectOperation({
101
112
  projectRoot,
@@ -207,7 +218,7 @@ try {
207
218
  await main();
208
219
  }
209
220
  catch (error) {
210
- const failure = normalizeRehearsalError(error, {
221
+ const failure = normalizeCliFailure(error, {
211
222
  expected: "a safe, versioned, local-only Rehearsal operation",
212
223
  actual: errorMessage(error),
213
224
  refused: "No further Rehearsal action was performed.",
@@ -168,13 +168,20 @@ export const createRuntimeActionHandlers = ({ prepareCandidateConfirmation, prep
168
168
  : undefined;
169
169
  const renderedHttpProofResults = "httpProofResults" in value ? value.httpProofResults : [];
170
170
  const renderedLifecycle = "lifecycle" in value ? value.lifecycle : undefined;
171
+ const hasApplicationLaunch = topology.primary.config.application.readiness !== null;
171
172
  const nextAction = {
172
173
  run: renderedLifecycle?.runtimeState === "stopped"
173
- ? "The runtime was stopped with its database and Storage changes preserved. Run rehearsal start or rehearsal open to resume."
174
- : "Next: run rehearsal open for hands-on testing, then rehearsal verify.",
175
- start: "Next: run rehearsal open for hands-on testing or rehearsal status.",
174
+ ? `The runtime was stopped with its database and Storage changes preserved. Run rehearsal start${hasApplicationLaunch ? " or rehearsal open" : ""} to resume.`
175
+ : hasApplicationLaunch
176
+ ? "Next: run rehearsal open for hands-on testing, then rehearsal verify."
177
+ : "The rehearsal command is complete; the database remains running. Next: run rehearsal verify or inspect the local database. This database-only project has no application to open.",
178
+ start: hasApplicationLaunch
179
+ ? "Next: run rehearsal open for hands-on testing or rehearsal status."
180
+ : "The database is running. Next: run rehearsal status or rehearsal verify; no application launch is configured.",
176
181
  migrate: "Next: run rehearsal verify to prove the current runtime.",
177
- reset: "Next: run rehearsal open or rehearsal verify.",
182
+ reset: hasApplicationLaunch
183
+ ? "Next: run rehearsal open or rehearsal verify."
184
+ : "Next: run rehearsal verify or inspect the local database; no application launch is configured.",
178
185
  status: "Next: run rehearsal verify, reset, stop, or discard.",
179
186
  stop: "Next: run rehearsal start when you want to resume.",
180
187
  discard: "The disposable runtime was removed; the immutable baseline remains.",
@@ -14,12 +14,14 @@ import { RehearsalError, normalizeRehearsalError, redactDiagnosticValue, } from
14
14
  import { assertInstalledPackageFingerprint } from "../shared/operation_guard.mjs";
15
15
  import { getOperationCancellationSignal, throwIfOperationCancelled, } from "../shared/cancellation.mjs";
16
16
  import { runOwnedProcess } from "../shared/owned_process.mjs";
17
+ import { createCleanProcessEnvironment } from "../shared/process_environment.mjs";
17
18
  import { getOwnedOperationProgressId, trackOperationStage, } from "../shared/operation_progress.mjs";
18
19
  import { buildRuntimeManagerArguments } from "../targets/target.mjs";
19
20
  import { parseRuntimeStatusSnapshot, renderRuntimeStatusSnapshot, } from "../runtime/status.mjs";
20
21
  import { applyRuntimePersistence, shouldStopRuntime, } from "../runtime/persistence.mjs";
21
22
  import { executeRuntimeTargetStack } from "./runtime_stack_execution.mjs";
22
- import { summarizeRuntimeManagerFailure } from "./runtime_failure_summary.mjs";
23
+ import { summarizeRuntimeManagerFailure, runtimePortFailureGuidance, } from "./runtime_failure_summary.mjs";
24
+ import { readManagerMigrationFailure } from "../runtime/migration_failure.mjs";
23
25
  import { releaseOwnedColimaWhenIdle } from "../runtime/container_engine_ownership.mjs";
24
26
  import { inspectRehearsalResources } from "../runtime/resource_inventory.mjs";
25
27
  import { assertLocalDockerContext } from "../runtime/local_docker_context.mjs";
@@ -55,27 +57,15 @@ const parseCommand = (source) => {
55
57
  throw new Error("Application proof command is empty.");
56
58
  return values;
57
59
  };
58
- const projectCommandEnvironment = (additional = {}) => ({
59
- ...Object.fromEntries([
60
- "CI",
61
- "COLORTERM",
62
- "FORCE_COLOR",
63
- "HOME",
64
- "LANG",
65
- "LC_ALL",
66
- "NO_COLOR",
67
- "PATH",
68
- "SHELL",
69
- "TERM",
70
- "TMPDIR",
71
- "USER",
72
- ].flatMap((key) => process.env[key] === undefined ? [] : [[key, process.env[key]]])),
73
- ...additional,
74
- });
60
+ const projectCommandEnvironment = (additional = {}) => createCleanProcessEnvironment({ overrides: additional });
75
61
  const RUNTIME_STARTUP_FAILURE_PATTERN = /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;
76
62
  export const classifyManagerFailure = ({ action, output, inferredCategory, }) => {
63
+ if (/^(?:Error:\s*)?The baseline (?:PostgreSQL|Supabase) migration failed \(/mu.test(output))
64
+ return "baseline_invalid";
65
+ if (/^(?:Error:\s*)?The confirmed candidate (?:PostgreSQL|Supabase) migration failed \(/mu.test(output))
66
+ return "migration_candidate_failure";
77
67
  if (RUNTIME_STARTUP_FAILURE_PATTERN.test(output) ||
78
- /Supabase startup failed \(/u.test(output) ||
68
+ /(?:PostgreSQL|Supabase) startup failed \(/u.test(output) ||
79
69
  /^Error:\s*supabase start(?:\s|$)/imu.test(output)) {
80
70
  return "runtime_dependency_failure";
81
71
  }
@@ -287,22 +277,31 @@ export const createRuntimeCommands = ({ packageRoot, projectRoot, managerPath, g
287
277
  inferredCategory: inferred.category,
288
278
  });
289
279
  const startupFailure = category === "runtime_dependency_failure";
290
- const engineCapacity = startupFailure
280
+ const migrationFailure = readManagerMigrationFailure(completeOutput);
281
+ const portGuidance = runtimePortFailureGuidance(conciseOutput);
282
+ const engineCapacity = startupFailure && portGuidance.length === 0
291
283
  ? inspectContainerCapacity()
292
284
  : undefined;
293
285
  throw new RehearsalError({
294
286
  category,
295
287
  code: `MANAGER_${action.toUpperCase()}_FAILED`,
296
- message: `The local Rehearsal ${action} operation did not complete.`,
288
+ message: `The local Rehearsal ${action} operation did not complete.${migrationFailure ? ` ${migrationFailure.summary}` : ""}`,
297
289
  expected: "the isolated runtime operation to finish and verify",
298
290
  actual: conciseOutput || `exit ${result.status}`,
299
- context: { action, ...(engineCapacity ? { engineCapacity } : {}) },
291
+ context: {
292
+ action,
293
+ target: targetName ?? "primary",
294
+ ...(migrationFailure ? { migrationFailure } : {}),
295
+ ...(engineCapacity ? { engineCapacity } : {}),
296
+ },
300
297
  refused: "The runtime was not reported as trusted.",
301
298
  suggestions: [
299
+ ...(migrationFailure?.suggestions ?? []),
300
+ ...portGuidance,
302
301
  "Review the concise failure above, then rerun rehearsal doctor before retrying.",
303
- ...(startupFailure
302
+ ...(startupFailure && portGuidance.length === 0
304
303
  ? [
305
- "Stop unrelated local stacks or increase Docker/Colima capacity, then retry. Rehearsal will not change engine settings or bypass service health checks.",
304
+ "If startup evidence confirms a resource shortage, stop only stacks you own or increase Docker/Colima capacity, then retry. Rehearsal will not change engine settings or bypass service health checks.",
306
305
  "Run docker stats --no-stream to identify active containers using the shared engine.",
307
306
  ]
308
307
  : []),
@@ -1 +1,2 @@
1
+ export declare const runtimePortFailureGuidance: (output: string) => readonly string[];
1
2
  export declare const summarizeRuntimeManagerFailure: (output: string) => string;
@@ -1,5 +1,17 @@
1
1
  /** Keep useful manager failures rather than debug advice and trailing Node stacks. */
2
2
  import { redactDiagnosticValue } from "../shared/diagnostics.mjs";
3
+ import { readManagerMigrationFailure, renderMigrationFailureDiagnostic, } from "../runtime/migration_failure.mjs";
4
+ import { portStartupGuidance } from "../targets/port_start_failure.mjs";
5
+ export const runtimePortFailureGuidance = (output) => {
6
+ const provider = output.match(/^(?:Error:\s*)?(PostgreSQL|Supabase) startup failed \((?:exit -?\d+|process failure)\)\.$/mu)?.[1];
7
+ const evidence = output
8
+ .match(/^Reported startup evidence: ([a-z_, ]+)\.$/mu)?.[1]
9
+ ?.split(/,\s*/u);
10
+ const port = evidence?.find((value) => value === "port_in_use" || value === "port_unavailable");
11
+ return provider && (port === "port_in_use" || port === "port_unavailable")
12
+ ? portStartupGuidance(port, provider === "PostgreSQL" ? "postgresql" : "supabase").split("\n")
13
+ : [];
14
+ };
3
15
  export const summarizeRuntimeManagerFailure = (output) => {
4
16
  let nodeSourceContext = false;
5
17
  const lines = output
@@ -27,16 +39,22 @@ export const summarizeRuntimeManagerFailure = (output) => {
27
39
  .replace(/\b[a-z][a-z0-9+.-]*:\/\/[^\s"']+/giu, "[REDACTED_URL]")
28
40
  .replace(/\b[A-Z][A-Z0-9_]*\s*=\s*[^\s]+/gu, "[REDACTED_ENV]")
29
41
  .replace(/(?:\/[A-Za-z0-9_.-]+){2,}(?::\d+(?::\d+)?)?/gu, "[LOCAL_PATH]"));
30
- const startup = lines.findIndex((line) => /Supabase startup failed \(/u.test(line));
42
+ const startup = lines.findIndex((line) => /(?:PostgreSQL|Supabase) startup failed \(/u.test(line));
31
43
  const failure = lines.reduce((last, line, index) => (/^[A-Za-z]*Error:/u.test(line) ? index : last), -1);
32
44
  const migration = lines.findIndex((line) => /^Error: The (?:confirmed candidate|baseline) (?:PostgreSQL|Supabase) migration failed \(exit (?:-?\d+|unknown); stderr \d+ bytes\)\. SQL and provider output are withheld\.$/u.test(line));
33
45
  const rollbackFailed = /The runtime operation failed and its owned runtime could not be removed automatically\./u.test(output);
46
+ const migrationDiagnostic = migration >= 0 ? readManagerMigrationFailure(lines.join("\n")) : undefined;
34
47
  const selected = startup >= 0
35
48
  ? lines
36
- .slice(startup, startup + 4)
37
- .filter((line) => /^(?:Error:\s*)?Supabase startup failed \(|^Reported startup evidence:|^Reported service states:|^Provider logs, paths, environment values and connection URLs are omitted\./u.test(line))
49
+ .slice(startup, startup + 12)
50
+ .filter((line) => /^(?:Error:\s*)?(?:PostgreSQL|Supabase) startup failed \(|^Reported startup evidence:|^Reported service states:|^Provider logs, (?:command arguments, )?paths, environment values and connection URLs are omitted\./u.test(line))
38
51
  : migration >= 0
39
- ? [lines[migration]]
52
+ ? [
53
+ lines[migration],
54
+ ...(migrationDiagnostic
55
+ ? [renderMigrationFailureDiagnostic(migrationDiagnostic)]
56
+ : []),
57
+ ]
40
58
  : rollbackFailed
41
59
  ? [
42
60
  "Error: The operation failed; the original cause could not be safely summarized.",
@@ -44,10 +62,18 @@ export const summarizeRuntimeManagerFailure = (output) => {
44
62
  : failure >= 0
45
63
  ? lines.slice(failure, failure + 8)
46
64
  : lines.slice(-8);
65
+ if (startup >= 0)
66
+ selected.push(...runtimePortFailureGuidance(selected.join("\n")));
47
67
  if (rollbackFailed)
48
68
  selected.push("Owned rollback cleanup could not be confirmed. Resources may remain; inspect activity and status before retrying. No cleanup success is claimed.");
49
69
  if (startup >= 0 &&
50
70
  /owned cleanup could not be proved|owned resources were retained|Resources and data were retained|retained resources need review/iu.test(output))
51
71
  selected.push("Owned startup cleanup could not be proved; resources and data were retained. Inspect activity and status before retrying.");
72
+ if (selected.join("\n").length > 2_000 && migrationDiagnostic?.source) {
73
+ // Human summary stays bounded and retains cleanup warnings; structured output
74
+ // independently reads the complete, bounded manager frame.
75
+ const { source: _source, ...diagnostic } = migrationDiagnostic;
76
+ selected[1] = renderMigrationFailureDiagnostic(diagnostic);
77
+ }
52
78
  return selected.join("\n").slice(0, 2_000);
53
79
  };
@@ -1,6 +1,7 @@
1
1
  import { buildRehearsalPlan, runRehearsalDoctor } from "../runtime/plan.mjs";
2
2
  import { inspectRuntimeTopologyCandidates, loadRuntimeTopology, } from "../runtime/topology.mjs";
3
3
  import { redactDiagnosticValue } from "../shared/diagnostics.mjs";
4
+ import { inspectHostPlatform } from "../shared/host_platform.mjs";
4
5
  const errorMessage = (error) => error instanceof Error ? error.message : String(error);
5
6
  export const buildRuntimeStackPlan = async (planOptions) => {
6
7
  const topology = await loadRuntimeTopology(planOptions);
@@ -52,6 +53,11 @@ export const buildRuntimeStackPlan = async (planOptions) => {
52
53
  };
53
54
  };
54
55
  export const runRuntimeStackDoctor = async (planOptions) => {
56
+ const host = await inspectHostPlatform({
57
+ projectRoot: planOptions.projectRoot ?? process.cwd(),
58
+ });
59
+ if (!host.supported)
60
+ return runRehearsalDoctor(planOptions);
55
61
  let topology;
56
62
  try {
57
63
  topology = await loadRuntimeTopology(planOptions);
@@ -6,6 +6,7 @@ import { access, readFile, readdir, stat } from "node:fs/promises";
6
6
  import { isAbsolute, join, relative, resolve, sep } from "node:path";
7
7
  import { applyRehearsalSetup, planRehearsalSetup } from "./setup.mjs";
8
8
  import { findRehearsalConfigPath, inspectDetectedProject, loadRehearsalConfig, } from "./configuration.mjs";
9
+ import { inspectHostPlatform } from "../shared/host_platform.mjs";
9
10
  const PACKAGE_NAME = "@rehearsal-db/core";
10
11
  const DEPENDENCY_GROUPS = Object.freeze([
11
12
  "dependencies",
@@ -224,6 +225,12 @@ export const createInstalledRehearsalScaffold = async ({ packageRoot, environmen
224
225
  });
225
226
  if (resolution.status !== "resolved")
226
227
  return resolution;
228
+ const host = await inspectHostPlatform({
229
+ projectRoot: resolution.projectRoot,
230
+ });
231
+ if (!host.supported) {
232
+ return skip(`${host.detail} ${host.remediation}`.replace(/\.$/u, ""));
233
+ }
227
234
  const detected = await inspectDetectedProject({
228
235
  projectRoot: resolution.projectRoot,
229
236
  });
@@ -7,6 +7,7 @@ import { lstat, readFile, readdir } from "node:fs/promises";
7
7
  import { basename, join, relative, resolve, sep } from "node:path";
8
8
  import { promisify } from "node:util";
9
9
  import { CONFIG_FILENAMES } from "./configuration_discovery.mjs";
10
+ import { createCleanProcessEnvironment } from "../shared/process_environment.mjs";
10
11
  const MAX_METADATA_BYTES = 1024 * 1024;
11
12
  const execFileAsync = promisify(execFile);
12
13
  const inside = (root, path) => {
@@ -112,17 +113,14 @@ export const probeContainerEngineWithoutStarting = async () => {
112
113
  await execFileAsync("docker", ["info", "--format", "{{.ServerVersion}}"], {
113
114
  timeout: 2_000,
114
115
  maxBuffer: 64 * 1024,
115
- env: Object.fromEntries([
116
- "COLIMA_HOME",
117
- "DOCKER_CONFIG",
118
- "DOCKER_CONTEXT",
119
- "DOCKER_HOST",
120
- "HOME",
121
- "PATH",
122
- "USER",
123
- ]
124
- .map((key) => [key, process.env[key]])
125
- .filter((entry) => Boolean(entry[1]))),
116
+ env: createCleanProcessEnvironment({
117
+ passthroughKeys: [
118
+ "COLIMA_HOME",
119
+ "DOCKER_CONFIG",
120
+ "DOCKER_CONTEXT",
121
+ "DOCKER_HOST",
122
+ ],
123
+ }),
126
124
  });
127
125
  return "running";
128
126
  }
@@ -0,0 +1,28 @@
1
+ /** Translate provider-native SQLSTATE evidence into fixed, privacy-safe guidance. */
2
+ import { type MigrationFailureSource } from "./migration_failure_source.mjs";
3
+ export type MigrationFailureClassification = "syntax" | "schema" | "constraint" | "permission" | "connectivity" | "timeout_or_cancel" | "unknown";
4
+ export interface MigrationFailureDiagnostic {
5
+ readonly schemaVersion: 1;
6
+ readonly provider: "postgresql" | "supabase";
7
+ readonly classification: MigrationFailureClassification;
8
+ readonly sqlState: string | null;
9
+ readonly evidence: "provider_sqlstate" | "unavailable" | "ambiguous";
10
+ readonly summary: string;
11
+ readonly suggestions: readonly string[];
12
+ readonly source?: MigrationFailureSource;
13
+ }
14
+ export declare const migrationFailureDiagnostic: ({ provider, sqlState, evidence, }: {
15
+ provider: MigrationFailureDiagnostic["provider"];
16
+ sqlState?: string | null;
17
+ evidence?: MigrationFailureDiagnostic["evidence"];
18
+ }) => MigrationFailureDiagnostic;
19
+ /** Read only known provider error envelopes, never arbitrary mentions of SQLSTATE. */
20
+ export declare const diagnoseMigrationFailure: ({ provider, stderr, }: {
21
+ provider: MigrationFailureDiagnostic["provider"];
22
+ stderr: string;
23
+ }) => MigrationFailureDiagnostic;
24
+ export declare const renderMigrationFailureDiagnostic: (diagnostic: MigrationFailureDiagnostic) => string;
25
+ /** Closed public projection: reject free text or metadata not generated by this owner. */
26
+ export declare const parseMigrationFailureDiagnostic: (value: unknown) => MigrationFailureDiagnostic;
27
+ /** Parse only Rehearsal's fixed migration header and adjacent fixed code line. */
28
+ export declare const readManagerMigrationFailure: (output: string) => MigrationFailureDiagnostic | undefined;