# v0.60.0 — Code Quality, Test Coverage & CI (Full Details) > **Summary:** [v0.60.0.md](v0.60.0.md) > **Assessment source:** [plans/PLAN_OVERALL_ASSESSMENT_12.md](../plans/PLAN_OVERALL_ASSESSMENT_12.md) > **Findings addressed:** Q-1, Q-2, Q-3, T-1, T-2, T-3, T-4, T-5, C-5, C-6, CI-1, CI-2, CI-3 --- ## Code Quality ### QUAL-1: Scheduler Log Level Standardisation **Assessment finding:** Q-1 (MEDIUM) **Files:** `src/scheduler/dispatch.rs`, `src/scheduler/pool.rs`, `src/scheduler/scheduler_loop.rs` Prior to this release, routine scheduler events (worker registration, lock acquisition, dispatch start) were logged at `log!()` (DEBUG5 in PostgreSQL terms), invisible at the default `log_min_messages = warning`. Recoverable failures also used `warning!()`, making it hard to distinguish between "expected noise" and "something an operator should act on". After this release, the convention is: - `info!()` — normal state transitions and progress (scheduler start, dispatch complete, worker pool size changes). - `warning!()` — recoverable failures that do not stop the scheduler (e.g., a worker failed to acquire a lock and will retry). - `error!()` — failures requiring operator attention (e.g., the scheduler cannot reconnect after 3 retries). The change affects ~40 log call sites across the three files. ### QUAL-2: refresh/codegen.rs Decomposition **Assessment finding:** Q-2 (MEDIUM) **Files:** `src/refresh/codegen.rs` → `src/refresh/sql_fragments.rs` (new), `src/refresh/codegen.rs` (reduced) The new `sql_fragments.rs` module contains: ```rust // Builds the WHERE predicate that restricts a CTE scan to the delta window. pub(crate) fn build_cte_predicate(lsn_col: &str, prev_lsn: &str, new_lsn: &str) -> String // Builds the ON clause for a MERGE statement. pub(crate) fn build_merge_join_condition(key_cols: &[&str]) -> String // Builds the MD5 content-hash column expression for deduplication. pub(crate) fn build_content_hash_column(data_cols: &[&str]) -> String // Builds the MERGE WHEN MATCHED / NOT MATCHED target lists. pub(crate) fn build_delta_target_list( action: MergeAction, cols: &[ColumnSpec], ) -> String ``` Each function has a dedicated unit-test block verifying its output against expected SQL strings for common and edge-case inputs (empty column lists, columns with reserved characters, single-key tables). ### QUAL-3: src/cdc.rs Module Decomposition **Assessment finding:** Q-3 (MEDIUM) The 3 383-line `src/cdc.rs` is split into: | New module | Contents | ~LOC | |------------|----------|-------| | `src/cdc/triggers.rs` | Trigger creation/drop SQL builders, column-name escaping (`cb_col_name`), statement vs row mode, trigger function SQL | ~900 | | `src/cdc/buffer.rs` | Change-buffer DDL, `buffer_base_name_for_oid`, `buffer_qualified_name_for_oid`, buffer initialisation | ~600 | | `src/cdc/compact.rs` | Compaction scheduling, `pg_try_advisory_xact_lock` wrapper, `CompactionResult` enum, holdback classification | ~400 | | `src/cdc/partition.rs` | `should_promote_to_partitioned`, partition-promotion logic, `check_if_publication_needs_rebuild` | ~300 | | `src/cdc/mod.rs` | Public SQL-callable functions, re-exports, shared constants | ~400 | No public API changes. --- ## Test Coverage ### TEST-1: Refresh Orchestrator and Merge Unit Tests **Assessment finding:** T-1 (MEDIUM) New `#[cfg(test)]` modules added to: **`src/refresh/orchestrator.rs`** (12 tests): - `test_cost_model_cold_start_requires_minimum_observations` — asserts mode is not activated until ≥ 3 DIFFERENTIAL + 1 FULL observations. - `test_cost_model_prefers_differential_when_change_ratio_low` — change ratio below `differential_max_change_ratio` → DIFFERENTIAL preferred. - `test_cost_model_prefers_full_when_change_ratio_high` — change ratio above threshold → FULL preferred. - `test_cost_model_recovery_after_consecutive_errors` — after N consecutive errors the mode resets to cold-start. - `test_orchestrator_adaptive_score_boundary_values` — verifies the ±0.15 dead-zone does not flip the mode on noisy data. **`src/refresh/merge/columns.rs`** (8 tests): - Column-list builder for various schema shapes (all-nullable, mixed types, empty list, single column). **`src/refresh/codegen.rs`** → deferred to `sql_fragments.rs` tests (see QUAL-2). ### TEST-2: CDC Pure-Logic Unit Tests **Assessment finding:** T-2 (MEDIUM) New `#[cfg(test)]` modules added to `src/cdc/triggers.rs` and `src/cdc/buffer.rs`: - `test_cb_col_name_escapes_reserved_pgt_prefix` — `__pgt_action` → `__usr___pgt_action` - `test_cb_col_name_passes_through_normal_name` — `order_id` → `order_id` - `test_buffer_base_name_for_oid` — OID 12345 → `changes_12345` - `test_build_changed_cols_bitmask_expr_single_col` — single column → correct bitmask expression - `test_build_changed_cols_bitmask_expr_empty` — no columns → `0::bit(1)` - `test_should_promote_to_partitioned_false_for_regular` — `relkind = 'r'` → no promotion - `test_should_promote_to_partitioned_true_for_partitioned` — `relkind = 'p'` → promote ### TEST-3: DDL Hook Classification Unit Tests **Assessment finding:** T-3 (MEDIUM) **File:** `src/hooks.rs` New `#[cfg(test)]` block with 14 table-driven tests covering: | Input | Expected outcome | |-------|-----------------| | `ALTER TABLE ADD COLUMN` on tracked table | `DdlCommandKind::AddColumn` → mark needs_reinit | | `ALTER TABLE DROP COLUMN` on tracked table | `DdlCommandKind::DropColumn` → mark needs_reinit | | `ALTER TABLE ALTER COLUMN TYPE` | `DdlCommandKind::AlterColumnType` → mark needs_reinit | | `ALTER TABLE RENAME TO` | `DdlCommandKind::RenameTable` → mark needs_reinit | | `CREATE INDEX` | `DdlCommandKind::CreateIndex` → no-op | | Any DDL on untracked table | no-op | | DDL on `pgtrickle.*` internal table | no-op | ### TEST-4: Remaining Fixed Sleep Replacement **Assessment finding:** T-4 (LOW) Fixed sleeps replaced with `wait_for_*` polling helpers in: - `tests/e2e_quota_tests.rs` (3 sleeps) - `tests/e2e_bgworker_tests.rs` (4 sleeps) - `tests/e2e_wal_cdc_tests.rs` (2 sleeps) - `tests/e2e_immediate_tests.rs` (1 sleep) Total sleeps eliminated: 10. The `wait_for_auto_refresh`, `wait_for_condition`, and `wait_for_row_count` helpers in `tests/common/mod.rs` cover all cases. ### TEST-5: Differential Idempotence Proptest **Assessment finding:** T-5 (LOW) **File:** `src/dvm/diff.rs` New `proptest!` block: ``` Given: randomly-generated sequence of INSERT/UPDATE/DELETE rows partitioned into two batches in random order When: batch 1 is applied and the stream table is refreshed, then batch 2 is applied and the stream table is refreshed Then: the stream table contents equal the result of applying all rows in any order followed by a single refresh ``` This tests the idempotence and commutativity of the Z-set delta engine under arbitrary ingest ordering. --- ## Correctness ### COR-5: WAL Decoder OID-Based Table Filter **Assessment finding:** C-5 (MEDIUM) **File:** `src/wal_decoder.rs` The `should_include_change()` filter previously compared the decoded table name (from `test_decoding` output) against the tracked source table's qualified name using string equality. This was fragile for: - Partition routing (child arrives instead of root) - Search-path-sensitive unqualified names - Case-sensitive quoted identifiers After this fix, the decoder resolves each tracked source table's OID via `to_regclass()` once at the start of each poll cycle and compares relation OIDs numerically. String matching is retained only as a fallback for `test_decoding`-format output that does not carry a relation OID. ### COR-6: Publication Rebuild Detects Table-Becomes-Partition **Assessment finding:** C-6 (MEDIUM) **File:** `src/cdc/partition.rs` — `check_if_publication_needs_rebuild` `check_if_publication_needs_rebuild()` now checks: 1. Has `relkind` changed from `'r'` (regular) to `'p'` (partitioned root)? (Existing check.) 2. Has the source OID appeared in `pg_inherits.inhrelid`? (New check.) When condition 2 is detected, the function rebuilds the publication with `publish_via_partition_root = true` and marks the stream table for reinitialisation, preventing the silent CDC freeze that otherwise occurs when a plain table is converted to a partition of another parent. --- ## CI/CD ### CI-1: Path-Filtered Full E2E on PRs **Assessment finding:** CI-1 (MEDIUM) **File:** `.github/workflows/ci.yml` New job `full-e2e-on-dvm-change`: ```yaml if: | github.event_name == 'pull_request' && contains(github.event.pull_request.changed_files, 'src/dvm/') || contains(github.event.pull_request.changed_files, 'src/refresh/') || contains(github.event.pull_request.changed_files, 'src/cdc/') ``` Uses the `docker/build-push-action` cache and the existing `test-full-e2e` recipe. Estimated additional PR gate time: 20 minutes, triggered only when the above paths change. ### CI-2: Dockerfile.e2e Non-Root User **Assessment finding:** CI-2 (LOW) **File:** `tests/Dockerfile.e2e` Added before `CMD`: ```dockerfile USER postgres ``` All test containers now run as the unprivileged `postgres` OS user. ### CI-3: Codecov Per-Module Thresholds **Assessment finding:** CI-3 (LOW) **File:** `codecov.yml` New per-path coverage gates (patch coverage): ```yaml coverage: status: patch: src/cdc/: target: 50% src/hooks.rs: target: 40% src/wal_decoder.rs: target: 40% ``` --- ## Upgrade Notes No SQL schema changes. No `ALTER EXTENSION` migration is required.