@dbsp/cli 3.0.1 → 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,7 +15,7 @@ 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";
@@ -24,30 +24,31 @@ 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,
32
- readPgLedgerAddressChain as readPgLedgerAddressChain2,
29
+ executionIdsForRun,
30
+ readPgLedgerAddressChain,
33
31
  readPgLedgerReservationsForExecution,
34
32
  readTransitionJournal,
35
33
  TransitionRunIdentityMismatchError,
36
- validatePgManagedLedgerCurrency,
37
34
  withPgTransitionRunLock
38
35
  } from "@dbsp/adapter-pgsql";
39
- 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";
40
43
  import {
41
44
  acquireExclusiveTransitionLease,
42
45
  acquireTransitionLease as acquireTransitionLease3,
43
- createApplier,
44
- createPackRegistry as createPackRegistry2,
45
46
  selectorMatchesResource,
46
47
  transitionPlanDigest as transitionPlanDigest2,
47
- validateNormalizedManagedStepManifest as validateNormalizedManagedStepManifest3
48
+ validateNormalizedManagedStepManifest as validateNormalizedManagedStepManifest2
48
49
  } from "@dbsp/core";
49
50
  import { mintDurablyLoadedRun } from "@dbsp/core/internal";
50
- import { ledgerAddressKey as ledgerAddressKey2, REFUSAL_VOCABULARY as REFUSAL_VOCABULARY2 } from "@dbsp/types";
51
+ import { ledgerAddressKey, REFUSAL_VOCABULARY as REFUSAL_VOCABULARY2 } from "@dbsp/types";
51
52
  import { Command as Command2 } from "commander";
52
53
 
53
54
  // src/utils/output.ts
@@ -61,907 +62,47 @@ function printCliJson(value) {
61
62
  console.log(serializeCliJson(value));
62
63
  }
63
64
 
64
- // src/commands/execution-ids.ts
65
- function executionIdsForRun(journal) {
66
- const executionIds = /* @__PURE__ */ new Set([journal.run.runId]);
67
- for (const event of journal.events) {
68
- const record2 = event.record;
69
- if (event.event === "intent" && "executionId" in record2 && typeof record2.executionId === "string")
70
- executionIds.add(record2.executionId);
71
- if (event.event === "observed" && "intent" in record2 && record2.intent && typeof record2.intent.executionId === "string")
72
- executionIds.add(record2.intent.executionId);
73
- }
74
- if ("generator" in journal.plan)
75
- executionIds.add(`dbsp.generator.execution.${journal.run.runId}`);
76
- return [...executionIds];
77
- }
78
-
79
- // src/commands/generator-execution.ts
80
- import { randomUUID } from "crypto";
81
- import { isDeepStrictEqual } from "util";
82
- import {
83
- assertGeneratedPostconditionSession,
84
- compareSchemata,
85
- createPgsqlAdapter,
86
- decodeGeneratedPostconditionPayload,
87
- executePgAdmittedOperation,
88
- executePgDeclaredAdoption,
89
- executePgPersistedTableReaddress,
90
- preflightPgDeclaredAdoption,
91
- readPgCatalogueIdentity,
92
- readPgLedgerAddressChain,
93
- readPgLedgerScopeCurrency,
94
- readPgRemovalEffectsClosure,
95
- toGeneratedPostconditionBindingAddress,
96
- verifyGeneratedCheckPostcondition,
97
- verifyGeneratedColumnPostcondition,
98
- verifyGeneratedIdentityPostcondition,
99
- verifyGeneratedIndexPostcondition,
100
- verifyGeneratedTablePostcondition,
101
- withGeneratedPostconditionSession
102
- } from "@dbsp/adapter-pgsql";
103
- import {
104
- canonicalJson,
105
- canonicalJsonDigest,
106
- outcomeClaimEventId,
107
- outcomeClaimId,
108
- projectLedgerChain,
109
- validateNormalizedManagedStepManifest
110
- } from "@dbsp/core";
111
- import { decideDestructiveDecision } from "@dbsp/core/internal";
112
- import { ledgerAddressKey } from "@dbsp/types";
113
- function managedSteps(manifest) {
114
- return manifest.steps;
115
- }
116
- function matchesReviewedReplacementSelector(reviewed, provided) {
117
- return provided === reviewed || reviewed.startsWith("table:") && provided === reviewed.slice("table:".length);
118
- }
119
- function modelForAdoption(table) {
120
- const tables = /* @__PURE__ */ new Map([[table.name, table]]);
121
- const relations = /* @__PURE__ */ new Map();
122
- return {
123
- tables,
124
- relations,
125
- getTable: (name) => tables.get(name),
126
- getRelation: (name) => relations.get(name),
127
- getRelationsFrom: () => [],
128
- getRelationsTo: () => [],
129
- isAmbiguous: () => ({ ambiguous: false, options: [] })
130
- };
131
- }
132
- async function adoptionShapeMatches(pool, schema, shape) {
133
- const live = await createPgsqlAdapter(pool).introspect({ schema });
134
- const diff = compareSchemata(modelForAdoption(shape), live);
135
- return !diff.changes.some((change) => change.table === shape.name);
136
- }
137
- var SYSTEM_LEDGER_SCHEMAS = /* @__PURE__ */ new Set([
138
- "pg_toast",
139
- "pg_catalog",
140
- "information_schema"
141
- ]);
142
- var SystemSchemaLedgerHomeError = class extends Error {
143
- address;
144
- constructor(address) {
145
- super(
146
- `SystemSchemaLedgerHomeError: ledger home is unavailable for system schema ${address.schema}: ${ledgerAddressKey(address)}`
147
- );
148
- this.name = "SystemSchemaLedgerHomeError";
149
- this.address = address;
150
- }
151
- };
152
- function home(address) {
153
- if (address.scope !== "database" && address.schema && SYSTEM_LEDGER_SCHEMAS.has(address.schema))
154
- throw new SystemSchemaLedgerHomeError(address);
155
- return address.scope === "database" ? { scope: "database" } : address.schema ? { scope: "schema", schema: address.schema } : (() => {
156
- throw new Error(
157
- `schema-scoped generated address ${address.name} has no schema ledger`
158
- );
159
- })();
160
- }
161
- function acceptance(planDigest, approval) {
162
- return approval.approvals.some(
163
- (grant) => grant.class === `destructive-plan-accepted:${planDigest}`
164
- ) === true ? "destructive-plan-accepted" : "absent";
165
- }
166
- function withheldDestructiveAuthority(evidence) {
167
- if (evidence.declaration !== "requires-removal" && evidence.declaration !== "requires-lossy-change" && evidence.declaration !== "replacement-requested-by-plan")
168
- return "destructive declaration authority";
169
- if (evidence.ownership !== "managed-by-me")
170
- return "destructive ownership authority";
171
- if (evidence.catalogueIdentity !== "matches-recorded")
172
- return "destructive catalogue identity authority";
173
- if (evidence.operatorAcceptance !== "destructive-plan-accepted")
174
- return "destructive operator acceptance authority";
175
- if (evidence.containment !== void 0 && evidence.containment !== "all-contained-or-managed")
176
- return "destructive containment authority";
177
- if (evidence.ledgerLineage !== "matches-database")
178
- return "destructive ledger lineage authority";
179
- return void 0;
180
- }
181
- function withheldDestructiveAuthorityFromReason(reason) {
182
- if (reason.includes("operator acceptance"))
183
- return "destructive operator acceptance authority";
184
- if (reason.includes("ledger lineage"))
185
- return "destructive ledger lineage authority";
186
- return void 0;
187
- }
188
- async function databaseId(pool) {
189
- const result = await pool.query("SELECT current_database() AS database_id");
190
- const database = result.rows[0]?.database_id;
191
- if (typeof database !== "string" || database.length === 0)
192
- throw new Error(
193
- "schema-differ generator could not read current database identity"
194
- );
195
- return database;
196
- }
197
- async function managed(executor, address) {
198
- const chain = await readPgLedgerAddressChain(
199
- executor,
200
- home(address),
201
- address
202
- );
203
- const projection = projectLedgerChain(chain);
204
- return projection.kind === "projected-ledger-chain" && projection.stableState === "managed";
205
- }
206
- async function alreadyAppliedCreation(executor, address) {
207
- const live = await readPgCatalogueIdentity(executor, address);
208
- if (!live?.catalogueIdentity) return false;
209
- const chain = await readPgLedgerAddressChain(
210
- executor,
211
- home(address),
212
- address
213
- );
214
- const projection = projectLedgerChain(chain);
215
- const recorded = chain.terminalMember?.catalogueIdentity;
216
- return projection.kind === "projected-ledger-chain" && projection.stableState === "managed" && recorded !== void 0 && isDeepStrictEqual(recorded, live.catalogueIdentity);
217
- }
218
- function observed(address) {
219
- return {
220
- value: { kind: address.kind, name: address.name },
221
- digest: `generator:${address.kind}:${address.name}`
222
- };
223
- }
224
- function generatedPayload(value) {
225
- const encoded = canonicalJson(value);
226
- const normalized = JSON.parse(encoded);
227
- return {
228
- value: normalized,
229
- digest: canonicalJsonDigest(normalized),
230
- payloadKind: "generated-structural-observation"
231
- };
232
- }
233
- async function identityObserved(executor, postcondition, address, kind) {
234
- const verified = await verifyGeneratedIdentityPostcondition({
235
- session: executor,
236
- postcondition,
237
- address,
238
- kind
239
- });
240
- const value = JSON.parse(
241
- canonicalJson({
242
- kind: "identity-observed",
243
- observedKind: verified.kind,
244
- address: {
245
- scope: address.scope,
246
- ...address.schema === void 0 ? {} : { schema: address.schema },
247
- name: address.name
248
- },
249
- identity: verified.identity,
250
- structuralSemantics: "unverified"
251
- })
252
- );
253
- return {
254
- catalogueIdentity: verified.catalogueIdentity,
255
- observed: {
256
- value,
257
- digest: canonicalJsonDigest(value),
258
- payloadKind: "generated-identity-observation"
259
- }
260
- };
261
- }
262
- function generatedPostcondition(step, address) {
263
- const declaration = step.expectedDeclaration;
264
- if (!declaration?.value || typeof declaration.value !== "object" || Array.isArray(declaration.value))
265
- throw new Error(
266
- `generated ${address.kind} step ${step.stepKey} has no decodable generated declaration`
267
- );
268
- return decodeGeneratedPostconditionPayload(declaration, step.stepKey);
269
- }
270
- async function readGeneratedV3Postcondition(executor, postcondition, address) {
271
- const bindingAddress = toGeneratedPostconditionBindingAddress(address);
272
- switch (postcondition.declaration.kind) {
273
- case "column": {
274
- const verified = await verifyGeneratedColumnPostcondition({
275
- session: executor,
276
- postcondition,
277
- address: bindingAddress
278
- });
279
- return {
280
- catalogueIdentity: verified.catalogueIdentity,
281
- observed: generatedPayload({
282
- kind: "column",
283
- type: verified.projection.type,
284
- nullable: verified.projection.nullable,
285
- ...verified.projection.default === void 0 ? {} : { default: verified.projection.default },
286
- ...verified.projection.collation === void 0 ? {} : { collation: verified.projection.collation },
287
- ...verified.projection.identity === void 0 ? {} : { identity: verified.projection.identity }
288
- })
289
- };
290
- }
291
- case "check": {
292
- const verified = await verifyGeneratedCheckPostcondition({
293
- session: executor,
294
- postcondition,
295
- address: bindingAddress
296
- });
297
- return {
298
- catalogueIdentity: verified.catalogueIdentity,
299
- observed: generatedPayload({
300
- kind: verified.kind,
301
- type: "c",
302
- expression: verified.projection.expression,
303
- validated: verified.projection.validated,
304
- noInherit: verified.projection.noInherit,
305
- enforced: verified.projection.enforced,
306
- isLocal: verified.projection.isLocal,
307
- inheritanceCount: verified.projection.inheritanceCount,
308
- parentId: verified.projection.parentId
309
- })
310
- };
311
- }
312
- case "constraint": {
313
- return identityObserved(
314
- executor,
315
- postcondition,
316
- bindingAddress,
317
- "constraint"
318
- );
319
- }
320
- case "index": {
321
- const verified = await verifyGeneratedIndexPostcondition({
322
- session: executor,
323
- postcondition,
324
- address: bindingAddress
325
- });
326
- return {
327
- catalogueIdentity: verified.catalogueIdentity,
328
- observed: generatedPayload({
329
- kind: verified.kind,
330
- projection: verified.projection
331
- })
332
- };
333
- }
334
- case "table": {
335
- const verified = await verifyGeneratedTablePostcondition({
336
- session: executor,
337
- postcondition,
338
- address: bindingAddress
339
- });
340
- return {
341
- catalogueIdentity: verified.catalogueIdentity,
342
- observed: generatedPayload({
343
- kind: verified.kind,
344
- columns: verified.projection.columns.map((column) => ({
345
- name: column.name,
346
- type: column.type,
347
- nullable: column.nullable,
348
- ...column.default === void 0 ? {} : { default: column.default },
349
- ...column.collation === void 0 ? {} : { collation: column.collation },
350
- ...column.identity === void 0 ? {} : { identity: column.identity }
351
- }))
352
- })
353
- };
354
- }
355
- case "enum": {
356
- return identityObserved(executor, postcondition, bindingAddress, "enum");
357
- }
358
- case "sequence": {
359
- return identityObserved(
360
- executor,
361
- postcondition,
362
- bindingAddress,
363
- "sequence"
364
- );
365
- }
366
- case "extension": {
367
- return identityObserved(
368
- executor,
369
- postcondition,
370
- bindingAddress,
371
- "extension"
372
- );
373
- }
374
- case "absent": {
375
- const live = await readPgCatalogueIdentity(executor, address);
376
- if (live)
377
- throw new Error(
378
- `generated ${address.kind} absence postcondition differs: ${address.name} is still present`
379
- );
380
- return { observed: generatedPayload({ kind: "absent" }) };
381
- }
382
- default:
383
- throw new Error("generated v3 postcondition has no declarable read-back");
384
- }
385
- }
386
- async function readGeneratedPostcondition(executor, step, address) {
387
- executor = assertGeneratedPostconditionSession(executor);
388
- const decodedPostcondition = generatedPostcondition(step, address);
389
- if (decodedPostcondition.postconditionVersion === 3)
390
- return (await readGeneratedV3Postcondition(
391
- executor,
392
- decodedPostcondition,
393
- address
394
- )).observed;
395
- throw new Error(
396
- "generated postcondition decoder returned no supported version"
397
- );
398
- }
399
- function containedBy(root, candidate) {
400
- for (let parent = candidate.parent; parent; parent = parent.parent) {
401
- if (parent.engine === root.engine && parent.database === root.database && parent.schema === root.schema && parent.kind === root.kind && parent.name === root.name)
402
- return true;
403
- }
404
- return false;
405
- }
406
- async function destructiveEvidence(input) {
407
- const { executor, address } = input;
408
- let ownership = "uncomputable";
409
- let catalogueIdentity = "catalogue-unavailable";
410
- let ledgerLineage = "unreadable";
411
- try {
412
- const chain = await readPgLedgerAddressChain(
413
- executor,
414
- home(address),
415
- address
416
- );
417
- const projection = projectLedgerChain(chain);
418
- ownership = projection.kind === "projected-ledger-chain" ? projection.stableState === "managed" ? "managed-by-me" : projection.stableState === "unknown" ? "unknown" : "blocked" : "uncomputable";
419
- const live = await readPgCatalogueIdentity(executor, address);
420
- const recorded = chain.terminalMember?.catalogueIdentity;
421
- catalogueIdentity = !live ? "object-absent" : recorded === void 0 ? "differs" : isDeepStrictEqual(live.catalogueIdentity, recorded) ? "matches-recorded" : "differs";
422
- const currency = await readPgLedgerScopeCurrency(executor, home(address));
423
- ledgerLineage = currency.kind === "current" ? "matches-database" : "differs";
424
- } catch {
425
- }
426
- let containment;
427
- let containmentOutcome;
428
- if (input.classification === "removal") {
429
- containment = await readPgRemovalEffectsClosure({
430
- executor,
431
- root: address,
432
- isManaged: (candidate) => managed(executor, candidate)
433
- });
434
- containmentOutcome = containment.kind;
435
- }
436
- return {
437
- evidence: {
438
- declaration: input.classification === "removal" ? input.selection?.kind === "replacement" ? "replacement-requested-by-plan" : "requires-removal" : "requires-lossy-change",
439
- ...input.selection?.kind === "replacement" ? { replacementAddress: address } : {},
440
- ownership,
441
- catalogueIdentity,
442
- operatorAcceptance: acceptance(input.planDigest, input.approval),
443
- ...containmentOutcome === void 0 ? {} : { containment: containmentOutcome },
444
- ...containment?.kind === "reaches-unmanaged" ? { containmentUnmanaged: containment.unmanaged } : {},
445
- ...containment?.kind === "undecidable" ? { containmentReason: containment.reason } : {},
446
- ledgerLineage
447
- },
448
- ...containment === void 0 ? {} : { containment }
449
- };
450
- }
451
- async function removalContainment(executor, address) {
452
- return readPgRemovalEffectsClosure({
453
- executor,
454
- root: address,
455
- isManaged: (candidate) => managed(executor, candidate)
456
- });
457
- }
458
- async function executeGeneratorPlan(input) {
459
- const validation = input.manifest ? { ok: true, manifest: input.manifest } : validateNormalizedManagedStepManifest(
460
- input.plan?.steps ?? []
461
- );
462
- if (!validation.ok)
463
- return { outcome: "execution-failed", detail: validation.detail };
464
- const manifest = validation.manifest;
465
- const approval = input.approval ?? {
466
- approvals: (input.accepts ?? []).map((value) => ({ class: value }))
467
- };
468
- const completedStepKeys = [];
469
- let destructiveRefusal;
470
- let destructiveClosureReplanRequired = false;
471
- const partial = (detail) => completedStepKeys.length === 0 ? { outcome: "execution-failed", detail } : {
472
- outcome: "partially-applied",
473
- detail,
474
- completedStepKeys,
475
- notStartedStepKeys: managedSteps(manifest).filter((step) => !completedStepKeys.includes(step.stepKey)).map((step) => step.stepKey)
476
- };
477
- try {
478
- const database = await databaseId(input.pool);
479
- const executionId = `dbsp.generator.execution.${randomUUID()}`;
480
- await input.recordAttempt(executionId);
481
- const steps = managedSteps(manifest);
482
- for (const step of steps) {
483
- if (step.lifecycle?.kind === "adoption-refused")
484
- return {
485
- outcome: "adoption-refused",
486
- detail: `declared adoption for ${step.address?.name ?? step.stepKey} refuses live shape mismatch`
487
- };
488
- const lifecycle = step.lifecycle;
489
- if (lifecycle?.kind !== "adoption") continue;
490
- const address = step.address;
491
- if (!address || !step.expectedDeclaration || !step.expectedCatalogueIdentity)
492
- return {
493
- outcome: "execution-failed",
494
- detail: `adoption step ${step.stepKey} has incomplete normalized material`
495
- };
496
- const preflight = await preflightPgDeclaredAdoption({
497
- executor: input.pool,
498
- home: home(address),
499
- address,
500
- declaration: step.expectedDeclaration,
501
- expectedCatalogueIdentity: step.expectedCatalogueIdentity,
502
- shapeMatches: () => adoptionShapeMatches(input.pool, input.schema, lifecycle.shape)
503
- });
504
- if (preflight.outcome !== "ready" && preflight.outcome !== "no-op")
505
- return preflight.outcome === "adoption-refused" ? { outcome: "adoption-refused", detail: preflight.detail } : { outcome: "execution-failed", detail: preflight.detail };
506
- }
507
- const replacementSelectors = steps.filter((step) => step.selection?.kind === "replacement").map((step) => step.selection?.selector).filter((selector) => selector !== void 0);
508
- if (replacementSelectors.length > 0 && !input.replaces?.length)
509
- return {
510
- outcome: "destructive-authority-refused",
511
- detail: "replacement requires a named --replace selector from the reviewed plan",
512
- refusal: { withheldAuthority: "destructive declaration authority" }
513
- };
514
- for (const selector of input.replaces ?? [])
515
- if (!replacementSelectors.some(
516
- (reviewed) => matchesReviewedReplacementSelector(reviewed, selector)
517
- ))
518
- return {
519
- outcome: "destructive-authority-refused",
520
- detail: `replacement ${selector} was not requested by the reviewed plan`,
521
- refusal: { withheldAuthority: "destructive declaration authority" }
522
- };
523
- if (replacementSelectors.some(
524
- (reviewed) => !input.replaces?.some(
525
- (provided) => matchesReviewedReplacementSelector(reviewed, provided)
526
- )
527
- ))
528
- return {
529
- outcome: "selection-incomplete",
530
- detail: "replacement selection does not cover every reviewed replacement"
531
- };
532
- for (const step of steps) {
533
- if (step.lifecycle?.kind === "adoption-refused") continue;
534
- if (step.lifecycle?.kind === "adoption") {
535
- const lifecycle = step.lifecycle;
536
- const address2 = step.address;
537
- if (!address2 || !step.expectedDeclaration || !step.expectedCatalogueIdentity)
538
- return {
539
- outcome: "execution-failed",
540
- detail: `adoption step ${step.stepKey} has incomplete normalized material`
541
- };
542
- const adopted = await executePgDeclaredAdoption({
543
- executor: input.pool,
544
- run: input.run,
545
- manifest,
546
- recomputedPlanDigest: input.planDigest,
547
- approval,
548
- executionId,
549
- step,
550
- home: home(address2),
551
- address: address2,
552
- declaration: step.expectedDeclaration,
553
- expectedCatalogueIdentity: step.expectedCatalogueIdentity,
554
- shapeMatches: () => adoptionShapeMatches(input.pool, input.schema, lifecycle.shape),
555
- ...input.observer === void 0 ? {} : { observer: input.observer }
556
- });
557
- if (adopted.outcome === "completed" || adopted.outcome === "no-op") {
558
- completedStepKeys.push(step.stepKey);
559
- continue;
560
- }
561
- if (adopted.outcome === "adoption-refused")
562
- return { outcome: "adoption-refused", detail: adopted.detail };
563
- if (adopted.outcome === "recovery-required") return adopted;
564
- if (adopted.outcome === "transport-ambiguous") return adopted;
565
- return { outcome: "execution-failed", detail: adopted.detail };
566
- }
567
- if (step.lifecycle?.kind === "readdress") {
568
- const result = await executePgPersistedTableReaddress({
569
- executor: input.pool,
570
- run: input.run,
571
- manifest,
572
- recomputedPlanDigest: input.planDigest,
573
- approval,
574
- executionId,
575
- step,
576
- database,
577
- targetSchema: input.schema,
578
- ...input.observer === void 0 ? {} : { observer: input.observer }
579
- });
580
- if (result.outcome === "completed" || result.outcome === "no-op") {
581
- completedStepKeys.push(step.stepKey);
582
- continue;
583
- }
584
- return result;
585
- }
586
- if (step.statementBundle.statements.length === 0) {
587
- completedStepKeys.push(step.stepKey);
588
- continue;
589
- }
590
- const address = step.address ?? step.closure?.root;
591
- if (!address)
592
- return {
593
- outcome: "execution-failed",
594
- detail: `managed step ${step.stepKey} has no address`
595
- };
596
- const plannedClaimKey = step.plannedClaimKeys[0];
597
- if (!plannedClaimKey)
598
- return {
599
- outcome: "execution-failed",
600
- detail: `managed step ${step.stepKey} has no planned claim key`
601
- };
602
- if (step.classification === "non-destructive" && step.requiresVacancy && await alreadyAppliedCreation(input.pool, address)) {
603
- completedStepKeys.push(step.stepKey);
604
- continue;
605
- }
606
- const claimKind = step.claimKind;
607
- const rootClaimId = outcomeClaimId(executionId, plannedClaimKey, address);
608
- const claim = {
609
- claimId: rootClaimId,
610
- claimSpecies: "sql-bearing",
611
- executionId,
612
- plannedClaimKey,
613
- claimGroupId: rootClaimId,
614
- rootClaimId,
615
- address,
616
- claimKind,
617
- statementBundle: step.statementBundle,
618
- requiresVacancy: step.requiresVacancy
619
- };
620
- const baseReservation = {
621
- address,
622
- claimKind,
623
- executionId,
624
- rootClaimId: claim.claimId,
625
- homeLedger: home(address)
626
- };
627
- if (step.classification === "non-destructive") {
628
- const result = await executePgAdmittedOperation(input.pool, {
629
- run: input.run,
630
- approval,
631
- manifest,
632
- recomputedPlanDigest: input.planDigest,
633
- operation: {
634
- kind: "single-outcome",
635
- request: {
636
- plan: claim,
637
- reservations: [baseReservation],
638
- resolution: {
639
- eventId: outcomeClaimEventId(claim.claimId, "observed"),
640
- eventKind: "observed"
641
- },
642
- // Transactional DDL is visible only on the admitted session until
643
- // its terminal ledger fact commits with it.
644
- readBack: async (session) => readGeneratedPostcondition(session, step, address),
645
- recordCatalogueIdentity: true,
646
- ...input.observer === void 0 ? {} : { observer: input.observer },
647
- vacancy: async (executor) => await readPgCatalogueIdentity(executor, address) ? {
648
- kind: "occupied",
649
- reason: `creation claim ${claim.claimId} refuses occupied live address ${address.name}`
650
- } : { kind: "vacant" }
651
- }
652
- }
653
- });
654
- if (result.kind === "outcome-recovery-required")
655
- return {
656
- outcome: "recovery-required",
657
- claimId: result.claimId,
658
- detail: `claim ${result.claimId} remains open and requires recovery: ${result.reason}`
659
- };
660
- if (result.kind === "outcome-transport-ambiguous")
661
- return { outcome: "transport-ambiguous", detail: result.reason };
662
- if ("reason" in result) return partial(result.reason);
663
- completedStepKeys.push(step.stepKey);
664
- continue;
665
- }
666
- const containment = step.classification === "removal" ? await removalContainment(input.pool, address) : void 0;
667
- const closureMembers = /* @__PURE__ */ new Map();
668
- for (const member of step.closure?.members ?? []) {
669
- closureMembers.set(ledgerAddressKey(member.address), {
670
- address: member.address,
671
- plannedClaimKey: member.plannedClaimKey
672
- });
673
- }
674
- if (containment?.kind === "all-contained-or-managed") {
675
- for (const effect of containment.effects) {
676
- if (effect.internalOwned) continue;
677
- if (!containedBy(address, effect.address)) continue;
678
- if (!await managed(input.pool, effect.address)) continue;
679
- const key = ledgerAddressKey(effect.address);
680
- if (closureMembers.has(key)) continue;
681
- closureMembers.set(key, {
682
- address: effect.address,
683
- plannedClaimKey: `closure:${effect.address.kind}:${effect.address.name}`
684
- });
685
- }
686
- }
687
- const containedClaims = [];
688
- for (const member of closureMembers.values()) {
689
- const effect = member.address;
690
- const childClaimId = outcomeClaimId(
691
- executionId,
692
- plannedClaimKey,
693
- effect,
694
- member.plannedClaimKey
695
- );
696
- const childReservation = {
697
- ...baseReservation,
698
- address: effect,
699
- rootClaimId: claim.claimId
700
- };
701
- containedClaims.push({
702
- plan: {
703
- ...claim,
704
- claimId: childClaimId,
705
- claimSpecies: "cascade-covered",
706
- address: effect,
707
- plannedClaimKey: member.plannedClaimKey,
708
- // Each member contributes only its terminal absence fact; SQL is
709
- // exclusively carried by the destructive root manifest claim.
710
- statementBundle: { statements: [] }
711
- },
712
- reservation: childReservation
713
- });
714
- }
715
- const admitRequest = {
716
- plan: claim,
717
- reservations: [baseReservation],
718
- members: containedClaims.map(({ plan, reservation }) => ({
719
- plan,
720
- reservations: [reservation]
721
- })),
722
- destructiveDecision: async (executor) => {
723
- const lockedAuthority = await destructiveEvidence({
724
- executor,
725
- address,
726
- classification: step.classification,
727
- selection: step.selection,
728
- planDigest: input.planDigest,
729
- approval
730
- });
731
- if (step.classification === "removal" && (containment?.kind !== "all-contained-or-managed" || lockedAuthority.containment?.kind !== "all-contained-or-managed" || containment.closureDigest !== lockedAuthority.containment.closureDigest)) {
732
- destructiveClosureReplanRequired = true;
733
- const decision2 = decideDestructiveDecision(
734
- { kind: "removal", address },
735
- { ...lockedAuthority.evidence, containment: "undecidable" }
736
- );
737
- if (decision2.kind === "destructive-decision-refused")
738
- destructiveRefusal = {
739
- withheldAuthority: withheldDestructiveAuthority({
740
- ...lockedAuthority.evidence,
741
- containment: "undecidable"
742
- }) ?? "destructive containment authority"
743
- };
744
- return decision2;
745
- }
746
- const decision = decideDestructiveDecision(
747
- {
748
- kind: step.classification === "removal" ? "removal" : "data-destructive",
749
- address,
750
- ...step.classification === "removal" && lockedAuthority.containment?.kind === "all-contained-or-managed" ? { closureDigest: lockedAuthority.containment.closureDigest } : {}
751
- },
752
- lockedAuthority.evidence
753
- );
754
- if (decision.kind === "destructive-decision-refused")
755
- destructiveRefusal = {
756
- withheldAuthority: withheldDestructiveAuthority(lockedAuthority.evidence) ?? "destructive authority"
757
- };
758
- return decision;
759
- },
760
- ...input.observer === void 0 ? {} : { observer: input.observer }
761
- };
762
- const executed = await executePgAdmittedOperation(input.pool, {
763
- run: input.run,
764
- approval,
765
- manifest,
766
- recomputedPlanDigest: input.planDigest,
767
- operation: {
768
- kind: "destructive-outcome",
769
- request: admitRequest,
770
- readBackAndResolve: async (session) => {
771
- const declarationReadBack = step.classification === "data-destructive" && step.expectedDeclaration !== void 0 ? await withGeneratedPostconditionSession(
772
- {
773
- connect: async () => ({
774
- query: session.query.bind(session),
775
- release: () => void 0
776
- })
777
- },
778
- (proof) => {
779
- const postcondition = generatedPostcondition(
780
- step,
781
- address
782
- );
783
- if (postcondition.postconditionVersion !== 3)
784
- throw new Error(
785
- "generated postcondition decoder returned no supported version"
786
- );
787
- return readGeneratedV3Postcondition(
788
- proof,
789
- postcondition,
790
- address
791
- );
792
- }
793
- ) : void 0;
794
- const live = step.classification === "removal" ? await readPgCatalogueIdentity(session, address) : void 0;
795
- const terminalCatalogueIdentity = step.classification === "removal" ? live?.catalogueIdentity : declarationReadBack?.catalogueIdentity;
796
- const survivors = step.classification === "removal" && live ? [address] : [];
797
- const childLives = await Promise.all(
798
- containedClaims.map(async (child) => ({
799
- child,
800
- live: await readPgCatalogueIdentity(
801
- session,
802
- child.plan.address
803
- )
804
- }))
805
- );
806
- for (const { child, live: childLive } of childLives)
807
- if (childLive) survivors.push(child.plan.address);
808
- if (survivors.length > 0) {
809
- const survivorNames = survivors.map(
810
- (survivor) => `${survivor.kind} ${survivor.schema ?? "<database>"}.${survivor.name}`
811
- );
812
- const survivorValue = { survivors: survivorNames };
813
- const survivorObservation = {
814
- value: survivorValue,
815
- digest: canonicalJsonDigest(survivorValue)
816
- };
817
- return {
818
- rootClaimId: claim.claimId,
819
- members: [
820
- {
821
- target: home(address),
822
- member: {
823
- eventId: outcomeClaimEventId(
824
- claim.claimId,
825
- "indeterminate"
826
- ),
827
- executionId,
828
- plannedClaimKey: claim.plannedClaimKey,
829
- claimGroupId: claim.claimGroupId,
830
- rootClaimId: claim.rootClaimId,
831
- address,
832
- eventKind: "indeterminate",
833
- observed: survivorObservation
834
- }
835
- },
836
- ...containedClaims.map((child) => ({
837
- target: home(child.plan.address),
838
- member: {
839
- eventId: outcomeClaimEventId(
840
- child.plan.claimId,
841
- "indeterminate"
842
- ),
843
- executionId,
844
- plannedClaimKey: child.plan.plannedClaimKey,
845
- claimGroupId: claim.claimId,
846
- rootClaimId: claim.claimId,
847
- address: child.plan.address,
848
- eventKind: "indeterminate",
849
- observed: survivorObservation
850
- }
851
- }))
852
- ],
853
- reservations: [
854
- baseReservation,
855
- ...containedClaims.map(({ reservation }) => reservation)
856
- ]
857
- };
858
- }
859
- const terminals = [
860
- {
861
- target: home(address),
862
- member: {
863
- eventId: outcomeClaimEventId(
864
- claim.claimId,
865
- step.classification === "removal" ? "absent" : "observed"
866
- ),
867
- executionId,
868
- plannedClaimKey: claim.plannedClaimKey,
869
- claimGroupId: claim.claimGroupId,
870
- rootClaimId: claim.rootClaimId,
871
- address,
872
- eventKind: step.classification === "removal" ? "absent" : "observed",
873
- // The append protocol reads this address's terminal on its
874
- // pinned transaction session after the post-DDL read-back.
875
- ...terminalCatalogueIdentity === void 0 ? {} : { catalogueIdentity: terminalCatalogueIdentity },
876
- ...step.classification === "removal" ? {} : {
877
- observed: declarationReadBack?.observed ?? observed(address)
878
- }
879
- }
880
- }
881
- ];
882
- for (const child of containedClaims) {
883
- terminals.push({
884
- target: home(child.plan.address),
885
- member: {
886
- eventId: outcomeClaimEventId(child.plan.claimId, "absent"),
887
- executionId,
888
- plannedClaimKey: child.plan.plannedClaimKey,
889
- claimGroupId: claim.claimId,
890
- rootClaimId: claim.claimId,
891
- address: child.plan.address,
892
- eventKind: "absent",
893
- predecessor: child.plan.claimId
894
- }
895
- });
896
- }
897
- return {
898
- rootClaimId: claim.claimId,
899
- members: terminals,
900
- reservations: [
901
- baseReservation,
902
- ...containedClaims.map(({ reservation }) => reservation)
903
- ]
904
- };
905
- }
906
- }
907
- });
908
- if (executed.kind === "outcome-recovery-required")
909
- return {
910
- outcome: "recovery-required",
911
- claimId: executed.claimId,
912
- detail: `claim ${executed.claimId} remains open and requires recovery: ${executed.reason}`
913
- };
914
- if (executed.kind === "outcome-transport-ambiguous")
915
- return { outcome: "transport-ambiguous", detail: executed.reason };
916
- if (executed.kind === "outcome-protocol-pending")
917
- return partial(
918
- `destructive claim ${claim.claimId} remains pending after executing: ${executed.reason}`
919
- );
920
- if (executed.kind !== "executed-destructive-outcome")
921
- return {
922
- outcome: "destructive-authority-refused",
923
- detail: destructiveClosureReplanRequired ? `${executed.reason}; destructive closure changed under lock; replan required` : executed.reason,
924
- ...destructiveRefusal === void 0 && withheldDestructiveAuthorityFromReason(executed.reason) === void 0 ? {} : {
925
- refusal: destructiveRefusal ?? {
926
- withheldAuthority: withheldDestructiveAuthorityFromReason(executed.reason) ?? "destructive authority"
927
- }
928
- }
929
- };
930
- completedStepKeys.push(step.stepKey);
931
- }
932
- return { outcome: "completed" };
933
- } catch (error) {
934
- return partial(error instanceof Error ? error.message : String(error));
935
- }
936
- }
937
-
938
65
  // src/commands/generator-plan.ts
939
- import { randomUUID as randomUUID2 } from "crypto";
66
+ import { randomUUID } from "crypto";
940
67
  import {
941
68
  assertDeclarableChangeKind,
942
69
  classifyGeneratedMutation,
943
70
  comparePgsqlDatabaseSchema,
944
- createPgsqlAdapter as createPgsqlAdapter2,
71
+ createPgPhysicalModel,
72
+ createPgsqlAdapter,
945
73
  createPgsqlGeneratedManagedStep,
946
74
  createPgTransitionLessor,
947
75
  createPgTransitionRunPersister,
948
76
  generatedPostconditionForChange,
949
77
  generateMigrationSQL,
950
- readPgCatalogueIdentity as readPgCatalogueIdentity2,
78
+ ReferencedKeyRemovalError,
79
+ readPgCatalogueIdentity,
951
80
  renderPgTableReaddressStatements
952
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";
953
90
  import {
954
91
  acquireTransitionLease,
955
- canonicalJson as canonicalJson2,
956
- canonicalJsonDigest as canonicalJsonDigest2,
92
+ canonicalJsonDigest,
957
93
  transitionPlanDigest,
958
- validateNormalizedManagedStepManifest as validateNormalizedManagedStepManifest2
94
+ validateNormalizedManagedStepManifest
959
95
  } from "@dbsp/core";
960
- function adoptionDeclaration(table) {
961
- const value = JSON.parse(
962
- canonicalJson2({ kind: "table", name: table.name, shape: table })
963
- );
964
- return { value, digest: canonicalJsonDigest2(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
+ };
965
106
  }
966
107
  function replacementStatements(table, schema) {
967
108
  const quote = (value) => `"${value.replaceAll('"', '""')}"`;
@@ -985,7 +126,7 @@ function replacementStatements(table, schema) {
985
126
  };
986
127
  return {
987
128
  retireStatements: [`DROP TABLE ${quote(schema)}.${quote(table.name)}`],
988
- createStatements: generateMigrationSQL(createDiff, {
129
+ createStatements: generateMigrationSQLForPhysicalDiff(createDiff, {
989
130
  includeDestructive: true,
990
131
  schemaName: schema
991
132
  })
@@ -1104,6 +245,65 @@ function changeTargetsDeclaredTable(change, table) {
1104
245
  "alter_sequence"
1105
246
  ])).has(change.kind);
1106
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
+ }
1107
307
  function requiredAdoptionIdentity(identities, table) {
1108
308
  const identity = identities.get(table);
1109
309
  if (!identity)
@@ -1111,7 +311,7 @@ function requiredAdoptionIdentity(identities, table) {
1111
311
  return identity;
1112
312
  }
1113
313
  function asDurableGeneratorPlan(material, steps) {
1114
- const validation = validateNormalizedManagedStepManifest2(steps);
314
+ const validation = validateNormalizedManagedStepManifest(steps);
1115
315
  if (!validation.ok)
1116
316
  throw new Error(
1117
317
  `generator planning refuses invalid managed-step manifest: ${validation.detail}`
@@ -1146,7 +346,7 @@ function render(plan, planDigest) {
1146
346
  ]
1147
347
  ].join("\n");
1148
348
  }
1149
- async function databaseId2(pool) {
349
+ async function databaseId(pool) {
1150
350
  const result = await pool.query("SELECT current_database() AS database_id");
1151
351
  const value = result.rows[0]?.database_id;
1152
352
  if (typeof value !== "string" || value.length === 0)
@@ -1157,19 +357,39 @@ async function databaseId2(pool) {
1157
357
  }
1158
358
  async function runGeneratorPlan(input) {
1159
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
+ }
1160
383
  const { pool } = await createDbConnection(input.db);
1161
384
  try {
1162
- const schema = input.schema ?? "public";
1163
385
  const diff = await comparePgsqlDatabaseSchema(
1164
- createPgsqlAdapter2(pool),
1165
- loaded.model,
386
+ createPgsqlAdapter(pool),
387
+ physical,
1166
388
  {
1167
- schema,
1168
389
  // `apply --schema` owns one schema. Extensions are database-scoped,
1169
390
  // so an extension not declared by this schema must not become a
1170
391
  // schema-plan removal merely because it is installed in the database.
1171
- ignoreUnmanagedExtensions: true,
1172
- ...loaded.dbCasing ? { dbCasing: loaded.dbCasing } : {}
392
+ ignoreUnmanagedExtensions: true
1173
393
  }
1174
394
  );
1175
395
  const declaredLifecycleWork = [...loaded.model.tables.values()].some(
@@ -1198,39 +418,57 @@ async function runGeneratorPlan(input) {
1198
418
  planDigest: null
1199
419
  };
1200
420
  }
1201
- const adoptionMismatches = new Set(
1202
- [...loaded.model.tables.values()].filter(
1203
- (table) => table.adopt === true && diff.changes.some(
1204
- (change) => changeTargetsDeclaredTable(change, table.name)
1205
- )
1206
- ).map((table) => table.name)
1207
- );
1208
- 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);
1209
436
  const adoptionIdentities = /* @__PURE__ */ new Map();
1210
437
  for (const table of loaded.model.tables.values()) {
1211
- if (table.adopt !== true || adoptionMismatches.has(table.name)) continue;
1212
- 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, {
1213
442
  engine: "postgresql",
1214
443
  database,
1215
444
  schema: input.schema ?? "public",
1216
445
  kind: "table",
1217
- name: table.name
446
+ name: physicalName
1218
447
  });
1219
448
  if (!live?.catalogueIdentity)
1220
449
  throw new Error(
1221
- `declared adoption for ${table.name} refuses absent live identity`
450
+ `declared adoption for ${physicalName} refuses absent live identity`
1222
451
  );
1223
- adoptionIdentities.set(table.name, live.catalogueIdentity);
452
+ adoptionIdentities.set(physicalName, live.catalogueIdentity);
1224
453
  }
1225
454
  const executableDiff = {
1226
455
  ...diff,
1227
456
  changes: diff.changes.filter(
1228
457
  (change) => ![...loaded.model.tables.values()].some(
1229
- (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
+ )
1230
462
  )
1231
463
  )
1232
464
  };
1233
- 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) => ({
1234
472
  kind: change.kind,
1235
473
  table: change.table,
1236
474
  ...change.column ? { column: change.column } : {},
@@ -1241,45 +479,54 @@ async function runGeneratorPlan(input) {
1241
479
  schema,
1242
480
  change.meta.readdress
1243
481
  ) : generateMigrationSQL(
1244
- { ...executableDiff, changes: [change] },
1245
- { includeDestructive: true, schemaName: schema }
482
+ { ...executableDiff, physical: diff.physical, changes: [change] },
483
+ { includeDestructive: true }
1246
484
  ),
1247
485
  ...change.kind === "readdress_table" && change.meta?.readdress ? { readdress: change.meta.readdress } : {}
1248
486
  }));
1249
487
  const material = {
1250
488
  kind: "schema-differ-generator",
1251
489
  planningSchema: schema,
490
+ ...dbCasing === "preserve" ? {} : { dbCasing },
1252
491
  changes: [
1253
492
  ...ordinaryChanges,
1254
- ...[...loaded.model.tables.values()].filter((table) => adoptionMismatches.has(table.name)).map((table) => ({
1255
- kind: "adoption_refused",
1256
- table: table.name,
1257
- classification: "non-destructive",
1258
- details: `Refuse adoption of table "${table.name}": live shape does not match declaration`,
1259
- statements: []
1260
- })),
1261
- // `comparePgsqlDatabaseSchema` has just compared each side through
1262
- // the established live canonicalisation path. An adopted table is
1263
- // admitted only when that comparison has no remaining change for it.
1264
- ...[...loaded.model.tables.values()].filter(
1265
- (table) => table.adopt === true && !diff.changes.some(
1266
- (change) => changeTargetsDeclaredTable(change, table.name)
1267
- )
1268
- ).map((table) => ({
1269
- kind: "adopt_table",
1270
- table: table.name,
1271
- classification: "non-destructive",
1272
- details: `Adopt existing table "${table.name}" after live shape match`,
1273
- statements: [],
1274
- adoption: {
1275
- declaration: adoptionDeclaration(table),
1276
- shape: table,
1277
- catalogueIdentity: requiredAdoptionIdentity(
1278
- adoptionIdentities,
1279
- table.name
1280
- )
1281
- }
1282
- })),
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
+ }),
1283
530
  ...[...loaded.model.tables.values()].filter((table) => table.replace === true).map((table) => ({
1284
531
  kind: "replace_table",
1285
532
  table: table.name,
@@ -1291,7 +538,7 @@ async function runGeneratorPlan(input) {
1291
538
  ]
1292
539
  };
1293
540
  const ordinaryStepSources = [];
1294
- for (const [changeIndex, change] of executableDiff.changes.entries()) {
541
+ for (const [changeIndex, change] of phaseOrderedChanges.entries()) {
1295
542
  const generated = ordinaryChanges[changeIndex];
1296
543
  if (!generated)
1297
544
  throw new Error(
@@ -1408,18 +655,27 @@ async function runGeneratorPlan(input) {
1408
655
  continue;
1409
656
  }
1410
657
  if (change.kind === "adopt_table" && change.adoption) {
1411
- lifecycleSteps.push(
1412
- lifecycleStep({
1413
- ...base,
1414
- stepKey: `generator:${base.order}:adoption`,
1415
- classification: "non-destructive",
1416
- selection: { kind: "adoption", selector: `table:${change.table}` },
1417
- expectedDeclaration: change.adoption.declaration,
1418
- expectedCatalogueIdentity: change.adoption.catalogueIdentity,
1419
- claimKind: "adopt-intent",
1420
- lifecycle: { kind: "adoption", shape: change.adoption.shape }
1421
- })
1422
- );
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
+ });
1423
679
  continue;
1424
680
  }
1425
681
  lifecycleSteps.push(
@@ -1456,13 +712,13 @@ async function runGeneratorPlan(input) {
1456
712
  plan
1457
713
  );
1458
714
  const run = {
1459
- runId: `dbsp-generator-${randomUUID2()}`,
715
+ runId: `dbsp-generator-${randomUUID()}`,
1460
716
  planDigest,
1461
- targetContextDigest: canonicalJsonDigest2({
1462
- database: await databaseId2(pool),
717
+ targetContextDigest: canonicalJsonDigest({
718
+ database: await databaseId(pool),
1463
719
  schema
1464
720
  }),
1465
- databaseId: await databaseId2(pool),
721
+ databaseId: await databaseId(pool),
1466
722
  coreVersion: "schema-differ-generator-v1",
1467
723
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1468
724
  replayability: material.changes.some(
@@ -1501,20 +757,25 @@ async function runGeneratorPlan(input) {
1501
757
  import {
1502
758
  acquirePgTransitionClient,
1503
759
  createPgExecutionContract,
1504
- createPgsqlAdapter as createPgsqlAdapter3,
760
+ createPgPhysicalModel as createPgPhysicalModel2,
761
+ createPgsqlAdapter as createPgsqlAdapter2,
1505
762
  createPgTransitionLessor as createPgTransitionLessor2,
1506
763
  createPgTransitionPack,
1507
764
  createPgTransitionRunPersister as createPgTransitionRunPersister2,
1508
765
  ensureTransitionJournal,
1509
766
  escapeDiagnosticText,
1510
- getNamingPluginForDbCasing,
1511
767
  PgExecutionContractDerivationError,
1512
768
  pgTargetIdentityMismatch,
1513
769
  readPgExecutionTargetFromClient,
1514
770
  readPgObservationContextFromLessor
1515
771
  } from "@dbsp/adapter-pgsql";
772
+ import {
773
+ PgTransitionRunPersistenceIndeterminateError,
774
+ planPgTransitionRun
775
+ } from "@dbsp/adapter-pgsql/internal";
1516
776
  import {
1517
777
  acquireTransitionLease as acquireTransitionLease2,
778
+ assertCanonicalizableJson,
1518
779
  bindDeclarationSet,
1519
780
  bindExecutionContract,
1520
781
  createComparator,
@@ -1525,6 +786,7 @@ import {
1525
786
  validateDeclarationModel
1526
787
  } from "@dbsp/core";
1527
788
  import { Command } from "commander";
789
+ var planPhysicalPgTransitionRun = planPgTransitionRun;
1528
790
  var PlanPersistenceIndeterminateError = class extends Error {
1529
791
  runId;
1530
792
  result;
@@ -1574,6 +836,15 @@ var PlanOperationAndCleanupError = class extends AggregateError {
1574
836
  cleanupError;
1575
837
  };
1576
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
+ }
1577
848
  function equivalenceContext(context) {
1578
849
  return {
1579
850
  engine: context.engine,
@@ -1654,20 +925,20 @@ var defaultPlanDeps = {
1654
925
  async loadCurrent(pool, schema, expectedTargetIdentity) {
1655
926
  const lease = await acquirePgTransitionClient(pool);
1656
927
  try {
1657
- const observed2 = await readPgExecutionTargetFromClient(
928
+ const observed = await readPgExecutionTargetFromClient(
1658
929
  lease.client,
1659
930
  [schema ?? "public"]
1660
931
  );
1661
932
  const mismatch = pgTargetIdentityMismatch(
1662
933
  expectedTargetIdentity,
1663
- observed2.identity
934
+ observed.identity
1664
935
  );
1665
936
  if (mismatch) {
1666
937
  throw new Error(
1667
938
  `PostgreSQL introspection target identity does not match the captured target: ${mismatch}`
1668
939
  );
1669
940
  }
1670
- return await createPgsqlAdapter3(lease.client, {
941
+ return await createPgsqlAdapter2(lease.client, {
1671
942
  borrowedClient: true
1672
943
  }).introspect(schema === void 0 ? {} : { schema });
1673
944
  } finally {
@@ -1682,18 +953,14 @@ var defaultPlanDeps = {
1682
953
  expectedTargetIdentity
1683
954
  );
1684
955
  },
1685
- createPlanner(loaded) {
1686
- const registry2 = createPackRegistry([
1687
- createPgTransitionPack({
1688
- ...loaded.dbCasing ? { dbCasing: loaded.dbCasing } : {}
1689
- })
1690
- ]);
1691
- const comparator = createComparator(registry2);
1692
- const prover = createProver(registry2);
956
+ createPlanner() {
957
+ const registry = createPackRegistry([createPgTransitionPack()]);
958
+ const comparator = createComparator(registry);
959
+ const prover = createProver(registry);
1693
960
  return {
1694
961
  compare: (desired, current, context) => comparator.compare(desired, current, equivalenceContext(context)),
1695
962
  prove: (compare, pool, context) => prover.prove(compare, createPgTransitionLessor2(pool), context),
1696
- render: (plan, context) => renderProvenPlanSql(registry2, plan, context)
963
+ render: (plan, context) => renderProvenPlanSql(registry, plan, context)
1697
964
  };
1698
965
  },
1699
966
  async persist(pool, run, plan) {
@@ -1728,9 +995,9 @@ function executionContractBlockedAssessment(error) {
1728
995
  ]
1729
996
  };
1730
997
  }
1731
- function renderProvenPlanSql(registry2, plan, context) {
998
+ function renderProvenPlanSql(registry, plan, context) {
1732
999
  const statements = plan.steps.map((step) => {
1733
- const resolution = registry2.resolveOperation(step.operation);
1000
+ const resolution = registry.resolveOperation(step.operation);
1734
1001
  if (!resolution.ok || !resolution.semantics.renderPlanSql) {
1735
1002
  throw new Error(
1736
1003
  `no SQL renderer is registered for proven operation ${step.operation.operationKind.name}`
@@ -1750,126 +1017,170 @@ async function runPlan(options, overrides) {
1750
1017
  throw new Error(`unsupported plan format ${format}; expected sql or json`);
1751
1018
  }
1752
1019
  const loaded = await deps.loadSchema(options.schemaFile);
1753
- 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);
1754
1028
  const connection = await deps.createDbConnection(options.db);
1755
1029
  const { pool } = connection;
1756
1030
  let result;
1031
+ let renderedSql;
1757
1032
  let operationError;
1758
- let failed = false;
1759
- try {
1760
- const targetIdentity = await deps.captureTargetIdentity(
1761
- pool,
1762
- options.schema
1763
- );
1764
- await deps.ensureTransitionJournal(pool);
1765
- const current = await deps.loadCurrent(
1766
- pool,
1767
- options.schema,
1768
- targetIdentity
1769
- );
1770
- const context = await deps.readContext(
1771
- pool,
1772
- options.schema,
1773
- targetIdentity
1774
- );
1775
- const planner = deps.createPlanner(loaded);
1776
- const compare = planner.compare(loaded.model, current, context);
1777
- const prove = await planner.prove(compare, pool, context);
1778
- if (prove.kind === "proven") {
1779
- let executionContract;
1780
- try {
1781
- executionContract = await deps.buildExecutionContract(
1782
- pool,
1783
- options.schema,
1784
- prove.plan,
1785
- targetIdentity
1786
- );
1787
- } catch (error) {
1788
- if (!(error instanceof PgExecutionContractDerivationError)) throw error;
1033
+ let failed = false;
1034
+ try {
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");
1789
1052
  result = {
1790
- compareKind: compare.kind,
1791
- proveKind: "blocked",
1792
- 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,
1793
1067
  persisted: false,
1794
1068
  runId: null,
1795
1069
  planDigest: null
1796
1070
  };
1797
1071
  }
1798
- if (executionContract !== void 0) {
1799
- const contractedPlan = bindExecutionContract(
1800
- prove.plan,
1801
- executionContract
1802
- );
1803
- const durablePlan = bindDeclarationSet(
1804
- contractedPlan,
1805
- declarationSetFromModel(
1806
- loaded.model,
1807
- {
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, {
1808
1120
  engine: context.engine,
1809
1121
  database: context.databaseId,
1810
- schema: options.schema ?? "public"
1811
- },
1812
- getNamingPluginForDbCasing(loaded.dbCasing ?? "preserve")
1813
- )
1814
- );
1815
- const run = createTransitionRunMetadata(durablePlan);
1816
- const proofContext = prove.plan.observations.find(
1817
- (observation) => observation.role === "evidence"
1818
- )?.context;
1819
- if (!proofContext) {
1820
- throw new Error(
1821
- "internal error: minted proven plan has no evidence observation context"
1122
+ schema: physical.schema
1123
+ })
1822
1124
  );
1823
- }
1824
- const sql = planner.render(durablePlan, proofContext);
1825
- if (!options.dryRun) {
1826
- try {
1827
- await deps.persist(pool, run, durablePlan);
1828
- } catch (error) {
1829
- throw new PlanPersistenceIndeterminateError(
1830
- {
1831
- compareKind: compare.kind,
1832
- proveKind: prove.kind,
1833
- assessment: prove.assessment,
1834
- persisted: "indeterminate",
1835
- runId: run.runId,
1836
- planDigest: run.planDigest
1837
- },
1838
- 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"
1839
1132
  );
1840
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
+ };
1841
1162
  }
1163
+ }
1164
+ if (prove.kind === "no-drift") {
1165
+ result = {
1166
+ compareKind: compare.kind,
1167
+ proveKind: prove.kind,
1168
+ assessment: prove.assessment,
1169
+ persisted: false,
1170
+ runId: null,
1171
+ planDigest: null
1172
+ };
1173
+ } else if (prove.kind !== "proven") {
1842
1174
  result = {
1843
1175
  compareKind: compare.kind,
1844
1176
  proveKind: prove.kind,
1845
1177
  assessment: prove.assessment,
1846
- persisted: !options.dryRun,
1847
- runId: options.dryRun ? null : run.runId,
1848
- planDigest: run.planDigest,
1849
- plan: durablePlan,
1850
- sql
1178
+ persisted: false,
1179
+ runId: null,
1180
+ planDigest: null
1851
1181
  };
1852
1182
  }
1853
1183
  }
1854
- if (prove.kind === "no-drift") {
1855
- result = {
1856
- compareKind: compare.kind,
1857
- proveKind: prove.kind,
1858
- assessment: prove.assessment,
1859
- persisted: false,
1860
- runId: null,
1861
- planDigest: null
1862
- };
1863
- } else if (prove.kind !== "proven") {
1864
- result = {
1865
- compareKind: compare.kind,
1866
- proveKind: prove.kind,
1867
- assessment: prove.assessment,
1868
- persisted: false,
1869
- runId: null,
1870
- planDigest: null
1871
- };
1872
- }
1873
1184
  } catch (error) {
1874
1185
  failed = true;
1875
1186
  operationError = error;
@@ -1881,6 +1192,20 @@ async function runPlan(options, overrides) {
1881
1192
  cleanup = { kind: "failed", error };
1882
1193
  }
1883
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
+ }
1884
1209
  if (operationError instanceof PlanPersistenceIndeterminateError) {
1885
1210
  throw new PlanPersistenceIndeterminateError(
1886
1211
  operationError.result,
@@ -2059,7 +1384,7 @@ function generatorPlanLedgerAddresses(steps) {
2059
1384
  const addresses = /* @__PURE__ */ new Map();
2060
1385
  for (const step of steps) {
2061
1386
  if (step.address !== void 0) {
2062
- addresses.set(ledgerAddressKey2(step.address), step.address);
1387
+ addresses.set(ledgerAddressKey(step.address), step.address);
2063
1388
  continue;
2064
1389
  }
2065
1390
  const closure = step.closure;
@@ -2070,7 +1395,7 @@ function generatorPlanLedgerAddresses(steps) {
2070
1395
  ...closure.members.map((member) => member.address)
2071
1396
  ];
2072
1397
  for (const address of candidates)
2073
- addresses.set(ledgerAddressKey2(address), address);
1398
+ addresses.set(ledgerAddressKey(address), address);
2074
1399
  }
2075
1400
  return [...addresses.values()];
2076
1401
  }
@@ -2080,21 +1405,21 @@ async function generatorRunHasLedgerEvidence(session, journal, steps) {
2080
1405
  const addresses = generatorPlanLedgerAddresses(steps);
2081
1406
  const homes = /* @__PURE__ */ new Map();
2082
1407
  for (const address of addresses) {
2083
- const home2 = ledgerHomeForGeneratorAddress(address);
2084
- homes.set(`${home2.scope}:${home2.schema ?? ""}`, home2);
1408
+ const home = ledgerHomeForGeneratorAddress(address);
1409
+ homes.set(`${home.scope}:${home.schema ?? ""}`, home);
2085
1410
  }
2086
- for (const home2 of homes.values()) {
1411
+ for (const home of homes.values()) {
2087
1412
  for (const executionId of executionIds) {
2088
1413
  const reservations = await readPgLedgerReservationsForExecution(
2089
1414
  session,
2090
- home2,
1415
+ home,
2091
1416
  executionId
2092
1417
  );
2093
1418
  if (reservations.length > 0) return true;
2094
1419
  }
2095
1420
  }
2096
1421
  for (const address of addresses) {
2097
- const chain = await readPgLedgerAddressChain2(
1422
+ const chain = await readPgLedgerAddressChain(
2098
1423
  session,
2099
1424
  ledgerHomeForGeneratorAddress(address),
2100
1425
  address
@@ -2160,19 +1485,19 @@ async function withPoolCleanupReported(result, close) {
2160
1485
  function compareCodeUnits(left, right) {
2161
1486
  return left < right ? -1 : left > right ? 1 : 0;
2162
1487
  }
2163
- function canonicalJson3(value) {
1488
+ function canonicalJson(value) {
2164
1489
  if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
2165
1490
  if (typeof value === "number" && !Number.isFinite(value))
2166
1491
  throw new Error("policy values must be finite JSON values");
2167
1492
  return JSON.stringify(value);
2168
1493
  }
2169
- if (Array.isArray(value)) return `[${value.map(canonicalJson3).join(",")}]`;
1494
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
2170
1495
  if (typeof value !== "object")
2171
1496
  throw new Error(
2172
1497
  "policy values must be JSON values (undefined is not allowed)"
2173
1498
  );
2174
1499
  const record2 = value;
2175
- return `{${Object.keys(record2).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson3(record2[key])}`).join(",")}}`;
1500
+ return `{${Object.keys(record2).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record2[key])}`).join(",")}}`;
2176
1501
  }
2177
1502
  function nonEmptyString(value, path) {
2178
1503
  if (typeof value !== "string" || value.length === 0 || value.trim() !== value)
@@ -2232,7 +1557,7 @@ function validateResourceAddress(value, path) {
2232
1557
  `${path}.catalogueIdentity.value`
2233
1558
  );
2234
1559
  try {
2235
- canonicalJson3(identityValue);
1560
+ canonicalJson(identityValue);
2236
1561
  } catch (error) {
2237
1562
  throw new Error(
2238
1563
  `${path}.catalogueIdentity.value must be a canonicalizable JSON object: ${errorDetail(error)}`
@@ -2298,15 +1623,15 @@ function validateAssumptionAcceptance(value, path = "acceptance") {
2298
1623
  }
2299
1624
  return candidate;
2300
1625
  }
2301
- function normaliseAcceptance(acceptance2) {
2302
- const validated = validateAssumptionAcceptance(acceptance2);
1626
+ function normaliseAcceptance(acceptance) {
1627
+ const validated = validateAssumptionAcceptance(acceptance);
2303
1628
  const withinScope = validated.withinScope ? [
2304
1629
  ...new Map(
2305
- validated.withinScope.map((item) => [canonicalJson3(item), item])
1630
+ validated.withinScope.map((item) => [canonicalJson(item), item])
2306
1631
  )
2307
- ].sort(([left], [right]) => compareCodeUnits(left, right)).map(([, item]) => JSON.parse(canonicalJson3(item))) : void 0;
1632
+ ].sort(([left], [right]) => compareCodeUnits(left, right)).map(([, item]) => JSON.parse(canonicalJson(item))) : void 0;
2308
1633
  return JSON.parse(
2309
- canonicalJson3({
1634
+ canonicalJson({
2310
1635
  class: validated.class,
2311
1636
  ...validated.fromTrustRoot ? { fromTrustRoot: validated.fromTrustRoot } : {},
2312
1637
  ...withinScope === void 0 ? {} : { withinScope }
@@ -2318,7 +1643,7 @@ function canonicalApplyPolicy(acceptances) {
2318
1643
  ...new Map(
2319
1644
  acceptances.map((item) => {
2320
1645
  const normalized = normaliseAcceptance(item);
2321
- return [canonicalJson3(normalized), normalized];
1646
+ return [canonicalJson(normalized), normalized];
2322
1647
  })
2323
1648
  )
2324
1649
  ].sort(([left], [right]) => compareCodeUnits(left, right)).map(([, item]) => item);
@@ -2328,7 +1653,7 @@ function authorizationDigest(runId, planDigest, policy, grants, actor, authorize
2328
1653
  if (Number.isNaN(authorizedInstant.getTime()))
2329
1654
  throw new Error("authorization timestamp must be a valid instant");
2330
1655
  return createHash("sha256").update(
2331
- canonicalJson3({
1656
+ canonicalJson({
2332
1657
  runId,
2333
1658
  planDigest,
2334
1659
  policy: canonicalApplyPolicy(policy),
@@ -2341,7 +1666,7 @@ function authorizationDigest(runId, planDigest, policy, grants, actor, authorize
2341
1666
  }
2342
1667
  function hasReusableAuthorization(authorizations, runId, planDigest, policy, grants) {
2343
1668
  return authorizations?.some(
2344
- (item) => canonicalJson3(item.policy) === canonicalJson3(policy) && canonicalJson3(item.grants) === canonicalJson3(grants) && item.digest === authorizationDigest(
1669
+ (item) => canonicalJson(item.policy) === canonicalJson(policy) && canonicalJson(item.grants) === canonicalJson(grants) && item.digest === authorizationDigest(
2345
1670
  runId,
2346
1671
  planDigest,
2347
1672
  policy,
@@ -2351,6 +1676,13 @@ function hasReusableAuthorization(authorizations, runId, planDigest, policy, gra
2351
1676
  )
2352
1677
  ) ?? false;
2353
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
+ }
2354
1686
  async function effectiveApplyPolicy(options) {
2355
1687
  let fromFile = [];
2356
1688
  if (options.acceptPolicy) {
@@ -2375,9 +1707,6 @@ async function effectiveApplyPolicy(options) {
2375
1707
  )
2376
1708
  };
2377
1709
  }
2378
- function registry() {
2379
- return createPackRegistry2([createPgTransitionPack2({})]);
2380
- }
2381
1710
  var APPLY_OUTCOME_CONTRACT = [
2382
1711
  ["completed", 0, "all planned steps completed"],
2383
1712
  ["operation-failed-not-applied", 10, "operation failed before application"],
@@ -2676,13 +2005,6 @@ detail: ${escapeDiagnosticText3(execution.detail)}`;
2676
2005
  ].join("\n");
2677
2006
  }
2678
2007
  }
2679
- function acceptanceMatches(assumption, acceptance2) {
2680
- return acceptance2.class === assumption.class && (!acceptance2.fromTrustRoot || canonicalJson3(acceptance2.fromTrustRoot) === canonicalJson3(assumption.asserter)) && (assumption.scope.length === 0 ? !acceptance2.withinScope || acceptance2.withinScope.length === 0 : !acceptance2.withinScope || acceptance2.withinScope.length === 0 || assumption.scope.every(
2681
- (resource) => acceptance2.withinScope?.some(
2682
- (selector) => selectorMatchesResource(selector, resource)
2683
- )
2684
- ));
2685
- }
2686
2008
  function recordedPlanRefusal() {
2687
2009
  return { ...REFUSAL_VOCABULARY2["ERR-10"], state: "recorded-plan" };
2688
2010
  }
@@ -2884,7 +2206,15 @@ async function runApplyInternal(runId, options, pool) {
2884
2206
  throw new Error(
2885
2207
  "persisted run no longer contains a generator plan"
2886
2208
  );
2887
- 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;
2888
2218
  if (!recordedSchema)
2889
2219
  throw new Error(
2890
2220
  "persisted generator run has no recorded planning schema and is non-resumable"
@@ -2899,7 +2229,7 @@ async function runApplyInternal(runId, options, pool) {
2899
2229
  }
2900
2230
  if (actualDigest !== current.run.planDigest || actualDigest !== expectedPlanDigest)
2901
2231
  throw new RecordedPlanDigestMismatchError();
2902
- const manifest = validateNormalizedManagedStepManifest3(
2232
+ const manifest = validateNormalizedManagedStepManifest2(
2903
2233
  current.plan.steps
2904
2234
  );
2905
2235
  if (!manifest.ok)
@@ -2947,6 +2277,18 @@ async function runApplyInternal(runId, options, pool) {
2947
2277
  await journalLease.release();
2948
2278
  }
2949
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
+ },
2950
2292
  ...options.replace === void 0 ? {} : { replaces: options.replace },
2951
2293
  runId
2952
2294
  });
@@ -2974,88 +2316,61 @@ async function runApplyInternal(runId, options, pool) {
2974
2316
  );
2975
2317
  }
2976
2318
  } else {
2977
- const locked = await withPgTransitionRunLock(
2319
+ const applied = await applyPgTransitionRun(
2978
2320
  owned,
2979
2321
  runId,
2980
- async (target) => {
2981
- const loadCurrent = async (id) => {
2982
- return loadOnTarget(target, id);
2983
- };
2984
- const applier = createApplier(registry(), {
2985
- // A durable apply only verifies the existing immutable row. The applier
2986
- // calls this before execution; a changed record remains a refusal.
2987
- persist: async () => void 0
2988
- });
2989
- return applier.applyDurable({
2990
- runId,
2991
- expectedPlanDigest,
2992
- loadCurrent,
2993
- prepareExecutionSession: async (session, contract, plan) => {
2994
- const currency = await validatePgManagedLedgerCurrency(
2995
- session,
2996
- plan
2997
- );
2998
- if (currency)
2999
- return {
3000
- ok: false,
3001
- kind: "refused",
3002
- detail: currency
3003
- };
3004
- return preparePgExecutionSession(session, contract, plan);
3005
- },
3006
- policy,
3007
- target,
3008
- authorize: async (run, plan, session) => {
3009
- const current = await loadOnTarget(target, run.runId);
3010
- const grants = plan.assumptions.map((assumption) => ({
3011
- assumptionId: assumption.id,
3012
- grant: policy.accepts.findIndex(
3013
- (grant) => acceptanceMatches(assumption, grant)
3014
- )
3015
- }));
3016
- if (hasReusableAuthorization(
3017
- 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],
3018
2335
  run.runId,
3019
2336
  transitionPlanDigest2(plan),
3020
2337
  policy.accepts,
3021
2338
  grants
3022
- ))
3023
- return;
3024
- const actor = process.env.USER ?? process.env.LOGNAME ?? "unknown-local-actor";
3025
- const authorizedAt = (/* @__PURE__ */ new Date()).toISOString();
3026
- const digest = 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(
3027
2349
  run.runId,
3028
2350
  transitionPlanDigest2(plan),
3029
2351
  policy.accepts,
3030
2352
  grants,
3031
2353
  actor,
3032
2354
  authorizedAt
3033
- );
3034
- const record2 = {
3035
- runId: run.runId,
3036
- policy: policy.accepts,
3037
- grants,
3038
- digest,
3039
- actor,
3040
- authorizedAt
3041
- };
3042
- await appendTransitionAuthorization(session, record2);
3043
- }
3044
- });
2355
+ ),
2356
+ actor,
2357
+ authorizedAt
2358
+ };
2359
+ }
3045
2360
  }
3046
2361
  );
3047
- if (locked.kind === "busy") result = { outcome: "run-busy", runId };
2362
+ if (applied.kind === "busy") result = { outcome: "run-busy", runId };
3048
2363
  else {
3049
- const outcome = outcomeForApplyResult(locked.value);
2364
+ const outcome = outcomeForApplyResult(applied.result);
3050
2365
  const preAppendRefusal = applyPreAppendRefusal(
3051
2366
  outcome,
3052
2367
  persisted.plan,
3053
- locked.value
2368
+ applied.result
3054
2369
  );
3055
2370
  result = {
3056
2371
  outcome,
3057
2372
  runId,
3058
- result: locked.value,
2373
+ result: applied.result,
3059
2374
  ...preAppendRefusal === void 0 ? {} : { refusal: preAppendRefusal }
3060
2375
  };
3061
2376
  }
@@ -3250,13 +2565,19 @@ var generateCommand = new Command3("generate").description("Generate code from s
3250
2565
  case "ddl": {
3251
2566
  const dbCasing = mapCasingToDbCasing(options.casing) ?? schema.dbCasing ?? "snake_case";
3252
2567
  const casingLabel = formatGenerateCasingLabel(dbCasing);
3253
- const { createPgsqlCompileOnlyAdapter } = await import("@dbsp/adapter-pgsql");
2568
+ const { createPgPhysicalModel: createPgPhysicalModel6, createPgsqlCompileOnlyAdapter } = await import("@dbsp/adapter-pgsql");
3254
2569
  const adapter = createPgsqlCompileOnlyAdapter({
3255
2570
  dbCasing,
3256
2571
  ...options.schemaName ? { schemaName: options.schemaName } : {}
3257
2572
  });
3258
2573
  {
3259
- 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, {
3260
2581
  ...options.drop !== void 0 && {
3261
2582
  includeDropStatements: options.drop
3262
2583
  },
@@ -3321,16 +2642,16 @@ function formatGenerateCasingLabel(dbCasing) {
3321
2642
  }
3322
2643
 
3323
2644
  // src/commands/inspect.ts
3324
- import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
2645
+ import { isDeepStrictEqual } from "util";
3325
2646
  import {
3326
2647
  createPgTransitionLessor as createPgTransitionLessor4,
3327
2648
  DBSP_LEDGER_EVENT_TABLE,
3328
2649
  escapeDiagnosticText as escapeDiagnosticText4,
3329
- readPgCatalogueIdentity as readPgCatalogueIdentity3,
3330
- readPgLedgerAddressChain as readPgLedgerAddressChain3,
2650
+ readPgCatalogueIdentity as readPgCatalogueIdentity2,
2651
+ readPgLedgerAddressChain as readPgLedgerAddressChain2,
3331
2652
  readPgLedgerMarker
3332
2653
  } from "@dbsp/adapter-pgsql";
3333
- import { acquireTransitionLease as acquireTransitionLease4, projectLedgerChain as projectLedgerChain2 } from "@dbsp/core";
2654
+ import { acquireTransitionLease as acquireTransitionLease4, projectLedgerChain } from "@dbsp/core";
3334
2655
  import {
3335
2656
  canonicalResourceParent
3336
2657
  } from "@dbsp/types";
@@ -3376,7 +2697,7 @@ function inspectLiveDrift(projection, live) {
3376
2697
  kind: "catalogue-drift",
3377
2698
  detail: "ledger records a present catalogue identity but the object is absent"
3378
2699
  };
3379
- return isDeepStrictEqual2(terminal.catalogueIdentity, live.catalogueIdentity) ? { kind: "matches-ledger" } : {
2700
+ return isDeepStrictEqual(terminal.catalogueIdentity, live.catalogueIdentity) ? { kind: "matches-ledger" } : {
3380
2701
  kind: "catalogue-drift",
3381
2702
  detail: "live catalogue identity differs from the ledger terminal"
3382
2703
  };
@@ -3499,8 +2820,8 @@ async function runInspect(selector, options) {
3499
2820
  );
3500
2821
  let projection;
3501
2822
  try {
3502
- projection = projectLedgerChain2(
3503
- await readPgLedgerAddressChain3(lease.session, ledger, address)
2823
+ projection = projectLedgerChain(
2824
+ await readPgLedgerAddressChain2(lease.session, ledger, address)
3504
2825
  );
3505
2826
  } catch (error) {
3506
2827
  return {
@@ -3519,7 +2840,7 @@ async function runInspect(selector, options) {
3519
2840
  };
3520
2841
  }
3521
2842
  try {
3522
- const live = await readPgCatalogueIdentity3(lease.session, address);
2843
+ const live = await readPgCatalogueIdentity2(lease.session, address);
3523
2844
  const refusal = inspectRefusal(projection);
3524
2845
  const openReaddress = projection?.kind === "projected-ledger-chain" && projection.openClaim?.kind === "readdress-intent" && projection.openClaim.event.pairId ? {
3525
2846
  pairId: projection.openClaim.event.pairId,
@@ -3600,69 +2921,409 @@ function reportWarnings(warnings) {
3600
2921
  console.log(` \u26A0\uFE0F ${warning}`);
3601
2922
  }
3602
2923
  }
3603
- 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(
3604
- "--exclude <patterns>",
3605
- "Tables to exclude (comma-separated glob patterns)",
3606
- "_migrations,_prisma*,pg_*"
3607
- ).option("--include <patterns>", "Tables to include (comma-separated)").option("--no-db-type-comments", "Omit original DB type comments").option(
3608
- "--db-casing <casing>",
3609
- "Database column casing (snake_case \u2192 camelCase in generated code)",
3610
- "snake_case"
3611
- ).action(
3612
- async (options) => {
3613
- const redactedUrl = redactDbUrl(options.db);
3614
- console.log(`\u{1F50D} Introspecting database: ${redactedUrl}`);
3615
- console.log(` Schema: ${options.schemaName}`);
3616
- if (options.exclude) {
3617
- console.log(` Excluding: ${options.exclude}`);
3618
- }
3619
- console.log("");
3620
- try {
3621
- 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);
3622
3215
  try {
3623
- const { introspect } = await import("@dbsp/adapter-pgsql");
3624
- const excludePatterns = options.exclude ? options.exclude.split(",").map((s) => s.trim()) : void 0;
3625
- const includePatterns = options.include ? options.include.split(",").map((s) => s.trim()) : void 0;
3626
- const model = await introspect(pool, {
3627
- schema: options.schemaName,
3628
- ...excludePatterns ? { exclude: excludePatterns } : {},
3629
- ...includePatterns ? { include: includePatterns } : {}
3630
- });
3631
- const tableCount = model.tables.size;
3632
- const relationCount = model.relations.size;
3633
- const hierarchyCount = model.hierarchies?.length ?? 0;
3634
- console.log(
3635
- `\u{1F4CA} Found ${tableCount} tables, ${relationCount} relations, ${hierarchyCount} hierarchies`
3636
- );
3637
- const codegenOptions = {
3638
- sourceUrl: options.db,
3639
- includeDbTypeComments: options.dbTypeComments,
3640
- introspectedAt: model.introspectedAt,
3641
- dbCasing: options.dbCasing
3642
- };
3643
- const generated = generateSchemaFileWithDiagnostics(
3644
- model,
3645
- codegenOptions
3646
- );
3647
- const warnings = uniqueWarnings(generated.warnings);
3648
- if (warnings.length > 0) {
3649
- reportWarnings(warnings);
3650
- }
3651
- console.log("");
3652
- const outPath = resolve2(process.cwd(), options.out);
3653
- mkdirSync2(dirname2(outPath), { recursive: true });
3654
- writeFileSync2(outPath, generated.code, "utf-8");
3655
- console.log(`\u2705 Generated schema: ${outPath}`);
3656
- console.log(` Tables: ${tableCount}`);
3657
- console.log(` Relations: ${relationCount}`);
3658
- } finally {
3659
- await pool.end();
3216
+ sensitive.add(decodeURIComponent(password));
3217
+ } catch {
3660
3218
  }
3661
- } catch (error) {
3662
- const message = error instanceof Error ? error.message : String(error);
3663
- console.error(`\u274C ${message}`);
3664
- process.exit(1);
3665
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;
3666
3327
  }
3667
3328
  );
3668
3329
 
@@ -3671,12 +3332,12 @@ import { createHash as createHash2 } from "crypto";
3671
3332
  import { mkdtemp, open, rename, rm } from "fs/promises";
3672
3333
  import { dirname as dirname3, join } from "path";
3673
3334
  import {
3674
- escapeDiagnosticText as escapeDiagnosticText5,
3675
- getNamingPluginForDbCasing as getNamingPluginForDbCasing2,
3335
+ createPgPhysicalModel as createPgPhysicalModel4,
3336
+ escapeDiagnosticText as escapeDiagnosticText6,
3676
3337
  runPgReinitializePreflight
3677
3338
  } from "@dbsp/adapter-pgsql";
3678
3339
  import { declarationSetFromModel as declarationSetFromModel2 } from "@dbsp/core";
3679
- import { Command as Command6 } from "commander";
3340
+ import { Command as Command7 } from "commander";
3680
3341
  var defaultDeps = { createDbConnection, loadSchema };
3681
3342
  var nodeAdoptionFileSystem = {
3682
3343
  mkdtemp,
@@ -3726,13 +3387,9 @@ async function databaseName(pool) {
3726
3387
  throw new Error("current database could not be read");
3727
3388
  return name;
3728
3389
  }
3729
- function declarationsForScopes(model, database, scopes, naming) {
3390
+ function declarationsForScopes(model, database, scopes) {
3730
3391
  const declarations = scopes.flatMap(
3731
- (schema) => declarationSetFromModel2(
3732
- model,
3733
- { engine: "postgresql", database, schema },
3734
- naming
3735
- ).declarations
3392
+ (schema) => declarationSetFromModel2(model, { engine: "postgresql", database, schema }).declarations
3736
3393
  );
3737
3394
  const seen = /* @__PURE__ */ new Set();
3738
3395
  const uniqueDeclarations = declarations.filter((declaration) => {
@@ -3767,19 +3424,25 @@ async function runPreflight(options, deps = defaultDeps) {
3767
3424
  throw new Error("dbsp preflight requires --reinitialize");
3768
3425
  if (!options.out)
3769
3426
  throw new Error("dbsp preflight --reinitialize requires --out <file>");
3770
- if (options.scopes.length === 0)
3427
+ const physicalSchema = options.scopes[0];
3428
+ if (physicalSchema === void 0)
3771
3429
  throw new Error(
3772
3430
  "dbsp preflight requires at least one explicit --scope <schema>"
3773
3431
  );
3774
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
+ });
3775
3439
  const connection = await deps.createDbConnection(options.db);
3776
3440
  try {
3777
3441
  const database = await databaseName(connection.pool);
3778
3442
  const declarations = declarationsForScopes(
3779
- loaded.model,
3443
+ physical.model,
3780
3444
  database,
3781
- options.scopes,
3782
- getNamingPluginForDbCasing2(loaded.dbCasing ?? "preserve")
3445
+ options.scopes
3783
3446
  );
3784
3447
  return await runPgReinitializePreflight({
3785
3448
  pool: connection.pool,
@@ -3794,18 +3457,18 @@ async function runPreflight(options, deps = defaultDeps) {
3794
3457
  function formatScope(scope) {
3795
3458
  const name = scope.ledger.scope === "database" ? "dbsp_meta" : scope.ledger.schema ?? "unknown";
3796
3459
  if (scope.outcome === "failed")
3797
- return `${escapeDiagnosticText5(name)}: failed (${escapeDiagnosticText5(scope.reason.step)}: ${escapeDiagnosticText5(scope.reason.message)})`;
3798
- 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)}`;
3799
3462
  }
3800
3463
  function formatReinitializeSplit(scopes) {
3801
3464
  const names = (predicate) => scopes.filter(predicate).map(
3802
- (scope) => escapeDiagnosticText5(
3465
+ (scope) => escapeDiagnosticText6(
3803
3466
  scope.ledger.scope === "database" ? "dbsp_meta" : scope.ledger.schema ?? "unknown"
3804
3467
  )
3805
3468
  ).join(",") || "none";
3806
3469
  return `reinitialize-split: changed=${names((scope) => scope.outcome === "current")}; failed=${names((scope) => scope.outcome === "failed")}; not-attempted=${names((scope) => scope.outcome === "not-attempted")}`;
3807
3470
  }
3808
- var preflightCommand = new Command6("preflight").description(
3471
+ var preflightCommand = new Command7("preflight").description(
3809
3472
  "Run the separately privileged managed-ledger reinitialize cutover"
3810
3473
  ).requiredOption("-d, --db <url>", "Database connection URL (required)").requiredOption(
3811
3474
  "--reinitialize",
@@ -3830,7 +3493,7 @@ var preflightCommand = new Command6("preflight").description(
3830
3493
  );
3831
3494
  } catch (error) {
3832
3495
  console.error(
3833
- escapeDiagnosticText5(
3496
+ escapeDiagnosticText6(
3834
3497
  error instanceof Error ? error.message : String(error)
3835
3498
  )
3836
3499
  );
@@ -3839,33 +3502,12 @@ var preflightCommand = new Command6("preflight").description(
3839
3502
  });
3840
3503
 
3841
3504
  // src/commands/reconcile.ts
3842
- import { createHash as createHash3 } from "crypto";
3843
3505
  import {
3844
- assertCreateUniqueIndexConcurrentlyRecoveryNotInvalid,
3845
- assertPgDatabaseWritable,
3846
- escapeDiagnosticText as escapeDiagnosticText6,
3847
- isPgDatabaseReadOnlyError,
3848
- readPgLedgerAddressChain as readPgLedgerAddressChain4,
3849
- readPgLedgerReservationsForExecution as readPgLedgerReservationsForExecution2,
3850
- readPgLedgerScopeCurrency as readPgLedgerScopeCurrency2,
3851
- readTransitionJournal as readTransitionJournal2,
3852
- readVerifiedPgLedgerReservationsForPair,
3853
- recoverPgReaddressPair,
3854
- withPgTransitionRunLock as withPgTransitionRunLock2
3506
+ escapeDiagnosticText as escapeDiagnosticText7,
3507
+ PgReconcileTransitionRunError,
3508
+ reconcilePgTransitionRun
3855
3509
  } from "@dbsp/adapter-pgsql";
3856
- import { recoverPgOutcomeClaim } from "@dbsp/adapter-pgsql/internal";
3857
- import {
3858
- acquireExclusiveTransitionLease as acquireExclusiveTransitionLease2,
3859
- assumptionAccepted,
3860
- canonicalJson as canonicalJson4,
3861
- canonicalJsonDigest as canonicalJsonDigest3,
3862
- outcomeClaimId as outcomeClaimId2,
3863
- projectLedgerChain as projectLedgerChain3,
3864
- resourceScopeCovers,
3865
- transitionPlanDigest as transitionPlanDigest3
3866
- } from "@dbsp/core";
3867
- import { sameLedgerAddress } from "@dbsp/types";
3868
- import { Command as Command7 } from "commander";
3510
+ import { Command as Command8 } from "commander";
3869
3511
  function pgSqlState(error) {
3870
3512
  if (typeof error !== "object" || error === null || !("code" in error))
3871
3513
  return void 0;
@@ -3892,170 +3534,120 @@ function classifyReconcileFailure(error, stage) {
3892
3534
  return "catalogue";
3893
3535
  }
3894
3536
  function isUnresolvedRecoveryOutcome(report) {
3895
- return 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-appended" || report.outcome === "indeterminate-pair";
3896
- }
3897
- function ledgerHome(address) {
3898
- if (address.scope === "database") return { scope: "database" };
3899
- if (!address.schema)
3900
- throw new Error(
3901
- `schema-scoped managed claim ${address.name} has no schema`
3902
- );
3903
- return { scope: "schema", schema: address.schema };
3904
- }
3905
- function recoveryPayload(identity) {
3906
- const value = JSON.parse(
3907
- canonicalJson4({
3908
- ...identity === void 0 ? {} : { catalogueIdentity: identity }
3909
- })
3910
- );
3911
- return {
3912
- value,
3913
- digest: canonicalJsonDigest3(value)
3914
- };
3915
- }
3916
- function recoveryReport(address, result) {
3917
- if (result.kind === "outcome-recovery-appended") {
3918
- const indeterminate = result.classification.resolution.eventKind === "indeterminate";
3919
- return {
3920
- address,
3921
- outcome: indeterminate ? "indeterminate-appended" : result.append.kind === "already-appended-outcome-resolution" ? "already-appended" : "appended",
3922
- reason: result.classification.resolution.reason
3923
- };
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" });
3924
3556
  }
3925
- if (result.kind === "outcome-recovery-no-open-claim")
3926
- return { address, outcome: "no-open-claim" };
3927
- if (result.kind === "outcome-recovery-pending")
3928
- return {
3929
- address,
3930
- outcome: "pending",
3931
- reason: result.reason,
3932
- ...result.reasonCode === "catalogue-unavailable" ? { failureCause: "catalogue" } : {},
3933
- ...result.reasonCode === "catalogue-unavailable" ? {
3934
- refusal: preAppendRefusalFor("ERR-09", {
3935
- address,
3936
- state: "unknown"
3937
- })
3938
- } : {}
3939
- };
3940
- if (result.kind === "outcome-recovery-blocked")
3941
- return { address, outcome: "blocked", reason: result.reason };
3942
- if (result.kind === "outcome-recovery-malformed-chain")
3943
- return {
3944
- address,
3945
- outcome: "malformed-chain",
3946
- reason: result.reason,
3947
- failureCause: "malformed-journal",
3948
- refusal: preAppendRefusalFor("ERR-08", { address, state: "unknown" })
3949
- };
3950
- if (result.kind === "outcome-transport-ambiguous")
3951
- return {
3952
- address,
3953
- outcome: "transport-ambiguous",
3954
- reason: result.reason,
3955
- failureCause: "transport"
3956
- };
3957
- return { address, outcome: "protocol-refused", reason: result.reason };
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" }
3564
+ );
3958
3565
  }
3959
- function readdressRecoveryReport(reservations, result) {
3960
- const first = reservations[0];
3961
- if (!first) throw new Error("re-address recovery has no reservation");
3962
- if (result.kind === "readdress-recovery-refused-pair")
3963
- return {
3964
- address: first.address,
3965
- outcome: "refused-pair",
3966
- pairId: result.pairId
3967
- };
3968
- if (result.kind === "readdress-recovery-indeterminate-pair")
3969
- return {
3970
- address: first.address,
3971
- outcome: "indeterminate-pair",
3972
- reason: result.reason,
3973
- pairId: result.pairId
3974
- };
3975
- if (result.kind === "readdress-recovery-transport-ambiguous-pair")
3976
- return {
3977
- address: first.address,
3978
- outcome: "transport-ambiguous",
3979
- reason: result.reason,
3980
- failureCause: "transport",
3981
- pairId: result.pairId
3982
- };
3983
- return {
3984
- address: first.address,
3985
- outcome: "pending",
3986
- reason: result.reason,
3987
- pairId: result.pairId
3988
- };
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
+ }));
3989
3589
  }
3990
3590
  function unresolvedRecoveryDetail(reports) {
3991
3591
  const unresolved = reports.filter(isUnresolvedRecoveryOutcome);
3992
3592
  if (unresolved.length === 0) return void 0;
3993
3593
  return unresolved.map(
3994
- (report) => `${escapeDiagnosticText6(report.address.name)}: ${escapeDiagnosticText6(report.reason ?? report.outcome)}`
3594
+ (report) => `${escapeDiagnosticText7(report.address.name)}: ${escapeDiagnosticText7(report.reason ?? report.outcome)}`
3995
3595
  ).join("; ");
3996
3596
  }
3997
3597
  function formatReconcileHuman(result) {
3998
- const line = `${escapeDiagnosticText6(result.outcome)}: ${escapeDiagnosticText6(result.runId)}`;
3598
+ const line = `${escapeDiagnosticText7(result.outcome)}: ${escapeDiagnosticText7(result.runId)}`;
3999
3599
  const base = result.refusal ? formatPreAppendRefusalHuman(line, result.refusal) : line;
4000
3600
  if (result.outcome === "reconcile-completed" || !result.recovery) return base;
4001
3601
  const unresolved = result.recovery.filter(isUnresolvedRecoveryOutcome);
4002
3602
  return unresolved.length === 0 ? base : [
4003
3603
  base,
4004
3604
  ...unresolved.map(
4005
- (report) => `${escapeDiagnosticText6(report.address.name)}: ${escapeDiagnosticText6(report.outcome)}${report.reason ? `: ${escapeDiagnosticText6(report.reason)}` : ""}`
3605
+ (report) => `${escapeDiagnosticText7(report.address.name)}: ${escapeDiagnosticText7(report.outcome)}${report.reason ? `: ${escapeDiagnosticText7(report.reason)}` : ""}`
4006
3606
  )
4007
3607
  ].join("\n");
4008
3608
  }
4009
- function statementBundleDigest(statements) {
4010
- return createHash3("sha256").update(
4011
- JSON.stringify(statements.map(({ ordinal, sql }) => [ordinal, sql]))
4012
- ).digest("hex");
4013
- }
4014
- function recoveryEvidenceForClaim(input) {
4015
- if (transitionPlanDigest3(input.journal.plan) !== input.journal.run.planDigest)
4016
- return void 0;
4017
- const step = input.journal.plan.steps.find(
4018
- (candidate) => candidate.managedClaim?.plannedClaimKey === input.plannedClaimKey
4019
- );
4020
- const claim = step?.managedClaim;
4021
- if (!step || !claim || !sameLedgerAddress(claim.address, input.row.address) || outcomeClaimId2(
4022
- input.row.executionId,
4023
- input.plannedClaimKey,
4024
- input.row.address
4025
- ) !== input.row.rootClaimId)
4026
- return void 0;
4027
- const binding = step.guards.find(
4028
- (guard) => guard.protocol.binding.kind === "external-ddl-exclusion" && resourceScopeCovers(guard.protocol.binding.scope, [input.row.address])
4029
- )?.protocol.binding;
4030
- if (binding?.kind !== "external-ddl-exclusion") return void 0;
4031
- const assumption = input.journal.plan.assumptions.find(
4032
- (candidate) => candidate.id === binding.assumption && candidate.class === "external-ddl-exclusion" && step.restsOnAssumptions.includes(candidate.id) && resourceScopeCovers(candidate.scope, [input.row.address])
4033
- );
4034
- if (!assumption) return void 0;
4035
- const accepted = input.journal.authorizations?.some((authorization) => {
4036
- const grant = authorization.grants.find(
4037
- (candidate) => candidate.assumptionId === assumption.id
4038
- );
4039
- if (!grant) return false;
4040
- const acceptance2 = authorization.policy[grant.grant];
4041
- return acceptance2 !== void 0 && assumptionAccepted(assumption, { accepts: [acceptance2] });
4042
- });
4043
- if (!accepted) return void 0;
4044
- const bundleDigest = statementBundleDigest(claim.statementBundle.statements);
3609
+ function mapAdapterResult(result) {
3610
+ if (result.kind === "busy")
3611
+ return {
3612
+ outcome: "reconcile-run-unavailable",
3613
+ runId: result.runId,
3614
+ addresses: []
3615
+ };
3616
+ if (result.kind === "selection-unavailable")
3617
+ return {
3618
+ outcome: "reconcile-claim-selection-unavailable",
3619
+ runId: result.runId,
3620
+ addresses: result.addresses,
3621
+ ...result.detail === void 0 ? {} : { detail: result.detail }
3622
+ };
3623
+ if (result.kind === "database-read-only") {
3624
+ const refusal = refusalForSelectedIssue(result.selectedIssue);
3625
+ return {
3626
+ outcome: "database-read-only",
3627
+ runId: result.runId,
3628
+ addresses: result.addresses,
3629
+ detail: result.selectedIssue.reason,
3630
+ ...refusal === void 0 ? {} : { refusal }
3631
+ };
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);
3637
+ return {
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 }
3644
+ };
3645
+ }
4045
3646
  return {
4046
- runId: input.journal.run.runId,
4047
- planDigest: input.journal.run.planDigest,
4048
- executionId: input.row.executionId,
4049
- claimId: input.row.rootClaimId,
4050
- plannedClaimKey: input.plannedClaimKey,
4051
- admittedBundleDigest: bundleDigest,
4052
- persistedBundleDigest: bundleDigest,
4053
- recordedPreState: input.stableStateBeforeClaim,
4054
- externalDdlExclusion: {
4055
- planDigest: input.journal.run.planDigest,
4056
- address: input.row.address,
4057
- trustRoot: JSON.stringify(assumption.asserter)
4058
- }
3647
+ outcome: "reconcile-completed",
3648
+ runId: result.runId,
3649
+ addresses: result.addresses,
3650
+ ...recovery === void 0 ? {} : { recovery }
4059
3651
  };
4060
3652
  }
4061
3653
  async function runReconcile(runId, options, pool) {
@@ -4070,303 +3662,28 @@ async function runReconcile(runId, options, pool) {
4070
3662
  failureCause: classifyReconcileFailure(error, "reconcile")
4071
3663
  };
4072
3664
  }
4073
- let stage = "reconcile";
4074
3665
  try {
4075
- const locked = await withPgTransitionRunLock2(
4076
- owned,
4077
- runId,
4078
- async (target) => {
4079
- const lease = await acquireExclusiveTransitionLease2(target);
4080
- try {
4081
- stage = "journal";
4082
- const journal = await readTransitionJournal2(lease.session, runId, {
4083
- ensure: false
4084
- });
4085
- stage = "reconcile";
4086
- const material = journal.plan.steps.map((step) => {
4087
- const generatedStep = step;
4088
- const generated = generatedStep.address;
4089
- return step.managedClaim ?? (generated === void 0 ? void 0 : {
4090
- address: generated,
4091
- ...generatedStep.plannedClaimKeys?.[0] === void 0 ? {} : {
4092
- plannedClaimKey: generatedStep.plannedClaimKeys[0]
4093
- }
4094
- });
4095
- }).filter(
4096
- (claim) => claim !== void 0
4097
- );
4098
- try {
4099
- await assertPgDatabaseWritable(lease.session);
4100
- } catch (error) {
4101
- if (isPgDatabaseReadOnlyError(error))
4102
- return {
4103
- kind: "database-read-only",
4104
- detail: error.message,
4105
- addresses: material.map((claim) => claim.address)
4106
- };
4107
- throw error;
4108
- }
4109
- if (material.length === 0)
4110
- return {
4111
- kind: "selection-unavailable",
4112
- addresses: journal.plan.declarations?.declarations.map(
4113
- (declaration) => declaration.address
4114
- ) ?? []
4115
- };
4116
- const homes = /* @__PURE__ */ new Map();
4117
- for (const claim of material) {
4118
- const home2 = ledgerHome(claim.address);
4119
- homes.set(`${home2.scope}:${home2.schema ?? ""}`, home2);
4120
- }
4121
- for (const home2 of homes.values()) {
4122
- const currency = await readPgLedgerScopeCurrency2(
4123
- lease.session,
4124
- home2
4125
- );
4126
- if (currency.kind === "current") continue;
4127
- 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`;
4128
- const affected = material.filter((claim) => {
4129
- const claimHome = ledgerHome(claim.address);
4130
- return claimHome.scope === home2.scope && claimHome.schema === home2.schema;
4131
- });
4132
- return {
4133
- kind: "unresolved",
4134
- addresses: affected.map((claim) => claim.address),
4135
- detail,
4136
- recovery: affected.map((claim) => ({
4137
- address: claim.address,
4138
- outcome: "blocked",
4139
- reason: detail,
4140
- refusal: preAppendRefusalFor("ERR-03", {
4141
- address: claim.address,
4142
- state: "unknown"
4143
- })
4144
- }))
4145
- };
4146
- }
4147
- const executionIds = executionIdsForRun(journal);
4148
- const reservations = (await Promise.all(
4149
- [...homes.values()].flatMap(
4150
- (home2) => executionIds.map(
4151
- (executionId) => readPgLedgerReservationsForExecution2(
4152
- lease.session,
4153
- home2,
4154
- executionId
4155
- )
4156
- )
4157
- )
4158
- )).flat();
4159
- if (reservations.length === 0)
4160
- return {
4161
- kind: "selection-unavailable",
4162
- addresses: material.map((x) => x.address)
4163
- };
4164
- const readdressPairs = /* @__PURE__ */ new Map();
4165
- const byRoot = /* @__PURE__ */ new Map();
4166
- for (const row of reservations) {
4167
- if (row.claimKind === "readdress-intent" && row.pairId !== void 0) {
4168
- const pairKey = `${row.executionId}:${row.pairId}`;
4169
- readdressPairs.set(pairKey, [
4170
- ...readdressPairs.get(pairKey) ?? [],
4171
- row
4172
- ]);
4173
- continue;
4174
- }
4175
- byRoot.set(row.rootClaimId, [
4176
- ...byRoot.get(row.rootClaimId) ?? [],
4177
- row
4178
- ]);
4179
- }
4180
- const recovery = [];
4181
- for (const [_pairKey, pairRows] of readdressPairs) {
4182
- const pairId = pairRows[0]?.pairId;
4183
- const executionId = pairRows[0]?.executionId;
4184
- if (!pairId || !executionId) continue;
4185
- const readPairInSelectedHomes = readVerifiedPgLedgerReservationsForPair;
4186
- const closure = await readPairInSelectedHomes(
4187
- lease.session,
4188
- pairId,
4189
- [...homes.values()]
4190
- );
4191
- const recovered = await recoverPgReaddressPair(lease.session, {
4192
- pairId,
4193
- executionId,
4194
- reservations: closure
4195
- });
4196
- recovery.push(readdressRecoveryReport(closure, recovered));
4197
- }
4198
- for (const rows of byRoot.values()) {
4199
- const rootClaimId = rows[0]?.rootClaimId;
4200
- if (!rootClaimId) continue;
4201
- if (rows.some(
4202
- (row) => !executionIds.includes(row.executionId) || row.rootClaimId !== rootClaimId
4203
- ))
4204
- return {
4205
- kind: "selection-unavailable",
4206
- addresses: rows.map((row) => row.address),
4207
- detail: `reservation disagreement for root claim ${rootClaimId}`
4208
- };
4209
- const rootCandidates = [];
4210
- for (const row of rows) {
4211
- stage = "catalogue";
4212
- const chain = await readPgLedgerAddressChain4(
4213
- lease.session,
4214
- ledgerHome(row.address),
4215
- row.address
4216
- );
4217
- stage = "reconcile";
4218
- const projection = projectLedgerChain3(chain);
4219
- if (projection.kind !== "projected-ledger-chain" || projection.openClaim === void 0)
4220
- continue;
4221
- const open2 = projection.openClaim.event;
4222
- const openRoot = open2.rootClaimId ?? open2.eventId;
4223
- if (open2.executionId !== row.executionId || openRoot !== rootClaimId)
4224
- return {
4225
- kind: "selection-unavailable",
4226
- addresses: rows.map((item) => item.address),
4227
- detail: `open chain member disagrees with reservation root ${rootClaimId}`
4228
- };
4229
- if (open2.eventId === rootClaimId)
4230
- rootCandidates.push({
4231
- row,
4232
- stableStateBeforeClaim: projection.openClaim.stableStateBeforeClaim,
4233
- ...open2.plannedClaimKey === void 0 ? {} : { plannedClaimKey: open2.plannedClaimKey }
4234
- });
4235
- }
4236
- if (rootCandidates.length !== 1)
4237
- return {
4238
- kind: "selection-unavailable",
4239
- addresses: rows.map((row) => row.address),
4240
- detail: `execution ${runId} has ${rootCandidates.length} open root members for ${rootClaimId}`
4241
- };
4242
- const selected = rootCandidates[0];
4243
- if (!selected) continue;
4244
- if (!selected.plannedClaimKey || !material.some(
4245
- (claim) => claim.plannedClaimKey === selected.plannedClaimKey
4246
- ))
4247
- return {
4248
- kind: "selection-unavailable",
4249
- addresses: rows.map((row) => row.address),
4250
- detail: `open root ${rootClaimId} has no matching persisted managed step`
4251
- };
4252
- const step = journal.plan.steps.find(
4253
- (candidate) => candidate.managedClaim?.plannedClaimKey === selected.plannedClaimKey || "plannedClaimKeys" in candidate && Array.isArray(candidate.plannedClaimKeys) && candidate.plannedClaimKeys.includes(
4254
- selected.plannedClaimKey
4255
- )
4256
- );
4257
- const operationReadBack = step?.operation?.operationKind.name === "CreateUniqueIndexConcurrently" ? async (executor, _address, identity) => {
4258
- await assertCreateUniqueIndexConcurrentlyRecoveryNotInvalid(
4259
- executor,
4260
- step.operation
4261
- );
4262
- return {
4263
- observed: recoveryPayload(identity),
4264
- effect: "unverifiable"
4265
- };
4266
- } : void 0;
4267
- const indeterminateEvidence = recoveryEvidenceForClaim({
4268
- journal,
4269
- row: selected.row,
4270
- plannedClaimKey: selected.plannedClaimKey,
4271
- stableStateBeforeClaim: selected.stableStateBeforeClaim
4272
- });
4273
- for (const row of rows) {
4274
- const isRoot = sameLedgerAddress(
4275
- row.address,
4276
- selected.row.address
4277
- );
4278
- const recovered = await recoverPgOutcomeClaim(owned, {
4279
- address: row.address,
4280
- reservations: [row],
4281
- resolutionEventId: isRoot ? `${rootClaimId}:reconcile:${runId}` : `${rootClaimId}:reconcile:${runId}:${row.address.kind}:${row.address.name}`,
4282
- acceptedExternalDdlExclusion: isRoot && indeterminateEvidence !== void 0,
4283
- resolveIndeterminate: true,
4284
- readBack: async (_executor, _address, identity) => recoveryPayload(identity),
4285
- ...isRoot && indeterminateEvidence !== void 0 ? { indeterminateEvidence } : {},
4286
- ...isRoot && operationReadBack !== void 0 ? { operationReadBack } : {}
4287
- });
4288
- recovery.push(recoveryReport(row.address, recovered));
4289
- }
4290
- }
4291
- const unresolved = unresolvedRecoveryDetail(recovery);
4292
- if (unresolved)
4293
- return {
4294
- kind: "unresolved",
4295
- addresses: reservations.map((item) => item.address),
4296
- detail: unresolved,
4297
- recovery
4298
- };
4299
- return {
4300
- kind: "completed",
4301
- addresses: reservations.map((item) => item.address),
4302
- recovery
4303
- };
4304
- } finally {
4305
- await lease.release();
4306
- }
4307
- }
4308
- );
4309
- if (locked.kind === "busy")
4310
- return { outcome: "reconcile-run-unavailable", runId, addresses: [] };
4311
- if (locked.value.kind === "selection-unavailable")
4312
- return {
4313
- outcome: "reconcile-claim-selection-unavailable",
4314
- runId,
4315
- addresses: locked.value.addresses,
4316
- ...locked.value.detail ? { detail: locked.value.detail } : {}
4317
- };
4318
- if (locked.value.kind === "database-read-only") {
4319
- const address = locked.value.addresses[0];
4320
- return {
4321
- outcome: "database-read-only",
4322
- runId,
4323
- addresses: locked.value.addresses,
4324
- detail: locked.value.detail,
4325
- ...address === void 0 ? {} : {
4326
- refusal: preAppendRefusalFor("ERR-07", {
4327
- address,
4328
- state: "unknown"
4329
- })
4330
- }
4331
- };
4332
- }
4333
- if (locked.value.kind === "unresolved") {
4334
- const address = locked.value.addresses[0];
4335
- const markerRefusal = address !== void 0 && locked.value.detail?.includes("run dbsp preflight --reinitialize") ? preAppendRefusalFor("ERR-03", { address, state: "unknown" }) : void 0;
4336
- const recoveryRefusal = locked.value.recovery?.find(
4337
- (report) => report.refusal !== void 0
4338
- )?.refusal;
4339
- const refusal = markerRefusal ?? recoveryRefusal;
4340
- return {
4341
- outcome: "reconcile-unresolved",
4342
- runId,
4343
- addresses: locked.value.addresses,
4344
- ...locked.value.detail ? { detail: locked.value.detail } : {},
4345
- ...locked.value.recovery ? { recovery: locked.value.recovery } : {},
4346
- ...refusal === void 0 ? {} : { refusal }
4347
- };
4348
- }
4349
- return {
4350
- outcome: "reconcile-completed",
4351
- runId,
4352
- addresses: locked.value.addresses,
4353
- ...locked.value.recovery ? { recovery: locked.value.recovery } : {}
4354
- };
3666
+ return mapAdapterResult(await reconcilePgTransitionRun(owned, runId));
4355
3667
  } catch (error) {
3668
+ const typed = error instanceof PgReconcileTransitionRunError ? error : void 0;
3669
+ const cause = typed?.originalCause ?? error;
4356
3670
  return {
4357
3671
  outcome: "reconcile-run-unavailable",
4358
3672
  runId,
4359
3673
  addresses: [],
4360
- detail: escapeDiagnosticText6(
4361
- error instanceof Error ? error.message : String(error)
3674
+ detail: escapeDiagnosticText7(
3675
+ cause instanceof Error ? cause.message : String(cause)
4362
3676
  ),
4363
- failureCause: classifyReconcileFailure(error, stage)
3677
+ failureCause: classifyReconcileFailure(
3678
+ cause,
3679
+ typed?.stage ?? "reconcile"
3680
+ )
4364
3681
  };
4365
3682
  } finally {
4366
3683
  if (pool === void 0) await owned.end();
4367
3684
  }
4368
3685
  }
4369
- 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) => {
4370
3687
  const result = await runReconcile(runId, options);
4371
3688
  if (options.format === "json") printCliJson(result);
4372
3689
  else console.log(formatReconcileHuman(result));
@@ -4375,25 +3692,25 @@ var reconcileCommand = new Command7("reconcile").description("Resolve this run\u
4375
3692
 
4376
3693
  // src/commands/recover.ts
4377
3694
  import {
4378
- assertPgDatabaseWritable as assertPgDatabaseWritable2,
4379
- createPgTransitionPack as createPgTransitionPack3,
4380
- isPgDatabaseReadOnlyError as isPgDatabaseReadOnlyError2,
3695
+ assertPgDatabaseWritable,
3696
+ createPgTransitionPack as createPgTransitionPack2,
3697
+ isPgDatabaseReadOnlyError,
4381
3698
  preparePgRecoveryAdmission,
4382
- readPgLedgerScopeCurrency as readPgLedgerScopeCurrency3,
3699
+ readPgLedgerScopeCurrency,
4383
3700
  readPgObservationContextFromLessor as readPgObservationContextFromLessor2,
4384
- readTransitionJournal as readTransitionJournal3,
4385
- withPgTransitionRunLock as withPgTransitionRunLock3
3701
+ readTransitionJournal as readTransitionJournal2,
3702
+ withPgTransitionRunLock as withPgTransitionRunLock2
4386
3703
  } from "@dbsp/adapter-pgsql";
4387
3704
  import {
4388
3705
  acquireTransitionTargetLease,
4389
- assumptionAccepted as assumptionAccepted2,
4390
- createApplier as createApplier2,
4391
- createPackRegistry as createPackRegistry3,
3706
+ assumptionAccepted,
3707
+ createApplier,
3708
+ createPackRegistry as createPackRegistry2,
4392
3709
  loadVerifiedRecoveryJournal,
4393
- transitionPlanDigest as transitionPlanDigest4,
3710
+ transitionPlanDigest as transitionPlanDigest3,
4394
3711
  validateTransitionRelationalInvariants
4395
3712
  } from "@dbsp/core";
4396
- import { Command as Command8 } from "commander";
3713
+ import { Command as Command9 } from "commander";
4397
3714
  var RECOVER_OUTCOME_CONTRACT = [
4398
3715
  ["completed", 0, "all attempted steps were durably reconciled"],
4399
3716
  ["recovery-resume-required", 40, "remaining work requires a new proof"],
@@ -4482,7 +3799,7 @@ function validateRecoveryAuthorization(journal) {
4482
3799
  if (journal.plan.assumptions.every((assumption) => {
4483
3800
  const grant = grants.get(assumption.id);
4484
3801
  const acceptedGrant = grant === void 0 ? void 0 : accepts[grant];
4485
- 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, {
4486
3803
  accepts: [acceptedGrant]
4487
3804
  });
4488
3805
  }))
@@ -4493,7 +3810,7 @@ function validateRecoveryAuthorization(journal) {
4493
3810
  return { ok: false, outcome: "recovery-authorization-invalid" };
4494
3811
  }
4495
3812
  function recoveryPolicyForJournal(loaded, runId) {
4496
- 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)
4497
3814
  return { ok: false, outcome: "recovery-plan-invalid" };
4498
3815
  return validateRecoveryAuthorization(loaded);
4499
3816
  }
@@ -4521,12 +3838,12 @@ function outcomeForRecoveryResult(result) {
4521
3838
  async function load(target, runId) {
4522
3839
  const lease = await acquireTransitionTargetLease(target);
4523
3840
  try {
4524
- return await readTransitionJournal3(lease.session, runId, { ensure: false });
3841
+ return await readTransitionJournal2(lease.session, runId, { ensure: false });
4525
3842
  } finally {
4526
3843
  await lease.release();
4527
3844
  }
4528
3845
  }
4529
- function ledgerHome2(address) {
3846
+ function ledgerHome(address) {
4530
3847
  if (address.scope === "database") return { scope: "database" };
4531
3848
  if (!address.schema)
4532
3849
  throw new Error(
@@ -4544,24 +3861,24 @@ function recoveryAddresses(journal) {
4544
3861
  return address === void 0 ? [] : [address];
4545
3862
  });
4546
3863
  }
4547
- function formatLedgerHome(home2) {
4548
- return home2.scope === "database" ? "database" : `schema ${home2.schema}`;
3864
+ function formatLedgerHome(home) {
3865
+ return home.scope === "database" ? "database" : `schema ${home.schema}`;
4549
3866
  }
4550
3867
  async function recoverMarkerRefusal(target, journal) {
4551
3868
  const addresses = recoveryAddresses(journal);
4552
3869
  const homes = [];
4553
3870
  for (const address of addresses) {
4554
- const home2 = ledgerHome2(address);
4555
- if (homes.some((candidate) => sameLedgerHome(candidate.home, home2)))
3871
+ const home = ledgerHome(address);
3872
+ if (homes.some((candidate) => sameLedgerHome(candidate.home, home)))
4556
3873
  continue;
4557
- homes.push({ address, home: home2 });
3874
+ homes.push({ address, home });
4558
3875
  }
4559
3876
  const lease = await acquireTransitionTargetLease(target);
4560
3877
  try {
4561
- for (const { address, home: home2 } of homes) {
4562
- const currency = await readPgLedgerScopeCurrency3(lease.session, home2);
3878
+ for (const { address, home } of homes) {
3879
+ const currency = await readPgLedgerScopeCurrency(lease.session, home);
4563
3880
  if (currency.kind === "current") continue;
4564
- 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`;
4565
3882
  return {
4566
3883
  detail,
4567
3884
  refusal: preAppendRefusalFor("ERR-03", {
@@ -4581,7 +3898,7 @@ async function runRecover(runId, options, pool) {
4581
3898
  const owned = pool === void 0 ? (await createDbConnection(options.db)).pool : pool;
4582
3899
  let result;
4583
3900
  try {
4584
- const locked = await withPgTransitionRunLock3(
3901
+ const locked = await withPgTransitionRunLock2(
4585
3902
  owned,
4586
3903
  runId,
4587
3904
  async (target) => {
@@ -4604,9 +3921,9 @@ async function runRecover(runId, options, pool) {
4604
3921
  }
4605
3922
  const writabilityLease = await acquireTransitionTargetLease(target);
4606
3923
  try {
4607
- await assertPgDatabaseWritable2(writabilityLease.session);
3924
+ await assertPgDatabaseWritable(writabilityLease.session);
4608
3925
  } catch (error) {
4609
- if (isPgDatabaseReadOnlyError2(error))
3926
+ if (isPgDatabaseReadOnlyError(error))
4610
3927
  return {
4611
3928
  outcome: "database-read-only",
4612
3929
  detail: error.message
@@ -4627,8 +3944,8 @@ async function runRecover(runId, options, pool) {
4627
3944
  const authorization = loaded.journal.events.length === 0 ? void 0 : recoveryPolicyForJournal(loaded.journal, runId);
4628
3945
  if (authorization && !authorization.ok)
4629
3946
  return { outcome: authorization.outcome };
4630
- const result2 = await createApplier2(
4631
- createPackRegistry3([createPgTransitionPack3({})]),
3947
+ const result2 = await createApplier(
3948
+ createPackRegistry2([createPgTransitionPack2({})]),
4632
3949
  { persist: async () => void 0 }
4633
3950
  ).resume(
4634
3951
  loaded.journal,
@@ -4662,7 +3979,7 @@ async function runRecover(runId, options, pool) {
4662
3979
  }
4663
3980
  return result;
4664
3981
  }
4665
- var recoverCommand = new Command8("recover").description(
3982
+ var recoverCommand = new Command9("recover").description(
4666
3983
  "Observe and classify an attempted durable run; performs no planned target DDL and does not re-authorize assumptions"
4667
3984
  ).argument("<run-id>", "Durable run identifier").requiredOption("-d, --db <url>", "Database connection URL (required)").requiredOption(
4668
3985
  "--plan-digest <sha>",
@@ -4698,11 +4015,11 @@ ${RECOVER_OUTCOME_CONTRACT.map(([outcome, code, description]) => ` ${outcome} -
4698
4015
  // src/commands/release.ts
4699
4016
  import {
4700
4017
  createPgTransitionLessor as createPgTransitionLessor5,
4701
- escapeDiagnosticText as escapeDiagnosticText7,
4018
+ escapeDiagnosticText as escapeDiagnosticText8,
4702
4019
  releasePgManagedAddress
4703
4020
  } from "@dbsp/adapter-pgsql";
4704
4021
  import { acquireTransitionLease as acquireTransitionLease5 } from "@dbsp/core";
4705
- import { Command as Command9 } from "commander";
4022
+ import { Command as Command10 } from "commander";
4706
4023
  async function runRelease(selector, options) {
4707
4024
  const { pool } = await createDbConnection(options.db);
4708
4025
  const schema = options.schema ?? "public";
@@ -4737,18 +4054,18 @@ async function runRelease(selector, options) {
4737
4054
  function formatReleaseHuman(result) {
4738
4055
  if (result.outcome === "released") return "released";
4739
4056
  if (result.outcome === "database-read-only")
4740
- return `database-read-only: ${escapeDiagnosticText7(result.detail)}`;
4057
+ return `database-read-only: ${escapeDiagnosticText8(result.detail)}`;
4741
4058
  if (result.outcome === "release-unavailable")
4742
- return `release-unavailable: ${escapeDiagnosticText7(result.detail)}`;
4059
+ return `release-unavailable: ${escapeDiagnosticText8(result.detail)}`;
4743
4060
  return [
4744
- `release-refused: ${escapeDiagnosticText7(result.detail)}`,
4745
- `address: ${escapeDiagnosticText7(JSON.stringify(result.address))}`,
4746
- `state: ${escapeDiagnosticText7(result.refusal.state)}`,
4747
- `withheld authority: ${escapeDiagnosticText7(result.refusal.withheldAuthority)}`,
4748
- `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)}`
4749
4066
  ].join("\n");
4750
4067
  }
4751
- var releaseCommand = new Command9("release").description(
4068
+ var releaseCommand = new Command10("release").description(
4752
4069
  "End management for one address without touching the database object"
4753
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(
4754
4071
  "--parent <kind:name>",
@@ -4766,15 +4083,15 @@ var releaseCommand = new Command9("release").description(
4766
4083
  const detail = error instanceof Error ? error.message : String(error);
4767
4084
  if (options.format === "json")
4768
4085
  printCliJson({ outcome: "release-refused", detail });
4769
- else console.error(`release-refused: ${escapeDiagnosticText7(detail)}`);
4086
+ else console.error(`release-refused: ${escapeDiagnosticText8(detail)}`);
4770
4087
  process.exitCode = 1;
4771
4088
  }
4772
4089
  });
4773
4090
 
4774
4091
  // src/commands/repl.ts
4775
4092
  import { readFileSync } from "fs";
4776
- import { Command as Command10 } from "commander";
4777
- 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(
4778
4095
  "-d, --db <url>",
4779
4096
  "PostgreSQL connection URL for execution mode (e.g., postgres://localhost/mydb)"
4780
4097
  ).option("-e, --eval <query>", "Execute a single query and exit (batch mode)").option(
@@ -4896,10 +4213,11 @@ function mapReplCasingOption(casing) {
4896
4213
  // src/commands/verify.ts
4897
4214
  import {
4898
4215
  comparePgsqlDatabaseSchema as comparePgsqlDatabaseSchema2,
4899
- createPgsqlAdapter as createPgsqlAdapter4,
4900
- escapeDiagnosticText as escapeDiagnosticText8
4216
+ createPgPhysicalModel as createPgPhysicalModel5,
4217
+ createPgsqlAdapter as createPgsqlAdapter3,
4218
+ escapeDiagnosticText as escapeDiagnosticText9
4901
4219
  } from "@dbsp/adapter-pgsql";
4902
- import { Command as Command11 } from "commander";
4220
+ import { Command as Command12 } from "commander";
4903
4221
 
4904
4222
  // src/verifier.ts
4905
4223
  var CHANGE_TO_DRIFT = {
@@ -4929,6 +4247,10 @@ var CHANGE_TO_DRIFT = {
4929
4247
  // Column enhancements
4930
4248
  alter_column_collation: { type: "collation_mismatch", severity: "warning" },
4931
4249
  alter_column_identity: { type: "identity_mismatch", severity: "warning" },
4250
+ alter_column_auto_increment: {
4251
+ type: "auto_increment_mismatch",
4252
+ severity: "error"
4253
+ },
4932
4254
  // Comments
4933
4255
  add_comment: { type: "comment_mismatch", severity: "info" },
4934
4256
  drop_comment: { type: "comment_mismatch", severity: "info" },
@@ -5041,7 +4363,7 @@ function formatVerifyResult(result) {
5041
4363
  }
5042
4364
 
5043
4365
  // src/commands/verify.ts
5044
- 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(
5045
4367
  "-s, --schema <path>",
5046
4368
  "Path to schema file (default: dbsp.schema.ts)"
5047
4369
  ).requiredOption("-d, --db <url>", "Database connection URL (required)").option("--schema-name <name>", "Database schema name (default: public)").option("--json", "Output as JSON").action(
@@ -5059,14 +4381,18 @@ var verifyCommand = new Command11("verify").description("Compare schema vs real
5059
4381
  try {
5060
4382
  const loaded = await loadSchema(schemaPath);
5061
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
+ });
5062
4390
  const { pool } = await createDbConnection(options.db);
5063
4391
  try {
5064
- const adapter = createPgsqlAdapter4(pool);
5065
- const diff = await comparePgsqlDatabaseSchema2(adapter, schemaModel, {
5066
- ...options.schemaName ? { schema: options.schemaName } : {},
5067
- ...loaded.dbCasing !== void 0 ? { dbCasing: loaded.dbCasing } : {},
4392
+ const adapter = createPgsqlAdapter3(pool);
4393
+ const diff = await comparePgsqlDatabaseSchema2(adapter, physical, {
5068
4394
  onExpressionCanonicalizationWarning: (warning) => console.warn(
5069
- `\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}`
5070
4396
  )
5071
4397
  });
5072
4398
  const dbModel = await adapter.introspect({
@@ -5103,12 +4429,13 @@ var verifyCommand = new Command11("verify").description("Compare schema vs real
5103
4429
  );
5104
4430
 
5105
4431
  // src/index.ts
5106
- var program = new Command12();
4432
+ var program = new Command13();
5107
4433
  var packageJson = createRequire(import.meta.url)("../package.json");
5108
4434
  var jsonFormatCommands = /* @__PURE__ */ new Set([
5109
4435
  "apply",
5110
4436
  "inspect",
5111
4437
  "plan",
4438
+ "migrate",
5112
4439
  "recover",
5113
4440
  "reconcile",
5114
4441
  "release"
@@ -5136,6 +4463,7 @@ program.name("dbsp").description("Schema-first code generation for db-semantic-p
5136
4463
  program.addCommand(generateCommand);
5137
4464
  program.addCommand(introspectCommand);
5138
4465
  program.addCommand(planCommand);
4466
+ program.addCommand(migrateCommand);
5139
4467
  program.addCommand(preflightCommand);
5140
4468
  program.addCommand(applyCommand);
5141
4469
  program.addCommand(inspectCommand);