@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,299 +0,0 @@
1
- /**
2
- * Generic, fail-closed sanitization policy validation and value operations.
3
- * Projects own the policy, schema inventory, pseudonym keys, and derivation logic.
4
- */
5
-
6
- import { createHash } from "node:crypto";
7
- import { validateExecutablePrivacyPolicy } from "./privacy_engine.mjs";
8
-
9
- export const SANITIZATION_ACTIONS = Object.freeze({
10
- KEEP: "KEEP",
11
- PSEUDONYMIZE: "PSEUDONYMIZE",
12
- REPLACE: "REPLACE",
13
- EXCLUDE: "EXCLUDE",
14
- DERIVE: "DERIVE",
15
- });
16
-
17
- export const EXCLUDED_VALUE = Symbol("REHEARSAL_EXCLUDED_VALUE");
18
-
19
- const actionAliases = new Map([
20
- ["KEEP", SANITIZATION_ACTIONS.KEEP],
21
- ["KEEP EXACTLY", SANITIZATION_ACTIONS.KEEP],
22
- ["PSEUDONYMIZE", SANITIZATION_ACTIONS.PSEUDONYMIZE],
23
- ["REPLACE", SANITIZATION_ACTIONS.REPLACE],
24
- ["REPLACE WITH SYNTHETIC", SANITIZATION_ACTIONS.REPLACE],
25
- ["EXCLUDE", SANITIZATION_ACTIONS.EXCLUDE],
26
- ["DERIVE", SANITIZATION_ACTIONS.DERIVE],
27
- ]);
28
-
29
- const identifierPattern = /^[a-z][a-z0-9_]{0,62}$/u;
30
-
31
- const assertIdentifier = (value, label) => {
32
- if (!identifierPattern.test(value ?? "")) {
33
- throw new Error(`${label} must be a lowercase PostgreSQL identifier.`);
34
- }
35
- return value;
36
- };
37
- const tableSchema = (table) => table.schema ?? "public";
38
- const tableKey = (table) =>
39
- tableSchema(table) === "public"
40
- ? table.name
41
- : `${tableSchema(table)}.${table.name}`;
42
-
43
- export const normalizeSanitizationAction = (action) => {
44
- const normalized = actionAliases.get(
45
- String(action ?? "")
46
- .trim()
47
- .toUpperCase(),
48
- );
49
- if (!normalized) {
50
- throw new Error(`Unknown Rehearsal sanitization action: ${action}.`);
51
- }
52
- return normalized;
53
- };
54
-
55
- export const validateRuntimeSanitizationPolicy = (policy) => {
56
- if (!policy || typeof policy !== "object" || Array.isArray(policy)) {
57
- throw new Error("A Rehearsal sanitization policy object is required.");
58
- }
59
- if (policy.draft === true) {
60
- throw new Error(
61
- "The sanitization policy is still a REVIEW REQUIRED draft.",
62
- );
63
- }
64
- if (policy.policyVersion === 2) {
65
- return validateExecutablePrivacyPolicy(policy);
66
- }
67
- if (policy.policyVersion !== 1) {
68
- throw new Error("The sanitization policy must use policyVersion 1 or 2.");
69
- }
70
- if (!/^\d{14}$/u.test(policy.migrationCutoff ?? "")) {
71
- throw new Error(
72
- "The sanitization policy must declare a timestamp migrationCutoff.",
73
- );
74
- }
75
- if (!Array.isArray(policy.tables) || policy.tables.length === 0) {
76
- throw new Error("The sanitization policy does not list tables.");
77
- }
78
- const tables = new Set();
79
- for (const table of policy.tables) {
80
- const schemaName = assertIdentifier(
81
- tableSchema(table),
82
- "Sanitization policy table schema",
83
- );
84
- const tableName = assertIdentifier(
85
- table?.name,
86
- "Sanitization policy table name",
87
- );
88
- const key =
89
- schemaName === "public" ? tableName : `${schemaName}.${tableName}`;
90
- if (tables.has(key)) {
91
- throw new Error(`Sanitization policy duplicates table ${key}.`);
92
- }
93
- tables.add(key);
94
- if (!["STREAM AND SANITIZE", "EXCLUDE"].includes(table.sourceRows)) {
95
- throw new Error(
96
- `Sanitization policy table ${tableName} has an invalid sourceRows decision.`,
97
- );
98
- }
99
- if (!Array.isArray(table.columns) || table.columns.length === 0) {
100
- throw new Error(
101
- `Sanitization policy table ${tableName} must list its columns.`,
102
- );
103
- }
104
- const columns = new Set();
105
- for (const column of table.columns) {
106
- const columnName = assertIdentifier(
107
- column?.name,
108
- `Sanitization policy column in ${tableName}`,
109
- );
110
- if (columns.has(columnName)) {
111
- throw new Error(
112
- `Sanitization policy duplicates column ${tableName}.${columnName}.`,
113
- );
114
- }
115
- columns.add(columnName);
116
- normalizeSanitizationAction(column.action);
117
- if (!["ALWAYS", "NEVER"].includes(column.generated)) {
118
- throw new Error(
119
- `Sanitization policy column ${tableName}.${columnName} must classify generated as ALWAYS or NEVER.`,
120
- );
121
- }
122
- if (!["YES", "NO"].includes(column.identity)) {
123
- throw new Error(
124
- `Sanitization policy column ${tableName}.${columnName} must classify identity as YES or NO.`,
125
- );
126
- }
127
- if (column.foreignKey !== null) {
128
- if (
129
- !column.foreignKey ||
130
- typeof column.foreignKey !== "object" ||
131
- Array.isArray(column.foreignKey)
132
- ) {
133
- throw new Error(
134
- `Sanitization policy column ${tableName}.${columnName} has an invalid foreignKey decision.`,
135
- );
136
- }
137
- assertIdentifier(
138
- column.foreignKey.schema,
139
- `Foreign-key schema for ${tableName}.${columnName}`,
140
- );
141
- assertIdentifier(
142
- column.foreignKey.table,
143
- `Foreign-key table for ${tableName}.${columnName}`,
144
- );
145
- assertIdentifier(
146
- column.foreignKey.column,
147
- `Foreign-key column for ${tableName}.${columnName}`,
148
- );
149
- }
150
- }
151
- }
152
- return policy;
153
- };
154
-
155
- export const readBoundRuntimeSanitizationPolicy = ({
156
- bytes,
157
- expectedSha256,
158
- }) => {
159
- const source = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
160
- const policy = validateRuntimeSanitizationPolicy(
161
- JSON.parse(source.toString("utf8")),
162
- );
163
- const actualSha256 = createHash("sha256").update(source).digest("hex");
164
- if (actualSha256 !== expectedSha256) {
165
- throw new Error(
166
- "The active sanitization policy does not match the reviewed baseline policy checksum.",
167
- );
168
- }
169
- return policy;
170
- };
171
-
172
- const indexTables = (tables, label) => {
173
- if (!Array.isArray(tables)) throw new Error(`${label} must be an array.`);
174
- const index = new Map();
175
- for (const table of tables) {
176
- const schema = assertIdentifier(
177
- tableSchema(table),
178
- `${label} table schema`,
179
- );
180
- const name = assertIdentifier(table?.name, `${label} table name`);
181
- const key = schema === "public" ? name : `${schema}.${name}`;
182
- if (index.has(key)) throw new Error(`${label} duplicates table ${key}.`);
183
- if (!Array.isArray(table.columns)) {
184
- throw new Error(`${label} table ${name} must list its columns.`);
185
- }
186
- const columns = new Map();
187
- for (const column of table.columns) {
188
- const columnName = assertIdentifier(
189
- typeof column === "string" ? column : column?.name,
190
- `${label} column name`,
191
- );
192
- if (columns.has(columnName)) {
193
- throw new Error(`${label} duplicates column ${name}.${columnName}.`);
194
- }
195
- columns.set(columnName, column);
196
- }
197
- index.set(key, { table: { ...table, schema }, columns });
198
- }
199
- return index;
200
- };
201
-
202
- export const validateSanitizationCoverage = ({ policy, schemaTables }) => {
203
- if (!policy || typeof policy !== "object" || Array.isArray(policy)) {
204
- throw new Error("A Rehearsal sanitization policy object is required.");
205
- }
206
- const schema = indexTables(schemaTables, "Schema inventory");
207
- const classified = indexTables(policy.tables, "Sanitization policy");
208
- const normalizedTables = [];
209
-
210
- for (const [tableName, schemaEntry] of schema) {
211
- const policyEntry = classified.get(tableName);
212
- if (!policyEntry) {
213
- throw new Error(
214
- `Sanitization policy does not classify table ${tableName}.`,
215
- );
216
- }
217
- const normalizedColumns = [];
218
- for (const columnName of schemaEntry.columns.keys()) {
219
- const column = policyEntry.columns.get(columnName);
220
- if (!column) {
221
- throw new Error(
222
- `Sanitization policy does not classify column ${tableName}.${columnName}.`,
223
- );
224
- }
225
- normalizedColumns.push({
226
- ...column,
227
- name: columnName,
228
- action: normalizeSanitizationAction(column.action),
229
- });
230
- }
231
- for (const columnName of policyEntry.columns.keys()) {
232
- if (!schemaEntry.columns.has(columnName)) {
233
- throw new Error(
234
- `Sanitization policy references unknown column ${tableName}.${columnName}.`,
235
- );
236
- }
237
- }
238
- normalizedTables.push({
239
- ...policyEntry.table,
240
- schema: tableSchema(policyEntry.table),
241
- columns: normalizedColumns,
242
- });
243
- }
244
-
245
- for (const tableName of classified.keys()) {
246
- if (!schema.has(tableName)) {
247
- throw new Error(
248
- `Sanitization policy references unknown table ${tableName}.`,
249
- );
250
- }
251
- }
252
-
253
- return Object.freeze({
254
- policyVersion: policy.policyVersion,
255
- tableCount: normalizedTables.length,
256
- columnCount: normalizedTables.reduce(
257
- (total, table) => total + table.columns.length,
258
- 0,
259
- ),
260
- tables: Object.freeze(normalizedTables),
261
- });
262
- };
263
-
264
- export const applySanitizationAction = ({
265
- action,
266
- value,
267
- replace,
268
- pseudonymize,
269
- derive,
270
- context = {},
271
- }) => {
272
- switch (normalizeSanitizationAction(action)) {
273
- case SANITIZATION_ACTIONS.KEEP:
274
- return value;
275
- case SANITIZATION_ACTIONS.EXCLUDE:
276
- return EXCLUDED_VALUE;
277
- case SANITIZATION_ACTIONS.REPLACE:
278
- if (typeof replace !== "function") {
279
- throw new Error(
280
- "REPLACE requires a project-owned replacement function.",
281
- );
282
- }
283
- return replace(value, context);
284
- case SANITIZATION_ACTIONS.PSEUDONYMIZE:
285
- if (typeof pseudonymize !== "function") {
286
- throw new Error(
287
- "PSEUDONYMIZE requires a project-owned pseudonymization function.",
288
- );
289
- }
290
- return pseudonymize(value, context);
291
- case SANITIZATION_ACTIONS.DERIVE:
292
- if (typeof derive !== "function") {
293
- throw new Error("DERIVE requires a project-owned derivation function.");
294
- }
295
- return derive(value, context);
296
- default:
297
- throw new Error("Unreachable Rehearsal sanitization action.");
298
- }
299
- };
@@ -1,113 +0,0 @@
1
- /**
2
- * Purpose: Capture and compare bounded, data-free PostgreSQL public-schema
3
- * snapshots for a local Rehearsal runtime. Do not run directly.
4
- */
5
-
6
- import { spawnSync } from "node:child_process";
7
- import { createHash } from "node:crypto";
8
- import { chmod, mkdir, mkdtemp, readFile, rm } from "node:fs/promises";
9
- import { basename, join, resolve, sep } from "node:path";
10
- import { createCleanProcessEnvironment } from "../shared/process_environment.mjs";
11
-
12
- const maximumSchemaBytes = 64 * 1024 * 1024;
13
-
14
- const assertArtifactRoot = (artifactRoot) => {
15
- const resolved = resolve(artifactRoot);
16
- if (basename(resolved) !== ".rehearsal" || resolved === sep) {
17
- throw new Error(
18
- `Refusing an unsafe Rehearsal artifact root: ${artifactRoot}`,
19
- );
20
- }
21
- return resolved;
22
- };
23
-
24
- export const assertProductionSchemaDump = (schemaSql) => {
25
- if (typeof schemaSql !== "string" || schemaSql.length === 0) {
26
- throw new Error("The Rehearsal production schema snapshot is empty.");
27
- }
28
- if (Buffer.byteLength(schemaSql) > maximumSchemaBytes) {
29
- throw new Error("The Rehearsal production schema snapshot is too large.");
30
- }
31
- if (!schemaSql.includes('CREATE SCHEMA IF NOT EXISTS "public";')) {
32
- throw new Error(
33
- "The Rehearsal production schema snapshot does not define public.",
34
- );
35
- }
36
- if (/^COPY\s|^\\connect\s|^CREATE DATABASE\s/mu.test(schemaSql)) {
37
- throw new Error(
38
- "The Rehearsal production schema snapshot contains data or database switching commands.",
39
- );
40
- }
41
- return schemaSql;
42
- };
43
-
44
- const comparableSchemaDump = (schemaSql) =>
45
- assertProductionSchemaDump(schemaSql)
46
- .split("\n")
47
- .map((line) => line.trimEnd())
48
- .filter(Boolean)
49
- .map((line) => {
50
- const checkConstraint = line.match(
51
- /^(\s*CONSTRAINT "[^"]+" CHECK ).*(,?)$/u,
52
- );
53
- return checkConstraint
54
- ? `${checkConstraint[1]}<database-normalized-expression>${checkConstraint[2]}`
55
- : line;
56
- })
57
- .join("\n");
58
-
59
- export const compareProductionSchemaDumps = ({ expected, actual }) => {
60
- const expectedComparable = comparableSchemaDump(expected);
61
- const actualComparable = comparableSchemaDump(actual);
62
- if (expectedComparable !== actualComparable) {
63
- throw new Error(
64
- "The local Rehearsal public schema does not structurally match the production snapshot.",
65
- );
66
- }
67
- return createHash("sha256").update(actualComparable).digest("hex");
68
- };
69
-
70
- export const captureSupabasePublicSchema = async ({
71
- repositoryRoot,
72
- artifactRoot,
73
- arguments_,
74
- }) => {
75
- const root = assertArtifactRoot(artifactRoot);
76
- await mkdir(root, { recursive: true, mode: 0o700 });
77
- const temporaryDirectory = await mkdtemp(join(root, ".schema-capture-"));
78
- const destination = join(temporaryDirectory, "production-schema.sql");
79
- try {
80
- const result = spawnSync(
81
- "supabase",
82
- [...arguments_, "--schema", "public", "--file", destination],
83
- {
84
- cwd: repositoryRoot,
85
- env: createCleanProcessEnvironment(),
86
- encoding: "utf8",
87
- stdio: ["ignore", "pipe", "pipe"],
88
- maxBuffer: 4 * 1024 * 1024,
89
- },
90
- );
91
- if (result.error || result.status !== 0) {
92
- throw new Error(
93
- "The exact Rehearsal schema snapshot could not be captured.",
94
- { cause: result.error },
95
- );
96
- }
97
- await chmod(destination, 0o600);
98
- return assertProductionSchemaDump(await readFile(destination, "utf8"));
99
- } finally {
100
- await rm(temporaryDirectory, { recursive: true, force: true });
101
- }
102
- };
103
-
104
- export const captureLocalPublicSchema = ({
105
- repositoryRoot,
106
- artifactRoot,
107
- workdir,
108
- }) =>
109
- captureSupabasePublicSchema({
110
- repositoryRoot,
111
- artifactRoot,
112
- arguments_: ["db", "dump", "--local", "--workdir", workdir],
113
- });
@@ -1,126 +0,0 @@
1
- /** Parse and classify the stable Rehearsal command-line contract. */
2
-
3
- export const parseArguments = (arguments_) => {
4
- const flags = {
5
- json: false,
6
- help: false,
7
- version: false,
8
- verbosity: "normal",
9
- dryRun: false,
10
- write: false,
11
- plain: false,
12
- configPath: undefined,
13
- confirmation: undefined,
14
- recordsPath: undefined,
15
- ledgerPath: undefined,
16
- assetsPath: undefined,
17
- target: undefined,
18
- includeRuntime: false,
19
- includeImages: false,
20
- cleanupConfirmation: undefined,
21
- sourceAccessConfirmation: undefined,
22
- sourceRetirementConfirmation: undefined,
23
- refreshConfirmation: undefined,
24
- identityConfirmation: undefined,
25
- identityName: undefined,
26
- runtimeTopology: undefined,
27
- targetConfirmations: undefined,
28
- exitGuidedSession: false,
29
- };
30
- const positionals = [];
31
- for (const argument of arguments_) {
32
- if (argument === "--json") flags.json = true;
33
- else if (argument === "--help" || argument === "-h") flags.help = true;
34
- else if (argument === "--version" || argument === "-V")
35
- flags.version = true;
36
- else if (argument === "--verbose") flags.verbosity = "verbose";
37
- else if (argument === "--debug") flags.verbosity = "debug";
38
- else if (argument === "--dry-run") flags.dryRun = true;
39
- else if (argument === "--write") flags.write = true;
40
- else if (argument === "--plain") flags.plain = true;
41
- else if (argument.startsWith("--config="))
42
- flags.configPath = argument.slice("--config=".length);
43
- else if (argument.startsWith("--confirm-candidates="))
44
- flags.confirmation = argument.slice("--confirm-candidates=".length);
45
- else if (argument.startsWith("--confirm-cleanup="))
46
- flags.cleanupConfirmation = argument.slice("--confirm-cleanup=".length);
47
- else if (argument.startsWith("--confirm-source-access="))
48
- flags.sourceAccessConfirmation = argument.slice(
49
- "--confirm-source-access=".length,
50
- );
51
- else if (argument.startsWith("--confirm-source-retirement="))
52
- flags.sourceRetirementConfirmation = argument.slice(
53
- "--confirm-source-retirement=".length,
54
- );
55
- else if (argument.startsWith("--confirm-refresh="))
56
- flags.refreshConfirmation = argument.slice("--confirm-refresh=".length);
57
- else if (argument.startsWith("--confirm-identity="))
58
- flags.identityConfirmation = argument.slice("--confirm-identity=".length);
59
- else if (argument.startsWith("--identity="))
60
- flags.identityName = argument.slice("--identity=".length);
61
- else if (argument.startsWith("--records="))
62
- flags.recordsPath = argument.slice("--records=".length);
63
- else if (argument.startsWith("--ledger="))
64
- flags.ledgerPath = argument.slice("--ledger=".length);
65
- else if (argument.startsWith("--assets="))
66
- flags.assetsPath = argument.slice("--assets=".length);
67
- else if (argument.startsWith("--target="))
68
- flags.target = argument.slice("--target=".length);
69
- else if (argument === "--include-runtime") flags.includeRuntime = true;
70
- else if (argument === "--include-images") flags.includeImages = true;
71
- else if (argument.startsWith("--"))
72
- throw new Error(`Unknown Rehearsal option: ${argument}.`);
73
- else positionals.push(argument);
74
- }
75
- return { flags, positionals };
76
- };
77
-
78
- export const resetGuidedFlags = (flags) => {
79
- Object.assign(flags, {
80
- dryRun: false,
81
- write: false,
82
- confirmation: undefined,
83
- recordsPath: undefined,
84
- ledgerPath: undefined,
85
- assetsPath: undefined,
86
- target: undefined,
87
- includeRuntime: false,
88
- includeImages: false,
89
- cleanupConfirmation: undefined,
90
- sourceAccessConfirmation: undefined,
91
- sourceRetirementConfirmation: undefined,
92
- refreshConfirmation: undefined,
93
- identityConfirmation: undefined,
94
- identityName: undefined,
95
- runtimeTopology: undefined,
96
- targetConfirmations: undefined,
97
- exitGuidedSession: false,
98
- setupPlan: undefined,
99
- preparationPlan: undefined,
100
- cleanupPlan: undefined,
101
- });
102
- };
103
-
104
- export const commandMutatesProject = ({ command, flags, guided }) => {
105
- if (guided) return true;
106
- if (["setup", "init", "baseline prepare", "privacy key"].includes(command))
107
- return flags.write;
108
- if (command === "cleanup") return flags.write;
109
- if (command === "source apply")
110
- return Boolean(flags.sourceAccessConfirmation);
111
- if (command === "source retire")
112
- return Boolean(flags.sourceRetirementConfirmation);
113
- if (command === "refresh") return Boolean(flags.refreshConfirmation);
114
- if (command === "identity claim") return Boolean(flags.identityConfirmation);
115
- if (command === "run") return !flags.dryRun;
116
- return [
117
- "baseline create",
118
- "baseline refresh",
119
- "open",
120
- "start",
121
- "migrate",
122
- "reset",
123
- "stop",
124
- "discard",
125
- ].includes(command);
126
- };