@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
@@ -1,255 +0,0 @@
1
- /** Verify an externally provisioned PostgreSQL reader without source writes. */
2
-
3
- import pg from "pg";
4
- import { externalViewDefinitionFingerprint } from "./access.mjs";
5
-
6
- const { Client } = pg;
7
-
8
- const connect = async (connectionString, clientFactory) => {
9
- const client = clientFactory
10
- ? await clientFactory(connectionString)
11
- : new Client({
12
- connectionString,
13
- application_name: "rehearsal-external-source-verification",
14
- });
15
- if (typeof client.connect === "function") await client.connect();
16
- return client;
17
- };
18
-
19
- const columnKey = (schema, relation, column) =>
20
- `${schema}\0${relation}\0${column}`;
21
-
22
- const expectedReadableColumns = (plan) =>
23
- new Set([
24
- ...plan.policy.relations.flatMap((relation) =>
25
- relation.columns.map((column) =>
26
- columnKey(plan.policy.exportSchema, relation.view, column),
27
- ),
28
- ),
29
- ...[
30
- plan.policy.migrationLedger.versionColumn,
31
- plan.policy.migrationLedger.nameColumn,
32
- plan.policy.migrationLedger.statementsColumn,
33
- ].map((column) =>
34
- columnKey(
35
- plan.policy.migrationLedger.schema,
36
- plan.policy.migrationLedger.table,
37
- column,
38
- ),
39
- ),
40
- ]);
41
-
42
- const count = (result, label) => {
43
- const value = Number(result.rows[0]?.count);
44
- if (!Number.isSafeInteger(value) || value < 0) {
45
- throw new Error(`External source reader ${label} check was inconclusive.`);
46
- }
47
- return value;
48
- };
49
-
50
- export const verifyExternalPostgresqlReader = async ({
51
- plan,
52
- connectionString,
53
- clientFactory,
54
- now = new Date(),
55
- }) => {
56
- if (plan.policy.reader.mode !== "external") {
57
- throw new Error("External reader verification requires external mode.");
58
- }
59
- const client = await connect(connectionString, clientFactory);
60
- let transactionOpen = false;
61
- try {
62
- await client.query("begin transaction read only");
63
- transactionOpen = true;
64
- const roleResult = await client.query(
65
- `select rolname as role, rolcanlogin, rolvaliduntil,
66
- rolsuper, rolcreaterole, rolcreatedb, rolreplication, rolbypassrls
67
- from pg_roles where rolname = current_user`,
68
- );
69
- const role = roleResult.rows[0];
70
- if (!role || role.role !== plan.policy.reader.role || !role.rolcanlogin) {
71
- throw new Error(
72
- "External source credential does not authenticate as the reviewed login role.",
73
- );
74
- }
75
- if (
76
- role.rolsuper ||
77
- role.rolcreaterole ||
78
- role.rolcreatedb ||
79
- role.rolreplication ||
80
- role.rolbypassrls
81
- ) {
82
- throw new Error("External source reader has privileged role attributes.");
83
- }
84
- const expiresAt = new Date(role.rolvaliduntil);
85
- const maximumExpiration =
86
- now.valueOf() + plan.policy.reader.maximumValidForMinutes * 60_000;
87
- if (
88
- !Number.isFinite(expiresAt.valueOf()) ||
89
- expiresAt.valueOf() <= now.valueOf() ||
90
- expiresAt.valueOf() > maximumExpiration
91
- ) {
92
- throw new Error(
93
- "External source reader must have a future database-enforced expiration within the reviewed limit.",
94
- );
95
- }
96
-
97
- const memberships = await client.query(
98
- `select count(*)::integer as count
99
- from pg_auth_members membership
100
- join pg_roles member on member.oid = membership.member
101
- where member.rolname = current_user`,
102
- );
103
- if (count(memberships, "role membership") !== 0) {
104
- throw new Error(
105
- "External source reader belongs to another database role.",
106
- );
107
- }
108
-
109
- const databaseWrites = await client.query(
110
- `select count(*)::integer as count
111
- where has_database_privilege(current_user, current_database(), 'CREATE')`,
112
- );
113
- const schemaWrites = await client.query(
114
- `select count(*)::integer as count
115
- from pg_namespace
116
- where nspname not like 'pg_temp_%'
117
- and has_schema_privilege(current_user, oid, 'CREATE')`,
118
- );
119
- const relationWrites = await client.query(
120
- `select count(*)::integer as count
121
- from pg_class relation
122
- join pg_namespace namespace on namespace.oid = relation.relnamespace
123
- where namespace.nspname not in ('pg_catalog', 'information_schema')
124
- and relation.relkind in ('r', 'p', 'v', 'm', 'f')
125
- and (has_table_privilege(current_user, relation.oid, 'INSERT')
126
- or has_table_privilege(current_user, relation.oid, 'UPDATE')
127
- or has_table_privilege(current_user, relation.oid, 'DELETE')
128
- or has_table_privilege(current_user, relation.oid, 'TRUNCATE')
129
- or has_table_privilege(current_user, relation.oid, 'REFERENCES')
130
- or has_table_privilege(current_user, relation.oid, 'TRIGGER'))`,
131
- );
132
- const columnWrites = await client.query(
133
- `select count(*)::integer as count
134
- from pg_class relation
135
- join pg_namespace namespace on namespace.oid = relation.relnamespace
136
- join pg_attribute attribute on attribute.attrelid = relation.oid
137
- where namespace.nspname not in ('pg_catalog', 'information_schema')
138
- and relation.relkind in ('r', 'p', 'v', 'm', 'f')
139
- and attribute.attnum > 0 and not attribute.attisdropped
140
- and (has_column_privilege(current_user, relation.oid, attribute.attnum, 'INSERT')
141
- or has_column_privilege(current_user, relation.oid, attribute.attnum, 'UPDATE')
142
- or has_column_privilege(current_user, relation.oid, attribute.attnum, 'REFERENCES'))`,
143
- );
144
- const sequenceAccess = await client.query(
145
- `select count(*)::integer as count
146
- from pg_class sequence
147
- join pg_namespace namespace on namespace.oid = sequence.relnamespace
148
- where namespace.nspname not in ('pg_catalog', 'information_schema')
149
- and sequence.relkind = 'S'
150
- and (has_sequence_privilege(current_user, sequence.oid, 'SELECT')
151
- or has_sequence_privilege(current_user, sequence.oid, 'USAGE')
152
- or has_sequence_privilege(current_user, sequence.oid, 'UPDATE'))`,
153
- );
154
- if (
155
- [
156
- databaseWrites,
157
- schemaWrites,
158
- relationWrites,
159
- columnWrites,
160
- sequenceAccess,
161
- ].some((result) => count(result, "write or sequence privilege") !== 0)
162
- ) {
163
- throw new Error(
164
- "External source reader has database write or sequence privileges.",
165
- );
166
- }
167
-
168
- const readable = await client.query(
169
- `select namespace.nspname as schema, relation.relname as relation,
170
- attribute.attname as column
171
- from pg_class relation
172
- join pg_namespace namespace on namespace.oid = relation.relnamespace
173
- join pg_attribute attribute on attribute.attrelid = relation.oid
174
- where namespace.nspname not in ('pg_catalog', 'information_schema')
175
- and namespace.nspname not like 'pg_toast%'
176
- and relation.relkind in ('r', 'p', 'v', 'm', 'f')
177
- and attribute.attnum > 0 and not attribute.attisdropped
178
- and has_column_privilege(current_user, relation.oid, attribute.attnum, 'SELECT')
179
- order by namespace.nspname, relation.relname, attribute.attnum`,
180
- );
181
- const expected = expectedReadableColumns(plan);
182
- const actual = new Set(
183
- readable.rows.map((row) =>
184
- columnKey(row.schema, row.relation, row.column),
185
- ),
186
- );
187
- if (
188
- actual.size !== expected.size ||
189
- [...actual].some((entry) => !expected.has(entry))
190
- ) {
191
- throw new Error(
192
- "External source reader's readable columns do not exactly match the reviewed export surface.",
193
- );
194
- }
195
-
196
- for (const relation of plan.policy.relations) {
197
- const view = await client.query(
198
- `select pg_get_viewdef(relation.oid, false) as definition,
199
- relation.reloptions,
200
- owner.rolname as owner
201
- from pg_class relation
202
- join pg_namespace namespace on namespace.oid = relation.relnamespace
203
- join pg_roles owner on owner.oid = relation.relowner
204
- where namespace.nspname = $1 and relation.relname = $2
205
- and relation.relkind = 'v'`,
206
- [plan.policy.exportSchema, relation.view],
207
- );
208
- const actual = view.rows[0];
209
- if (!actual) {
210
- throw new Error("An externally reviewed export view is missing.");
211
- }
212
- if (
213
- !Array.isArray(actual.reloptions) ||
214
- !actual.reloptions.includes("security_barrier=true")
215
- ) {
216
- throw new Error(
217
- "External export views must enable PostgreSQL security_barrier.",
218
- );
219
- }
220
- if (actual.owner === plan.policy.reader.role) {
221
- throw new Error("External source reader must not own an export view.");
222
- }
223
- if (
224
- externalViewDefinitionFingerprint(actual.definition) !==
225
- relation.viewDefinitionSha256
226
- ) {
227
- throw new Error(
228
- "External export view definition does not match its reviewed fingerprint.",
229
- );
230
- }
231
- }
232
-
233
- const securityDefiners = await client.query(
234
- `select count(*)::integer as count
235
- from pg_proc procedure
236
- join pg_namespace namespace on namespace.oid = procedure.pronamespace
237
- where namespace.nspname not in ('pg_catalog', 'information_schema')
238
- and procedure.prosecdef
239
- and has_function_privilege(current_user, procedure.oid, 'EXECUTE')`,
240
- );
241
- if (count(securityDefiners, "security-definer function") !== 0) {
242
- throw new Error(
243
- "External source reader can execute a non-system security-definer function.",
244
- );
245
- }
246
- await client.query("rollback");
247
- transactionOpen = false;
248
- return Object.freeze({ expiresAt: expiresAt.toISOString() });
249
- } catch (error) {
250
- if (transactionOpen) await client.query("rollback").catch(() => undefined);
251
- throw error;
252
- } finally {
253
- await client.end?.();
254
- }
255
- };