@rehearsal-db/core 1.1.0 → 1.3.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 +43 -1
- package/COMPATIBILITY.md +40 -0
- package/README.md +7 -3
- package/dist/package.json +4 -1
- package/dist/src/cli/arguments.d.mts +1 -0
- package/dist/src/cli/arguments.mjs +5 -0
- package/dist/src/cli/basic_command_handlers.mjs +6 -0
- package/dist/src/cli/cli_commands.mjs +2 -0
- package/dist/src/cli/editor_command_handlers.mjs +24 -0
- package/dist/src/cli/project_commands.d.mts +8 -0
- package/dist/src/cli/runtime_commands.d.mts +2 -19
- package/dist/src/cli/runtime_commands.mjs +3 -90
- package/dist/src/cli/runtime_stack_runner.d.mts +41 -0
- package/dist/src/cli/runtime_stack_runner.mjs +104 -0
- package/dist/src/cli/runtime_workload_admission.d.mts +8 -0
- package/dist/src/cli/runtime_workload_admission.mjs +22 -0
- package/dist/src/cli/source_commands.d.mts +8 -0
- package/dist/src/cli/workload_command_handlers.d.mts +2 -0
- package/dist/src/cli/workload_command_handlers.mjs +38 -0
- package/dist/src/project/configuration_contract.d.mts +5 -0
- package/dist/src/project/configuration_normalization.d.mts +4 -0
- package/dist/src/project/configuration_resolution.d.mts +4 -0
- package/dist/src/project/configuration_resource_policy.d.mts +4 -0
- package/dist/src/project/configuration_resource_policy.mjs +20 -1
- package/dist/src/project/configuration_schema.mjs +1 -0
- package/dist/src/runtime/migration_history.d.mts +5 -1
- package/dist/src/runtime/migration_history.mjs +24 -6
- package/dist/src/runtime/migration_impact.d.mts +125 -0
- package/dist/src/runtime/migration_impact.mjs +278 -0
- package/dist/src/runtime/migration_impact_catalog.d.mts +18 -0
- package/dist/src/runtime/migration_impact_catalog.mjs +116 -0
- package/dist/src/runtime/migration_impact_plan.d.mts +47 -0
- package/dist/src/runtime/migration_impact_plan.mjs +297 -0
- package/dist/src/runtime/migration_impact_queries.d.mts +22 -0
- package/dist/src/runtime/migration_impact_queries.mjs +270 -0
- package/dist/src/runtime/migration_insights.d.mts +137 -0
- package/dist/src/runtime/migration_insights.mjs +143 -0
- package/dist/src/runtime/migration_insights_input.d.mts +13 -0
- package/dist/src/runtime/migration_insights_input.mjs +153 -0
- package/dist/src/runtime/migration_sql_lexer.d.mts +14 -0
- package/dist/src/runtime/migration_sql_lexer.mjs +204 -0
- package/dist/src/runtime/migration_sql_scan.d.mts +16 -0
- package/dist/src/runtime/migration_sql_scan.mjs +369 -0
- package/dist/src/runtime/plan.d.mts +2 -1
- package/dist/src/runtime/plan.mjs +4 -4
- package/dist/src/runtime/topology.d.mts +1 -1
- package/dist/src/runtime/topology.mjs +2 -2
- package/dist/src/runtime/workload_queue.d.mts +39 -0
- package/dist/src/runtime/workload_queue.mjs +211 -0
- package/dist/src/runtime/workload_queue_location.d.mts +1 -0
- package/dist/src/runtime/workload_queue_location.mjs +43 -0
- package/dist/src/runtime/workload_queue_store.d.mts +27 -0
- package/dist/src/runtime/workload_queue_store.mjs +182 -0
- package/dist/src/shared/cancellation.mjs +2 -0
- package/dist/src/shared/held_path_ancestry.d.mts +7 -0
- package/dist/src/shared/held_path_ancestry.mjs +110 -0
- package/dist/src/shared/operation_guard.mjs +1 -0
- package/dist/src/targets/migration_impact_runtime.d.mts +22 -0
- package/dist/src/targets/migration_impact_runtime.mjs +367 -0
- package/docs/README.md +20 -7
- package/docs/adapters.md +10 -0
- package/docs/architecture.md +84 -1
- package/docs/commands.md +189 -34
- package/docs/configuration.md +56 -6
- package/docs/getting-started.md +1 -1
- package/docs/index.md +1 -0
- package/docs/releasing.md +15 -9
- package/docs/roadmap.md +27 -6
- package/docs/runtime-lifecycle.md +10 -0
- package/docs/stability.md +4 -4
- package/docs/updating.md +73 -0
- package/docs/vscode.md +23 -17
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,46 @@ a Changelog, and published versions follow Semantic Versioning.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2026-10-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Optional free local-user workload admission for participating `start`, `reset`,
|
|
13
|
+
`run` and `migrate` lifecycle phases. Bounded waits, cancellation and explicit
|
|
14
|
+
dead-holder metadata recovery do not interrupt another project or expire live
|
|
15
|
+
owners. Read-only `workloads` reports participation, not available host/VM memory.
|
|
16
|
+
Existing configurations and editor field metadata remain unchanged when omitted.
|
|
17
|
+
Application proofs, interactive sessions and nonparticipating tools are outside
|
|
18
|
+
this first lifecycle-only queue; this is not a distributed or hard-memory scheduler.
|
|
19
|
+
|
|
20
|
+
## [1.2.0] - 2026-10-24
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- An optional `migrationImpact: 1` contract: offline `inspect impact` and explicitly
|
|
25
|
+
confirmed `measure impact` for one selected, already-verified disposable local
|
|
26
|
+
sandbox. Bounded read-only snapshot aggregates cover narrow supported row,
|
|
27
|
+
populated/NULL, duplicate and foreign-key orphan counts; unsupported or incomplete
|
|
28
|
+
evidence stays unknown. No pending migration is executed, no runtime is started
|
|
29
|
+
or repaired, and no SQL, object names or row contents are returned. Counts are
|
|
30
|
+
not sequential simulation, production prediction or a safety verdict. Existing
|
|
31
|
+
project locking/cancellation is reused without adding a rehearsal gate or CLI fee.
|
|
32
|
+
- An optional versioned `inspect insights` contract for bounded, read-only static
|
|
33
|
+
migration warnings. Findings bind to exact pending file bytes and the complete
|
|
34
|
+
existing candidate digest, with fixed explanations and lexical locations.
|
|
35
|
+
Unsupported, malformed or truncated analysis remains unknown; no detected
|
|
36
|
+
warning is never SQL validity, database verification or a safety verdict.
|
|
37
|
+
- Project-local input bounds, unchanged leaf/directory identity checks and shared
|
|
38
|
+
input hash binding. Inspection neither executes SQL nor starts an engine or
|
|
39
|
+
contacts a database. Existing migration-plan v1 and failure diagnosis remain
|
|
40
|
+
separate, unchanged contracts.
|
|
41
|
+
|
|
42
|
+
### Documentation
|
|
43
|
+
|
|
44
|
+
- Clarify reviewed PostgreSQL/application/full service dependencies and resource
|
|
45
|
+
measurement limits without adding service exclusions or a new acceptance gate.
|
|
46
|
+
- Distinguish project runtime hooks from native migration-tool history support.
|
|
47
|
+
|
|
8
48
|
## [1.1.0] - 2026-10-21
|
|
9
49
|
|
|
10
50
|
### Added
|
|
@@ -840,7 +880,9 @@ not unattended cleanup, and container age is not proof of user inactivity.
|
|
|
840
880
|
publication uses short-lived trusted OIDC, and every release tag must already exist on
|
|
841
881
|
protected `main`.
|
|
842
882
|
|
|
843
|
-
[Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.
|
|
883
|
+
[Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.3.0...HEAD
|
|
884
|
+
[1.3.0]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.2.0...v1.3.0
|
|
885
|
+
[1.2.0]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.1.0...v1.2.0
|
|
844
886
|
[1.1.0]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.2...v1.1.0
|
|
845
887
|
[1.0.2]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.1...v1.0.2
|
|
846
888
|
[1.0.1]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.0...v1.0.1
|
package/COMPATIBILITY.md
CHANGED
|
@@ -38,6 +38,46 @@ through the project's existing version manager or use the helper's explicit exec
|
|
|
38
38
|
path; see [OS setup](installers/README.md). Helpers never install Node or switch a
|
|
39
39
|
project's runtime automatically.
|
|
40
40
|
|
|
41
|
+
Core 1.2.0 introduced optional capability
|
|
42
|
+
`rehearsalContracts.migrationInsights: 1` and `inspect insights --json`. It preserves
|
|
43
|
+
existing migration-plan v1, baseline formats, digest meaning and failure diagnosis.
|
|
44
|
+
Consumers must check the capability and exact plan/file bindings; absent or
|
|
45
|
+
unavailable static analysis is not safety evidence. Package publication does not
|
|
46
|
+
establish completed editor, accessibility or genuine-provider acceptance.
|
|
47
|
+
|
|
48
|
+
Core 1.2.0 also introduced optional `rehearsalContracts.migrationImpact: 1` with
|
|
49
|
+
`inspect impact` and `measure impact`. Consumers must explicitly select a configured
|
|
50
|
+
local target and bind measurement consent to the exact whole-plan digest. Preview
|
|
51
|
+
does not contact a database; measurement reads only the current verified sandbox,
|
|
52
|
+
without applying migrations, starting/repairing a runtime or changing database data.
|
|
53
|
+
Aggregate execution requires PostgreSQL 17 or newer with server-side transaction
|
|
54
|
+
deadlines and narrowly supported ordinary-table/built-in semantics; older servers
|
|
55
|
+
remain unknown, with no automatic database upgrade.
|
|
56
|
+
Supabase measurement uses the ownership-proven local container's built-in
|
|
57
|
+
`supabase_admin` socket role so the fixed disk bound and catalog lock can be enforced.
|
|
58
|
+
It does not promote `postgres`, change grants, load credentials or connect remotely.
|
|
59
|
+
Read-only catalog preflight is separate from the count snapshot. Bounded strong
|
|
60
|
+
relation locks precede that snapshot and temporarily block writers and metadata
|
|
61
|
+
changes in the selected disposable sandbox; this is not a nonblocking query promise.
|
|
62
|
+
Counts are decimal strings or `null` with an explicit unknown reason, never a safety
|
|
63
|
+
verdict, sequential simulation or production prediction. Partial/unknown measurement
|
|
64
|
+
uses `not_ready` with the existing not-ready exit `1`; normal rehearsal gates and existing JSON contracts
|
|
65
|
+
are unchanged. This capability does not claim a new database family or additional
|
|
66
|
+
platform/native acceptance.
|
|
67
|
+
Older strict editor activity consumers can reject the new `measure impact` operation
|
|
68
|
+
kind, including a retained completed receipt. Pair this capability with an editor
|
|
69
|
+
consumer explicitly supporting that kind; the CLI does not silently rename the
|
|
70
|
+
operation, relax unknown-kind validation or update an active editor installation.
|
|
71
|
+
|
|
72
|
+
Core 1.3.0 introduces optional `rehearsalContracts.workloadQueue: 1`: local
|
|
73
|
+
`workloads` inspection, exact-confirmed dead-holder metadata recovery and opt-in
|
|
74
|
+
`resources.workloadQueue` lifecycle admission. Omitted configuration preserves prior
|
|
75
|
+
behavior. Participating clients cooperate within the same OS user, boot and process
|
|
76
|
+
namespace; older clients and other tools do not participate. This does not measure
|
|
77
|
+
available memory, govern standing runtimes or promise a hard machine-capacity budget.
|
|
78
|
+
The existing editor field-metadata contract is unchanged. See
|
|
79
|
+
[resource review policies](docs/configuration.md#resource-review-policies).
|
|
80
|
+
|
|
41
81
|
## Operating systems and execution location
|
|
42
82
|
|
|
43
83
|
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.
|
|
50
|
+
[npm](https://www.npmjs.com/package/@rehearsal-db/core). This guide targets **1.3.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.
|
|
@@ -198,6 +198,7 @@ Start here:
|
|
|
198
198
|
- [VS Code extension](docs/vscode.md) — use the project-aware visual workbench
|
|
199
199
|
- [Safe hands-on tutorial](docs/tutorial.md) — try the full flow in a disposable project
|
|
200
200
|
- [Troubleshooting](docs/troubleshooting.md) — fix common setup problems
|
|
201
|
+
- [Updates](docs/updating.md) — update the CLI or extension without resetting data
|
|
201
202
|
- [Next steps](docs/roadmap.md) — see the current product sequence
|
|
202
203
|
|
|
203
204
|
Reference:
|
|
@@ -214,9 +215,12 @@ Reference:
|
|
|
214
215
|
- [Standalone workflow and security gates](docs/standalone-workflow.md)
|
|
215
216
|
- [Runtime and local identity policies](docs/runtime-policies.md)
|
|
216
217
|
- [Glossary](docs/glossary.md)
|
|
217
|
-
- [Repository architecture](docs/architecture.md)
|
|
218
218
|
- [Stability and the first stable release](docs/stability.md)
|
|
219
|
-
|
|
219
|
+
|
|
220
|
+
For maintainers developing and publishing Rehearsal:
|
|
221
|
+
|
|
222
|
+
- [Repository architecture](docs/architecture.md)
|
|
223
|
+
- [Maintainer release process](docs/releasing.md)
|
|
220
224
|
|
|
221
225
|
## Getting support
|
|
222
226
|
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rehearsal-db/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
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": {
|
|
@@ -26,7 +26,10 @@
|
|
|
26
26
|
"projectDiscovery": 1,
|
|
27
27
|
"resourceInventory": 1,
|
|
28
28
|
"resourceManager": 1,
|
|
29
|
+
"workloadQueue": 1,
|
|
29
30
|
"migrationReview": 1,
|
|
31
|
+
"migrationInsights": 1,
|
|
32
|
+
"migrationImpact": 1,
|
|
30
33
|
"configurationReview": 1,
|
|
31
34
|
"baselineShape": 1
|
|
32
35
|
},
|
|
@@ -20,6 +20,7 @@ export interface RehearsalCliFlags {
|
|
|
20
20
|
imagesOnly: boolean;
|
|
21
21
|
cleanupConfirmation: string | undefined;
|
|
22
22
|
stopConfirmation: string | undefined;
|
|
23
|
+
workloadConfirmation: string | undefined;
|
|
23
24
|
proposalPath: string | undefined;
|
|
24
25
|
sourceAccessConfirmation: string | undefined;
|
|
25
26
|
sourceRetirementConfirmation: string | undefined;
|
|
@@ -21,6 +21,7 @@ export const parseArguments = (arguments_) => {
|
|
|
21
21
|
imagesOnly: false,
|
|
22
22
|
cleanupConfirmation: undefined,
|
|
23
23
|
stopConfirmation: undefined,
|
|
24
|
+
workloadConfirmation: undefined,
|
|
24
25
|
proposalPath: undefined,
|
|
25
26
|
sourceAccessConfirmation: undefined,
|
|
26
27
|
sourceRetirementConfirmation: undefined,
|
|
@@ -56,6 +57,8 @@ export const parseArguments = (arguments_) => {
|
|
|
56
57
|
flags.cleanupConfirmation = argument.slice("--confirm-cleanup=".length);
|
|
57
58
|
else if (argument.startsWith("--confirm-stop="))
|
|
58
59
|
flags.stopConfirmation = argument.slice("--confirm-stop=".length);
|
|
60
|
+
else if (argument.startsWith("--confirm-workloads="))
|
|
61
|
+
flags.workloadConfirmation = argument.slice("--confirm-workloads=".length);
|
|
59
62
|
else if (argument.startsWith("--proposal="))
|
|
60
63
|
flags.proposalPath = argument.slice("--proposal=".length);
|
|
61
64
|
else if (argument.startsWith("--confirm-source-access="))
|
|
@@ -114,6 +117,7 @@ export const resetGuidedFlags = (flags) => {
|
|
|
114
117
|
imagesOnly: false,
|
|
115
118
|
cleanupConfirmation: undefined,
|
|
116
119
|
stopConfirmation: undefined,
|
|
120
|
+
workloadConfirmation: undefined,
|
|
117
121
|
proposalPath: undefined,
|
|
118
122
|
sourceAccessConfirmation: undefined,
|
|
119
123
|
sourceRetirementConfirmation: undefined,
|
|
@@ -150,6 +154,7 @@ export const commandMutatesProject = ({ command, flags, guided, }) => {
|
|
|
150
154
|
"open",
|
|
151
155
|
"start",
|
|
152
156
|
"migrate",
|
|
157
|
+
"measure impact",
|
|
153
158
|
"reset",
|
|
154
159
|
"stop",
|
|
155
160
|
"discard",
|
|
@@ -27,6 +27,8 @@ Commands:
|
|
|
27
27
|
activity Report idle, busy, stale, or invalid operation state
|
|
28
28
|
environments List owned runtimes and Docker capacity without starting it
|
|
29
29
|
resources Browse individual images, volumes, and other local resources
|
|
30
|
+
workloads Inspect participating local lifecycle queue metadata
|
|
31
|
+
workloads recover --confirm-workloads= Recover reviewed dead-holder metadata only
|
|
30
32
|
explain Show the immutable execution plan
|
|
31
33
|
run --dry-run Alias the exact explain plan without mutations
|
|
32
34
|
run --confirm-candidates= Execute reset, migration, and verification locally
|
|
@@ -35,6 +37,9 @@ Commands:
|
|
|
35
37
|
inspect baseline Show verified baseline provenance
|
|
36
38
|
inspect migrations Classify represented, applied, and candidate migrations
|
|
37
39
|
inspect plan Review ordered migrations, required dependencies, and warnings
|
|
40
|
+
inspect insights Review bounded static SQL warnings; never a safety verdict
|
|
41
|
+
inspect impact Preview aggregate measurements for one explicit --target
|
|
42
|
+
measure impact Measure reviewed aggregates on that running local sandbox
|
|
38
43
|
start Start an existing verified local runtime
|
|
39
44
|
migrate --confirm-candidates= Apply the exact candidate suffix without resetting
|
|
40
45
|
reset Restore and verify the immutable local baseline
|
|
@@ -48,6 +53,7 @@ Commands:
|
|
|
48
53
|
|
|
49
54
|
Options: --help, -h --version, -V --json --verbose --debug --plain
|
|
50
55
|
--config=<path> --target=<provider for setup; configured name for runtime>
|
|
56
|
+
--confirm-candidates=<whole-plan digest for run, migrate, or measure impact>
|
|
51
57
|
--confirm-source-access=<digest> --confirm-source-retirement=<digest>
|
|
52
58
|
--confirm-refresh=<digest>`;
|
|
53
59
|
export const createBasicCommandHandlers = () => [
|
|
@@ -5,6 +5,7 @@ import { createCommandRegistry } from "./command_registry.mjs";
|
|
|
5
5
|
import { createIdentityCommandHandlers } from "./identity_command_handlers.mjs";
|
|
6
6
|
import { createInspectionCommandHandlers } from "./inspection_command_handlers.mjs";
|
|
7
7
|
import { createEditorCommandHandlers } from "./editor_command_handlers.mjs";
|
|
8
|
+
import { createWorkloadCommandHandlers } from "./workload_command_handlers.mjs";
|
|
8
9
|
import { createProjectCommandHandlers } from "./project_command_handlers.mjs";
|
|
9
10
|
import { createRuntimeActionHandlers } from "./runtime_action_handlers.mjs";
|
|
10
11
|
import { createSourceCommandHandlers } from "./source_command_handlers.mjs";
|
|
@@ -23,6 +24,7 @@ export const createCliCommandRegistry = ({ projectRoot, projectCommands, runtime
|
|
|
23
24
|
startRuntimeApplication: runtimeCommands.startRuntimeApplication,
|
|
24
25
|
}),
|
|
25
26
|
...createInspectionCommandHandlers(),
|
|
27
|
+
...createWorkloadCommandHandlers(),
|
|
26
28
|
...createEditorCommandHandlers(),
|
|
27
29
|
...createApplicationCommandHandlers({
|
|
28
30
|
openRuntimeApplication: runtimeCommands.openRuntimeApplication,
|
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
/** Versioned, read-only contracts for editor review; never a second runtime engine. */
|
|
2
2
|
import { inspectMigrationReviewPlan } from "../runtime/migration_plan.mjs";
|
|
3
|
+
import { inspectMigrationInsights, renderMigrationInsights, } from "../runtime/migration_insights.mjs";
|
|
4
|
+
import { inspectMigrationImpact, measureMigrationImpact, renderMigrationImpact, } from "../runtime/migration_impact.mjs";
|
|
3
5
|
import { exactCommands, renderedCommand } from "./command_contract.mjs";
|
|
4
6
|
import { applyStopStalePlan, inspectStopStalePlan, stopReviewedStaleTarget, } from "../runtime/stale_environment_plan.mjs";
|
|
5
7
|
import { inspectConfigurationMetadata, reviewConfigurationProposal, } from "../project/configuration_review.mjs";
|
|
6
8
|
export const createEditorCommandHandlers = () => [
|
|
9
|
+
exactCommands("migration-impact-preview", ["inspect impact"], async ({ planOptions, flags }) => {
|
|
10
|
+
const data = await inspectMigrationImpact(planOptions, flags.target);
|
|
11
|
+
return renderedCommand({ data, render: renderMigrationImpact });
|
|
12
|
+
}),
|
|
13
|
+
exactCommands("migration-impact-measurement", ["measure impact"], async ({ planOptions, flags }) => {
|
|
14
|
+
const data = await measureMigrationImpact({
|
|
15
|
+
options: planOptions,
|
|
16
|
+
targetName: flags.target,
|
|
17
|
+
confirmation: flags.confirmation,
|
|
18
|
+
});
|
|
19
|
+
const incomplete = ["partial", "unknown"].includes(data.measurementStatus);
|
|
20
|
+
return renderedCommand({
|
|
21
|
+
data,
|
|
22
|
+
render: renderMigrationImpact,
|
|
23
|
+
status: incomplete ? "not_ready" : "success",
|
|
24
|
+
...(incomplete ? { exitCode: 1 } : {}),
|
|
25
|
+
});
|
|
26
|
+
}),
|
|
27
|
+
exactCommands("migration-insights", ["inspect insights"], async ({ planOptions }) => {
|
|
28
|
+
const data = await inspectMigrationInsights(planOptions);
|
|
29
|
+
return renderedCommand({ data, render: renderMigrationInsights });
|
|
30
|
+
}),
|
|
7
31
|
exactCommands("configuration", ["configuration", "configuration review"], async ({ command, planOptions, flags }) => {
|
|
8
32
|
const data = command === "configuration review"
|
|
9
33
|
? await reviewConfigurationProposal(planOptions, flags.proposalPath ??
|
|
@@ -80,6 +80,10 @@ export declare const createProjectCommands: ({ projectRoot, runManager, }: {
|
|
|
80
80
|
retainBaselineGenerations: number;
|
|
81
81
|
}>;
|
|
82
82
|
resources: Readonly<{
|
|
83
|
+
workloadQueue?: {
|
|
84
|
+
enabled: boolean;
|
|
85
|
+
waitTimeoutSeconds: number;
|
|
86
|
+
};
|
|
83
87
|
staleAfterSeconds: number | null;
|
|
84
88
|
retainedEnvironmentCount: number;
|
|
85
89
|
maximumBytes: number | null;
|
|
@@ -232,6 +236,10 @@ export declare const createProjectCommands: ({ projectRoot, runManager, }: {
|
|
|
232
236
|
retainBaselineGenerations: number;
|
|
233
237
|
}>;
|
|
234
238
|
resources: Readonly<{
|
|
239
|
+
workloadQueue?: {
|
|
240
|
+
enabled: boolean;
|
|
241
|
+
waitTimeoutSeconds: number;
|
|
242
|
+
};
|
|
235
243
|
staleAfterSeconds: number | null;
|
|
236
244
|
retainedEnvironmentCount: number;
|
|
237
245
|
maximumBytes: number | null;
|
|
@@ -3,28 +3,11 @@ import type { RehearsalConfigPathOptions } from "../project/configuration.mjs";
|
|
|
3
3
|
export { buildRuntimeStackPlan, runRuntimeStackDoctor, topologyCandidateSummary, } from "./runtime_planning.mjs";
|
|
4
4
|
import { loadRuntimeTopology } from "../runtime/topology.mjs";
|
|
5
5
|
import type { InstalledPackageFingerprint } from "../shared/operation_guard.mjs";
|
|
6
|
-
import type
|
|
6
|
+
import { type ManagerReceipt, type RuntimeAction } from "./runtime_stack_runner.mjs";
|
|
7
7
|
import type { RehearsalCliFlags } from "./arguments.mjs";
|
|
8
8
|
import type { RehearsalErrorCategory } from "../shared/diagnostics.mjs";
|
|
9
9
|
import type { CliSessionState } from "./command_contract.mjs";
|
|
10
10
|
type RuntimeTopology = Awaited<ReturnType<typeof loadRuntimeTopology>>;
|
|
11
|
-
type RuntimeAction = "run" | "start" | "migrate" | "reset" | "status" | "stop" | "discard" | "verify";
|
|
12
|
-
interface ManagerReceipt {
|
|
13
|
-
readonly action: RuntimeAction;
|
|
14
|
-
readonly target: string;
|
|
15
|
-
readonly durationMs: number;
|
|
16
|
-
readonly output: string;
|
|
17
|
-
readonly runtimeStatus?: RuntimeStatusSnapshot;
|
|
18
|
-
}
|
|
19
|
-
interface RuntimeStackResult {
|
|
20
|
-
readonly topology: RuntimeTopology;
|
|
21
|
-
readonly runtime: ManagerReceipt | Readonly<{
|
|
22
|
-
action: RuntimeAction;
|
|
23
|
-
durationMs: number;
|
|
24
|
-
output: string;
|
|
25
|
-
targets: readonly ManagerReceipt[];
|
|
26
|
-
}>;
|
|
27
|
-
}
|
|
28
11
|
export declare const classifyManagerFailure: ({ action, output, inferredCategory, }: {
|
|
29
12
|
action: RuntimeAction;
|
|
30
13
|
output: string;
|
|
@@ -165,5 +148,5 @@ export declare const createRuntimeCommands: ({ packageRoot, projectRoot, manager
|
|
|
165
148
|
flags: RehearsalCliFlags;
|
|
166
149
|
planOptions: RehearsalConfigPathOptions;
|
|
167
150
|
session: CliSessionState;
|
|
168
|
-
}) => Promise<RuntimeStackResult>;
|
|
151
|
+
}) => Promise<import("./runtime_stack_runner.mjs").RuntimeStackResult>;
|
|
169
152
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/** Orchestrate isolated database runtimes and project-owned application proofs. */
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { randomUUID } from "node:crypto";
|
|
4
3
|
import { access } from "node:fs/promises";
|
|
5
4
|
import { join } from "node:path";
|
|
6
5
|
import { performance } from "node:perf_hooks";
|
|
@@ -18,15 +17,11 @@ import { createCleanProcessEnvironment } from "../shared/process_environment.mjs
|
|
|
18
17
|
import { getOwnedOperationProgressId, trackOperationStage, } from "../shared/operation_progress.mjs";
|
|
19
18
|
import { buildRuntimeManagerArguments } from "../targets/target.mjs";
|
|
20
19
|
import { parseRuntimeStatusSnapshot, renderRuntimeStatusSnapshot, } from "../runtime/status.mjs";
|
|
21
|
-
import { applyRuntimePersistence
|
|
22
|
-
import {
|
|
20
|
+
import { applyRuntimePersistence } from "../runtime/persistence.mjs";
|
|
21
|
+
import { createRuntimeStackRunner, } from "./runtime_stack_runner.mjs";
|
|
23
22
|
import { summarizeRuntimeManagerFailure, runtimePortFailureGuidance, } from "./runtime_failure_summary.mjs";
|
|
24
23
|
import { readManagerMigrationFailure } from "../runtime/migration_failure.mjs";
|
|
25
|
-
import { releaseOwnedColimaWhenIdle } from "../runtime/container_engine_ownership.mjs";
|
|
26
|
-
import { inspectRehearsalResources } from "../runtime/resource_inventory.mjs";
|
|
27
|
-
import { assertLocalDockerContext } from "../runtime/local_docker_context.mjs";
|
|
28
24
|
import { runtimeConfigurationSha256 } from "../runtime/configuration_binding.mjs";
|
|
29
|
-
import { assertResourcePolicyAllowsStartup, evaluateResourcePolicy, } from "../runtime/resource_policy.mjs";
|
|
30
25
|
import { formatDuration, isHumanTerminal, promptForConfirmation, terminalStyle, useStyledPrompts, } from "./terminal.mjs";
|
|
31
26
|
const parseCommand = (source) => {
|
|
32
27
|
const values = [];
|
|
@@ -340,89 +335,7 @@ export const createRuntimeCommands = ({ packageRoot, projectRoot, managerPath, g
|
|
|
340
335
|
: "runtime-lifecycle",
|
|
341
336
|
target: options.targetName ?? "primary",
|
|
342
337
|
}, () => executeManager(options));
|
|
343
|
-
const runRuntimeStack =
|
|
344
|
-
const topology = session.runtimeTopology ??
|
|
345
|
-
(await loadRuntimeTopology(planOptions));
|
|
346
|
-
const reverse = ["stop", "discard"].includes(command);
|
|
347
|
-
if (["start", "run", "reset"].includes(command) &&
|
|
348
|
-
topology.primary.config.resources.maximumBytes !== null) {
|
|
349
|
-
assertResourcePolicyAllowsStartup(evaluateResourcePolicy({
|
|
350
|
-
inventory: await inspectRehearsalResources(planOptions),
|
|
351
|
-
policy: topology.primary.config.resources,
|
|
352
|
-
}));
|
|
353
|
-
}
|
|
354
|
-
const selectedTargets = selectRuntimeTargets(topology.targets, flags.target, command);
|
|
355
|
-
const targets = reverse ? [...selectedTargets].reverse() : selectedTargets;
|
|
356
|
-
const engine = command === "status"
|
|
357
|
-
? null
|
|
358
|
-
: assertLocalDockerContext({ cwd: topology.projectRoot });
|
|
359
|
-
const engineBinding = engine
|
|
360
|
-
? { dockerContext: engine.context, dockerIdentity: engine.identity }
|
|
361
|
-
: {};
|
|
362
|
-
const operationId = randomUUID();
|
|
363
|
-
const results = await executeRuntimeTargetStack({
|
|
364
|
-
targets,
|
|
365
|
-
cleanupOnFailure: command === "run"
|
|
366
|
-
? shouldStopRuntime(topology.primary.config.lifecycle.run)
|
|
367
|
-
: command === "start" &&
|
|
368
|
-
shouldStopRuntime(topology.primary.config.lifecycle.open),
|
|
369
|
-
execute: async (target) => {
|
|
370
|
-
const confirmation = session.targetConfirmations?.get(target.configPath);
|
|
371
|
-
return runManager({
|
|
372
|
-
action: command,
|
|
373
|
-
flags,
|
|
374
|
-
configPath: target.configPath,
|
|
375
|
-
...(confirmation === undefined ? {} : { confirmation }),
|
|
376
|
-
...(topology.targets.length > 1 ? { targetName: target.name } : {}),
|
|
377
|
-
operationId,
|
|
378
|
-
...engineBinding,
|
|
379
|
-
configSha256: runtimeConfigurationSha256(target.config),
|
|
380
|
-
});
|
|
381
|
-
},
|
|
382
|
-
stop: (target) => runManager({
|
|
383
|
-
action: "stop",
|
|
384
|
-
flags,
|
|
385
|
-
configPath: target.configPath,
|
|
386
|
-
targetName: target.name,
|
|
387
|
-
operationId,
|
|
388
|
-
...engineBinding,
|
|
389
|
-
configSha256: runtimeConfigurationSha256(target.config),
|
|
390
|
-
}),
|
|
391
|
-
});
|
|
392
|
-
const release = ["stop", "discard"].includes(command)
|
|
393
|
-
? releaseOwnedColimaWhenIdle({
|
|
394
|
-
projectRoot: topology.projectRoot,
|
|
395
|
-
targets: topology.targets,
|
|
396
|
-
})
|
|
397
|
-
: null;
|
|
398
|
-
const withRelease = (receipt) => release
|
|
399
|
-
? {
|
|
400
|
-
...receipt,
|
|
401
|
-
output: [receipt.output, release.message]
|
|
402
|
-
.filter(Boolean)
|
|
403
|
-
.join("\n"),
|
|
404
|
-
}
|
|
405
|
-
: receipt;
|
|
406
|
-
if (results.length === 1)
|
|
407
|
-
return { topology, runtime: withRelease(results[0]) };
|
|
408
|
-
return {
|
|
409
|
-
topology,
|
|
410
|
-
runtime: {
|
|
411
|
-
action: command,
|
|
412
|
-
durationMs: Math.round(results.reduce((total, result) => total + result.durationMs, 0) *
|
|
413
|
-
100) / 100,
|
|
414
|
-
output: [
|
|
415
|
-
results
|
|
416
|
-
.map((result) => `[${result.target}]\n${result.output}`)
|
|
417
|
-
.join("\n\n"),
|
|
418
|
-
release?.message,
|
|
419
|
-
]
|
|
420
|
-
.filter(Boolean)
|
|
421
|
-
.join("\n\n"),
|
|
422
|
-
targets: results,
|
|
423
|
-
},
|
|
424
|
-
};
|
|
425
|
-
};
|
|
338
|
+
const runRuntimeStack = createRuntimeStackRunner(runManager);
|
|
426
339
|
const runApplicationProof = async (planOptions, options = {}) => {
|
|
427
340
|
const { config, projectRoot: configuredRoot } = await loadRehearsalConfig(planOptions);
|
|
428
341
|
return trackOperationStage({ phase: "application-proof", target: options.targetName ?? "primary" }, () => runProjectCommand({
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { RehearsalConfigPathOptions } from "../project/configuration.mjs";
|
|
2
|
+
import { loadRuntimeTopology } from "../runtime/topology.mjs";
|
|
3
|
+
import type { RuntimeStatusSnapshot } from "../runtime/status.mjs";
|
|
4
|
+
import type { RehearsalCliFlags } from "./arguments.mjs";
|
|
5
|
+
import type { CliSessionState } from "./command_contract.mjs";
|
|
6
|
+
type RuntimeTopology = Awaited<ReturnType<typeof loadRuntimeTopology>>;
|
|
7
|
+
export type RuntimeAction = "run" | "start" | "migrate" | "reset" | "status" | "stop" | "discard" | "verify";
|
|
8
|
+
export interface ManagerReceipt {
|
|
9
|
+
readonly action: RuntimeAction;
|
|
10
|
+
readonly target: string;
|
|
11
|
+
readonly durationMs: number;
|
|
12
|
+
readonly output: string;
|
|
13
|
+
readonly runtimeStatus?: RuntimeStatusSnapshot;
|
|
14
|
+
}
|
|
15
|
+
export interface RuntimeStackResult {
|
|
16
|
+
readonly topology: RuntimeTopology;
|
|
17
|
+
readonly runtime: ManagerReceipt | Readonly<{
|
|
18
|
+
action: RuntimeAction;
|
|
19
|
+
durationMs: number;
|
|
20
|
+
output: string;
|
|
21
|
+
targets: readonly ManagerReceipt[];
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
interface ManagerInvocation {
|
|
25
|
+
action: RuntimeAction;
|
|
26
|
+
flags: RehearsalCliFlags;
|
|
27
|
+
configPath?: string;
|
|
28
|
+
confirmation?: string;
|
|
29
|
+
targetName?: string;
|
|
30
|
+
operationId?: string;
|
|
31
|
+
dockerContext?: string;
|
|
32
|
+
dockerIdentity?: string;
|
|
33
|
+
configSha256?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare const createRuntimeStackRunner: (runManager: (options: ManagerInvocation) => Promise<ManagerReceipt>) => ({ command, flags, planOptions, session, }: {
|
|
36
|
+
command: RuntimeAction;
|
|
37
|
+
flags: RehearsalCliFlags;
|
|
38
|
+
planOptions: RehearsalConfigPathOptions;
|
|
39
|
+
session: CliSessionState;
|
|
40
|
+
}) => Promise<RuntimeStackResult>;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** Coordinate configured runtime stacks separately from application sessions/proofs. */
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { loadRuntimeTopology, selectRuntimeTargets, } from "../runtime/topology.mjs";
|
|
4
|
+
import { shouldStopRuntime } from "../runtime/persistence.mjs";
|
|
5
|
+
import { releaseOwnedColimaWhenIdle } from "../runtime/container_engine_ownership.mjs";
|
|
6
|
+
import { inspectRehearsalResources } from "../runtime/resource_inventory.mjs";
|
|
7
|
+
import { assertLocalDockerContext } from "../runtime/local_docker_context.mjs";
|
|
8
|
+
import { runtimeConfigurationSha256 } from "../runtime/configuration_binding.mjs";
|
|
9
|
+
import { assertResourcePolicyAllowsStartup, evaluateResourcePolicy, } from "../runtime/resource_policy.mjs";
|
|
10
|
+
import { executeRuntimeTargetStack } from "./runtime_stack_execution.mjs";
|
|
11
|
+
import { withRuntimeWorkloadAdmission } from "./runtime_workload_admission.mjs";
|
|
12
|
+
export const createRuntimeStackRunner = (runManager) => async ({ command, flags, planOptions, session, }) => {
|
|
13
|
+
const topology = session.runtimeTopology ??
|
|
14
|
+
(await loadRuntimeTopology(planOptions));
|
|
15
|
+
const executeStack = async () => {
|
|
16
|
+
const reverse = ["stop", "discard"].includes(command);
|
|
17
|
+
if (["start", "run", "reset"].includes(command) &&
|
|
18
|
+
topology.primary.config.resources.maximumBytes !== null) {
|
|
19
|
+
assertResourcePolicyAllowsStartup(evaluateResourcePolicy({
|
|
20
|
+
inventory: await inspectRehearsalResources(planOptions),
|
|
21
|
+
policy: topology.primary.config.resources,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
const selectedTargets = selectRuntimeTargets(topology.targets, flags.target, command);
|
|
25
|
+
const targets = reverse
|
|
26
|
+
? [...selectedTargets].reverse()
|
|
27
|
+
: selectedTargets;
|
|
28
|
+
const engine = command === "status"
|
|
29
|
+
? null
|
|
30
|
+
: assertLocalDockerContext({ cwd: topology.projectRoot });
|
|
31
|
+
const engineBinding = engine
|
|
32
|
+
? { dockerContext: engine.context, dockerIdentity: engine.identity }
|
|
33
|
+
: {};
|
|
34
|
+
const operationId = randomUUID();
|
|
35
|
+
const results = await executeRuntimeTargetStack({
|
|
36
|
+
targets,
|
|
37
|
+
cleanupOnFailure: command === "run"
|
|
38
|
+
? shouldStopRuntime(topology.primary.config.lifecycle.run)
|
|
39
|
+
: command === "start" &&
|
|
40
|
+
shouldStopRuntime(topology.primary.config.lifecycle.open),
|
|
41
|
+
execute: async (target) => {
|
|
42
|
+
const confirmation = session.targetConfirmations?.get(target.configPath);
|
|
43
|
+
return runManager({
|
|
44
|
+
action: command,
|
|
45
|
+
flags,
|
|
46
|
+
configPath: target.configPath,
|
|
47
|
+
...(confirmation === undefined ? {} : { confirmation }),
|
|
48
|
+
...(topology.targets.length > 1 ? { targetName: target.name } : {}),
|
|
49
|
+
operationId,
|
|
50
|
+
...engineBinding,
|
|
51
|
+
configSha256: runtimeConfigurationSha256(target.config),
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
stop: (target) => runManager({
|
|
55
|
+
action: "stop",
|
|
56
|
+
flags,
|
|
57
|
+
configPath: target.configPath,
|
|
58
|
+
targetName: target.name,
|
|
59
|
+
operationId,
|
|
60
|
+
...engineBinding,
|
|
61
|
+
configSha256: runtimeConfigurationSha256(target.config),
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
const release = ["stop", "discard"].includes(command)
|
|
65
|
+
? releaseOwnedColimaWhenIdle({
|
|
66
|
+
projectRoot: topology.projectRoot,
|
|
67
|
+
targets: topology.targets,
|
|
68
|
+
})
|
|
69
|
+
: null;
|
|
70
|
+
const withRelease = (receipt) => release
|
|
71
|
+
? {
|
|
72
|
+
...receipt,
|
|
73
|
+
output: [receipt.output, release.message]
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.join("\n"),
|
|
76
|
+
}
|
|
77
|
+
: receipt;
|
|
78
|
+
if (results.length === 1)
|
|
79
|
+
return { topology, runtime: withRelease(results[0]) };
|
|
80
|
+
return {
|
|
81
|
+
topology,
|
|
82
|
+
runtime: {
|
|
83
|
+
action: command,
|
|
84
|
+
durationMs: Math.round(results.reduce((total, result) => total + result.durationMs, 0) *
|
|
85
|
+
100) / 100,
|
|
86
|
+
output: [
|
|
87
|
+
results
|
|
88
|
+
.map((result) => `[${result.target}]\n${result.output}`)
|
|
89
|
+
.join("\n\n"),
|
|
90
|
+
release?.message,
|
|
91
|
+
]
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
.join("\n\n"),
|
|
94
|
+
targets: results,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
return withRuntimeWorkloadAdmission({
|
|
99
|
+
policy: topology.primary.config.resources.workloadQueue,
|
|
100
|
+
command,
|
|
101
|
+
json: flags.json,
|
|
102
|
+
execute: executeStack,
|
|
103
|
+
});
|
|
104
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Bind configured lifecycle-only admission without queuing proofs or interactive sessions. */
|
|
2
|
+
import type { RehearsalResourcePolicy } from "../project/configuration_contract.mjs";
|
|
3
|
+
export declare const withRuntimeWorkloadAdmission: <Result>({ policy, command, json, execute, }: {
|
|
4
|
+
policy: RehearsalResourcePolicy["workloadQueue"];
|
|
5
|
+
command: string;
|
|
6
|
+
json: boolean;
|
|
7
|
+
execute: () => Promise<Result>;
|
|
8
|
+
}) => Promise<Result>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getOperationCancellationSignal } from "../shared/cancellation.mjs";
|
|
2
|
+
import { withWorkloadAdmission } from "../runtime/workload_queue.mjs";
|
|
3
|
+
export const withRuntimeWorkloadAdmission = async ({ policy, command, json, execute, }) => {
|
|
4
|
+
if (!policy?.enabled ||
|
|
5
|
+
(command !== "start" &&
|
|
6
|
+
command !== "run" &&
|
|
7
|
+
command !== "reset" &&
|
|
8
|
+
command !== "migrate"))
|
|
9
|
+
return execute();
|
|
10
|
+
const signal = getOperationCancellationSignal();
|
|
11
|
+
return withWorkloadAdmission({
|
|
12
|
+
enabled: true,
|
|
13
|
+
command,
|
|
14
|
+
waitTimeoutSeconds: policy.waitTimeoutSeconds ?? 30,
|
|
15
|
+
...(signal ? { signal } : {}),
|
|
16
|
+
onWaiting: () => {
|
|
17
|
+
if (!json)
|
|
18
|
+
process.stderr.write("Waiting for another participating local Rehearsal runtime phase. Ctrl+C cancels this request.\n");
|
|
19
|
+
},
|
|
20
|
+
execute,
|
|
21
|
+
});
|
|
22
|
+
};
|
|
@@ -67,6 +67,10 @@ export declare const loadPreparationContext: (planOptions: RehearsalConfigPathOp
|
|
|
67
67
|
retainBaselineGenerations: number;
|
|
68
68
|
}>;
|
|
69
69
|
resources: Readonly<{
|
|
70
|
+
workloadQueue?: {
|
|
71
|
+
enabled: boolean;
|
|
72
|
+
waitTimeoutSeconds: number;
|
|
73
|
+
};
|
|
70
74
|
staleAfterSeconds: number | null;
|
|
71
75
|
retainedEnvironmentCount: number;
|
|
72
76
|
maximumBytes: number | null;
|
|
@@ -245,6 +249,10 @@ export declare const buildRefreshWorkflowPlan: (planOptions: RehearsalConfigPath
|
|
|
245
249
|
retainBaselineGenerations: number;
|
|
246
250
|
}>;
|
|
247
251
|
resources: Readonly<{
|
|
252
|
+
workloadQueue?: {
|
|
253
|
+
enabled: boolean;
|
|
254
|
+
waitTimeoutSeconds: number;
|
|
255
|
+
};
|
|
248
256
|
staleAfterSeconds: number | null;
|
|
249
257
|
retainedEnvironmentCount: number;
|
|
250
258
|
maximumBytes: number | null;
|