@rehearsal-db/core 0.1.0-beta.9 → 0.1.0-rc.2

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 (247) hide show
  1. package/CHANGELOG.md +319 -1
  2. package/COMPATIBILITY.md +15 -0
  3. package/README.md +73 -16
  4. package/dist/package.json +100 -0
  5. package/dist/scripts/install/create_root_config.d.mts +3 -0
  6. package/dist/scripts/install/create_root_config.mjs +13 -0
  7. package/dist/scripts/runtime/manage_database.d.mts +6 -0
  8. package/{scripts → dist/scripts}/runtime/manage_database.mjs +6 -8
  9. package/dist/src/application/interactive_task.d.mts +30 -0
  10. package/dist/src/application/interactive_task.mjs +83 -0
  11. package/dist/src/application/session.d.mts +97 -0
  12. package/dist/src/application/session.mjs +380 -0
  13. package/dist/src/baseline/artifact.d.mts +119 -0
  14. package/dist/src/baseline/artifact.mjs +522 -0
  15. package/dist/src/baseline/builder.d.mts +19 -0
  16. package/dist/src/baseline/builder.mjs +142 -0
  17. package/dist/src/baseline/input_discovery.d.mts +13 -0
  18. package/dist/src/baseline/input_discovery.mjs +149 -0
  19. package/dist/src/baseline/policy_review.d.mts +56 -0
  20. package/dist/src/baseline/policy_review.mjs +73 -0
  21. package/dist/src/baseline/preparation.d.mts +63 -0
  22. package/dist/src/baseline/preparation.mjs +295 -0
  23. package/dist/src/baseline/privacy_contract.d.mts +147 -0
  24. package/dist/src/baseline/privacy_contract.mjs +2 -0
  25. package/dist/src/baseline/privacy_dependency_graph.d.mts +7 -0
  26. package/dist/src/baseline/privacy_dependency_graph.mjs +110 -0
  27. package/dist/src/baseline/privacy_engine.d.mts +5 -0
  28. package/dist/src/baseline/privacy_engine.mjs +4 -0
  29. package/dist/src/baseline/privacy_key.d.mts +7 -0
  30. package/dist/src/baseline/privacy_key.mjs +42 -0
  31. package/dist/src/baseline/privacy_pipeline.d.mts +7 -0
  32. package/dist/src/baseline/privacy_pipeline.mjs +127 -0
  33. package/dist/src/baseline/privacy_policy.d.mts +6 -0
  34. package/dist/src/baseline/privacy_policy.mjs +237 -0
  35. package/dist/src/baseline/privacy_recipe_validation.d.mts +5 -0
  36. package/dist/src/baseline/privacy_recipe_validation.mjs +289 -0
  37. package/dist/src/baseline/privacy_rule_registry.d.mts +18 -0
  38. package/dist/src/baseline/privacy_rule_registry.mjs +143 -0
  39. package/dist/src/baseline/privacy_structured_json.d.mts +66 -0
  40. package/dist/src/baseline/privacy_structured_json.mjs +464 -0
  41. package/dist/src/baseline/privacy_transforms.d.mts +18 -0
  42. package/dist/src/baseline/privacy_transforms.mjs +142 -0
  43. package/dist/src/baseline/privacy_validation_shared.d.mts +31 -0
  44. package/dist/src/baseline/privacy_validation_shared.mjs +127 -0
  45. package/dist/src/baseline/sanitization_policy.d.mts +69 -0
  46. package/dist/src/baseline/sanitization_policy.mjs +227 -0
  47. package/dist/src/baseline/schema_snapshot.d.mts +19 -0
  48. package/dist/src/baseline/schema_snapshot.mjs +79 -0
  49. package/dist/src/cli/application_command_handlers.d.mts +8 -0
  50. package/dist/src/cli/application_command_handlers.mjs +52 -0
  51. package/dist/src/cli/arguments.d.mts +38 -0
  52. package/dist/src/cli/arguments.mjs +130 -0
  53. package/dist/src/cli/basic_command_handlers.d.mts +4 -0
  54. package/dist/src/cli/basic_command_handlers.mjs +57 -0
  55. package/dist/src/cli/cli_commands.d.mts +14 -0
  56. package/dist/src/cli/cli_commands.mjs +34 -0
  57. package/dist/src/cli/command_contract.d.mts +57 -0
  58. package/dist/src/cli/command_contract.mjs +39 -0
  59. package/dist/src/cli/command_output.d.mts +12 -0
  60. package/dist/src/cli/command_output.mjs +20 -0
  61. package/dist/src/cli/command_registry.d.mts +7 -0
  62. package/dist/src/cli/command_registry.mjs +14 -0
  63. package/dist/src/cli/guided.d.mts +53 -0
  64. package/dist/src/cli/guided.mjs +727 -0
  65. package/dist/src/cli/identity_command_handlers.d.mts +12 -0
  66. package/dist/src/cli/identity_command_handlers.mjs +239 -0
  67. package/dist/src/cli/identity_renderer.d.mts +8 -0
  68. package/dist/src/cli/identity_renderer.mjs +33 -0
  69. package/dist/src/cli/init_command.d.mts +21 -0
  70. package/dist/src/cli/init_command.mjs +90 -0
  71. package/dist/src/cli/inspection_command_handlers.d.mts +3 -0
  72. package/dist/src/cli/inspection_command_handlers.mjs +79 -0
  73. package/dist/src/cli/project_command_handlers.d.mts +11 -0
  74. package/dist/src/cli/project_command_handlers.mjs +92 -0
  75. package/dist/src/cli/project_commands.d.mts +283 -0
  76. package/dist/src/cli/project_commands.mjs +192 -0
  77. package/dist/src/cli/rehearsal.d.mts +7 -0
  78. package/dist/src/cli/rehearsal.mjs +190 -0
  79. package/dist/src/cli/renderers.d.mts +299 -0
  80. package/dist/src/cli/renderers.mjs +554 -0
  81. package/dist/src/cli/runtime_action_handlers.d.mts +12 -0
  82. package/dist/src/cli/runtime_action_handlers.mjs +201 -0
  83. package/dist/src/cli/runtime_commands.d.mts +294 -0
  84. package/dist/src/cli/runtime_commands.mjs +619 -0
  85. package/dist/src/cli/source_command_handlers.d.mts +8 -0
  86. package/dist/src/cli/source_command_handlers.mjs +62 -0
  87. package/dist/src/cli/source_commands.d.mts +436 -0
  88. package/dist/src/cli/source_commands.mjs +301 -0
  89. package/dist/src/cli/terminal.d.mts +59 -0
  90. package/dist/src/cli/terminal.mjs +230 -0
  91. package/dist/src/identity/claim.d.mts +5 -0
  92. package/dist/src/identity/claim.mjs +4 -0
  93. package/dist/src/identity/claim_contract.d.mts +135 -0
  94. package/dist/src/identity/claim_contract.mjs +6 -0
  95. package/dist/src/identity/claim_executor.d.mts +10 -0
  96. package/dist/src/identity/claim_executor.mjs +358 -0
  97. package/dist/src/identity/claim_identity_lookup.d.mts +23 -0
  98. package/dist/src/identity/claim_identity_lookup.mjs +134 -0
  99. package/dist/src/identity/claim_plan.d.mts +8 -0
  100. package/dist/src/identity/claim_plan.mjs +61 -0
  101. package/dist/src/identity/claim_policy.d.mts +3 -0
  102. package/dist/src/identity/claim_policy.mjs +377 -0
  103. package/dist/src/identity/claim_sql.d.mts +10 -0
  104. package/dist/src/identity/claim_sql.mjs +46 -0
  105. package/dist/src/identity/claim_verification.d.mts +13 -0
  106. package/dist/src/identity/claim_verification.mjs +35 -0
  107. package/dist/src/identity/connect.d.mts +30 -0
  108. package/dist/src/identity/connect.mjs +111 -0
  109. package/dist/src/identity/provider_configuration.d.mts +37 -0
  110. package/dist/src/identity/provider_configuration.mjs +132 -0
  111. package/dist/src/identity/storage.d.mts +21 -0
  112. package/dist/src/identity/storage.mjs +122 -0
  113. package/dist/src/index.d.mts +4 -0
  114. package/dist/src/index.mjs +3 -0
  115. package/dist/src/project/configuration.d.mts +13 -0
  116. package/dist/src/project/configuration.mjs +9 -0
  117. package/dist/src/project/configuration_contract.d.mts +125 -0
  118. package/dist/src/project/configuration_contract.mjs +27 -0
  119. package/dist/src/project/configuration_discovery.d.mts +7 -0
  120. package/dist/src/project/configuration_discovery.mjs +37 -0
  121. package/dist/src/project/configuration_normalization.d.mts +107 -0
  122. package/dist/src/project/configuration_normalization.mjs +302 -0
  123. package/dist/src/project/configuration_rendering.d.mts +14 -0
  124. package/dist/src/project/configuration_rendering.mjs +228 -0
  125. package/dist/src/project/configuration_resolution.d.mts +125 -0
  126. package/dist/src/project/configuration_resolution.mjs +81 -0
  127. package/dist/src/project/configuration_schema.d.mts +22 -0
  128. package/dist/src/project/configuration_schema.mjs +160 -0
  129. package/dist/src/project/configuration_validation.d.mts +22 -0
  130. package/dist/src/project/configuration_validation.mjs +136 -0
  131. package/dist/src/project/install_scaffold.d.mts +42 -0
  132. package/dist/src/project/install_scaffold.mjs +263 -0
  133. package/dist/src/project/project_detection.d.mts +14 -0
  134. package/dist/src/project/project_detection.mjs +47 -0
  135. package/dist/src/project/setup.d.mts +87 -0
  136. package/dist/src/project/setup.mjs +414 -0
  137. package/dist/src/project/support_report.d.mts +88 -0
  138. package/dist/src/project/support_report.mjs +83 -0
  139. package/dist/src/runtime/cleanup.d.mts +85 -0
  140. package/dist/src/runtime/cleanup.mjs +311 -0
  141. package/dist/src/runtime/lifecycle_engine.d.mts +63 -0
  142. package/dist/src/runtime/lifecycle_engine.mjs +114 -0
  143. package/dist/src/runtime/migration_history.d.mts +51 -0
  144. package/dist/src/runtime/migration_history.mjs +170 -0
  145. package/dist/src/runtime/plan.d.mts +134 -0
  146. package/dist/src/runtime/plan.mjs +640 -0
  147. package/dist/src/runtime/policy.d.mts +66 -0
  148. package/dist/src/runtime/policy.mjs +369 -0
  149. package/dist/src/runtime/restore.d.mts +22 -0
  150. package/dist/src/runtime/restore.mjs +276 -0
  151. package/dist/src/runtime/service_environment.d.mts +11 -0
  152. package/dist/src/runtime/service_environment.mjs +58 -0
  153. package/dist/src/runtime/topology.d.mts +51 -0
  154. package/dist/src/runtime/topology.mjs +128 -0
  155. package/dist/src/shared/diagnostics.d.mts +97 -0
  156. package/dist/src/shared/diagnostics.mjs +170 -0
  157. package/dist/src/shared/human_output.d.mts +2 -0
  158. package/{src → dist/src}/shared/human_output.mjs +1 -3
  159. package/dist/src/shared/node_runtime.d.mts +14 -0
  160. package/dist/src/shared/node_runtime.mjs +25 -0
  161. package/dist/src/shared/operation_guard.d.mts +22 -0
  162. package/dist/src/shared/operation_guard.mjs +150 -0
  163. package/dist/src/shared/process_environment.d.mts +15 -0
  164. package/dist/src/shared/process_environment.mjs +50 -0
  165. package/dist/src/source/access.d.mts +165 -0
  166. package/dist/src/source/access.mjs +602 -0
  167. package/dist/src/source/asset_transfer.d.mts +27 -0
  168. package/dist/src/source/asset_transfer.mjs +202 -0
  169. package/dist/src/source/baseline.d.mts +66 -0
  170. package/dist/src/source/baseline.mjs +372 -0
  171. package/dist/src/source/external_reader.d.mts +42 -0
  172. package/dist/src/source/external_reader.mjs +278 -0
  173. package/dist/src/source/postgresql_access.d.mts +86 -0
  174. package/dist/src/source/postgresql_access.mjs +454 -0
  175. package/dist/src/source/privacy_paths.d.mts +10 -0
  176. package/dist/src/source/privacy_paths.mjs +27 -0
  177. package/dist/src/targets/driver.d.mts +2 -0
  178. package/dist/src/targets/driver.mjs +13 -0
  179. package/dist/src/targets/postgresql.d.mts +5 -0
  180. package/dist/src/targets/postgresql.mjs +620 -0
  181. package/dist/src/targets/supabase.d.mts +8 -0
  182. package/dist/src/targets/supabase.mjs +629 -0
  183. package/dist/src/targets/supabase_environment.d.mts +54 -0
  184. package/dist/src/targets/supabase_environment.mjs +184 -0
  185. package/dist/src/targets/target.d.mts +33 -0
  186. package/dist/src/targets/target.mjs +39 -0
  187. package/docs/README.md +35 -16
  188. package/docs/architecture.md +135 -10
  189. package/docs/commands.md +53 -15
  190. package/docs/configuration.md +186 -31
  191. package/docs/getting-started.md +66 -9
  192. package/docs/index.md +67 -0
  193. package/docs/production-source.md +175 -17
  194. package/docs/releasing.md +24 -12
  195. package/docs/roadmap.md +3 -2
  196. package/docs/runtime-lifecycle.md +99 -0
  197. package/docs/runtime-policies.md +80 -7
  198. package/docs/sanitization.md +572 -4
  199. package/docs/security-model.md +8 -0
  200. package/docs/stability.md +70 -0
  201. package/docs/standalone-workflow.md +36 -13
  202. package/docs/troubleshooting.md +82 -10
  203. package/docs/tutorial.md +7 -1
  204. package/docs/typescript.md +134 -0
  205. package/package.json +44 -29
  206. package/types/configuration.d.mts +5 -0
  207. package/src/README.md +0 -17
  208. package/src/application/session.mjs +0 -313
  209. package/src/baseline/artifact.mjs +0 -661
  210. package/src/baseline/builder.mjs +0 -164
  211. package/src/baseline/input_discovery.mjs +0 -155
  212. package/src/baseline/policy_review.mjs +0 -92
  213. package/src/baseline/preparation.mjs +0 -323
  214. package/src/baseline/privacy_engine.mjs +0 -413
  215. package/src/baseline/sanitization_policy.mjs +0 -299
  216. package/src/baseline/schema_snapshot.mjs +0 -113
  217. package/src/cli/arguments.mjs +0 -120
  218. package/src/cli/guided.mjs +0 -807
  219. package/src/cli/rehearsal.mjs +0 -933
  220. package/src/cli/renderers.mjs +0 -584
  221. package/src/cli/runtime_commands.mjs +0 -553
  222. package/src/cli/source_commands.mjs +0 -326
  223. package/src/cli/terminal.mjs +0 -275
  224. package/src/identity/claim.mjs +0 -975
  225. package/src/identity/storage.mjs +0 -165
  226. package/src/project/configuration.d.mts +0 -153
  227. package/src/project/configuration.mjs +0 -1197
  228. package/src/project/setup.mjs +0 -379
  229. package/src/project/support_report.mjs +0 -105
  230. package/src/runtime/cleanup.mjs +0 -381
  231. package/src/runtime/migration_history.mjs +0 -220
  232. package/src/runtime/plan.mjs +0 -685
  233. package/src/runtime/policy.mjs +0 -438
  234. package/src/runtime/restore.mjs +0 -329
  235. package/src/runtime/service_environment.mjs +0 -82
  236. package/src/runtime/topology.mjs +0 -177
  237. package/src/shared/diagnostics.mjs +0 -199
  238. package/src/shared/operation_guard.mjs +0 -162
  239. package/src/shared/process_environment.mjs +0 -68
  240. package/src/source/access.mjs +0 -578
  241. package/src/source/asset_transfer.mjs +0 -177
  242. package/src/source/baseline.mjs +0 -446
  243. package/src/source/postgresql_access.mjs +0 -480
  244. package/src/targets/postgresql.mjs +0 -808
  245. package/src/targets/supabase.mjs +0 -830
  246. package/src/targets/supabase_environment.mjs +0 -229
  247. package/src/targets/target.mjs +0 -66
@@ -0,0 +1,301 @@
1
+ /**
2
+ * CLI orchestration for temporary production-source access and safe baseline
3
+ * replacement. Domain validation remains in src/source and src/baseline.
4
+ */
5
+ import { createHash } from "node:crypto";
6
+ import { readFile, stat } from "node:fs/promises";
7
+ import { join, relative } from "node:path";
8
+ import { activateBaselineGeneration, planBaselineGenerationPrune, planBaselineReplacementPrune, pruneBaselineGenerations, verifyActiveBaseline, } from "../baseline/artifact.mjs";
9
+ import { createPrivacyEngine, readPrivacyKey, } from "../baseline/privacy_engine.mjs";
10
+ import { loadRehearsalConfig } from "../project/configuration.mjs";
11
+ import { createSourceAccessPlan, validateSourceAccessPolicy, } from "../source/access.mjs";
12
+ import { streamApprovedSupabaseAssets } from "../source/asset_transfer.mjs";
13
+ import { refreshPostgresqlBaseline } from "../source/baseline.mjs";
14
+ import { resolvePrivacyMappedAssets } from "../source/privacy_paths.mjs";
15
+ import { applyPostgresqlSourceAccess, planPostgresqlSourceAccessRetirement, resolvePostgresqlSourceStatePaths, retirePostgresqlSourceAccess, } from "../source/postgresql_access.mjs";
16
+ import { loadRuntimeTopology } from "../runtime/topology.mjs";
17
+ const parseSourceAccessReceipt = (source) => {
18
+ const parsed = JSON.parse(source);
19
+ if (typeof parsed.targetFingerprint !== "string" ||
20
+ typeof parsed.planDigest !== "string" ||
21
+ typeof parsed.expiresAt !== "string") {
22
+ throw new Error("The source-access receipt has an invalid shape.");
23
+ }
24
+ return parsed;
25
+ };
26
+ export const loadPreparationContext = async (planOptions) => {
27
+ const loaded = await loadRehearsalConfig(planOptions);
28
+ if (!loaded.config.preparation) {
29
+ throw new Error("Source preparation is not configured. Review docs/standalone-workflow.md, then enable config.preparation.");
30
+ }
31
+ if (!loaded.paths.sourcePolicy || !loaded.paths.privacyKey) {
32
+ throw new Error("Source preparation paths are incomplete.");
33
+ }
34
+ const sourcePolicyBytes = await readFile(loaded.paths.sourcePolicy);
35
+ const sourcePolicy = validateSourceAccessPolicy(JSON.parse(sourcePolicyBytes.toString("utf8")));
36
+ return { loaded, sourcePolicy, sourcePolicyBytes };
37
+ };
38
+ export const runSourceAccess = async ({ flags, planOptions, }) => {
39
+ const { loaded, sourcePolicy } = await loadPreparationContext(planOptions);
40
+ const plan = createSourceAccessPlan({ policy: sourcePolicy });
41
+ if (!flags.sourceAccessConfirmation)
42
+ return { mode: "preview", plan };
43
+ const applied = await applyPostgresqlSourceAccess({
44
+ plan,
45
+ confirmation: flags.sourceAccessConfirmation,
46
+ projectRoot: loaded.projectRoot,
47
+ artifactRoot: loaded.paths.artifactDirectory,
48
+ });
49
+ return {
50
+ mode: "written",
51
+ plan,
52
+ receipt: applied.receipt,
53
+ credentialFile: relative(loaded.projectRoot, applied.credentialPath),
54
+ };
55
+ };
56
+ export const runSourceRetirement = async ({ flags, planOptions, }) => {
57
+ const { loaded, sourcePolicy } = await loadPreparationContext(planOptions);
58
+ const plan = await planPostgresqlSourceAccessRetirement({
59
+ projectRoot: loaded.projectRoot,
60
+ artifactRoot: loaded.paths.artifactDirectory,
61
+ targetFingerprint: sourcePolicy.targetFingerprint,
62
+ });
63
+ if (!flags.sourceRetirementConfirmation)
64
+ return { mode: "preview", plan };
65
+ await retirePostgresqlSourceAccess({
66
+ plan,
67
+ confirmation: flags.sourceRetirementConfirmation,
68
+ ...(sourcePolicy.administratorEnvironmentVariable
69
+ ? {
70
+ administratorEnvironmentVariable: sourcePolicy.administratorEnvironmentVariable,
71
+ }
72
+ : {}),
73
+ projectRoot: loaded.projectRoot,
74
+ artifactRoot: loaded.paths.artifactDirectory,
75
+ credentialFile: sourcePolicy.reader.credentialFile,
76
+ });
77
+ return { mode: "written", plan };
78
+ };
79
+ const readSourceReaderCredential = async (path) => {
80
+ const details = await stat(path);
81
+ if (!details.isFile() || (details.mode & 0o077) !== 0) {
82
+ throw new Error("Source reader credential must be an owner-only regular file.");
83
+ }
84
+ const lines = (await readFile(path, "utf8")).split(/\r?\n/u).filter(Boolean);
85
+ if (lines.length !== 1 ||
86
+ !lines[0]?.startsWith("REHEARSAL_SOURCE_DATABASE_URL=")) {
87
+ throw new Error("Source reader credential file has an invalid shape.");
88
+ }
89
+ const value = lines[0].slice("REHEARSAL_SOURCE_DATABASE_URL=".length);
90
+ if (!value)
91
+ throw new Error("Source reader credential file is empty.");
92
+ return value;
93
+ };
94
+ export const runSourceBaselineRefresh = async ({ planOptions, }) => {
95
+ const { loaded, sourcePolicy } = await loadPreparationContext(planOptions);
96
+ const preparation = loaded.config.preparation;
97
+ const privacyKeyPath = loaded.paths.privacyKey;
98
+ if (!preparation || !privacyKeyPath) {
99
+ throw new Error("Source preparation configuration is incomplete.");
100
+ }
101
+ const { credentialPath } = resolvePostgresqlSourceStatePaths({
102
+ projectRoot: loaded.projectRoot,
103
+ artifactRoot: loaded.paths.artifactDirectory,
104
+ credentialFile: sourcePolicy.reader.credentialFile,
105
+ });
106
+ const [sourceAccessReceipt, privacyPolicyBytes, privacyKey, connectionString,] = await Promise.all([
107
+ readFile(join(loaded.paths.artifactDirectory, "source-access-receipt.json"), "utf8").then(parseSourceAccessReceipt),
108
+ readFile(loaded.paths.sanitizationPolicy),
109
+ readPrivacyKey(privacyKeyPath),
110
+ readSourceReaderCredential(credentialPath),
111
+ ]);
112
+ const privacyPolicy = JSON.parse(privacyPolicyBytes.toString("utf8"));
113
+ const assetPrivacyEngine = createPrivacyEngine({
114
+ policy: privacyPolicy,
115
+ key: privacyKey,
116
+ });
117
+ const assetDeclarations = resolvePrivacyMappedAssets({
118
+ sourcePolicy,
119
+ privacyPolicy,
120
+ });
121
+ return refreshPostgresqlBaseline({
122
+ connectionString,
123
+ sourcePolicy,
124
+ sourceAccessReceipt,
125
+ privacyPolicyBytes,
126
+ privacyKey,
127
+ artifactRoot: loaded.paths.artifactDirectory,
128
+ migrationDirectory: loaded.paths.migrationDirectory,
129
+ limits: {
130
+ batchRows: preparation.batchRows,
131
+ maximumRows: preparation.maximumRows,
132
+ maximumBytes: preparation.maximumBytes,
133
+ diskHeadroomBytes: preparation.diskHeadroomBytes,
134
+ },
135
+ assets: sourcePolicy.assetReader
136
+ ? streamApprovedSupabaseAssets({
137
+ baseUrl: process.env[sourcePolicy.assetReader.baseUrlEnvironmentVariable] ??
138
+ "",
139
+ token: process.env[sourcePolicy.assetReader.tokenEnvironmentVariable] ??
140
+ "",
141
+ declarations: assetDeclarations,
142
+ maximumObjects: sourcePolicy.assetReader.maximumObjects,
143
+ maximumObjectBytes: sourcePolicy.assetReader.maximumObjectBytes,
144
+ maximumTotalBytes: sourcePolicy.assetReader.maximumTotalBytes,
145
+ pathMapper: ({ mapping, value }) => assetPrivacyEngine.remapPath({ mapping, value }),
146
+ })
147
+ : [],
148
+ });
149
+ };
150
+ export const buildRefreshWorkflowPlan = async (planOptions) => {
151
+ const [{ loaded, sourcePolicy, sourcePolicyBytes }, topology] = await Promise.all([
152
+ loadPreparationContext(planOptions),
153
+ loadRuntimeTopology(planOptions),
154
+ ]);
155
+ const hashFile = async (path) => path
156
+ ? createHash("sha256")
157
+ .update(await readFile(path))
158
+ .digest("hex")
159
+ : null;
160
+ const [currentBaseline, sourceAccessReceipt, replacementCleanup, privacyPolicySha256, runtimeConfigurations,] = await Promise.all([
161
+ verifyActiveBaseline({ artifactRoot: loaded.paths.artifactDirectory }),
162
+ readFile(join(loaded.paths.artifactDirectory, "source-access-receipt.json"), "utf8").then(parseSourceAccessReceipt),
163
+ planBaselineReplacementPrune({
164
+ artifactRoot: loaded.paths.artifactDirectory,
165
+ retain: loaded.config.cleanup.retainBaselineGenerations,
166
+ }),
167
+ hashFile(loaded.paths.sanitizationPolicy),
168
+ Promise.all(topology.targets.map(async (target) => ({
169
+ target: target.name,
170
+ configSha256: await hashFile(target.configPath),
171
+ runtimePolicySha256: await hashFile(target.paths.runtimePolicy),
172
+ runtimeAdapterSha256: await hashFile(target.paths.runtimeAdapter),
173
+ localServiceConfigSha256: await hashFile(target.paths.rehearsalConfig),
174
+ }))),
175
+ ]);
176
+ if (sourceAccessReceipt.targetFingerprint !== sourcePolicy.targetFingerprint ||
177
+ !/^[a-f0-9]{64}$/u.test(sourceAccessReceipt.planDigest ?? "")) {
178
+ throw new Error("The source-access receipt does not match the selected refresh policy.");
179
+ }
180
+ const sourceReaderExpiry = new Date(sourceAccessReceipt.expiresAt).valueOf();
181
+ if (!Number.isFinite(sourceReaderExpiry) ||
182
+ sourceReaderExpiry <= Date.now()) {
183
+ throw new Error("The temporary source reader has expired. Preview and apply source access again before refreshing.");
184
+ }
185
+ const review = {
186
+ planVersion: 1,
187
+ operation: "refresh-and-replace-local-copy",
188
+ currentBaseline: currentBaseline.generationId,
189
+ source: {
190
+ targetFingerprint: sourcePolicy.targetFingerprint,
191
+ accessPlanDigest: sourceAccessReceipt.planDigest,
192
+ readerExpiresAt: sourceAccessReceipt.expiresAt,
193
+ sourcePolicySha256: createHash("sha256")
194
+ .update(sourcePolicyBytes)
195
+ .digest("hex"),
196
+ privacyPolicySha256,
197
+ },
198
+ runtimeConfigurations,
199
+ runtimeTargets: topology.targets.map((target) => target.name),
200
+ dependentPreparations: topology.dependents.flatMap((target) => target.declaration?.prepareCommand
201
+ ? [{ target: target.name, command: target.declaration.prepareCommand }]
202
+ : []),
203
+ retention: {
204
+ generations: loaded.config.cleanup.retainBaselineGenerations,
205
+ removeAfterReplacement: replacementCleanup.removedAfterReplacement,
206
+ keepFromCurrent: replacementCleanup.retainedAfterReplacement,
207
+ },
208
+ steps: [
209
+ "build and verify a new immutable baseline beside the current baseline",
210
+ "activate the new baseline only after verification",
211
+ "discard runtime edits and reset the complete local runtime stack",
212
+ "run configured dependent preparation commands",
213
+ "remove only the old baseline generations listed in this plan",
214
+ ],
215
+ };
216
+ return {
217
+ loaded,
218
+ topology,
219
+ plan: {
220
+ review,
221
+ digest: createHash("sha256")
222
+ .update(`${JSON.stringify(review, null, "\t")}\n`)
223
+ .digest("hex"),
224
+ },
225
+ };
226
+ };
227
+ export const createRefreshWorkflow = ({ runRuntimeStack, prepareDependentTargets, topologyCommandEnvironment, buildPlan = buildRefreshWorkflowPlan, refreshBaseline = runSourceBaselineRefresh, }) => async ({ flags, planOptions, session, }) => {
228
+ const context = await buildPlan(planOptions);
229
+ if (!flags.refreshConfirmation) {
230
+ return { mode: "preview", plan: context.plan };
231
+ }
232
+ if (flags.refreshConfirmation !== context.plan.digest) {
233
+ throw new Error(`Refresh confirmation does not match this exact plan. Expected ${context.plan.digest}. Nothing changed.`);
234
+ }
235
+ let refreshed;
236
+ let runtime;
237
+ let preparations = [];
238
+ try {
239
+ refreshed = await refreshBaseline({ planOptions });
240
+ const stack = await runRuntimeStack({
241
+ command: "reset",
242
+ flags,
243
+ planOptions,
244
+ session,
245
+ });
246
+ runtime = stack.runtime;
247
+ preparations = prepareDependentTargets({
248
+ topology: stack.topology,
249
+ environment: topologyCommandEnvironment(stack.topology),
250
+ });
251
+ }
252
+ catch (error) {
253
+ if (refreshed) {
254
+ await activateBaselineGeneration({
255
+ artifactRoot: context.loaded.paths.artifactDirectory,
256
+ generationId: context.plan.review.currentBaseline,
257
+ });
258
+ try {
259
+ const rollback = await runRuntimeStack({
260
+ command: "reset",
261
+ flags,
262
+ planOptions,
263
+ session,
264
+ });
265
+ prepareDependentTargets({
266
+ topology: rollback.topology,
267
+ environment: topologyCommandEnvironment(rollback.topology),
268
+ });
269
+ }
270
+ catch (rollbackError) {
271
+ throw new AggregateError([error, rollbackError], "The refreshed runtime failed and the previous local runtime could not be restored automatically. The previous immutable baseline is active; run rehearsal reset after correcting the reported problem.");
272
+ }
273
+ throw new Error("The refreshed runtime failed verification. Rehearsal restored the previous baseline and local runtime.", { cause: error });
274
+ }
275
+ throw error;
276
+ }
277
+ let cleanup;
278
+ try {
279
+ const result = await pruneBaselineGenerations({
280
+ artifactRoot: context.loaded.paths.artifactDirectory,
281
+ retain: context.plan.review.retention.generations,
282
+ expectedRemoved: context.plan.review.retention.removeAfterReplacement,
283
+ });
284
+ cleanup = { state: "complete", removed: result.removed };
285
+ }
286
+ catch {
287
+ const remaining = await planBaselineGenerationPrune({
288
+ artifactRoot: context.loaded.paths.artifactDirectory,
289
+ retain: context.plan.review.retention.generations,
290
+ }).catch(() => ({ removed: [] }));
291
+ cleanup = { state: "incomplete", remaining: remaining.removed.length };
292
+ }
293
+ return {
294
+ mode: "written",
295
+ plan: context.plan,
296
+ refreshed,
297
+ runtime,
298
+ preparations,
299
+ cleanup,
300
+ };
301
+ };
@@ -0,0 +1,59 @@
1
+ /** Terminal presentation and prompt primitives shared by guided CLI workflows. */
2
+ export interface TerminalFlags {
3
+ readonly plain?: boolean;
4
+ readonly json?: boolean;
5
+ }
6
+ export interface ChoiceOption<Value = unknown> {
7
+ readonly label: string;
8
+ readonly hint?: string;
9
+ readonly command?: string;
10
+ readonly value?: Value;
11
+ }
12
+ interface ValueChoiceOption<Value> extends ChoiceOption<Value> {
13
+ readonly value: Value;
14
+ }
15
+ type PathValidator = (value: string) => string | undefined;
16
+ export declare const terminalStyle: (flags: TerminalFlags, code: string, value: string) => string;
17
+ export declare const isHumanTerminal: (flags: TerminalFlags) => boolean;
18
+ export declare const useStyledPrompts: (flags: TerminalFlags) => boolean;
19
+ export declare const formatDuration: (durationMs: number) => string;
20
+ export declare const promptForChoice: <Option extends ChoiceOption>({ message, options, flags, optionsAlreadyShown, }: {
21
+ message: string;
22
+ options: readonly Option[];
23
+ flags?: TerminalFlags;
24
+ optionsAlreadyShown?: boolean;
25
+ }) => Promise<Option | undefined>;
26
+ export declare function promptForConfirmation(message: string, flags: TerminalFlags, options: {
27
+ cancelValue: null;
28
+ }): Promise<boolean | null>;
29
+ export declare function promptForConfirmation(message: string, flags?: TerminalFlags, options?: {
30
+ cancelValue?: boolean;
31
+ }): Promise<boolean>;
32
+ export declare const promptForPath: ({ message, defaultValue, optional, flags, validate, }: {
33
+ message: string;
34
+ defaultValue?: string;
35
+ optional?: boolean;
36
+ flags?: TerminalFlags;
37
+ validate?: PathValidator;
38
+ }) => Promise<string | undefined>;
39
+ export declare const promptForSelection: <Value>({ message, options, flags, }: {
40
+ message: string;
41
+ options: readonly ValueChoiceOption<Value>[];
42
+ flags?: TerminalFlags;
43
+ }) => Promise<Value | undefined>;
44
+ export declare const promptForDiscoveredPath: ({ message, candidates, optional, flags, }: {
45
+ message: string;
46
+ candidates: readonly string[];
47
+ optional?: boolean;
48
+ flags?: TerminalFlags;
49
+ }) => Promise<string | undefined>;
50
+ export declare const promptForMultipleChoice: ({ message, options, initialValues, flags, }: {
51
+ message: string;
52
+ options: readonly ValueChoiceOption<string>[];
53
+ initialValues?: readonly string[];
54
+ flags?: TerminalFlags;
55
+ }) => Promise<string[] | undefined>;
56
+ export declare const installGuidedExitShortcut: (flags: TerminalFlags, { beforeExit }?: {
57
+ beforeExit?: () => void;
58
+ }) => (() => void);
59
+ export {};
@@ -0,0 +1,230 @@
1
+ /** Terminal presentation and prompt primitives shared by guided CLI workflows. */
2
+ import { createInterface } from "node:readline/promises";
3
+ import * as prompts from "@clack/prompts";
4
+ const useColor = (flags) => !flags.plain &&
5
+ process.stdout.isTTY &&
6
+ process.env.NO_COLOR === undefined &&
7
+ process.env.TERM !== "dumb";
8
+ export const terminalStyle = (flags, code, value) => (useColor(flags) ? `\u001B[${code}m${value}\u001B[0m` : value);
9
+ export const isHumanTerminal = (flags) => !flags.json && process.stdin.isTTY && process.stdout.isTTY;
10
+ export const useStyledPrompts = (flags) => isHumanTerminal(flags) && useColor(flags);
11
+ export const formatDuration = (durationMs) => durationMs < 1_000
12
+ ? `${Math.round(durationMs)}ms`
13
+ : `${(durationMs / 1_000).toFixed(1)}s`;
14
+ export const promptForChoice = async ({ message, options, flags = {}, optionsAlreadyShown = false, }) => {
15
+ if (useStyledPrompts(flags)) {
16
+ const selected = await prompts.select({
17
+ message,
18
+ maxItems: options.length,
19
+ options: options.map((option, index) => ({
20
+ value: index,
21
+ label: option.label,
22
+ ...(option.hint === undefined ? {} : { hint: option.hint }),
23
+ })),
24
+ });
25
+ return prompts.isCancel(selected)
26
+ ? options.find((option) => option.command === "exit")
27
+ : options[selected];
28
+ }
29
+ if (!optionsAlreadyShown) {
30
+ console.log([
31
+ "",
32
+ ...options.map((option, index) => ` ${index === 0 ? "›" : " "} ${index + 1}. ${option.label}${option.hint ? ` — ${option.hint}` : ""}`),
33
+ "",
34
+ ].join("\n"));
35
+ }
36
+ const prompt = createInterface({
37
+ input: process.stdin,
38
+ output: process.stdout,
39
+ });
40
+ try {
41
+ while (true) {
42
+ const answer = (await prompt.question(`${message} [1]: `)).trim();
43
+ const selected = answer === "" ? 1 : Number(answer);
44
+ if (Number.isInteger(selected) &&
45
+ selected >= 1 &&
46
+ selected <= options.length) {
47
+ return options[selected - 1];
48
+ }
49
+ console.log(`Choose a number from 1 to ${options.length}.`);
50
+ }
51
+ }
52
+ finally {
53
+ prompt.close();
54
+ }
55
+ };
56
+ export async function promptForConfirmation(message, flags = {}, { cancelValue = false } = {}) {
57
+ if (useStyledPrompts(flags)) {
58
+ const accepted = await prompts.confirm({ message, initialValue: false });
59
+ return prompts.isCancel(accepted) ? cancelValue : accepted;
60
+ }
61
+ const prompt = createInterface({
62
+ input: process.stdin,
63
+ output: process.stdout,
64
+ });
65
+ try {
66
+ const answer = (await prompt.question(`${message} (y/N) `))
67
+ .trim()
68
+ .toLowerCase();
69
+ return answer === "y" || answer === "yes";
70
+ }
71
+ finally {
72
+ prompt.close();
73
+ }
74
+ }
75
+ export const promptForPath = async ({ message, defaultValue, optional = false, flags = {}, validate, }) => {
76
+ if (useStyledPrompts(flags)) {
77
+ const answer = await prompts.text({
78
+ message,
79
+ placeholder: defaultValue ?? (optional ? "Leave blank for none" : ""),
80
+ ...(defaultValue === undefined ? {} : { defaultValue }),
81
+ validate: (value) => {
82
+ const resolved = String(value || defaultValue || "").trim();
83
+ if (!optional && !resolved)
84
+ return "Enter a value.";
85
+ return resolved && validate ? validate(resolved) : undefined;
86
+ },
87
+ });
88
+ if (prompts.isCancel(answer))
89
+ return undefined;
90
+ return String(answer || defaultValue || "").trim() || undefined;
91
+ }
92
+ const prompt = createInterface({
93
+ input: process.stdin,
94
+ output: process.stdout,
95
+ });
96
+ try {
97
+ while (true) {
98
+ const suffix = defaultValue
99
+ ? ` [${defaultValue}]`
100
+ : optional
101
+ ? ""
102
+ : " (required)";
103
+ const answer = (await prompt.question(`${message}${suffix}: `)).trim();
104
+ const value = answer || defaultValue || "";
105
+ const invalid = value && validate ? validate(value) : undefined;
106
+ if (invalid) {
107
+ console.log(invalid);
108
+ continue;
109
+ }
110
+ if (value || optional)
111
+ return value || undefined;
112
+ console.log("Enter a project-relative path.");
113
+ }
114
+ }
115
+ finally {
116
+ prompt.close();
117
+ }
118
+ };
119
+ export const promptForSelection = async ({ message, options, flags = {}, }) => {
120
+ const selected = await promptForChoice({ message, options, flags });
121
+ return selected?.value;
122
+ };
123
+ export const promptForDiscoveredPath = async ({ message, candidates, optional = false, flags = {}, }) => {
124
+ if (candidates.length === 0)
125
+ return promptForPath({ message, optional, flags });
126
+ const noneValue = "\0none";
127
+ const manualValue = "\0manual";
128
+ const selected = await promptForSelection({
129
+ message,
130
+ flags,
131
+ options: [
132
+ ...(optional
133
+ ? [
134
+ {
135
+ label: "No storage assets",
136
+ hint: "Continue without an asset manifest",
137
+ value: noneValue,
138
+ },
139
+ ]
140
+ : []),
141
+ ...candidates.map((path) => ({ label: path, value: path })),
142
+ { label: "Enter another path", value: manualValue },
143
+ ],
144
+ });
145
+ if (!selected || selected === noneValue)
146
+ return undefined;
147
+ if (selected !== manualValue)
148
+ return selected;
149
+ return promptForPath({ message, optional, flags });
150
+ };
151
+ export const promptForMultipleChoice = async ({ message, options, initialValues = [], flags = {}, }) => {
152
+ if (useStyledPrompts(flags)) {
153
+ const selected = await prompts.multiselect({
154
+ message,
155
+ options: options.map((option) => ({
156
+ value: option.value,
157
+ label: option.label,
158
+ ...(option.hint === undefined ? {} : { hint: option.hint }),
159
+ })),
160
+ initialValues: [...initialValues],
161
+ required: false,
162
+ });
163
+ return prompts.isCancel(selected) ? undefined : selected;
164
+ }
165
+ const initial = new Set(initialValues);
166
+ console.log("");
167
+ console.log(message);
168
+ for (const [index, option] of options.entries()) {
169
+ console.log(` ${index + 1}. ${option.label}${initial.has(option.value) ? " (suggested)" : ""}`);
170
+ }
171
+ const defaultNumbers = options
172
+ .map((option, index) => (initial.has(option.value) ? index + 1 : undefined))
173
+ .filter(Boolean)
174
+ .join(",");
175
+ const prompt = createInterface({
176
+ input: process.stdin,
177
+ output: process.stdout,
178
+ });
179
+ try {
180
+ while (true) {
181
+ const suffix = defaultNumbers ? ` [${defaultNumbers}]` : " [none]";
182
+ const answer = (await prompt.question(`Choose comma-separated numbers, all, or none${suffix}: `))
183
+ .trim()
184
+ .toLowerCase();
185
+ if (!answer)
186
+ return [...initialValues];
187
+ if (answer === "none")
188
+ return [];
189
+ if (answer === "all")
190
+ return options.map((option) => option.value);
191
+ const indexes = answer.split(",").map((value) => Number(value.trim()));
192
+ if (indexes.length > 0 &&
193
+ indexes.every((index) => Number.isInteger(index) && index >= 1 && index <= options.length)) {
194
+ return [...new Set(indexes)].map((index) => options[index - 1].value);
195
+ }
196
+ console.log(`Choose numbers from 1 to ${options.length}, all, or none.`);
197
+ }
198
+ }
199
+ finally {
200
+ prompt.close();
201
+ }
202
+ };
203
+ export const installGuidedExitShortcut = (flags, { beforeExit } = {}) => {
204
+ let exiting = false;
205
+ const exit = () => {
206
+ if (exiting)
207
+ return;
208
+ exiting = true;
209
+ if (process.stdin.isTTY && process.stdin.isRaw)
210
+ process.stdin.setRawMode(false);
211
+ beforeExit?.();
212
+ process.stdout.write("\u001B[?25h");
213
+ if (useStyledPrompts(flags))
214
+ prompts.outro("Rehearsal exited.");
215
+ else
216
+ console.log("\nRehearsal exited.");
217
+ process.exit(0);
218
+ };
219
+ const onInput = (chunk) => {
220
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
221
+ if (bytes.includes(0x1a))
222
+ exit();
223
+ };
224
+ process.stdin.prependListener("data", onInput);
225
+ process.prependListener("SIGTSTP", exit);
226
+ return () => {
227
+ process.stdin.removeListener("data", onInput);
228
+ process.removeListener("SIGTSTP", exit);
229
+ };
230
+ };
@@ -0,0 +1,5 @@
1
+ /** Stable public facade for local identity claim planning and execution. */
2
+ export type { IdentityClaimPlan, IdentityDeclaration, IdentityPolicy, } from "./claim_contract.mjs";
3
+ export { applyIdentityClaim } from "./claim_executor.mjs";
4
+ export { createIdentityClaimPlan } from "./claim_plan.mjs";
5
+ export { validateIdentityPolicy } from "./claim_policy.mjs";
@@ -0,0 +1,4 @@
1
+ /** Stable public facade for local identity claim planning and execution. */
2
+ export { applyIdentityClaim } from "./claim_executor.mjs";
3
+ export { createIdentityClaimPlan } from "./claim_plan.mjs";
4
+ export { validateIdentityPolicy } from "./claim_policy.mjs";