Skip to content

feat(automation): add rollout ergonomics - #21

Merged
BjornMelin merged 1 commit into
mainfrom
feat/automation-rollout-ergonomics
May 7, 2026
Merged

feat(automation): add rollout ergonomics#21
BjornMelin merged 1 commit into
mainfrom
feat/automation-rollout-ergonomics

Conversation

@BjornMelin

Copy link
Copy Markdown
Owner

Summary

  • add automation rollout for read-only readiness, rules validation, candidate preview, command plan output, and first-wave trash blocking
  • add dry-run-default automation prune for stale local automation snapshot cleanup with retention/status filters and --execute
  • update operator docs and JSON error metadata for the new commands

Verification

  • rtk test cargo test rollout -- --nocapture
  • rtk test cargo test prune_automation_runs -- --nocapture
  • rtk test cargo test automation_prune_validation -- --nocapture
  • rtk err cargo fmt --check
  • rtk err cargo clippy --all-targets --all-features -- -D warnings
  • rtk test cargo test
  • rtk err cargo run -- automation rollout --json
  • rtk err cargo run -- automation rollout
  • rtk err cargo run -- automation prune --older-than-days 30 --json
  • rtk err cargo run -- automation prune --older-than-days 30
  • rtk test bash -lc 'set +e; cargo run --quiet -- automation prune --older-than-days 0 --json >/tmp/mailroom-prune-invalid.json; status=$?; set -e; [ "$status" -eq 2 ]; jq -e ".success == false and .error.code == \"validation_failed\" and .error.operation == \"automation.prune\"" /tmp/mailroom-prune-invalid.json >/dev/null'
  • rtk err cargo run -- paths --json
  • rtk err cargo run -- doctor --json

Copilot AI review requested due to automatic review settings May 7, 2026 07:05
@qodo-code-review

Copy link
Copy Markdown
ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one.

@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 77b173f1-7742-4868-b636-c170df4fbf3f

📥 Commits

Reviewing files that changed from the base of the PR and between fbccca2 and d7d8fa4.

📒 Files selected for processing (16)
  • README.md
  • docs/operations/automation-rules-and-bulk-actions.md
  • docs/operations/verification-and-hardening.md
  • src/automation/mod.rs
  • src/automation/model.rs
  • src/automation/output.rs
  • src/automation/service.rs
  • src/cli.rs
  • src/cli_output/errors.rs
  • src/cli_output/tests.rs
  • src/handlers/automation.rs
  • src/lib.rs
  • src/store/automation/mod.rs
  • src/store/automation/tests.rs
  • src/store/automation/types.rs
  • src/store/automation/write.rs
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.rs: Use anyhow for command dispatch and top-level context in src/lib.rs; prefer typed thiserror errors in Gmail, workflow, and store layers in Rust
Keep error enums local to the layer that owns the failure semantics; do not introduce a repo-wide catch-all error enum unless it clearly reduces total code and cognitive load
For new CLI JSON contracts, normalize success and failure to one top-level shape: { "success": true, "data": ... } for success and { "success": false, "error": { "code": ..., "message": ..., "kind": ..., "operation": ..., "causes": [...] } } for failure
Keep error.code stable and operator-oriented, keep error.kind for deeper subsystem detail, and keep error.causes to an ordered message chain only in JSON error contracts
Do not include debug or backtrace payloads in the JSON error contract; use stderr and Rust backtrace env vars for deep diagnostics instead
Preserve existing human-facing error text unless it is misleading, ambiguous, or missing required operator action
When adding or changing CLI failures, keep exit codes in a small stable bucket set rather than creating one-off codes per variant
Keep blocking SQLite and filesystem work behind tokio::task::spawn_blocking; do not treat running spawn_blocking work as abortable
Add focused error-path tests for every new failure class; if CLI JSON or exit-code behavior changes, add contract tests for the new output and exit mapping in the same pass

Files:

  • src/cli_output/errors.rs
  • src/lib.rs
  • src/handlers/automation.rs
  • src/store/automation/tests.rs
  • src/automation/mod.rs
  • src/store/automation/write.rs
  • src/automation/output.rs
  • src/store/automation/types.rs
  • src/cli.rs
  • src/automation/model.rs
  • src/store/automation/mod.rs
  • src/automation/service.rs
  • src/cli_output/tests.rs
**/*.{js,jsx,ts,tsx,md,txt,json}

📄 CodeRabbit inference engine (Custom checks)

Reject Unicode EM DASH (code point U+2014) in comments and string literals. Use -- instead. Hard FAIL if any newly introduced content includes U+2014.

Files:

  • README.md
  • docs/operations/automation-rules-and-bulk-actions.md
  • docs/operations/verification-and-hardening.md
docs/{operations,workflows}/**

📄 CodeRabbit inference engine (AGENTS.md)

docs/{operations,workflows}/**: Put operator procedures in docs/operations/ or docs/workflows/
Update operator-facing docs in the same change whenever CLI error contracts, JSON envelopes, or exit-code behavior change

Files:

  • docs/operations/automation-rules-and-bulk-actions.md
  • docs/operations/verification-and-hardening.md
docs/**

📄 CodeRabbit inference engine (AGENTS.md)

Keep docs concrete and aligned with the current binary surface

Files:

  • docs/operations/automation-rules-and-bulk-actions.md
  • docs/operations/verification-and-hardening.md
🔍 Remote MCP Firecrawl, GitHub Copilot

Perfect. Now I have enough information to provide a comprehensive review. Let me compile my findings:

Summary of Gathered Context

Based on my investigation of the pull request and related code, I found important information regarding security practices, CLI patterns, and implementation details:

1. SQL Injection Prevention & Parameter Handling,

The PR's prune_automation_runs function in src/store/automation/write.rs uses rusqlite's parameter binding mechanism correctly. According to the Firecrawl web search results, the recommended approach in Rust is to use prepared statements with libraries like rusqlite for SQLite databases to prevent SQL injection. The implementation uses params_from_iter() with Value::Text() wrapper types, which ensures all user inputs (status values, account IDs) are properly parameterized and cannot inject SQL code. The status list is constructed dynamically with placeholders (vec!["?"; input.statuses.len()].join(", ")), but the values themselves are passed through the params vector, maintaining safety.

2. Dry-Run Pattern Best Practices

According to sources on CLI best practices, a critical safety practice for destructive operations is to support a --dry-run flag that simulates the operation without making changes. The PR implements this pattern well—automation prune defaults to dry-run mode (execute: false) and requires an explicit --execute flag for deletion. The implementation also includes warnings in the output to remind users they're in dry-run mode, which aligns with best practices for safe CLI tool design.

3. CLI Argument Validation

The AutomationPruneStatusArg enum in src/cli.rs is correctly implemented as a clap::ValueEnum, which provides compile-time validation. The From trait implementation mapping CLI args to the service layer type is clean and type-safe. Notably, the --older-than-days parameter is validated to be non-zero in the service layer (returning InvalidPruneWindow error), preventing nonsensical prune operations.

4. Rollout Safety Design

The rollout feature explicitly blocks trash rules in the first wave (lines from the diff show: blocked_rule_ids ... filter(|rule| rule.action_kind() == AutomationActionKind::Trash)). This is a thoughtful safeguard that prevents destructive actions from being applied without explicit user review, aligning with the documented first-wave micro-batch safety approach.

5. Test Coverage

The PR includes three rollout tests demonstrating key behaviors:

  • rollout_reports_missing_rules_without_persisting_run - validates no persistence occurs on error
  • rollout_blocks_trash_rules_for_first_wave - validates blocker behavior
  • rollout_previews_candidates_without_persisting_run - validates preview-only nature

For prune operations, two tests verify:

  • prune_automation_runs_dry_run_reports_without_deleting - dry-run doesn't delete
  • prune_automation_runs_execute_deletes_runs_and_cascades_detail - execution deletes and cascades correctly, including verification that in-progress applying runs are NOT deleted

Sources Used:

    • Web search for SQL injection prevention and CLI dry-run patterns
    • Repository file inspection for implementation details
    • PR diff analysis for feature design and test verification
🔇 Additional comments (27)
src/cli_output/errors.rs (1)

119-121: Validation mapping for rollout/prune input errors is consistent.

This preserves the existing JSON error contract and validation exit bucket behavior.

src/lib.rs (1)

226-229: Operation metadata wiring for new automation subcommands looks good.

Both automation.rollout and automation.prune correctly propagate the json flag and operation identifiers.

Also applies to: 238-241

src/cli_output/tests.rs (1)

169-180: Good contract test coverage for prune validation failures.

This is a focused test that protects both JSON error mapping and exit-code behavior.

src/handlers/automation.rs (1)

28-37: New rollout/prune handler branches are wired cleanly.

Request construction and output handling match the existing automation command pattern.

Also applies to: 48-62

src/store/automation/mod.rs (1)

13-17: Store module exports for prune support are consistent.

The re-export surface cleanly exposes the new store-level prune types and write operation.

Also applies to: 21-21

src/store/automation/types.rs (1)

275-289: New prune store request/report types are well-scoped.

These additions keep prune persistence concerns clearly in the store layer.

README.md (1)

76-76: README command examples are updated appropriately.

Including rollout and prune in the native command list improves operator discoverability.

Also applies to: 81-81

src/store/automation/tests.rs (1)

104-171: Prune store tests cover the critical safety and execution paths well.

The dry-run and execute cases both validate counts and persistence outcomes with good precision.

Also applies to: 173-264

docs/operations/verification-and-hardening.md (2)

165-178: LGTM!

The documentation updates for Phase 5 correctly introduce the rollout command and the new inspection items (blockers, warnings, candidates), aligning well with the new CLI surface and the safety-first verification workflow.


238-242: LGTM!

The commands recap correctly includes the new automation rollout and automation prune commands with appropriate defaults.

src/automation/mod.rs (1)

6-11: LGTM!

The module re-exports are well-organized, cleanly exposing the new rollout and prune request/status types alongside the corresponding service functions.

docs/operations/automation-rules-and-bulk-actions.md (2)

110-115: LGTM!

The new rollout command documentation clearly explains the first-wave readiness checking and candidate preview behavior, with appropriate examples showing both unrestricted and rule-specific rollouts.


150-159: LGTM!

The prune documentation clearly communicates the dry-run default behavior, status filters, and the important safety guarantees (local-only deletion, no Gmail mutation, no in-progress run targeting).

src/cli.rs (2)

283-328: LGTM!

The new Rollout and Prune subcommands follow the established CLI patterns in this codebase. The --execute flag for prune correctly defaults to dry-run mode, and the --older-than-days requirement ensures explicit age specification.


331-345: LGTM!

The AutomationPruneStatusArg enum with its From implementation cleanly bridges CLI argument parsing to the service layer's status type.

src/store/automation/write.rs (2)

269-324: LGTM!

The prune_automation_runs implementation correctly handles:

  • Early return with zeroed report when no statuses are specified
  • Parameterized SQL queries via params_from_iter preventing SQL injection
  • Atomic count-then-delete within a transaction
  • Proper separation of matched counts vs deleted counts for dry-run reporting

The reliance on CASCADE deletes for child tables (automation_run_candidates, automation_run_events) is appropriate given the FK relationships.


326-337: LGTM!

The prune_params helper cleanly constructs the parameter vector in the correct order matching the SQL predicate (account_id, cutoff_epoch_s, then statuses).

src/automation/output.rs (2)

73-144: LGTM!

The AutomationRolloutReport output implementation follows the established patterns, with proper TSV formatting for candidates and clear separation of blockers, warnings, next steps, and command plan in the plain text output.


165-200: LGTM!

The AutomationPruneReport output implementation correctly displays all prune-related metrics and follows the same output conventions as other automation reports.

src/automation/service.rs (5)

161-239: LGTM!

The rollout function implements a well-designed first-wave readiness check:

  • Validates limit upfront
  • Gracefully handles missing/invalid rules as blockers rather than hard errors
  • Blocks trash rules for first-wave safety
  • Generates actionable command plan and next steps
  • Does not persist any run state (read-only operation)

247-308: LGTM!

The prune_runs function correctly implements the dry-run-by-default pattern with:

  • Upfront validation of older_than_days > 0
  • Status normalization defaulting to Previewed when unspecified
  • Safe cutoff calculation using saturating_sub
  • Clear warnings distinguishing dry-run from execution mode

507-557: LGTM!

The resolve_rollout_candidates helper correctly:

  • Short-circuits when trash rules are detected, returning empty candidates
  • Reuses existing build_run_candidates logic for candidate selection
  • Transforms candidates to summary format for rollout output

632-652: LGTM!

The prune status helpers provide clean mapping between prune-specific statuses and run statuses, with sensible default normalization to Previewed when no statuses are specified.


1384-1508: LGTM!

The new rollout tests provide good coverage of key behaviors:

  • rollout_reports_missing_rules_without_persisting_run: Verifies blocker reporting and no DB persistence
  • rollout_blocks_trash_rules_for_first_wave: Verifies trash rule blocking
  • rollout_previews_candidates_without_persisting_run: Verifies successful preview without persistence
src/automation/model.rs (3)

16-45: LGTM!

The new request types and AutomationPruneStatus enum are well-designed. The as_str() method correctly mirrors the #[serde(rename_all = "snake_case")] serialization, ensuring consistency between JSON output and programmatic string usage.


70-96: LGTM!

The AutomationRolloutReport and AutomationRolloutCandidateSummary structs capture all relevant rollout information including verification state, rule validation, candidate preview, and actionable guidance (blockers, warnings, next steps, command plan).


112-125: LGTM!

The AutomationPruneReport struct provides comprehensive prune operation feedback including matched/deleted counts and dry-run awareness through warnings and next steps.


Walkthrough

This PR implements two new automation subcommands: automation rollout for previewing matching candidates without persisting a run, and automation prune for deleting old automation snapshots. The implementation spans data models (request/response structs, storage types), CLI arguments, service functions with candidate resolution and validation, database write operations with conditional deletion, command handlers, output formatting, error classification, and comprehensive tests verifying dry-run and execution behaviors. Documentation updates clarify the rollout first-wave readiness workflow and prune safety guarantees.

Possibly related issues

  • BjornMelin/mailroom#10: This PR adds new rollout/prune APIs and service-level functions to the same src/automation/service.rs file that issue #10 targets for decomposition, directly overlapping in scope.

Possibly related PRs

  • BjornMelin/mailroom#19: Extends the same automation feature set and modifies identical error-classification code in src/cli_output/errors.rs to handle new AutomationServiceError variants.
  • BjornMelin/mailroom#11: Introduces foundational automation modules, CLI structure, and store operations that this PR builds upon with new rollout and prune capabilities.
🚥 Pre-merge checks | ✅ 8 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.64% which is insufficient. The required threshold is 50.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (8 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(automation): add rollout ergonomics' follows conventional commits format with a 'feat' type and scope, clearly summarizing the main addition of automation rollout functionality.
Description check ✅ Passed The PR description is directly related to the changeset, detailing the addition of 'automation rollout' and 'automation prune' commands along with documentation updates, which aligns with the code changes across multiple files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
React + Next.Js Best Practices ✅ Passed Not applicable. This is a Rust CLI project, not React/Next.js. The check scope explicitly requires React, TypeScript, and Next.js—none present here.
Docstring & Research Standards ✅ Passed This is a Rust project with 0 TypeScript/JavaScript files and no Biome configuration. The custom check is designed for TS/JS projects and is not applicable.
Web Interface Guidelines ✅ Passed The "Web Interface Guidelines" check is not applicable to this PR. This is a Rust CLI backend tool with no web UI code—only Rust, documentation, and database operations are modified.
Google Python Style Compliance ✅ Passed This PR modifies only Rust source files (.rs), documentation (.md), and configuration files. It contains no Python files. The Google Python Style Guide check is not applicable to this codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/automation-rollout-ergonomics
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/automation-rollout-ergonomics

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds operator-facing ergonomics around automation rollouts and local snapshot lifecycle management, extending the existing automation CLI/service/store layers with read-only rollout readiness checks and a dry-run-by-default pruning command.

Changes:

  • Introduces automation rollout to validate rules, run readiness checks, preview candidate summaries without persisting a run, and block first-wave trash rules.
  • Introduces automation prune to report/delete stale local automation snapshots with status filters and --execute to perform deletion.
  • Updates CLI output error classification, operator docs, and adds store/service test coverage for the new workflows.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/store/automation/write.rs Adds SQLite-backed prune implementation with matched/deleted counts.
src/store/automation/types.rs Introduces store-layer prune input/report types.
src/store/automation/tests.rs Adds unit tests covering dry-run vs execute pruning behavior and cascade expectations.
src/store/automation/mod.rs Re-exports prune types and write API.
src/lib.rs Adds operation metadata for automation.rollout and automation.prune (JSON error metadata).
src/handlers/automation.rs Wires new CLI subcommands into automation service entrypoints.
src/cli.rs Adds automation rollout and automation prune clap definitions and status enum.
src/cli_output/errors.rs Classifies new automation validation errors for consistent exit codes / JSON error envelopes.
src/cli_output/tests.rs Adds regression test for prune validation error mapping.
src/automation/service.rs Implements rollout readiness/preview flow and prune orchestration (cutoff calculation, status normalization).
src/automation/output.rs Adds plain/JSON output rendering for rollout and prune reports.
src/automation/model.rs Adds request/report models for rollout and prune (including rollout candidate summary).
src/automation/mod.rs Exposes new automation APIs/constants.
README.md Documents example usage for new commands.
docs/operations/verification-and-hardening.md Updates hardening/verification operator flow to include rollout and prune.
docs/operations/automation-rules-and-bulk-actions.md Adds rollout + prune docs, output model notes, and updated safety/ops sequence.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@BjornMelin
BjornMelin merged commit 2bd7eea into main May 7, 2026
7 checks passed
@BjornMelin
BjornMelin deleted the feat/automation-rollout-ergonomics branch May 7, 2026 07:13

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d7d8fa4a9e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/automation/service.rs
));
}

let command_plan = rollout_command_plan(&selected_rule_ids, request.limit);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Suppress runnable plans while rollout has blockers

When rollout finds blockers, such as a selected trash rule, this still builds command_plan from the blocked selected_rule_ids, so the JSON/plain output contains runnable automation run ... and automation apply ... commands for the very rule the rollout says is unsafe for the first wave. Because the docs present these as exact follow-up commands, an operator copying the plan can bypass the rollout block; omit the run/apply plan or generate it only when blockers is empty.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants