@rehearsal-db/core 1.2.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 +17 -1
- package/COMPATIBILITY.md +15 -6
- package/README.md +1 -1
- package/dist/package.json +2 -1
- package/dist/src/cli/arguments.d.mts +1 -0
- package/dist/src/cli/arguments.mjs +4 -0
- package/dist/src/cli/basic_command_handlers.mjs +2 -0
- package/dist/src/cli/cli_commands.mjs +2 -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/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 +1 -0
- package/docs/architecture.md +9 -0
- package/docs/commands.md +26 -6
- package/docs/configuration.md +33 -1
- package/docs/getting-started.md +1 -1
- package/docs/releasing.md +4 -0
- package/docs/roadmap.md +8 -6
- package/docs/stability.md +4 -4
- package/docs/updating.md +2 -2
- package/docs/vscode.md +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
All notable Rehearsal package changes are documented here. The format follows Keep
|
|
4
4
|
a Changelog, and published versions follow Semantic Versioning.
|
|
5
5
|
|
|
6
|
+
## [Unreleased]
|
|
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
|
+
|
|
6
20
|
## [1.2.0] - 2026-10-24
|
|
7
21
|
|
|
8
22
|
### Added
|
|
@@ -866,7 +880,9 @@ not unattended cleanup, and container age is not proof of user inactivity.
|
|
|
866
880
|
publication uses short-lived trusted OIDC, and every release tag must already exist on
|
|
867
881
|
protected `main`.
|
|
868
882
|
|
|
869
|
-
[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
|
|
870
886
|
[1.1.0]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.2...v1.1.0
|
|
871
887
|
[1.0.2]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.1...v1.0.2
|
|
872
888
|
[1.0.1]: https://github.com/Ddupasquier/rehearsal-db/compare/v1.0.0...v1.0.1
|
package/COMPATIBILITY.md
CHANGED
|
@@ -38,14 +38,14 @@ 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
|
-
|
|
41
|
+
Core 1.2.0 introduced optional capability
|
|
42
42
|
`rehearsalContracts.migrationInsights: 1` and `inspect insights --json`. It preserves
|
|
43
43
|
existing migration-plan v1, baseline formats, digest meaning and failure diagnosis.
|
|
44
44
|
Consumers must check the capability and exact plan/file bindings; absent or
|
|
45
|
-
unavailable static analysis is not safety evidence.
|
|
46
|
-
|
|
45
|
+
unavailable static analysis is not safety evidence. Package publication does not
|
|
46
|
+
establish completed editor, accessibility or genuine-provider acceptance.
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
Core 1.2.0 also introduced optional `rehearsalContracts.migrationImpact: 1` with
|
|
49
49
|
`inspect impact` and `measure impact`. Consumers must explicitly select a configured
|
|
50
50
|
local target and bind measurement consent to the exact whole-plan digest. Preview
|
|
51
51
|
does not contact a database; measurement reads only the current verified sandbox,
|
|
@@ -62,13 +62,22 @@ changes in the selected disposable sandbox; this is not a nonblocking query prom
|
|
|
62
62
|
Counts are decimal strings or `null` with an explicit unknown reason, never a safety
|
|
63
63
|
verdict, sequential simulation or production prediction. Partial/unknown measurement
|
|
64
64
|
uses `not_ready` with the existing not-ready exit `1`; normal rehearsal gates and existing JSON contracts
|
|
65
|
-
are unchanged. This
|
|
66
|
-
|
|
65
|
+
are unchanged. This capability does not claim a new database family or additional
|
|
66
|
+
platform/native acceptance.
|
|
67
67
|
Older strict editor activity consumers can reject the new `measure impact` operation
|
|
68
68
|
kind, including a retained completed receipt. Pair this capability with an editor
|
|
69
69
|
consumer explicitly supporting that kind; the CLI does not silently rename the
|
|
70
70
|
operation, relax unknown-kind validation or update an active editor installation.
|
|
71
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
|
+
|
|
72
81
|
## Operating systems and execution location
|
|
73
82
|
|
|
74
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.
|
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,6 +26,7 @@
|
|
|
26
26
|
"projectDiscovery": 1,
|
|
27
27
|
"resourceInventory": 1,
|
|
28
28
|
"resourceManager": 1,
|
|
29
|
+
"workloadQueue": 1,
|
|
29
30
|
"migrationReview": 1,
|
|
30
31
|
"migrationInsights": 1,
|
|
31
32
|
"migrationImpact": 1,
|
|
@@ -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,
|
|
@@ -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
|
|
@@ -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,
|
|
@@ -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;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Inspect/recover queue metadata without needing Docker, config or a project lock. */
|
|
2
|
+
import { inspectWorkloads, recoverWorkloads, withWorkloadDiagnostics, } from "../runtime/workload_queue.mjs";
|
|
3
|
+
import { exactCommands, renderedCommand, } from "./command_contract.mjs";
|
|
4
|
+
import { RehearsalError } from "../shared/diagnostics.mjs";
|
|
5
|
+
export const createWorkloadCommandHandlers = () => [
|
|
6
|
+
exactCommands("workloads", ["workloads"], async () => renderedCommand({
|
|
7
|
+
data: await withWorkloadDiagnostics(() => inspectWorkloads()),
|
|
8
|
+
render: (result) => [
|
|
9
|
+
"LOCAL WORKLOAD QUEUE — READ ONLY",
|
|
10
|
+
"Participating lifecycle phases only. Host/VM capacity is not measured.",
|
|
11
|
+
...result.entries.map((entry) => `${entry.command} · ${entry.phase} · owner ${entry.owner} · PID ${entry.pid}${entry.recoveryRequired ? " · recovery required" : ""}`),
|
|
12
|
+
result.entries.length
|
|
13
|
+
? ""
|
|
14
|
+
: "No participating workload is queued or held.",
|
|
15
|
+
...(result.recoveryDigest
|
|
16
|
+
? [
|
|
17
|
+
"A dead holder may have surviving children. Check its owned commands before recovery.",
|
|
18
|
+
`After checking: rehearsal workloads recover --confirm-workloads=${result.recoveryDigest}`,
|
|
19
|
+
"Recovery clears exactly reviewed queue metadata; it does not stop processes or change databases.",
|
|
20
|
+
]
|
|
21
|
+
: []),
|
|
22
|
+
]
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.join("\n"),
|
|
25
|
+
})),
|
|
26
|
+
exactCommands("workloads-recover", ["workloads recover"], async ({ flags }) => {
|
|
27
|
+
if (!flags.workloadConfirmation)
|
|
28
|
+
throw new RehearsalError({
|
|
29
|
+
category: "configuration_invalid",
|
|
30
|
+
code: "CONFIGURATION_INVALID",
|
|
31
|
+
message: "Inspect rehearsal workloads and review dead holders before using --confirm-workloads=<digest>. Check their owned commands first; recovery does not stop surviving children.",
|
|
32
|
+
});
|
|
33
|
+
return renderedCommand({
|
|
34
|
+
data: await withWorkloadDiagnostics(() => recoverWorkloads(flags.workloadConfirmation)),
|
|
35
|
+
render: (result) => `Recovered ${result.recovered} reviewed dead workload holder(s). No process, container or database was changed.`,
|
|
36
|
+
});
|
|
37
|
+
}),
|
|
38
|
+
];
|
|
@@ -4,6 +4,11 @@ export type RunPersistenceMode = "keep-until-stop" | "stop-after-run";
|
|
|
4
4
|
export type OpenPersistenceMode = "keep-until-stop" | "stop-on-application-exit";
|
|
5
5
|
export type ApplicationRequiredService = "database" | "auth" | "storage" | "postgrest" | "gateway" | "mail" | "studio" | "realtime" | "edge-functions" | "analytics" | "image-transforms";
|
|
6
6
|
export interface RehearsalResourcePolicy {
|
|
7
|
+
/** Cooperatively serialize participating local runtime lifecycle phases. */
|
|
8
|
+
workloadQueue?: {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
waitTimeoutSeconds?: number;
|
|
11
|
+
};
|
|
7
12
|
/** Review cutoff based on container start/stop, not application or database use. */
|
|
8
13
|
staleAfterSeconds?: number | null;
|
|
9
14
|
/** Advisory limit; retained databases are never automatically deleted. */
|
|
@@ -54,6 +54,10 @@ export declare const normalizeRehearsalConfig: (input: unknown) => Readonly<{
|
|
|
54
54
|
retainBaselineGenerations: number;
|
|
55
55
|
}>;
|
|
56
56
|
resources: Readonly<{
|
|
57
|
+
workloadQueue?: {
|
|
58
|
+
enabled: boolean;
|
|
59
|
+
waitTimeoutSeconds: number;
|
|
60
|
+
};
|
|
57
61
|
staleAfterSeconds: number | null;
|
|
58
62
|
retainedEnvironmentCount: number;
|
|
59
63
|
maximumBytes: number | null;
|
|
@@ -54,6 +54,10 @@ export declare const loadRehearsalConfig: ({ projectRoot, configPath, }?: Rehear
|
|
|
54
54
|
retainBaselineGenerations: number;
|
|
55
55
|
}>;
|
|
56
56
|
resources: Readonly<{
|
|
57
|
+
workloadQueue?: {
|
|
58
|
+
enabled: boolean;
|
|
59
|
+
waitTimeoutSeconds: number;
|
|
60
|
+
};
|
|
57
61
|
staleAfterSeconds: number | null;
|
|
58
62
|
retainedEnvironmentCount: number;
|
|
59
63
|
maximumBytes: number | null;
|
|
@@ -3,6 +3,10 @@ import type { ApplicationRequiredService } from "./configuration_contract.mjs";
|
|
|
3
3
|
import { type PlainObject } from "./configuration_validation.mjs";
|
|
4
4
|
export declare const APPLICATION_REQUIRED_SERVICES: readonly ["database", "auth", "storage", "postgrest", "gateway", "mail", "studio", "realtime", "edge-functions", "analytics", "image-transforms"];
|
|
5
5
|
export declare const normalizeResourcePolicy: (resources: PlainObject) => Readonly<{
|
|
6
|
+
workloadQueue?: {
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
waitTimeoutSeconds: number;
|
|
9
|
+
};
|
|
6
10
|
staleAfterSeconds: number | null;
|
|
7
11
|
retainedEnvironmentCount: number;
|
|
8
12
|
maximumBytes: number | null;
|