Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
For planned future versions, see ROADMAP.md.
Unreleased
[0.20.0] - 2025-07-14
This release delivers the three high-priority architectural improvements identified in the Phase 4 engineering assessment: typed executor configuration, full multi-tenant catalog parameterisation, and automated crash recovery via the compensating-step registry.
The ExecutionContext struct replaces the optional builder pattern for the two safety-critical executor fields (desired_state and connection_string), making it a compile error to omit them. All command handlers (apply, rollback, promote) have been updated accordingly.
Every catalog SQL constant now accepts a CatalogSchema parameter and substitutes the schema name via for_schema(). The validate_catalog_schema_not_overridden() stop-gap from v0.19 is removed. aqueduct init --schema my_catalog creates all catalog tables in my_catalog; the multi-tenant isolation integration test verifies that two projects on the same database with different catalog schemas have fully independent version histories.
The --resume flow now queries aqueduct.ddl_log for rows in status = 'running' before advancing to the checkpoint. For each such row the compensating SQL is executed and the row is updated to status = 'compensated'. A new integration test covers the crash-after-DDL, before-RecordSnapshot scenario.
The OutputEmitter is now stored as a process-wide singleton via OnceLock, initialised in main before command dispatch and accessible via output::emitter(). The init command is the first to use the emitter for its success messages.
The httpmock dependency was replaced with wiremock in v0.17, eliminating the last cargo audit --deny warnings finding (RUSTSEC-2025-0052).
The catalog schema version advances from 8 to 9, adding a status column to aqueduct.ddl_log used by compensating-step recovery.
Added
- Typed
ExecutionContextstruct (ARCH-2):ExecutionContextstruct with requireddesired_stateandconnection_stringfields replaces the optional builder pattern. Omitting either field is now a compile error.for_apply(),for_rollback(), andfor_promote()constructors updated. - Full catalog schema parameterisation (ARCH-1):
CatalogSchemathreaded through every catalog SQL function.ensure_catalog_current(),connect_and_migrate(),read_live_state(),get_latest_dag_version(),detect_extension_installed(),read_ddl_log(),import_from_live(),compute_promotion_plan(),validate_source_clean(), anddestroy_project()all accept&CatalogSchema.validate_catalog_schema_not_overridden()stop-gap removed. - Multi-tenant catalog isolation test:
test_multi_tenant_catalog_isolationverifies that two catalogs (tenant_a,tenant_b) on the same database have independentdag_versionstables with no cross-contamination. - Automated compensating-step recovery (CORR-2):
find_resume_step()queriesaqueduct.ddl_logforstatus = 'running'rows and executes theircompensating_sqlviabatch_executebefore advancing to the resume checkpoint. Rows are updated tostatus = 'compensated'on success. COMPLETE_COMPENSATING_STEP_SQLandMARK_COMPENSATING_APPLIED_SQLconstants incatalog.rsfor the two-phase compensating-step lifecycle.- CORR-2 crash recovery integration test:
test_corr2_compensating_step_recoveryinserts a syntheticstatus = 'running'ddl_log row and verifies that--resumemoves it out of therunningstate. - Catalog schema version 9:
CATALOG_SCHEMA_VERSIONadvances from 8 to 9. The v8→v9 migration adds astatus TEXT NOT NULL DEFAULT 'complete'column toaqueduct.ddl_logand backfills existing rows. OutputEmitterprocess-wide singleton (M-1):output::init_emitter(mode)stores the emitter in astatic OnceLock<OutputEmitter>;output::emitter()returns the global reference.main.rscallsinit_emitterbefore dispatch. Theinitcommand uses the emitter for its success messages, replacing directprintln!calls.
Changed
for_schema(sql, schema)now substitutes= 'aqueduct'string-literal comparisons andCREATE/DROP SCHEMA IF NOT EXISTS aqueductstatements in addition to theaqueduct.dot-prefix references.DestroyOptionsgains acatalog_schema: CatalogSchemafield.- All
cargo check --testscall sites forread_live_state,get_latest_dag_version,detect_extension_installed,read_ddl_log,import_from_live,compute_promotion_plan,validate_source_clean, andDestroyOptions::newupdated throughout CLI commands and integration tests. - Workspace version bumped
0.19.0→0.20.0.
Fixed
executor.rs:import_from_livewas callingread_live_state(client, None)(2-arg form) instead of the 3-argread_live_state(client, None, catalog_schema). Fixed.- Extra trailing semicolons (
await?;;) incommands/promote.rsandcommands/status.rsintroduced by the multi-replace pass. Removed.
[0.19.0] - 2026-05-20
This release eliminates every structural correctness bug identified in the Phase 4 engineering assessment, makes the blue/green view swap fully atomic, and ensures every known correctness regression path is covered by a test that would catch it.
The headline change is that the deployment status row update in SwapConsumerViews is now executed inside the surrounding BEGIN/COMMIT block, eliminating the window where consumer views point at the green schema while the deployment row still shows status = 'active'. If the transaction is rolled back for any reason, both the view assignments and the deployment row revert together.
The heartbeat task is now supervised by a JoinHandle-based wrapper. If the heartbeat task panics — something that could previously produce a silent hang — the executor immediately receives a LockLost signal, stops execution, and surfaces the failure. This closes the last known path to a silent runaway executor.
The mock pg_trickle DDL gains a pgtrickle.paused_nodes table so integration tests can assert precisely which nodes were paused during DDL and that the scheduler was fully resumed after apply or after a failure. Two new test-kit helpers (assert_scheduler_idle and assert_scheduler_paused_for) make these assertions one-liners.
Four executor and CLI quality fixes round out the release: the WaitForConvergence poll query is now guarded by a statement_timeout to prevent a hung pg_trickle query from holding the loop past the configured deadline; status --watch applies exponential backoff on repeated connection failures; the EXPLAIN trade-off in cost.rs is documented with a code comment; and aqueduct init and aqueduct apply now return an explicit NotYetImplemented error when catalog_schema is set to anything other than "aqueduct", preventing silent data mixing in multi-tenant deployments until full schema parameterisation ships in v0.20.
All ten failure-mode test cases identified in the Phase 4 assessment are now present and passing.
Added
- Blue/green atomicity (ARCH-3):
SWAP_BLUE_GREEN_SQL(theblue_green_deploymentsstatus row update) is now executed inside theSwapConsumerViewstransaction. A failed mid-swap now rolls back both view assignments and the deployment status atomically. Integration testswap_consumer_views_atomicityverifies this. - Heartbeat panic supervisor (CORR-3):
tokio::spawn(run_heartbeat(...))is wrapped in aJoinHandle-based supervisor that callslock_lost_tx.send(true)if the heartbeat task panics. Integration testheartbeat_panic_signals_lock_lossverifies the signal. - Observable scheduler mock (TEST-3):
mock_pgtrickle.rsgains apgtrickle.paused_nodes(node_name text PRIMARY KEY, paused_at timestamptz)table.pause_schedulerinserts into it;resume_schedulerdeletes from it. - Test-kit helpers:
assert_scheduler_idle(client)andassert_scheduler_paused_for(client, nodes)added toaqueduct-testkit. Integration testsmock_scheduler_records_pauseandmock_scheduler_idle_after_applydemonstrate usage. statement_timeoutguard forWaitForConvergence(M-9): the pollSELECTis wrapped inBEGIN READ ONLY; SET LOCAL statement_timeout = '{timeout}ms'so a hungpg_tricklequery cannot hold the loop open past the configured deadline. Integration testwaitforconvergence_deadline_respectedverifies the deadline is enforced.- Exponential backoff in
status --watch(M-8): consecutive connection or poll failures back off up to5 × intervalbefore retrying; the counter resets on the next successful poll. - EXPLAIN trade-off comment (M-4):
cost.rs::estimate_rowsnow documents whyformat!("EXPLAIN...")is intentional and safe. The EXPLAIN call is also wrapped in aBEGIN READ ONLY; SET LOCAL statement_timeout = '5s'block. - Catalog schema stop-gap (ARCH-1 partial):
validate_catalog_schema_not_overriddenincatalog.rsreturnsAqueductError::NotYetImplemented { feature: "catalog_schema override" }for any value other than"aqueduct". Called fromaqueduct initandaqueduct applyto prevent silent data mixing until full multi-tenant support lands in v0.20. Integration testcatalog_schema_override_rejected_until_implementedverifies this. - Failure-mode tests (Phase 4): all ten test cases identified in the assessment are
now present and passing:
consumer_sql_injection_rejected_at_apply,heartbeat_panic_signals_lock_loss,swap_consumer_views_atomicity,plan_fail_on_drift_counts_consumer_deltas,destroy_auto_migrates_catalog,catalog_schema_override_rejected_until_implemented,age_identity_file_traversal_rejected,cnpg_preview_requires_valid_cert,waitforconvergence_deadline_respected,mock_scheduler_records_pause. AqueductError::NotYetImplemented { feature }variant added (error code 1308).cnpg_preview_requires_valid_certunit test inpreview.rsverifies that acreate_preview_cnpgcall to a plain-HTTP endpoint fails with a TLS/connection error whenCNPG_INSECURE_SKIP_VERIFYis absent (SEC-2).
Changed
- Workspace version bumped to
0.19.0. - Six user-facing integration files updated to reference version
0.19.0:.github/workflows/aqueduct-plan.yml,.github/workflows/aqueduct-apply.yml,ci/gitlab/aqueduct.gitlab-ci.yml,.pre-commit-hooks.yaml,docs/api-reference.md,docs/introduction.md. ROADMAP.mdstatus line updated tov0.19.0 released.
This release closes every open security finding from the Phase 4 engineering assessment, bringing pg_aqueduct to zero open CVEs and zero open critical or high security issues. The headline change is that consumer view bodies are now validated as a single SELECT statement before any database call is made — meaning a misconfigured or maliciously crafted migration file containing DDL like CREATE TABLE or DROP TABLE can never reach the database, even if it somehow made it into your migrations directory. This is an important defence-in-depth layer for teams running Aqueduct in automated CI/CD pipelines.
Two additional critical security fixes ship in this version: the CloudNativePG preview client now validates TLS certificates by default (previously it silently accepted any certificate), and the age secret backend now validates the identity file path for both path traversal and flag injection before passing it to the age subprocess. Together with the consumer SQL validation, these three fixes mean pg_aqueduct meets the full OWASP A03 (Injection) and A02 (Cryptographic Failures) controls relevant to its operation.
Beyond security, this version improves day-to-day operational quality: aqueduct plan --fail-on-drift now correctly detects drift in consumer views and source tables (not just stream tables), aqueduct destroy auto-migrates a stale catalog before querying it, and several documentation files and CI template files that had stale version pins are updated to match the current release. A new .github/SECURITY.md establishes the project's vulnerability reporting policy.
Added
validate_consumer_sql_is_single_select()is now called immediately before everyCREATE OR REPLACE VIEWin the executor (ManageConsumerViewandSwapConsumerViewsarms). A migration file containing injected DDL is rejected withAqueductError::UntrustedSqlBodybefore any database call is made (SEC-1).- Integration test
consumer_sql_injection_rejected_at_applyconfirms that a consumer migration with aCREATE TABLEbody is rejected at the executor level (SEC-1). - CNPG preview HTTP client now validates TLS certificates by default. Set
CNPG_INSECURE_SKIP_VERIFYto skip verification in development environments only. Emits awarn!when the env var is set (SEC-2). - Unit test
cnpg_tls_default_does_not_skip_verificationconfirms the env-var gate logic (SEC-2). agesecret backend now validates the identity file path (fromSOPS_AGE_KEY_FILE/AGE_KEY_FILE) viavalidate_secret_path()and rejects paths starting with-(flag-injection guard). Unit testsage_identity_file_traversal_rejectedandage_identity_file_flag_injection_rejectedcover both rejection cases (SEC-3)..github/SECURITY.md: security policy with contact address, response SLA, supported version matrix, and a summary of implemented controls (L-5).docs/security.md: new sections covering consumer SQL injection protection, CNPG TLS configuration, andageidentity file security (SEC-1, SEC-2, SEC-3).
Changed
aqueduct plan --fail-on-drift: drift count now includessource_deltasandconsumer_deltasin addition todeltas, matching the already-correctstatusimplementation. Previously, consumer-layer and source-layer drift was silently ignored (M-2).aqueduct destroy: changed fromconnect()toconnect_and_migrate()so a stale catalog schema is auto-migrated before destroy queries it (M-5).release.yml: removed globalFORCE_JAVASCRIPT_ACTIONS_TO_NODE24env var — all actions in the workflow use modern Node.js runtimes that do not require this workaround (M-6). Addedjust gen-docsstep to regeneratedocs/api-reference.mdon every release tag.ingest.rstest helpers:fs::write(...).unwrap()replaced with descriptive.expect("...")calls so a failing CI write produces a clear error message (M-7).fmt.rs: renamedKNOWN_FRONTMATTER_KEYStoKNOWN_DIRECTIVE_KEYSwith updated doc comment explaining it is used to skip already-emitted keys, not enforce ordering (L-1).main.rs: replaced stale// L7 (v0.14)tracking comment with descriptive per-platform CI env var documentation (L-3).Cargo.toml: replaced stale# DEP-1 (v0.14): pre-declare serde_yamlcomment with# YAML serialization for plan/status/diff --format yaml output.(L-4).- Six user-facing integration files updated to reference version
0.18.0:.github/workflows/aqueduct-plan.yml,.github/workflows/aqueduct-apply.yml,ci/gitlab/aqueduct.gitlab-ci.yml(also fixes archive name pattern to match the release pipeline:aqueduct-{version}-{os}-{arch}.tar.gz),.pre-commit-hooks.yaml,docs/api-reference.md,docs/introduction.md(CI-1). version-lintCI job extended to check nine files:README.md,docs/installation.md,docs/api-reference.md,docs/introduction.md,ci/gitlab/aqueduct.gitlab-ci.yml,.pre-commit-hooks.yaml,.github/workflows/aqueduct-plan.yml,.github/workflows/aqueduct-apply.yml, andROADMAP.md. Fails if any disagrees with the workspaceCargo.tomlversion (CI-1, L-6).ROADMAP.mdstatus line updated tov0.18.0 released(L-6).- Workspace version bumped to
0.18.0.
[0.17.0] - 2026-06-10
This release is all about confidence and safety for teams running critical database infrastructure. When you're managing a high-availability PostgreSQL cluster using tools like Patroni, you can now tell Aqueduct exactly where to check that the database is still the primary leader between every migration step. If the database fails over to a replica mid-migration, Aqueduct detects this immediately, stops what it's doing, and marks the migration as interrupted — rather than blindly continuing and potentially corrupting data on a read-only standby. This dramatically reduces the risk of one of the most dangerous scenarios in database operations: a migration that half-completes during a failover event.
Beyond failover protection, this version gives teams unprecedented visibility into what Aqueduct is doing at runtime. Every single migration step — when it starts, how long it takes, whether it succeeded or failed — is now emitted as a structured event, making it trivial to connect Aqueduct to monitoring dashboards, alerting pipelines, or log aggregation systems. The new aqueduct audit command lets you look back at recent migrations and see exactly what happened and when. For teams that need to demonstrate compliance or investigate incidents, this audit trail is invaluable. And with a built-in Prometheus metrics endpoint, you can wire Aqueduct directly into Grafana and get real-time migration dashboards with zero extra tooling.
Added
--patroni-endpoint <url>flag onaqueduct apply. Between each migration step, the CLI callsGET <endpoint>/master; a non-200 response marks the migrationinterruptedand aborts (DOC-2 / v0.17-A).aqueduct_core::hamodule:HaBackendenum,detect_ha_backend(),check_still_primary()(usespg_is_in_recovery()), andcheck_patroni_primary()HTTP check (DOC-2 / v0.17-A).- Between-step primary re-check via
SELECT pg_is_in_recovery()in the executor. On failover, migration is markedinterrupted(v0.17-A). - Per-step JSON events emitted to stderr:
step_start,step_complete,step_failedwithstep_index,step_type,migration_id,project, andduration_msfields (v0.17-B). Documented indocs/cli-events-schema.json. aqueduct auditsubcommand: lists recent migrations with step counts, error messages, and durations. Supports--format table|json|yamland--limit N(v0.17-B).aqueduct.migration_historySQL view (catalog v8): joinsaqueduct.migrationsandaqueduct.migration_stepsfor per-step audit queries (v0.17-B).aqueduct_core::catalog::LIST_MIGRATIONS_FOR_AUDIT_SQLquery constant (v0.17-B).- Prometheus metrics endpoint behind
--features metricsonaqueduct-cli. Pass--metrics-addr 0.0.0.0:9090to expose/metricsduringapply(v0.17-B). - Catalog schema v8: adds
'interrupted'to themigrations.statuscheck constraint and a partial index forinterruptedrows. Includes auto-migration from v7 (v0.17-A). - GitHub-native build provenance attestation in release workflow via
actions/attest-build-provenance@v2. Verify withgh attestation verify(v0.17-C). release-verifyCI job: downloads the linux-amd64 release archive, verifies its SHA256 checksum, and confirmsaqueduct --versionmatches the tag (v0.17-C).- Version linting CI step (
version-lintjob): fails if README.md status banner,docs/installation.md, andCargo.tomlworkspace version disagree (v0.17-D). just publish-dry-runrecipe for dry-run crates.io publish verification (v0.17-C).
Changed
- Catalog schema bumped from v7 to v8 (
CATALOG_SCHEMA_VERSION = 8). aqueduct-coreandaqueduct-testkitCargo.toml:publish = true;aqueduct-cli:publish = false(v0.17-C).docs/ha-operations.mdrewritten to accurately describe implemented behaviors:check_still_primary(), Patroni/mastercheck, per-step events,aqueduct audit, and the recovery runbook. Speculativesystem_identifier/timeline_idcontent removed (v0.17-D).docs/installation.md: updated version example to0.17.0, MSRV to 1.88, added build provenance verification section (v0.17-C/D).ci.ymlsecurity-audit job: removed--ignore RUSTSEC-2025-0052(httpmock replaced).httpmockreplaced withwiremockinaqueduct-coredev-dependencies; all five HTTP mock tests updated to the async wiremock API (v0.17-D).
[0.16.0] - 2026-05-20
Version 0.16 is a polish release focused on making Aqueduct a pleasure to work with, both for humans sitting at a terminal and for automated systems parsing its output. Teams integrating Aqueduct into CI/CD pipelines can now choose between structured JSON, clean YAML, a minimal porcelain format designed for shell scripting, or a completely silent quiet mode that suppresses all decorative output and shows only what matters. These improvements mean that whatever your downstream tooling looks like — Slack alerts, dashboards, custom scripts — Aqueduct can speak its language directly without any fragile text scraping.
Under the hood, this version also significantly raised the quality bar for testing, adding a comprehensive suite of binary-level tests that verify every subcommand's exit codes, help text, and output formats behave exactly as documented. This means fewer surprises when you upgrade — if a command's behavior changed accidentally, a test will catch it before it ships. The aqueduct status command gained a new --poll-interval flag with smarter file-change detection, so in watch mode it only reloads migration files when they actually change, making long-running monitoring sessions faster and lighter on the database.
Added
OutputModeenum (Human,Json,Yaml,Quiet,Porcelain) andOutputEmitterstruct inaqueduct-cli::output; global--quietand--porcelainflags are now routed through the emitter (ERG-1, M11).serde_yamlworkspace dependency; YAML output inplan,status, anddiffsubcommands now usesserde_yaml::to_stringinstead of hand-rolled format strings, correctly escaping quotes, colons, and newlines (ERG-3).just gen-docsrecipe that regeneratesdocs/api-reference.mdfromaqueduct --helpoutput; stale references to--patroni-endpoint,--timeout,--lock-timeout, and--no-costremoved (ERG-4, M3).- Binary CLI tests (
assert_cmd) for every subcommand's exit codes and--helpoutput:quiet_suppresses_decorative_output,porcelain_outputs_key_value_only,yaml_escapes_quotes_and_newlines,plan_help_documents_fail_if_changed,apply_help_documents_dry_run,status_help_documents_fail_on_drift,diff_help_documents_fail_on_drift,destroy_help_documents_dry_run,rollback_help_documents_dry_run,plan_missing_dsn_exits_2,validate_exits_0_on_valid_files,validate_differential_ivm_unsupportable_fails,validate_format_json_produces_valid_json,version_flag_outputs_semver(TEST-2). --poll-intervalflag onaqueduct status(PERF-2).- Spec-hash caching in
status --watch: migration files are only reloaded when any.sqlmtime changes; live state always polled fresh (PERF-2). postgres_version_matrix_min_supportedintegration test verifying the full create/apply/status cycle on PostgreSQL 18+ (H10).CONTRIBUTING.mddocumenting Docker/Testcontainers prerequisites,justrecipes, integration test environment variables, PG version matrix, coding standards, and a step-by-step guide for adding cookbook recipes.
Changed
fmt::format_migrationnow returnsResult<Option<String>>and propagates IO errors;aqueduct fmtprints a diagnostic when a file cannot be read instead of silently treating it as empty (L2).CANONICAL_KEY_ORDERconstant inaqueduct-core::fmtrenamed toKNOWN_FRONTMATTER_KEYSto reflect its actual role (L3).TestDb::connection_stringfield visibility narrowed frompubtopub(crate)to prevent external crates from depending on the internal representation (L14).docs/security.md: AWS, GCP, and Vault secret backend rows updated from "Planned (v0.12)" to "Implemented"; injected SQL trust-boundary note updated to describe theCatalogSchemanewtype introduced in v0.14.docs/api-reference.mdregenerated fromaqueduct --helpoutput.
Fixed
aqueduct status --format yamlnow produces valid YAML that passesserde_yaml::from_strround-trip including special characters.aqueduct plan --format yamlcorrectly escapes project names containing quotes, colons, and newlines.
0.15.0 - 2026-05-20
This release is the one that gives operations teams confidence to run Aqueduct in the most demanding production environments. Database schema changes are inherently risky — if something goes wrong halfway through, you need a clear path back. Version 0.15 introduces "saga-style" compensating actions: before Aqueduct executes any potentially destructive DDL, it first records what it would take to undo that action. If the process crashes or is interrupted, you can resume from exactly where you left off, or roll back cleanly without any manual investigation. For teams who have ever been woken up at 3 AM to manually clean up a half-finished migration, this is a transformative improvement.
Security and resilience round out this release. Aqueduct now validates catalog schema names at the earliest possible point, firmly rejecting any value that could be used to inject SQL — a class of vulnerability that has burned many database automation tools. Row-level security policies, which PostgreSQL uses to control which users can see which data, are now properly preserved across table recreations, meaning a migration that rebuilds a stream table won't silently drop your access controls. And for situations where a migration step is genuinely ambiguous — perhaps a step that appears to have started but you can't confirm it completed — operators now have --force-retry and --force-skip flags to advance past stuck states safely.
Added
- Saga-style compensating steps:
CreateStreamTableandDropStreamTablewrite a compensating action toaqueduct.ddl_logbefore executing DDL, enabling crash recovery via--resume. --force-retry <step-index>and--force-skip <step-index>flags onaqueduct applyto advance past ambiguous steps (require--yes).CatalogSchemanewtype validating catalog schema names at parse time, rejecting SQL injection markers, non-identifier characters, leading digits, and reserved system schema names (pg_catalog,information_schema, allpg_*prefixed names).ExecutionContextenum withApply,Rollback,Promote, andDryRunvariants replacing the optional builder pattern; named constructorsfor_apply(),for_rollback(), andfor_promote().StartBlueGreenDeploymentplan step inserting a row intoaqueduct.blue_green_deploymentswithstatus = 'active'as the first post-lock step.- All
SwapConsumerViewssteps wrapped in a singleBEGIN ... COMMITblock for an all-or-nothing consumer view swap. - Real RLS policy capture via
pg_policies/pg_class.relrowsecuritybeforeDropStreamTable;RecreatePolicystep restores policies after table recreation; failures are non-fatal and logged toaqueduct.ddl_log. rebuild-may-affect-rlslint warning when aFULL-mode table has RLS enabled.- Batch convergence polling:
WaitForConvergenceissues a singleSELECT ... WHERE table_name = ANY($2)per poll interval instead of one query per node. convergence_poll_interval_ms(default 500 ms) andconvergence_timeout_secs(default 300 s) options inBuildPlanOptions.validate_migration_files_diagnosticandvalidate_dag_diagnosticpublic functions returning structuredDiagnosticSetentries with file paths, error codes, and severity levels.aqueduct validate --format jsonoutput matching thelintJSON schema.- Catalog schema v7:
aqueduct.ddl_loggainsmigration_id bigintandcompensating_sql text;aqueduct.blue_green_deploymentsgainsrolled_back_at timestamptzand an updatedbg_status_checkconstraint accepting'rolled_back'.
Changed
- Executor writes
status = 'swapped'after the atomic consumer view swap andstatus = 'retired'afterRetireBlueSchema. - Integration tests now target
postgres:18-alpineexclusively (PostgreSQL 18+ required by pg_trickle).
0.14.0 - 2026-05-20
Version 0.14 is a focused correctness and security release that addressed a number of real-world edge cases discovered through extensive integration testing. The most important of these is that failed migrations are now properly marked as recoverable and can be resumed — previously, a failure in certain code paths could leave the migration in a state where Aqueduct wouldn't offer a clean resume path. A heartbeat mechanism that keeps the database advisory lock alive during long-running migrations now correctly signals the main process when the lock is lost, preventing processes from continuing work on a database they no longer exclusively own.
Three security improvements shipped alongside these correctness fixes. Connection strings that embed plaintext passwords are now caught and rejected for keyword-value style DSNs, matching the protection already in place for URL-style connections. Consumer view SQL bodies are validated to be single SELECT statements before any database objects are created, closing off a potential SQL injection path through migration files. Read-only connections now explicitly begin a READ ONLY transaction and use a safe allowlist for statement timeout values, preventing injection through configuration parameters. Together these fixes ensure that even if a migration file or configuration value is maliciously crafted, Aqueduct won't execute arbitrary SQL against your database.
Added
pg_versionfield inaqueduct status --format jsonoutput.pgtrickle_mock.scheduler_statetable in catalog schema v6; mockpause_scheduler/resume_schedulerfunctions insert and delete rows to assert scheduler state transitions in tests.- Nine new integration tests covering all CORR, SEC, and TEST items.
Fixed
- Progress preserved on recoverable failures; migration marked
recoverable_failureand resumable (CORR-1). - Heartbeat correctly signals the main executor loop via
tokio::sync::watchwhen the advisory lock is lost (CORR-3). - Promotion filters destination state by project (CORR-4).
- Promote command migrates catalog before use and records
spec_jsonb(CORR-5). - Consumer view drops now delete the catalog row so drift counts remain accurate (CORR-7).
- Status drift counting now sums stream, source, and consumer deltas (CORR-8).
aqueduct destroyexits with code 2 on error viaanyhow::bail!instead ofstd::process::exit(1).- DSN-redaction regex compiled once via
LazyLockinstead of per-call. - CI environment variable detection now checks for value
"true"rather than mere presence, preventing false-positive CI detection. - GitHub Actions composite
planandapplyactions now map runner OS/architecture to the correct release archive suffix. action-smokeworkflow exercises composite actions end-to-end with a locally-packaged archive.mdbook testis now a blocking CI step; docs-lint fails if any documented subcommand is missing from the binary.
Security
- Keyword-value DSNs (e.g.
host=... password=secret) now also checked for plaintext passwords, matching URI-style DSN validation (SEC-1). - Consumer SQL bodies validated as a single
SELECTstatement before view creation, preventing DDL injection via migration files (SEC-2). connect_read_onlynow issuesBEGIN READ ONLYbeforeSET LOCAL statement_timeout;sanitize_statement_timeoutallowlist prevents injection via the timeout argument (SEC-3).
0.13.0 - 2026-05-20
Version 0.13 delivers the complete blue/green deployment workflow that enterprise teams need for zero-downtime schema migrations on production databases. When your changes are complex enough to require restructuring the underlying tables — for example, changing which columns are used as grouping keys in a large aggregation — Aqueduct now orchestrates the full sequence automatically: creating a parallel "green" schema, building the new stream tables there, waiting for them to fully catch up with the "blue" production tables, atomically swapping all consumer views in a single transaction, and then retiring the old schema. The entire process is hands-off once initiated, dramatically reducing the skill and attention required to execute what was previously one of the most nerve-wracking database operations.
Two other significant capabilities arrived in this release. Pre- and post-migration hooks let teams wire Aqueduct into their broader operational playbook — for example, firing a Slack notification when a migration starts, or triggering a cache flush after it completes — all configured declaratively in aqueduct.toml. The new --out and --plan flags let teams generate a migration plan during a code review, have a human or automated system approve it, and then execute exactly that pre-approved plan later — with a cryptographic hash check ensuring the plan hasn't been tampered with between approval and execution. This separation of planning from execution is a powerful governance tool for organizations with strict change-management requirements.
Added
- Full blue/green migration step sequence for topology-restructuring diffs
(
--strategy blue-green):CreateGreenSchema,CreateStreamTableInGreen,WaitForConvergence,SwapConsumerViews,RetireBlueSchema. IMMEDIATErefresh mode parsed from-- @aqueduct:refresh_mode = "IMMEDIATE", withPauseImmediate/ResumeImmediateplan steps.--no-immediate-downgradeflag rejecting plans that would temporarily degrade anIMMEDIATEtable.- Pre/post migration hooks from
[apply.hooks]inaqueduct.tomlemitted asRunHookplan steps; pre-hook fires afterLockDag, post-hook fires beforeRecordSnapshot. aqueduct plan --out <file>writes a plan with a SHA-256 spec hash;aqueduct apply --plan <file>executes the pre-computed plan and validates the spec hash to reject stale artifacts.- Catalog schema v5:
aqueduct.migration_stepsrecords every executed step withrunning/done/failedstatus for audit and replay. aqueduct importnow callsensure_catalog_currentand records version 1 inaqueduct.dag_versions; a subsequentaqueduct planreturns an empty plan.- Real
create_preview_cnpgandcreate_preview_neonHTTP implementations using the CNPG Kubernetes API and the Neon management API. - Rollback classification:
Safe,PointInTime, orDataLossbased on WAL retention window;--within-window/--accept-data-lossflags. "schema_version": 1field in all structured JSON CLI events; schema published atdocs/cli-events-schema.json.
0.12.0 - 2026-05-20
Version 0.12 is a major leap forward for production readiness, tackling three areas that enterprise teams consistently ask about: secret management, resilience against infrastructure variation, and confidence in the planner's correctness. Rather than requiring database passwords to live in environment variables or configuration files, Aqueduct can now fetch connection credentials directly from AWS Secrets Manager, GCP Secret Manager, or HashiCorp Vault at runtime. This means your production database password never needs to touch a CI/CD configuration file, a developer's laptop, or a Kubernetes secret — it stays in your secrets vault and is retrieved only at the moment it's needed.
A subtler but equally important improvement is how Aqueduct now deals with different versions of pg_trickle, the underlying PostgreSQL extension it builds on. Rather than assuming all features are available and failing at runtime, Aqueduct now probes the installed version's capabilities at startup and gracefully skips or adapts any step the current version doesn't support. This makes deployments far more robust across environments where the extension version may differ from what was tested in CI. The addition of property-based fuzz testing — automatically generating thousands of random migration scenarios and verifying that the planner never panics and always produces consistent results — gives the entire planning engine a level of correctness assurance that hand-written tests simply can't match.
Added
PgtrickleCapsstruct probing the installed pg_trickle version at startup viato_regprocedure; each capability individually gated for graceful degradation on older builds.WaitForRefreshplan step gated onpgtrickle_caps.has_refresh_status; older deployments skip this step.- Real AWS Secrets Manager (inline SigV4 signing, no AWS SDK dependency), GCP Secret
Manager (bearer token), and HashiCorp Vault (KV v2) HTTP clients using
reqwestwithrustls-tls; all three verified withhttpmockunit tests. build_source_index()building aHashMap<QualifiedName, Vec<QualifiedName>>once per diff;compute_source_deltas()performs O(1) cascade impact lookups.- Catalog schema v4 with four new performance indexes on
dag_versions,migrations, andlockstables. plan_stats()public function computingPlanSummaryfrom&[PlanStep]without executing.collect_subgraph()computing the transitive dependency closure for an anchor table;--tableflag onaqueduct previewlimits preview to that subgraph.- Full integration test suite in a
pgtrickle-integrationCI job against PostgreSQL 18 with mock pg_trickle schema. test_blue_green_topology_restructurecovering diamond DAG creation.- Binary-level CLI tests using
assert_cmdandpredicates. - Project isolation destructive test: applies two projects, destroys one, asserts the other is untouched.
- Property-based fuzz tests via
proptest:build_plannever panics for arbitrary inputs;plan_statsalways consistent withbuild_plan.summary. - Tutorial smoke test parsing bash code blocks in docs and validating known subcommands.
action-smoke.ymlworkflow: builds binary, packages as versioned archive, exercisesplanandapplyagainst a live PostgreSQL 18 service container.reqwest 0.12(rustls-tls),proptest,assert_cmd,predicates, andhttpmockadded as dependencies.
Changed
connect_read_only()andconnect_read_only_with_timeout()now issueSET LOCAL statement_timeoutbeforeBEGIN READ ONLY.estimate_rows()returns typedCostErrorinstead ofanyhow::Error; SQL errors and unsupported plan types surfaced as structured tracing warnings.- Coverage threshold updated to 60% workspace-wide minimum.
build-artifactsinrelease.ymlnow requires thetestjob to pass.- macOS Intel (
macos-13) added to release build matrix asmacos-amd64. rust-versioninCargo.tomlset to1.88(effective MSRV).
Fixed
- Windows builds in
release.ymlnow fail the release when they fail.
Security
rewrite_query_for_preview_ast()rewrites table references using the sqlparser AST instead of string substitution, eliminating query injection risk via schema or table names.
0.11.0 - 2026-05-19
Version 0.11 focuses on the experience of getting help when something goes wrong. All errors, lint warnings, and validation failures that Aqueduct produces now share a single, unified format that includes the exact file and line number where the problem occurred, a stable numeric error code, and a severity level. This means that editor plugins, CI parsers, and custom tooling can all reliably interpret Aqueduct's output without having to guess at error message formats. The stable numeric codes are particularly valuable for teams writing runbooks — instead of matching on potentially-changing error text, you can reference code 1200 for a planning error or 1300 for an execution error and know that code will remain stable across versions.
The documentation story got a major upgrade too. A new CI job now automatically builds the documentation and verifies that every subcommand mentioned in the docs actually exists and responds to --help, so documentation can never silently drift out of sync with the binary. Security also improved: all database connection strings are now scrubbed of passwords before they appear in error messages, log output, or CI logs — a simple but critical safeguard that prevents credentials from leaking into build artifacts or error reports. The aqueduct plan and aqueduct status commands are now strictly read-only at the connection level, making it physically impossible for them to accidentally modify database state even in edge-case error paths.
Added
- Unified
Diagnostictype andDiagnosticSetwith file/line/column context, replacing separateValidationError,LintDiagnostic, and ad-hoc error strings. - Stable numeric error codes on
AqueductError: 1000–1099 database, 1100–1199 configuration, 1200–1299 planning, 1300–1399 execution, 1400–1499 catalog. --fail-on-driftflag foraqueduct diff, exiting 1 when any delta is non-Unchanged.- YAML output format for
aqueduct status(--format yaml). docs/roles.sqlwith SQL grant templates foraqueduct_planner,aqueduct_applier,aqueduct_preview, andaqueduct_destroyroles.docs-buildCI job runningmdbook buildandmdbook teston every push.docs-lintCI job verifying every documented subcommand responds to--helpand that binary--versionmatchesCargo.toml.docs-clijustfile recipe generating a Markdown CLI reference from--helpoutput.
Changed
aqueduct plannow exits 0 by default; exits 1 only when--fail-if-changedor--fail-on-driftis explicitly set.executor.execute()returnsExecutionResult { migration_id, dag_version }instead of a bareu64;apply_completeJSON event emits both fields as separate keys.ManageWalSlotuses typedWalSlotActionenum (Create/Drop) instead of a free-form string, eliminating the "Unknown action" dead code path.- Typed
PlanExecutorconstructors:for_apply(),for_rollback(),for_promote(),for_dry_run(). - Typed DAG state newtypes:
DesiredDagState,LiveDagState,RecordedDagState. lib.rsexportsCLI_VERSION = env!("CARGO_PKG_VERSION")as the single source of version truth.- Example workflow version pins updated from
@v0.4.0to@v0.11.0.
Fixed
aqueduct planandaqueduct statusnow useconnect_read_only()and cannot accidentally mutate data.
Security
redact_dsn()replaces passwords with***in all connection strings shown in error messages, covering both URL-form and keyword-value DSNs.
0.10.0 - 2026-05-19
Version 0.10 closes out a category of subtle bugs that could cause incorrect behavior when working with consumer views — the downstream SQL views that read from stream tables. Previously, a plan that only added, dropped, or modified consumer views without touching any stream tables was silently treated as a no-op and never executed. This was a correctness bug with real consequences: teams could deploy consumer view changes, see no errors, and then discover their views weren't updated because Aqueduct thought there was nothing to do. All consumer operations now correctly flow through the full plan-and-execute path with proper tracking and reporting.
Project isolation — the ability to manage multiple independent DAGs in the same PostgreSQL database — received a significant reliability upgrade. All live state reads now correctly filter by project, meaning one project's tables can never accidentally appear in another project's plan or status. The aqueduct destroy command now verifies that it actually owns the tables it's about to drop, preventing accidental cross-project destruction. Several concurrency scenarios were also hardened: the lock heartbeat is now bound to the specific holder that acquired the lock, so a fresh Aqueduct process starting up won't be fooled by a stale heartbeat from a crashed predecessor into thinking the database is still locked.
Added
- Catalog schema v3:
aqueduct.stream_table_ownership(project, schema_name, table_name, managed_since)withPRIMARY KEY (schema_name, table_name). recoverable_failureadded to theaqueduct.migrationsstatus_checkconstraint.- Six new integration tests: consumer-only apply, concurrent apply race, stale plan detection, two-project isolation, resume step skipping, and holder-bound heartbeat.
Fixed
- Consumer-only plans are no longer silently skipped as no-ops;
PlanSummarytracksconsumer_creates,consumer_drops, andconsumer_alters, andis_empty()/total_changes()include consumer deltas. RecordSnapshotusesRETURNING versionto capture the actual bigserial value assigned by the database.read_live_state()populatessourcesfromspec_jsonbinaqueduct.dag_versionsinstead of always returning empty.read_live_state()andread_live_consumers()filter by project viastream_table_ownership;destroy_projectverifies ownership before dropping tables (--force-unownedto bypass).check_pgtrickle_version()probesto_regprocedurebefore calling to avoid a database error when pg_trickle is absent.classify_delta()no longer panics on missingdesired/actualin theAlterQueryarm; returnsRebuildinstead.- Failed migrations now marked
recoverable_failureand resumable;LockDagalways re-executes on resume, DDL steps skipped only for steps already confirmed complete. - Scheduler pause now runs inside the
LockDagexecutor arm after the lock is acquired, preventing a pre-lock pause from affecting other projects. - Heartbeat SQL is now holder-bound (
WHERE project = $1 AND holder = $2); zero rows affected logged as a lock-stolen warning. - Project lock released on all exit paths via an explicit cleanup block.
- Step progress checkpoint writes are now fatal rather than silently continuing.
aqueduct apply --dry-runuses a plain connection to avoid upgrading the catalog schema during previews.DROP TABLEindestroyand executor no longer usesCASCADE; opt in with--force-cascade.aqueduct rollbacknow enforces--accept-data-losswhen the rollback plan contains rebuild-class steps.- First-time stream table creations increment
create_count, notrebuild_count, preventing false-positive maintenance-window blocking on initial deployments. - Lock holder string is now
aqueduct/{version}/{hostname}/{pid}/{ts}, making concurrent processes distinguishable. - Scheduler pause failure is now fatal by default.
0.9.0 - 2026-05-19
Version 0.9 is a turning point in how operators interact with Aqueduct day-to-day. The new aqueduct diff command provides a clear, human-readable comparison between what your migration files describe and what currently exists in the live database — across every table, every column, and every configuration detail. This makes drift visible at a glance before you commit to running any migration, transforming what was previously a moment of uncertainty before a production apply into a clear-eyed, informed decision. Whether you're checking before a deployment, investigating after an incident, or auditing compliance, aqueduct diff gives you the full picture.
This release also introduced the secret backend system that underpins enterprise-grade credential management: database connection strings can now reference secrets stored in AWS, GCP, Vault, SOPS-encrypted files, or age-encrypted files using a simple ${secret:BACKEND:KEY} syntax, keeping credentials entirely out of configuration files and CI logs. Every successful aqueduct apply now emits a structured JSON event with the migration ID, version numbers, and project name — making it trivial for CI systems to capture and act on migration results. For teams using interactive terminals, a confirmation prompt before any apply ensures no one accidentally runs a migration they didn't intend to.
Added
aqueduct diffsubcommand computing a per-table diff between desired state (migration files) and live database state; supports--tableand--format text|json|yaml|markdown.aqueduct plan --fail-on-drift: exits 1 when changes are detected, 0 when the DAG is up-to-date.- Interactive confirmation prompt on
aqueduct applyin a TTY ("Apply N changes? [y/N]");--yes/-yskips it. - Structured
apply_completeJSON event on stderr withmigration_id,from_version,to_version, andprojectfields; CI composite action reads these as output variables. aqueduct validate --strict: promotes warnings to errors and exits non-zero.aqueduct plan --format yamloutput.${secret:BACKEND:KEY}inline secret syntax in DSN strings; supported backends:env,aws,gcp,vault,sops,age.--quiet/--porcelainglobal flags suppressing info-level log output.- Eight new
PlanStepvariants:RecreatePolicy,DetachOutbox,ReattachOutbox,ManageWalSlot,PauseImmediate,ResumeImmediate,WaitForRefresh,RunHook. cargo auditsecurity-audit CI job running on every push and pull request.- Coverage threshold enforced at 70% with
fail_ci_if_error: true. - DSN masking in CI composite
planaction via::add-mask::.
Changed
aqueduct planexit codes changed:0= no changes,1= changes detected (or--fail-on-drift),2= error.- CLI errors now exit with code
2instead of1. - All
regex::Regexpatterns compiled once viastd::sync::LazyLock. cdc_modevalues normalized at parse time:"ROW"/"STATEMENT"→"trigger","WAL"→"wal","NONE"/"DISABLED"→"none"; unknown values rejected with a clear error.- Integration tests run against PostgreSQL 18 (
postgres:18-alpine).
Fixed
status --watchcreates a fresh database connection on each poll tick; network errors logged as warnings rather than aborting the watch.
Security
validate_secret_path()rejects key arguments containing..to prevent path traversal in SOPS and Age subprocess invocations.- DSN strings containing cleartext passwords are now rejected; pass
--allow-plaintext-passwordto override.
0.8.0 - 2026-05-19
Version 0.8 tackles one of the hardest problems in database automation: what happens when things go wrong in the middle of a migration. The executor now tracks progress at the granularity of individual steps, persisting a checkpoint to the database after each one. If a migration is interrupted — whether by a network blip, a process crash, or an operator hitting Ctrl-C — re-running aqueduct apply --resume will pick up from exactly where it left off, skipping every step that already completed successfully. For long-running migrations that touch large tables, this means an interruption no longer requires starting over from scratch.
The lock mechanism that prevents two Aqueduct processes from stepping on each other's work was made significantly more robust. A background task now continuously renews the advisory lock while a migration is running, sending a heartbeat every ten seconds on a dedicated database connection. This ensures that a migration taking longer than PostgreSQL's default lock timeout can complete without its lock expiring mid-flight. If the heartbeat connection is lost — for example, because the database restarted — the main migration process is immediately notified and can shut down cleanly rather than continuing to issue commands to a database that may no longer be in the expected state.
Added
- Resume-aware step-level progress tracking:
PlanExecutorskips steps belowlast_completed_stepon resume and persists the step index toaqueduct.migrations.progressafter each non-structural step. - Lock heartbeat: background tokio task renewing the advisory lock every 10 s via a dedicated connection; shuts down cleanly after plan completion or error.
- Catalog schema v2 via
CATALOG_INIT_V2_SQL;ensure_catalog_current()applies the v1→v2 migration on every connect.
Changed
build_plan()now returnsResult<Plan>instead ofPlan; all call sites propagate the error with?.AlterStreamTableexecutor passesnew_queryas the 6th argument topgtrickle.alter_stream_table.aqueduct plan --validate-ivmnow callsvalidate_ivm_supportability()forDIFFERENTIALtables andvalidate_sql_syntax()for others.- Column removal classified as
Rebuildunless the column is in the trailing position of the select list. run_steps()uses a drain-then-pause protocol:pause_schedulerbefore the first migration step,resume_schedulerin a deferred cleanup guard.- Diamond DAG convergence nodes trigger group promotion: all transitive ancestors elevated to the highest migration class in the group.
poll_once()inaqueduct statusloads the migrations directory and computes a real diff for accurate drift detection.spec_jsonbinaqueduct.dag_versionsnow stores the full serialisedDagStateinstead of an empty{}.
Fixed
aqueduct rollbacknow deserialisesspec_jsonbfrom thedag_versionsrow instead of re-reading migration files from disk.CreateStreamTableon non-pg_trickle instances returnsAqueductError::PgTrickleNotInstalledinstead of silently creating bare tables.
Removed
- Unused
deadpool-postgresworkspace dependency.
0.7.0 - 2026-05-18
Version 0.7 is the documentation and confidence-building release. Thirty detailed cookbook recipes were added covering every migration pattern that Aqueduct supports — from simply adding a column, to changing GROUP BY keys, to performing a full blue/green topology restructure — each with a complete before-and-after example and a matching integration test. Whether you're a new team trying to understand what Aqueduct can do, or an experienced operator looking for the exact right pattern for a non-obvious migration, the cookbook gives you a concrete, tested answer rather than requiring you to reason from first principles.
Public benchmarks were published for the first time, quantifying the core value proposition in plain numbers: on a 200-node DAG with a 5-node change set, Aqueduct applies changes in roughly 6 milliseconds — compared to approximately 180 seconds that a naive drop-and-recreate approach would take on the same workload. That's a 28,000× speed improvement. Security and high-availability documentation were written in depth, covering everything from setting up least-privilege database roles to handling failover events and crash recovery. With 197 tests now passing across unit, integration, and CLI test suites, this version represents the highest level of tested confidence the project had achieved to that point.
Added
- 30 cookbook patterns in
docs/cookbook/covering every migration class (Free, In-place, Rebuild, Blue/green, Create, Drop), each with an integration test. - Public benchmarks in
benchmarks/: 200-node DAG, 5-node change set — targeted apply (~6 ms) vs. drop/recreate (~180 s), ~28 000x speed improvement. docs/security.md: least-privilege role setup, secret backends, read-only guarantees, and audit trail.docs/ha-operations.md: primary detection for Patroni, CloudNativePG, and Stolon; maintenance windows; crash recovery; failover handling.docs/api-reference.md: complete CLI command reference, front-matter directive table, andaqueduct.tomlschema.- 197 tests total (101 unit + 63 integration + 33 CLI); all passing, none skipped.
0.6.0 - 2026-05-18
Version 0.6 completes the core operational toolset that teams need to manage Aqueduct migrations across the full software delivery lifecycle. The aqueduct promote command solves the environment promotion problem elegantly: rather than manually copying migration files between staging and production and hoping you didn't miss anything, you point Aqueduct at your source environment, it validates that everything is clean, computes exactly what needs to happen in the destination environment, asks for confirmation, and records the promotion event in the audit trail. This makes the act of "promoting to production" repeatable, auditable, and safe — the same migration files validated in staging are what get applied to production.
The aqueduct destroy command gives teams a clean, safe way to tear down all objects associated with a project — useful for cleaning up ephemeral preview environments or decommissioning retired workloads. High-availability primary detection was formalized with support for Patroni, CloudNativePG, and Stolon, so Aqueduct can reliably determine whether it's talking to a writable primary before taking any action. The aqueduct status --watch mode, combined with configurable alerting thresholds, means teams can leave a monitor running in production that immediately flags any drift between what's deployed and what the migration files describe — turning Aqueduct into a lightweight continuous compliance tool as well as a migration executor.
Added
aqueduct promotecommand promoting a migrations directory from one environment to another, with source-clean validation, destination plan computation, interactive confirmation, and promotion recorded inaqueduct.migrations.- Encrypted secret backends: AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, SOPS-encrypted files, and age-encrypted files.
${secret:BACKEND:KEY}inline syntax in DSN strings.aqueduct status --watchlong-running watch mode with configurable--interval,--max-drift-count, and JSON output for alerting pipelines.- HA primary detection for Patroni (HTTP
GET /master), CloudNativePG (GUC), and Stolon (pg_stat_activity), returning a typedHaBackendenum. - Planner fuzzing test: 8 LCG-seeded random mutations over a 4-table DAG asserting
the plan-convergence invariant (deterministic seed
0xDEAD_BEEF_CAFE_1234). aqueduct destroycommand tearing down all project-owned stream tables, consumer views, and catalog rows; requires--confirmor--dry-run.- 167 tests total (101 unit + 33 integration + 33 CLI); all passing, none skipped.
0.5.0 - 2026-05-18
Version 0.5 bridges two worlds that data teams live in simultaneously: dbt, the SQL transformation tool that millions of data engineers use to define their data models, and Aqueduct, which manages the real-time stream tables that power live analytics. The new aqueduct ingest --from dbt-target command reads the compiled output of a dbt project and automatically generates a canonical Aqueduct migrations directory. Schedule, refresh mode, CDC mode, schema, and dependency relationships are all inferred from the dbt model configuration, so no manual translation work is required — what you express in dbt flows directly into Aqueduct.
The practical impact is that teams can maintain a single source of truth in their dbt project and have Aqueduct automatically stay in sync. The examples/dbt-roundtrip/ example included with this release demonstrates the full workflow: run dbt, ingest the results into Aqueduct, validate, plan, apply, and roll back — end to end, without any manual steps. Because the ingest command is idempotent, it can safely be re-run after every dbt compile without risk of creating duplicates or inconsistencies, making it trivial to slot into CI/CD pipelines that already build dbt artifacts.
Added
aqueduct ingest --from dbt-targetcommand reading compiled dbt artefacts and generating a canonical aqueduct migrations directory; idempotent.- Front-matter directives populated from dbt model config (
+schedule,+refresh_mode,+cdc_mode,+schema,+depends_on). examples/dbt-roundtrip/demonstrating the full ingest → validate → plan → apply → rollback round-trip.
0.4.0 - 2026-05-18
Version 0.4 is the release that makes Aqueduct a first-class member of your development workflow rather than just a tool you run manually before deployments. The aqueduct fmt command automatically reformats migration files to a canonical style — consistent SQL keyword casing, stable front-matter ordering, no trailing whitespace — and a --check mode makes it easy to enforce this standard in CI without modifying files. The aqueduct lint command catches five categories of common mistakes before they ever reach a database: schedules that are too aggressive for the workload, queries that IVM can't optimize, consumer views that reference sources that don't exist, and more.
The GitHub Actions composite actions make it genuinely effortless to add Aqueduct to any GitHub-hosted project: a single line in your PR workflow will install the binary, run aqueduct plan, and post the plan as a PR comment so reviewers can see exactly what database changes a PR will produce before merging. The same experience is available for GitLab CI via provided templates. Pre-commit hooks for fmt, validate, and lint mean that quality checks happen before code is even committed, catching problems at the earliest possible moment. Together, these integrations turn database migration management from an afterthought into a first-class, automated part of the code review process.
Added
aqueduct fmtcommand canonicalising migration files: SQL keyword casing, stable front-matter directive order, trailing whitespace removal.--checkmode for CI.aqueduct lintcommand with five rules:schedule-too-aggressive(warning),full-refresh-no-filter(warning),differential-ivm-unsupportable(warning),cypher-source-missing(error),consumer-source-not-found(error).--fail-on-warnand--format jsonflags.trickle-labs/pg-aqueduct/.github/actions/plan@v0.4.0composite action: installs binary, runsaqueduct plan --format markdown, posts plan as PR comment.trickle-labs/pg-aqueduct/.github/actions/apply@v0.4.0composite action: runsaqueduct applywith--resumesupport, masks DSN secrets.- Example workflows:
aqueduct-plan.yml(on PRs touchingmigrations/) andaqueduct-apply.yml(on push tomain). - GitLab CI templates in
ci/gitlab/aqueduct.gitlab-ci.yml. ci/hooks/aqueduct-pre-commitgit hook runningfmt --checkandvalidate..pre-commit-hooks.yamldefiningaqueduct-fmt,aqueduct-validate, andaqueduct-linthooks for the pre-commit framework.- 118 tests total (73 unit + 25 integration + 20 CLI); all passing, none skipped.
0.3.0 - 2026-05-18
Version 0.3 introduces three powerful capabilities that together define Aqueduct's vision for sophisticated, production-safe database change management. Blue/green plan steps — creating a parallel schema, building new stream tables, waiting for convergence, atomically swapping consumer views in a single transaction, retiring the old schema — provide the foundation for zero-downtime migrations of even the most complex DAG topologies. Consumer views give teams a clean way to expose stable, versioned SQL interfaces to downstream applications, completely decoupling what the application queries from the underlying stream table implementation.
The aqueduct preview command is perhaps the most innovative addition in this release: it creates a throwaway copy of your entire DAG in a scratch schema, populated with a sampled subset of real production data, so you can run and test your new migration against realistic data without any risk of affecting production. This works natively against any PostgreSQL instance and has stub implementations for CloudNativePG and Neon-managed databases. For data teams building and iterating on complex analytics queries, the ability to spin up a safe, realistic preview environment in seconds — and tear it down just as quickly — fundamentally changes how development and testing workflows operate.
Added
- Blue/green plan step variants:
CreateGreenSchema,CreateStreamTableInGreen,WaitForConvergence,SwapConsumerViews,RetireBlueSchema. - Consumer view management: declare views in
migrations/consumers/with@aqueduct:kind = consumer,@aqueduct:source, and@aqueduct:expose_as; newConsumerSpec,ConsumerDelta, andConsumerDeltaKindtypes;ManageConsumerViewplan step. aqueduct preview --branch <name>creating a throwaway copy of the DAG in a scratch schema withTABLESAMPLE-sampled data; native, CloudNativePG stub, and Neon stub backends.- Optional companion extension support:
aqueduct.ddl_logDDL event log,detect_extension_installed(),read_ddl_log(). - Catalog schema v2:
aqueduct.ddl_log,aqueduct.consumer_views,aqueduct.blue_green_deploymentstables.
0.2.0 - 2026-05-18
Version 0.2 introduces the intelligence at the core of Aqueduct's value proposition: the migration classifier. When you change a migration file, Aqueduct doesn't just blindly re-execute everything — it analyzes what changed and classifies the migration into one of four cost categories: Free (can be applied with zero downtime and no data movement), In-place (a structural change made with a simple ALTER TABLE), Rebuild (requires recreating the table, typically within a maintenance window), or Blue/green (a topology-level restructure requiring the full zero-downtime workflow). This classification drives every decision Aqueduct makes about how to safely apply your changes.
A particularly important extension of this classifier is the automatic cascade analysis for ALTER TABLE changes. When a source table that stream tables depend on changes — for example, a column is renamed or its type changes — Aqueduct automatically identifies every stream table in the DAG that references that source and escalates them to a Rebuild classification. This prevents the silent failures that plague naive migration tools, where a source table change breaks dependent views in ways that aren't discovered until queries start returning errors. The new --explain-cost flag rounds out this release by showing per-step row count estimates and duration projections, giving operators a clear picture of what a migration will actually cost before committing to run it.
Added
- Full migration classifier decision tree: Free (schedule /
cdc_mode/refresh_modeDIFF→FULL), In-place (add or drop a column), Rebuild (rename column, changeGROUP BY, change or add/remove a JOIN, changeWHERE, FULL→DIFF), and Blue/green (topology restructure). @aqueduct:cypher_sourcefront-matter directive for pg_eddy Cypher-backed stream tables.- ALTER TABLE cascade analysis (Tier 1):
AlterBaseTableplan step plus automatic Rebuild cascade for all stream tables referencing the altered source table. aqueduct plan --explain-costshowing per-step row counts (viaEXPLAIN FORMAT JSON) and duration estimates.- 85 tests total (unit + integration + CLI); all passing, none skipped.
0.1.0 - 2026-05-18
Version 0.1 is the foundation on which everything else is built. At its core, Aqueduct solves a problem that every team managing real-time analytics in PostgreSQL eventually hits: how do you safely change a complex web of interdependent materialized views and stream tables — built with extensions like pg_trickle — without causing outages, data loss, or hours of manual database administration? The answer is to treat database schema changes the same way software engineers treat code changes: with version control, automated planning, safe execution, and the ability to roll back. This first release establishes all of those primitives.
The key capabilities in this release — automatic DAG inference from SQL FROM/JOIN clauses, cycle detection, migration classification, advisory locking for crash-safe serialization, read-only plan and status commands, structured JSON logging for CI, and standby detection via pg_is_in_recovery() — together form a complete and production-usable system even at this initial version. The minimal example included in the repository shows a 3-node DAG going from nothing to a fully-managed real-time analytics pipeline in a handful of configuration lines. With 58 tests passing and five CI jobs running on every commit, the project launched with a commitment to quality and correctness that every subsequent release has built upon.
Added
aqueduct init,plan,apply,status,validate,rollback,import, andunlockCLI commands.aqueduct.*catalog schema:dag_versions,migrations,locks, andcluster_profiletables.- SQL front-matter parsing (
-- @aqueduct:key = value) forkind,schedule,refresh_mode,cdc_mode,depends_on, andowneddirectives. - DAG dependency inference from SQL
FROM/JOINclauses viasqlparser(pure Rust, no C build dependencies). - Cycle detection in the DAG with clear pre-DDL error reporting.
- Migration classification into four cost classes: Free, In-place, Rebuild, and Blue/green.
- Template variable substitution:
{{ var.NAME }}from per-targetvarsinaqueduct.toml;${ENV_VAR}from the environment. allow_full_refresh = falseconfig blocking Rebuild-class plans.maintenance_windowconfig gating Rebuild and Blue/green steps to a configured time window.- Standby detection via
pg_is_in_recovery(), preventing applies against hot standbys. - Read-only transactions for
planandstatuscommands. - Advisory lock with TTL expiry for crash-safe serialisation.
- Structured JSON logs on stderr when
--log-format jsonis set or when running in CI ($CI,$GITHUB_ACTIONS,$GITLAB_CI,$CIRCLECI). application_nameset toaqueduct/<project>/<migration_id>on every connection, making in-flight migrations visible inpg_stat_activity.- 58 tests (42 unit, 10 integration, 6 CLI); all passing, none skipped.
- Five GitHub Actions CI jobs: lint, unit tests, integration tests, CLI tests, and coverage.
examples/minimal/— 3-node DAG example with a completeaqueduct.toml.ESSENCE.md— architecture overview and design principles.justfile— developer convenience recipes (test,lint,fmt,coverage).