@dbsp/cli 3.0.0 → 4.0.0

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.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createDbConnection,
4
4
  generateSchemaFileWithDiagnostics,
5
5
  redactDbUrl
6
- } from "./chunk-G6NS4BAV.js";
6
+ } from "./chunk-MFQPE7CY.js";
7
7
  import {
8
8
  loadSchema,
9
9
  loadSchemaFromCwd
@@ -15,37 +15,40 @@ import {
15
15
 
16
16
  // src/index.ts
17
17
  import { createRequire } from "module";
18
- import { Command as Command12, CommanderError } from "commander";
18
+ import { Command as Command13, CommanderError } from "commander";
19
19
 
20
20
  // src/commands/apply.ts
21
21
  import { AsyncLocalStorage } from "async_hooks";
22
- import { createHash as createHash3 } from "crypto";
22
+ import { createHash } from "crypto";
23
23
  import { readFile } from "fs/promises";
24
24
  import { createInterface } from "readline/promises";
25
25
  import {
26
26
  appendIntentJournal,
27
- appendTransitionAuthorization,
28
27
  createPgTransitionLessor as createPgTransitionLessor3,
29
- createPgTransitionPack as createPgTransitionPack2,
30
28
  escapeDiagnosticText as escapeDiagnosticText3,
31
- preparePgExecutionSession,
29
+ executionIdsForRun,
30
+ readPgLedgerAddressChain,
31
+ readPgLedgerReservationsForExecution,
32
32
  readTransitionJournal,
33
33
  TransitionRunIdentityMismatchError,
34
- validatePgManagedLedgerCurrency,
35
34
  withPgTransitionRunLock
36
35
  } from "@dbsp/adapter-pgsql";
37
- import { lockPgJournalRun } from "@dbsp/adapter-pgsql/internal";
36
+ import {
37
+ applyPgTransitionRun,
38
+ comparePgsqlDeclaredAdoptionSchema as comparePgsqlDeclaredAdoptionSchema2,
39
+ executeGeneratorPlan,
40
+ lockPgJournalRun,
41
+ modelForDeclaredAdoption as modelForDeclaredAdoption2
42
+ } from "@dbsp/adapter-pgsql/internal";
38
43
  import {
39
44
  acquireExclusiveTransitionLease,
40
45
  acquireTransitionLease as acquireTransitionLease3,
41
- createApplier,
42
- createPackRegistry as createPackRegistry2,
43
46
  selectorMatchesResource,
44
47
  transitionPlanDigest as transitionPlanDigest2,
45
- validateNormalizedManagedStepManifest as validateNormalizedManagedStepManifest3
48
+ validateNormalizedManagedStepManifest as validateNormalizedManagedStepManifest2
46
49
  } from "@dbsp/core";
47
50
  import { mintDurablyLoadedRun } from "@dbsp/core/internal";
48
- import { REFUSAL_VOCABULARY as REFUSAL_VOCABULARY2 } from "@dbsp/types";
51
+ import { ledgerAddressKey, REFUSAL_VOCABULARY as REFUSAL_VOCABULARY2 } from "@dbsp/types";
49
52
  import { Command as Command2 } from "commander";
50
53
 
51
54
  // src/utils/output.ts
@@ -59,932 +62,47 @@ function printCliJson(value) {
59
62
  console.log(serializeCliJson(value));
60
63
  }
61
64
 
62
- // src/commands/generator-execution.ts
63
- import { createHash, randomUUID } from "crypto";
64
- import { isDeepStrictEqual } from "util";
65
- import {
66
- compareSchemata,
67
- createPgsqlAdapter,
68
- dbTypesEqual,
69
- executePgAdmittedOperation,
70
- executePgDeclaredAdoption,
71
- executePgPersistedTableReaddress,
72
- preflightPgDeclaredAdoption,
73
- readPgCatalogueIdentity,
74
- readPgLedgerAddressChain,
75
- readPgLedgerScopeCurrency,
76
- readPgRemovalEffectsClosure
77
- } from "@dbsp/adapter-pgsql";
78
- import {
79
- outcomeClaimEventId,
80
- outcomeClaimId,
81
- projectLedgerChain,
82
- validateNormalizedManagedStepManifest
83
- } from "@dbsp/core";
84
- import { decideDestructiveDecision } from "@dbsp/core/internal";
85
- import { ledgerAddressKey } from "@dbsp/types";
86
- function managedSteps(manifest) {
87
- return manifest.steps;
88
- }
89
- function matchesReviewedReplacementSelector(reviewed, provided) {
90
- return provided === reviewed || reviewed.startsWith("table:") && provided === reviewed.slice("table:".length);
91
- }
92
- function modelForAdoption(table) {
93
- const tables = /* @__PURE__ */ new Map([[table.name, table]]);
94
- const relations = /* @__PURE__ */ new Map();
95
- return {
96
- tables,
97
- relations,
98
- getTable: (name) => tables.get(name),
99
- getRelation: (name) => relations.get(name),
100
- getRelationsFrom: () => [],
101
- getRelationsTo: () => [],
102
- isAmbiguous: () => ({ ambiguous: false, options: [] })
103
- };
104
- }
105
- async function adoptionShapeMatches(pool, schema, shape) {
106
- const live = await createPgsqlAdapter(pool).introspect({ schema });
107
- const diff = compareSchemata(modelForAdoption(shape), live);
108
- return !diff.changes.some((change) => change.table === shape.name);
109
- }
110
- var SYSTEM_LEDGER_SCHEMAS = /* @__PURE__ */ new Set([
111
- "pg_toast",
112
- "pg_catalog",
113
- "information_schema"
114
- ]);
115
- var SystemSchemaLedgerHomeError = class extends Error {
116
- address;
117
- constructor(address) {
118
- super(
119
- `SystemSchemaLedgerHomeError: ledger home is unavailable for system schema ${address.schema}: ${ledgerAddressKey(address)}`
120
- );
121
- this.name = "SystemSchemaLedgerHomeError";
122
- this.address = address;
123
- }
124
- };
125
- function home(address) {
126
- if (address.scope !== "database" && address.schema && SYSTEM_LEDGER_SCHEMAS.has(address.schema))
127
- throw new SystemSchemaLedgerHomeError(address);
128
- return address.scope === "database" ? { scope: "database" } : address.schema ? { scope: "schema", schema: address.schema } : (() => {
129
- throw new Error(
130
- `schema-scoped generated address ${address.name} has no schema ledger`
131
- );
132
- })();
133
- }
134
- function acceptance(planDigest, approval) {
135
- return approval.approvals.some(
136
- (grant) => grant.class === `destructive-plan-accepted:${planDigest}`
137
- ) === true ? "destructive-plan-accepted" : "absent";
138
- }
139
- function withheldDestructiveAuthority(evidence) {
140
- if (evidence.declaration !== "requires-removal" && evidence.declaration !== "requires-lossy-change" && evidence.declaration !== "replacement-requested-by-plan")
141
- return "destructive declaration authority";
142
- if (evidence.ownership !== "managed-by-me")
143
- return "destructive ownership authority";
144
- if (evidence.catalogueIdentity !== "matches-recorded")
145
- return "destructive catalogue identity authority";
146
- if (evidence.operatorAcceptance !== "destructive-plan-accepted")
147
- return "destructive operator acceptance authority";
148
- if (evidence.containment !== void 0 && evidence.containment !== "all-contained-or-managed")
149
- return "destructive containment authority";
150
- if (evidence.ledgerLineage !== "matches-database")
151
- return "destructive ledger lineage authority";
152
- return void 0;
153
- }
154
- function withheldDestructiveAuthorityFromReason(reason) {
155
- if (reason.includes("operator acceptance"))
156
- return "destructive operator acceptance authority";
157
- if (reason.includes("ledger lineage"))
158
- return "destructive ledger lineage authority";
159
- return void 0;
160
- }
161
- async function databaseId(pool) {
162
- const result = await pool.query("SELECT current_database() AS database_id");
163
- const database = result.rows[0]?.database_id;
164
- if (typeof database !== "string" || database.length === 0)
165
- throw new Error(
166
- "schema-differ generator could not read current database identity"
167
- );
168
- return database;
169
- }
170
- async function managed(executor, address) {
171
- const chain = await readPgLedgerAddressChain(
172
- executor,
173
- home(address),
174
- address
175
- );
176
- const projection = projectLedgerChain(chain);
177
- return projection.kind === "projected-ledger-chain" && projection.stableState === "managed";
178
- }
179
- async function alreadyAppliedCreation(executor, address) {
180
- const live = await readPgCatalogueIdentity(executor, address);
181
- if (!live?.catalogueIdentity) return false;
182
- const chain = await readPgLedgerAddressChain(
183
- executor,
184
- home(address),
185
- address
186
- );
187
- const projection = projectLedgerChain(chain);
188
- const recorded = chain.terminalMember?.catalogueIdentity;
189
- return projection.kind === "projected-ledger-chain" && projection.stableState === "managed" && recorded !== void 0 && isDeepStrictEqual(recorded, live.catalogueIdentity);
190
- }
191
- function observed(address) {
192
- return {
193
- value: { kind: address.kind, name: address.name },
194
- digest: `generator:${address.kind}:${address.name}`
195
- };
196
- }
197
- function normalizedDefinition(value) {
198
- return value.replaceAll('"', "").replace(/\s+/gu, " ").replace(/;$/u, "").trim().toLowerCase();
199
- }
200
- function generatedPayload(value) {
201
- return {
202
- value,
203
- digest: createHash("sha256").update(JSON.stringify(value)).digest("hex")
204
- };
205
- }
206
- function textList(value) {
207
- return Array.isArray(value) ? value.map((item) => String(item)) : [];
208
- }
209
- function foreignKeyAction(code) {
210
- switch (code) {
211
- case "a":
212
- return "NO ACTION";
213
- case "r":
214
- return "RESTRICT";
215
- case "c":
216
- return "CASCADE";
217
- case "n":
218
- return "SET NULL";
219
- case "d":
220
- return "SET DEFAULT";
221
- default:
222
- return void 0;
223
- }
224
- }
225
- function generatedPostcondition(step, address) {
226
- const value = step.expectedDeclaration?.value;
227
- if (!value || typeof value !== "object" || Array.isArray(value))
228
- throw new Error(
229
- `generated ${address.kind} step ${step.stepKey} has no structural postcondition`
230
- );
231
- const postcondition = value;
232
- return postcondition;
233
- }
234
- async function readGeneratedPostcondition(executor, step, address) {
235
- const parent = address.parent?.name;
236
- if (address.kind === "column" && parent && address.schema) {
237
- const postcondition = generatedPostcondition(step, address);
238
- if (postcondition.kind !== "column")
239
- throw new Error(
240
- `generated column ${address.name} has a non-column structural postcondition`
241
- );
242
- const expected = postcondition.column;
243
- if (expected.name !== address.name)
244
- throw new Error(
245
- `generated column ${address.name} structural postcondition names another column`
246
- );
247
- const row = (await executor.query(
248
- `SELECT pg_catalog.format_type(attribute.atttypid, attribute.atttypmod) AS column_type, attribute.attnotnull AS is_not_null, pg_catalog.pg_get_expr(default_value.adbin, default_value.adrelid) AS column_default, collation.collname AS collation_name, attribute.attidentity AS identity_kind FROM pg_catalog.pg_attribute attribute JOIN pg_catalog.pg_class relation ON relation.oid = attribute.attrelid JOIN pg_catalog.pg_namespace namespace ON namespace.oid = relation.relnamespace LEFT JOIN pg_catalog.pg_attrdef default_value ON default_value.adrelid = attribute.attrelid AND default_value.adnum = attribute.attnum LEFT JOIN pg_catalog.pg_collation collation ON collation.oid = attribute.attcollation WHERE namespace.nspname = $1 AND relation.relname = $2 AND attribute.attname = $3 AND attribute.attnum > 0 AND NOT attribute.attisdropped`,
249
- [address.schema, parent, address.name]
250
- )).rows[0];
251
- if (!row) throw new Error(`generated column ${address.name} is absent`);
252
- const type = String(row.column_type ?? "");
253
- const nullable = row.is_not_null === true ? false : row.is_not_null === false ? true : void 0;
254
- const actualDefault = row.column_default == null ? void 0 : String(row.column_default);
255
- const actualCollation = row.collation_name == null ? null : String(row.collation_name);
256
- const actualIdentity = row.identity_kind === "a" ? "always" : row.identity_kind === "d" ? "byDefault" : null;
257
- if (expected.type !== void 0 && !dbTypesEqual(type, expected.type))
258
- throw new Error(
259
- `generated column ${address.name} type postcondition differs`
260
- );
261
- if (expected.nullable !== void 0 && nullable !== expected.nullable)
262
- throw new Error(
263
- `generated column ${address.name} nullability postcondition differs`
264
- );
265
- if (expected.hasDefault === true && (actualDefault === void 0 || expected.default === void 0 || actualDefault !== expected.default))
266
- throw new Error(
267
- `generated column ${address.name} default postcondition differs`
268
- );
269
- if (expected.hasDefault === false && actualDefault !== void 0)
270
- throw new Error(
271
- `generated column ${address.name} default postcondition differs`
272
- );
273
- if (expected.collation !== void 0 && actualCollation !== expected.collation)
274
- throw new Error(
275
- `generated column ${address.name} collation postcondition differs`
276
- );
277
- if (expected.identity !== void 0 && actualIdentity !== expected.identity)
278
- throw new Error(
279
- `generated column ${address.name} identity postcondition differs`
280
- );
281
- return generatedPayload({
282
- kind: "column",
283
- type,
284
- nullable,
285
- default: actualDefault
286
- });
287
- }
288
- if (address.kind === "constraint" && parent && address.schema) {
289
- const postcondition = generatedPostcondition(step, address);
290
- if (postcondition.kind !== "constraint")
291
- throw new Error(
292
- `generated constraint ${address.name} has a non-constraint structural postcondition`
293
- );
294
- const expected = postcondition.constraint;
295
- const row = (await executor.query(
296
- `SELECT constraint_item.contype AS constraint_type, pg_catalog.pg_get_constraintdef(constraint_item.oid, true) AS constraint_definition, ARRAY(SELECT attribute.attname::text FROM unnest(constraint_item.conkey) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_catalog.pg_attribute attribute ON attribute.attrelid = constraint_item.conrelid AND attribute.attnum = key_column.attnum ORDER BY key_column.position) AS key_columns, referenced_namespace.nspname AS referenced_schema, referenced_relation.relname AS referenced_table, ARRAY(SELECT attribute.attname::text FROM unnest(constraint_item.confkey) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_catalog.pg_attribute attribute ON attribute.attrelid = constraint_item.confrelid AND attribute.attnum = key_column.attnum ORDER BY key_column.position) AS referenced_columns, constraint_item.confdeltype AS on_delete, constraint_item.confupdtype AS on_update, constraint_item.condeferrable AS is_deferrable, constraint_item.condeferred AS is_deferred, constraint_item.convalidated AS is_validated FROM pg_catalog.pg_constraint constraint_item JOIN pg_catalog.pg_class relation ON relation.oid = constraint_item.conrelid JOIN pg_catalog.pg_namespace namespace ON namespace.oid = relation.relnamespace LEFT JOIN pg_catalog.pg_class referenced_relation ON referenced_relation.oid = constraint_item.confrelid LEFT JOIN pg_catalog.pg_namespace referenced_namespace ON referenced_namespace.oid = referenced_relation.relnamespace WHERE namespace.nspname = $1 AND relation.relname = $2 AND constraint_item.conname = $3`,
297
- [address.schema, parent, address.name]
298
- )).rows[0];
299
- if (!row) throw new Error(`generated constraint ${address.name} is absent`);
300
- const definition = String(row.constraint_definition ?? "");
301
- if (String(row.constraint_type) !== expected.type)
302
- throw new Error(
303
- `generated constraint ${address.name} postcondition differs`
304
- );
305
- if ((expected.type === "p" || expected.type === "u") && JSON.stringify(textList(row.key_columns)) !== JSON.stringify(expected.columns))
306
- throw new Error(
307
- `generated constraint ${address.name} postcondition differs`
308
- );
309
- if (expected.type === "c") {
310
- if (normalizedDefinition(definition) !== normalizedDefinition(expected.definition) || row.is_validated === true === expected.notValid)
311
- throw new Error(
312
- `generated constraint ${address.name} postcondition differs`
313
- );
314
- }
315
- if (expected.type === "f") {
316
- if (JSON.stringify(textList(row.key_columns)) !== JSON.stringify(expected.columns) || String(row.referenced_table ?? "") !== expected.references.table || expected.references.schema !== void 0 && String(row.referenced_schema ?? "") !== expected.references.schema || JSON.stringify(textList(row.referenced_columns)) !== JSON.stringify(expected.references.columns) || foreignKeyAction(row.on_delete) !== expected.onDelete || foreignKeyAction(row.on_update) !== expected.onUpdate || (row.is_deferrable === true && row.is_deferred === true) !== expected.deferred || row.is_validated === true === expected.notValid)
317
- throw new Error(
318
- `generated constraint ${address.name} postcondition differs`
319
- );
320
- }
321
- return generatedPayload({
322
- kind: "constraint",
323
- type: String(row.constraint_type ?? ""),
324
- definition
325
- });
326
- }
327
- if (address.kind === "index" && parent && address.schema) {
328
- const postcondition = generatedPostcondition(step, address);
329
- if (postcondition.kind !== "index")
330
- throw new Error(
331
- `generated index ${address.name} has a non-index structural postcondition`
332
- );
333
- const row = (await executor.query(
334
- `SELECT index_relation.relname AS index_name, index_meta.indisunique AS is_unique, pg_catalog.pg_get_indexdef(index_meta.indexrelid, 0, true) AS index_definition FROM pg_catalog.pg_index index_meta JOIN pg_catalog.pg_class relation ON relation.oid = index_meta.indrelid JOIN pg_catalog.pg_namespace namespace ON namespace.oid = relation.relnamespace JOIN pg_catalog.pg_class index_relation ON index_relation.oid = index_meta.indexrelid WHERE namespace.nspname = $1 AND relation.relname = $2 AND index_relation.relname = $3`,
335
- [address.schema, parent, address.name]
336
- )).rows[0];
337
- if (!row) throw new Error(`generated index ${address.name} is absent`);
338
- const definition = String(row.index_definition ?? "");
339
- if (normalizedDefinition(definition) !== normalizedDefinition(postcondition.definition))
340
- throw new Error(`generated index ${address.name} postcondition differs`);
341
- return generatedPayload({
342
- kind: "index",
343
- unique: row.is_unique === true,
344
- definition
345
- });
346
- }
347
- if (address.kind === "table" && address.schema) {
348
- const postcondition = generatedPostcondition(step, address);
349
- if (postcondition.kind !== "table")
350
- throw new Error(
351
- `generated table ${address.name} has a non-table structural postcondition`
352
- );
353
- const rows = (await executor.query(
354
- `SELECT attribute.attname AS column_name, pg_catalog.format_type(attribute.atttypid, attribute.atttypmod) AS column_type, attribute.attnotnull AS is_not_null, pg_catalog.pg_get_expr(default_value.adbin, default_value.adrelid) AS column_default FROM pg_catalog.pg_attribute attribute JOIN pg_catalog.pg_class relation ON relation.oid = attribute.attrelid JOIN pg_catalog.pg_namespace namespace ON namespace.oid = relation.relnamespace LEFT JOIN pg_catalog.pg_attrdef default_value ON default_value.adrelid = attribute.attrelid AND default_value.adnum = attribute.attnum WHERE namespace.nspname = $1 AND relation.relname = $2 AND attribute.attnum > 0 AND NOT attribute.attisdropped ORDER BY attribute.attnum`,
355
- [address.schema, address.name]
356
- )).rows;
357
- const expected = postcondition.columns;
358
- if (expected.length === 0 || rows.length !== expected.length)
359
- throw new Error(
360
- `generated table ${address.name} column postcondition differs: expected ${JSON.stringify(expected)}, live ${JSON.stringify(rows)}`
361
- );
362
- for (const specification of expected) {
363
- const actual = rows.find((row) => row.column_name === specification.name);
364
- if (!actual || specification.type !== void 0 && !dbTypesEqual(
365
- String(actual.column_type ?? ""),
366
- specification.type
367
- ) || specification.nullable !== void 0 && actual.is_not_null !== true !== specification.nullable || specification.hasDefault === true && (specification.default === void 0 || actual.column_default == null || String(actual.column_default) !== specification.default) || specification.hasDefault === false && actual.column_default != null)
368
- throw new Error(
369
- `generated table ${address.name} column postcondition differs: expected ${JSON.stringify(specification)}, live ${JSON.stringify(actual)}`
370
- );
371
- }
372
- return generatedPayload({
373
- kind: "table",
374
- columns: rows.map((row) => ({
375
- name: String(row.column_name),
376
- type: String(row.column_type),
377
- nullable: row.is_not_null !== true,
378
- default: row.column_default == null ? void 0 : String(row.column_default)
379
- }))
380
- });
381
- }
382
- if (address.kind === "enum" && address.schema) {
383
- const postcondition = generatedPostcondition(step, address);
384
- if (postcondition.kind !== "enum")
385
- throw new Error(
386
- `generated enum ${address.name} has a non-enum structural postcondition`
387
- );
388
- const rows = (await executor.query(
389
- `SELECT enum_label.enumlabel AS label FROM pg_catalog.pg_type type JOIN pg_catalog.pg_namespace namespace ON namespace.oid = type.typnamespace JOIN pg_catalog.pg_enum enum_label ON enum_label.enumtypid = type.oid WHERE namespace.nspname = $1 AND type.typname = $2 ORDER BY enum_label.enumsortorder`,
390
- [address.schema, address.name]
391
- )).rows;
392
- const labels = rows.map((row) => String(row.label));
393
- if (JSON.stringify(labels) !== JSON.stringify(postcondition.labels))
394
- throw new Error(`generated enum ${address.name} postcondition differs`);
395
- return generatedPayload({ kind: "enum", labels });
396
- }
397
- if (address.kind === "sequence" && address.schema) {
398
- const postcondition = generatedPostcondition(step, address);
399
- if (postcondition.kind !== "sequence")
400
- throw new Error(
401
- `generated sequence ${address.name} has a non-sequence structural postcondition`
402
- );
403
- const row = (await executor.query(
404
- `SELECT sequence.start_value::text AS start_value, sequence.increment_by::text AS increment_by, sequence.min_value::text AS min_value, sequence.max_value::text AS max_value, sequence.cache_size::text AS cache_size, sequence.cycle AS cycle FROM pg_catalog.pg_sequences sequence WHERE sequence.schemaname = $1 AND sequence.sequencename = $2`,
405
- [address.schema, address.name]
406
- )).rows[0];
407
- if (!row) throw new Error(`generated sequence ${address.name} is absent`);
408
- const actual = {
409
- startValue: String(row.start_value),
410
- incrementBy: String(row.increment_by),
411
- minValue: String(row.min_value),
412
- maxValue: String(row.max_value),
413
- cycle: row.cycle === true
414
- };
415
- for (const key of [
416
- "startValue",
417
- "incrementBy",
418
- "minValue",
419
- "maxValue",
420
- "cycle"
421
- ])
422
- if (postcondition[key] !== void 0 && postcondition[key] !== actual[key])
423
- throw new Error(
424
- `generated sequence ${address.name} postcondition differs`
425
- );
426
- return generatedPayload({ kind: "sequence", ...row });
427
- }
428
- if (address.kind === "extension") {
429
- const postcondition = generatedPostcondition(step, address);
430
- if (postcondition.kind !== "extension")
431
- throw new Error(
432
- `generated extension ${address.name} has a non-extension structural postcondition`
433
- );
434
- const row = (await executor.query(
435
- `SELECT extension.extversion AS version FROM pg_catalog.pg_extension extension WHERE extension.extname = $1`,
436
- [address.name]
437
- )).rows[0];
438
- if (!row)
439
- throw new Error(
440
- `generated extension ${address.name} version postcondition differs`
441
- );
442
- if (postcondition.version !== void 0 && String(row.version) !== postcondition.version)
443
- throw new Error(
444
- `generated extension ${address.name} version postcondition differs`
445
- );
446
- return generatedPayload({
447
- kind: "extension",
448
- version: String(row.version)
449
- });
450
- }
451
- throw new Error(`generated ${address.kind} has no declarable read-back`);
452
- }
453
- function containedBy(root, candidate) {
454
- for (let parent = candidate.parent; parent; parent = parent.parent) {
455
- if (parent.engine === root.engine && parent.database === root.database && parent.schema === root.schema && parent.kind === root.kind && parent.name === root.name)
456
- return true;
457
- }
458
- return false;
459
- }
460
- async function destructiveEvidence(input) {
461
- const { executor, address } = input;
462
- let ownership = "uncomputable";
463
- let catalogueIdentity = "catalogue-unavailable";
464
- let ledgerLineage = "unreadable";
465
- try {
466
- const chain = await readPgLedgerAddressChain(
467
- executor,
468
- home(address),
469
- address
470
- );
471
- const projection = projectLedgerChain(chain);
472
- ownership = projection.kind === "projected-ledger-chain" ? projection.stableState === "managed" ? "managed-by-me" : projection.stableState === "unknown" ? "unknown" : "blocked" : "uncomputable";
473
- const live = await readPgCatalogueIdentity(executor, address);
474
- const recorded = chain.terminalMember?.catalogueIdentity;
475
- catalogueIdentity = !live ? "object-absent" : recorded === void 0 ? "differs" : isDeepStrictEqual(live.catalogueIdentity, recorded) ? "matches-recorded" : "differs";
476
- const currency = await readPgLedgerScopeCurrency(executor, home(address));
477
- ledgerLineage = currency.kind === "current" ? "matches-database" : "differs";
478
- } catch {
479
- }
480
- let containment;
481
- let containmentOutcome;
482
- if (input.classification === "removal") {
483
- containment = await readPgRemovalEffectsClosure({
484
- executor,
485
- root: address,
486
- isManaged: (candidate) => managed(executor, candidate)
487
- });
488
- containmentOutcome = containment.kind;
489
- }
490
- return {
491
- evidence: {
492
- declaration: input.classification === "removal" ? input.selection?.kind === "replacement" ? "replacement-requested-by-plan" : "requires-removal" : "requires-lossy-change",
493
- ...input.selection?.kind === "replacement" ? { replacementAddress: address } : {},
494
- ownership,
495
- catalogueIdentity,
496
- operatorAcceptance: acceptance(input.planDigest, input.approval),
497
- ...containmentOutcome === void 0 ? {} : { containment: containmentOutcome },
498
- ...containment?.kind === "reaches-unmanaged" ? { containmentUnmanaged: containment.unmanaged } : {},
499
- ...containment?.kind === "undecidable" ? { containmentReason: containment.reason } : {},
500
- ledgerLineage
501
- },
502
- ...containment === void 0 ? {} : { containment }
503
- };
504
- }
505
- async function removalContainment(executor, address) {
506
- return readPgRemovalEffectsClosure({
507
- executor,
508
- root: address,
509
- isManaged: (candidate) => managed(executor, candidate)
510
- });
511
- }
512
- async function executeGeneratorPlan(input) {
513
- const validation = input.manifest ? { ok: true, manifest: input.manifest } : validateNormalizedManagedStepManifest(
514
- input.plan?.steps ?? []
515
- );
516
- if (!validation.ok)
517
- return { outcome: "execution-failed", detail: validation.detail };
518
- const manifest = validation.manifest;
519
- const approval = input.approval ?? {
520
- approvals: (input.accepts ?? []).map((value) => ({ class: value }))
521
- };
522
- const completedStepKeys = [];
523
- let destructiveRefusal;
524
- let destructiveClosureReplanRequired = false;
525
- const partial = (detail) => completedStepKeys.length === 0 ? { outcome: "execution-failed", detail } : {
526
- outcome: "partially-applied",
527
- detail,
528
- completedStepKeys,
529
- notStartedStepKeys: managedSteps(manifest).filter((step) => !completedStepKeys.includes(step.stepKey)).map((step) => step.stepKey)
530
- };
531
- try {
532
- const database = await databaseId(input.pool);
533
- const executionId = `dbsp.generator.execution.${randomUUID()}`;
534
- await input.recordAttempt?.(executionId);
535
- const steps = managedSteps(manifest);
536
- for (const step of steps) {
537
- if (step.lifecycle?.kind === "adoption-refused")
538
- return {
539
- outcome: "adoption-refused",
540
- detail: `declared adoption for ${step.address?.name ?? step.stepKey} refuses live shape mismatch`
541
- };
542
- const lifecycle = step.lifecycle;
543
- if (lifecycle?.kind !== "adoption") continue;
544
- const address = step.address;
545
- if (!address || !step.expectedDeclaration || !step.expectedCatalogueIdentity)
546
- return {
547
- outcome: "execution-failed",
548
- detail: `adoption step ${step.stepKey} has incomplete normalized material`
549
- };
550
- const preflight = await preflightPgDeclaredAdoption({
551
- executor: input.pool,
552
- home: home(address),
553
- address,
554
- declaration: step.expectedDeclaration,
555
- expectedCatalogueIdentity: step.expectedCatalogueIdentity,
556
- shapeMatches: () => adoptionShapeMatches(input.pool, input.schema, lifecycle.shape)
557
- });
558
- if (preflight.outcome !== "ready" && preflight.outcome !== "no-op")
559
- return preflight.outcome === "adoption-refused" ? { outcome: "adoption-refused", detail: preflight.detail } : { outcome: "execution-failed", detail: preflight.detail };
560
- }
561
- const replacementSelectors = steps.filter((step) => step.selection?.kind === "replacement").map((step) => step.selection?.selector).filter((selector) => selector !== void 0);
562
- if (replacementSelectors.length > 0 && !input.replaces?.length)
563
- return {
564
- outcome: "destructive-authority-refused",
565
- detail: "replacement requires a named --replace selector from the reviewed plan",
566
- refusal: { withheldAuthority: "destructive declaration authority" }
567
- };
568
- for (const selector of input.replaces ?? [])
569
- if (!replacementSelectors.some(
570
- (reviewed) => matchesReviewedReplacementSelector(reviewed, selector)
571
- ))
572
- return {
573
- outcome: "destructive-authority-refused",
574
- detail: `replacement ${selector} was not requested by the reviewed plan`,
575
- refusal: { withheldAuthority: "destructive declaration authority" }
576
- };
577
- if (replacementSelectors.some(
578
- (reviewed) => !input.replaces?.some(
579
- (provided) => matchesReviewedReplacementSelector(reviewed, provided)
580
- )
581
- ))
582
- return {
583
- outcome: "selection-incomplete",
584
- detail: "replacement selection does not cover every reviewed replacement"
585
- };
586
- for (const step of steps) {
587
- if (step.lifecycle?.kind === "adoption-refused") continue;
588
- if (step.lifecycle?.kind === "adoption") {
589
- const lifecycle = step.lifecycle;
590
- const address2 = step.address;
591
- if (!address2 || !step.expectedDeclaration || !step.expectedCatalogueIdentity)
592
- return {
593
- outcome: "execution-failed",
594
- detail: `adoption step ${step.stepKey} has incomplete normalized material`
595
- };
596
- const adopted = await executePgDeclaredAdoption({
597
- executor: input.pool,
598
- run: input.run,
599
- manifest,
600
- recomputedPlanDigest: input.planDigest,
601
- approval,
602
- step,
603
- home: home(address2),
604
- address: address2,
605
- declaration: step.expectedDeclaration,
606
- expectedCatalogueIdentity: step.expectedCatalogueIdentity,
607
- shapeMatches: () => adoptionShapeMatches(input.pool, input.schema, lifecycle.shape),
608
- ...input.observer === void 0 ? {} : { observer: input.observer }
609
- });
610
- if (adopted.outcome === "completed" || adopted.outcome === "no-op") {
611
- completedStepKeys.push(step.stepKey);
612
- continue;
613
- }
614
- return adopted.outcome === "adoption-refused" ? { outcome: "adoption-refused", detail: adopted.detail } : { outcome: "execution-failed", detail: adopted.detail };
615
- }
616
- if (step.lifecycle?.kind === "readdress") {
617
- const result = await executePgPersistedTableReaddress({
618
- executor: input.pool,
619
- run: input.run,
620
- manifest,
621
- recomputedPlanDigest: input.planDigest,
622
- approval,
623
- step,
624
- database,
625
- targetSchema: input.schema,
626
- ...input.observer === void 0 ? {} : { observer: input.observer }
627
- });
628
- if (result.outcome === "completed" || result.outcome === "no-op") {
629
- completedStepKeys.push(step.stepKey);
630
- continue;
631
- }
632
- return result;
633
- }
634
- if (step.statementBundle.statements.length === 0) {
635
- completedStepKeys.push(step.stepKey);
636
- continue;
637
- }
638
- const address = step.address ?? step.closure?.root;
639
- if (!address)
640
- return {
641
- outcome: "execution-failed",
642
- detail: `managed step ${step.stepKey} has no address`
643
- };
644
- const plannedClaimKey = step.plannedClaimKeys[0];
645
- if (!plannedClaimKey)
646
- return {
647
- outcome: "execution-failed",
648
- detail: `managed step ${step.stepKey} has no planned claim key`
649
- };
650
- if (step.classification === "non-destructive" && step.requiresVacancy && await alreadyAppliedCreation(input.pool, address)) {
651
- completedStepKeys.push(step.stepKey);
652
- continue;
653
- }
654
- const claimKind = step.claimKind;
655
- const rootClaimId = outcomeClaimId(executionId, plannedClaimKey, address);
656
- const claim = {
657
- claimId: rootClaimId,
658
- claimSpecies: "sql-bearing",
659
- executionId,
660
- plannedClaimKey,
661
- claimGroupId: rootClaimId,
662
- rootClaimId,
663
- address,
664
- claimKind,
665
- statementBundle: step.statementBundle,
666
- requiresVacancy: step.requiresVacancy
667
- };
668
- const baseReservation = {
669
- address,
670
- claimKind,
671
- executionId,
672
- rootClaimId: claim.claimId,
673
- homeLedger: home(address)
674
- };
675
- if (step.classification === "non-destructive") {
676
- const result = await executePgAdmittedOperation(input.pool, {
677
- run: input.run,
678
- approval,
679
- manifest,
680
- recomputedPlanDigest: input.planDigest,
681
- operation: {
682
- kind: "single-outcome",
683
- request: {
684
- plan: claim,
685
- reservations: [baseReservation],
686
- resolution: {
687
- eventId: outcomeClaimEventId(claim.claimId, "observed"),
688
- eventKind: "observed"
689
- },
690
- // Transactional DDL is visible only on the admitted session until
691
- // its terminal ledger fact commits with it.
692
- readBack: async (session) => readGeneratedPostcondition(session, step, address),
693
- recordCatalogueIdentity: true,
694
- ...input.observer === void 0 ? {} : { observer: input.observer },
695
- vacancy: async (executor) => await readPgCatalogueIdentity(executor, address) ? {
696
- kind: "occupied",
697
- reason: `creation claim ${claim.claimId} refuses occupied live address ${address.name}`
698
- } : { kind: "vacant" }
699
- }
700
- }
701
- });
702
- if (result.kind === "outcome-recovery-required")
703
- return {
704
- outcome: "recovery-required",
705
- claimId: result.claimId,
706
- detail: `claim ${result.claimId} remains open and requires recovery: ${result.reason}`
707
- };
708
- if ("reason" in result) return partial(result.reason);
709
- completedStepKeys.push(step.stepKey);
710
- continue;
711
- }
712
- const containment = step.classification === "removal" ? await removalContainment(input.pool, address) : void 0;
713
- const closureMembers = /* @__PURE__ */ new Map();
714
- for (const member of step.closure?.members ?? []) {
715
- closureMembers.set(ledgerAddressKey(member.address), {
716
- address: member.address,
717
- plannedClaimKey: member.plannedClaimKey
718
- });
719
- }
720
- if (containment?.kind === "all-contained-or-managed") {
721
- for (const effect of containment.effects) {
722
- if (effect.internalOwned) continue;
723
- if (!containedBy(address, effect.address)) continue;
724
- if (!await managed(input.pool, effect.address)) continue;
725
- const key = ledgerAddressKey(effect.address);
726
- if (closureMembers.has(key)) continue;
727
- closureMembers.set(key, {
728
- address: effect.address,
729
- plannedClaimKey: `closure:${effect.address.kind}:${effect.address.name}`
730
- });
731
- }
732
- }
733
- const containedClaims = [];
734
- for (const member of closureMembers.values()) {
735
- const effect = member.address;
736
- const childClaimId = outcomeClaimId(
737
- executionId,
738
- plannedClaimKey,
739
- effect,
740
- member.plannedClaimKey
741
- );
742
- const childReservation = {
743
- ...baseReservation,
744
- address: effect,
745
- rootClaimId: claim.claimId
746
- };
747
- containedClaims.push({
748
- plan: {
749
- ...claim,
750
- claimId: childClaimId,
751
- claimSpecies: "cascade-covered",
752
- address: effect,
753
- plannedClaimKey: member.plannedClaimKey,
754
- // Each member contributes only its terminal absence fact; SQL is
755
- // exclusively carried by the destructive root manifest claim.
756
- statementBundle: { statements: [] }
757
- },
758
- reservation: childReservation
759
- });
760
- }
761
- const admitRequest = {
762
- plan: claim,
763
- reservations: [baseReservation],
764
- members: containedClaims.map(({ plan, reservation }) => ({
765
- plan,
766
- reservations: [reservation]
767
- })),
768
- destructiveDecision: async (executor) => {
769
- const lockedAuthority = await destructiveEvidence({
770
- executor,
771
- address,
772
- classification: step.classification,
773
- selection: step.selection,
774
- planDigest: input.planDigest,
775
- approval
776
- });
777
- if (step.classification === "removal" && (containment?.kind !== "all-contained-or-managed" || lockedAuthority.containment?.kind !== "all-contained-or-managed" || containment.closureDigest !== lockedAuthority.containment.closureDigest)) {
778
- destructiveClosureReplanRequired = true;
779
- const decision2 = decideDestructiveDecision(
780
- { kind: "removal", address },
781
- { ...lockedAuthority.evidence, containment: "undecidable" }
782
- );
783
- if (decision2.kind === "destructive-decision-refused")
784
- destructiveRefusal = {
785
- withheldAuthority: withheldDestructiveAuthority({
786
- ...lockedAuthority.evidence,
787
- containment: "undecidable"
788
- }) ?? "destructive containment authority"
789
- };
790
- return decision2;
791
- }
792
- const decision = decideDestructiveDecision(
793
- {
794
- kind: step.classification === "removal" ? "removal" : "data-destructive",
795
- address,
796
- ...step.classification === "removal" && lockedAuthority.containment?.kind === "all-contained-or-managed" ? { closureDigest: lockedAuthority.containment.closureDigest } : {}
797
- },
798
- lockedAuthority.evidence
799
- );
800
- if (decision.kind === "destructive-decision-refused")
801
- destructiveRefusal = {
802
- withheldAuthority: withheldDestructiveAuthority(lockedAuthority.evidence) ?? "destructive authority"
803
- };
804
- return decision;
805
- },
806
- ...input.observer === void 0 ? {} : { observer: input.observer }
807
- };
808
- const executed = await executePgAdmittedOperation(input.pool, {
809
- run: input.run,
810
- approval,
811
- manifest,
812
- recomputedPlanDigest: input.planDigest,
813
- operation: {
814
- kind: "destructive-outcome",
815
- request: admitRequest,
816
- readBackAndResolve: async (session) => {
817
- const live = await readPgCatalogueIdentity(session, address);
818
- const survivors = step.classification === "removal" && live ? [address] : [];
819
- const childLives = await Promise.all(
820
- containedClaims.map(async (child) => ({
821
- child,
822
- live: await readPgCatalogueIdentity(
823
- session,
824
- child.plan.address
825
- )
826
- }))
827
- );
828
- for (const { child, live: childLive } of childLives)
829
- if (childLive) survivors.push(child.plan.address);
830
- if (survivors.length > 0) {
831
- const survivorNames = survivors.map(
832
- (survivor) => `${survivor.kind} ${survivor.schema ?? "<database>"}.${survivor.name}`
833
- );
834
- const survivorObservation = {
835
- value: { survivors: survivorNames },
836
- digest: createHash("sha256").update(JSON.stringify(survivorNames)).digest("hex")
837
- };
838
- return {
839
- rootClaimId: claim.claimId,
840
- members: [
841
- {
842
- target: home(address),
843
- member: {
844
- eventId: outcomeClaimEventId(
845
- claim.claimId,
846
- "indeterminate"
847
- ),
848
- executionId,
849
- plannedClaimKey: claim.plannedClaimKey,
850
- claimGroupId: claim.claimGroupId,
851
- rootClaimId: claim.rootClaimId,
852
- address,
853
- eventKind: "indeterminate",
854
- observed: survivorObservation
855
- }
856
- },
857
- ...containedClaims.map((child) => ({
858
- target: home(child.plan.address),
859
- member: {
860
- eventId: outcomeClaimEventId(
861
- child.plan.claimId,
862
- "indeterminate"
863
- ),
864
- executionId,
865
- plannedClaimKey: child.plan.plannedClaimKey,
866
- claimGroupId: claim.claimId,
867
- rootClaimId: claim.claimId,
868
- address: child.plan.address,
869
- eventKind: "indeterminate",
870
- observed: survivorObservation
871
- }
872
- }))
873
- ],
874
- reservations: [
875
- baseReservation,
876
- ...containedClaims.map(({ reservation }) => reservation)
877
- ]
878
- };
879
- }
880
- const terminals = [
881
- {
882
- target: home(address),
883
- member: {
884
- eventId: outcomeClaimEventId(
885
- claim.claimId,
886
- step.classification === "removal" ? "absent" : "observed"
887
- ),
888
- executionId,
889
- plannedClaimKey: claim.plannedClaimKey,
890
- claimGroupId: claim.claimGroupId,
891
- rootClaimId: claim.rootClaimId,
892
- address,
893
- eventKind: step.classification === "removal" ? "absent" : "observed",
894
- // The append protocol reads this address's terminal on its
895
- // pinned transaction session after the post-DDL read-back.
896
- ...live?.catalogueIdentity ? { catalogueIdentity: live.catalogueIdentity } : {},
897
- ...step.classification === "removal" ? {} : { observed: observed(address) }
898
- }
899
- }
900
- ];
901
- for (const child of containedClaims) {
902
- terminals.push({
903
- target: home(child.plan.address),
904
- member: {
905
- eventId: outcomeClaimEventId(child.plan.claimId, "absent"),
906
- executionId,
907
- plannedClaimKey: child.plan.plannedClaimKey,
908
- claimGroupId: claim.claimId,
909
- rootClaimId: claim.claimId,
910
- address: child.plan.address,
911
- eventKind: "absent",
912
- predecessor: child.plan.claimId
913
- }
914
- });
915
- }
916
- return {
917
- rootClaimId: claim.claimId,
918
- members: terminals,
919
- reservations: [
920
- baseReservation,
921
- ...containedClaims.map(({ reservation }) => reservation)
922
- ]
923
- };
924
- }
925
- }
926
- });
927
- if (executed.kind === "outcome-recovery-required")
928
- return {
929
- outcome: "recovery-required",
930
- claimId: executed.claimId,
931
- detail: `claim ${executed.claimId} remains open and requires recovery: ${executed.reason}`
932
- };
933
- if (executed.kind === "outcome-transport-ambiguous")
934
- return {
935
- outcome: "execution-failed",
936
- detail: executed.reason
937
- };
938
- if (executed.kind === "outcome-protocol-pending")
939
- return partial(
940
- `destructive claim ${claim.claimId} remains pending after executing: ${executed.reason}`
941
- );
942
- if (executed.kind !== "executed-destructive-outcome")
943
- return {
944
- outcome: "destructive-authority-refused",
945
- detail: destructiveClosureReplanRequired ? `${executed.reason}; destructive closure changed under lock; replan required` : executed.reason,
946
- ...destructiveRefusal === void 0 && withheldDestructiveAuthorityFromReason(executed.reason) === void 0 ? {} : {
947
- refusal: destructiveRefusal ?? {
948
- withheldAuthority: withheldDestructiveAuthorityFromReason(executed.reason) ?? "destructive authority"
949
- }
950
- }
951
- };
952
- completedStepKeys.push(step.stepKey);
953
- }
954
- return { outcome: "completed" };
955
- } catch (error) {
956
- return partial(error instanceof Error ? error.message : String(error));
957
- }
958
- }
959
-
960
65
  // src/commands/generator-plan.ts
961
- import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
66
+ import { randomUUID } from "crypto";
962
67
  import {
963
68
  assertDeclarableChangeKind,
964
69
  classifyGeneratedMutation,
965
70
  comparePgsqlDatabaseSchema,
966
- createPgsqlAdapter as createPgsqlAdapter2,
71
+ createPgPhysicalModel,
72
+ createPgsqlAdapter,
967
73
  createPgsqlGeneratedManagedStep,
968
74
  createPgTransitionLessor,
969
75
  createPgTransitionRunPersister,
970
76
  generatedPostconditionForChange,
971
77
  generateMigrationSQL,
972
- readPgCatalogueIdentity as readPgCatalogueIdentity2,
78
+ ReferencedKeyRemovalError,
79
+ readPgCatalogueIdentity,
973
80
  renderPgTableReaddressStatements
974
81
  } from "@dbsp/adapter-pgsql";
82
+ import {
83
+ collectReferencedKeyRemovalConflicts,
84
+ comparePgsqlDeclaredAdoptionSchema,
85
+ createPgsqlDeclaredAdoptionStep,
86
+ generateMigrationSQL as generateMigrationSQLForPhysicalDiff,
87
+ modelForDeclaredAdoption,
88
+ pgsqlDeclaredAdoptionDeclaration
89
+ } from "@dbsp/adapter-pgsql/internal";
975
90
  import {
976
91
  acquireTransitionLease,
92
+ canonicalJsonDigest,
977
93
  transitionPlanDigest,
978
- validateNormalizedManagedStepManifest as validateNormalizedManagedStepManifest2
94
+ validateNormalizedManagedStepManifest
979
95
  } from "@dbsp/core";
980
- function digest(value) {
981
- return createHash2("sha256").update(JSON.stringify(value)).digest("hex");
982
- }
983
- function adoptionDeclaration(table) {
984
- const value = JSON.parse(
985
- JSON.stringify({ kind: "table", name: table.name, shape: table })
986
- );
987
- return { value, digest: digest(value) };
96
+ function decodeGeneratorPlanMaterial(material) {
97
+ if (material === null || typeof material !== "object" || Array.isArray(material) || !("kind" in material) || material.kind !== "schema-differ-generator")
98
+ throw new Error("persisted generator material has an invalid kind");
99
+ const dbCasing = "dbCasing" in material ? material.dbCasing : void 0;
100
+ if (dbCasing !== void 0 && dbCasing !== "snake_case" && dbCasing !== "camelCase")
101
+ throw new Error("persisted generator material has an invalid dbCasing");
102
+ return {
103
+ ...material,
104
+ dbCasing: dbCasing ?? "preserve"
105
+ };
988
106
  }
989
107
  function replacementStatements(table, schema) {
990
108
  const quote = (value) => `"${value.replaceAll('"', '""')}"`;
@@ -1008,7 +126,7 @@ function replacementStatements(table, schema) {
1008
126
  };
1009
127
  return {
1010
128
  retireStatements: [`DROP TABLE ${quote(schema)}.${quote(table.name)}`],
1011
- createStatements: generateMigrationSQL(createDiff, {
129
+ createStatements: generateMigrationSQLForPhysicalDiff(createDiff, {
1012
130
  includeDestructive: true,
1013
131
  schemaName: schema
1014
132
  })
@@ -1127,6 +245,65 @@ function changeTargetsDeclaredTable(change, table) {
1127
245
  "alter_sequence"
1128
246
  ])).has(change.kind);
1129
247
  }
248
+ function getGeneratorPhase(kind) {
249
+ switch (kind) {
250
+ case "drop_foreign_key":
251
+ case "drop_check_constraint":
252
+ return 0;
253
+ case "drop_index":
254
+ return 1;
255
+ case "drop_column":
256
+ return 2;
257
+ case "drop_primary_key":
258
+ return 3;
259
+ case "drop_table":
260
+ case "drop_enum":
261
+ case "drop_extension":
262
+ case "drop_sequence":
263
+ return 4;
264
+ case "create_enum":
265
+ case "create_extension":
266
+ case "create_sequence":
267
+ return 5;
268
+ case "create_table":
269
+ case "readdress_table":
270
+ return 6;
271
+ case "add_column":
272
+ return 7;
273
+ case "alter_sequence":
274
+ case "alter_column_type":
275
+ case "alter_column_nullable":
276
+ case "alter_column_default":
277
+ case "alter_column_collation":
278
+ case "alter_column_identity":
279
+ case "alter_column_auto_increment":
280
+ return 8;
281
+ case "alter_column_unique":
282
+ case "add_primary_key":
283
+ return 9;
284
+ case "create_index":
285
+ return 10;
286
+ case "add_foreign_key":
287
+ return 11;
288
+ case "alter_foreign_key":
289
+ return 12;
290
+ case "add_check_constraint":
291
+ return 13;
292
+ case "alter_enum_add_value":
293
+ return 14;
294
+ case "add_comment":
295
+ case "drop_comment":
296
+ return 15;
297
+ case "validate_constraint":
298
+ return 16;
299
+ case "enable_rls":
300
+ case "disable_rls":
301
+ return 17;
302
+ case "create_policy":
303
+ case "drop_policy":
304
+ return 18;
305
+ }
306
+ }
1130
307
  function requiredAdoptionIdentity(identities, table) {
1131
308
  const identity = identities.get(table);
1132
309
  if (!identity)
@@ -1134,7 +311,7 @@ function requiredAdoptionIdentity(identities, table) {
1134
311
  return identity;
1135
312
  }
1136
313
  function asDurableGeneratorPlan(material, steps) {
1137
- const validation = validateNormalizedManagedStepManifest2(steps);
314
+ const validation = validateNormalizedManagedStepManifest(steps);
1138
315
  if (!validation.ok)
1139
316
  throw new Error(
1140
317
  `generator planning refuses invalid managed-step manifest: ${validation.detail}`
@@ -1169,7 +346,7 @@ function render(plan, planDigest) {
1169
346
  ]
1170
347
  ].join("\n");
1171
348
  }
1172
- async function databaseId2(pool) {
349
+ async function databaseId(pool) {
1173
350
  const result = await pool.query("SELECT current_database() AS database_id");
1174
351
  const value = result.rows[0]?.database_id;
1175
352
  if (typeof value !== "string" || value.length === 0)
@@ -1180,19 +357,39 @@ async function databaseId2(pool) {
1180
357
  }
1181
358
  async function runGeneratorPlan(input) {
1182
359
  const loaded = await loadSchema(input.schemaFile);
360
+ for (const sequence of loaded.model.sequences?.values() ?? []) {
361
+ if (sequence.adopt === true)
362
+ throw new Error(
363
+ `generator planning refuses sequence adoption for ${sequence.name}: sequence adoption is available through convergePg / dbsp migrate`
364
+ );
365
+ }
366
+ const schema = input.schema ?? "public";
367
+ const dbCasing = loaded.dbCasing ?? "preserve";
368
+ const physical = createPgPhysicalModel({
369
+ mode: "logical",
370
+ model: loaded.model,
371
+ schema,
372
+ ...loaded.dbCasing === void 0 ? {} : { dbCasing: loaded.dbCasing }
373
+ });
374
+ const physicalTable = (name) => physical.inventory.get({ kind: "table", schema, name });
375
+ for (const table of loaded.model.tables.values()) {
376
+ if (table.replace !== true) continue;
377
+ const physicalName = physicalTable(table.name);
378
+ if (physicalName !== table.name)
379
+ throw new Error(
380
+ `generator planning refuses replacement ${table.name}: dbCasing addresses physical table ${physicalName}; replacement requires preserve casing`
381
+ );
382
+ }
1183
383
  const { pool } = await createDbConnection(input.db);
1184
384
  try {
1185
- const schema = input.schema ?? "public";
1186
385
  const diff = await comparePgsqlDatabaseSchema(
1187
- createPgsqlAdapter2(pool),
1188
- loaded.model,
386
+ createPgsqlAdapter(pool),
387
+ physical,
1189
388
  {
1190
- schema,
1191
389
  // `apply --schema` owns one schema. Extensions are database-scoped,
1192
390
  // so an extension not declared by this schema must not become a
1193
391
  // schema-plan removal merely because it is installed in the database.
1194
- ignoreUnmanagedExtensions: true,
1195
- ...loaded.dbCasing ? { dbCasing: loaded.dbCasing } : {}
392
+ ignoreUnmanagedExtensions: true
1196
393
  }
1197
394
  );
1198
395
  const declaredLifecycleWork = [...loaded.model.tables.values()].some(
@@ -1221,39 +418,57 @@ async function runGeneratorPlan(input) {
1221
418
  planDigest: null
1222
419
  };
1223
420
  }
1224
- const adoptionMismatches = new Set(
1225
- [...loaded.model.tables.values()].filter(
1226
- (table) => table.adopt === true && diff.changes.some(
1227
- (change) => changeTargetsDeclaredTable(change, table.name)
1228
- )
1229
- ).map((table) => table.name)
1230
- );
1231
- const database = await databaseId2(pool);
421
+ const adoptionPhysicalNames = /* @__PURE__ */ new Map();
422
+ const adoptionMismatches = /* @__PURE__ */ new Set();
423
+ for (const table of loaded.model.tables.values()) {
424
+ if (table.adopt !== true) continue;
425
+ const physicalName = physicalTable(table.name);
426
+ adoptionPhysicalNames.set(table.name, physicalName);
427
+ const compared = await comparePgsqlDeclaredAdoptionSchema({
428
+ executor: pool,
429
+ model: modelForDeclaredAdoption(table),
430
+ schema,
431
+ dbCasing
432
+ });
433
+ if (compared.changes.length > 0) adoptionMismatches.add(physicalName);
434
+ }
435
+ const database = await databaseId(pool);
1232
436
  const adoptionIdentities = /* @__PURE__ */ new Map();
1233
437
  for (const table of loaded.model.tables.values()) {
1234
- if (table.adopt !== true || adoptionMismatches.has(table.name)) continue;
1235
- const live = await readPgCatalogueIdentity2(pool, {
438
+ const physicalName = adoptionPhysicalNames.get(table.name);
439
+ if (table.adopt !== true || physicalName === void 0 || adoptionMismatches.has(physicalName))
440
+ continue;
441
+ const live = await readPgCatalogueIdentity(pool, {
1236
442
  engine: "postgresql",
1237
443
  database,
1238
444
  schema: input.schema ?? "public",
1239
445
  kind: "table",
1240
- name: table.name
446
+ name: physicalName
1241
447
  });
1242
448
  if (!live?.catalogueIdentity)
1243
449
  throw new Error(
1244
- `declared adoption for ${table.name} refuses absent live identity`
450
+ `declared adoption for ${physicalName} refuses absent live identity`
1245
451
  );
1246
- adoptionIdentities.set(table.name, live.catalogueIdentity);
452
+ adoptionIdentities.set(physicalName, live.catalogueIdentity);
1247
453
  }
1248
454
  const executableDiff = {
1249
455
  ...diff,
1250
456
  changes: diff.changes.filter(
1251
457
  (change) => ![...loaded.model.tables.values()].some(
1252
- (table) => (table.adopt === true || table.replace === true) && changeTargetsDeclaredTable(change, table.name)
458
+ (table) => (table.adopt === true || table.replace === true) && changeTargetsDeclaredTable(
459
+ change,
460
+ table.adopt === true ? adoptionPhysicalNames.get(table.name) ?? table.name : table.name
461
+ )
1253
462
  )
1254
463
  )
1255
464
  };
1256
- const ordinaryChanges = executableDiff.changes.map((change) => ({
465
+ const phaseOrderedChanges = [...executableDiff.changes].sort(
466
+ (left, right) => getGeneratorPhase(left.kind) - getGeneratorPhase(right.kind)
467
+ );
468
+ const referencedKeyConflicts = collectReferencedKeyRemovalConflicts(phaseOrderedChanges);
469
+ if (referencedKeyConflicts.length > 0)
470
+ throw new ReferencedKeyRemovalError(referencedKeyConflicts);
471
+ const ordinaryChanges = phaseOrderedChanges.map((change) => ({
1257
472
  kind: change.kind,
1258
473
  table: change.table,
1259
474
  ...change.column ? { column: change.column } : {},
@@ -1264,45 +479,54 @@ async function runGeneratorPlan(input) {
1264
479
  schema,
1265
480
  change.meta.readdress
1266
481
  ) : generateMigrationSQL(
1267
- { ...executableDiff, changes: [change] },
1268
- { includeDestructive: true, schemaName: schema }
482
+ { ...executableDiff, physical: diff.physical, changes: [change] },
483
+ { includeDestructive: true }
1269
484
  ),
1270
485
  ...change.kind === "readdress_table" && change.meta?.readdress ? { readdress: change.meta.readdress } : {}
1271
486
  }));
1272
487
  const material = {
1273
488
  kind: "schema-differ-generator",
1274
489
  planningSchema: schema,
490
+ ...dbCasing === "preserve" ? {} : { dbCasing },
1275
491
  changes: [
1276
492
  ...ordinaryChanges,
1277
- ...[...loaded.model.tables.values()].filter((table) => adoptionMismatches.has(table.name)).map((table) => ({
1278
- kind: "adoption_refused",
1279
- table: table.name,
1280
- classification: "non-destructive",
1281
- details: `Refuse adoption of table "${table.name}": live shape does not match declaration`,
1282
- statements: []
1283
- })),
1284
- // `comparePgsqlDatabaseSchema` has just compared each side through
1285
- // the established live canonicalisation path. An adopted table is
1286
- // admitted only when that comparison has no remaining change for it.
1287
- ...[...loaded.model.tables.values()].filter(
1288
- (table) => table.adopt === true && !diff.changes.some(
1289
- (change) => changeTargetsDeclaredTable(change, table.name)
1290
- )
1291
- ).map((table) => ({
1292
- kind: "adopt_table",
1293
- table: table.name,
1294
- classification: "non-destructive",
1295
- details: `Adopt existing table "${table.name}" after live shape match`,
1296
- statements: [],
1297
- adoption: {
1298
- declaration: adoptionDeclaration(table),
1299
- shape: table,
1300
- catalogueIdentity: requiredAdoptionIdentity(
1301
- adoptionIdentities,
1302
- table.name
1303
- )
1304
- }
1305
- })),
493
+ ...[...loaded.model.tables.values()].flatMap((table) => {
494
+ const physicalName = adoptionPhysicalNames.get(table.name);
495
+ if (physicalName === void 0 || !adoptionMismatches.has(physicalName))
496
+ return [];
497
+ return [
498
+ {
499
+ kind: "adoption_refused",
500
+ table: physicalName,
501
+ classification: "non-destructive",
502
+ details: `Refuse adoption of table "${physicalName}": live shape does not match declaration`,
503
+ statements: []
504
+ }
505
+ ];
506
+ }),
507
+ // Adoption is admitted only by its declaration-scoped comparison.
508
+ ...[...loaded.model.tables.values()].flatMap((table) => {
509
+ const physicalName = adoptionPhysicalNames.get(table.name);
510
+ if (table.adopt !== true || physicalName === void 0 || adoptionMismatches.has(physicalName))
511
+ return [];
512
+ return [
513
+ {
514
+ kind: "adopt_table",
515
+ table: physicalName,
516
+ classification: "non-destructive",
517
+ details: `Adopt existing table "${physicalName}" after live shape match`,
518
+ statements: [],
519
+ adoption: {
520
+ declaration: pgsqlDeclaredAdoptionDeclaration(table),
521
+ shape: table,
522
+ catalogueIdentity: requiredAdoptionIdentity(
523
+ adoptionIdentities,
524
+ physicalName
525
+ )
526
+ }
527
+ }
528
+ ];
529
+ }),
1306
530
  ...[...loaded.model.tables.values()].filter((table) => table.replace === true).map((table) => ({
1307
531
  kind: "replace_table",
1308
532
  table: table.name,
@@ -1314,7 +538,7 @@ async function runGeneratorPlan(input) {
1314
538
  ]
1315
539
  };
1316
540
  const ordinaryStepSources = [];
1317
- for (const [changeIndex, change] of executableDiff.changes.entries()) {
541
+ for (const [changeIndex, change] of phaseOrderedChanges.entries()) {
1318
542
  const generated = ordinaryChanges[changeIndex];
1319
543
  if (!generated)
1320
544
  throw new Error(
@@ -1431,18 +655,27 @@ async function runGeneratorPlan(input) {
1431
655
  continue;
1432
656
  }
1433
657
  if (change.kind === "adopt_table" && change.adoption) {
1434
- lifecycleSteps.push(
1435
- lifecycleStep({
1436
- ...base,
1437
- stepKey: `generator:${base.order}:adoption`,
1438
- classification: "non-destructive",
1439
- selection: { kind: "adoption", selector: `table:${change.table}` },
1440
- expectedDeclaration: change.adoption.declaration,
1441
- expectedCatalogueIdentity: change.adoption.catalogueIdentity,
1442
- claimKind: "adopt-intent",
1443
- lifecycle: { kind: "adoption", shape: change.adoption.shape }
1444
- })
1445
- );
658
+ const adoptionStep = createPgsqlDeclaredAdoptionStep({
659
+ address: {
660
+ scope: "schema",
661
+ engine: "postgresql",
662
+ database,
663
+ schema,
664
+ kind: "table",
665
+ name: change.table
666
+ },
667
+ table: change.adoption.shape,
668
+ stepKey: `generator:${base.order}:adoption`,
669
+ order: base.order,
670
+ catalogueIdentity: change.adoption.catalogueIdentity
671
+ });
672
+ lifecycleSteps.push({
673
+ ...adoptionStep,
674
+ selection: {
675
+ kind: "adoption",
676
+ selector: `table:${change.table}`
677
+ }
678
+ });
1446
679
  continue;
1447
680
  }
1448
681
  lifecycleSteps.push(
@@ -1479,10 +712,13 @@ async function runGeneratorPlan(input) {
1479
712
  plan
1480
713
  );
1481
714
  const run = {
1482
- runId: `dbsp-generator-${randomUUID2()}`,
715
+ runId: `dbsp-generator-${randomUUID()}`,
1483
716
  planDigest,
1484
- targetContextDigest: digest({ database: await databaseId2(pool), schema }),
1485
- databaseId: await databaseId2(pool),
717
+ targetContextDigest: canonicalJsonDigest({
718
+ database: await databaseId(pool),
719
+ schema
720
+ }),
721
+ databaseId: await databaseId(pool),
1486
722
  coreVersion: "schema-differ-generator-v1",
1487
723
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1488
724
  replayability: material.changes.some(
@@ -1521,20 +757,25 @@ async function runGeneratorPlan(input) {
1521
757
  import {
1522
758
  acquirePgTransitionClient,
1523
759
  createPgExecutionContract,
1524
- createPgsqlAdapter as createPgsqlAdapter3,
760
+ createPgPhysicalModel as createPgPhysicalModel2,
761
+ createPgsqlAdapter as createPgsqlAdapter2,
1525
762
  createPgTransitionLessor as createPgTransitionLessor2,
1526
763
  createPgTransitionPack,
1527
764
  createPgTransitionRunPersister as createPgTransitionRunPersister2,
1528
765
  ensureTransitionJournal,
1529
766
  escapeDiagnosticText,
1530
- getNamingPluginForDbCasing,
1531
767
  PgExecutionContractDerivationError,
1532
768
  pgTargetIdentityMismatch,
1533
769
  readPgExecutionTargetFromClient,
1534
770
  readPgObservationContextFromLessor
1535
771
  } from "@dbsp/adapter-pgsql";
772
+ import {
773
+ PgTransitionRunPersistenceIndeterminateError,
774
+ planPgTransitionRun
775
+ } from "@dbsp/adapter-pgsql/internal";
1536
776
  import {
1537
777
  acquireTransitionLease as acquireTransitionLease2,
778
+ assertCanonicalizableJson,
1538
779
  bindDeclarationSet,
1539
780
  bindExecutionContract,
1540
781
  createComparator,
@@ -1545,6 +786,7 @@ import {
1545
786
  validateDeclarationModel
1546
787
  } from "@dbsp/core";
1547
788
  import { Command } from "commander";
789
+ var planPhysicalPgTransitionRun = planPgTransitionRun;
1548
790
  var PlanPersistenceIndeterminateError = class extends Error {
1549
791
  runId;
1550
792
  result;
@@ -1594,6 +836,15 @@ var PlanOperationAndCleanupError = class extends AggregateError {
1594
836
  cleanupError;
1595
837
  };
1596
838
  var noCleanupFailure = { kind: "not-failed" };
839
+ function validateSourceDeclarationValues(model) {
840
+ for (const [tableKey, table] of model.tables)
841
+ for (const [index, column] of table.columns.entries())
842
+ if (column.default !== void 0)
843
+ assertCanonicalizableJson(
844
+ column.default,
845
+ `schema.tables[${JSON.stringify(tableKey)}].columns[${index}].default`
846
+ );
847
+ }
1597
848
  function equivalenceContext(context) {
1598
849
  return {
1599
850
  engine: context.engine,
@@ -1674,20 +925,20 @@ var defaultPlanDeps = {
1674
925
  async loadCurrent(pool, schema, expectedTargetIdentity) {
1675
926
  const lease = await acquirePgTransitionClient(pool);
1676
927
  try {
1677
- const observed2 = await readPgExecutionTargetFromClient(
928
+ const observed = await readPgExecutionTargetFromClient(
1678
929
  lease.client,
1679
930
  [schema ?? "public"]
1680
931
  );
1681
932
  const mismatch = pgTargetIdentityMismatch(
1682
933
  expectedTargetIdentity,
1683
- observed2.identity
934
+ observed.identity
1684
935
  );
1685
936
  if (mismatch) {
1686
937
  throw new Error(
1687
938
  `PostgreSQL introspection target identity does not match the captured target: ${mismatch}`
1688
939
  );
1689
940
  }
1690
- return await createPgsqlAdapter3(lease.client, {
941
+ return await createPgsqlAdapter2(lease.client, {
1691
942
  borrowedClient: true
1692
943
  }).introspect(schema === void 0 ? {} : { schema });
1693
944
  } finally {
@@ -1702,18 +953,14 @@ var defaultPlanDeps = {
1702
953
  expectedTargetIdentity
1703
954
  );
1704
955
  },
1705
- createPlanner(loaded) {
1706
- const registry2 = createPackRegistry([
1707
- createPgTransitionPack({
1708
- ...loaded.dbCasing ? { dbCasing: loaded.dbCasing } : {}
1709
- })
1710
- ]);
1711
- const comparator = createComparator(registry2);
1712
- const prover = createProver(registry2);
956
+ createPlanner() {
957
+ const registry = createPackRegistry([createPgTransitionPack()]);
958
+ const comparator = createComparator(registry);
959
+ const prover = createProver(registry);
1713
960
  return {
1714
961
  compare: (desired, current, context) => comparator.compare(desired, current, equivalenceContext(context)),
1715
962
  prove: (compare, pool, context) => prover.prove(compare, createPgTransitionLessor2(pool), context),
1716
- render: (plan, context) => renderProvenPlanSql(registry2, plan, context)
963
+ render: (plan, context) => renderProvenPlanSql(registry, plan, context)
1717
964
  };
1718
965
  },
1719
966
  async persist(pool, run, plan) {
@@ -1748,9 +995,9 @@ function executionContractBlockedAssessment(error) {
1748
995
  ]
1749
996
  };
1750
997
  }
1751
- function renderProvenPlanSql(registry2, plan, context) {
998
+ function renderProvenPlanSql(registry, plan, context) {
1752
999
  const statements = plan.steps.map((step) => {
1753
- const resolution = registry2.resolveOperation(step.operation);
1000
+ const resolution = registry.resolveOperation(step.operation);
1754
1001
  if (!resolution.ok || !resolution.semantics.renderPlanSql) {
1755
1002
  throw new Error(
1756
1003
  `no SQL renderer is registered for proven operation ${step.operation.operationKind.name}`
@@ -1770,126 +1017,170 @@ async function runPlan(options, overrides) {
1770
1017
  throw new Error(`unsupported plan format ${format}; expected sql or json`);
1771
1018
  }
1772
1019
  const loaded = await deps.loadSchema(options.schemaFile);
1773
- validateDeclarationModel(loaded.model);
1020
+ validateSourceDeclarationValues(loaded.model);
1021
+ const physical = createPgPhysicalModel2({
1022
+ mode: "logical",
1023
+ model: loaded.model,
1024
+ schema: options.schema ?? "public",
1025
+ ...loaded.dbCasing === void 0 ? {} : { dbCasing: loaded.dbCasing }
1026
+ });
1027
+ validateDeclarationModel(physical.model);
1774
1028
  const connection = await deps.createDbConnection(options.db);
1775
1029
  const { pool } = connection;
1776
1030
  let result;
1031
+ let renderedSql;
1777
1032
  let operationError;
1778
1033
  let failed = false;
1779
1034
  try {
1780
- const targetIdentity = await deps.captureTargetIdentity(
1781
- pool,
1782
- options.schema
1783
- );
1784
- await deps.ensureTransitionJournal(pool);
1785
- const current = await deps.loadCurrent(
1786
- pool,
1787
- options.schema,
1788
- targetIdentity
1789
- );
1790
- const context = await deps.readContext(
1791
- pool,
1792
- options.schema,
1793
- targetIdentity
1794
- );
1795
- const planner = deps.createPlanner(loaded);
1796
- const compare = planner.compare(loaded.model, current, context);
1797
- const prove = await planner.prove(compare, pool, context);
1798
- if (prove.kind === "proven") {
1799
- let executionContract;
1800
- try {
1801
- executionContract = await deps.buildExecutionContract(
1802
- pool,
1803
- options.schema,
1804
- prove.plan,
1805
- targetIdentity
1806
- );
1807
- } catch (error) {
1808
- if (!(error instanceof PgExecutionContractDerivationError)) throw error;
1035
+ if (overrides === void 0) {
1036
+ const lifecycle = await planPhysicalPgTransitionRun(
1037
+ physical,
1038
+ async (client, schema) => createPgsqlAdapter2(client, {
1039
+ borrowedClient: true
1040
+ }).introspect(schema === void 0 ? {} : { schema }),
1041
+ pool,
1042
+ {
1043
+ persist: options.dryRun !== true,
1044
+ beforePersist: async (plan, proofContext) => {
1045
+ renderedSql = deps.createPlanner().render(plan, proofContext);
1046
+ }
1047
+ }
1048
+ );
1049
+ if (lifecycle.kind === "proven") {
1050
+ if (renderedSql === void 0)
1051
+ throw new Error("internal error: durable plan SQL was not rendered");
1809
1052
  result = {
1810
- compareKind: compare.kind,
1811
- proveKind: "blocked",
1812
- assessment: executionContractBlockedAssessment(error),
1053
+ compareKind: lifecycle.compare.kind,
1054
+ proveKind: "proven",
1055
+ assessment: lifecycle.assessment,
1056
+ persisted: options.dryRun !== true,
1057
+ runId: options.dryRun === true ? null : lifecycle.run.runId,
1058
+ planDigest: lifecycle.run.planDigest,
1059
+ plan: lifecycle.plan,
1060
+ sql: renderedSql
1061
+ };
1062
+ } else {
1063
+ result = {
1064
+ compareKind: lifecycle.compare.kind,
1065
+ proveKind: lifecycle.kind,
1066
+ assessment: lifecycle.assessment,
1813
1067
  persisted: false,
1814
1068
  runId: null,
1815
1069
  planDigest: null
1816
1070
  };
1817
1071
  }
1818
- if (executionContract !== void 0) {
1819
- const contractedPlan = bindExecutionContract(
1820
- prove.plan,
1821
- executionContract
1822
- );
1823
- const durablePlan = bindDeclarationSet(
1824
- contractedPlan,
1825
- declarationSetFromModel(
1826
- loaded.model,
1827
- {
1072
+ } else {
1073
+ const targetIdentity = await deps.captureTargetIdentity(
1074
+ pool,
1075
+ physical.schema
1076
+ );
1077
+ await deps.ensureTransitionJournal(pool);
1078
+ const current = await deps.loadCurrent(
1079
+ pool,
1080
+ physical.schema,
1081
+ targetIdentity
1082
+ );
1083
+ const context = await deps.readContext(
1084
+ pool,
1085
+ physical.schema,
1086
+ targetIdentity
1087
+ );
1088
+ const planner = deps.createPlanner();
1089
+ const compare = planner.compare(physical.model, current, context);
1090
+ const prove = await planner.prove(compare, pool, context);
1091
+ if (prove.kind === "proven") {
1092
+ let executionContract;
1093
+ try {
1094
+ executionContract = await deps.buildExecutionContract(
1095
+ pool,
1096
+ physical.schema,
1097
+ prove.plan,
1098
+ targetIdentity
1099
+ );
1100
+ } catch (error) {
1101
+ if (!(error instanceof PgExecutionContractDerivationError))
1102
+ throw error;
1103
+ result = {
1104
+ compareKind: compare.kind,
1105
+ proveKind: "blocked",
1106
+ assessment: executionContractBlockedAssessment(error),
1107
+ persisted: false,
1108
+ runId: null,
1109
+ planDigest: null
1110
+ };
1111
+ }
1112
+ if (executionContract !== void 0) {
1113
+ const contractedPlan = bindExecutionContract(
1114
+ prove.plan,
1115
+ executionContract
1116
+ );
1117
+ const durablePlan = bindDeclarationSet(
1118
+ contractedPlan,
1119
+ declarationSetFromModel(physical.model, {
1828
1120
  engine: context.engine,
1829
1121
  database: context.databaseId,
1830
- schema: options.schema ?? "public"
1831
- },
1832
- getNamingPluginForDbCasing(loaded.dbCasing ?? "preserve")
1833
- )
1834
- );
1835
- const run = createTransitionRunMetadata(durablePlan);
1836
- const proofContext = prove.plan.observations.find(
1837
- (observation) => observation.role === "evidence"
1838
- )?.context;
1839
- if (!proofContext) {
1840
- throw new Error(
1841
- "internal error: minted proven plan has no evidence observation context"
1122
+ schema: physical.schema
1123
+ })
1842
1124
  );
1843
- }
1844
- const sql = planner.render(durablePlan, proofContext);
1845
- if (!options.dryRun) {
1846
- try {
1847
- await deps.persist(pool, run, durablePlan);
1848
- } catch (error) {
1849
- throw new PlanPersistenceIndeterminateError(
1850
- {
1851
- compareKind: compare.kind,
1852
- proveKind: prove.kind,
1853
- assessment: prove.assessment,
1854
- persisted: "indeterminate",
1855
- runId: run.runId,
1856
- planDigest: run.planDigest
1857
- },
1858
- error
1125
+ const run = createTransitionRunMetadata(durablePlan);
1126
+ const proofContext = prove.plan.observations.find(
1127
+ (observation) => observation.role === "evidence"
1128
+ )?.context;
1129
+ if (!proofContext) {
1130
+ throw new Error(
1131
+ "internal error: minted proven plan has no evidence observation context"
1859
1132
  );
1860
1133
  }
1134
+ const sql = planner.render(durablePlan, proofContext);
1135
+ if (!options.dryRun) {
1136
+ try {
1137
+ await deps.persist(pool, run, durablePlan);
1138
+ } catch (error) {
1139
+ throw new PlanPersistenceIndeterminateError(
1140
+ {
1141
+ compareKind: compare.kind,
1142
+ proveKind: prove.kind,
1143
+ assessment: prove.assessment,
1144
+ persisted: "indeterminate",
1145
+ runId: run.runId,
1146
+ planDigest: run.planDigest
1147
+ },
1148
+ error
1149
+ );
1150
+ }
1151
+ }
1152
+ result = {
1153
+ compareKind: compare.kind,
1154
+ proveKind: prove.kind,
1155
+ assessment: prove.assessment,
1156
+ persisted: !options.dryRun,
1157
+ runId: options.dryRun ? null : run.runId,
1158
+ planDigest: run.planDigest,
1159
+ plan: durablePlan,
1160
+ sql
1161
+ };
1861
1162
  }
1163
+ }
1164
+ if (prove.kind === "no-drift") {
1862
1165
  result = {
1863
1166
  compareKind: compare.kind,
1864
1167
  proveKind: prove.kind,
1865
1168
  assessment: prove.assessment,
1866
- persisted: !options.dryRun,
1867
- runId: options.dryRun ? null : run.runId,
1868
- planDigest: run.planDigest,
1869
- plan: durablePlan,
1870
- sql
1169
+ persisted: false,
1170
+ runId: null,
1171
+ planDigest: null
1172
+ };
1173
+ } else if (prove.kind !== "proven") {
1174
+ result = {
1175
+ compareKind: compare.kind,
1176
+ proveKind: prove.kind,
1177
+ assessment: prove.assessment,
1178
+ persisted: false,
1179
+ runId: null,
1180
+ planDigest: null
1871
1181
  };
1872
1182
  }
1873
1183
  }
1874
- if (prove.kind === "no-drift") {
1875
- result = {
1876
- compareKind: compare.kind,
1877
- proveKind: prove.kind,
1878
- assessment: prove.assessment,
1879
- persisted: false,
1880
- runId: null,
1881
- planDigest: null
1882
- };
1883
- } else if (prove.kind !== "proven") {
1884
- result = {
1885
- compareKind: compare.kind,
1886
- proveKind: prove.kind,
1887
- assessment: prove.assessment,
1888
- persisted: false,
1889
- runId: null,
1890
- planDigest: null
1891
- };
1892
- }
1893
1184
  } catch (error) {
1894
1185
  failed = true;
1895
1186
  operationError = error;
@@ -1901,6 +1192,20 @@ async function runPlan(options, overrides) {
1901
1192
  cleanup = { kind: "failed", error };
1902
1193
  }
1903
1194
  if (failed) {
1195
+ if (operationError instanceof PgTransitionRunPersistenceIndeterminateError) {
1196
+ throw new PlanPersistenceIndeterminateError(
1197
+ {
1198
+ compareKind: operationError.compare.kind,
1199
+ proveKind: "proven",
1200
+ assessment: operationError.assessment,
1201
+ persisted: "indeterminate",
1202
+ runId: operationError.run.runId,
1203
+ planDigest: operationError.run.planDigest
1204
+ },
1205
+ operationError.persistenceError,
1206
+ cleanup
1207
+ );
1208
+ }
1904
1209
  if (operationError instanceof PlanPersistenceIndeterminateError) {
1905
1210
  throw new PlanPersistenceIndeterminateError(
1906
1211
  operationError.result,
@@ -2059,13 +1364,79 @@ function formatPreAppendRefusalHuman(line, refusal) {
2059
1364
  `resolving command: ${escapeDiagnosticText2(refusal.refusal.resolvingCommand)}`
2060
1365
  ].join("\n");
2061
1366
  }
2062
-
2063
- // src/commands/apply.ts
2064
- function generatorRunHasPriorStepEvents(loaded) {
2065
- return loaded.events.some((event) => {
2066
- const executionId = event.event === "intent" && "executionId" in event.record ? event.record.executionId : void 0;
2067
- return !(typeof executionId === "string" && event.stepId === `dbsp.generator.attempt:${executionId}` && event.operationRef === "dbsp.generator.attempt");
2068
- });
1367
+
1368
+ // src/commands/apply.ts
1369
+ function generatorRunHasPriorStepEvents(loaded) {
1370
+ return loaded.events.some((event) => {
1371
+ const executionId = event.event === "intent" && "executionId" in event.record ? event.record.executionId : void 0;
1372
+ return !(typeof executionId === "string" && event.stepId === `dbsp.generator.attempt:${executionId}` && event.operationRef === "dbsp.generator.attempt");
1373
+ });
1374
+ }
1375
+ function ledgerHomeForGeneratorAddress(address) {
1376
+ if (address.scope === "database") return { scope: "database" };
1377
+ if (!address.schema)
1378
+ throw new Error(
1379
+ `schema-scoped generated address ${address.name} has no schema ledger`
1380
+ );
1381
+ return { scope: "schema", schema: address.schema };
1382
+ }
1383
+ function generatorPlanLedgerAddresses(steps) {
1384
+ const addresses = /* @__PURE__ */ new Map();
1385
+ for (const step of steps) {
1386
+ if (step.address !== void 0) {
1387
+ addresses.set(ledgerAddressKey(step.address), step.address);
1388
+ continue;
1389
+ }
1390
+ const closure = step.closure;
1391
+ if (!closure)
1392
+ throw new Error(`generated step ${step.stepKey} has no ledger address`);
1393
+ const candidates = [
1394
+ closure.root,
1395
+ ...closure.members.map((member) => member.address)
1396
+ ];
1397
+ for (const address of candidates)
1398
+ addresses.set(ledgerAddressKey(address), address);
1399
+ }
1400
+ return [...addresses.values()];
1401
+ }
1402
+ async function generatorRunHasLedgerEvidence(session, journal, steps) {
1403
+ const executionIds = executionIdsForRun(journal);
1404
+ const executionIdSet = new Set(executionIds);
1405
+ const addresses = generatorPlanLedgerAddresses(steps);
1406
+ const homes = /* @__PURE__ */ new Map();
1407
+ for (const address of addresses) {
1408
+ const home = ledgerHomeForGeneratorAddress(address);
1409
+ homes.set(`${home.scope}:${home.schema ?? ""}`, home);
1410
+ }
1411
+ for (const home of homes.values()) {
1412
+ for (const executionId of executionIds) {
1413
+ const reservations = await readPgLedgerReservationsForExecution(
1414
+ session,
1415
+ home,
1416
+ executionId
1417
+ );
1418
+ if (reservations.length > 0) return true;
1419
+ }
1420
+ }
1421
+ for (const address of addresses) {
1422
+ const chain = await readPgLedgerAddressChain(
1423
+ session,
1424
+ ledgerHomeForGeneratorAddress(address),
1425
+ address
1426
+ );
1427
+ if (chain.events.some((event) => executionIdSet.has(event.executionId ?? "")))
1428
+ return true;
1429
+ }
1430
+ return false;
1431
+ }
1432
+ async function generatorRunRequiresRecovery(target, journal, steps) {
1433
+ if (generatorRunHasPriorStepEvents(journal)) return true;
1434
+ const lease = await acquireExclusiveTransitionLease(target);
1435
+ try {
1436
+ return await generatorRunHasLedgerEvidence(lease.session, journal, steps);
1437
+ } finally {
1438
+ await lease.release();
1439
+ }
2069
1440
  }
2070
1441
  var GENERATOR_ATTEMPT_OPERATION = {
2071
1442
  ref: "dbsp.generator.attempt",
@@ -2252,8 +1623,8 @@ function validateAssumptionAcceptance(value, path = "acceptance") {
2252
1623
  }
2253
1624
  return candidate;
2254
1625
  }
2255
- function normaliseAcceptance(acceptance2) {
2256
- const validated = validateAssumptionAcceptance(acceptance2);
1626
+ function normaliseAcceptance(acceptance) {
1627
+ const validated = validateAssumptionAcceptance(acceptance);
2257
1628
  const withinScope = validated.withinScope ? [
2258
1629
  ...new Map(
2259
1630
  validated.withinScope.map((item) => [canonicalJson(item), item])
@@ -2281,7 +1652,7 @@ function authorizationDigest(runId, planDigest, policy, grants, actor, authorize
2281
1652
  const authorizedInstant = new Date(authorizedAt);
2282
1653
  if (Number.isNaN(authorizedInstant.getTime()))
2283
1654
  throw new Error("authorization timestamp must be a valid instant");
2284
- return createHash3("sha256").update(
1655
+ return createHash("sha256").update(
2285
1656
  canonicalJson({
2286
1657
  runId,
2287
1658
  planDigest,
@@ -2305,6 +1676,13 @@ function hasReusableAuthorization(authorizations, runId, planDigest, policy, gra
2305
1676
  )
2306
1677
  ) ?? false;
2307
1678
  }
1679
+ function acceptanceMatches(assumption, acceptance) {
1680
+ return acceptance.class === assumption.class && (!acceptance.fromTrustRoot || canonicalJson(acceptance.fromTrustRoot) === canonicalJson(assumption.asserter)) && (assumption.scope.length === 0 ? !acceptance.withinScope || acceptance.withinScope.length === 0 : !acceptance.withinScope || acceptance.withinScope.length === 0 || assumption.scope.every(
1681
+ (resource) => acceptance.withinScope?.some(
1682
+ (selector) => selectorMatchesResource(selector, resource)
1683
+ )
1684
+ ));
1685
+ }
2308
1686
  async function effectiveApplyPolicy(options) {
2309
1687
  let fromFile = [];
2310
1688
  if (options.acceptPolicy) {
@@ -2329,9 +1707,6 @@ async function effectiveApplyPolicy(options) {
2329
1707
  )
2330
1708
  };
2331
1709
  }
2332
- function registry() {
2333
- return createPackRegistry2([createPgTransitionPack2({})]);
2334
- }
2335
1710
  var APPLY_OUTCOME_CONTRACT = [
2336
1711
  ["completed", 0, "all planned steps completed"],
2337
1712
  ["operation-failed-not-applied", 10, "operation failed before application"],
@@ -2370,7 +1745,7 @@ var APPLY_OUTCOME_CONTRACT = [
2370
1745
  [
2371
1746
  "prior-step-events-refusal",
2372
1747
  19,
2373
- "attempted runs must be classified by recover"
1748
+ "attempted runs with prior generator step-attempt events must be classified by dbsp reconcile --db <database> <run-id>"
2374
1749
  ],
2375
1750
  [
2376
1751
  "compatibility-refusal",
@@ -2402,7 +1777,12 @@ var APPLY_OUTCOME_CONTRACT = [
2402
1777
  [
2403
1778
  "recovery-required",
2404
1779
  64,
2405
- "an admitted non-transactional claim remains open; run recover with the reported claim reference"
1780
+ "an admitted claim remains open; run dbsp reconcile --db <database> <run-id>"
1781
+ ],
1782
+ [
1783
+ "transport-ambiguous",
1784
+ 65,
1785
+ "an admitted operation has an ambiguous transport outcome; reconcile the run before retrying"
2406
1786
  ],
2407
1787
  ["database-read-only", 34, "target cannot accept managed writes"],
2408
1788
  [
@@ -2455,6 +1835,7 @@ function exitCodeForApplyOutcome(outcome) {
2455
1835
  return applyExitCodes.get(outcome);
2456
1836
  }
2457
1837
  function outcomeForApplyResult(result) {
1838
+ if (result.unresolvedOutcome) return result.unresolvedOutcome.kind;
2458
1839
  if (result.durableOutcome && isApplyExecutionOutcome(result.durableOutcome))
2459
1840
  return result.durableOutcome;
2460
1841
  if (result.assessment.lifecycle === "partially-applied")
@@ -2475,8 +1856,90 @@ function outcomeForApplyResult(result) {
2475
1856
  return "outcome-unknown";
2476
1857
  return "context-mismatch";
2477
1858
  }
1859
+ function generatorApplyCommandResult(execution, runId, refusal) {
1860
+ const fields = refusal === void 0 ? {} : { refusal };
1861
+ switch (execution.outcome) {
1862
+ case "completed":
1863
+ return {
1864
+ outcome: execution.outcome,
1865
+ runId,
1866
+ result: execution,
1867
+ ...fields
1868
+ };
1869
+ case "partially-applied":
1870
+ return {
1871
+ outcome: execution.outcome,
1872
+ runId,
1873
+ result: execution,
1874
+ ...fields
1875
+ };
1876
+ case "selection-incomplete":
1877
+ return {
1878
+ outcome: execution.outcome,
1879
+ runId,
1880
+ result: execution,
1881
+ ...fields
1882
+ };
1883
+ case "adoption-refused":
1884
+ return {
1885
+ outcome: execution.outcome,
1886
+ runId,
1887
+ result: execution,
1888
+ ...fields
1889
+ };
1890
+ case "readdress-unsupported":
1891
+ return {
1892
+ outcome: execution.outcome,
1893
+ runId,
1894
+ result: execution,
1895
+ ...fields
1896
+ };
1897
+ case "readdress-refused":
1898
+ return {
1899
+ outcome: execution.outcome,
1900
+ runId,
1901
+ result: execution,
1902
+ ...fields
1903
+ };
1904
+ case "destructive-authority-refused":
1905
+ return {
1906
+ outcome: execution.outcome,
1907
+ runId,
1908
+ result: execution,
1909
+ ...fields
1910
+ };
1911
+ case "prior-step-events-refusal":
1912
+ return {
1913
+ outcome: execution.outcome,
1914
+ runId,
1915
+ result: execution,
1916
+ ...fields
1917
+ };
1918
+ case "recovery-required":
1919
+ return {
1920
+ outcome: execution.outcome,
1921
+ runId,
1922
+ result: execution,
1923
+ ...fields
1924
+ };
1925
+ case "transport-ambiguous":
1926
+ return {
1927
+ outcome: execution.outcome,
1928
+ runId,
1929
+ result: execution,
1930
+ ...fields
1931
+ };
1932
+ case "execution-failed":
1933
+ return {
1934
+ outcome: execution.outcome,
1935
+ runId,
1936
+ result: execution,
1937
+ ...fields
1938
+ };
1939
+ }
1940
+ }
2478
1941
  function formatApplyHuman(result) {
2479
- const line = `${result.outcome}: ${result.runId}`;
1942
+ const line = `${escapeDiagnosticText3(result.outcome)}: ${escapeDiagnosticText3(result.runId)}`;
2480
1943
  if (result.refusal) {
2481
1944
  if ("address" in result.refusal)
2482
1945
  return formatPreAppendRefusalHuman(line, result.refusal);
@@ -2488,17 +1951,59 @@ function formatApplyHuman(result) {
2488
1951
  `resolving command: ${escapeDiagnosticText3(result.refusal.resolvingCommand)}`
2489
1952
  ].join("\n");
2490
1953
  }
2491
- if (result.outcome !== "plan-digest-mismatch" || !result.result) return line;
2492
- const detail = result.result.assessment.reasons[0]?.detail;
2493
- return detail ? `${line}
2494
- ${detail}` : line;
2495
- }
2496
- function acceptanceMatches(assumption, acceptance2) {
2497
- return acceptance2.class === assumption.class && (!acceptance2.fromTrustRoot || canonicalJson(acceptance2.fromTrustRoot) === canonicalJson(assumption.asserter)) && (assumption.scope.length === 0 ? !acceptance2.withinScope || acceptance2.withinScope.length === 0 : !acceptance2.withinScope || acceptance2.withinScope.length === 0 || assumption.scope.every(
2498
- (resource) => acceptance2.withinScope?.some(
2499
- (selector) => selectorMatchesResource(selector, resource)
2500
- )
2501
- ));
1954
+ if (!("result" in result)) return line;
1955
+ if ("assessment" in result.result) {
1956
+ if (result.result.unresolvedOutcome) {
1957
+ const unresolved = result.result.unresolvedOutcome;
1958
+ return unresolved.kind === "recovery-required" ? [
1959
+ line,
1960
+ `claim: ${escapeDiagnosticText3(unresolved.claimId)}`,
1961
+ `detail: ${escapeDiagnosticText3(unresolved.detail)}`,
1962
+ `resolving command: dbsp reconcile --db <database> ${escapeDiagnosticText3(result.runId)}`
1963
+ ].join("\n") : [
1964
+ line,
1965
+ `detail: ${escapeDiagnosticText3(unresolved.detail)}`,
1966
+ `resolving command: dbsp reconcile --db <database> ${escapeDiagnosticText3(result.runId)}`
1967
+ ].join("\n");
1968
+ }
1969
+ if (result.outcome !== "plan-digest-mismatch") return line;
1970
+ const detail = result.result.assessment.reasons[0]?.detail;
1971
+ return detail ? `${line}
1972
+ ${escapeDiagnosticText3(detail)}` : line;
1973
+ }
1974
+ const execution = result.result;
1975
+ switch (execution.outcome) {
1976
+ case "completed":
1977
+ return line;
1978
+ case "recovery-required":
1979
+ return [
1980
+ line,
1981
+ `claim: ${escapeDiagnosticText3(execution.claimId)}`,
1982
+ `detail: ${escapeDiagnosticText3(execution.detail)}`,
1983
+ `resolving command: dbsp reconcile --db <database> ${escapeDiagnosticText3(result.runId)}`
1984
+ ].join("\n");
1985
+ case "transport-ambiguous":
1986
+ return [
1987
+ line,
1988
+ `detail: ${escapeDiagnosticText3(execution.detail)}`,
1989
+ `resolving command: dbsp reconcile --db <database> ${escapeDiagnosticText3(result.runId)}`
1990
+ ].join("\n");
1991
+ case "partially-applied":
1992
+ case "selection-incomplete":
1993
+ case "adoption-refused":
1994
+ case "readdress-unsupported":
1995
+ case "readdress-refused":
1996
+ case "destructive-authority-refused":
1997
+ case "execution-failed":
1998
+ return `${line}
1999
+ detail: ${escapeDiagnosticText3(execution.detail)}`;
2000
+ case "prior-step-events-refusal":
2001
+ return [
2002
+ line,
2003
+ `detail: ${escapeDiagnosticText3(execution.detail)}`,
2004
+ `resolving command: dbsp reconcile --db <database> ${escapeDiagnosticText3(result.runId)}`
2005
+ ].join("\n");
2006
+ }
2502
2007
  }
2503
2008
  function recordedPlanRefusal() {
2504
2009
  return { ...REFUSAL_VOCABULARY2["ERR-10"], state: "recorded-plan" };
@@ -2701,7 +2206,15 @@ async function runApplyInternal(runId, options, pool) {
2701
2206
  throw new Error(
2702
2207
  "persisted run no longer contains a generator plan"
2703
2208
  );
2704
- const recordedSchema = current.plan.generator.planningSchema;
2209
+ let material;
2210
+ try {
2211
+ material = decodeGeneratorPlanMaterial(current.plan.generator);
2212
+ } catch (error) {
2213
+ throw new RecordedPlanDigestMismatchError(
2214
+ `persisted generator material is invalid: ${errorDetail(error)}`
2215
+ );
2216
+ }
2217
+ const recordedSchema = material.planningSchema;
2705
2218
  if (!recordedSchema)
2706
2219
  throw new Error(
2707
2220
  "persisted generator run has no recorded planning schema and is non-resumable"
@@ -2716,7 +2229,7 @@ async function runApplyInternal(runId, options, pool) {
2716
2229
  }
2717
2230
  if (actualDigest !== current.run.planDigest || actualDigest !== expectedPlanDigest)
2718
2231
  throw new RecordedPlanDigestMismatchError();
2719
- const manifest = validateNormalizedManagedStepManifest3(
2232
+ const manifest = validateNormalizedManagedStepManifest2(
2720
2233
  current.plan.steps
2721
2234
  );
2722
2235
  if (!manifest.ok)
@@ -2730,10 +2243,14 @@ async function runApplyInternal(runId, options, pool) {
2730
2243
  throw new RecordedPlanDigestMismatchError(
2731
2244
  `persisted generator manifest is invalid: ${lifecycleError}`
2732
2245
  );
2733
- if (generatorRunHasPriorStepEvents(current))
2246
+ if (await generatorRunRequiresRecovery(
2247
+ target,
2248
+ current,
2249
+ manifest.manifest.steps
2250
+ ))
2734
2251
  return {
2735
2252
  outcome: "prior-step-events-refusal",
2736
- detail: "run has prior generator step-attempt events; run dbsp recover instead"
2253
+ detail: "run has prior generator step-attempt events; run dbsp reconcile --db <database> <run-id>"
2737
2254
  };
2738
2255
  return executeGeneratorPlan({
2739
2256
  pool: owned,
@@ -2760,6 +2277,18 @@ async function runApplyInternal(runId, options, pool) {
2760
2277
  await journalLease.release();
2761
2278
  }
2762
2279
  },
2280
+ verifyDeclaredAdoptionShape: async (executor, step) => {
2281
+ if (step.lifecycle?.kind !== "adoption") return false;
2282
+ const compared = await comparePgsqlDeclaredAdoptionSchema2({
2283
+ // Outcome protocol provides its claimed pg session here. It is
2284
+ // intentionally not the outer pool held by runApply.
2285
+ executor,
2286
+ model: modelForDeclaredAdoption2(step.lifecycle.shape),
2287
+ schema: recordedSchema,
2288
+ dbCasing: material.dbCasing
2289
+ });
2290
+ return compared.changes.length === 0;
2291
+ },
2763
2292
  ...options.replace === void 0 ? {} : { replaces: options.replace },
2764
2293
  runId
2765
2294
  });
@@ -2780,96 +2309,68 @@ async function runApplyInternal(runId, options, pool) {
2780
2309
  withheldAuthority
2781
2310
  }
2782
2311
  };
2783
- result = {
2784
- outcome: execution.outcome,
2312
+ result = generatorApplyCommandResult(
2313
+ execution,
2785
2314
  runId,
2786
- result: execution,
2787
- ...preAppendRefusal === void 0 ? {} : { refusal: preAppendRefusal }
2788
- };
2315
+ preAppendRefusal
2316
+ );
2789
2317
  }
2790
2318
  } else {
2791
- const locked = await withPgTransitionRunLock(
2319
+ const applied = await applyPgTransitionRun(
2792
2320
  owned,
2793
2321
  runId,
2794
- async (target) => {
2795
- const loadCurrent = async (id) => {
2796
- return loadOnTarget(target, id);
2797
- };
2798
- const applier = createApplier(registry(), {
2799
- // A durable apply only verifies the existing immutable row. The applier
2800
- // calls this before execution; a changed record remains a refusal.
2801
- persist: async () => void 0
2802
- });
2803
- return applier.applyDurable({
2804
- runId,
2805
- expectedPlanDigest,
2806
- loadCurrent,
2807
- prepareExecutionSession: async (session, contract, plan) => {
2808
- const currency = await validatePgManagedLedgerCurrency(
2809
- session,
2810
- plan
2811
- );
2812
- if (currency)
2813
- return {
2814
- ok: false,
2815
- kind: "refused",
2816
- detail: currency
2817
- };
2818
- return preparePgExecutionSession(session, contract, plan);
2819
- },
2820
- policy,
2821
- target,
2822
- authorize: async (run, plan, session) => {
2823
- const current = await loadOnTarget(target, run.runId);
2824
- const grants = plan.assumptions.map((assumption) => ({
2825
- assumptionId: assumption.id,
2826
- grant: policy.accepts.findIndex(
2827
- (grant) => acceptanceMatches(assumption, grant)
2828
- )
2829
- }));
2830
- if (hasReusableAuthorization(
2831
- current.authorizations,
2322
+ policy,
2323
+ expectedPlanDigest,
2324
+ {
2325
+ authorize: async ({ current, run, plan }) => {
2326
+ const grants = plan.assumptions.map((assumption) => ({
2327
+ assumptionId: assumption.id,
2328
+ grant: policy.accepts.findIndex(
2329
+ (grant) => acceptanceMatches(assumption, grant)
2330
+ )
2331
+ }));
2332
+ const reusable = current.authorizations?.find(
2333
+ (authorization) => hasReusableAuthorization(
2334
+ [authorization],
2832
2335
  run.runId,
2833
2336
  transitionPlanDigest2(plan),
2834
2337
  policy.accepts,
2835
2338
  grants
2836
- ))
2837
- return;
2838
- const actor = process.env.USER ?? process.env.LOGNAME ?? "unknown-local-actor";
2839
- const authorizedAt = (/* @__PURE__ */ new Date()).toISOString();
2840
- const digest2 = authorizationDigest(
2339
+ )
2340
+ );
2341
+ if (reusable) return reusable;
2342
+ const actor = process.env.USER ?? process.env.LOGNAME ?? "unknown-local-actor";
2343
+ const authorizedAt = (/* @__PURE__ */ new Date()).toISOString();
2344
+ return {
2345
+ runId: run.runId,
2346
+ policy: policy.accepts,
2347
+ grants,
2348
+ digest: authorizationDigest(
2841
2349
  run.runId,
2842
2350
  transitionPlanDigest2(plan),
2843
2351
  policy.accepts,
2844
2352
  grants,
2845
2353
  actor,
2846
2354
  authorizedAt
2847
- );
2848
- const record2 = {
2849
- runId: run.runId,
2850
- policy: policy.accepts,
2851
- grants,
2852
- digest: digest2,
2853
- actor,
2854
- authorizedAt
2855
- };
2856
- await appendTransitionAuthorization(session, record2);
2857
- }
2858
- });
2355
+ ),
2356
+ actor,
2357
+ authorizedAt
2358
+ };
2359
+ }
2859
2360
  }
2860
2361
  );
2861
- if (locked.kind === "busy") result = { outcome: "run-busy", runId };
2362
+ if (applied.kind === "busy") result = { outcome: "run-busy", runId };
2862
2363
  else {
2863
- const outcome = outcomeForApplyResult(locked.value);
2364
+ const outcome = outcomeForApplyResult(applied.result);
2864
2365
  const preAppendRefusal = applyPreAppendRefusal(
2865
2366
  outcome,
2866
2367
  persisted.plan,
2867
- locked.value
2368
+ applied.result
2868
2369
  );
2869
2370
  result = {
2870
2371
  outcome,
2871
2372
  runId,
2872
- result: locked.value,
2373
+ result: applied.result,
2873
2374
  ...preAppendRefusal === void 0 ? {} : { refusal: preAppendRefusal }
2874
2375
  };
2875
2376
  }
@@ -2965,9 +2466,7 @@ var applyCommand = new Command2("apply").description(
2965
2466
  console.log("confirmation-declined: persisted run was not executed");
2966
2467
  else if (result2.outcome === "not-executable") {
2967
2468
  } else if (result2.outcome !== "dry-run" && "result" in result2)
2968
- console.log(
2969
- "runId" in result2.result ? formatApplyHuman(result2.result) : `${result2.result.outcome}${"detail" in result2.result ? `: ${result2.result.detail}` : ""}`
2970
- );
2469
+ console.log(formatApplyHuman(result2.result));
2971
2470
  }
2972
2471
  process.exitCode = exitCode2;
2973
2472
  return;
@@ -3066,13 +2565,19 @@ var generateCommand = new Command3("generate").description("Generate code from s
3066
2565
  case "ddl": {
3067
2566
  const dbCasing = mapCasingToDbCasing(options.casing) ?? schema.dbCasing ?? "snake_case";
3068
2567
  const casingLabel = formatGenerateCasingLabel(dbCasing);
3069
- const { createPgsqlCompileOnlyAdapter } = await import("@dbsp/adapter-pgsql");
2568
+ const { createPgPhysicalModel: createPgPhysicalModel6, createPgsqlCompileOnlyAdapter } = await import("@dbsp/adapter-pgsql");
3070
2569
  const adapter = createPgsqlCompileOnlyAdapter({
3071
2570
  dbCasing,
3072
2571
  ...options.schemaName ? { schemaName: options.schemaName } : {}
3073
2572
  });
3074
2573
  {
3075
- const ddlStatements = adapter.generateDDL(schema.model, {
2574
+ const physical = createPgPhysicalModel6({
2575
+ mode: "logical",
2576
+ model: schema.model,
2577
+ schema: options.schemaName ?? "public",
2578
+ dbCasing
2579
+ });
2580
+ const ddlStatements = adapter.generateDDL(physical, {
3076
2581
  ...options.drop !== void 0 && {
3077
2582
  includeDropStatements: options.drop
3078
2583
  },
@@ -3137,16 +2642,16 @@ function formatGenerateCasingLabel(dbCasing) {
3137
2642
  }
3138
2643
 
3139
2644
  // src/commands/inspect.ts
3140
- import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
2645
+ import { isDeepStrictEqual } from "util";
3141
2646
  import {
3142
2647
  createPgTransitionLessor as createPgTransitionLessor4,
3143
2648
  DBSP_LEDGER_EVENT_TABLE,
3144
2649
  escapeDiagnosticText as escapeDiagnosticText4,
3145
- readPgCatalogueIdentity as readPgCatalogueIdentity3,
2650
+ readPgCatalogueIdentity as readPgCatalogueIdentity2,
3146
2651
  readPgLedgerAddressChain as readPgLedgerAddressChain2,
3147
2652
  readPgLedgerMarker
3148
2653
  } from "@dbsp/adapter-pgsql";
3149
- import { acquireTransitionLease as acquireTransitionLease4, projectLedgerChain as projectLedgerChain2 } from "@dbsp/core";
2654
+ import { acquireTransitionLease as acquireTransitionLease4, projectLedgerChain } from "@dbsp/core";
3150
2655
  import {
3151
2656
  canonicalResourceParent
3152
2657
  } from "@dbsp/types";
@@ -3192,7 +2697,7 @@ function inspectLiveDrift(projection, live) {
3192
2697
  kind: "catalogue-drift",
3193
2698
  detail: "ledger records a present catalogue identity but the object is absent"
3194
2699
  };
3195
- return isDeepStrictEqual2(terminal.catalogueIdentity, live.catalogueIdentity) ? { kind: "matches-ledger" } : {
2700
+ return isDeepStrictEqual(terminal.catalogueIdentity, live.catalogueIdentity) ? { kind: "matches-ledger" } : {
3196
2701
  kind: "catalogue-drift",
3197
2702
  detail: "live catalogue identity differs from the ledger terminal"
3198
2703
  };
@@ -3315,7 +2820,7 @@ async function runInspect(selector, options) {
3315
2820
  );
3316
2821
  let projection;
3317
2822
  try {
3318
- projection = projectLedgerChain2(
2823
+ projection = projectLedgerChain(
3319
2824
  await readPgLedgerAddressChain2(lease.session, ledger, address)
3320
2825
  );
3321
2826
  } catch (error) {
@@ -3335,7 +2840,7 @@ async function runInspect(selector, options) {
3335
2840
  };
3336
2841
  }
3337
2842
  try {
3338
- const live = await readPgCatalogueIdentity3(lease.session, address);
2843
+ const live = await readPgCatalogueIdentity2(lease.session, address);
3339
2844
  const refusal = inspectRefusal(projection);
3340
2845
  const openReaddress = projection?.kind === "projected-ledger-chain" && projection.openClaim?.kind === "readdress-intent" && projection.openClaim.event.pairId ? {
3341
2846
  pairId: projection.openClaim.event.pairId,
@@ -3416,83 +2921,423 @@ function reportWarnings(warnings) {
3416
2921
  console.log(` \u26A0\uFE0F ${warning}`);
3417
2922
  }
3418
2923
  }
3419
- var introspectCommand = new Command5("introspect").description("Generate schema.ts from database introspection").requiredOption("-d, --db <url>", "Database connection URL (required)").option("-o, --out <file>", "Output schema file", "./dbsp.schema.ts").option("--schema-name <name>", "Database schema name", "public").option(
3420
- "--exclude <patterns>",
3421
- "Tables to exclude (comma-separated glob patterns)",
3422
- "_migrations,_prisma*,pg_*"
3423
- ).option("--include <patterns>", "Tables to include (comma-separated)").option("--no-db-type-comments", "Omit original DB type comments").option(
3424
- "--db-casing <casing>",
3425
- "Database column casing (snake_case \u2192 camelCase in generated code)",
3426
- "snake_case"
3427
- ).action(
3428
- async (options) => {
3429
- const redactedUrl = redactDbUrl(options.db);
3430
- console.log(`\u{1F50D} Introspecting database: ${redactedUrl}`);
3431
- console.log(` Schema: ${options.schemaName}`);
3432
- if (options.exclude) {
3433
- console.log(` Excluding: ${options.exclude}`);
3434
- }
3435
- console.log("");
3436
- try {
3437
- const { pool } = await createDbConnection(options.db);
2924
+ var introspectCommand = new Command5("introspect").description("Generate schema.ts from database introspection").requiredOption("-d, --db <url>", "Database connection URL (required)").option("-o, --out <file>", "Output schema file", "./dbsp.schema.ts").option("--schema-name <name>", "Database schema name", "public").option(
2925
+ "--exclude <patterns>",
2926
+ "Tables to exclude (comma-separated glob patterns)",
2927
+ "_migrations,_prisma*,pg_*"
2928
+ ).option("--include <patterns>", "Tables to include (comma-separated)").option("--no-db-type-comments", "Omit original DB type comments").option(
2929
+ "--db-casing <casing>",
2930
+ "Database column casing (snake_case \u2192 camelCase in generated code)",
2931
+ "snake_case"
2932
+ ).action(
2933
+ async (options) => {
2934
+ const redactedUrl = redactDbUrl(options.db);
2935
+ console.log(`\u{1F50D} Introspecting database: ${redactedUrl}`);
2936
+ console.log(` Schema: ${options.schemaName}`);
2937
+ if (options.exclude) {
2938
+ console.log(` Excluding: ${options.exclude}`);
2939
+ }
2940
+ console.log("");
2941
+ try {
2942
+ const { pool } = await createDbConnection(options.db);
2943
+ try {
2944
+ const { introspect } = await import("@dbsp/adapter-pgsql");
2945
+ const excludePatterns = options.exclude ? options.exclude.split(",").map((s) => s.trim()) : void 0;
2946
+ const includePatterns = options.include ? options.include.split(",").map((s) => s.trim()) : void 0;
2947
+ const model = await introspect(pool, {
2948
+ schema: options.schemaName,
2949
+ ...excludePatterns ? { exclude: excludePatterns } : {},
2950
+ ...includePatterns ? { include: includePatterns } : {}
2951
+ });
2952
+ const tableCount = model.tables.size;
2953
+ const relationCount = model.relations.size;
2954
+ const hierarchyCount = model.hierarchies?.length ?? 0;
2955
+ console.log(
2956
+ `\u{1F4CA} Found ${tableCount} tables, ${relationCount} relations, ${hierarchyCount} hierarchies`
2957
+ );
2958
+ const codegenOptions = {
2959
+ sourceUrl: options.db,
2960
+ includeDbTypeComments: options.dbTypeComments,
2961
+ introspectedAt: model.introspectedAt,
2962
+ dbCasing: options.dbCasing
2963
+ };
2964
+ const generated = generateSchemaFileWithDiagnostics(
2965
+ model,
2966
+ codegenOptions
2967
+ );
2968
+ const warnings = uniqueWarnings(generated.warnings);
2969
+ if (warnings.length > 0) {
2970
+ reportWarnings(warnings);
2971
+ }
2972
+ console.log("");
2973
+ const outPath = resolve2(process.cwd(), options.out);
2974
+ mkdirSync2(dirname2(outPath), { recursive: true });
2975
+ writeFileSync2(outPath, generated.code, "utf-8");
2976
+ console.log(`\u2705 Generated schema: ${outPath}`);
2977
+ console.log(` Tables: ${tableCount}`);
2978
+ console.log(` Relations: ${relationCount}`);
2979
+ } finally {
2980
+ await pool.end();
2981
+ }
2982
+ } catch (error) {
2983
+ const message = error instanceof Error ? error.message : String(error);
2984
+ console.error(`\u274C ${message}`);
2985
+ process.exit(1);
2986
+ }
2987
+ }
2988
+ );
2989
+
2990
+ // src/commands/migrate.ts
2991
+ import {
2992
+ convergePg,
2993
+ createPgPhysicalModel as createPgPhysicalModel3,
2994
+ escapeDiagnosticText as escapeDiagnosticText5,
2995
+ PgConvergeRefusalError
2996
+ } from "@dbsp/adapter-pgsql";
2997
+ import { Command as Command6 } from "commander";
2998
+ var MIGRATE_OUTCOME_CONTRACT = [
2999
+ ["no-drift", 0, "database matches declaration"],
3000
+ ["applied", 0, "admitted additions or adoptions applied"],
3001
+ ["partially-applied", 11, "some target effects may be durable"],
3002
+ ["busy", 21, "ledger lock or claims are busy; retry later"],
3003
+ ["database-read-only", 34, "target cannot accept managed writes"],
3004
+ ["load-failed", 27, "schema file could not be loaded"],
3005
+ ["migrate-failed", 29, "migration, connection, or planning failure"],
3006
+ ["adoption-refused", 62, "declared adoption no longer matches live object"],
3007
+ [
3008
+ "recovery-required",
3009
+ 64,
3010
+ "admitted claim remains open; reconcile before retrying"
3011
+ ],
3012
+ [
3013
+ "transport-ambiguous",
3014
+ 65,
3015
+ "admitted operation has ambiguous transport outcome"
3016
+ ],
3017
+ [
3018
+ "invalid-options",
3019
+ 70,
3020
+ "an --external-index or --format value is malformed, or convergePg refused its options"
3021
+ ],
3022
+ ["ledger-absent", 71, "convergence ledger is not initialized"],
3023
+ ["incompatible-ledger", 72, "ledger version is unsupported"],
3024
+ ["unsupported-server", 73, "PostgreSQL server is unsupported"],
3025
+ ["unsupported-change", 74, "requested schema change is not admitted"],
3026
+ ["unmanaged-object", 75, "live object is not ledger-managed"],
3027
+ ["unmanaged-parent", 76, "parent live object is not ledger-managed"],
3028
+ ["concurrent-drift", 77, "live schema changed during admission"],
3029
+ ["execution-refused", 78, "admitted execution step was refused"],
3030
+ ["cleanup-failed", 79, "pool failed to close after convergence"]
3031
+ ];
3032
+ var migrateExitCodes = new Map(
3033
+ MIGRATE_OUTCOME_CONTRACT.map(([outcome, exitCode]) => [outcome, exitCode])
3034
+ );
3035
+ function exitCodeForMigrateOutcome(outcome) {
3036
+ return migrateExitCodes.get(outcome);
3037
+ }
3038
+ var refusalOutcomes = {
3039
+ "invalid-options": "invalid-options",
3040
+ "unsupported-change": "unsupported-change",
3041
+ "unmanaged-object": "unmanaged-object",
3042
+ "unmanaged-parent": "unmanaged-parent",
3043
+ "concurrent-drift": "concurrent-drift",
3044
+ "ledger-absent": "ledger-absent",
3045
+ "incompatible-ledger": "incompatible-ledger",
3046
+ "unsupported-server": "unsupported-server",
3047
+ busy: "busy",
3048
+ "recovery-required": "recovery-required",
3049
+ "database-read-only": "database-read-only",
3050
+ "execution-refused": "execution-refused",
3051
+ "adoption-refused": "adoption-refused",
3052
+ // dbsp migrate never supplies application steps, but preserve the closed converge map.
3053
+ "application-step-changed": "migrate-failed",
3054
+ "application-step-failed": "migrate-failed",
3055
+ // dbsp migrate never supplies initialize, but preserve the closed converge map.
3056
+ "initialization-refused": "migrate-failed"
3057
+ };
3058
+ var defaultMigrateDeps = {
3059
+ loadSchema,
3060
+ createDbConnection,
3061
+ converge: convergePg
3062
+ };
3063
+ function resolvedDeps2(overrides) {
3064
+ return { ...defaultMigrateDeps, ...overrides };
3065
+ }
3066
+ function describeThrown2(error) {
3067
+ return error instanceof Error ? error.message : String(error);
3068
+ }
3069
+ function parseExternalIndexes(entries) {
3070
+ if (entries === void 0 || entries.length === 0) return void 0;
3071
+ return entries.map((entry) => {
3072
+ const separator = entry.indexOf(":");
3073
+ if (separator <= 0 || separator === entry.length - 1)
3074
+ throw new Error(
3075
+ `invalid --external-index ${entry}; expected <table>:<index>`
3076
+ );
3077
+ return {
3078
+ table: entry.slice(0, separator),
3079
+ name: entry.slice(separator + 1)
3080
+ };
3081
+ });
3082
+ }
3083
+ function resultForConverge(result, schema, schemaFile) {
3084
+ switch (result.kind) {
3085
+ case "no-drift":
3086
+ case "applied":
3087
+ return {
3088
+ outcome: result.kind,
3089
+ exitCode: exitCodeForMigrateOutcome(result.kind),
3090
+ schema,
3091
+ schemaFile,
3092
+ applied: result.applied
3093
+ };
3094
+ case "partially-applied":
3095
+ return {
3096
+ outcome: result.kind,
3097
+ exitCode: exitCodeForMigrateOutcome(result.kind),
3098
+ schema,
3099
+ schemaFile,
3100
+ completedStepKeys: result.completedStepKeys,
3101
+ notStartedStepKeys: result.notStartedStepKeys,
3102
+ detail: result.detail
3103
+ };
3104
+ case "transport-ambiguous":
3105
+ return {
3106
+ outcome: result.kind,
3107
+ exitCode: exitCodeForMigrateOutcome(result.kind),
3108
+ schema,
3109
+ schemaFile,
3110
+ detail: result.detail
3111
+ };
3112
+ }
3113
+ const unhandled = result;
3114
+ return unhandled;
3115
+ }
3116
+ function refusalResult(error, schema, schemaFile) {
3117
+ const outcome = refusalOutcomes[error.refusal];
3118
+ return {
3119
+ outcome,
3120
+ exitCode: exitCodeForMigrateOutcome(outcome),
3121
+ schema,
3122
+ schemaFile,
3123
+ changes: error.changes,
3124
+ ...error.detail === void 0 ? {} : { detail: error.detail },
3125
+ ...error.runIds === void 0 ? {} : { runIds: error.runIds },
3126
+ ...error.busyRunIds === void 0 ? {} : { busyRunIds: error.busyRunIds },
3127
+ ...error.executionIds === void 0 ? {} : { executionIds: error.executionIds }
3128
+ };
3129
+ }
3130
+ function failureResult(outcome, error, schema, schemaFile) {
3131
+ return {
3132
+ outcome,
3133
+ exitCode: exitCodeForMigrateOutcome(outcome),
3134
+ schema,
3135
+ schemaFile,
3136
+ error: describeThrown2(error)
3137
+ };
3138
+ }
3139
+ async function runMigrate(schemaPath, options, overrides) {
3140
+ const schema = options.schema ?? "public";
3141
+ const deps = resolvedDeps2(overrides);
3142
+ if (options.format !== void 0 && options.format !== "text" && options.format !== "json")
3143
+ return failureResult(
3144
+ "invalid-options",
3145
+ new Error(
3146
+ `unsupported migrate format ${options.format}; expected text or json`
3147
+ ),
3148
+ schema,
3149
+ schemaPath
3150
+ );
3151
+ let externalIndexes;
3152
+ try {
3153
+ externalIndexes = parseExternalIndexes(options.externalIndex);
3154
+ } catch (error) {
3155
+ return failureResult("invalid-options", error, schema, schemaPath);
3156
+ }
3157
+ let loaded;
3158
+ try {
3159
+ loaded = await deps.loadSchema(schemaPath);
3160
+ } catch (error) {
3161
+ return failureResult("load-failed", error, schema, schemaPath);
3162
+ }
3163
+ let physical;
3164
+ try {
3165
+ physical = createPgPhysicalModel3({
3166
+ mode: "logical",
3167
+ model: loaded.model,
3168
+ schema,
3169
+ ...loaded.dbCasing === void 0 ? {} : { dbCasing: loaded.dbCasing }
3170
+ });
3171
+ } catch (error) {
3172
+ return failureResult("migrate-failed", error, schema, schemaPath);
3173
+ }
3174
+ let pool;
3175
+ try {
3176
+ ({ pool } = await deps.createDbConnection(options.db));
3177
+ } catch (error) {
3178
+ return failureResult("migrate-failed", error, schema, schemaPath);
3179
+ }
3180
+ let result;
3181
+ let convergeResult;
3182
+ try {
3183
+ convergeResult = await deps.converge(pool, physical, {
3184
+ ...externalIndexes === void 0 ? {} : { externalIndexes }
3185
+ });
3186
+ result = resultForConverge(convergeResult, schema, schemaPath);
3187
+ } catch (error) {
3188
+ result = error instanceof PgConvergeRefusalError ? refusalResult(error, schema, schemaPath) : failureResult("migrate-failed", error, schema, schemaPath);
3189
+ }
3190
+ try {
3191
+ await pool.end();
3192
+ } catch (error) {
3193
+ const cleanupError = describeThrown2(error);
3194
+ if (result.exitCode === 0 && convergeResult !== void 0) {
3195
+ return {
3196
+ outcome: "cleanup-failed",
3197
+ exitCode: exitCodeForMigrateOutcome("cleanup-failed"),
3198
+ schema,
3199
+ schemaFile: schemaPath,
3200
+ cleanupError,
3201
+ result: convergeResult
3202
+ };
3203
+ }
3204
+ return { ...result, cleanupError };
3205
+ }
3206
+ return result;
3207
+ }
3208
+ function redactDiagnostic(value, db) {
3209
+ const sensitive = /* @__PURE__ */ new Set([db]);
3210
+ try {
3211
+ const url = new URL(db);
3212
+ const password = url.password;
3213
+ if (password) {
3214
+ sensitive.add(password);
3438
3215
  try {
3439
- const { introspect } = await import("@dbsp/adapter-pgsql");
3440
- const excludePatterns = options.exclude ? options.exclude.split(",").map((s) => s.trim()) : void 0;
3441
- const includePatterns = options.include ? options.include.split(",").map((s) => s.trim()) : void 0;
3442
- const model = await introspect(pool, {
3443
- schema: options.schemaName,
3444
- ...excludePatterns ? { exclude: excludePatterns } : {},
3445
- ...includePatterns ? { include: includePatterns } : {}
3446
- });
3447
- const tableCount = model.tables.size;
3448
- const relationCount = model.relations.size;
3449
- const hierarchyCount = model.hierarchies?.length ?? 0;
3450
- console.log(
3451
- `\u{1F4CA} Found ${tableCount} tables, ${relationCount} relations, ${hierarchyCount} hierarchies`
3452
- );
3453
- const codegenOptions = {
3454
- sourceUrl: options.db,
3455
- includeDbTypeComments: options.dbTypeComments,
3456
- introspectedAt: model.introspectedAt,
3457
- dbCasing: options.dbCasing
3458
- };
3459
- const generated = generateSchemaFileWithDiagnostics(
3460
- model,
3461
- codegenOptions
3462
- );
3463
- const warnings = uniqueWarnings(generated.warnings);
3464
- if (warnings.length > 0) {
3465
- reportWarnings(warnings);
3466
- }
3467
- console.log("");
3468
- const outPath = resolve2(process.cwd(), options.out);
3469
- mkdirSync2(dirname2(outPath), { recursive: true });
3470
- writeFileSync2(outPath, generated.code, "utf-8");
3471
- console.log(`\u2705 Generated schema: ${outPath}`);
3472
- console.log(` Tables: ${tableCount}`);
3473
- console.log(` Relations: ${relationCount}`);
3474
- } finally {
3475
- await pool.end();
3216
+ sensitive.add(decodeURIComponent(password));
3217
+ } catch {
3476
3218
  }
3477
- } catch (error) {
3478
- const message = error instanceof Error ? error.message : String(error);
3479
- console.error(`\u274C ${message}`);
3480
- process.exit(1);
3481
3219
  }
3220
+ for (const password2 of url.searchParams.getAll("password"))
3221
+ sensitive.add(password2);
3222
+ for (const pair of url.search.slice(1).split("&")) {
3223
+ const separator = pair.indexOf("=");
3224
+ const name = separator === -1 ? pair : pair.slice(0, separator);
3225
+ if (name !== "password") continue;
3226
+ if (separator !== -1) sensitive.add(pair.slice(separator + 1));
3227
+ }
3228
+ } catch {
3229
+ const match = /:\/\/[^/:]+:([^@]+)@/.exec(db);
3230
+ if (match?.[1]) sensitive.add(match[1]);
3231
+ }
3232
+ return [...sensitive].filter((entry) => entry.length > 0).sort((left, right) => right.length - left.length).reduce((text, entry) => text.replaceAll(entry, "<redacted>"), value);
3233
+ }
3234
+ function redactedConvergeResult(result, db) {
3235
+ return result.kind === "partially-applied" || result.kind === "transport-ambiguous" ? { ...result, detail: redactDiagnostic(result.detail, db) } : result;
3236
+ }
3237
+ function redactedResult(result, db) {
3238
+ return {
3239
+ ...result,
3240
+ ...result.detail === void 0 ? {} : { detail: redactDiagnostic(result.detail, db) },
3241
+ ...result.error === void 0 ? {} : { error: redactDiagnostic(result.error, db) },
3242
+ ...result.cleanupError === void 0 ? {} : { cleanupError: redactDiagnostic(result.cleanupError, db) },
3243
+ ...result.result === void 0 ? {} : { result: redactedConvergeResult(result.result, db) }
3244
+ };
3245
+ }
3246
+ function formatMigrateJson(result, db) {
3247
+ const safe = redactedResult(result, db);
3248
+ const { schemaFile: _schemaFile, ...document } = safe;
3249
+ return document;
3250
+ }
3251
+ function formatMigrateHuman(result, db) {
3252
+ const safe = redactedResult(result, db);
3253
+ const diagnostic = (value) => escapeDiagnosticText5(value);
3254
+ const lines = [`${diagnostic(safe.outcome)}: ${diagnostic(safe.schema)}`];
3255
+ if (safe.applied?.length)
3256
+ for (const applied of safe.applied) lines.push(diagnostic(applied));
3257
+ if (safe.outcome === "partially-applied") {
3258
+ const stepKeys = (entries) => entries?.length ? entries.map(diagnostic).join(", ") : "none";
3259
+ lines.push(`completed: ${stepKeys(safe.completedStepKeys)}`);
3260
+ lines.push(`not started: ${stepKeys(safe.notStartedStepKeys)}`);
3261
+ }
3262
+ if (safe.detail !== void 0) lines.push(diagnostic(safe.detail));
3263
+ if (safe.changes?.length)
3264
+ for (const change of safe.changes)
3265
+ lines.push(diagnostic(JSON.stringify(change)));
3266
+ if (safe.outcome === "recovery-required") {
3267
+ if (safe.runIds?.length) {
3268
+ lines.push("dbsp reconcile --db <database> <run-id>");
3269
+ for (const runId of safe.runIds)
3270
+ lines.push(`run id: ${diagnostic(runId)}`);
3271
+ }
3272
+ if (safe.busyRunIds?.length) {
3273
+ for (const busyRunId of safe.busyRunIds)
3274
+ lines.push(`busy run id: ${diagnostic(busyRunId)}`);
3275
+ lines.push("these runs are still executing; retry later");
3276
+ }
3277
+ if (safe.executionIds?.length) {
3278
+ for (const executionId of safe.executionIds)
3279
+ lines.push(`execution id: ${diagnostic(executionId)}`);
3280
+ lines.push(
3281
+ "no dbsp command resolves a claim by execution id; the owner named in the detail above must resolve these claims"
3282
+ );
3283
+ }
3284
+ }
3285
+ if (safe.outcome === "ledger-absent")
3286
+ lines.push(
3287
+ "dbsp preflight --reinitialize --db <database> --schema-file <schema-file> --scope <schema> --out <adoption-file>",
3288
+ `schema file: ${diagnostic(safe.schemaFile)}`,
3289
+ `schema: ${diagnostic(safe.schema)}`
3290
+ );
3291
+ if (safe.outcome === "cleanup-failed" && safe.result !== void 0) {
3292
+ lines.push(`convergence: ${diagnostic(safe.result.kind)}`);
3293
+ if ("applied" in safe.result && safe.result.applied.length)
3294
+ for (const applied of safe.result.applied)
3295
+ lines.push(diagnostic(applied));
3296
+ }
3297
+ if (safe.error !== void 0) lines.push(diagnostic(safe.error));
3298
+ if (safe.cleanupError !== void 0)
3299
+ lines.push(`Connection cleanup failed: ${diagnostic(safe.cleanupError)}`);
3300
+ return lines.join("\n");
3301
+ }
3302
+ var migrateCommand = new Command6("migrate").description(
3303
+ "Converge without confirmation: applies only additions and declared adoptions admitted by convergePg; refused changes send no DDL, but execution can still be refused or partially applied. Use dbsp plan plus dbsp apply for reviewed changes."
3304
+ ).argument("<schema-file>", "Schema DSL file").requiredOption("-d, --db <url>", "Database connection URL (required)").option("--schema <name>", "Database schema name", "public").option(
3305
+ "--external-index <model-table:index>",
3306
+ "Physical index to leave alone; the table is the model name (before dbCasing maps it), then the index by its exact PostgreSQL name; the table part cannot contain :; repeatable.",
3307
+ (value, previous = []) => {
3308
+ previous.push(value);
3309
+ return previous;
3310
+ }
3311
+ ).option("--format <format>", "Output format: text or json", "text").addHelpText(
3312
+ "after",
3313
+ `
3314
+ Outcome contract:
3315
+ ${MIGRATE_OUTCOME_CONTRACT.map(([outcome, exitCode, description]) => ` ${outcome} (${exitCode}): ${description}`).join("\n")}
3316
+ Command-line syntax errors (an unknown option, a missing --db or schema file) exit 1 with { status, error } under --format json, as for every dbsp command.`
3317
+ ).exitOverride().configureOutput({ writeErr: () => {
3318
+ } }).action(
3319
+ async (schemaFile, options) => {
3320
+ const result = await runMigrate(schemaFile, options);
3321
+ if (options.format === "json")
3322
+ printCliJson(formatMigrateJson(result, options.db));
3323
+ else if (result.exitCode === 0)
3324
+ console.log(formatMigrateHuman(result, options.db));
3325
+ else console.error(formatMigrateHuman(result, options.db));
3326
+ process.exitCode = result.exitCode;
3482
3327
  }
3483
3328
  );
3484
3329
 
3485
3330
  // src/commands/preflight.ts
3486
- import { createHash as createHash4 } from "crypto";
3331
+ import { createHash as createHash2 } from "crypto";
3487
3332
  import { mkdtemp, open, rename, rm } from "fs/promises";
3488
3333
  import { dirname as dirname3, join } from "path";
3489
3334
  import {
3490
- escapeDiagnosticText as escapeDiagnosticText5,
3491
- getNamingPluginForDbCasing as getNamingPluginForDbCasing2,
3335
+ createPgPhysicalModel as createPgPhysicalModel4,
3336
+ escapeDiagnosticText as escapeDiagnosticText6,
3492
3337
  runPgReinitializePreflight
3493
3338
  } from "@dbsp/adapter-pgsql";
3494
3339
  import { declarationSetFromModel as declarationSetFromModel2 } from "@dbsp/core";
3495
- import { Command as Command6 } from "commander";
3340
+ import { Command as Command7 } from "commander";
3496
3341
  var defaultDeps = { createDbConnection, loadSchema };
3497
3342
  var nodeAdoptionFileSystem = {
3498
3343
  mkdtemp,
@@ -3542,13 +3387,9 @@ async function databaseName(pool) {
3542
3387
  throw new Error("current database could not be read");
3543
3388
  return name;
3544
3389
  }
3545
- function declarationsForScopes(model, database, scopes, naming) {
3390
+ function declarationsForScopes(model, database, scopes) {
3546
3391
  const declarations = scopes.flatMap(
3547
- (schema) => declarationSetFromModel2(
3548
- model,
3549
- { engine: "postgresql", database, schema },
3550
- naming
3551
- ).declarations
3392
+ (schema) => declarationSetFromModel2(model, { engine: "postgresql", database, schema }).declarations
3552
3393
  );
3553
3394
  const seen = /* @__PURE__ */ new Set();
3554
3395
  const uniqueDeclarations = declarations.filter((declaration) => {
@@ -3568,7 +3409,7 @@ function declarationsForScopes(model, database, scopes, naming) {
3568
3409
  });
3569
3410
  return {
3570
3411
  version: 1,
3571
- digest: createHash4("sha256").update(canonical).digest("hex"),
3412
+ digest: createHash2("sha256").update(canonical).digest("hex"),
3572
3413
  declarations: uniqueDeclarations
3573
3414
  };
3574
3415
  }
@@ -3583,19 +3424,25 @@ async function runPreflight(options, deps = defaultDeps) {
3583
3424
  throw new Error("dbsp preflight requires --reinitialize");
3584
3425
  if (!options.out)
3585
3426
  throw new Error("dbsp preflight --reinitialize requires --out <file>");
3586
- if (options.scopes.length === 0)
3427
+ const physicalSchema = options.scopes[0];
3428
+ if (physicalSchema === void 0)
3587
3429
  throw new Error(
3588
3430
  "dbsp preflight requires at least one explicit --scope <schema>"
3589
3431
  );
3590
3432
  const loaded = await deps.loadSchema(options.schemaFile);
3433
+ const physical = createPgPhysicalModel4({
3434
+ mode: "logical",
3435
+ model: loaded.model,
3436
+ schema: physicalSchema,
3437
+ ...loaded.dbCasing === void 0 ? {} : { dbCasing: loaded.dbCasing }
3438
+ });
3591
3439
  const connection = await deps.createDbConnection(options.db);
3592
3440
  try {
3593
3441
  const database = await databaseName(connection.pool);
3594
3442
  const declarations = declarationsForScopes(
3595
- loaded.model,
3443
+ physical.model,
3596
3444
  database,
3597
- options.scopes,
3598
- getNamingPluginForDbCasing2(loaded.dbCasing ?? "preserve")
3445
+ options.scopes
3599
3446
  );
3600
3447
  return await runPgReinitializePreflight({
3601
3448
  pool: connection.pool,
@@ -3610,18 +3457,18 @@ async function runPreflight(options, deps = defaultDeps) {
3610
3457
  function formatScope(scope) {
3611
3458
  const name = scope.ledger.scope === "database" ? "dbsp_meta" : scope.ledger.schema ?? "unknown";
3612
3459
  if (scope.outcome === "failed")
3613
- return `${escapeDiagnosticText5(name)}: failed (${escapeDiagnosticText5(scope.reason.step)}: ${escapeDiagnosticText5(scope.reason.message)})`;
3614
- return scope.refusal ? `${escapeDiagnosticText5(name)}: ${escapeDiagnosticText5(scope.outcome)} (${escapeDiagnosticText5(scope.refusal.code)}: ${escapeDiagnosticText5(scope.refusal.detail)})` : `${escapeDiagnosticText5(name)}: ${escapeDiagnosticText5(scope.outcome)}`;
3460
+ return `${escapeDiagnosticText6(name)}: failed (${escapeDiagnosticText6(scope.reason.step)}: ${escapeDiagnosticText6(scope.reason.message)})`;
3461
+ return scope.refusal ? `${escapeDiagnosticText6(name)}: ${escapeDiagnosticText6(scope.outcome)} (${escapeDiagnosticText6(scope.refusal.code)}: ${escapeDiagnosticText6(scope.refusal.detail)})` : `${escapeDiagnosticText6(name)}: ${escapeDiagnosticText6(scope.outcome)}`;
3615
3462
  }
3616
3463
  function formatReinitializeSplit(scopes) {
3617
3464
  const names = (predicate) => scopes.filter(predicate).map(
3618
- (scope) => escapeDiagnosticText5(
3465
+ (scope) => escapeDiagnosticText6(
3619
3466
  scope.ledger.scope === "database" ? "dbsp_meta" : scope.ledger.schema ?? "unknown"
3620
3467
  )
3621
3468
  ).join(",") || "none";
3622
3469
  return `reinitialize-split: changed=${names((scope) => scope.outcome === "current")}; failed=${names((scope) => scope.outcome === "failed")}; not-attempted=${names((scope) => scope.outcome === "not-attempted")}`;
3623
3470
  }
3624
- var preflightCommand = new Command6("preflight").description(
3471
+ var preflightCommand = new Command7("preflight").description(
3625
3472
  "Run the separately privileged managed-ledger reinitialize cutover"
3626
3473
  ).requiredOption("-d, --db <url>", "Database connection URL (required)").requiredOption(
3627
3474
  "--reinitialize",
@@ -3646,7 +3493,7 @@ var preflightCommand = new Command6("preflight").description(
3646
3493
  );
3647
3494
  } catch (error) {
3648
3495
  console.error(
3649
- escapeDiagnosticText5(
3496
+ escapeDiagnosticText6(
3650
3497
  error instanceof Error ? error.message : String(error)
3651
3498
  )
3652
3499
  );
@@ -3655,31 +3502,12 @@ var preflightCommand = new Command6("preflight").description(
3655
3502
  });
3656
3503
 
3657
3504
  // src/commands/reconcile.ts
3658
- import { createHash as createHash5 } from "crypto";
3659
3505
  import {
3660
- assertCreateUniqueIndexConcurrentlyRecoveryNotInvalid,
3661
- assertPgDatabaseWritable,
3662
- escapeDiagnosticText as escapeDiagnosticText6,
3663
- isPgDatabaseReadOnlyError,
3664
- readPgLedgerAddressChain as readPgLedgerAddressChain3,
3665
- readPgLedgerReservationsForExecution,
3666
- readPgLedgerScopeCurrency as readPgLedgerScopeCurrency2,
3667
- readTransitionJournal as readTransitionJournal2,
3668
- readVerifiedPgLedgerReservationsForPair,
3669
- recoverPgReaddressPair,
3670
- withPgTransitionRunLock as withPgTransitionRunLock2
3506
+ escapeDiagnosticText as escapeDiagnosticText7,
3507
+ PgReconcileTransitionRunError,
3508
+ reconcilePgTransitionRun
3671
3509
  } from "@dbsp/adapter-pgsql";
3672
- import { recoverPgOutcomeClaim } from "@dbsp/adapter-pgsql/internal";
3673
- import {
3674
- acquireExclusiveTransitionLease as acquireExclusiveTransitionLease2,
3675
- assumptionAccepted,
3676
- outcomeClaimId as outcomeClaimId2,
3677
- projectLedgerChain as projectLedgerChain3,
3678
- resourceScopeCovers,
3679
- transitionPlanDigest as transitionPlanDigest3
3680
- } from "@dbsp/core";
3681
- import { sameLedgerAddress } from "@dbsp/types";
3682
- import { Command as Command7 } from "commander";
3510
+ import { Command as Command8 } from "commander";
3683
3511
  function pgSqlState(error) {
3684
3512
  if (typeof error !== "object" || error === null || !("code" in error))
3685
3513
  return void 0;
@@ -3705,172 +3533,121 @@ function classifyReconcileFailure(error, stage) {
3705
3533
  if (stage === "journal") return "malformed-journal";
3706
3534
  return "catalogue";
3707
3535
  }
3708
- function ledgerHome(address) {
3709
- if (address.scope === "database") return { scope: "database" };
3710
- if (!address.schema)
3711
- throw new Error(
3712
- `schema-scoped managed claim ${address.name} has no schema`
3713
- );
3714
- return { scope: "schema", schema: address.schema };
3715
- }
3716
- function recoveryPayload(identity) {
3717
- const value = JSON.parse(
3718
- JSON.stringify({ catalogueIdentity: identity })
3536
+ function isUnresolvedRecoveryOutcome(report) {
3537
+ return [
3538
+ "pending",
3539
+ "blocked",
3540
+ "malformed-chain",
3541
+ "protocol-refused",
3542
+ "transport-ambiguous",
3543
+ "no-open-claim",
3544
+ "indeterminate-appended",
3545
+ "indeterminate-pair"
3546
+ ].includes(report.outcome);
3547
+ }
3548
+ function ledgerCurrencyDetail(issue) {
3549
+ return issue.currency.kind === "not-current" && issue.currency.reason === "lineage" ? "ledger lineage mismatch; run dbsp preflight --reinitialize" : `ledger marker ${issue.currency.marker.kind}; run dbsp preflight --reinitialize`;
3550
+ }
3551
+ function refusalForSelectedIssue(issue) {
3552
+ if (issue === void 0) return void 0;
3553
+ if (issue.kind === "ledger-not-current") {
3554
+ const address = issue.affectedAddresses[0];
3555
+ return address === void 0 ? void 0 : preAppendRefusalFor("ERR-03", { address, state: "unknown" });
3556
+ }
3557
+ if (issue.kind === "database-read-only") {
3558
+ const address = issue.affectedAddresses[0];
3559
+ return address === void 0 ? void 0 : preAppendRefusalFor("ERR-07", { address, state: "unknown" });
3560
+ }
3561
+ return preAppendRefusalFor(
3562
+ issue.kind === "catalogue-unavailable" ? "ERR-09" : "ERR-08",
3563
+ { address: issue.address, state: "unknown" }
3719
3564
  );
3720
- return {
3721
- value,
3722
- digest: createHash5("sha256").update(JSON.stringify(value)).digest("hex")
3723
- };
3724
3565
  }
3725
- function recoveryReport(address, result) {
3726
- if (result.kind === "outcome-recovery-appended") {
3727
- return {
3728
- address,
3729
- outcome: result.append.kind === "already-appended-outcome-resolution" ? "already-appended" : "appended",
3730
- reason: result.classification.resolution.reason
3731
- };
3732
- }
3733
- if (result.kind === "outcome-recovery-no-open-claim")
3734
- return { address, outcome: "no-open-claim" };
3735
- if (result.kind === "outcome-recovery-pending")
3736
- return {
3737
- address,
3738
- outcome: "pending",
3739
- reason: result.reason,
3740
- ...result.reasonCode === "catalogue-unavailable" ? { failureCause: "catalogue" } : {},
3741
- ...result.reasonCode === "catalogue-unavailable" ? {
3742
- refusal: preAppendRefusalFor("ERR-09", {
3743
- address,
3744
- state: "unknown"
3745
- })
3746
- } : {}
3747
- };
3748
- if (result.kind === "outcome-recovery-blocked")
3749
- return { address, outcome: "blocked", reason: result.reason };
3750
- if (result.kind === "outcome-recovery-malformed-chain")
3751
- return {
3752
- address,
3753
- outcome: "malformed-chain",
3754
- reason: result.reason,
3755
- failureCause: "malformed-journal",
3756
- refusal: preAppendRefusalFor("ERR-08", { address, state: "unknown" })
3757
- };
3758
- if (result.kind === "outcome-transport-ambiguous")
3566
+ function mapRecovery(reports, issue) {
3567
+ if (reports === void 0) return void 0;
3568
+ const markerDetail = issue?.kind === "ledger-not-current" ? ledgerCurrencyDetail(issue) : void 0;
3569
+ return reports.map((report) => ({
3570
+ ...report,
3571
+ ...markerDetail === void 0 ? {} : { reason: markerDetail },
3572
+ ...issue?.kind === "ledger-not-current" ? {
3573
+ refusal: preAppendRefusalFor("ERR-03", {
3574
+ address: report.address,
3575
+ state: "unknown"
3576
+ })
3577
+ } : report.failureCause === "catalogue" ? {
3578
+ refusal: preAppendRefusalFor("ERR-09", {
3579
+ address: report.address,
3580
+ state: "unknown"
3581
+ })
3582
+ } : report.failureCause === "malformed-journal" ? {
3583
+ refusal: preAppendRefusalFor("ERR-08", {
3584
+ address: report.address,
3585
+ state: "unknown"
3586
+ })
3587
+ } : {}
3588
+ }));
3589
+ }
3590
+ function unresolvedRecoveryDetail(reports) {
3591
+ const unresolved = reports.filter(isUnresolvedRecoveryOutcome);
3592
+ if (unresolved.length === 0) return void 0;
3593
+ return unresolved.map(
3594
+ (report) => `${escapeDiagnosticText7(report.address.name)}: ${escapeDiagnosticText7(report.reason ?? report.outcome)}`
3595
+ ).join("; ");
3596
+ }
3597
+ function formatReconcileHuman(result) {
3598
+ const line = `${escapeDiagnosticText7(result.outcome)}: ${escapeDiagnosticText7(result.runId)}`;
3599
+ const base = result.refusal ? formatPreAppendRefusalHuman(line, result.refusal) : line;
3600
+ if (result.outcome === "reconcile-completed" || !result.recovery) return base;
3601
+ const unresolved = result.recovery.filter(isUnresolvedRecoveryOutcome);
3602
+ return unresolved.length === 0 ? base : [
3603
+ base,
3604
+ ...unresolved.map(
3605
+ (report) => `${escapeDiagnosticText7(report.address.name)}: ${escapeDiagnosticText7(report.outcome)}${report.reason ? `: ${escapeDiagnosticText7(report.reason)}` : ""}`
3606
+ )
3607
+ ].join("\n");
3608
+ }
3609
+ function mapAdapterResult(result) {
3610
+ if (result.kind === "busy")
3759
3611
  return {
3760
- address,
3761
- outcome: "transport-ambiguous",
3762
- reason: result.reason,
3763
- failureCause: "transport"
3612
+ outcome: "reconcile-run-unavailable",
3613
+ runId: result.runId,
3614
+ addresses: []
3764
3615
  };
3765
- return { address, outcome: "protocol-refused", reason: result.reason };
3766
- }
3767
- function readdressRecoveryReport(reservations, result) {
3768
- const first = reservations[0];
3769
- if (!first) throw new Error("re-address recovery has no reservation");
3770
- if (result.kind === "readdress-recovery-refused-pair")
3616
+ if (result.kind === "selection-unavailable")
3771
3617
  return {
3772
- address: first.address,
3773
- outcome: "refused-pair",
3774
- pairId: result.pairId
3618
+ outcome: "reconcile-claim-selection-unavailable",
3619
+ runId: result.runId,
3620
+ addresses: result.addresses,
3621
+ ...result.detail === void 0 ? {} : { detail: result.detail }
3775
3622
  };
3776
- if (result.kind === "readdress-recovery-indeterminate-pair")
3623
+ if (result.kind === "database-read-only") {
3624
+ const refusal = refusalForSelectedIssue(result.selectedIssue);
3777
3625
  return {
3778
- address: first.address,
3779
- outcome: "indeterminate-pair",
3780
- reason: result.reason,
3781
- pairId: result.pairId
3626
+ outcome: "database-read-only",
3627
+ runId: result.runId,
3628
+ addresses: result.addresses,
3629
+ detail: result.selectedIssue.reason,
3630
+ ...refusal === void 0 ? {} : { refusal }
3782
3631
  };
3783
- if (result.kind === "readdress-recovery-transport-ambiguous-pair")
3632
+ }
3633
+ const recovery = mapRecovery(result.recovery, result.selectedIssue);
3634
+ const detail = result.selectedIssue?.kind === "ledger-not-current" ? ledgerCurrencyDetail(result.selectedIssue) : recovery === void 0 ? void 0 : unresolvedRecoveryDetail(recovery);
3635
+ if (result.kind === "unresolved") {
3636
+ const refusal = refusalForSelectedIssue(result.selectedIssue);
3784
3637
  return {
3785
- address: first.address,
3786
- outcome: "transport-ambiguous",
3787
- reason: result.reason,
3788
- failureCause: "transport",
3789
- pairId: result.pairId
3638
+ outcome: "reconcile-unresolved",
3639
+ runId: result.runId,
3640
+ addresses: result.addresses,
3641
+ ...detail === void 0 ? {} : { detail },
3642
+ ...recovery === void 0 ? {} : { recovery },
3643
+ ...refusal === void 0 ? {} : { refusal }
3790
3644
  };
3791
- return {
3792
- address: first.address,
3793
- outcome: "pending",
3794
- reason: result.reason,
3795
- pairId: result.pairId
3796
- };
3797
- }
3798
- function unresolvedRecoveryDetail(reports) {
3799
- const unresolved = reports.filter(
3800
- (report) => report.outcome === "pending" || report.outcome === "blocked" || report.outcome === "malformed-chain" || report.outcome === "protocol-refused" || report.outcome === "transport-ambiguous" || report.outcome === "no-open-claim" || report.outcome === "indeterminate-pair"
3801
- );
3802
- if (unresolved.length === 0) return void 0;
3803
- return unresolved.map(
3804
- (report) => `${escapeDiagnosticText6(report.address.name)}: ${escapeDiagnosticText6(report.reason ?? report.outcome)}`
3805
- ).join("; ");
3806
- }
3807
- function formatReconcileHuman(result) {
3808
- const line = `${escapeDiagnosticText6(result.outcome)}: ${escapeDiagnosticText6(result.runId)}`;
3809
- return result.refusal ? formatPreAppendRefusalHuman(line, result.refusal) : line;
3810
- }
3811
- function executionIdsForRun(journal) {
3812
- const executionIds = /* @__PURE__ */ new Set([journal.run.runId]);
3813
- for (const event of journal.events) {
3814
- const record2 = event.record;
3815
- if (event.event === "intent" && "executionId" in record2 && typeof record2.executionId === "string")
3816
- executionIds.add(record2.executionId);
3817
- if (event.event === "observed" && "intent" in record2 && record2.intent && typeof record2.intent.executionId === "string")
3818
- executionIds.add(record2.intent.executionId);
3819
3645
  }
3820
- if ("generator" in journal.plan)
3821
- executionIds.add(`dbsp.generator.execution.${journal.run.runId}`);
3822
- return [...executionIds];
3823
- }
3824
- function statementBundleDigest(statements) {
3825
- return createHash5("sha256").update(
3826
- JSON.stringify(statements.map(({ ordinal, sql }) => [ordinal, sql]))
3827
- ).digest("hex");
3828
- }
3829
- function recoveryEvidenceForClaim(input) {
3830
- if (transitionPlanDigest3(input.journal.plan) !== input.journal.run.planDigest)
3831
- return void 0;
3832
- const step = input.journal.plan.steps.find(
3833
- (candidate) => candidate.managedClaim?.plannedClaimKey === input.plannedClaimKey
3834
- );
3835
- const claim = step?.managedClaim;
3836
- if (!step || !claim || !sameLedgerAddress(claim.address, input.row.address) || outcomeClaimId2(
3837
- input.row.executionId,
3838
- input.plannedClaimKey,
3839
- input.row.address
3840
- ) !== input.row.rootClaimId)
3841
- return void 0;
3842
- const binding = step.guards.find(
3843
- (guard) => guard.protocol.binding.kind === "external-ddl-exclusion" && resourceScopeCovers(guard.protocol.binding.scope, [input.row.address])
3844
- )?.protocol.binding;
3845
- if (binding?.kind !== "external-ddl-exclusion") return void 0;
3846
- const assumption = input.journal.plan.assumptions.find(
3847
- (candidate) => candidate.id === binding.assumption && candidate.class === "external-ddl-exclusion" && step.restsOnAssumptions.includes(candidate.id) && resourceScopeCovers(candidate.scope, [input.row.address])
3848
- );
3849
- if (!assumption) return void 0;
3850
- const accepted = input.journal.authorizations?.some((authorization) => {
3851
- const grant = authorization.grants.find(
3852
- (candidate) => candidate.assumptionId === assumption.id
3853
- );
3854
- if (!grant) return false;
3855
- const acceptance2 = authorization.policy[grant.grant];
3856
- return acceptance2 !== void 0 && assumptionAccepted(assumption, { accepts: [acceptance2] });
3857
- });
3858
- if (!accepted) return void 0;
3859
- const bundleDigest = statementBundleDigest(claim.statementBundle.statements);
3860
3646
  return {
3861
- runId: input.journal.run.runId,
3862
- planDigest: input.journal.run.planDigest,
3863
- executionId: input.row.executionId,
3864
- claimId: input.row.rootClaimId,
3865
- plannedClaimKey: input.plannedClaimKey,
3866
- admittedBundleDigest: bundleDigest,
3867
- persistedBundleDigest: bundleDigest,
3868
- recordedPreState: input.stableStateBeforeClaim,
3869
- externalDdlExclusion: {
3870
- planDigest: input.journal.run.planDigest,
3871
- address: input.row.address,
3872
- trustRoot: JSON.stringify(assumption.asserter)
3873
- }
3647
+ outcome: "reconcile-completed",
3648
+ runId: result.runId,
3649
+ addresses: result.addresses,
3650
+ ...recovery === void 0 ? {} : { recovery }
3874
3651
  };
3875
3652
  }
3876
3653
  async function runReconcile(runId, options, pool) {
@@ -3885,303 +3662,28 @@ async function runReconcile(runId, options, pool) {
3885
3662
  failureCause: classifyReconcileFailure(error, "reconcile")
3886
3663
  };
3887
3664
  }
3888
- let stage = "reconcile";
3889
3665
  try {
3890
- const locked = await withPgTransitionRunLock2(
3891
- owned,
3892
- runId,
3893
- async (target) => {
3894
- const lease = await acquireExclusiveTransitionLease2(target);
3895
- try {
3896
- stage = "journal";
3897
- const journal = await readTransitionJournal2(lease.session, runId, {
3898
- ensure: false
3899
- });
3900
- stage = "reconcile";
3901
- const material = journal.plan.steps.map((step) => {
3902
- const generatedStep = step;
3903
- const generated = generatedStep.address;
3904
- return step.managedClaim ?? (generated === void 0 ? void 0 : {
3905
- address: generated,
3906
- ...generatedStep.plannedClaimKeys?.[0] === void 0 ? {} : {
3907
- plannedClaimKey: generatedStep.plannedClaimKeys[0]
3908
- }
3909
- });
3910
- }).filter(
3911
- (claim) => claim !== void 0
3912
- );
3913
- try {
3914
- await assertPgDatabaseWritable(lease.session);
3915
- } catch (error) {
3916
- if (isPgDatabaseReadOnlyError(error))
3917
- return {
3918
- kind: "database-read-only",
3919
- detail: error.message,
3920
- addresses: material.map((claim) => claim.address)
3921
- };
3922
- throw error;
3923
- }
3924
- if (material.length === 0)
3925
- return {
3926
- kind: "selection-unavailable",
3927
- addresses: journal.plan.declarations?.declarations.map(
3928
- (declaration) => declaration.address
3929
- ) ?? []
3930
- };
3931
- const homes = /* @__PURE__ */ new Map();
3932
- for (const claim of material) {
3933
- const home2 = ledgerHome(claim.address);
3934
- homes.set(`${home2.scope}:${home2.schema ?? ""}`, home2);
3935
- }
3936
- for (const home2 of homes.values()) {
3937
- const currency = await readPgLedgerScopeCurrency2(
3938
- lease.session,
3939
- home2
3940
- );
3941
- if (currency.kind === "current") continue;
3942
- const detail = currency.kind === "not-current" && currency.reason === "lineage" ? "ledger lineage mismatch; run dbsp preflight --reinitialize" : `ledger marker ${currency.marker.kind}; run dbsp preflight --reinitialize`;
3943
- const affected = material.filter((claim) => {
3944
- const claimHome = ledgerHome(claim.address);
3945
- return claimHome.scope === home2.scope && claimHome.schema === home2.schema;
3946
- });
3947
- return {
3948
- kind: "unresolved",
3949
- addresses: affected.map((claim) => claim.address),
3950
- detail,
3951
- recovery: affected.map((claim) => ({
3952
- address: claim.address,
3953
- outcome: "blocked",
3954
- reason: detail,
3955
- refusal: preAppendRefusalFor("ERR-03", {
3956
- address: claim.address,
3957
- state: "unknown"
3958
- })
3959
- }))
3960
- };
3961
- }
3962
- const executionIds = executionIdsForRun(journal);
3963
- const reservations = (await Promise.all(
3964
- [...homes.values()].flatMap(
3965
- (home2) => executionIds.map(
3966
- (executionId) => readPgLedgerReservationsForExecution(
3967
- lease.session,
3968
- home2,
3969
- executionId
3970
- )
3971
- )
3972
- )
3973
- )).flat();
3974
- if (reservations.length === 0)
3975
- return {
3976
- kind: "selection-unavailable",
3977
- addresses: material.map((x) => x.address)
3978
- };
3979
- const readdressPairs = /* @__PURE__ */ new Map();
3980
- const byRoot = /* @__PURE__ */ new Map();
3981
- for (const row of reservations) {
3982
- if (row.claimKind === "readdress-intent" && row.pairId !== void 0) {
3983
- const pairKey = `${row.executionId}:${row.pairId}`;
3984
- readdressPairs.set(pairKey, [
3985
- ...readdressPairs.get(pairKey) ?? [],
3986
- row
3987
- ]);
3988
- continue;
3989
- }
3990
- byRoot.set(row.rootClaimId, [
3991
- ...byRoot.get(row.rootClaimId) ?? [],
3992
- row
3993
- ]);
3994
- }
3995
- const recovery = [];
3996
- for (const [_pairKey, pairRows] of readdressPairs) {
3997
- const pairId = pairRows[0]?.pairId;
3998
- const executionId = pairRows[0]?.executionId;
3999
- if (!pairId || !executionId) continue;
4000
- const readPairInSelectedHomes = readVerifiedPgLedgerReservationsForPair;
4001
- const closure = await readPairInSelectedHomes(
4002
- lease.session,
4003
- pairId,
4004
- [...homes.values()]
4005
- );
4006
- const recovered = await recoverPgReaddressPair(lease.session, {
4007
- pairId,
4008
- executionId,
4009
- reservations: closure
4010
- });
4011
- recovery.push(readdressRecoveryReport(closure, recovered));
4012
- }
4013
- for (const rows of byRoot.values()) {
4014
- const rootClaimId = rows[0]?.rootClaimId;
4015
- if (!rootClaimId) continue;
4016
- if (rows.some(
4017
- (row) => !executionIds.includes(row.executionId) || row.rootClaimId !== rootClaimId
4018
- ))
4019
- return {
4020
- kind: "selection-unavailable",
4021
- addresses: rows.map((row) => row.address),
4022
- detail: `reservation disagreement for root claim ${rootClaimId}`
4023
- };
4024
- const rootCandidates = [];
4025
- for (const row of rows) {
4026
- stage = "catalogue";
4027
- const chain = await readPgLedgerAddressChain3(
4028
- lease.session,
4029
- ledgerHome(row.address),
4030
- row.address
4031
- );
4032
- stage = "reconcile";
4033
- const projection = projectLedgerChain3(chain);
4034
- if (projection.kind !== "projected-ledger-chain" || projection.openClaim === void 0)
4035
- continue;
4036
- const open2 = projection.openClaim.event;
4037
- const openRoot = open2.rootClaimId ?? open2.eventId;
4038
- if (open2.executionId !== row.executionId || openRoot !== rootClaimId)
4039
- return {
4040
- kind: "selection-unavailable",
4041
- addresses: rows.map((item) => item.address),
4042
- detail: `open chain member disagrees with reservation root ${rootClaimId}`
4043
- };
4044
- if (open2.eventId === rootClaimId)
4045
- rootCandidates.push({
4046
- row,
4047
- stableStateBeforeClaim: projection.openClaim.stableStateBeforeClaim,
4048
- ...open2.plannedClaimKey === void 0 ? {} : { plannedClaimKey: open2.plannedClaimKey }
4049
- });
4050
- }
4051
- if (rootCandidates.length !== 1)
4052
- return {
4053
- kind: "selection-unavailable",
4054
- addresses: rows.map((row) => row.address),
4055
- detail: `execution ${runId} has ${rootCandidates.length} open root members for ${rootClaimId}`
4056
- };
4057
- const selected = rootCandidates[0];
4058
- if (!selected) continue;
4059
- if (!selected.plannedClaimKey || !material.some(
4060
- (claim) => claim.plannedClaimKey === selected.plannedClaimKey
4061
- ))
4062
- return {
4063
- kind: "selection-unavailable",
4064
- addresses: rows.map((row) => row.address),
4065
- detail: `open root ${rootClaimId} has no matching persisted managed step`
4066
- };
4067
- const step = journal.plan.steps.find(
4068
- (candidate) => candidate.managedClaim?.plannedClaimKey === selected.plannedClaimKey || "plannedClaimKeys" in candidate && Array.isArray(candidate.plannedClaimKeys) && candidate.plannedClaimKeys.includes(
4069
- selected.plannedClaimKey
4070
- )
4071
- );
4072
- const operationReadBack = step?.operation?.operationKind.name === "CreateUniqueIndexConcurrently" ? async (executor, _address, identity) => {
4073
- await assertCreateUniqueIndexConcurrentlyRecoveryNotInvalid(
4074
- executor,
4075
- step.operation
4076
- );
4077
- return {
4078
- observed: recoveryPayload(identity),
4079
- effect: "unverifiable"
4080
- };
4081
- } : void 0;
4082
- const indeterminateEvidence = recoveryEvidenceForClaim({
4083
- journal,
4084
- row: selected.row,
4085
- plannedClaimKey: selected.plannedClaimKey,
4086
- stableStateBeforeClaim: selected.stableStateBeforeClaim
4087
- });
4088
- for (const row of rows) {
4089
- const isRoot = sameLedgerAddress(
4090
- row.address,
4091
- selected.row.address
4092
- );
4093
- const recovered = await recoverPgOutcomeClaim(owned, {
4094
- address: row.address,
4095
- reservations: [row],
4096
- resolutionEventId: isRoot ? `${rootClaimId}:reconcile:${runId}` : `${rootClaimId}:reconcile:${runId}:${row.address.kind}:${row.address.name}`,
4097
- acceptedExternalDdlExclusion: isRoot && indeterminateEvidence !== void 0,
4098
- resolveIndeterminate: true,
4099
- readBack: async (_executor, _address, identity) => recoveryPayload(identity),
4100
- ...isRoot && indeterminateEvidence !== void 0 ? { indeterminateEvidence } : {},
4101
- ...isRoot && operationReadBack !== void 0 ? { operationReadBack } : {}
4102
- });
4103
- recovery.push(recoveryReport(row.address, recovered));
4104
- }
4105
- }
4106
- const unresolved = unresolvedRecoveryDetail(recovery);
4107
- if (unresolved)
4108
- return {
4109
- kind: "unresolved",
4110
- addresses: reservations.map((item) => item.address),
4111
- detail: unresolved,
4112
- recovery
4113
- };
4114
- return {
4115
- kind: "completed",
4116
- addresses: reservations.map((item) => item.address),
4117
- recovery
4118
- };
4119
- } finally {
4120
- await lease.release();
4121
- }
4122
- }
4123
- );
4124
- if (locked.kind === "busy")
4125
- return { outcome: "reconcile-run-unavailable", runId, addresses: [] };
4126
- if (locked.value.kind === "selection-unavailable")
4127
- return {
4128
- outcome: "reconcile-claim-selection-unavailable",
4129
- runId,
4130
- addresses: locked.value.addresses,
4131
- ...locked.value.detail ? { detail: locked.value.detail } : {}
4132
- };
4133
- if (locked.value.kind === "database-read-only") {
4134
- const address = locked.value.addresses[0];
4135
- return {
4136
- outcome: "database-read-only",
4137
- runId,
4138
- addresses: locked.value.addresses,
4139
- detail: locked.value.detail,
4140
- ...address === void 0 ? {} : {
4141
- refusal: preAppendRefusalFor("ERR-07", {
4142
- address,
4143
- state: "unknown"
4144
- })
4145
- }
4146
- };
4147
- }
4148
- if (locked.value.kind === "unresolved") {
4149
- const address = locked.value.addresses[0];
4150
- const markerRefusal = address !== void 0 && locked.value.detail?.includes("run dbsp preflight --reinitialize") ? preAppendRefusalFor("ERR-03", { address, state: "unknown" }) : void 0;
4151
- const recoveryRefusal = locked.value.recovery?.find(
4152
- (report) => report.refusal !== void 0
4153
- )?.refusal;
4154
- const refusal = markerRefusal ?? recoveryRefusal;
4155
- return {
4156
- outcome: "reconcile-unresolved",
4157
- runId,
4158
- addresses: locked.value.addresses,
4159
- ...locked.value.detail ? { detail: locked.value.detail } : {},
4160
- ...locked.value.recovery ? { recovery: locked.value.recovery } : {},
4161
- ...refusal === void 0 ? {} : { refusal }
4162
- };
4163
- }
4164
- return {
4165
- outcome: "reconcile-completed",
4166
- runId,
4167
- addresses: locked.value.addresses,
4168
- ...locked.value.recovery ? { recovery: locked.value.recovery } : {}
4169
- };
3666
+ return mapAdapterResult(await reconcilePgTransitionRun(owned, runId));
4170
3667
  } catch (error) {
3668
+ const typed = error instanceof PgReconcileTransitionRunError ? error : void 0;
3669
+ const cause = typed?.originalCause ?? error;
4171
3670
  return {
4172
3671
  outcome: "reconcile-run-unavailable",
4173
3672
  runId,
4174
3673
  addresses: [],
4175
- detail: escapeDiagnosticText6(
4176
- error instanceof Error ? error.message : String(error)
3674
+ detail: escapeDiagnosticText7(
3675
+ cause instanceof Error ? cause.message : String(cause)
4177
3676
  ),
4178
- failureCause: classifyReconcileFailure(error, stage)
3677
+ failureCause: classifyReconcileFailure(
3678
+ cause,
3679
+ typed?.stage ?? "reconcile"
3680
+ )
4179
3681
  };
4180
3682
  } finally {
4181
3683
  if (pool === void 0) await owned.end();
4182
3684
  }
4183
3685
  }
4184
- var reconcileCommand = new Command7("reconcile").description("Resolve this run\u2019s open managed claims from live evidence only").argument("<run-id>", "Durable run identifier").requiredOption("-d, --db <url>", "Database connection URL (required)").option("--format <format>", "Output format: text or json", "text").action(async (runId, options) => {
3686
+ var reconcileCommand = new Command8("reconcile").description("Resolve this run\u2019s open managed claims from live evidence only").argument("<run-id>", "Durable run identifier").requiredOption("-d, --db <url>", "Database connection URL (required)").option("--format <format>", "Output format: text or json", "text").action(async (runId, options) => {
4185
3687
  const result = await runReconcile(runId, options);
4186
3688
  if (options.format === "json") printCliJson(result);
4187
3689
  else console.log(formatReconcileHuman(result));
@@ -4190,25 +3692,25 @@ var reconcileCommand = new Command7("reconcile").description("Resolve this run\u
4190
3692
 
4191
3693
  // src/commands/recover.ts
4192
3694
  import {
4193
- assertPgDatabaseWritable as assertPgDatabaseWritable2,
4194
- createPgTransitionPack as createPgTransitionPack3,
4195
- isPgDatabaseReadOnlyError as isPgDatabaseReadOnlyError2,
3695
+ assertPgDatabaseWritable,
3696
+ createPgTransitionPack as createPgTransitionPack2,
3697
+ isPgDatabaseReadOnlyError,
4196
3698
  preparePgRecoveryAdmission,
4197
- readPgLedgerScopeCurrency as readPgLedgerScopeCurrency3,
3699
+ readPgLedgerScopeCurrency,
4198
3700
  readPgObservationContextFromLessor as readPgObservationContextFromLessor2,
4199
- readTransitionJournal as readTransitionJournal3,
4200
- withPgTransitionRunLock as withPgTransitionRunLock3
3701
+ readTransitionJournal as readTransitionJournal2,
3702
+ withPgTransitionRunLock as withPgTransitionRunLock2
4201
3703
  } from "@dbsp/adapter-pgsql";
4202
3704
  import {
4203
3705
  acquireTransitionTargetLease,
4204
- assumptionAccepted as assumptionAccepted2,
4205
- createApplier as createApplier2,
4206
- createPackRegistry as createPackRegistry3,
3706
+ assumptionAccepted,
3707
+ createApplier,
3708
+ createPackRegistry as createPackRegistry2,
4207
3709
  loadVerifiedRecoveryJournal,
4208
- transitionPlanDigest as transitionPlanDigest4,
3710
+ transitionPlanDigest as transitionPlanDigest3,
4209
3711
  validateTransitionRelationalInvariants
4210
3712
  } from "@dbsp/core";
4211
- import { Command as Command8 } from "commander";
3713
+ import { Command as Command9 } from "commander";
4212
3714
  var RECOVER_OUTCOME_CONTRACT = [
4213
3715
  ["completed", 0, "all attempted steps were durably reconciled"],
4214
3716
  ["recovery-resume-required", 40, "remaining work requires a new proof"],
@@ -4297,7 +3799,7 @@ function validateRecoveryAuthorization(journal) {
4297
3799
  if (journal.plan.assumptions.every((assumption) => {
4298
3800
  const grant = grants.get(assumption.id);
4299
3801
  const acceptedGrant = grant === void 0 ? void 0 : accepts[grant];
4300
- return grant !== void 0 && grant >= 0 && grant < accepts.length && acceptedGrant !== void 0 && assumptionAccepted2(assumption, {
3802
+ return grant !== void 0 && grant >= 0 && grant < accepts.length && acceptedGrant !== void 0 && assumptionAccepted(assumption, {
4301
3803
  accepts: [acceptedGrant]
4302
3804
  });
4303
3805
  }))
@@ -4308,7 +3810,7 @@ function validateRecoveryAuthorization(journal) {
4308
3810
  return { ok: false, outcome: "recovery-authorization-invalid" };
4309
3811
  }
4310
3812
  function recoveryPolicyForJournal(loaded, runId) {
4311
- if (loaded.run.runId !== runId || loaded.run.planDigest !== transitionPlanDigest4(loaded.plan) || !validateTransitionRelationalInvariants({ kind: "plan", plan: loaded.plan }).ok)
3813
+ if (loaded.run.runId !== runId || loaded.run.planDigest !== transitionPlanDigest3(loaded.plan) || !validateTransitionRelationalInvariants({ kind: "plan", plan: loaded.plan }).ok)
4312
3814
  return { ok: false, outcome: "recovery-plan-invalid" };
4313
3815
  return validateRecoveryAuthorization(loaded);
4314
3816
  }
@@ -4336,12 +3838,12 @@ function outcomeForRecoveryResult(result) {
4336
3838
  async function load(target, runId) {
4337
3839
  const lease = await acquireTransitionTargetLease(target);
4338
3840
  try {
4339
- return await readTransitionJournal3(lease.session, runId, { ensure: false });
3841
+ return await readTransitionJournal2(lease.session, runId, { ensure: false });
4340
3842
  } finally {
4341
3843
  await lease.release();
4342
3844
  }
4343
3845
  }
4344
- function ledgerHome2(address) {
3846
+ function ledgerHome(address) {
4345
3847
  if (address.scope === "database") return { scope: "database" };
4346
3848
  if (!address.schema)
4347
3849
  throw new Error(
@@ -4359,24 +3861,24 @@ function recoveryAddresses(journal) {
4359
3861
  return address === void 0 ? [] : [address];
4360
3862
  });
4361
3863
  }
4362
- function formatLedgerHome(home2) {
4363
- return home2.scope === "database" ? "database" : `schema ${home2.schema}`;
3864
+ function formatLedgerHome(home) {
3865
+ return home.scope === "database" ? "database" : `schema ${home.schema}`;
4364
3866
  }
4365
3867
  async function recoverMarkerRefusal(target, journal) {
4366
3868
  const addresses = recoveryAddresses(journal);
4367
3869
  const homes = [];
4368
3870
  for (const address of addresses) {
4369
- const home2 = ledgerHome2(address);
4370
- if (homes.some((candidate) => sameLedgerHome(candidate.home, home2)))
3871
+ const home = ledgerHome(address);
3872
+ if (homes.some((candidate) => sameLedgerHome(candidate.home, home)))
4371
3873
  continue;
4372
- homes.push({ address, home: home2 });
3874
+ homes.push({ address, home });
4373
3875
  }
4374
3876
  const lease = await acquireTransitionTargetLease(target);
4375
3877
  try {
4376
- for (const { address, home: home2 } of homes) {
4377
- const currency = await readPgLedgerScopeCurrency3(lease.session, home2);
3878
+ for (const { address, home } of homes) {
3879
+ const currency = await readPgLedgerScopeCurrency(lease.session, home);
4378
3880
  if (currency.kind === "current") continue;
4379
- const detail = currency.kind === "not-current" && currency.reason === "lineage" ? `ledger lineage mismatch in ${formatLedgerHome(home2)}; run dbsp preflight --reinitialize` : `ledger marker ${currency.marker.kind} in ${formatLedgerHome(home2)}; run dbsp preflight --reinitialize`;
3881
+ const detail = currency.kind === "not-current" && currency.reason === "lineage" ? `ledger lineage mismatch in ${formatLedgerHome(home)}; run dbsp preflight --reinitialize` : `ledger marker ${currency.marker.kind} in ${formatLedgerHome(home)}; run dbsp preflight --reinitialize`;
4380
3882
  return {
4381
3883
  detail,
4382
3884
  refusal: preAppendRefusalFor("ERR-03", {
@@ -4396,7 +3898,7 @@ async function runRecover(runId, options, pool) {
4396
3898
  const owned = pool === void 0 ? (await createDbConnection(options.db)).pool : pool;
4397
3899
  let result;
4398
3900
  try {
4399
- const locked = await withPgTransitionRunLock3(
3901
+ const locked = await withPgTransitionRunLock2(
4400
3902
  owned,
4401
3903
  runId,
4402
3904
  async (target) => {
@@ -4419,9 +3921,9 @@ async function runRecover(runId, options, pool) {
4419
3921
  }
4420
3922
  const writabilityLease = await acquireTransitionTargetLease(target);
4421
3923
  try {
4422
- await assertPgDatabaseWritable2(writabilityLease.session);
3924
+ await assertPgDatabaseWritable(writabilityLease.session);
4423
3925
  } catch (error) {
4424
- if (isPgDatabaseReadOnlyError2(error))
3926
+ if (isPgDatabaseReadOnlyError(error))
4425
3927
  return {
4426
3928
  outcome: "database-read-only",
4427
3929
  detail: error.message
@@ -4442,8 +3944,8 @@ async function runRecover(runId, options, pool) {
4442
3944
  const authorization = loaded.journal.events.length === 0 ? void 0 : recoveryPolicyForJournal(loaded.journal, runId);
4443
3945
  if (authorization && !authorization.ok)
4444
3946
  return { outcome: authorization.outcome };
4445
- const result2 = await createApplier2(
4446
- createPackRegistry3([createPgTransitionPack3({})]),
3947
+ const result2 = await createApplier(
3948
+ createPackRegistry2([createPgTransitionPack2({})]),
4447
3949
  { persist: async () => void 0 }
4448
3950
  ).resume(
4449
3951
  loaded.journal,
@@ -4477,7 +3979,7 @@ async function runRecover(runId, options, pool) {
4477
3979
  }
4478
3980
  return result;
4479
3981
  }
4480
- var recoverCommand = new Command8("recover").description(
3982
+ var recoverCommand = new Command9("recover").description(
4481
3983
  "Observe and classify an attempted durable run; performs no planned target DDL and does not re-authorize assumptions"
4482
3984
  ).argument("<run-id>", "Durable run identifier").requiredOption("-d, --db <url>", "Database connection URL (required)").requiredOption(
4483
3985
  "--plan-digest <sha>",
@@ -4513,11 +4015,11 @@ ${RECOVER_OUTCOME_CONTRACT.map(([outcome, code, description]) => ` ${outcome} -
4513
4015
  // src/commands/release.ts
4514
4016
  import {
4515
4017
  createPgTransitionLessor as createPgTransitionLessor5,
4516
- escapeDiagnosticText as escapeDiagnosticText7,
4018
+ escapeDiagnosticText as escapeDiagnosticText8,
4517
4019
  releasePgManagedAddress
4518
4020
  } from "@dbsp/adapter-pgsql";
4519
4021
  import { acquireTransitionLease as acquireTransitionLease5 } from "@dbsp/core";
4520
- import { Command as Command9 } from "commander";
4022
+ import { Command as Command10 } from "commander";
4521
4023
  async function runRelease(selector, options) {
4522
4024
  const { pool } = await createDbConnection(options.db);
4523
4025
  const schema = options.schema ?? "public";
@@ -4552,18 +4054,18 @@ async function runRelease(selector, options) {
4552
4054
  function formatReleaseHuman(result) {
4553
4055
  if (result.outcome === "released") return "released";
4554
4056
  if (result.outcome === "database-read-only")
4555
- return `database-read-only: ${escapeDiagnosticText7(result.detail)}`;
4057
+ return `database-read-only: ${escapeDiagnosticText8(result.detail)}`;
4556
4058
  if (result.outcome === "release-unavailable")
4557
- return `release-unavailable: ${escapeDiagnosticText7(result.detail)}`;
4059
+ return `release-unavailable: ${escapeDiagnosticText8(result.detail)}`;
4558
4060
  return [
4559
- `release-refused: ${escapeDiagnosticText7(result.detail)}`,
4560
- `address: ${escapeDiagnosticText7(JSON.stringify(result.address))}`,
4561
- `state: ${escapeDiagnosticText7(result.refusal.state)}`,
4562
- `withheld authority: ${escapeDiagnosticText7(result.refusal.withheldAuthority)}`,
4563
- `resolving command: ${escapeDiagnosticText7(result.refusal.resolvingCommand)}`
4061
+ `release-refused: ${escapeDiagnosticText8(result.detail)}`,
4062
+ `address: ${escapeDiagnosticText8(JSON.stringify(result.address))}`,
4063
+ `state: ${escapeDiagnosticText8(result.refusal.state)}`,
4064
+ `withheld authority: ${escapeDiagnosticText8(result.refusal.withheldAuthority)}`,
4065
+ `resolving command: ${escapeDiagnosticText8(result.refusal.resolvingCommand)}`
4564
4066
  ].join("\n");
4565
4067
  }
4566
- var releaseCommand = new Command9("release").description(
4068
+ var releaseCommand = new Command10("release").description(
4567
4069
  "End management for one address without touching the database object"
4568
4070
  ).argument("<address>", "Address as name or kind:name").requiredOption("-d, --db <url>", "Database connection URL (required)").option("--schema <name>", "Schema ledger containing the address", "public").option("--kind <kind>", "Kind for an unqualified address", "table").option(
4569
4071
  "--parent <kind:name>",
@@ -4581,15 +4083,15 @@ var releaseCommand = new Command9("release").description(
4581
4083
  const detail = error instanceof Error ? error.message : String(error);
4582
4084
  if (options.format === "json")
4583
4085
  printCliJson({ outcome: "release-refused", detail });
4584
- else console.error(`release-refused: ${escapeDiagnosticText7(detail)}`);
4086
+ else console.error(`release-refused: ${escapeDiagnosticText8(detail)}`);
4585
4087
  process.exitCode = 1;
4586
4088
  }
4587
4089
  });
4588
4090
 
4589
4091
  // src/commands/repl.ts
4590
4092
  import { readFileSync } from "fs";
4591
- import { Command as Command10 } from "commander";
4592
- var replCommand = new Command10("repl").description("Launch interactive REPL for exploring schema and queries").option("-s, --schema <path>", "Path to schema file (default: auto-detect)").option(
4093
+ import { Command as Command11 } from "commander";
4094
+ var replCommand = new Command11("repl").description("Launch interactive REPL for exploring schema and queries").option("-s, --schema <path>", "Path to schema file (default: auto-detect)").option(
4593
4095
  "-d, --db <url>",
4594
4096
  "PostgreSQL connection URL for execution mode (e.g., postgres://localhost/mydb)"
4595
4097
  ).option("-e, --eval <query>", "Execute a single query and exit (batch mode)").option(
@@ -4711,10 +4213,11 @@ function mapReplCasingOption(casing) {
4711
4213
  // src/commands/verify.ts
4712
4214
  import {
4713
4215
  comparePgsqlDatabaseSchema as comparePgsqlDatabaseSchema2,
4714
- createPgsqlAdapter as createPgsqlAdapter4,
4715
- escapeDiagnosticText as escapeDiagnosticText8
4216
+ createPgPhysicalModel as createPgPhysicalModel5,
4217
+ createPgsqlAdapter as createPgsqlAdapter3,
4218
+ escapeDiagnosticText as escapeDiagnosticText9
4716
4219
  } from "@dbsp/adapter-pgsql";
4717
- import { Command as Command11 } from "commander";
4220
+ import { Command as Command12 } from "commander";
4718
4221
 
4719
4222
  // src/verifier.ts
4720
4223
  var CHANGE_TO_DRIFT = {
@@ -4744,6 +4247,10 @@ var CHANGE_TO_DRIFT = {
4744
4247
  // Column enhancements
4745
4248
  alter_column_collation: { type: "collation_mismatch", severity: "warning" },
4746
4249
  alter_column_identity: { type: "identity_mismatch", severity: "warning" },
4250
+ alter_column_auto_increment: {
4251
+ type: "auto_increment_mismatch",
4252
+ severity: "error"
4253
+ },
4747
4254
  // Comments
4748
4255
  add_comment: { type: "comment_mismatch", severity: "info" },
4749
4256
  drop_comment: { type: "comment_mismatch", severity: "info" },
@@ -4856,7 +4363,7 @@ function formatVerifyResult(result) {
4856
4363
  }
4857
4364
 
4858
4365
  // src/commands/verify.ts
4859
- var verifyCommand = new Command11("verify").description("Compare schema vs real database (drift detection)").option(
4366
+ var verifyCommand = new Command12("verify").description("Compare schema vs real database (drift detection)").option(
4860
4367
  "-s, --schema <path>",
4861
4368
  "Path to schema file (default: dbsp.schema.ts)"
4862
4369
  ).requiredOption("-d, --db <url>", "Database connection URL (required)").option("--schema-name <name>", "Database schema name (default: public)").option("--json", "Output as JSON").action(
@@ -4874,14 +4381,18 @@ var verifyCommand = new Command11("verify").description("Compare schema vs real
4874
4381
  try {
4875
4382
  const loaded = await loadSchema(schemaPath);
4876
4383
  const schemaModel = loaded.model;
4384
+ const physical = createPgPhysicalModel5({
4385
+ mode: "logical",
4386
+ model: schemaModel,
4387
+ schema: options.schemaName ?? "public",
4388
+ ...loaded.dbCasing === void 0 ? {} : { dbCasing: loaded.dbCasing }
4389
+ });
4877
4390
  const { pool } = await createDbConnection(options.db);
4878
4391
  try {
4879
- const adapter = createPgsqlAdapter4(pool);
4880
- const diff = await comparePgsqlDatabaseSchema2(adapter, schemaModel, {
4881
- ...options.schemaName ? { schema: options.schemaName } : {},
4882
- ...loaded.dbCasing !== void 0 ? { dbCasing: loaded.dbCasing } : {},
4392
+ const adapter = createPgsqlAdapter3(pool);
4393
+ const diff = await comparePgsqlDatabaseSchema2(adapter, physical, {
4883
4394
  onExpressionCanonicalizationWarning: (warning) => console.warn(
4884
- `\u26A0\uFE0F [${warning.kind} ${escapeDiagnosticText8(warning.table)}.${escapeDiagnosticText8(warning.name)}] ${warning.message}`
4395
+ `\u26A0\uFE0F [${warning.kind} ${escapeDiagnosticText9(warning.table)}.${escapeDiagnosticText9(warning.name)}] ${warning.message}`
4885
4396
  )
4886
4397
  });
4887
4398
  const dbModel = await adapter.introspect({
@@ -4918,12 +4429,13 @@ var verifyCommand = new Command11("verify").description("Compare schema vs real
4918
4429
  );
4919
4430
 
4920
4431
  // src/index.ts
4921
- var program = new Command12();
4432
+ var program = new Command13();
4922
4433
  var packageJson = createRequire(import.meta.url)("../package.json");
4923
4434
  var jsonFormatCommands = /* @__PURE__ */ new Set([
4924
4435
  "apply",
4925
4436
  "inspect",
4926
4437
  "plan",
4438
+ "migrate",
4927
4439
  "recover",
4928
4440
  "reconcile",
4929
4441
  "release"
@@ -4951,6 +4463,7 @@ program.name("dbsp").description("Schema-first code generation for db-semantic-p
4951
4463
  program.addCommand(generateCommand);
4952
4464
  program.addCommand(introspectCommand);
4953
4465
  program.addCommand(planCommand);
4466
+ program.addCommand(migrateCommand);
4954
4467
  program.addCommand(preflightCommand);
4955
4468
  program.addCommand(applyCommand);
4956
4469
  program.addCommand(inspectCommand);