v0.19.0
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_deployments
status 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 a
pgtrickle.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)and
assert_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 hung
pg_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 why
format!("EXPLAIN...")is intentional and safe. The EXPLAIN call is also wrapped in a
BEGIN READ ONLY; SET LOCAL statement_timeout = '5s'block. - Catalog schema stop-gap (ARCH-1 partial):
validate_catalog_schema_not_overridden
incatalog.rsreturnsAqueductError::NotYetImplemented { feature: "catalog_schema override" }
for any value other than"aqueduct". Called fromaqueduct initandaqueduct apply
to 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 a
create_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 every
CREATE OR REPLACE VIEWin the executor (ManageConsumerViewandSwapConsumerViews
arms). A migration file containing injected DDL is rejected with
AqueductError::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_rejectedand
age_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_deltasand
consumer_deltasin addition todeltas, matching the already-correctstatus
implementation. 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.md
on 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.