Skip to content

v3.1.0

Choose a tag to compare

@github-actions github-actions released this 29 May 02:14
· 6 commits to main since this release

[3.1.0] — 2026-05-29 — Interruption-Safe Ledger (crash / SIGTERM safety)

A migration interrupted mid-run — a SIGTERM from an orchestrator (Argo Rollouts
pre-init, a Kubernetes Job timeout), a SIGKILL after the grace period, or node
death — is now safe to restart. Previously the ledger row was written only after
the migration body completed, so an interruption left no record and the next run
re-ran the whole migration, double-applying non-idempotent data operations. v3.1
adds a two-tier safety model. Backward compatible: existing migrations and custom
IMigrationRecordStore implementations keep working unchanged.

Highlights

  • Tier 1 — in-flight sentinel (all providers, fail-closed). The runner writes a
    durable Kind=InProgress sentinel row before a migration body and deletes it after
    the journal write. On restart, a leftover sentinel means the migration was
    interrupted: [DataMigration] and unannotated migrations fail closed
    (MigrationInterruptedException) until an operator sets ForceResume;
    [StructuralOnly] migrations reap the sentinel and re-run (idempotent replay); cron
    and non-journaled migrations are exempt. Mirrors Flyway's failed-state /
    golang-migrate's dirty-flag discipline.
  • Tier 2 — transaction-scoped apply (Postgres, fail-clean). Where the engine can
    wrap a migration's body and its journal write in one transaction, an interruption
    rolls back both atomically — no partial data, no ledger row, no sentinel, no operator
    step. Shipped for Postgres (a shared NpgsqlConnection/transaction enrolled by
    both the resource runner and the record store). The capability seam lets any provider
    opt in.
  • Per-provider tiers. Postgres = Tier 2 (fail-clean). MongoDB, Couchbase, Aerospike,
    OpenSearch = Tier 1 (fail-closed) — for three of them that is the only engine-correct
    option (no transaction can wrap DDL-heavy migrations); the MongoDB seam is ready for a
    future replica-set opt-in. Tier 1 is the universal net for every provider.
  • Postgres ledger constraint widened. The kind CHECK constraint now accepts
    Recovery (3) and InProgress (4), with an idempotent in-place upgrade for existing
    deployments.
  • MongoDB CreateCollection is now idempotent (guard-on-exists), matching the
    Couchbase/Aerospike create style, so a structural replay is a no-op.

New public surface

  • MigrationRecordKind.InProgress, InProgressRecord, MigrationInterruptedException
  • MigrationOptions.ForceResume (promoted from the OpenSearch provider options)
  • ITransactionalRecordStore, IMigrationTransactionScope, MigrationContext.AmbientTransaction

Decisions

  • ADR-0027 — Tier-1 marker-before-work
  • ADR-0028 — Tier-2 transaction-scoped apply

Tests

428 core + 889 squash unit tests (per target framework); 7 Postgres integration tests
including atomic body+journal rollback/commit and interrupt → fail-clean restart.