@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
@@ -1,199 +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
-
9
- export const REHEARSAL_RESULT_VERSION = 1;
10
- export const REHEARSAL_VERSION = "0.1.0-beta.9";
11
-
12
- export const REHEARSAL_EXIT_CODES = Object.freeze({
13
- success: 0,
14
- configuration_invalid: 2,
15
- unsafe_environment: 3,
16
- baseline_invalid: 4,
17
- baseline_checksum_mismatch: 5,
18
- migration_candidate_failure: 6,
19
- migration_verification_failure: 7,
20
- application_proof_failure: 8,
21
- runtime_dependency_failure: 9,
22
- internal_failure: 10,
23
- });
24
-
25
- const SECRET_KEY_PATTERN =
26
- /(?:authorization|cookie|credential|database[_-]?url|jwt|key|password|secret|service[_-]?role|token)/iu;
27
- const JWT_PATTERN =
28
- /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/gu;
29
- const CONNECTION_STRING_PATTERN =
30
- /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?):\/\/[^\s"']+/giu;
31
- const BEARER_PATTERN = /\bBearer\s+[^\s"']+/giu;
32
- const SUPABASE_KEY_PATTERN = /\bsb_(?:publishable|secret)_[A-Za-z0-9_-]+\b/gu;
33
-
34
- const redactString = (value) =>
35
- value
36
- .replace(CONNECTION_STRING_PATTERN, "[REDACTED_CONNECTION_STRING]")
37
- .replace(JWT_PATTERN, "[REDACTED_JWT]")
38
- .replace(BEARER_PATTERN, "Bearer [REDACTED]")
39
- .replace(SUPABASE_KEY_PATTERN, "[REDACTED_KEY]");
40
-
41
- export const redactDiagnosticValue = (value, key = "") => {
42
- if (SECRET_KEY_PATTERN.test(key)) return "[REDACTED]";
43
- if (typeof value === "string") return redactString(value);
44
- if (Array.isArray(value)) {
45
- return value.map((entry) => redactDiagnosticValue(entry));
46
- }
47
- if (value && typeof value === "object") {
48
- return Object.fromEntries(
49
- Object.entries(value).map(([entryKey, entryValue]) => [
50
- entryKey,
51
- redactDiagnosticValue(entryValue, entryKey),
52
- ]),
53
- );
54
- }
55
- return value;
56
- };
57
-
58
- const diagnosticIdFor = ({ category, code, message }) =>
59
- createHash("sha256")
60
- .update(`${category}\0${code}\0${message}`)
61
- .digest("hex")
62
- .slice(0, 12);
63
-
64
- export class RehearsalError extends Error {
65
- constructor({
66
- category,
67
- code,
68
- message,
69
- expected,
70
- actual,
71
- context,
72
- refused,
73
- suggestions = [],
74
- cause,
75
- }) {
76
- super(message, { cause });
77
- this.name = "RehearsalError";
78
- if (!Object.hasOwn(REHEARSAL_EXIT_CODES, category)) {
79
- throw new Error(`Unknown Rehearsal error category: ${category}.`);
80
- }
81
- this.category = category;
82
- this.code = code;
83
- this.expected = expected;
84
- this.actual = actual;
85
- this.context = context;
86
- this.refused = refused;
87
- this.suggestions = suggestions;
88
- this.diagnosticId = diagnosticIdFor({ category, code, message });
89
- this.exitCode = REHEARSAL_EXIT_CODES[category];
90
- }
91
- }
92
-
93
- const inferCategory = (error) => {
94
- const message = String(error?.message ?? error);
95
- if (/checksum/iu.test(message)) return "baseline_checksum_mismatch";
96
- if (/candidate|migration history diverges/iu.test(message)) {
97
- return "migration_candidate_failure";
98
- }
99
- if (/baseline|artifact|generation|sanitization policy/iu.test(message)) {
100
- return "baseline_invalid";
101
- }
102
- if (/migration|ledger|replay/iu.test(message)) {
103
- return "migration_verification_failure";
104
- }
105
- if (/config|unknown rehearsal|unsupported rehearsal/iu.test(message)) {
106
- return "configuration_invalid";
107
- }
108
- if (/loopback|hosted|unsafe|credential|outbound/iu.test(message)) {
109
- return "unsafe_environment";
110
- }
111
- if (
112
- /docker|postgres(?:ql)?|supabase|runtime|command|enoent|node\.js|ports?\b/iu.test(
113
- message,
114
- )
115
- ) {
116
- return "runtime_dependency_failure";
117
- }
118
- return "internal_failure";
119
- };
120
-
121
- export const normalizeRehearsalError = (error, overrides = {}) => {
122
- if (error instanceof RehearsalError) return error;
123
- const message = String(error?.message ?? error);
124
- const category = overrides.category ?? inferCategory(error);
125
- return new RehearsalError({
126
- category,
127
- code: overrides.code ?? category.toUpperCase(),
128
- message,
129
- expected: overrides.expected,
130
- actual: overrides.actual,
131
- context: overrides.context,
132
- refused:
133
- overrides.refused ??
134
- "Rehearsal stopped before performing the requested operation.",
135
- suggestions: overrides.suggestions ?? [],
136
- cause: error,
137
- });
138
- };
139
-
140
- export const createRehearsalResult = ({
141
- command,
142
- status,
143
- data,
144
- warnings = [],
145
- startedAt = new Date(),
146
- durationMs = 0,
147
- }) =>
148
- redactDiagnosticValue({
149
- schemaVersion: REHEARSAL_RESULT_VERSION,
150
- rehearsalVersion: REHEARSAL_VERSION,
151
- command,
152
- status,
153
- startedAt: startedAt.toISOString(),
154
- durationMs,
155
- warnings,
156
- data,
157
- });
158
-
159
- export const serializeRehearsalError = (error, { debug = false } = {}) => {
160
- const failure = normalizeRehearsalError(error);
161
- return redactDiagnosticValue({
162
- schemaVersion: REHEARSAL_RESULT_VERSION,
163
- rehearsalVersion: REHEARSAL_VERSION,
164
- status: "error",
165
- error: {
166
- category: failure.category,
167
- code: failure.code,
168
- message: failure.message,
169
- expected: failure.expected,
170
- actual: failure.actual,
171
- context: failure.context,
172
- refused: failure.refused,
173
- suggestions: failure.suggestions,
174
- diagnosticId: failure.diagnosticId,
175
- ...(debug && failure.cause
176
- ? { cause: String(failure.cause?.message ?? failure.cause) }
177
- : {}),
178
- },
179
- });
180
- };
181
-
182
- const formatValue = (value) =>
183
- typeof value === "string" ? value : JSON.stringify(value);
184
-
185
- export const renderHumanError = (error, { debug = false } = {}) => {
186
- const { error: failure } = serializeRehearsalError(error, { debug });
187
- return [
188
- `Rehearsal stopped: ${failure.message}`,
189
- failure.expected ? `Expected: ${formatValue(failure.expected)}` : null,
190
- failure.actual ? `Actual: ${formatValue(failure.actual)}` : null,
191
- failure.context ? `Context: ${formatValue(failure.context)}` : null,
192
- failure.refused ? `Refused: ${failure.refused}` : null,
193
- ...(failure.suggestions ?? []).map((value) => `Try: ${value}`),
194
- `Diagnostic: ${failure.diagnosticId} (${failure.category})`,
195
- debug && failure.cause ? `Cause: ${failure.cause}` : null,
196
- ]
197
- .filter(Boolean)
198
- .join("\n");
199
- };
@@ -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
- });