@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,278 @@
1
+ /** Verify an externally provisioned PostgreSQL reader without source writes. */
2
+ import pg from "pg";
3
+ import { RehearsalError } from "../shared/diagnostics.mjs";
4
+ import { externalViewDefinitionFingerprint } from "./access.mjs";
5
+ const { Client } = pg;
6
+ const connect = async (connectionString, clientFactory) => {
7
+ const client = clientFactory
8
+ ? await clientFactory(connectionString)
9
+ : new Client({
10
+ connectionString,
11
+ application_name: "rehearsal-external-source-verification",
12
+ });
13
+ if (typeof client.connect === "function")
14
+ await client.connect();
15
+ return client;
16
+ };
17
+ const columnKey = (schema, relation, column) => `${schema}\0${relation}\0${column}`;
18
+ const expectedReadableColumns = (plan) => new Set([
19
+ ...plan.policy.relations.flatMap((relation) => relation.columns.map((column) => columnKey(plan.policy.exportSchema, relation.view, column))),
20
+ ...[
21
+ plan.policy.migrationLedger.versionColumn,
22
+ plan.policy.migrationLedger.nameColumn,
23
+ plan.policy.migrationLedger.statementsColumn,
24
+ ].map((column) => columnKey(plan.policy.migrationLedger.schema, plan.policy.migrationLedger.table, column)),
25
+ ]);
26
+ const count = (result, label) => {
27
+ const value = Number(result.rows[0]?.count);
28
+ if (!Number.isSafeInteger(value) || value < 0) {
29
+ throw new Error(`External source reader ${label} check was inconclusive.`);
30
+ }
31
+ return value;
32
+ };
33
+ const sourceAuthorizationRefusal = ({ message, expected, actual, }) => new RehearsalError({
34
+ category: "unsafe_environment",
35
+ code: "SOURCE_AUTHORIZATION_REFUSED",
36
+ message,
37
+ expected,
38
+ actual,
39
+ refused: "The external source credential was not activated or saved.",
40
+ suggestions: [
41
+ "Review sourcePolicy.reader and the database role grants, then preview source plan again.",
42
+ ],
43
+ });
44
+ export const verifyExternalPostgresqlReader = async ({ plan, connectionString, clientFactory, now = new Date(), }) => {
45
+ if (plan.policy.reader.mode !== "external") {
46
+ throw new Error("External reader verification requires external mode.");
47
+ }
48
+ const client = await connect(connectionString, clientFactory);
49
+ let transactionOpen = false;
50
+ try {
51
+ await client.query("begin transaction read only");
52
+ transactionOpen = true;
53
+ const roleResult = await client.query(`select rolname as role, rolcanlogin, rolvaliduntil,
54
+ rolsuper, rolcreaterole, rolcreatedb, rolreplication, rolbypassrls
55
+ from pg_roles where rolname = current_user`);
56
+ const role = roleResult.rows[0];
57
+ if (!role || role.role !== plan.policy.reader.role || !role.rolcanlogin) {
58
+ throw sourceAuthorizationRefusal({
59
+ message: "External source credential does not authenticate as the reviewed login role.",
60
+ expected: "the reviewed login role",
61
+ actual: "a missing, different, or non-login role",
62
+ });
63
+ }
64
+ if (role.rolsuper ||
65
+ role.rolcreaterole ||
66
+ role.rolcreatedb ||
67
+ role.rolreplication ||
68
+ role.rolbypassrls) {
69
+ throw sourceAuthorizationRefusal({
70
+ message: "External source reader has privileged role attributes.",
71
+ expected: "a non-privileged login role",
72
+ actual: "one or more privileged role attributes",
73
+ });
74
+ }
75
+ const expiresAt = new Date(String(role.rolvaliduntil));
76
+ const maximumExpiration = now.valueOf() + plan.policy.reader.maximumValidForMinutes * 60_000;
77
+ if (!Number.isFinite(expiresAt.valueOf()) ||
78
+ expiresAt.valueOf() <= now.valueOf() ||
79
+ expiresAt.valueOf() > maximumExpiration) {
80
+ throw sourceAuthorizationRefusal({
81
+ message: "External source reader must have a future database-enforced expiration within the reviewed limit.",
82
+ expected: `a future expiration within ${plan.policy.reader.maximumValidForMinutes} minutes`,
83
+ actual: "an absent, expired, or overly long expiration",
84
+ });
85
+ }
86
+ const memberships = await client.query(`select granted.rolname as role, granted.rolcanlogin,
87
+ granted.rolsuper, granted.rolcreaterole, granted.rolcreatedb,
88
+ granted.rolreplication, granted.rolbypassrls,
89
+ membership.admin_option,
90
+ coalesce(
91
+ (to_jsonb(membership) ->> 'inherit_option')::boolean,
92
+ member.rolinherit
93
+ ) as inherit_option,
94
+ coalesce(
95
+ (to_jsonb(membership) ->> 'set_option')::boolean,
96
+ true
97
+ ) as set_option
98
+ from pg_auth_members membership
99
+ join pg_roles member on member.oid = membership.member
100
+ join pg_roles granted on granted.oid = membership.roleid
101
+ where member.rolname = current_user
102
+ order by granted.rolname`);
103
+ const expectedMemberships = [
104
+ ...plan.policy.reader.allowedMemberships,
105
+ ].sort();
106
+ const actualMemberships = memberships.rows
107
+ .map((membership) => String(membership.role))
108
+ .sort();
109
+ if (actualMemberships.length !== expectedMemberships.length ||
110
+ actualMemberships.some((membership, index) => membership !== expectedMemberships[index])) {
111
+ throw sourceAuthorizationRefusal({
112
+ message: "External source reader role memberships do not exactly match the reviewed allowlist.",
113
+ expected: expectedMemberships,
114
+ actual: actualMemberships,
115
+ });
116
+ }
117
+ for (const membership of memberships.rows) {
118
+ if (membership.rolcanlogin ||
119
+ membership.rolsuper ||
120
+ membership.rolcreaterole ||
121
+ membership.rolcreatedb ||
122
+ membership.rolreplication ||
123
+ membership.rolbypassrls ||
124
+ membership.admin_option ||
125
+ membership.inherit_option !== true ||
126
+ membership.set_option !== false) {
127
+ throw sourceAuthorizationRefusal({
128
+ message: "External source reader has an unsafe reviewed role membership.",
129
+ expected: "a non-login, non-privileged group with inheritance enabled and role switching and delegation disabled",
130
+ actual: `unsafe membership options for ${String(membership.role)}`,
131
+ });
132
+ }
133
+ }
134
+ if (expectedMemberships.length > 0) {
135
+ const nestedMemberships = await client.query(`select count(*)::integer as count
136
+ from pg_auth_members direct_membership
137
+ join pg_roles direct_role
138
+ on direct_role.oid = direct_membership.roleid
139
+ join pg_auth_members nested_membership
140
+ on nested_membership.member = direct_membership.roleid
141
+ join pg_roles member on member.oid = direct_membership.member
142
+ where member.rolname = current_user
143
+ and direct_role.rolname = any($1::name[])`, [expectedMemberships]);
144
+ if (count(nestedMemberships, "nested role membership") !== 0) {
145
+ throw sourceAuthorizationRefusal({
146
+ message: "An allowed external source group inherits another database role.",
147
+ expected: "no nested role memberships",
148
+ actual: "one or more nested memberships",
149
+ });
150
+ }
151
+ }
152
+ const databaseWrites = await client.query(`select count(*)::integer as count
153
+ where has_database_privilege(current_user, current_database(), 'CREATE')`);
154
+ const schemaWrites = await client.query(`select count(*)::integer as count
155
+ from pg_namespace
156
+ where nspname not like 'pg_temp_%'
157
+ and has_schema_privilege(current_user, oid, 'CREATE')`);
158
+ const relationWrites = await client.query(`select count(*)::integer as count
159
+ from pg_class relation
160
+ join pg_namespace namespace on namespace.oid = relation.relnamespace
161
+ where namespace.nspname not in ('pg_catalog', 'information_schema')
162
+ and relation.relkind in ('r', 'p', 'v', 'm', 'f')
163
+ and (has_table_privilege(current_user, relation.oid, 'INSERT')
164
+ or has_table_privilege(current_user, relation.oid, 'UPDATE')
165
+ or has_table_privilege(current_user, relation.oid, 'DELETE')
166
+ or has_table_privilege(current_user, relation.oid, 'TRUNCATE')
167
+ or has_table_privilege(current_user, relation.oid, 'REFERENCES')
168
+ or has_table_privilege(current_user, relation.oid, 'TRIGGER'))`);
169
+ const columnWrites = await client.query(`select count(*)::integer as count
170
+ from pg_class relation
171
+ join pg_namespace namespace on namespace.oid = relation.relnamespace
172
+ join pg_attribute attribute on attribute.attrelid = relation.oid
173
+ where namespace.nspname not in ('pg_catalog', 'information_schema')
174
+ and relation.relkind in ('r', 'p', 'v', 'm', 'f')
175
+ and attribute.attnum > 0 and not attribute.attisdropped
176
+ and (has_column_privilege(current_user, relation.oid, attribute.attnum, 'INSERT')
177
+ or has_column_privilege(current_user, relation.oid, attribute.attnum, 'UPDATE')
178
+ or has_column_privilege(current_user, relation.oid, attribute.attnum, 'REFERENCES'))`);
179
+ const sequenceAccess = await client.query(`select count(*)::integer as count
180
+ from pg_class sequence
181
+ join pg_namespace namespace on namespace.oid = sequence.relnamespace
182
+ where namespace.nspname not in ('pg_catalog', 'information_schema')
183
+ and sequence.relkind = 'S'
184
+ and (has_sequence_privilege(current_user, sequence.oid, 'SELECT')
185
+ or has_sequence_privilege(current_user, sequence.oid, 'USAGE')
186
+ or has_sequence_privilege(current_user, sequence.oid, 'UPDATE'))`);
187
+ if ([
188
+ databaseWrites,
189
+ schemaWrites,
190
+ relationWrites,
191
+ columnWrites,
192
+ sequenceAccess,
193
+ ].some((result) => count(result, "write or sequence privilege") !== 0)) {
194
+ throw sourceAuthorizationRefusal({
195
+ message: "External source reader has database write or sequence privileges.",
196
+ expected: "no database, schema, relation, column, or sequence write access",
197
+ actual: "one or more disallowed effective privileges",
198
+ });
199
+ }
200
+ const readable = await client.query(`select namespace.nspname as schema, relation.relname as relation,
201
+ attribute.attname as column
202
+ from pg_class relation
203
+ join pg_namespace namespace on namespace.oid = relation.relnamespace
204
+ join pg_attribute attribute on attribute.attrelid = relation.oid
205
+ where namespace.nspname not in ('pg_catalog', 'information_schema')
206
+ and namespace.nspname not like 'pg_toast%'
207
+ and relation.relkind in ('r', 'p', 'v', 'm', 'f')
208
+ and attribute.attnum > 0 and not attribute.attisdropped
209
+ and has_column_privilege(current_user, relation.oid, attribute.attnum, 'SELECT')
210
+ order by namespace.nspname, relation.relname, attribute.attnum`);
211
+ const expected = expectedReadableColumns(plan);
212
+ const actual = new Set(readable.rows.map((row) => columnKey(String(row.schema), String(row.relation), String(row.column))));
213
+ if (actual.size !== expected.size ||
214
+ [...actual].some((entry) => !expected.has(entry))) {
215
+ throw sourceAuthorizationRefusal({
216
+ message: "External source reader's readable columns do not exactly match the reviewed export surface.",
217
+ expected: `${expected.size} reviewed readable columns`,
218
+ actual: `${actual.size} effective readable columns`,
219
+ });
220
+ }
221
+ for (const relation of plan.policy.relations) {
222
+ const view = await client.query(`select pg_get_viewdef(relation.oid, false) as definition,
223
+ relation.reloptions,
224
+ owner.rolname as owner
225
+ from pg_class relation
226
+ join pg_namespace namespace on namespace.oid = relation.relnamespace
227
+ join pg_roles owner on owner.oid = relation.relowner
228
+ where namespace.nspname = $1 and relation.relname = $2
229
+ and relation.relkind = 'v'`, [plan.policy.exportSchema, relation.view]);
230
+ const actual = view.rows[0];
231
+ if (!actual) {
232
+ throw new Error("An externally reviewed export view is missing.");
233
+ }
234
+ if (!Array.isArray(actual.reloptions) ||
235
+ !actual.reloptions.includes("security_barrier=true")) {
236
+ throw new Error("External export views must enable PostgreSQL security_barrier.");
237
+ }
238
+ if ([
239
+ plan.policy.reader.role,
240
+ ...plan.policy.reader.allowedMemberships,
241
+ ].includes(String(actual.owner))) {
242
+ throw sourceAuthorizationRefusal({
243
+ message: "External source reader roles must not own an export view.",
244
+ expected: "a separate non-reader view owner",
245
+ actual: "the login or an allowed membership owns a reviewed view",
246
+ });
247
+ }
248
+ if (externalViewDefinitionFingerprint(String(actual.definition)) !==
249
+ relation.viewDefinitionSha256) {
250
+ throw new Error("External export view definition does not match its reviewed fingerprint.");
251
+ }
252
+ }
253
+ const securityDefiners = await client.query(`select count(*)::integer as count
254
+ from pg_proc procedure
255
+ join pg_namespace namespace on namespace.oid = procedure.pronamespace
256
+ where namespace.nspname not in ('pg_catalog', 'information_schema')
257
+ and procedure.prosecdef
258
+ and has_function_privilege(current_user, procedure.oid, 'EXECUTE')`);
259
+ if (count(securityDefiners, "security-definer function") !== 0) {
260
+ throw sourceAuthorizationRefusal({
261
+ message: "External source reader can execute a non-system security-definer function.",
262
+ expected: "no executable non-system security-definer functions",
263
+ actual: "one or more executable security-definer functions",
264
+ });
265
+ }
266
+ await client.query("rollback");
267
+ transactionOpen = false;
268
+ return Object.freeze({ expiresAt: expiresAt.toISOString() });
269
+ }
270
+ catch (error) {
271
+ if (transactionOpen)
272
+ await client.query("rollback").catch(() => undefined);
273
+ throw error;
274
+ }
275
+ finally {
276
+ await client.end?.();
277
+ }
278
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Apply and retire an exact PostgreSQL source-access plan. This module is used
3
+ * only by explicit source commands; ordinary runtime commands must not import it.
4
+ */
5
+ import type { SourceAccessPlan, SourceRetirementPlan } from "./access.mjs";
6
+ interface QueryResultLike {
7
+ readonly rows: Record<string, unknown>[];
8
+ }
9
+ interface SourceClient {
10
+ connect?(): Promise<void> | void;
11
+ query(query: string, values?: readonly unknown[]): Promise<QueryResultLike>;
12
+ end?(): Promise<void> | void;
13
+ }
14
+ type SourceClientFactory = (connectionString: string) => SourceClient | Promise<SourceClient>;
15
+ export interface SourceAccessReceipt {
16
+ readonly receiptVersion: 1;
17
+ readonly accessMode: "managed" | "external";
18
+ readonly policyFingerprint: string;
19
+ readonly targetFingerprint: string;
20
+ readonly planDigest: string;
21
+ readonly exportSchema: string;
22
+ readonly views: readonly string[];
23
+ readonly relations: readonly Readonly<{
24
+ schema: string;
25
+ table: string;
26
+ columns: readonly string[];
27
+ }>[];
28
+ readonly migrationLedger: Readonly<{
29
+ schema: string;
30
+ table: string;
31
+ versionColumn: string;
32
+ nameColumn: string;
33
+ statementsColumn: string;
34
+ }>;
35
+ readonly readerRole: string;
36
+ readonly ownerRole?: string;
37
+ readonly expiresAt: string;
38
+ }
39
+ export interface RetirementExecutionPlan extends SourceRetirementPlan {
40
+ readonly receipt: SourceAccessReceipt;
41
+ readonly receiptPath: string;
42
+ }
43
+ export declare const resolvePostgresqlSourceStatePaths: ({ projectRoot, artifactRoot, credentialFile, }: {
44
+ projectRoot?: string;
45
+ artifactRoot?: string;
46
+ credentialFile: string;
47
+ }) => Readonly<{
48
+ stateRoot: string;
49
+ credentialPath: string;
50
+ receiptPath: string;
51
+ }>;
52
+ export declare const applyPostgresqlSourceAccess: ({ plan, confirmation, projectRoot, artifactRoot, environment, clientFactory, now, }: {
53
+ plan: SourceAccessPlan;
54
+ confirmation: string;
55
+ projectRoot?: string;
56
+ artifactRoot?: string;
57
+ environment?: NodeJS.ProcessEnv;
58
+ clientFactory?: SourceClientFactory;
59
+ now?: Date;
60
+ }) => Promise<{
61
+ receipt: SourceAccessReceipt;
62
+ credentialPath: string;
63
+ receiptPath: string;
64
+ }>;
65
+ export declare const planPostgresqlSourceAccessRetirement: ({ projectRoot, artifactRoot, targetFingerprint, }: {
66
+ projectRoot?: string;
67
+ artifactRoot?: string;
68
+ targetFingerprint: string;
69
+ }) => Promise<RetirementExecutionPlan>;
70
+ export declare const retirePostgresqlSourceAccess: ({ plan, confirmation, administratorEnvironmentVariable, projectRoot, artifactRoot, environment, credentialFile, clientFactory, }: {
71
+ plan: RetirementExecutionPlan;
72
+ confirmation: string;
73
+ administratorEnvironmentVariable?: string;
74
+ projectRoot?: string;
75
+ artifactRoot?: string;
76
+ environment?: NodeJS.ProcessEnv;
77
+ credentialFile: string;
78
+ clientFactory?: SourceClientFactory;
79
+ }) => Promise<{
80
+ retired: boolean;
81
+ providerResourcesPreserved: boolean;
82
+ } | {
83
+ providerResourcesPreserved?: never;
84
+ retired: boolean;
85
+ }>;
86
+ export {};