@rehearsal-db/core 0.1.0-beta.15 → 0.1.0-beta.16

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 (102) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/dist/package.json +10 -9
  3. package/dist/src/baseline/privacy_contract.d.mts +147 -0
  4. package/dist/src/baseline/privacy_contract.mjs +2 -0
  5. package/dist/src/baseline/privacy_dependency_graph.d.mts +7 -0
  6. package/dist/src/baseline/privacy_dependency_graph.mjs +110 -0
  7. package/dist/src/baseline/privacy_engine.d.mts +5 -162
  8. package/dist/src/baseline/privacy_engine.mjs +4 -1162
  9. package/dist/src/baseline/privacy_key.d.mts +7 -0
  10. package/dist/src/baseline/privacy_key.mjs +42 -0
  11. package/dist/src/baseline/privacy_pipeline.d.mts +7 -0
  12. package/dist/src/baseline/privacy_pipeline.mjs +127 -0
  13. package/dist/src/baseline/privacy_policy.d.mts +6 -0
  14. package/dist/src/baseline/privacy_policy.mjs +237 -0
  15. package/dist/src/baseline/privacy_recipe_validation.d.mts +5 -0
  16. package/dist/src/baseline/privacy_recipe_validation.mjs +289 -0
  17. package/dist/src/baseline/privacy_rule_registry.d.mts +18 -0
  18. package/dist/src/baseline/privacy_rule_registry.mjs +143 -0
  19. package/dist/src/baseline/privacy_transforms.d.mts +18 -0
  20. package/dist/src/baseline/privacy_transforms.mjs +142 -0
  21. package/dist/src/baseline/privacy_validation_shared.d.mts +31 -0
  22. package/dist/src/baseline/privacy_validation_shared.mjs +127 -0
  23. package/dist/src/cli/application_command_handlers.d.mts +8 -0
  24. package/dist/src/cli/application_command_handlers.mjs +52 -0
  25. package/dist/src/cli/arguments.d.mts +0 -5
  26. package/dist/src/cli/arguments.mjs +0 -7
  27. package/dist/src/cli/basic_command_handlers.d.mts +4 -0
  28. package/dist/src/cli/basic_command_handlers.mjs +56 -0
  29. package/dist/src/cli/cli_commands.d.mts +14 -0
  30. package/dist/src/cli/cli_commands.mjs +32 -0
  31. package/dist/src/cli/command_contract.d.mts +57 -0
  32. package/dist/src/cli/command_contract.mjs +39 -0
  33. package/dist/src/cli/command_output.d.mts +12 -0
  34. package/dist/src/cli/command_output.mjs +20 -0
  35. package/dist/src/cli/command_registry.d.mts +7 -0
  36. package/dist/src/cli/command_registry.mjs +14 -0
  37. package/dist/src/cli/guided.d.mts +3 -1
  38. package/dist/src/cli/guided.mjs +13 -7
  39. package/dist/src/cli/identity_command_handlers.d.mts +8 -0
  40. package/dist/src/cli/identity_command_handlers.mjs +43 -0
  41. package/dist/src/cli/init_command.d.mts +1 -1
  42. package/dist/src/cli/inspection_command_handlers.d.mts +3 -0
  43. package/dist/src/cli/inspection_command_handlers.mjs +79 -0
  44. package/dist/src/cli/project_command_handlers.d.mts +11 -0
  45. package/dist/src/cli/project_command_handlers.mjs +92 -0
  46. package/dist/src/cli/project_commands.d.mts +6 -4
  47. package/dist/src/cli/project_commands.mjs +6 -6
  48. package/dist/src/cli/rehearsal.d.mts +2 -2
  49. package/dist/src/cli/rehearsal.mjs +51 -567
  50. package/dist/src/cli/runtime_action_handlers.d.mts +12 -0
  51. package/dist/src/cli/runtime_action_handlers.mjs +201 -0
  52. package/dist/src/cli/runtime_commands.d.mts +7 -3
  53. package/dist/src/cli/runtime_commands.mjs +10 -8
  54. package/dist/src/cli/source_command_handlers.d.mts +8 -0
  55. package/dist/src/cli/source_command_handlers.mjs +62 -0
  56. package/dist/src/cli/source_commands.d.mts +5 -1
  57. package/dist/src/cli/source_commands.mjs +3 -1
  58. package/dist/src/identity/claim.d.mts +5 -144
  59. package/dist/src/identity/claim.mjs +4 -947
  60. package/dist/src/identity/claim_contract.d.mts +135 -0
  61. package/dist/src/identity/claim_contract.mjs +6 -0
  62. package/dist/src/identity/claim_executor.d.mts +10 -0
  63. package/dist/src/identity/claim_executor.mjs +358 -0
  64. package/dist/src/identity/claim_identity_lookup.d.mts +17 -0
  65. package/dist/src/identity/claim_identity_lookup.mjs +125 -0
  66. package/dist/src/identity/claim_plan.d.mts +8 -0
  67. package/dist/src/identity/claim_plan.mjs +61 -0
  68. package/dist/src/identity/claim_policy.d.mts +3 -0
  69. package/dist/src/identity/claim_policy.mjs +377 -0
  70. package/dist/src/identity/claim_sql.d.mts +10 -0
  71. package/dist/src/identity/claim_sql.mjs +46 -0
  72. package/dist/src/identity/claim_verification.d.mts +13 -0
  73. package/dist/src/identity/claim_verification.mjs +35 -0
  74. package/dist/src/index.d.mts +1 -1
  75. package/dist/src/project/configuration.d.mts +11 -179
  76. package/dist/src/project/configuration.mjs +7 -956
  77. package/dist/src/project/configuration_contract.d.mts +125 -0
  78. package/dist/src/project/configuration_contract.mjs +27 -0
  79. package/dist/src/project/configuration_discovery.d.mts +7 -0
  80. package/dist/src/project/configuration_discovery.mjs +37 -0
  81. package/dist/src/project/configuration_normalization.d.mts +107 -0
  82. package/dist/src/project/configuration_normalization.mjs +302 -0
  83. package/dist/src/project/configuration_rendering.d.mts +14 -0
  84. package/dist/src/project/configuration_rendering.mjs +224 -0
  85. package/dist/src/project/configuration_resolution.d.mts +125 -0
  86. package/dist/src/project/configuration_resolution.mjs +81 -0
  87. package/dist/src/project/configuration_schema.d.mts +22 -0
  88. package/dist/src/project/configuration_schema.mjs +160 -0
  89. package/dist/src/project/configuration_validation.d.mts +22 -0
  90. package/dist/src/project/configuration_validation.mjs +136 -0
  91. package/dist/src/project/project_detection.d.mts +14 -0
  92. package/dist/src/project/project_detection.mjs +47 -0
  93. package/dist/src/runtime/lifecycle_engine.d.mts +63 -0
  94. package/dist/src/runtime/lifecycle_engine.mjs +114 -0
  95. package/dist/src/runtime/plan.mjs +2 -1
  96. package/dist/src/targets/postgresql.mjs +142 -163
  97. package/dist/src/targets/supabase.mjs +142 -174
  98. package/docs/architecture.md +118 -3
  99. package/docs/releasing.md +8 -3
  100. package/docs/typescript.md +57 -10
  101. package/package.json +10 -9
  102. package/types/configuration.d.mts +5 -164
@@ -0,0 +1,127 @@
1
+ /** Redacted primitive validators and closed vocabulary for privacy policy v2. */
2
+ export const IDENTIFIER = /^[a-z][a-z0-9_]{0,62}$/u;
3
+ export const BINDING_NAME = /^[a-z][a-z0-9-]{0,62}$/u;
4
+ export const ENVIRONMENT_KEY = /^[A-Z][A-Z0-9_]*$/u;
5
+ export const HEX_64 = /^[a-f0-9]{64}$/u;
6
+ export const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/iu;
7
+ export const PORTABLE_CODE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/u;
8
+ export const ACTIONS = new Set([
9
+ "KEEP",
10
+ "PSEUDONYMIZE",
11
+ "REPLACE",
12
+ "EXCLUDE",
13
+ "DERIVE",
14
+ ]);
15
+ export const PSEUDONYM_FORMATS = new Set([
16
+ "uuid",
17
+ "email",
18
+ "text",
19
+ "integer",
20
+ "hex",
21
+ "gtin",
22
+ "url",
23
+ ]);
24
+ export const HEX_LENGTHS = new Set([32, 64]);
25
+ export const GTIN_LENGTHS = new Set([8, 12, 13, 14]);
26
+ export const DATE_REPRESENTATIONS = new Set([
27
+ "iso-string",
28
+ "epoch-milliseconds",
29
+ ]);
30
+ export const DERIVATION_KINDS = new Set([
31
+ "date-shift",
32
+ "digest",
33
+ "enum",
34
+ "validated-string",
35
+ "path-map",
36
+ "json-object",
37
+ "json-array",
38
+ "json-union",
39
+ "json-dictionary",
40
+ "approved-owner",
41
+ "binding-substitute",
42
+ ]);
43
+ export const URL_PATH_PREFIX = "/rehearsal/";
44
+ export const URL_TOKEN_LENGTH = 32;
45
+ export const MAXIMUM_URL_LENGTH = 2_048;
46
+ export const MAXIMUM_TEXT_SUBSTITUTION_BYTES = 65_536;
47
+ export const LOOPBACK_HOSTNAMES = new Set([
48
+ "127.0.0.1",
49
+ "::1",
50
+ "[::1]",
51
+ "localhost",
52
+ ]);
53
+ export const DEFAULT_PATH_MAXIMUM_BYTES = 2_048;
54
+ export const DEFAULT_PATH_MAXIMUM_SEGMENTS = 32;
55
+ export const MAXIMUM_POLICY_DEPTH = 16;
56
+ export const MAXIMUM_ENUM_VALUES = 128;
57
+ export const MAXIMUM_ENUM_STRING_BYTES = 256;
58
+ export const isPrivacyObject = (value) => value !== null &&
59
+ typeof value === "object" &&
60
+ !Array.isArray(value) &&
61
+ Object.getPrototypeOf(value) === Object.prototype;
62
+ export const assertKnownKeys = (value, allowed, label) => {
63
+ for (const key of Object.keys(value)) {
64
+ if (!allowed.includes(key))
65
+ throw new Error(`${label}.${key} is unknown.`);
66
+ }
67
+ };
68
+ export const validateIdentifier = (value, label) => {
69
+ if (typeof value !== "string" || !IDENTIFIER.test(value)) {
70
+ throw new Error(`${label} must be a lowercase PostgreSQL identifier.`);
71
+ }
72
+ return value;
73
+ };
74
+ export const validateNonEmpty = (value, label) => {
75
+ if (typeof value !== "string" || !value.trim()) {
76
+ throw new Error(`${label} must be a non-empty string.`);
77
+ }
78
+ return value.trim();
79
+ };
80
+ export const validatePositiveInteger = (value, label, maximum = Number.MAX_SAFE_INTEGER) => {
81
+ if (typeof value !== "number" ||
82
+ !Number.isSafeInteger(value) ||
83
+ value < 1 ||
84
+ value > maximum) {
85
+ throw new Error(`${label} must be an integer from 1 through ${maximum}.`);
86
+ }
87
+ return value;
88
+ };
89
+ export const validateReviewedScalar = (value, label) => {
90
+ if (typeof value === "string") {
91
+ if (Buffer.byteLength(value) > MAXIMUM_ENUM_STRING_BYTES) {
92
+ throw new Error(`${label} must contain at most ${MAXIMUM_ENUM_STRING_BYTES} UTF-8 bytes.`);
93
+ }
94
+ return value;
95
+ }
96
+ if (typeof value === "boolean")
97
+ return value;
98
+ if (typeof value === "number" &&
99
+ Number.isFinite(value) &&
100
+ !Object.is(value, -0)) {
101
+ return value;
102
+ }
103
+ throw new Error(`${label} must be a JSON string, finite number, or boolean.`);
104
+ };
105
+ export const validateHttpsLoopbackOrigin = (value, label) => {
106
+ if (typeof value !== "string" || value.trim() !== value || !value) {
107
+ throw new Error(`${label} must be a safe HTTPS loopback origin.`);
108
+ }
109
+ let parsed;
110
+ try {
111
+ parsed = new URL(value);
112
+ }
113
+ catch {
114
+ throw new Error(`${label} must be a safe HTTPS loopback origin.`);
115
+ }
116
+ if (parsed.protocol !== "https:" ||
117
+ !LOOPBACK_HOSTNAMES.has(parsed.hostname) ||
118
+ ![parsed.origin, `${parsed.origin}/`].includes(value) ||
119
+ parsed.username ||
120
+ parsed.password ||
121
+ (parsed.pathname !== "/" && parsed.pathname !== "") ||
122
+ parsed.search ||
123
+ parsed.hash) {
124
+ throw new Error(`${label} must be a safe HTTPS loopback origin.`);
125
+ }
126
+ return parsed.origin;
127
+ };
@@ -0,0 +1,8 @@
1
+ /** Persistent application-session command handler. */
2
+ import type { CommandDefinition } from "./command_contract.mjs";
3
+ import type { createRuntimeCommands } from "./runtime_commands.mjs";
4
+ type RuntimeCommands = ReturnType<typeof createRuntimeCommands>;
5
+ export declare const createApplicationCommandHandlers: ({ openRuntimeApplication, }: {
6
+ openRuntimeApplication: RuntimeCommands["openRuntimeApplication"];
7
+ }) => readonly CommandDefinition[];
8
+ export {};
@@ -0,0 +1,52 @@
1
+ /** Persistent application-session command handler. */
2
+ import * as prompts from "@clack/prompts";
3
+ import { exactCommands, renderedCommand } from "./command_contract.mjs";
4
+ import { useStyledPrompts } from "./terminal.mjs";
5
+ export const createApplicationCommandHandlers = ({ openRuntimeApplication, }) => [
6
+ exactCommands("open", ["open"], async ({ flags, planOptions, guided, state }) => {
7
+ if (flags.dryRun) {
8
+ throw new Error("--dry-run is supported only by rehearsal run.");
9
+ }
10
+ const data = await openRuntimeApplication({
11
+ flags,
12
+ planOptions,
13
+ session: state,
14
+ onReady: (ready) => {
15
+ const lines = [
16
+ `Application: ${ready.url}`,
17
+ "The verified database runtime stays running and keeps your local changes.",
18
+ guided
19
+ ? "Press Ctrl+C to close the app and return here, or Ctrl+Z to exit Rehearsal."
20
+ : "Press Ctrl+C or Ctrl+Z to close the app. Rehearsal will leave the database runtime running.",
21
+ ];
22
+ if (useStyledPrompts(flags)) {
23
+ prompts.note(lines.join("\n"), "SANDBOX READY");
24
+ }
25
+ else if (!flags.json) {
26
+ console.log(["", "SANDBOX READY", ...lines, ""].join("\n"));
27
+ }
28
+ },
29
+ });
30
+ if (guided && data.application.stoppedBy === "SIGTSTP") {
31
+ flags.exitGuidedSession = true;
32
+ }
33
+ if (guided) {
34
+ state.lastGuidedDetails = [
35
+ data.started.output,
36
+ data.verified.output,
37
+ ...data.preparations.map((preparation) => preparation.output),
38
+ ]
39
+ .filter(Boolean)
40
+ .join("\n");
41
+ }
42
+ return renderedCommand({
43
+ data,
44
+ render: (result) => [
45
+ "SANDBOX APP CLOSED",
46
+ `Application stopped after ${result.application.stoppedBy}.`,
47
+ "The database runtime and its local database and Storage changes were preserved.",
48
+ "Run rehearsal open to return, rehearsal verify to check it, or rehearsal stop to stop the databases.",
49
+ ].join("\n"),
50
+ });
51
+ }),
52
+ ];
@@ -22,13 +22,8 @@ export interface RehearsalCliFlags {
22
22
  refreshConfirmation: string | undefined;
23
23
  identityConfirmation: string | undefined;
24
24
  identityName: string | undefined;
25
- runtimeTopology: unknown | undefined;
26
- targetConfirmations: unknown | undefined;
27
25
  exitGuidedSession: boolean;
28
26
  guided: boolean;
29
- setupPlan?: unknown;
30
- preparationPlan?: unknown;
31
- cleanupPlan?: unknown;
32
27
  }
33
28
  export interface ParsedArguments {
34
29
  flags: RehearsalCliFlags;
@@ -22,8 +22,6 @@ export const parseArguments = (arguments_) => {
22
22
  refreshConfirmation: undefined,
23
23
  identityConfirmation: undefined,
24
24
  identityName: undefined,
25
- runtimeTopology: undefined,
26
- targetConfirmations: undefined,
27
25
  exitGuidedSession: false,
28
26
  guided: false,
29
27
  };
@@ -97,12 +95,7 @@ export const resetGuidedFlags = (flags) => {
97
95
  refreshConfirmation: undefined,
98
96
  identityConfirmation: undefined,
99
97
  identityName: undefined,
100
- runtimeTopology: undefined,
101
- targetConfirmations: undefined,
102
98
  exitGuidedSession: false,
103
- setupPlan: undefined,
104
- preparationPlan: undefined,
105
- cleanupPlan: undefined,
106
99
  });
107
100
  };
108
101
  export const commandMutatesProject = ({ command, flags, guided, }) => {
@@ -0,0 +1,4 @@
1
+ /** Stable version and help handlers with no project-side effects. */
2
+ import type { CommandDefinition } from "./command_contract.mjs";
3
+ export declare const usage: () => string;
4
+ export declare const createBasicCommandHandlers: () => readonly CommandDefinition[];
@@ -0,0 +1,56 @@
1
+ /** Stable version and help handlers with no project-side effects. */
2
+ import { REHEARSAL_VERSION } from "../shared/diagnostics.mjs";
3
+ import { exactCommands, textCommand } from "./command_contract.mjs";
4
+ export const usage = () => `Usage: rehearsal <command> [options]
5
+
6
+ Commands:
7
+ version Print the installed Rehearsal version
8
+ guide Open the interactive, state-aware home screen
9
+ setup [--target=] [--write] Preview or create safe first-run scaffolding
10
+ init [--write] Preview or explicitly write safe starter config
11
+ baseline prepare --records= --ledger= [--write] Create a fail-closed policy draft
12
+ baseline create --records= --ledger= [--assets=] Create a baseline from safe local inputs
13
+ privacy key [--write] Preview or create the owner-only pseudonym key
14
+ source plan Preview exact, temporary source access
15
+ source apply --confirm-source-access= Apply the reviewed source-access plan
16
+ source retire [--confirm-source-retirement=] Preview or retire exact source access
17
+ baseline refresh Replace only the baseline from a reviewed source
18
+ refresh [--confirm-refresh=] Replace the database copy, reset locally, and remove old copies
19
+ identity plan --identity= Preview an approved local identity association
20
+ identity claim --identity= --confirm-identity= Apply the exact local association
21
+ doctor Check whether Rehearsal is safe and ready
22
+ support Print a safe, copy-ready support report
23
+ explain Show the immutable execution plan
24
+ run --dry-run Alias the exact explain plan without mutations
25
+ run --confirm-candidates= Execute reset, migration, and verification locally
26
+ open Start the runtime and keep the application open
27
+ candidates Show the exact pending migration digest
28
+ inspect baseline Show verified baseline provenance
29
+ inspect migrations Classify represented, applied, and candidate migrations
30
+ start Start an existing verified local runtime
31
+ migrate --confirm-candidates= Apply the exact candidate suffix without resetting
32
+ reset Restore and verify the immutable local baseline
33
+ status Report the disposable local runtime state
34
+ stop Stop only this project's local runtime
35
+ discard Remove only this project's disposable runtime
36
+ cleanup [--include-runtime] [--include-images] [--write] [--confirm-cleanup=]
37
+ Preview or apply conservative disk cleanup
38
+ verify Verify the current local Rehearsal runtime
39
+
40
+ Options: --help, -h --version, -V --json --verbose --debug --plain
41
+ --config=<path> --target=supabase|postgresql
42
+ --confirm-source-access=<digest> --confirm-source-retirement=<digest>
43
+ --confirm-refresh=<digest>`;
44
+ export const createBasicCommandHandlers = () => [
45
+ {
46
+ id: "version",
47
+ matches: ({ command, flags }) => command === "version" || flags.version,
48
+ handle: async ({ flags }) => textCommand(flags.json
49
+ ? JSON.stringify({ name: "@rehearsal-db/core", version: REHEARSAL_VERSION }, null, 2)
50
+ : REHEARSAL_VERSION),
51
+ },
52
+ {
53
+ ...exactCommands("help", ["help"], async () => textCommand(usage())),
54
+ matches: ({ command, flags }) => command === "help" || flags.help,
55
+ },
56
+ ];
@@ -0,0 +1,14 @@
1
+ /** Assemble the CLI command families into one ordered typed registry. */
2
+ import type { createProjectCommands } from "./project_commands.mjs";
3
+ import type { createRuntimeCommands } from "./runtime_commands.mjs";
4
+ import type { createRefreshWorkflow } from "./source_commands.mjs";
5
+ type ProjectCommands = ReturnType<typeof createProjectCommands>;
6
+ type RuntimeCommands = ReturnType<typeof createRuntimeCommands>;
7
+ type RefreshWorkflow = ReturnType<typeof createRefreshWorkflow>;
8
+ export declare const createCliCommandRegistry: ({ projectRoot, projectCommands, runtimeCommands, runRefreshWorkflow, }: {
9
+ projectRoot: string;
10
+ projectCommands: ProjectCommands;
11
+ runtimeCommands: RuntimeCommands;
12
+ runRefreshWorkflow: RefreshWorkflow;
13
+ }) => import("./command_registry.mjs").CommandRegistry;
14
+ export {};
@@ -0,0 +1,32 @@
1
+ /** Assemble the CLI command families into one ordered typed registry. */
2
+ import { createApplicationCommandHandlers } from "./application_command_handlers.mjs";
3
+ import { createBasicCommandHandlers } from "./basic_command_handlers.mjs";
4
+ import { createCommandRegistry } from "./command_registry.mjs";
5
+ import { createIdentityCommandHandlers } from "./identity_command_handlers.mjs";
6
+ import { createInspectionCommandHandlers } from "./inspection_command_handlers.mjs";
7
+ import { createProjectCommandHandlers } from "./project_command_handlers.mjs";
8
+ import { createRuntimeActionHandlers } from "./runtime_action_handlers.mjs";
9
+ import { createSourceCommandHandlers } from "./source_command_handlers.mjs";
10
+ export const createCliCommandRegistry = ({ projectRoot, projectCommands, runtimeCommands, runRefreshWorkflow, }) => createCommandRegistry([
11
+ ...createBasicCommandHandlers(),
12
+ ...createProjectCommandHandlers({
13
+ projectRoot,
14
+ runSetup: projectCommands.runSetup,
15
+ runBaselinePreparation: projectCommands.runBaselinePreparation,
16
+ runCleanup: projectCommands.runCleanup,
17
+ }),
18
+ ...createSourceCommandHandlers({ runRefreshWorkflow }),
19
+ ...createIdentityCommandHandlers({
20
+ loadIdentityClaimPlan: projectCommands.loadIdentityClaimPlan,
21
+ }),
22
+ ...createInspectionCommandHandlers(),
23
+ ...createApplicationCommandHandlers({
24
+ openRuntimeApplication: runtimeCommands.openRuntimeApplication,
25
+ }),
26
+ ...createRuntimeActionHandlers({
27
+ prepareCandidateConfirmation: runtimeCommands.prepareCandidateConfirmation,
28
+ prepareDependentTargets: runtimeCommands.prepareDependentTargets,
29
+ proveRuntimeStack: runtimeCommands.proveRuntimeStack,
30
+ runRuntimeStack: runtimeCommands.runRuntimeStack,
31
+ }),
32
+ ]);
@@ -0,0 +1,57 @@
1
+ /** Typed contracts shared by CLI command handlers and the process entry point. */
2
+ import type { RehearsalConfigPathOptions } from "../project/configuration.mjs";
3
+ import type { RehearsalStatus } from "../shared/diagnostics.mjs";
4
+ import type { RehearsalCliFlags, RehearsalVerbosity } from "./arguments.mjs";
5
+ export interface CliSessionState {
6
+ lastGuidedDetails: string | undefined;
7
+ runtimeTopology: unknown | undefined;
8
+ targetConfirmations: unknown | undefined;
9
+ setupPlan: unknown | undefined;
10
+ preparationPlan: unknown | undefined;
11
+ cleanupPlan: unknown | undefined;
12
+ }
13
+ export declare const createCliSessionState: () => CliSessionState;
14
+ export declare const resetCommandSessionState: (state: CliSessionState) => void;
15
+ export interface CommandContext {
16
+ readonly command: string;
17
+ readonly flags: RehearsalCliFlags;
18
+ readonly planOptions: RehearsalConfigPathOptions;
19
+ readonly guided: boolean;
20
+ readonly state: CliSessionState;
21
+ }
22
+ export interface RenderedCommandResult {
23
+ readonly kind: "rendered";
24
+ readonly data: unknown;
25
+ readonly status: RehearsalStatus;
26
+ readonly render: (verbosity: RehearsalVerbosity) => string;
27
+ readonly exitCode?: number;
28
+ }
29
+ export interface TextCommandResult {
30
+ readonly kind: "text";
31
+ readonly output: string;
32
+ readonly exitCode?: number;
33
+ }
34
+ export interface SilentCommandResult {
35
+ readonly kind: "silent";
36
+ readonly exitCode?: number;
37
+ }
38
+ export type CommandResult = RenderedCommandResult | TextCommandResult | SilentCommandResult;
39
+ export type CommandHandler = (context: CommandContext) => Promise<CommandResult>;
40
+ export interface CommandDefinition {
41
+ readonly id: string;
42
+ readonly matches: (context: CommandContext) => boolean;
43
+ readonly handle: CommandHandler;
44
+ }
45
+ export declare const renderedCommand: <Data>({ data, render, status, exitCode, }: {
46
+ data: Data;
47
+ render: (data: Data, verbosity: RehearsalVerbosity) => string;
48
+ status?: RehearsalStatus;
49
+ exitCode?: number;
50
+ }) => RenderedCommandResult;
51
+ export declare const textCommand: (output: string, options?: {
52
+ exitCode?: number;
53
+ }) => TextCommandResult;
54
+ export declare const silentCommand: (options?: {
55
+ exitCode?: number;
56
+ }) => SilentCommandResult;
57
+ export declare const exactCommands: (id: string, commands: readonly string[], handle: CommandHandler) => CommandDefinition;
@@ -0,0 +1,39 @@
1
+ /** Typed contracts shared by CLI command handlers and the process entry point. */
2
+ export const createCliSessionState = () => ({
3
+ lastGuidedDetails: undefined,
4
+ runtimeTopology: undefined,
5
+ targetConfirmations: undefined,
6
+ setupPlan: undefined,
7
+ preparationPlan: undefined,
8
+ cleanupPlan: undefined,
9
+ });
10
+ export const resetCommandSessionState = (state) => {
11
+ Object.assign(state, {
12
+ runtimeTopology: undefined,
13
+ targetConfirmations: undefined,
14
+ setupPlan: undefined,
15
+ preparationPlan: undefined,
16
+ cleanupPlan: undefined,
17
+ });
18
+ };
19
+ export const renderedCommand = ({ data, render, status = "success", exitCode, }) => ({
20
+ kind: "rendered",
21
+ data,
22
+ status,
23
+ render: (verbosity) => render(data, verbosity),
24
+ ...(exitCode === undefined ? {} : { exitCode }),
25
+ });
26
+ export const textCommand = (output, options = {}) => ({
27
+ kind: "text",
28
+ output,
29
+ ...(options.exitCode === undefined ? {} : { exitCode: options.exitCode }),
30
+ });
31
+ export const silentCommand = (options = {}) => ({
32
+ kind: "silent",
33
+ ...(options.exitCode === undefined ? {} : { exitCode: options.exitCode }),
34
+ });
35
+ export const exactCommands = (id, commands, handle) => ({
36
+ id,
37
+ matches: ({ command }) => commands.includes(command),
38
+ handle,
39
+ });
@@ -0,0 +1,12 @@
1
+ /** Emit the stable JSON envelope or human result for a typed command result. */
2
+ import type { RehearsalCliFlags } from "./arguments.mjs";
3
+ import type { CommandResult } from "./command_contract.mjs";
4
+ export declare const createCommandEmitter: ({ startedAt, startedMs, write, }: {
5
+ startedAt: Date;
6
+ startedMs: number;
7
+ write?: (output: string) => void;
8
+ }) => ({ command, flags, result, }: {
9
+ command: string;
10
+ flags: RehearsalCliFlags;
11
+ result: CommandResult;
12
+ }) => number | undefined;
@@ -0,0 +1,20 @@
1
+ /** Emit the stable JSON envelope or human result for a typed command result. */
2
+ import { performance } from "node:perf_hooks";
3
+ import { createRehearsalResult } from "../shared/diagnostics.mjs";
4
+ export const createCommandEmitter = ({ startedAt, startedMs, write = console.log, }) => ({ command, flags, result, }) => {
5
+ if (result.kind === "text")
6
+ write(result.output);
7
+ if (result.kind === "rendered") {
8
+ const envelope = createRehearsalResult({
9
+ command,
10
+ status: result.status,
11
+ data: result.data,
12
+ startedAt,
13
+ durationMs: Math.round((performance.now() - startedMs) * 100) / 100,
14
+ });
15
+ write(flags.json
16
+ ? JSON.stringify(envelope, null, 2)
17
+ : result.render(flags.verbosity));
18
+ }
19
+ return result.exitCode;
20
+ };
@@ -0,0 +1,7 @@
1
+ /** Resolve one CLI request to exactly one typed command handler. */
2
+ import type { CommandContext, CommandDefinition, CommandResult } from "./command_contract.mjs";
3
+ export interface CommandRegistry {
4
+ readonly ids: readonly string[];
5
+ execute(context: CommandContext): Promise<CommandResult | null>;
6
+ }
7
+ export declare const createCommandRegistry: (definitions: readonly CommandDefinition[]) => CommandRegistry;
@@ -0,0 +1,14 @@
1
+ /** Resolve one CLI request to exactly one typed command handler. */
2
+ export const createCommandRegistry = (definitions) => {
3
+ const ids = definitions.map(({ id }) => id);
4
+ if (new Set(ids).size !== ids.length) {
5
+ throw new Error("CLI command registry IDs must be unique.");
6
+ }
7
+ return Object.freeze({
8
+ ids: Object.freeze(ids),
9
+ async execute(context) {
10
+ const definition = definitions.find((candidate) => candidate.matches(context));
11
+ return definition ? definition.handle(context) : null;
12
+ },
13
+ });
14
+ };
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import type { RehearsalConfigPathOptions } from "../project/configuration.mjs";
5
5
  import type { RehearsalCliFlags } from "./arguments.mjs";
6
+ import type { CliSessionState } from "./command_contract.mjs";
6
7
  import type { RuntimeTopologyTarget } from "../runtime/topology.mjs";
7
8
  import type { RehearsalCleanupPlan } from "../runtime/cleanup.mjs";
8
9
  interface TopologySummary {
@@ -33,7 +34,7 @@ interface GuidedRefreshPlan {
33
34
  readonly steps: readonly string[];
34
35
  };
35
36
  }
36
- export declare const createGuidedHome: ({ projectRoot, topologyCandidateSummary, buildRefreshWorkflowPlan, planRuntimeStackCleanup, getLastGuidedDetails, }: {
37
+ export declare const createGuidedHome: ({ projectRoot, topologyCandidateSummary, buildRefreshWorkflowPlan, planRuntimeStackCleanup, getLastGuidedDetails, session, }: {
37
38
  projectRoot: string;
38
39
  topologyCandidateSummary: (options: RehearsalConfigPathOptions) => Promise<TopologySummary>;
39
40
  buildRefreshWorkflowPlan: (options: RehearsalConfigPathOptions) => Promise<{
@@ -44,6 +45,7 @@ export declare const createGuidedHome: ({ projectRoot, topologyCandidateSummary,
44
45
  planOptions: RehearsalConfigPathOptions;
45
46
  }) => Promise<GuidedCleanupPlan>;
46
47
  getLastGuidedDetails: () => string | undefined;
48
+ session: CliSessionState;
47
49
  }) => ({ flags, planOptions, }: {
48
50
  flags: RehearsalCliFlags;
49
51
  planOptions: RehearsalConfigPathOptions;
@@ -415,7 +415,7 @@ const showGuidedStatus = ({ state, options, flags, }) => {
415
415
  ].join("\n"));
416
416
  }
417
417
  };
418
- const guideCleanup = async ({ flags, planOptions, planRuntimeStackCleanup, }) => {
418
+ const guideCleanup = async ({ flags, planOptions, planRuntimeStackCleanup, session, }) => {
419
419
  const cleanupScope = await promptForChoice({
420
420
  message: "What may Rehearsal include in the cleanup preview?",
421
421
  flags,
@@ -444,7 +444,7 @@ const guideCleanup = async ({ flags, planOptions, planRuntimeStackCleanup, }) =>
444
444
  flags.includeRuntime = ["runtime", "all"].includes(cleanupScope.command);
445
445
  flags.includeImages = ["images", "all"].includes(cleanupScope.command);
446
446
  const cleanupPlan = await planRuntimeStackCleanup({ flags, planOptions });
447
- flags.cleanupPlan = cleanupPlan;
447
+ session.cleanupPlan = cleanupPlan;
448
448
  console.log(`\n${renderCleanup({ mode: "preview", plan: cleanupPlan })}`);
449
449
  const selectedCount = "targets" in cleanupPlan
450
450
  ? cleanupPlan.targets.reduce((total, target) => total +
@@ -465,7 +465,7 @@ const guideCleanup = async ({ flags, planOptions, planRuntimeStackCleanup, }) =>
465
465
  flags.cleanupConfirmation = cleanupPlan.digest;
466
466
  return "cleanup";
467
467
  };
468
- const guideBaseline = async ({ state, selected, flags, planOptions, projectRoot, }) => {
468
+ const guideBaseline = async ({ state, selected, flags, planOptions, projectRoot, session, }) => {
469
469
  console.log([
470
470
  "",
471
471
  terminalStyle(flags, "1", "CREATE A SAFE BASELINE"),
@@ -526,7 +526,7 @@ const guideBaseline = async ({ state, selected, flags, planOptions, projectRoot,
526
526
  console.log("No changes made.");
527
527
  return "home";
528
528
  }
529
- flags.preparationPlan = preparationPlan;
529
+ session.preparationPlan = preparationPlan;
530
530
  flags.write = true;
531
531
  return "baseline prepare";
532
532
  }
@@ -567,7 +567,7 @@ const guideBaseline = async ({ state, selected, flags, planOptions, projectRoot,
567
567
  return "home";
568
568
  }
569
569
  };
570
- export const createGuidedHome = ({ projectRoot, topologyCandidateSummary, buildRefreshWorkflowPlan, planRuntimeStackCleanup, getLastGuidedDetails, }) => async ({ flags, planOptions, }) => {
570
+ export const createGuidedHome = ({ projectRoot, topologyCandidateSummary, buildRefreshWorkflowPlan, planRuntimeStackCleanup, getLastGuidedDetails, session, }) => async ({ flags, planOptions, }) => {
571
571
  const state = await inspectGuidedProject({
572
572
  projectRoot,
573
573
  planOptions,
@@ -659,7 +659,12 @@ export const createGuidedHome = ({ projectRoot, topologyCandidateSummary, buildR
659
659
  return runtimeAction.command;
660
660
  }
661
661
  if (selected.command === "cleanup-guide") {
662
- return guideCleanup({ flags, planOptions, planRuntimeStackCleanup });
662
+ return guideCleanup({
663
+ flags,
664
+ planOptions,
665
+ planRuntimeStackCleanup,
666
+ session,
667
+ });
663
668
  }
664
669
  if (["baseline-guide", "baseline-prepare"].includes(selected.command)) {
665
670
  return guideBaseline({
@@ -668,6 +673,7 @@ export const createGuidedHome = ({ projectRoot, topologyCandidateSummary, buildR
668
673
  flags,
669
674
  planOptions,
670
675
  projectRoot,
676
+ session,
671
677
  });
672
678
  }
673
679
  if (selected.command !== "setup-write")
@@ -707,7 +713,7 @@ export const createGuidedHome = ({ projectRoot, topologyCandidateSummary, buildR
707
713
  console.log("No changes made.");
708
714
  return "home";
709
715
  }
710
- flags.setupPlan = setupPlan;
716
+ session.setupPlan = setupPlan;
711
717
  flags.write = true;
712
718
  return "setup";
713
719
  };
@@ -0,0 +1,8 @@
1
+ /** Provider-neutral copied-account planning and application handlers. */
2
+ import type { CommandDefinition } from "./command_contract.mjs";
3
+ import type { createProjectCommands } from "./project_commands.mjs";
4
+ type ProjectCommands = ReturnType<typeof createProjectCommands>;
5
+ export declare const createIdentityCommandHandlers: ({ loadIdentityClaimPlan, }: {
6
+ loadIdentityClaimPlan: ProjectCommands["loadIdentityClaimPlan"];
7
+ }) => readonly CommandDefinition[];
8
+ export {};
@@ -0,0 +1,43 @@
1
+ /** Provider-neutral copied-account planning and application handlers. */
2
+ import { readFile } from "node:fs/promises";
3
+ import { parseEnv } from "node:util";
4
+ import { applyIdentityClaim } from "../identity/claim.mjs";
5
+ import { createSupabaseStorageTransfer } from "../identity/storage.mjs";
6
+ import { exactCommands, renderedCommand } from "./command_contract.mjs";
7
+ import { renderIdentityPlan } from "./renderers.mjs";
8
+ export const createIdentityCommandHandlers = ({ loadIdentityClaimPlan, }) => [
9
+ exactCommands("identity", ["identity plan", "identity claim"], async ({ command, flags, planOptions }) => {
10
+ const { loaded, plan } = await loadIdentityClaimPlan({
11
+ flags,
12
+ planOptions,
13
+ });
14
+ if (command === "identity plan" || !flags.identityConfirmation) {
15
+ const data = { mode: "preview", plan };
16
+ return renderedCommand({ data, render: renderIdentityPlan });
17
+ }
18
+ const runtimeEnvironment = parseEnv(await readFile(loaded.paths.applicationEnvironment, "utf8"));
19
+ if (!runtimeEnvironment.DATABASE_URL) {
20
+ throw new Error("The local runtime environment has no DATABASE_URL.");
21
+ }
22
+ const needsStorageTransfer = plan.identity.assets.some((asset) => asset.rewritePath);
23
+ if (needsStorageTransfer &&
24
+ (!runtimeEnvironment.SUPABASE_URL ||
25
+ !runtimeEnvironment.SUPABASE_SERVICE_ROLE_KEY)) {
26
+ throw new Error("Identity asset transfer requires local Supabase URL and service-role credentials.");
27
+ }
28
+ const storageTransfer = needsStorageTransfer
29
+ ? createSupabaseStorageTransfer({
30
+ url: runtimeEnvironment.SUPABASE_URL,
31
+ serviceRoleKey: runtimeEnvironment.SUPABASE_SERVICE_ROLE_KEY,
32
+ })
33
+ : undefined;
34
+ const result = await applyIdentityClaim({
35
+ plan,
36
+ confirmation: flags.identityConfirmation,
37
+ connectionString: runtimeEnvironment.DATABASE_URL,
38
+ ...(storageTransfer === undefined ? {} : { storageTransfer }),
39
+ });
40
+ const data = { mode: "written", plan, result };
41
+ return renderedCommand({ data, render: renderIdentityPlan });
42
+ }),
43
+ ];
@@ -10,7 +10,7 @@ interface InitOptions {
10
10
  export declare const runRehearsalInit: ({ projectRoot, flags }: InitOptions) => Promise<{
11
11
  mode: string;
12
12
  destination: string;
13
- detected: import("../project/configuration.mjs").DetectedProject;
13
+ detected: import("../project/project_detection.mjs").DetectedProject;
14
14
  source: string;
15
15
  availableOptions: {
16
16
  path: string;