@rehearsal-db/core 1.0.1 → 1.1.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/CHANGELOG.md +62 -3
- package/COMPATIBILITY.md +10 -4
- package/README.md +2 -2
- package/dist/package.json +1 -1
- package/dist/src/cli/failure_output.d.mts +3 -0
- package/dist/src/cli/failure_output.mjs +48 -0
- package/dist/src/cli/rehearsal.mjs +3 -2
- package/dist/src/cli/runtime_action_handlers.mjs +11 -4
- package/dist/src/cli/runtime_commands.mjs +21 -7
- package/dist/src/cli/runtime_failure_summary.d.mts +1 -0
- package/dist/src/cli/runtime_failure_summary.mjs +30 -4
- package/dist/src/project/configuration_contract.d.mts +1 -0
- package/dist/src/project/configuration_normalization.mjs +2 -1
- package/dist/src/project/configuration_rendering.mjs +3 -2
- package/dist/src/project/configuration_resolution.mjs +0 -2
- package/dist/src/project/project_detection.d.mts +2 -0
- package/dist/src/project/project_detection.mjs +1 -0
- package/dist/src/project/runtime_project_identity.d.mts +5 -0
- package/dist/src/project/runtime_project_identity.mjs +41 -0
- package/dist/src/project/setup.mjs +3 -1
- package/dist/src/runtime/migration_failure.d.mts +28 -0
- package/dist/src/runtime/migration_failure.mjs +197 -0
- package/dist/src/runtime/migration_failure_source.d.mts +16 -0
- package/dist/src/runtime/migration_failure_source.mjs +68 -0
- package/dist/src/runtime/policy.d.mts +2 -0
- package/dist/src/runtime/policy.mjs +13 -1
- package/dist/src/runtime/privileges.d.mts +25 -0
- package/dist/src/runtime/privileges.mjs +198 -0
- package/dist/src/shared/diagnostics.mjs +5 -0
- package/dist/src/shared/operation_guard.d.mts +4 -1
- package/dist/src/shared/operation_guard.mjs +18 -13
- package/dist/src/shared/operation_progress.mjs +35 -1
- package/dist/src/shared/operation_progress_storage.d.mts +27 -6
- package/dist/src/shared/operation_progress_storage.mjs +65 -9
- package/dist/src/targets/port_start_failure.d.mts +15 -0
- package/dist/src/targets/port_start_failure.mjs +46 -0
- package/dist/src/targets/postgresql.mjs +8 -7
- package/dist/src/targets/postgresql_process.d.mts +8 -1
- package/dist/src/targets/postgresql_process.mjs +46 -2
- package/dist/src/targets/supabase.mjs +1 -0
- package/dist/src/targets/supabase_process.mjs +2 -1
- package/dist/src/targets/supabase_start_failure.mjs +5 -1
- package/docs/README.md +1 -1
- package/docs/architecture.md +32 -2
- package/docs/commands.md +35 -0
- package/docs/configuration.md +19 -4
- package/docs/getting-started.md +4 -4
- package/docs/releasing.md +4 -2
- package/docs/roadmap.md +30 -9
- package/docs/runtime-lifecycle.md +5 -4
- package/docs/runtime-policies.md +145 -0
- package/docs/stability.md +8 -4
- package/docs/troubleshooting.md +54 -2
- package/docs/tutorial.md +93 -8
- package/docs/vscode.md +48 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
All notable Rehearsal package changes
|
|
4
|
-
a Changelog, and versions
|
|
3
|
+
All notable Rehearsal package changes are documented here. The format follows Keep
|
|
4
|
+
a Changelog, and published versions follow Semantic Versioning.
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.1.0] - 2026-10-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Optional read-only runtime privilege expectations for reviewed schemas, tables,
|
|
13
|
+
sequences and exact function signatures. Required and forbidden effective access is
|
|
14
|
+
separate from RLS, includes inherited and column-only permissions, and never repairs
|
|
15
|
+
grants automatically. Existing policies without this field keep their behavior.
|
|
16
|
+
- Document explicit least-privilege Data API grants, forward-only correction and
|
|
17
|
+
positive/negative API proofs for Supabase's October 30 opt-in table-exposure change.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Record the Supabase provider in newly written runtime receipts, so inventory can
|
|
22
|
+
distinguish recorded format/provider compatibility from unknown legacy provenance.
|
|
23
|
+
Older receipts remain unknown rather than being silently rewritten or reverified.
|
|
24
|
+
- Bound newly generated Supabase project IDs to the provider's 40-character limit
|
|
25
|
+
with a deterministic suffix. Preserve valid existing identities; reject invalid
|
|
26
|
+
configured IDs without renaming or adopting resources.
|
|
27
|
+
- Align OS setup helpers with core's Node.js 22, 24 and 26 support. macOS/Linux
|
|
28
|
+
helpers accept an explicit `REHEARSAL_NODE_PATH`, and missing-Node guidance points
|
|
29
|
+
to version-manager selection or the official installer without installing an OS tool.
|
|
30
|
+
- Update compatible older CLI installations to the helper's exact current target
|
|
31
|
+
instead of treating every 1.0.1-or-newer installation as already current.
|
|
32
|
+
|
|
33
|
+
## [1.0.2] - 2026-10-20
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Explain recognized PostgreSQL/Supabase migration failures with bounded native
|
|
38
|
+
SQLSTATE evidence and safe syntax, schema, constraint, permission, connectivity
|
|
39
|
+
and timeout guidance. Unsupported or ambiguous evidence remains unknown; raw
|
|
40
|
+
SQL, provider details, credentials and private rows are not exposed.
|
|
41
|
+
- Preserve the original diagnosis across cleanup and bounded operation history so
|
|
42
|
+
the CLI and editor can show the same failure after refresh. Failed operations
|
|
43
|
+
never claim verification or unconditional rollback success.
|
|
44
|
+
- Optionally identify the exact approved PostgreSQL input file by hash, without
|
|
45
|
+
inventing a failing SQL origin, line or column. Reject invalid UTF-8 before
|
|
46
|
+
execution rather than submitting replacement-decoded bytes.
|
|
47
|
+
- Point recognized bind failures at the configured local ports, including safe
|
|
48
|
+
Windows/WSL exclusion-inspection guidance. Do not infer a reservation from every
|
|
49
|
+
denial or suggest terminating unknown listeners.
|
|
50
|
+
- Keep database-only command guidance separate from optional application launch.
|
|
51
|
+
|
|
52
|
+
### Documentation and distribution
|
|
53
|
+
|
|
54
|
+
- Add a main-tutorial existing-row constraint failure, reviewed backfill/fix and
|
|
55
|
+
verification sequence, plus a same-project VS Code handoff and safe local-copy
|
|
56
|
+
recovery for unsupported external npm links.
|
|
57
|
+
- Pair with Starter 0.1.22 for actionable persistent failures, safe exact-byte
|
|
58
|
+
source opening, distinct command/database states and truthful recorded proof.
|
|
59
|
+
- Refresh the separate macOS, Linux and Windows/WSL 2 setup bundles to exact core
|
|
60
|
+
1.0.2. Native Windows database execution remains unsupported.
|
|
61
|
+
|
|
62
|
+
## [1.0.1] - 2026-10-19
|
|
63
|
+
|
|
8
64
|
### Platform compatibility
|
|
9
65
|
|
|
10
66
|
- Keep macOS Intel/Apple Silicon and Linux execution distinct from Windows desktop
|
|
@@ -784,7 +840,10 @@ not unattended cleanup, and container age is not proof of user inactivity.
|
|
|
784
840
|
publication uses short-lived trusted OIDC, and every release tag must already exist on
|
|
785
841
|
protected `main`.
|
|
786
842
|
|
|
787
|
-
[Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.
|
|
843
|
+
[Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.1.0...HEAD
|
|
844
|
+
[1.1.0]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.2...v1.1.0
|
|
845
|
+
[1.0.2]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.1...v1.0.2
|
|
846
|
+
[1.0.1]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.0...v1.0.1
|
|
788
847
|
[1.0.0]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.10...v1.0.0
|
|
789
848
|
[0.1.0-rc.10]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.9...v0.1.0-rc.10
|
|
790
849
|
[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
|
@@ -26,12 +26,18 @@ The first `1.0.0` release requires external beta evidence, a support policy and
|
|
|
26
26
|
settled public API described in [the stability policy](docs/stability.md). Supporting
|
|
27
27
|
additional database families, package managers or operating systems is not implied.
|
|
28
28
|
|
|
29
|
-
Rehearsal supports maintained
|
|
30
|
-
matrix
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
Rehearsal supports the maintained Node.js release lines verified by its existing CI
|
|
30
|
+
matrix: Node.js 22, 24, and 26. Node.js 24 LTS is the recommended default and the
|
|
31
|
+
release-build runtime. End-of-life and unverified future release lines are not
|
|
32
|
+
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
|
+
Core 1.1.0's OS helpers accept the same three lines. A terminal or desktop launcher
|
|
36
|
+
that cannot find Node does not mean an installed version is unsupported. Select it
|
|
37
|
+
through the project's existing version manager or use the helper's explicit executable
|
|
38
|
+
path; see [OS setup](installers/README.md). Helpers never install Node or switch a
|
|
39
|
+
project's runtime automatically.
|
|
40
|
+
|
|
35
41
|
## Operating systems and execution location
|
|
36
42
|
|
|
37
43
|
Core 1.0.1's platform boundary distinguishes the computer running VS Code from the
|
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ it through a reviewed privacy policy, and retire the access again. It is never p
|
|
|
47
47
|
production deployment tool.
|
|
48
48
|
|
|
49
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
|
|
50
|
+
[npm](https://www.npmjs.com/package/@rehearsal-db/core). This guide targets **1.1.0**;
|
|
51
51
|
install only a version actually published there. Prefer a visual workflow?
|
|
52
52
|
[Install the free VS Code Starter preview](docs/vscode.md). Its included features are
|
|
53
53
|
free indefinitely for personal or commercial development—no account, card, or trial.
|
|
@@ -71,7 +71,7 @@ your migration is intended to change.
|
|
|
71
71
|
|
|
72
72
|
Prefer OS-specific files? Use the [Windows / WSL 2](installers/windows/README.md),
|
|
73
73
|
[macOS](installers/macos/README.md) or [Linux](installers/linux/README.md) setup folder.
|
|
74
|
-
These readable helpers
|
|
74
|
+
These readable helpers accept existing Node.js 22, 24 or 26 and check prerequisites by
|
|
75
75
|
default; installation is an explicit option. They do not install OS tools or start a
|
|
76
76
|
database. See [their exact behavior and release availability](installers/README.md).
|
|
77
77
|
|
package/dist/package.json
CHANGED
|
@@ -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,
|
|
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";
|
|
@@ -217,7 +218,7 @@ try {
|
|
|
217
218
|
await main();
|
|
218
219
|
}
|
|
219
220
|
catch (error) {
|
|
220
|
-
const failure =
|
|
221
|
+
const failure = normalizeCliFailure(error, {
|
|
221
222
|
expected: "a safe, versioned, local-only Rehearsal operation",
|
|
222
223
|
actual: errorMessage(error),
|
|
223
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
|
-
?
|
|
174
|
-
:
|
|
175
|
-
|
|
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:
|
|
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.",
|
|
@@ -20,7 +20,8 @@ import { buildRuntimeManagerArguments } from "../targets/target.mjs";
|
|
|
20
20
|
import { parseRuntimeStatusSnapshot, renderRuntimeStatusSnapshot, } from "../runtime/status.mjs";
|
|
21
21
|
import { applyRuntimePersistence, shouldStopRuntime, } from "../runtime/persistence.mjs";
|
|
22
22
|
import { executeRuntimeTargetStack } from "./runtime_stack_execution.mjs";
|
|
23
|
-
import { summarizeRuntimeManagerFailure } from "./runtime_failure_summary.mjs";
|
|
23
|
+
import { summarizeRuntimeManagerFailure, runtimePortFailureGuidance, } from "./runtime_failure_summary.mjs";
|
|
24
|
+
import { readManagerMigrationFailure } from "../runtime/migration_failure.mjs";
|
|
24
25
|
import { releaseOwnedColimaWhenIdle } from "../runtime/container_engine_ownership.mjs";
|
|
25
26
|
import { inspectRehearsalResources } from "../runtime/resource_inventory.mjs";
|
|
26
27
|
import { assertLocalDockerContext } from "../runtime/local_docker_context.mjs";
|
|
@@ -59,8 +60,12 @@ const parseCommand = (source) => {
|
|
|
59
60
|
const projectCommandEnvironment = (additional = {}) => createCleanProcessEnvironment({ overrides: additional });
|
|
60
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;
|
|
61
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";
|
|
62
67
|
if (RUNTIME_STARTUP_FAILURE_PATTERN.test(output) ||
|
|
63
|
-
/Supabase startup failed \(/u.test(output) ||
|
|
68
|
+
/(?:PostgreSQL|Supabase) startup failed \(/u.test(output) ||
|
|
64
69
|
/^Error:\s*supabase start(?:\s|$)/imu.test(output)) {
|
|
65
70
|
return "runtime_dependency_failure";
|
|
66
71
|
}
|
|
@@ -272,22 +277,31 @@ export const createRuntimeCommands = ({ packageRoot, projectRoot, managerPath, g
|
|
|
272
277
|
inferredCategory: inferred.category,
|
|
273
278
|
});
|
|
274
279
|
const startupFailure = category === "runtime_dependency_failure";
|
|
275
|
-
const
|
|
280
|
+
const migrationFailure = readManagerMigrationFailure(completeOutput);
|
|
281
|
+
const portGuidance = runtimePortFailureGuidance(conciseOutput);
|
|
282
|
+
const engineCapacity = startupFailure && portGuidance.length === 0
|
|
276
283
|
? inspectContainerCapacity()
|
|
277
284
|
: undefined;
|
|
278
285
|
throw new RehearsalError({
|
|
279
286
|
category,
|
|
280
287
|
code: `MANAGER_${action.toUpperCase()}_FAILED`,
|
|
281
|
-
message: `The local Rehearsal ${action} operation did not complete
|
|
288
|
+
message: `The local Rehearsal ${action} operation did not complete.${migrationFailure ? ` ${migrationFailure.summary}` : ""}`,
|
|
282
289
|
expected: "the isolated runtime operation to finish and verify",
|
|
283
290
|
actual: conciseOutput || `exit ${result.status}`,
|
|
284
|
-
context: {
|
|
291
|
+
context: {
|
|
292
|
+
action,
|
|
293
|
+
target: targetName ?? "primary",
|
|
294
|
+
...(migrationFailure ? { migrationFailure } : {}),
|
|
295
|
+
...(engineCapacity ? { engineCapacity } : {}),
|
|
296
|
+
},
|
|
285
297
|
refused: "The runtime was not reported as trusted.",
|
|
286
298
|
suggestions: [
|
|
299
|
+
...(migrationFailure?.suggestions ?? []),
|
|
300
|
+
...portGuidance,
|
|
287
301
|
"Review the concise failure above, then rerun rehearsal doctor before retrying.",
|
|
288
|
-
...(startupFailure
|
|
302
|
+
...(startupFailure && portGuidance.length === 0
|
|
289
303
|
? [
|
|
290
|
-
"
|
|
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.",
|
|
291
305
|
"Run docker stats --no-stream to identify active containers using the shared engine.",
|
|
292
306
|
]
|
|
293
307
|
: []),
|
|
@@ -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 +
|
|
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
|
-
? [
|
|
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
|
};
|
|
@@ -126,6 +126,7 @@ export interface RehearsalConfig {
|
|
|
126
126
|
protected?: boolean;
|
|
127
127
|
target?: "supabase" | "postgresql";
|
|
128
128
|
applicationUrl?: string;
|
|
129
|
+
/** Exact lowercase/digit/hyphen ownership ID: 2–40 Supabase, 2–63 PostgreSQL. */
|
|
129
130
|
projectId?: string;
|
|
130
131
|
apiPort?: number;
|
|
131
132
|
databasePort: number;
|
|
@@ -9,6 +9,7 @@ import { normalizeResourcePolicy, normalizeRequiredServices, } from "./configura
|
|
|
9
9
|
import { normalizeConfigurationProfiles } from "./configuration_profiles.mjs";
|
|
10
10
|
import { normalizeApplicationUrls } from "./configuration_application_urls.mjs";
|
|
11
11
|
import { normalizeApplicationActivity } from "./configuration_activity.mjs";
|
|
12
|
+
import { assertRuntimeProjectId } from "./runtime_project_identity.mjs";
|
|
12
13
|
import { assertBoolean, assertCommand, assertDatabaseIdentifier, assertEnvironmentKey, assertEnvironmentReference, assertHttpStatus, assertIdentifier, assertKnownKeys, assertLoopbackUrl, assertNonEmptyString, assertPlainObject, assertPort, assertPostgresImage, assertPositiveInteger, assertRelativePath, assertStringArray, } from "./configuration_validation.mjs";
|
|
13
14
|
const LOOPBACK_HOSTS = new Set(REHEARSAL_DEFAULTS.safety.allowedHosts);
|
|
14
15
|
const SUPPORTED_SUPABASE_OAUTH_PROVIDERS = new Set(["github", "google"]);
|
|
@@ -351,7 +352,7 @@ export const normalizeRehearsalConfig = (input) => {
|
|
|
351
352
|
protected: assertBoolean(runtime.protected ?? false, "config.runtime.protected"),
|
|
352
353
|
target: runtimeTarget,
|
|
353
354
|
applicationUrl: applicationUrls.applicationUrl,
|
|
354
|
-
projectId:
|
|
355
|
+
projectId: assertRuntimeProjectId(runtime.projectId ?? REHEARSAL_DEFAULTS.runtime.projectId, runtimeTarget),
|
|
355
356
|
ports: Object.freeze(ports),
|
|
356
357
|
}),
|
|
357
358
|
safety: Object.freeze({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Render reviewed starter configurations without reading or writing a project. */
|
|
2
|
+
import { createRuntimeProjectId } from "./runtime_project_identity.mjs";
|
|
2
3
|
export const renderDetectedConfig = (detected, { applicationUrl = "http://localhost:5175", ports = { api: 58321, database: 58322, studio: 58323 }, } = {}) => `// @ts-check
|
|
3
4
|
/**
|
|
4
5
|
* Rehearsal configuration for ${detected.projectName}.
|
|
@@ -132,7 +133,7 @@ export default defineRehearsalConfig({
|
|
|
132
133
|
protected: false, // Pin data against reset, run, migrate, discard, and runtime cleanup.
|
|
133
134
|
target: "supabase",
|
|
134
135
|
applicationUrl: ${JSON.stringify(applicationUrl)},
|
|
135
|
-
projectId:
|
|
136
|
+
projectId: ${JSON.stringify(createRuntimeProjectId(detected.projectName, "supabase", detected.projectIdSeed))},
|
|
136
137
|
apiPort: ${ports.api},
|
|
137
138
|
databasePort: ${ports.database},
|
|
138
139
|
studioPort: ${ports.studio},
|
|
@@ -266,7 +267,7 @@ export default defineRehearsalConfig({
|
|
|
266
267
|
protected: false, // Pin data against reset, run, migrate, discard, and runtime cleanup.
|
|
267
268
|
target: "postgresql",
|
|
268
269
|
applicationUrl: ${JSON.stringify(applicationUrl)},
|
|
269
|
-
projectId:
|
|
270
|
+
projectId: ${JSON.stringify(createRuntimeProjectId(detected.projectName, "postgresql", detected.projectIdSeed))},
|
|
270
271
|
databasePort: ${databasePort},
|
|
271
272
|
},
|
|
272
273
|
|
|
@@ -3,7 +3,6 @@ import { basename, isAbsolute, join, relative, resolve } from "node:path";
|
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { findRehearsalConfigPath, } from "./configuration_discovery.mjs";
|
|
5
5
|
import { normalizeRehearsalConfig } from "./configuration_normalization.mjs";
|
|
6
|
-
import { assertIdentifier } from "./configuration_validation.mjs";
|
|
7
6
|
export const loadRehearsalConfig = async ({ projectRoot = process.cwd(), configPath, } = {}) => {
|
|
8
7
|
const root = resolve(projectRoot);
|
|
9
8
|
const path = await findRehearsalConfigPath({
|
|
@@ -74,7 +73,6 @@ export const loadRehearsalConfig = async ({ projectRoot = process.cwd(), configP
|
|
|
74
73
|
throw new Error("config.preparation.privacyKey must resolve to a secret file inside config.baseline.artifactDirectory.");
|
|
75
74
|
}
|
|
76
75
|
}
|
|
77
|
-
assertIdentifier(config.runtime.projectId, "config.runtime.projectId");
|
|
78
76
|
return Object.freeze({
|
|
79
77
|
config,
|
|
80
78
|
configPath: path,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/** Inspect project-local files used to render a conservative starter config. */
|
|
2
2
|
export interface DetectedProject {
|
|
3
3
|
projectName: string;
|
|
4
|
+
/** Untruncated normalized name keeps shortened new runtime identities distinct. */
|
|
5
|
+
projectIdSeed?: string;
|
|
4
6
|
packageManager: "npm" | "pnpm" | "yarn";
|
|
5
7
|
hasSupabaseConfig: boolean;
|
|
6
8
|
hasMigrations: boolean;
|
|
@@ -31,6 +31,7 @@ export const inspectDetectedProject = async ({ projectRoot = process.cwd(), } =
|
|
|
31
31
|
"rehearsal-project";
|
|
32
32
|
return {
|
|
33
33
|
projectName,
|
|
34
|
+
projectIdSeed: normalizedProjectName || projectName,
|
|
34
35
|
packageManager,
|
|
35
36
|
hasSupabaseConfig: await hasPath("supabase/config.toml"),
|
|
36
37
|
hasMigrations: await hasPath("supabase/migrations"),
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Preserve exact configured runtime ownership and bound newly generated identities. */
|
|
2
|
+
import type { RehearsalRuntimeTargetId } from "../targets/target.mjs";
|
|
3
|
+
export declare const assertRuntimeProjectId: (value: unknown, target: RehearsalRuntimeTargetId) => string;
|
|
4
|
+
/** Only new setup identities are shortened; valid existing generated IDs stay stable. */
|
|
5
|
+
export declare const createRuntimeProjectId: (projectName: string, target: RehearsalRuntimeTargetId, identitySeed?: string) => string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Preserve exact configured runtime ownership and bound newly generated identities. */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { RehearsalError } from "../shared/diagnostics.mjs";
|
|
4
|
+
import { assertIdentifier, assertNonEmptyString, } from "./configuration_validation.mjs";
|
|
5
|
+
const maximumProjectIdLength = (target) => target === "supabase" ? 40 : 63;
|
|
6
|
+
export const assertRuntimeProjectId = (value, target) => {
|
|
7
|
+
const projectId = assertNonEmptyString(value, "config.runtime.projectId");
|
|
8
|
+
const maximumLength = maximumProjectIdLength(target);
|
|
9
|
+
if (projectId.length < 2 || projectId.length > maximumLength) {
|
|
10
|
+
const provider = target === "supabase" ? "Supabase" : "PostgreSQL";
|
|
11
|
+
throw new RehearsalError({
|
|
12
|
+
category: "configuration_invalid",
|
|
13
|
+
code: "CONFIGURATION_INVALID",
|
|
14
|
+
message: `config.runtime.projectId must contain 2–${maximumLength} characters for ${provider}.`,
|
|
15
|
+
expected: `2–${maximumLength} lowercase letters, digits, or hyphens`,
|
|
16
|
+
actual: { length: projectId.length },
|
|
17
|
+
context: { field: "runtime.projectId", target, maximumLength },
|
|
18
|
+
refused: "No runtime was started or renamed. Rehearsal never truncates a configured project ID or adopts resources by prefix.",
|
|
19
|
+
suggestions: [
|
|
20
|
+
"Choose a distinct, explicitly reviewed ID for a new disposable runtime. Review existing resources before changing their configured identity.",
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return assertIdentifier(projectId, "config.runtime.projectId");
|
|
25
|
+
};
|
|
26
|
+
/** Only new setup identities are shortened; valid existing generated IDs stay stable. */
|
|
27
|
+
export const createRuntimeProjectId = (projectName, target, identitySeed = projectName) => {
|
|
28
|
+
const candidate = `${projectName}-rehearsal`;
|
|
29
|
+
const maximumLength = maximumProjectIdLength(target);
|
|
30
|
+
if (candidate.length <= maximumLength)
|
|
31
|
+
return assertRuntimeProjectId(candidate, target);
|
|
32
|
+
const digest = createHash("sha256")
|
|
33
|
+
.update(`${identitySeed}-rehearsal`)
|
|
34
|
+
.digest("hex")
|
|
35
|
+
.slice(0, 12);
|
|
36
|
+
const suffix = `-${digest}-rehearsal`;
|
|
37
|
+
const prefix = projectName
|
|
38
|
+
.slice(0, maximumLength - suffix.length)
|
|
39
|
+
.replace(/-+$/u, "");
|
|
40
|
+
return assertRuntimeProjectId(`${prefix}${suffix}`, target);
|
|
41
|
+
};
|
|
@@ -8,6 +8,7 @@ import { access, mkdir, open, readFile, rename, rm, writeFile, } from "node:fs/p
|
|
|
8
8
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
9
9
|
import { findRehearsalConfigPath, inspectDetectedProject, loadRehearsalConfig, renderDetectedConfig, renderDetectedPostgresqlConfig, } from "./configuration.mjs";
|
|
10
10
|
import { resolveRuntimeTarget } from "../targets/target.mjs";
|
|
11
|
+
import { createRuntimeProjectId } from "./runtime_project_identity.mjs";
|
|
11
12
|
export { assertSupportedRehearsalNodeRuntime, inspectRehearsalNodeRuntime, } from "../shared/node_runtime.mjs";
|
|
12
13
|
import { assertSupportedRehearsalNodeRuntime } from "../shared/node_runtime.mjs";
|
|
13
14
|
const CONFIG_PATH = "rehearsal.config.mjs";
|
|
@@ -257,7 +258,8 @@ export const planRehearsalSetup = async ({ projectRoot = process.cwd(), configPa
|
|
|
257
258
|
...(isPortAvailable ? { isAvailable: isPortAvailable } : {}),
|
|
258
259
|
});
|
|
259
260
|
const applicationUrl = existing?.config.runtime.applicationUrl ?? "http://localhost:5175";
|
|
260
|
-
const projectId = existing?.config.runtime.projectId ??
|
|
261
|
+
const projectId = existing?.config.runtime.projectId ??
|
|
262
|
+
createRuntimeProjectId(detected.projectName, runtimeTarget, detected.projectIdSeed);
|
|
261
263
|
const sourceSupabaseConfig = runtimeTarget === "supabase"
|
|
262
264
|
? await readOptionalFile(join(root, "supabase/config.toml"))
|
|
263
265
|
: null;
|
|
@@ -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;
|