@rehearsal-db/core 0.1.0-beta.14 → 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 (236) hide show
  1. package/CHANGELOG.md +51 -1
  2. package/README.md +1 -0
  3. package/dist/package.json +101 -0
  4. package/dist/scripts/install/create_root_config.d.mts +3 -0
  5. package/dist/scripts/install/create_root_config.mjs +13 -0
  6. package/dist/scripts/runtime/manage_database.d.mts +6 -0
  7. package/{scripts → dist/scripts}/runtime/manage_database.mjs +6 -8
  8. package/dist/src/application/session.d.mts +96 -0
  9. package/dist/src/application/session.mjs +380 -0
  10. package/dist/src/baseline/artifact.d.mts +119 -0
  11. package/dist/src/baseline/artifact.mjs +522 -0
  12. package/dist/src/baseline/builder.d.mts +19 -0
  13. package/dist/src/baseline/builder.mjs +142 -0
  14. package/dist/src/baseline/input_discovery.d.mts +13 -0
  15. package/dist/src/baseline/input_discovery.mjs +149 -0
  16. package/dist/src/baseline/policy_review.d.mts +56 -0
  17. package/dist/src/baseline/policy_review.mjs +73 -0
  18. package/dist/src/baseline/preparation.d.mts +63 -0
  19. package/dist/src/baseline/preparation.mjs +295 -0
  20. package/dist/src/baseline/privacy_contract.d.mts +147 -0
  21. package/dist/src/baseline/privacy_contract.mjs +2 -0
  22. package/dist/src/baseline/privacy_dependency_graph.d.mts +7 -0
  23. package/dist/src/baseline/privacy_dependency_graph.mjs +110 -0
  24. package/dist/src/baseline/privacy_engine.d.mts +5 -0
  25. package/dist/src/baseline/privacy_engine.mjs +4 -0
  26. package/dist/src/baseline/privacy_key.d.mts +7 -0
  27. package/dist/src/baseline/privacy_key.mjs +42 -0
  28. package/dist/src/baseline/privacy_pipeline.d.mts +7 -0
  29. package/dist/src/baseline/privacy_pipeline.mjs +127 -0
  30. package/dist/src/baseline/privacy_policy.d.mts +6 -0
  31. package/dist/src/baseline/privacy_policy.mjs +237 -0
  32. package/dist/src/baseline/privacy_recipe_validation.d.mts +5 -0
  33. package/dist/src/baseline/privacy_recipe_validation.mjs +289 -0
  34. package/dist/src/baseline/privacy_rule_registry.d.mts +18 -0
  35. package/dist/src/baseline/privacy_rule_registry.mjs +143 -0
  36. package/dist/src/baseline/privacy_structured_json.d.mts +66 -0
  37. package/dist/src/baseline/privacy_structured_json.mjs +464 -0
  38. package/dist/src/baseline/privacy_transforms.d.mts +18 -0
  39. package/dist/src/baseline/privacy_transforms.mjs +142 -0
  40. package/dist/src/baseline/privacy_validation_shared.d.mts +31 -0
  41. package/dist/src/baseline/privacy_validation_shared.mjs +127 -0
  42. package/dist/src/baseline/sanitization_policy.d.mts +69 -0
  43. package/dist/src/baseline/sanitization_policy.mjs +227 -0
  44. package/dist/src/baseline/schema_snapshot.d.mts +19 -0
  45. package/dist/src/baseline/schema_snapshot.mjs +79 -0
  46. package/dist/src/cli/application_command_handlers.d.mts +8 -0
  47. package/dist/src/cli/application_command_handlers.mjs +52 -0
  48. package/dist/src/cli/arguments.d.mts +38 -0
  49. package/dist/src/cli/arguments.mjs +128 -0
  50. package/dist/src/cli/basic_command_handlers.d.mts +4 -0
  51. package/dist/src/cli/basic_command_handlers.mjs +56 -0
  52. package/dist/src/cli/cli_commands.d.mts +14 -0
  53. package/dist/src/cli/cli_commands.mjs +32 -0
  54. package/dist/src/cli/command_contract.d.mts +57 -0
  55. package/dist/src/cli/command_contract.mjs +39 -0
  56. package/dist/src/cli/command_output.d.mts +12 -0
  57. package/dist/src/cli/command_output.mjs +20 -0
  58. package/dist/src/cli/command_registry.d.mts +7 -0
  59. package/dist/src/cli/command_registry.mjs +14 -0
  60. package/dist/src/cli/guided.d.mts +53 -0
  61. package/dist/src/cli/guided.mjs +719 -0
  62. package/dist/src/cli/identity_command_handlers.d.mts +8 -0
  63. package/dist/src/cli/identity_command_handlers.mjs +43 -0
  64. package/dist/src/cli/init_command.d.mts +21 -0
  65. package/dist/src/cli/init_command.mjs +90 -0
  66. package/dist/src/cli/inspection_command_handlers.d.mts +3 -0
  67. package/dist/src/cli/inspection_command_handlers.mjs +79 -0
  68. package/dist/src/cli/project_command_handlers.d.mts +11 -0
  69. package/dist/src/cli/project_command_handlers.mjs +92 -0
  70. package/dist/src/cli/project_commands.d.mts +159 -0
  71. package/dist/src/cli/project_commands.mjs +183 -0
  72. package/dist/src/cli/rehearsal.d.mts +7 -0
  73. package/dist/src/cli/rehearsal.mjs +190 -0
  74. package/dist/src/cli/renderers.d.mts +299 -0
  75. package/dist/src/cli/renderers.mjs +554 -0
  76. package/dist/src/cli/runtime_action_handlers.d.mts +12 -0
  77. package/dist/src/cli/runtime_action_handlers.mjs +201 -0
  78. package/dist/src/cli/runtime_commands.d.mts +269 -0
  79. package/dist/src/cli/runtime_commands.mjs +601 -0
  80. package/dist/src/cli/source_command_handlers.d.mts +8 -0
  81. package/dist/src/cli/source_command_handlers.mjs +62 -0
  82. package/dist/src/cli/source_commands.d.mts +436 -0
  83. package/dist/src/cli/source_commands.mjs +301 -0
  84. package/dist/src/cli/terminal.d.mts +59 -0
  85. package/dist/src/cli/terminal.mjs +230 -0
  86. package/dist/src/identity/claim.d.mts +5 -0
  87. package/dist/src/identity/claim.mjs +4 -0
  88. package/dist/src/identity/claim_contract.d.mts +135 -0
  89. package/dist/src/identity/claim_contract.mjs +6 -0
  90. package/dist/src/identity/claim_executor.d.mts +10 -0
  91. package/dist/src/identity/claim_executor.mjs +358 -0
  92. package/dist/src/identity/claim_identity_lookup.d.mts +17 -0
  93. package/dist/src/identity/claim_identity_lookup.mjs +125 -0
  94. package/dist/src/identity/claim_plan.d.mts +8 -0
  95. package/dist/src/identity/claim_plan.mjs +61 -0
  96. package/dist/src/identity/claim_policy.d.mts +3 -0
  97. package/dist/src/identity/claim_policy.mjs +377 -0
  98. package/dist/src/identity/claim_sql.d.mts +10 -0
  99. package/dist/src/identity/claim_sql.mjs +46 -0
  100. package/dist/src/identity/claim_verification.d.mts +13 -0
  101. package/dist/src/identity/claim_verification.mjs +35 -0
  102. package/dist/src/identity/provider_configuration.d.mts +37 -0
  103. package/dist/src/identity/provider_configuration.mjs +132 -0
  104. package/dist/src/identity/storage.d.mts +21 -0
  105. package/dist/src/identity/storage.mjs +122 -0
  106. package/dist/src/index.d.mts +4 -0
  107. package/dist/src/index.mjs +3 -0
  108. package/dist/src/project/configuration.d.mts +13 -0
  109. package/dist/src/project/configuration.mjs +9 -0
  110. package/dist/src/project/configuration_contract.d.mts +125 -0
  111. package/dist/src/project/configuration_contract.mjs +27 -0
  112. package/dist/src/project/configuration_discovery.d.mts +7 -0
  113. package/dist/src/project/configuration_discovery.mjs +37 -0
  114. package/dist/src/project/configuration_normalization.d.mts +107 -0
  115. package/dist/src/project/configuration_normalization.mjs +302 -0
  116. package/dist/src/project/configuration_rendering.d.mts +14 -0
  117. package/dist/src/project/configuration_rendering.mjs +224 -0
  118. package/dist/src/project/configuration_resolution.d.mts +125 -0
  119. package/dist/src/project/configuration_resolution.mjs +81 -0
  120. package/dist/src/project/configuration_schema.d.mts +22 -0
  121. package/dist/src/project/configuration_schema.mjs +160 -0
  122. package/dist/src/project/configuration_validation.d.mts +22 -0
  123. package/dist/src/project/configuration_validation.mjs +136 -0
  124. package/dist/src/project/install_scaffold.d.mts +42 -0
  125. package/dist/src/project/install_scaffold.mjs +263 -0
  126. package/dist/src/project/project_detection.d.mts +14 -0
  127. package/dist/src/project/project_detection.mjs +47 -0
  128. package/dist/src/project/setup.d.mts +87 -0
  129. package/dist/src/project/setup.mjs +414 -0
  130. package/dist/src/project/support_report.d.mts +88 -0
  131. package/dist/src/project/support_report.mjs +83 -0
  132. package/dist/src/runtime/cleanup.d.mts +85 -0
  133. package/dist/src/runtime/cleanup.mjs +311 -0
  134. package/dist/src/runtime/lifecycle_engine.d.mts +63 -0
  135. package/dist/src/runtime/lifecycle_engine.mjs +114 -0
  136. package/dist/src/runtime/migration_history.d.mts +51 -0
  137. package/dist/src/runtime/migration_history.mjs +170 -0
  138. package/dist/src/runtime/plan.d.mts +134 -0
  139. package/dist/src/runtime/plan.mjs +637 -0
  140. package/dist/src/runtime/policy.d.mts +66 -0
  141. package/dist/src/runtime/policy.mjs +369 -0
  142. package/dist/src/runtime/restore.d.mts +22 -0
  143. package/dist/src/runtime/restore.mjs +276 -0
  144. package/dist/src/runtime/service_environment.d.mts +11 -0
  145. package/dist/src/runtime/service_environment.mjs +58 -0
  146. package/dist/src/runtime/topology.d.mts +51 -0
  147. package/dist/src/runtime/topology.mjs +128 -0
  148. package/dist/src/shared/diagnostics.d.mts +97 -0
  149. package/dist/src/shared/diagnostics.mjs +170 -0
  150. package/dist/src/shared/human_output.d.mts +2 -0
  151. package/{src → dist/src}/shared/human_output.mjs +1 -3
  152. package/dist/src/shared/node_runtime.d.mts +14 -0
  153. package/dist/src/shared/node_runtime.mjs +25 -0
  154. package/dist/src/shared/operation_guard.d.mts +22 -0
  155. package/dist/src/shared/operation_guard.mjs +150 -0
  156. package/dist/src/shared/process_environment.d.mts +15 -0
  157. package/dist/src/shared/process_environment.mjs +50 -0
  158. package/dist/src/source/access.d.mts +165 -0
  159. package/dist/src/source/access.mjs +602 -0
  160. package/dist/src/source/asset_transfer.d.mts +27 -0
  161. package/dist/src/source/asset_transfer.mjs +202 -0
  162. package/dist/src/source/baseline.d.mts +66 -0
  163. package/dist/src/source/baseline.mjs +372 -0
  164. package/dist/src/source/external_reader.d.mts +42 -0
  165. package/dist/src/source/external_reader.mjs +278 -0
  166. package/dist/src/source/postgresql_access.d.mts +86 -0
  167. package/dist/src/source/postgresql_access.mjs +454 -0
  168. package/dist/src/source/privacy_paths.d.mts +10 -0
  169. package/dist/src/source/privacy_paths.mjs +27 -0
  170. package/dist/src/targets/driver.d.mts +2 -0
  171. package/dist/src/targets/driver.mjs +13 -0
  172. package/dist/src/targets/postgresql.d.mts +5 -0
  173. package/dist/src/targets/postgresql.mjs +620 -0
  174. package/dist/src/targets/supabase.d.mts +8 -0
  175. package/dist/src/targets/supabase.mjs +627 -0
  176. package/dist/src/targets/supabase_environment.d.mts +49 -0
  177. package/dist/src/targets/supabase_environment.mjs +162 -0
  178. package/dist/src/targets/target.d.mts +33 -0
  179. package/dist/src/targets/target.mjs +39 -0
  180. package/docs/README.md +1 -0
  181. package/docs/architecture.md +133 -10
  182. package/docs/configuration.md +9 -3
  183. package/docs/index.md +1 -0
  184. package/docs/releasing.md +8 -3
  185. package/docs/typescript.md +134 -0
  186. package/package.json +36 -29
  187. package/types/configuration.d.mts +5 -0
  188. package/scripts/install/create_root_config.mjs +0 -19
  189. package/src/README.md +0 -17
  190. package/src/application/session.mjs +0 -435
  191. package/src/baseline/artifact.mjs +0 -684
  192. package/src/baseline/builder.mjs +0 -164
  193. package/src/baseline/input_discovery.mjs +0 -155
  194. package/src/baseline/policy_review.mjs +0 -92
  195. package/src/baseline/preparation.mjs +0 -323
  196. package/src/baseline/privacy_engine.mjs +0 -1399
  197. package/src/baseline/privacy_structured_json.mjs +0 -554
  198. package/src/baseline/sanitization_policy.mjs +0 -299
  199. package/src/baseline/schema_snapshot.mjs +0 -113
  200. package/src/cli/arguments.mjs +0 -126
  201. package/src/cli/guided.mjs +0 -814
  202. package/src/cli/init_command.mjs +0 -94
  203. package/src/cli/rehearsal.mjs +0 -972
  204. package/src/cli/renderers.mjs +0 -638
  205. package/src/cli/runtime_commands.mjs +0 -707
  206. package/src/cli/source_commands.mjs +0 -355
  207. package/src/cli/terminal.mjs +0 -275
  208. package/src/identity/claim.mjs +0 -1165
  209. package/src/identity/provider_configuration.mjs +0 -177
  210. package/src/identity/storage.mjs +0 -165
  211. package/src/project/configuration.d.mts +0 -164
  212. package/src/project/configuration.mjs +0 -1337
  213. package/src/project/install_scaffold.mjs +0 -325
  214. package/src/project/setup.mjs +0 -474
  215. package/src/project/support_report.mjs +0 -105
  216. package/src/runtime/cleanup.mjs +0 -381
  217. package/src/runtime/migration_history.mjs +0 -220
  218. package/src/runtime/plan.mjs +0 -722
  219. package/src/runtime/policy.mjs +0 -438
  220. package/src/runtime/restore.mjs +0 -337
  221. package/src/runtime/service_environment.mjs +0 -82
  222. package/src/runtime/topology.mjs +0 -177
  223. package/src/shared/diagnostics.mjs +0 -207
  224. package/src/shared/node_runtime.mjs +0 -31
  225. package/src/shared/operation_guard.mjs +0 -162
  226. package/src/shared/process_environment.mjs +0 -68
  227. package/src/source/access.mjs +0 -798
  228. package/src/source/asset_transfer.mjs +0 -259
  229. package/src/source/baseline.mjs +0 -482
  230. package/src/source/external_reader.mjs +0 -368
  231. package/src/source/postgresql_access.mjs +0 -638
  232. package/src/source/privacy_paths.mjs +0 -38
  233. package/src/targets/postgresql.mjs +0 -808
  234. package/src/targets/supabase.mjs +0 -840
  235. package/src/targets/supabase_environment.mjs +0 -229
  236. package/src/targets/target.mjs +0 -66
@@ -1,207 +0,0 @@
1
- /**
2
- * Purpose: Provide stable Rehearsal result, failure, redaction, and rendering
3
- * contracts for humans and automation. Do not run directly; this module is reusable
4
- * script infrastructure.
5
- */
6
-
7
- import { createHash } from "node:crypto";
8
- import packageManifest from "../../package.json" with { type: "json" };
9
-
10
- export const REHEARSAL_RESULT_VERSION = 1;
11
- export const REHEARSAL_VERSION = packageManifest.version;
12
-
13
- export const REHEARSAL_EXIT_CODES = Object.freeze({
14
- success: 0,
15
- configuration_invalid: 2,
16
- unsafe_environment: 3,
17
- baseline_invalid: 4,
18
- baseline_checksum_mismatch: 5,
19
- migration_candidate_failure: 6,
20
- migration_verification_failure: 7,
21
- application_proof_failure: 8,
22
- runtime_dependency_failure: 9,
23
- internal_failure: 10,
24
- });
25
-
26
- const SECRET_KEY_PATTERN =
27
- /(?:authorization|cookie|credential|database[_-]?url|jwt|key|password|secret|service[_-]?role|token)/iu;
28
- const JWT_PATTERN =
29
- /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/gu;
30
- const CONNECTION_STRING_PATTERN =
31
- /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?):\/\/[^\s"']+/giu;
32
- const BEARER_PATTERN = /\bBearer\s+[^\s"']+/giu;
33
- const SUPABASE_KEY_PATTERN = /\bsb_(?:publishable|secret)_[A-Za-z0-9_-]+\b/gu;
34
-
35
- const redactString = (value) =>
36
- value
37
- .replace(CONNECTION_STRING_PATTERN, "[REDACTED_CONNECTION_STRING]")
38
- .replace(JWT_PATTERN, "[REDACTED_JWT]")
39
- .replace(BEARER_PATTERN, "Bearer [REDACTED]")
40
- .replace(SUPABASE_KEY_PATTERN, "[REDACTED_KEY]");
41
-
42
- export const redactDiagnosticValue = (value, key = "") => {
43
- if (SECRET_KEY_PATTERN.test(key)) return "[REDACTED]";
44
- if (typeof value === "string") return redactString(value);
45
- if (Array.isArray(value)) {
46
- return value.map((entry) => redactDiagnosticValue(entry));
47
- }
48
- if (value && typeof value === "object") {
49
- return Object.fromEntries(
50
- Object.entries(value).map(([entryKey, entryValue]) => [
51
- entryKey,
52
- redactDiagnosticValue(entryValue, entryKey),
53
- ]),
54
- );
55
- }
56
- return value;
57
- };
58
-
59
- const diagnosticIdFor = ({ category, code, message }) =>
60
- createHash("sha256")
61
- .update(`${category}\0${code}\0${message}`)
62
- .digest("hex")
63
- .slice(0, 12);
64
-
65
- export class RehearsalError extends Error {
66
- constructor({
67
- category,
68
- code,
69
- message,
70
- expected,
71
- actual,
72
- context,
73
- refused,
74
- suggestions = [],
75
- cause,
76
- }) {
77
- super(message, { cause });
78
- this.name = "RehearsalError";
79
- if (!Object.hasOwn(REHEARSAL_EXIT_CODES, category)) {
80
- throw new Error(`Unknown Rehearsal error category: ${category}.`);
81
- }
82
- this.category = category;
83
- this.code = code;
84
- this.expected = expected;
85
- this.actual = actual;
86
- this.context = context;
87
- this.refused = refused;
88
- this.suggestions = suggestions;
89
- this.diagnosticId = diagnosticIdFor({ category, code, message });
90
- this.exitCode = REHEARSAL_EXIT_CODES[category];
91
- }
92
- }
93
-
94
- const inferCategory = (error) => {
95
- const message = String(error?.message ?? error);
96
- if (/checksum/iu.test(message)) return "baseline_checksum_mismatch";
97
- if (
98
- /container\b.*\b(?:is not ready|starting|unhealthy)|health(?:check)?\b.*\b(?:failed|starting|unhealthy)|out of memory|\boom\b|no space left|resource temporarily unavailable/iu.test(
99
- message,
100
- )
101
- ) {
102
- return "runtime_dependency_failure";
103
- }
104
- if (/candidate|migration history diverges/iu.test(message)) {
105
- return "migration_candidate_failure";
106
- }
107
- if (/baseline|artifact|generation|sanitization policy/iu.test(message)) {
108
- return "baseline_invalid";
109
- }
110
- if (/migration|ledger|replay/iu.test(message)) {
111
- return "migration_verification_failure";
112
- }
113
- if (/config|unknown rehearsal|unsupported rehearsal/iu.test(message)) {
114
- return "configuration_invalid";
115
- }
116
- if (/loopback|hosted|unsafe|credential|outbound/iu.test(message)) {
117
- return "unsafe_environment";
118
- }
119
- if (
120
- /docker|postgres(?:ql)?|supabase|runtime|command|enoent|node\.js|ports?\b/iu.test(
121
- message,
122
- )
123
- ) {
124
- return "runtime_dependency_failure";
125
- }
126
- return "internal_failure";
127
- };
128
-
129
- export const normalizeRehearsalError = (error, overrides = {}) => {
130
- if (error instanceof RehearsalError) return error;
131
- const message = String(error?.message ?? error);
132
- const category = overrides.category ?? inferCategory(error);
133
- return new RehearsalError({
134
- category,
135
- code: overrides.code ?? category.toUpperCase(),
136
- message,
137
- expected: overrides.expected,
138
- actual: overrides.actual,
139
- context: overrides.context,
140
- refused:
141
- overrides.refused ??
142
- "Rehearsal stopped before performing the requested operation.",
143
- suggestions: overrides.suggestions ?? [],
144
- cause: error,
145
- });
146
- };
147
-
148
- export const createRehearsalResult = ({
149
- command,
150
- status,
151
- data,
152
- warnings = [],
153
- startedAt = new Date(),
154
- durationMs = 0,
155
- }) =>
156
- redactDiagnosticValue({
157
- schemaVersion: REHEARSAL_RESULT_VERSION,
158
- rehearsalVersion: REHEARSAL_VERSION,
159
- command,
160
- status,
161
- startedAt: startedAt.toISOString(),
162
- durationMs,
163
- warnings,
164
- data,
165
- });
166
-
167
- export const serializeRehearsalError = (error, { debug = false } = {}) => {
168
- const failure = normalizeRehearsalError(error);
169
- return redactDiagnosticValue({
170
- schemaVersion: REHEARSAL_RESULT_VERSION,
171
- rehearsalVersion: REHEARSAL_VERSION,
172
- status: "error",
173
- error: {
174
- category: failure.category,
175
- code: failure.code,
176
- message: failure.message,
177
- expected: failure.expected,
178
- actual: failure.actual,
179
- context: failure.context,
180
- refused: failure.refused,
181
- suggestions: failure.suggestions,
182
- diagnosticId: failure.diagnosticId,
183
- ...(debug && failure.cause
184
- ? { cause: String(failure.cause?.message ?? failure.cause) }
185
- : {}),
186
- },
187
- });
188
- };
189
-
190
- const formatValue = (value) =>
191
- typeof value === "string" ? value : JSON.stringify(value);
192
-
193
- export const renderHumanError = (error, { debug = false } = {}) => {
194
- const { error: failure } = serializeRehearsalError(error, { debug });
195
- return [
196
- `Rehearsal stopped: ${failure.message}`,
197
- failure.expected ? `Expected: ${formatValue(failure.expected)}` : null,
198
- failure.actual ? `Actual: ${formatValue(failure.actual)}` : null,
199
- failure.context ? `Context: ${formatValue(failure.context)}` : null,
200
- failure.refused ? `Refused: ${failure.refused}` : null,
201
- ...(failure.suggestions ?? []).map((value) => `Try: ${value}`),
202
- `Diagnostic: ${failure.diagnosticId} (${failure.category})`,
203
- debug && failure.cause ? `Cause: ${failure.cause}` : null,
204
- ]
205
- .filter(Boolean)
206
- .join("\n");
207
- };
@@ -1,31 +0,0 @@
1
- /**
2
- * Supported Node.js release lines. Keep this aligned with package.json and the
3
- * verification matrix.
4
- */
5
-
6
- export const REHEARSAL_NODE_MAJORS = Object.freeze([22, 24, 26]);
7
- export const REHEARSAL_NODE_LABEL = "Node.js 22, 24, or 26";
8
- export const REHEARSAL_RECOMMENDED_NODE_MAJOR = 24;
9
-
10
- export const inspectRehearsalNodeRuntime = (
11
- version = process.versions.node,
12
- ) => {
13
- const major = Number.parseInt(String(version).split(".")[0], 10);
14
- return {
15
- version: String(version),
16
- major,
17
- supported: REHEARSAL_NODE_MAJORS.includes(major),
18
- };
19
- };
20
-
21
- export const assertSupportedRehearsalNodeRuntime = (
22
- version = process.versions.node,
23
- ) => {
24
- const runtime = inspectRehearsalNodeRuntime(version);
25
- if (!runtime.supported) {
26
- throw new Error(
27
- `Rehearsal requires ${REHEARSAL_NODE_LABEL}; this shell is using Node.js ${runtime.version}. Switch to a supported release, then rerun setup. With nvm: nvm install --lts && nvm use --lts.`,
28
- );
29
- }
30
- return runtime;
31
- };
@@ -1,162 +0,0 @@
1
- /** Project-wide mutation lock and installed-package integrity guard. */
2
-
3
- import { createHash, randomBytes } from "node:crypto";
4
- import {
5
- lstat,
6
- mkdir,
7
- readFile,
8
- readdir,
9
- realpath,
10
- rename,
11
- rm,
12
- stat,
13
- writeFile,
14
- } from "node:fs/promises";
15
- import { readFileSync, readdirSync, rmSync } from "node:fs";
16
- import { tmpdir } from "node:os";
17
- import { join, relative } from "node:path";
18
-
19
- const LOCK_ROOT = join(tmpdir(), "rehearsal-db-operation-locks");
20
-
21
- const digest = (value) => createHash("sha256").update(value).digest("hex");
22
-
23
- const packageFiles = (root, directory = root) =>
24
- readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
25
- const path = join(directory, entry.name);
26
- if (entry.isDirectory()) return packageFiles(root, path);
27
- if (!entry.isFile()) return [];
28
- return [relative(root, path)];
29
- });
30
-
31
- export const createInstalledPackageFingerprint = ({ packageRoot }) => {
32
- const files = [
33
- "package.json",
34
- ...["src", "scripts/runtime"].flatMap((directory) =>
35
- packageFiles(packageRoot, join(packageRoot, directory)),
36
- ),
37
- ].sort();
38
- const hash = createHash("sha256");
39
- for (const file of files) {
40
- hash.update(file);
41
- hash.update("\0");
42
- hash.update(readFileSync(join(packageRoot, file)));
43
- hash.update("\0");
44
- }
45
- return Object.freeze({ files: files.length, sha256: hash.digest("hex") });
46
- };
47
-
48
- export const assertInstalledPackageFingerprint = ({
49
- packageRoot,
50
- expected,
51
- }) => {
52
- const actual = createInstalledPackageFingerprint({ packageRoot });
53
- if (actual.files !== expected?.files || actual.sha256 !== expected?.sha256) {
54
- throw new Error(
55
- "The installed Rehearsal package changed during this operation. Reinstall the intended version, then start a new command.",
56
- );
57
- }
58
- return actual;
59
- };
60
-
61
- const processIsAlive = (pid) => {
62
- if (!Number.isSafeInteger(pid) || pid < 1) return false;
63
- try {
64
- process.kill(pid, 0);
65
- return true;
66
- } catch (error) {
67
- return error?.code === "EPERM";
68
- }
69
- };
70
-
71
- const assertPrivateLockRoot = async () => {
72
- await mkdir(LOCK_ROOT, { recursive: true, mode: 0o700 });
73
- const details = await stat(LOCK_ROOT);
74
- if (
75
- !details.isDirectory() ||
76
- (details.mode & 0o077) !== 0 ||
77
- (typeof process.getuid === "function" && details.uid !== process.getuid())
78
- ) {
79
- throw new Error("The Rehearsal operation-lock directory is not private.");
80
- }
81
- };
82
-
83
- export const acquireProjectOperation = async ({ projectRoot, command }) => {
84
- await assertPrivateLockRoot();
85
- const canonicalRoot = await realpath(projectRoot);
86
- const projectHash = digest(canonicalRoot);
87
- const lockDirectory = join(LOCK_ROOT, projectHash);
88
- const ownerPath = join(lockDirectory, "owner.json");
89
- const token = randomBytes(16).toString("hex");
90
- const owner = {
91
- formatVersion: 1,
92
- pid: process.pid,
93
- command,
94
- projectHash,
95
- token,
96
- startedAt: new Date().toISOString(),
97
- };
98
-
99
- for (let attempt = 0; attempt < 2; attempt += 1) {
100
- try {
101
- await mkdir(lockDirectory, { mode: 0o700 });
102
- await writeFile(ownerPath, `${JSON.stringify(owner)}\n`, {
103
- mode: 0o600,
104
- flag: "wx",
105
- });
106
- const assertOwnership = (current) => {
107
- if (current.token !== token || current.pid !== process.pid) {
108
- throw new Error(
109
- "The Rehearsal operation lock changed ownership unexpectedly.",
110
- );
111
- }
112
- };
113
- return Object.freeze({
114
- command,
115
- async release() {
116
- const current = JSON.parse(await readFile(ownerPath, "utf8"));
117
- assertOwnership(current);
118
- await rm(lockDirectory, { recursive: true });
119
- },
120
- releaseSync() {
121
- const current = JSON.parse(readFileSync(ownerPath, "utf8"));
122
- assertOwnership(current);
123
- rmSync(lockDirectory, { recursive: true });
124
- },
125
- });
126
- } catch (error) {
127
- if (error?.code !== "EEXIST" || attempt > 0) throw error;
128
- const details = await lstat(lockDirectory);
129
- if (!details.isDirectory() || details.isSymbolicLink()) {
130
- throw new Error("The Rehearsal operation lock is unsafe.");
131
- }
132
- let current;
133
- try {
134
- current = JSON.parse(await readFile(ownerPath, "utf8"));
135
- } catch {
136
- throw new Error(
137
- "Another Rehearsal operation owns this project, or its lock is incomplete.",
138
- );
139
- }
140
- if (processIsAlive(current.pid)) {
141
- throw new Error(
142
- `Another Rehearsal operation is already running for this project (PID ${current.pid}).`,
143
- );
144
- }
145
- const staleDirectory = `${lockDirectory}.stale-${token}`;
146
- try {
147
- await rename(lockDirectory, staleDirectory);
148
- } catch (renameError) {
149
- if (renameError?.code === "ENOENT") continue;
150
- throw renameError;
151
- }
152
- await rm(staleDirectory, { recursive: true });
153
- }
154
- }
155
- throw new Error("Rehearsal could not acquire its project operation lock.");
156
- };
157
-
158
- export const inspectProjectOperationLocks = async () =>
159
- readdir(LOCK_ROOT).catch((error) => {
160
- if (error?.code === "ENOENT") return [];
161
- throw error;
162
- });
@@ -1,68 +0,0 @@
1
- /**
2
- * Purpose: Build minimal child-process environments and validate loopback URLs for
3
- * the project-neutral Rehearsal runtime. Do not run directly; this module is reusable
4
- * script infrastructure.
5
- */
6
-
7
- const SAFE_SYSTEM_ENVIRONMENT_KEYS = Object.freeze([
8
- "CI",
9
- "COLORTERM",
10
- "FORCE_COLOR",
11
- "HOME",
12
- "LANG",
13
- "LANGUAGE",
14
- "LC_ALL",
15
- "LOGNAME",
16
- "NO_COLOR",
17
- "PATH",
18
- "SHELL",
19
- "TERM",
20
- "TMP",
21
- "TMPDIR",
22
- "TEMP",
23
- "USER",
24
- ]);
25
-
26
- const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "::1", "localhost"]);
27
-
28
- export const isLoopbackUrl = (value) => {
29
- try {
30
- const parsed = new URL(value);
31
- return (
32
- ["http:", "https:"].includes(parsed.protocol) &&
33
- LOOPBACK_HOSTNAMES.has(parsed.hostname)
34
- );
35
- } catch {
36
- return false;
37
- }
38
- };
39
-
40
- export const assertLoopbackUrl = (label, value) => {
41
- if (!value || !isLoopbackUrl(value)) {
42
- throw new Error(
43
- `${label} must use a loopback URL; received a non-local target.`,
44
- );
45
- }
46
- };
47
-
48
- export const pickEnvironmentVariables = (source, keys) =>
49
- Object.fromEntries(
50
- keys.flatMap((key) => {
51
- const value = source[key];
52
- return value === undefined ? [] : [[key, value]];
53
- }),
54
- );
55
-
56
- export const createCleanProcessEnvironment = ({
57
- inheritedEnvironment = process.env,
58
- overrides = {},
59
- passthroughKeys = [],
60
- } = {}) => ({
61
- ...pickEnvironmentVariables(inheritedEnvironment, [
62
- ...SAFE_SYSTEM_ENVIRONMENT_KEYS,
63
- ...passthroughKeys,
64
- ]),
65
- ...Object.fromEntries(
66
- Object.entries(overrides).filter(([, value]) => value !== undefined),
67
- ),
68
- });