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

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 (169) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +4 -1
  3. package/dist/package.json +100 -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_engine.d.mts +162 -0
  21. package/dist/src/baseline/privacy_engine.mjs +1162 -0
  22. package/dist/src/baseline/privacy_structured_json.d.mts +66 -0
  23. package/dist/src/baseline/privacy_structured_json.mjs +464 -0
  24. package/dist/src/baseline/sanitization_policy.d.mts +69 -0
  25. package/dist/src/baseline/sanitization_policy.mjs +227 -0
  26. package/dist/src/baseline/schema_snapshot.d.mts +19 -0
  27. package/dist/src/baseline/schema_snapshot.mjs +79 -0
  28. package/dist/src/cli/arguments.d.mts +43 -0
  29. package/dist/src/cli/arguments.mjs +135 -0
  30. package/dist/src/cli/guided.d.mts +51 -0
  31. package/dist/src/cli/guided.mjs +713 -0
  32. package/dist/src/cli/init_command.d.mts +21 -0
  33. package/dist/src/cli/init_command.mjs +90 -0
  34. package/dist/src/cli/project_commands.d.mts +157 -0
  35. package/dist/src/cli/project_commands.mjs +183 -0
  36. package/dist/src/cli/rehearsal.d.mts +7 -0
  37. package/dist/src/cli/rehearsal.mjs +706 -0
  38. package/dist/src/cli/renderers.d.mts +299 -0
  39. package/dist/src/cli/renderers.mjs +554 -0
  40. package/dist/src/cli/runtime_commands.d.mts +265 -0
  41. package/dist/src/cli/runtime_commands.mjs +599 -0
  42. package/dist/src/cli/source_commands.d.mts +432 -0
  43. package/dist/src/cli/source_commands.mjs +299 -0
  44. package/dist/src/cli/terminal.d.mts +59 -0
  45. package/dist/src/cli/terminal.mjs +230 -0
  46. package/dist/src/identity/claim.d.mts +144 -0
  47. package/dist/src/identity/claim.mjs +947 -0
  48. package/dist/src/identity/provider_configuration.d.mts +37 -0
  49. package/dist/src/identity/provider_configuration.mjs +132 -0
  50. package/dist/src/identity/storage.d.mts +21 -0
  51. package/dist/src/identity/storage.mjs +122 -0
  52. package/dist/src/index.d.mts +4 -0
  53. package/dist/src/index.mjs +3 -0
  54. package/dist/src/project/configuration.d.mts +181 -0
  55. package/dist/src/project/configuration.mjs +958 -0
  56. package/dist/src/project/install_scaffold.d.mts +42 -0
  57. package/dist/src/project/install_scaffold.mjs +263 -0
  58. package/dist/src/project/setup.d.mts +87 -0
  59. package/dist/src/project/setup.mjs +414 -0
  60. package/dist/src/project/support_report.d.mts +88 -0
  61. package/dist/src/project/support_report.mjs +83 -0
  62. package/dist/src/runtime/cleanup.d.mts +85 -0
  63. package/dist/src/runtime/cleanup.mjs +311 -0
  64. package/dist/src/runtime/migration_history.d.mts +51 -0
  65. package/dist/src/runtime/migration_history.mjs +170 -0
  66. package/dist/src/runtime/plan.d.mts +134 -0
  67. package/dist/src/runtime/plan.mjs +636 -0
  68. package/dist/src/runtime/policy.d.mts +66 -0
  69. package/dist/src/runtime/policy.mjs +369 -0
  70. package/dist/src/runtime/restore.d.mts +22 -0
  71. package/dist/src/runtime/restore.mjs +276 -0
  72. package/dist/src/runtime/service_environment.d.mts +11 -0
  73. package/dist/src/runtime/service_environment.mjs +58 -0
  74. package/dist/src/runtime/topology.d.mts +51 -0
  75. package/dist/src/runtime/topology.mjs +128 -0
  76. package/dist/src/shared/diagnostics.d.mts +97 -0
  77. package/dist/src/shared/diagnostics.mjs +170 -0
  78. package/dist/src/shared/human_output.d.mts +2 -0
  79. package/{src → dist/src}/shared/human_output.mjs +1 -3
  80. package/dist/src/shared/node_runtime.d.mts +14 -0
  81. package/dist/src/shared/node_runtime.mjs +25 -0
  82. package/dist/src/shared/operation_guard.d.mts +22 -0
  83. package/dist/src/shared/operation_guard.mjs +150 -0
  84. package/dist/src/shared/process_environment.d.mts +15 -0
  85. package/dist/src/shared/process_environment.mjs +50 -0
  86. package/dist/src/source/access.d.mts +165 -0
  87. package/dist/src/source/access.mjs +602 -0
  88. package/dist/src/source/asset_transfer.d.mts +27 -0
  89. package/dist/src/source/asset_transfer.mjs +202 -0
  90. package/dist/src/source/baseline.d.mts +66 -0
  91. package/dist/src/source/baseline.mjs +372 -0
  92. package/dist/src/source/external_reader.d.mts +42 -0
  93. package/dist/src/source/external_reader.mjs +278 -0
  94. package/dist/src/source/postgresql_access.d.mts +86 -0
  95. package/dist/src/source/postgresql_access.mjs +454 -0
  96. package/dist/src/source/privacy_paths.d.mts +10 -0
  97. package/dist/src/source/privacy_paths.mjs +27 -0
  98. package/dist/src/targets/driver.d.mts +2 -0
  99. package/dist/src/targets/driver.mjs +13 -0
  100. package/dist/src/targets/postgresql.d.mts +5 -0
  101. package/dist/src/targets/postgresql.mjs +641 -0
  102. package/dist/src/targets/supabase.d.mts +8 -0
  103. package/dist/src/targets/supabase.mjs +659 -0
  104. package/dist/src/targets/supabase_environment.d.mts +49 -0
  105. package/dist/src/targets/supabase_environment.mjs +162 -0
  106. package/dist/src/targets/target.d.mts +33 -0
  107. package/dist/src/targets/target.mjs +39 -0
  108. package/docs/README.md +1 -0
  109. package/docs/architecture.md +17 -9
  110. package/docs/configuration.md +49 -8
  111. package/docs/getting-started.md +28 -0
  112. package/docs/index.md +1 -0
  113. package/docs/production-source.md +43 -7
  114. package/docs/releasing.md +5 -3
  115. package/docs/roadmap.md +3 -2
  116. package/docs/sanitization.md +415 -14
  117. package/docs/standalone-workflow.md +12 -6
  118. package/docs/troubleshooting.md +5 -1
  119. package/docs/tutorial.md +5 -0
  120. package/docs/typescript.md +87 -0
  121. package/package.json +35 -29
  122. package/scripts/install/create_root_config.mjs +0 -36
  123. package/src/README.md +0 -17
  124. package/src/application/session.mjs +0 -435
  125. package/src/baseline/artifact.mjs +0 -684
  126. package/src/baseline/builder.mjs +0 -164
  127. package/src/baseline/input_discovery.mjs +0 -155
  128. package/src/baseline/policy_review.mjs +0 -92
  129. package/src/baseline/preparation.mjs +0 -323
  130. package/src/baseline/privacy_engine.mjs +0 -952
  131. package/src/baseline/sanitization_policy.mjs +0 -299
  132. package/src/baseline/schema_snapshot.mjs +0 -113
  133. package/src/cli/arguments.mjs +0 -126
  134. package/src/cli/guided.mjs +0 -814
  135. package/src/cli/init_command.mjs +0 -94
  136. package/src/cli/rehearsal.mjs +0 -970
  137. package/src/cli/renderers.mjs +0 -626
  138. package/src/cli/runtime_commands.mjs +0 -707
  139. package/src/cli/source_commands.mjs +0 -347
  140. package/src/cli/terminal.mjs +0 -275
  141. package/src/identity/claim.mjs +0 -1163
  142. package/src/identity/provider_configuration.mjs +0 -177
  143. package/src/identity/storage.mjs +0 -165
  144. package/src/project/configuration.mjs +0 -1337
  145. package/src/project/install_scaffold.mjs +0 -291
  146. package/src/project/setup.mjs +0 -466
  147. package/src/project/support_report.mjs +0 -105
  148. package/src/runtime/cleanup.mjs +0 -381
  149. package/src/runtime/migration_history.mjs +0 -220
  150. package/src/runtime/plan.mjs +0 -722
  151. package/src/runtime/policy.mjs +0 -438
  152. package/src/runtime/restore.mjs +0 -337
  153. package/src/runtime/service_environment.mjs +0 -82
  154. package/src/runtime/topology.mjs +0 -177
  155. package/src/shared/diagnostics.mjs +0 -207
  156. package/src/shared/node_runtime.mjs +0 -31
  157. package/src/shared/operation_guard.mjs +0 -162
  158. package/src/shared/process_environment.mjs +0 -68
  159. package/src/source/access.mjs +0 -753
  160. package/src/source/asset_transfer.mjs +0 -207
  161. package/src/source/baseline.mjs +0 -482
  162. package/src/source/external_reader.mjs +0 -255
  163. package/src/source/postgresql_access.mjs +0 -581
  164. package/src/source/privacy_paths.mjs +0 -38
  165. package/src/targets/postgresql.mjs +0 -808
  166. package/src/targets/supabase.mjs +0 -840
  167. package/src/targets/supabase_environment.mjs +0 -229
  168. package/src/targets/target.mjs +0 -66
  169. /package/{src/project → types}/configuration.d.mts +0 -0
@@ -0,0 +1,227 @@
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
+ import { createHash } from "node:crypto";
6
+ import { validateExecutablePrivacyPolicy, } from "./privacy_engine.mjs";
7
+ export const SANITIZATION_ACTIONS = Object.freeze({
8
+ KEEP: "KEEP",
9
+ PSEUDONYMIZE: "PSEUDONYMIZE",
10
+ REPLACE: "REPLACE",
11
+ EXCLUDE: "EXCLUDE",
12
+ DERIVE: "DERIVE",
13
+ });
14
+ export const EXCLUDED_VALUE = Symbol("REHEARSAL_EXCLUDED_VALUE");
15
+ const isPolicyObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
16
+ const assertPolicyObject = (value, label) => {
17
+ if (!isPolicyObject(value))
18
+ throw new Error(`${label} must be an object.`);
19
+ return value;
20
+ };
21
+ const actionAliases = new Map([
22
+ ["KEEP", SANITIZATION_ACTIONS.KEEP],
23
+ ["KEEP EXACTLY", SANITIZATION_ACTIONS.KEEP],
24
+ ["PSEUDONYMIZE", SANITIZATION_ACTIONS.PSEUDONYMIZE],
25
+ ["REPLACE", SANITIZATION_ACTIONS.REPLACE],
26
+ ["REPLACE WITH SYNTHETIC", SANITIZATION_ACTIONS.REPLACE],
27
+ ["EXCLUDE", SANITIZATION_ACTIONS.EXCLUDE],
28
+ ["DERIVE", SANITIZATION_ACTIONS.DERIVE],
29
+ ]);
30
+ const identifierPattern = /^[a-z][a-z0-9_]{0,62}$/u;
31
+ const assertIdentifier = (value, label) => {
32
+ if (typeof value !== "string" || !identifierPattern.test(value)) {
33
+ throw new Error(`${label} must be a lowercase PostgreSQL identifier.`);
34
+ }
35
+ return value;
36
+ };
37
+ const tableSchema = (table) => assertIdentifier(table.schema ?? "public", "Sanitization policy table schema");
38
+ export const normalizeSanitizationAction = (action) => {
39
+ const normalized = actionAliases.get(String(action ?? "")
40
+ .trim()
41
+ .toUpperCase());
42
+ if (!normalized) {
43
+ throw new Error(`Unknown Rehearsal sanitization action: ${action}.`);
44
+ }
45
+ return normalized;
46
+ };
47
+ export const validateRuntimeSanitizationPolicy = (policy) => {
48
+ if (!isPolicyObject(policy)) {
49
+ throw new Error("A Rehearsal sanitization policy object is required.");
50
+ }
51
+ if (policy.draft === true) {
52
+ throw new Error("The sanitization policy is still a REVIEW REQUIRED draft.");
53
+ }
54
+ if (policy.policyVersion === 2) {
55
+ return validateExecutablePrivacyPolicy(policy);
56
+ }
57
+ if (policy.policyVersion !== 1) {
58
+ throw new Error("The sanitization policy must use policyVersion 1 or 2.");
59
+ }
60
+ if (typeof policy.migrationCutoff !== "string" ||
61
+ !/^\d{14}$/u.test(policy.migrationCutoff)) {
62
+ throw new Error("The sanitization policy must declare a timestamp migrationCutoff.");
63
+ }
64
+ if (!Array.isArray(policy.tables) || policy.tables.length === 0) {
65
+ throw new Error("The sanitization policy does not list tables.");
66
+ }
67
+ const tables = new Set();
68
+ for (const tableValue of policy.tables) {
69
+ const table = assertPolicyObject(tableValue, "Sanitization policy table");
70
+ const schemaName = assertIdentifier(tableSchema(table), "Sanitization policy table schema");
71
+ const tableName = assertIdentifier(table?.name, "Sanitization policy table name");
72
+ const key = schemaName === "public" ? tableName : `${schemaName}.${tableName}`;
73
+ if (tables.has(key)) {
74
+ throw new Error(`Sanitization policy duplicates table ${key}.`);
75
+ }
76
+ tables.add(key);
77
+ if (typeof table.sourceRows !== "string" ||
78
+ !["STREAM AND SANITIZE", "EXCLUDE"].includes(table.sourceRows)) {
79
+ throw new Error(`Sanitization policy table ${tableName} has an invalid sourceRows decision.`);
80
+ }
81
+ if (!Array.isArray(table.columns) || table.columns.length === 0) {
82
+ throw new Error(`Sanitization policy table ${tableName} must list its columns.`);
83
+ }
84
+ const columns = new Set();
85
+ for (const columnValue of table.columns) {
86
+ const column = assertPolicyObject(columnValue, `Sanitization policy column in ${tableName}`);
87
+ const columnName = assertIdentifier(column?.name, `Sanitization policy column in ${tableName}`);
88
+ if (columns.has(columnName)) {
89
+ throw new Error(`Sanitization policy duplicates column ${tableName}.${columnName}.`);
90
+ }
91
+ columns.add(columnName);
92
+ normalizeSanitizationAction(column.action);
93
+ if (typeof column.generated !== "string" ||
94
+ !["ALWAYS", "NEVER"].includes(column.generated)) {
95
+ throw new Error(`Sanitization policy column ${tableName}.${columnName} must classify generated as ALWAYS or NEVER.`);
96
+ }
97
+ if (typeof column.identity !== "string" ||
98
+ !["YES", "NO"].includes(column.identity)) {
99
+ throw new Error(`Sanitization policy column ${tableName}.${columnName} must classify identity as YES or NO.`);
100
+ }
101
+ if (column.foreignKey !== null) {
102
+ if (!column.foreignKey ||
103
+ typeof column.foreignKey !== "object" ||
104
+ Array.isArray(column.foreignKey)) {
105
+ throw new Error(`Sanitization policy column ${tableName}.${columnName} has an invalid foreignKey decision.`);
106
+ }
107
+ const foreignKey = column.foreignKey;
108
+ assertIdentifier(foreignKey.schema, `Foreign-key schema for ${tableName}.${columnName}`);
109
+ assertIdentifier(foreignKey.table, `Foreign-key table for ${tableName}.${columnName}`);
110
+ assertIdentifier(foreignKey.column, `Foreign-key column for ${tableName}.${columnName}`);
111
+ }
112
+ }
113
+ }
114
+ return policy;
115
+ };
116
+ export const readBoundRuntimeSanitizationPolicy = ({ bytes, expectedSha256, }) => {
117
+ const source = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
118
+ const policy = validateRuntimeSanitizationPolicy(JSON.parse(source.toString("utf8")));
119
+ const actualSha256 = createHash("sha256").update(source).digest("hex");
120
+ if (actualSha256 !== expectedSha256) {
121
+ throw new Error("The active sanitization policy does not match the reviewed baseline policy checksum.");
122
+ }
123
+ return policy;
124
+ };
125
+ const indexTables = (tables, label) => {
126
+ if (!Array.isArray(tables))
127
+ throw new Error(`${label} must be an array.`);
128
+ const index = new Map();
129
+ for (const tableValue of tables) {
130
+ const table = assertPolicyObject(tableValue, `${label} table`);
131
+ const schema = assertIdentifier(tableSchema(table), `${label} table schema`);
132
+ const name = assertIdentifier(table?.name, `${label} table name`);
133
+ const key = schema === "public" ? name : `${schema}.${name}`;
134
+ if (index.has(key))
135
+ throw new Error(`${label} duplicates table ${key}.`);
136
+ if (!Array.isArray(table.columns)) {
137
+ throw new Error(`${label} table ${name} must list its columns.`);
138
+ }
139
+ const columns = new Map();
140
+ for (const column of table.columns) {
141
+ const columnName = assertIdentifier(typeof column === "string"
142
+ ? column
143
+ : isPolicyObject(column)
144
+ ? column.name
145
+ : undefined, `${label} column name`);
146
+ if (columns.has(columnName)) {
147
+ throw new Error(`${label} duplicates column ${name}.${columnName}.`);
148
+ }
149
+ columns.set(columnName, column);
150
+ }
151
+ index.set(key, { table: { ...table, schema }, columns });
152
+ }
153
+ return index;
154
+ };
155
+ export const validateSanitizationCoverage = ({ policy, schemaTables, }) => {
156
+ if (!isPolicyObject(policy)) {
157
+ throw new Error("A Rehearsal sanitization policy object is required.");
158
+ }
159
+ const schema = indexTables(schemaTables, "Schema inventory");
160
+ const classified = indexTables(policy.tables, "Sanitization policy");
161
+ const normalizedTables = [];
162
+ for (const [tableName, schemaEntry] of schema) {
163
+ const policyEntry = classified.get(tableName);
164
+ if (!policyEntry) {
165
+ throw new Error(`Sanitization policy does not classify table ${tableName}.`);
166
+ }
167
+ const normalizedColumns = [];
168
+ for (const columnName of schemaEntry.columns.keys()) {
169
+ const column = policyEntry.columns.get(columnName);
170
+ if (!column) {
171
+ throw new Error(`Sanitization policy does not classify column ${tableName}.${columnName}.`);
172
+ }
173
+ const policyColumn = assertPolicyObject(column, `Sanitization policy column ${tableName}.${columnName}`);
174
+ normalizedColumns.push({
175
+ ...policyColumn,
176
+ name: columnName,
177
+ action: normalizeSanitizationAction(policyColumn.action),
178
+ });
179
+ }
180
+ for (const columnName of policyEntry.columns.keys()) {
181
+ if (!schemaEntry.columns.has(columnName)) {
182
+ throw new Error(`Sanitization policy references unknown column ${tableName}.${columnName}.`);
183
+ }
184
+ }
185
+ normalizedTables.push({
186
+ ...policyEntry.table,
187
+ schema: tableSchema(policyEntry.table),
188
+ columns: normalizedColumns,
189
+ });
190
+ }
191
+ for (const tableName of classified.keys()) {
192
+ if (!schema.has(tableName)) {
193
+ throw new Error(`Sanitization policy references unknown table ${tableName}.`);
194
+ }
195
+ }
196
+ return Object.freeze({
197
+ policyVersion: policy.policyVersion,
198
+ tableCount: normalizedTables.length,
199
+ columnCount: normalizedTables.reduce((total, table) => total + table.columns.length, 0),
200
+ tables: Object.freeze(normalizedTables),
201
+ });
202
+ };
203
+ export const applySanitizationAction = ({ action, value, replace, pseudonymize, derive, context = {}, }) => {
204
+ switch (normalizeSanitizationAction(action)) {
205
+ case SANITIZATION_ACTIONS.KEEP:
206
+ return value;
207
+ case SANITIZATION_ACTIONS.EXCLUDE:
208
+ return EXCLUDED_VALUE;
209
+ case SANITIZATION_ACTIONS.REPLACE:
210
+ if (typeof replace !== "function") {
211
+ throw new Error("REPLACE requires a project-owned replacement function.");
212
+ }
213
+ return replace(value, context);
214
+ case SANITIZATION_ACTIONS.PSEUDONYMIZE:
215
+ if (typeof pseudonymize !== "function") {
216
+ throw new Error("PSEUDONYMIZE requires a project-owned pseudonymization function.");
217
+ }
218
+ return pseudonymize(value, context);
219
+ case SANITIZATION_ACTIONS.DERIVE:
220
+ if (typeof derive !== "function") {
221
+ throw new Error("DERIVE requires a project-owned derivation function.");
222
+ }
223
+ return derive(value, context);
224
+ default:
225
+ throw new Error("Unreachable Rehearsal sanitization action.");
226
+ }
227
+ };
@@ -0,0 +1,19 @@
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
+ export declare const assertProductionSchemaDump: (schemaSql: string) => string;
6
+ export declare const compareProductionSchemaDumps: ({ expected, actual, }: {
7
+ expected: string;
8
+ actual: string;
9
+ }) => string;
10
+ export declare const captureSupabasePublicSchema: ({ repositoryRoot, artifactRoot, arguments_, }: {
11
+ repositoryRoot: string;
12
+ artifactRoot: string;
13
+ arguments_: readonly string[];
14
+ }) => Promise<string>;
15
+ export declare const captureLocalPublicSchema: ({ repositoryRoot, artifactRoot, workdir, }: {
16
+ repositoryRoot: string;
17
+ artifactRoot: string;
18
+ workdir: string;
19
+ }) => Promise<string>;
@@ -0,0 +1,79 @@
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
+ import { spawnSync } from "node:child_process";
6
+ import { createHash } from "node:crypto";
7
+ import { chmod, mkdir, mkdtemp, readFile, rm } from "node:fs/promises";
8
+ import { basename, join, resolve, sep } from "node:path";
9
+ import { createCleanProcessEnvironment } from "../shared/process_environment.mjs";
10
+ const maximumSchemaBytes = 64 * 1024 * 1024;
11
+ const assertArtifactRoot = (artifactRoot) => {
12
+ const resolved = resolve(artifactRoot);
13
+ if (basename(resolved) !== ".rehearsal" || resolved === sep) {
14
+ throw new Error(`Refusing an unsafe Rehearsal artifact root: ${artifactRoot}`);
15
+ }
16
+ return resolved;
17
+ };
18
+ export const assertProductionSchemaDump = (schemaSql) => {
19
+ if (typeof schemaSql !== "string" || schemaSql.length === 0) {
20
+ throw new Error("The Rehearsal production schema snapshot is empty.");
21
+ }
22
+ if (Buffer.byteLength(schemaSql) > maximumSchemaBytes) {
23
+ throw new Error("The Rehearsal production schema snapshot is too large.");
24
+ }
25
+ if (!schemaSql.includes('CREATE SCHEMA IF NOT EXISTS "public";')) {
26
+ throw new Error("The Rehearsal production schema snapshot does not define public.");
27
+ }
28
+ if (/^COPY\s|^\\connect\s|^CREATE DATABASE\s/mu.test(schemaSql)) {
29
+ throw new Error("The Rehearsal production schema snapshot contains data or database switching commands.");
30
+ }
31
+ return schemaSql;
32
+ };
33
+ const comparableSchemaDump = (schemaSql) => assertProductionSchemaDump(schemaSql)
34
+ .split("\n")
35
+ .map((line) => line.trimEnd())
36
+ .filter(Boolean)
37
+ .map((line) => {
38
+ const checkConstraint = line.match(/^(\s*CONSTRAINT "[^"]+" CHECK ).*(,?)$/u);
39
+ return checkConstraint
40
+ ? `${checkConstraint[1]}<database-normalized-expression>${checkConstraint[2]}`
41
+ : line;
42
+ })
43
+ .join("\n");
44
+ export const compareProductionSchemaDumps = ({ expected, actual, }) => {
45
+ const expectedComparable = comparableSchemaDump(expected);
46
+ const actualComparable = comparableSchemaDump(actual);
47
+ if (expectedComparable !== actualComparable) {
48
+ throw new Error("The local Rehearsal public schema does not structurally match the production snapshot.");
49
+ }
50
+ return createHash("sha256").update(actualComparable).digest("hex");
51
+ };
52
+ export const captureSupabasePublicSchema = async ({ repositoryRoot, artifactRoot, arguments_, }) => {
53
+ const root = assertArtifactRoot(artifactRoot);
54
+ await mkdir(root, { recursive: true, mode: 0o700 });
55
+ const temporaryDirectory = await mkdtemp(join(root, ".schema-capture-"));
56
+ const destination = join(temporaryDirectory, "production-schema.sql");
57
+ try {
58
+ const result = spawnSync("supabase", [...arguments_, "--schema", "public", "--file", destination], {
59
+ cwd: repositoryRoot,
60
+ env: createCleanProcessEnvironment(),
61
+ encoding: "utf8",
62
+ stdio: ["ignore", "pipe", "pipe"],
63
+ maxBuffer: 4 * 1024 * 1024,
64
+ });
65
+ if (result.error || result.status !== 0) {
66
+ throw new Error("The exact Rehearsal schema snapshot could not be captured.", { cause: result.error });
67
+ }
68
+ await chmod(destination, 0o600);
69
+ return assertProductionSchemaDump(await readFile(destination, "utf8"));
70
+ }
71
+ finally {
72
+ await rm(temporaryDirectory, { recursive: true, force: true });
73
+ }
74
+ };
75
+ export const captureLocalPublicSchema = ({ repositoryRoot, artifactRoot, workdir, }) => captureSupabasePublicSchema({
76
+ repositoryRoot,
77
+ artifactRoot,
78
+ arguments_: ["db", "dump", "--local", "--workdir", workdir],
79
+ });
@@ -0,0 +1,43 @@
1
+ /** Parse and classify the stable Rehearsal command-line contract. */
2
+ export type RehearsalVerbosity = "normal" | "verbose" | "debug";
3
+ export interface RehearsalCliFlags {
4
+ json: boolean;
5
+ help: boolean;
6
+ version: boolean;
7
+ verbosity: RehearsalVerbosity;
8
+ dryRun: boolean;
9
+ write: boolean;
10
+ plain: boolean;
11
+ configPath: string | undefined;
12
+ confirmation: string | undefined;
13
+ recordsPath: string | undefined;
14
+ ledgerPath: string | undefined;
15
+ assetsPath: string | undefined;
16
+ target: string | undefined;
17
+ includeRuntime: boolean;
18
+ includeImages: boolean;
19
+ cleanupConfirmation: string | undefined;
20
+ sourceAccessConfirmation: string | undefined;
21
+ sourceRetirementConfirmation: string | undefined;
22
+ refreshConfirmation: string | undefined;
23
+ identityConfirmation: string | undefined;
24
+ identityName: string | undefined;
25
+ runtimeTopology: unknown | undefined;
26
+ targetConfirmations: unknown | undefined;
27
+ exitGuidedSession: boolean;
28
+ guided: boolean;
29
+ setupPlan?: unknown;
30
+ preparationPlan?: unknown;
31
+ cleanupPlan?: unknown;
32
+ }
33
+ export interface ParsedArguments {
34
+ flags: RehearsalCliFlags;
35
+ positionals: string[];
36
+ }
37
+ export declare const parseArguments: (arguments_: readonly string[]) => ParsedArguments;
38
+ export declare const resetGuidedFlags: (flags: RehearsalCliFlags) => void;
39
+ export declare const commandMutatesProject: ({ command, flags, guided, }: {
40
+ command: string;
41
+ flags: RehearsalCliFlags;
42
+ guided: boolean;
43
+ }) => boolean;
@@ -0,0 +1,135 @@
1
+ /** Parse and classify the stable Rehearsal command-line contract. */
2
+ export const parseArguments = (arguments_) => {
3
+ const flags = {
4
+ json: false,
5
+ help: false,
6
+ version: false,
7
+ verbosity: "normal",
8
+ dryRun: false,
9
+ write: false,
10
+ plain: false,
11
+ configPath: undefined,
12
+ confirmation: undefined,
13
+ recordsPath: undefined,
14
+ ledgerPath: undefined,
15
+ assetsPath: undefined,
16
+ target: undefined,
17
+ includeRuntime: false,
18
+ includeImages: false,
19
+ cleanupConfirmation: undefined,
20
+ sourceAccessConfirmation: undefined,
21
+ sourceRetirementConfirmation: undefined,
22
+ refreshConfirmation: undefined,
23
+ identityConfirmation: undefined,
24
+ identityName: undefined,
25
+ runtimeTopology: undefined,
26
+ targetConfirmations: undefined,
27
+ exitGuidedSession: false,
28
+ guided: false,
29
+ };
30
+ const positionals = [];
31
+ for (const argument of arguments_) {
32
+ if (argument === "--json")
33
+ flags.json = true;
34
+ else if (argument === "--help" || argument === "-h")
35
+ flags.help = true;
36
+ else if (argument === "--version" || argument === "-V")
37
+ flags.version = true;
38
+ else if (argument === "--verbose")
39
+ flags.verbosity = "verbose";
40
+ else if (argument === "--debug")
41
+ flags.verbosity = "debug";
42
+ else if (argument === "--dry-run")
43
+ flags.dryRun = true;
44
+ else if (argument === "--write")
45
+ flags.write = true;
46
+ else if (argument === "--plain")
47
+ flags.plain = true;
48
+ else if (argument.startsWith("--config="))
49
+ flags.configPath = argument.slice("--config=".length);
50
+ else if (argument.startsWith("--confirm-candidates="))
51
+ flags.confirmation = argument.slice("--confirm-candidates=".length);
52
+ else if (argument.startsWith("--confirm-cleanup="))
53
+ flags.cleanupConfirmation = argument.slice("--confirm-cleanup=".length);
54
+ else if (argument.startsWith("--confirm-source-access="))
55
+ flags.sourceAccessConfirmation = argument.slice("--confirm-source-access=".length);
56
+ else if (argument.startsWith("--confirm-source-retirement="))
57
+ flags.sourceRetirementConfirmation = argument.slice("--confirm-source-retirement=".length);
58
+ else if (argument.startsWith("--confirm-refresh="))
59
+ flags.refreshConfirmation = argument.slice("--confirm-refresh=".length);
60
+ else if (argument.startsWith("--confirm-identity="))
61
+ flags.identityConfirmation = argument.slice("--confirm-identity=".length);
62
+ else if (argument.startsWith("--identity="))
63
+ flags.identityName = argument.slice("--identity=".length);
64
+ else if (argument.startsWith("--records="))
65
+ flags.recordsPath = argument.slice("--records=".length);
66
+ else if (argument.startsWith("--ledger="))
67
+ flags.ledgerPath = argument.slice("--ledger=".length);
68
+ else if (argument.startsWith("--assets="))
69
+ flags.assetsPath = argument.slice("--assets=".length);
70
+ else if (argument.startsWith("--target="))
71
+ flags.target = argument.slice("--target=".length);
72
+ else if (argument === "--include-runtime")
73
+ flags.includeRuntime = true;
74
+ else if (argument === "--include-images")
75
+ flags.includeImages = true;
76
+ else if (argument.startsWith("--"))
77
+ throw new Error(`Unknown Rehearsal option: ${argument}.`);
78
+ else
79
+ positionals.push(argument);
80
+ }
81
+ return { flags, positionals };
82
+ };
83
+ export const resetGuidedFlags = (flags) => {
84
+ Object.assign(flags, {
85
+ dryRun: false,
86
+ write: false,
87
+ confirmation: undefined,
88
+ recordsPath: undefined,
89
+ ledgerPath: undefined,
90
+ assetsPath: undefined,
91
+ target: undefined,
92
+ includeRuntime: false,
93
+ includeImages: false,
94
+ cleanupConfirmation: undefined,
95
+ sourceAccessConfirmation: undefined,
96
+ sourceRetirementConfirmation: undefined,
97
+ refreshConfirmation: undefined,
98
+ identityConfirmation: undefined,
99
+ identityName: undefined,
100
+ runtimeTopology: undefined,
101
+ targetConfirmations: undefined,
102
+ exitGuidedSession: false,
103
+ setupPlan: undefined,
104
+ preparationPlan: undefined,
105
+ cleanupPlan: undefined,
106
+ });
107
+ };
108
+ export const commandMutatesProject = ({ command, flags, guided, }) => {
109
+ if (guided)
110
+ return true;
111
+ if (["setup", "init", "baseline prepare", "privacy key"].includes(command))
112
+ return flags.write;
113
+ if (command === "cleanup")
114
+ return flags.write;
115
+ if (command === "source apply")
116
+ return Boolean(flags.sourceAccessConfirmation);
117
+ if (command === "source retire")
118
+ return Boolean(flags.sourceRetirementConfirmation);
119
+ if (command === "refresh")
120
+ return Boolean(flags.refreshConfirmation);
121
+ if (command === "identity claim")
122
+ return Boolean(flags.identityConfirmation);
123
+ if (command === "run")
124
+ return !flags.dryRun;
125
+ return [
126
+ "baseline create",
127
+ "baseline refresh",
128
+ "open",
129
+ "start",
130
+ "migrate",
131
+ "reset",
132
+ "stop",
133
+ "discard",
134
+ ].includes(command);
135
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * State-aware interactive home screen and first-run flows.
3
+ */
4
+ import type { RehearsalConfigPathOptions } from "../project/configuration.mjs";
5
+ import type { RehearsalCliFlags } from "./arguments.mjs";
6
+ import type { RuntimeTopologyTarget } from "../runtime/topology.mjs";
7
+ import type { RehearsalCleanupPlan } from "../runtime/cleanup.mjs";
8
+ interface TopologySummary {
9
+ readonly topology: {
10
+ readonly dependents: readonly unknown[];
11
+ readonly targets: readonly RuntimeTopologyTarget[];
12
+ };
13
+ }
14
+ type GuidedCleanupPlan = RehearsalCleanupPlan | Readonly<{
15
+ digest: string;
16
+ targets: readonly Readonly<{
17
+ name: string;
18
+ plan: RehearsalCleanupPlan;
19
+ }>[];
20
+ }>;
21
+ interface GuidedRefreshPlan {
22
+ readonly digest: string;
23
+ readonly review: {
24
+ readonly currentBaseline: string;
25
+ readonly source: {
26
+ readonly readerExpiresAt: string;
27
+ };
28
+ readonly runtimeTargets: readonly string[];
29
+ readonly retention: {
30
+ readonly generations: number;
31
+ readonly removeAfterReplacement: readonly string[];
32
+ };
33
+ readonly steps: readonly string[];
34
+ };
35
+ }
36
+ export declare const createGuidedHome: ({ projectRoot, topologyCandidateSummary, buildRefreshWorkflowPlan, planRuntimeStackCleanup, getLastGuidedDetails, }: {
37
+ projectRoot: string;
38
+ topologyCandidateSummary: (options: RehearsalConfigPathOptions) => Promise<TopologySummary>;
39
+ buildRefreshWorkflowPlan: (options: RehearsalConfigPathOptions) => Promise<{
40
+ plan: GuidedRefreshPlan;
41
+ }>;
42
+ planRuntimeStackCleanup: (input: {
43
+ flags: RehearsalCliFlags;
44
+ planOptions: RehearsalConfigPathOptions;
45
+ }) => Promise<GuidedCleanupPlan>;
46
+ getLastGuidedDetails: () => string | undefined;
47
+ }) => ({ flags, planOptions, }: {
48
+ flags: RehearsalCliFlags;
49
+ planOptions: RehearsalConfigPathOptions;
50
+ }) => Promise<string | null>;
51
+ export {};