Skip to content

feat(pgpm-core): applyBundle — safety wrapper (dry-run, integrity/namespace gate, atomic, timeout) over PgpmMigrate#1421

Open
pyramation wants to merge 5 commits into
mainfrom
feat/pgpm-apply
Open

feat(pgpm-core): applyBundle — safety wrapper (dry-run, integrity/namespace gate, atomic, timeout) over PgpmMigrate#1421
pyramation wants to merge 5 commits into
mainfrom
feat/pgpm-apply

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

Summary

Atom #5: a safety-first wrapper that applies a MigrationBundle to a target database. It powers applyMigrationBundle / applyTranspiledMigrationBundle.

Stacked on the transpiler branch (#1420) → bundle (#1419) → pgpm-ast (#1418); targets main so CI runs the full suite. Net new-to-this-PR: bundle/apply.ts + its test + one export line.

Nothing here re-implements deployment — it composes verifyBundle (integrity gate), materializeBundle (bundle → module dir), and PgpmMigrate.deploy/.verify (execution), adding the safety envelope around them.

// pure pre-flight — no DB, no disk
previewBundleApply(bundle, { validateReferences? }): {
  name, deployOrder, bundleIssues, namespaceViolations, rollbackPlan // = reverse(deployOrder)
}

applyBundle(bundle, {
  config,                 // target DB (PgConfig)
  dryRun?,                // analyze only; never touches the DB
  verify?,                // run verify after deploy
  timeoutMs?,             // wall-clock budget -> BundleApplyError(timedOut)
  useTransaction = true,  // atomic apply => rollback on any failure
  validateReferences?,    // caller AST check for transpiled bundles
  allowUnverified?,       // default false: refuse a tampered bundle
  workDir?, migrateOptions?,
}): Promise<ApplyBundleResult>  // { preview, dryRun, executed, deploy?, verify?, durationMs, timedOut }

Safety gates, all before any DB write: refuse if verifyBundle finds digest/order issues (unless allowUnverified), and refuse if the caller's validateReferences reports references outside the target namespace (the applyTranspiledMigrationBundle guarantee). dryRun returns the full preview (deploy order + rollback plan) without connecting. Execution materializes to a temp dir (auto-cleaned), deploys in a single transaction so a mid-apply failure rolls the DB back rather than leaving it half-applied, races an optional timeoutMs, and returns an explicit result. As with the transpiler, core stays parser-agnostic — the namespace check is a caller callback.

Caveat: timeoutMs is a client-side wall-clock guard; combine with a DB-level statement_timeout for hard server-side enforcement.

Testing

  • New apply.test.ts: 7/7 — pure preview (order/integrity/rollback plan, namespace violations); DB-free gates (dry-run never executes, refuses tampered bundle, refuses namespace violations); live integration (materialize+deploy atomically then verify; re-apply skips already-deployed) via MigrateTestFixture.
  • Full pgpm/core suite green: 71 suites / 469 tests (Postgres up). tsc --noEmit and eslint clean.

Link to Devin session: https://app.devin.ai/sessions/ad40d16f38a349b48eb7ce9375e27e6e
Requested by: @pyramation

@pyramation pyramation self-assigned this Jul 22, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant