Migration squashing

Consolidate recorded auto-diff history with voltro db squash, preserving original operations and snapshot ownership.

voltro db squash marks eligible applied auto-diff plans as squashed and records one synthetic history entry carrying the declared schema fingerprint. It changes migration bookkeeping, not the application schema or data.

Squash does not accelerate replay of declarative migrations: the declarative boot path does not replay those historical operations. It compares the latest recorded fingerprint with the current declarations and, when required, computes a new diff against the live schema.

Run from the CLI

voltro db squash --before 2026-06-01 --note 'consolidate v1 migrations'

Use this to mark a reviewed set of auto-diff history entries as consolidated. Original rows remain queryable; squash does not prune the history table or create a database backup. The local dashboard also supports a preview and explicit confirmation on one replica. Cloud does not execute schema-management writes.

Flag Required Meaning
--before yes Select unsquashed auto-diff plans applied before this date
--note no Note stored on the synthetic entry

Saved-plan application with voltro db apply --plan ... records source: 'file' and is excluded from squash, just like file-based migration history. Ordinary declarative voltro db apply records auto-diff entries.

Validation and atomic update

The helper holds the shared migration lock and performs these steps in one SQL transaction:

  1. Select eligible auto-diff rows with squashedAt IS NULL before the cutoff.
  2. Compare the newest eligible row's fingerprint with the current declared-schema fingerprint. Refuse a mismatch or an empty selection.
  3. Stamp eligible rows with squashedAt, retaining their operations and metadata.
  4. Insert a unique squash-snapshot entry with the fingerprint, note, database timestamps, and operations: { kind: 'squash-snapshot', rowsSquashed: <N> }.

A failed update or insertion rolls back both writes. A retry evaluates the unchanged history. Concurrent calls cannot both consolidate the same eligible set.

A fingerprint refusal is not proof of unapplied changes: a cutoff before a newer schema change can also select an older fingerprint. Inspect the selected history and current declarations. Apply genuinely pending changes through your normal migration process; choose a cutoff whose newest eligible state matches the current declarations. Do not edit history fingerprints to bypass the check.

Confirming an exact selection

Custom operator tools can call previewMigrationSquash({ before, fingerprint }) from @voltro/database/sql. It reads the complete eligible set, not the last 20 dashboard history rows, and returns count, planIds, and an opaque token. It performs no writes. The fingerprint must come from the trusted current schema declarations, not an untrusted browser value.

Pass the token as expectedPreview to squashMigrationPlans when the operator confirms. The helper re-reads eligibility under the migration lock and transaction; added, removed, or already-squashed selected rows invalidate the token and refuse the write. Fetch and review a fresh preview before retrying. A token is a selection check, not authorization; the host must still enforce credentials and replica scope.

Ordinary host endpoints

SQL development and serve hosts expose GET /_voltro/inspect/migrations/squash-preview?before=<date> with Inspect read authentication. The response is the complete preview; the host derives the fingerprint from its own discovered declarations and SQL dialect.

Like other Inspect reads, the HTTP response wraps the preview in data and carries origin, scope, and completeness metadata. Its scope is process: the selection uses this replica's declarations, which may differ during a rolling update. The local transport unwraps data for the shared page. Targeted preview and write requests keep the same selected replica even when another process forwards them. An unavailable target fails; the gateway does not execute the write locally.

POST /_voltro/inspect/migrations/squash requires Inspect write authentication and { before, expectedPreview, note? }. A missing or malformed confirmation token returns 400 before the host operation. A changed selection refuses the write; request and review a new preview. These endpoints do not apply pending migrations and are unavailable on memory-only hosts.

Confirm in the local dashboard

Open an app's Migrations page, select one replica, enter a cutoff and optional note, and choose Preview history. The preview covers the complete eligible history, even when the timeline shows only 20 entries. Review the count and confirm. There is no minimum history size.

Changing the cutoff, note, app, or replica discards confirmation. Cancel returns focus to the preview action; Escape also cancels. Preview errors preserve your input. A failed write is not automatically retried: obtain a fresh preview before confirming again.

Custom shared-page hosts supply both usePreviewSquash and useSquashHistory. Forward the preview's token as expectedPreview without synthesizing it from displayed rows. Both calls must use the same single replica; set squashTarget to its stable identity, or null for combined selections. Remount the page when the app or endpoint changes. Read-only hosts omit the providers.

What happens on the next boot

The declarative boot path reads the latest recorded fingerprint, including a synthetic squash entry. A matching fingerprint can take the existing fast path; a changed declaration follows normal planning against the live database. It does not filter and replay plans by squashedAt or the synthetic entry's creation date.

A fresh database still needs normal schema provisioning. Copying only the squash entry into an empty database does not create tables. Squash is neither a portable schema snapshot nor a replacement for database backups.

The history entry has source: 'squash-snapshot'. Inspect-created entries record appliedBy: 'inspect:squash' and environment: 'inspect'. Shared history types and read-only RPCs preserve the source and arbitrary recorded environment labels. Custom history renderers must accept these values.

A synthetic entry reports opCount: 0: its metadata describes consolidated plans, not executed schema operations. Original plans retain their operation counts.

What stays available

  • Original history and operation metadata remain in _voltro_migration_plans.
  • Exact soft-drop ownership remains available; restoration still requires retained physical objects and an unambiguous, complete recorded set.
  • File-based migrations and their rollback mechanism are unchanged.
  • Normal drift inspection and subsequent declarative schema changes remain available.

There is no automatic reverse-squash command. Removing bookkeeping rows is not a schema rollback. Use the documented rollback or soft-drop recovery path for the operation you need.