@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.
Files changed (73) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/COMPATIBILITY.md +40 -0
  3. package/README.md +7 -3
  4. package/dist/package.json +4 -1
  5. package/dist/src/cli/arguments.d.mts +1 -0
  6. package/dist/src/cli/arguments.mjs +5 -0
  7. package/dist/src/cli/basic_command_handlers.mjs +6 -0
  8. package/dist/src/cli/cli_commands.mjs +2 -0
  9. package/dist/src/cli/editor_command_handlers.mjs +24 -0
  10. package/dist/src/cli/project_commands.d.mts +8 -0
  11. package/dist/src/cli/runtime_commands.d.mts +2 -19
  12. package/dist/src/cli/runtime_commands.mjs +3 -90
  13. package/dist/src/cli/runtime_stack_runner.d.mts +41 -0
  14. package/dist/src/cli/runtime_stack_runner.mjs +104 -0
  15. package/dist/src/cli/runtime_workload_admission.d.mts +8 -0
  16. package/dist/src/cli/runtime_workload_admission.mjs +22 -0
  17. package/dist/src/cli/source_commands.d.mts +8 -0
  18. package/dist/src/cli/workload_command_handlers.d.mts +2 -0
  19. package/dist/src/cli/workload_command_handlers.mjs +38 -0
  20. package/dist/src/project/configuration_contract.d.mts +5 -0
  21. package/dist/src/project/configuration_normalization.d.mts +4 -0
  22. package/dist/src/project/configuration_resolution.d.mts +4 -0
  23. package/dist/src/project/configuration_resource_policy.d.mts +4 -0
  24. package/dist/src/project/configuration_resource_policy.mjs +20 -1
  25. package/dist/src/project/configuration_schema.mjs +1 -0
  26. package/dist/src/runtime/migration_history.d.mts +5 -1
  27. package/dist/src/runtime/migration_history.mjs +24 -6
  28. package/dist/src/runtime/migration_impact.d.mts +125 -0
  29. package/dist/src/runtime/migration_impact.mjs +278 -0
  30. package/dist/src/runtime/migration_impact_catalog.d.mts +18 -0
  31. package/dist/src/runtime/migration_impact_catalog.mjs +116 -0
  32. package/dist/src/runtime/migration_impact_plan.d.mts +47 -0
  33. package/dist/src/runtime/migration_impact_plan.mjs +297 -0
  34. package/dist/src/runtime/migration_impact_queries.d.mts +22 -0
  35. package/dist/src/runtime/migration_impact_queries.mjs +270 -0
  36. package/dist/src/runtime/migration_insights.d.mts +137 -0
  37. package/dist/src/runtime/migration_insights.mjs +143 -0
  38. package/dist/src/runtime/migration_insights_input.d.mts +13 -0
  39. package/dist/src/runtime/migration_insights_input.mjs +153 -0
  40. package/dist/src/runtime/migration_sql_lexer.d.mts +14 -0
  41. package/dist/src/runtime/migration_sql_lexer.mjs +204 -0
  42. package/dist/src/runtime/migration_sql_scan.d.mts +16 -0
  43. package/dist/src/runtime/migration_sql_scan.mjs +369 -0
  44. package/dist/src/runtime/plan.d.mts +2 -1
  45. package/dist/src/runtime/plan.mjs +4 -4
  46. package/dist/src/runtime/topology.d.mts +1 -1
  47. package/dist/src/runtime/topology.mjs +2 -2
  48. package/dist/src/runtime/workload_queue.d.mts +39 -0
  49. package/dist/src/runtime/workload_queue.mjs +211 -0
  50. package/dist/src/runtime/workload_queue_location.d.mts +1 -0
  51. package/dist/src/runtime/workload_queue_location.mjs +43 -0
  52. package/dist/src/runtime/workload_queue_store.d.mts +27 -0
  53. package/dist/src/runtime/workload_queue_store.mjs +182 -0
  54. package/dist/src/shared/cancellation.mjs +2 -0
  55. package/dist/src/shared/held_path_ancestry.d.mts +7 -0
  56. package/dist/src/shared/held_path_ancestry.mjs +110 -0
  57. package/dist/src/shared/operation_guard.mjs +1 -0
  58. package/dist/src/targets/migration_impact_runtime.d.mts +22 -0
  59. package/dist/src/targets/migration_impact_runtime.mjs +367 -0
  60. package/docs/README.md +20 -7
  61. package/docs/adapters.md +10 -0
  62. package/docs/architecture.md +84 -1
  63. package/docs/commands.md +189 -34
  64. package/docs/configuration.md +56 -6
  65. package/docs/getting-started.md +1 -1
  66. package/docs/index.md +1 -0
  67. package/docs/releasing.md +15 -9
  68. package/docs/roadmap.md +27 -6
  69. package/docs/runtime-lifecycle.md +10 -0
  70. package/docs/stability.md +4 -4
  71. package/docs/updating.md +73 -0
  72. package/docs/vscode.md +23 -17
  73. 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.1.0...HEAD
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.1.0**;
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
- - [Release process](docs/releasing.md)
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.1.0",
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 { RuntimeStatusSnapshot } from "../runtime/status.mjs";
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, shouldStopRuntime, } from "../runtime/persistence.mjs";
22
- import { executeRuntimeTargetStack } from "./runtime_stack_execution.mjs";
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 = async ({ command, flags, planOptions, session, }) => {
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;