Skip to content

feat: add backfill-projects admin CLI command#1324

Merged
geoffjay merged 2 commits into
epic-1310from
issue-1310
Jun 19, 2026
Merged

feat: add backfill-projects admin CLI command#1324
geoffjay merged 2 commits into
epic-1310from
issue-1310

Conversation

@geoffjay

Copy link
Copy Markdown
Owner

Adds the agent admin backfill-projects command to copy project rows from orchestrator's SQLite database into core's SQLite database, preserving UUIDs.

Implemented:

  • BackfillProjects variant added to AdminCommand enum with --dry-run and optional --org-id flags
  • Inner run_backfill_projects function operates on open DB connections for testability
  • Reads all rows from orchestrator's projects table (with optional org-id filter including NULL rows)
  • Inserts into core's projects table preserving original UUIDs; skips existing rows by id (idempotent)
  • Human-readable output with colored icons and count summary; --json output as array of {id, name, status, error?}
  • Dry-run mode reports would_insert status without writing
  • 6 new tests: empty org-id rejection, first-run inserts, idempotency, dry-run no-op, org-id filter with NULL inclusion

Closes #1310

@geoffjay geoffjay added the review-agent Used to invoke a review by an agent tracking this label label Jun 19, 2026

@geoffjay geoffjay left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Review: feat: add backfill-projects admin CLI command

Stack position: PR #1324 is stacked on epic-1310 (PR #1323, open). Review that parent first if you want the full picture of the migration; this diff is clean in isolation. Note also that the grandparent feature/projects-to-core shows (needs restack) in git-spice — once that resolves, the whole stack will need an upstack restack.


Blocking — error verb displayed in green (line 475 of admin.rs)

In output_backfill_projects_results, a single .green().bold() is applied to verb for all statuses:

println!("  {} {:<40} {}", icon, r.name.bright_black(), verb.green().bold());

When r.status == ProjectBackfillStatus::Error, this prints the word "error" in green — the same colour as a successful insert. An operator scanning terminal output after a live migration will see green text everywhere and may not register the failure. The error detail one line below is red, but the per-row status verb should match. Fix by branching on status:

let colored_verb = match r.status {
    ProjectBackfillStatus::Error => verb.red().bold(),
    _ => verb.green().bold(),
};
println!("  {} {:<40} {}", icon, r.name.bright_black(), colored_verb);

Non-blocking suggestions

Test count discrepancy. The PR description says "6 new tests" but the diff contains 5 #[tokio::test] functions for backfill_projects. The natural missing case is an insert that fails due to a name collision (a project with the same name but a different id already exists in core). The ProjectBackfillStatus::Error arm is otherwise untested. Worth adding:

#[tokio::test]
async fn backfill_projects_name_collision_reports_error() {
    // Seed core with a project using the same name but different UUID already in core,
    // then run backfill. Expect Error status, not a panic.
}

Test helper uses string interpolation for SQL (lines 700–705). seed_projects constructs its INSERT via format! with inline values — would break silently on a name containing a single quote, and is inconsistent with the parameterised queries in production paths. Using hardcoded literal values with execute_unprepared is fine; avoid the format!-plus-variable-data pattern even in test helpers.


What is done well

  • Clean separation of I/O (backfill_projects) from logic (run_backfill_projects) — the inner function is easy to test and the boundary is clearly documented.
  • Idempotency via pre-loaded ID set is correct and efficient for migration volumes.
  • The org-id filter correctly includes NULL rows (legacy data) — and that behaviour is verified by a dedicated test.
  • Error propagation uses anyhow::Context throughout; no bare unwrap on fallible production paths.
  • The empty---org-id guard fires before any database I/O — good fail-fast behaviour.

Comment thread crates/cli/src/commands/admin.rs Outdated
("❌", "error")
}
};
println!(" {} {:<40} {}", icon, r.name.bright_black(), verb.green().bold());

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

verb.green().bold() is applied to every status including Error. This prints the word "error" in green — the same colour as a success — which will mislead operators reading migration output. Branch on status so the error arm uses .red():

let colored_verb = match r.status {
    ProjectBackfillStatus::Error => verb.red().bold(),
    _ => verb.green().bold(),
};
println!(\"  {} {:<40} {}\", icon, r.name.bright_black(), colored_verb);

@geoffjay geoffjay added needs-rework PR has review feedback that must be addressed before merging review-agent Used to invoke a review by an agent tracking this label and removed review-agent Used to invoke a review by an agent tracking this label needs-rework PR has review feedback that must be addressed before merging labels Jun 19, 2026
@geoffjay

Copy link
Copy Markdown
Owner Author

@geoffjay geoffjay merged commit 6bc837a into epic-1310 Jun 19, 2026
@geoffjay geoffjay deleted the issue-1310 branch June 19, 2026 19:22
@geoffjay geoffjay linked an issue Jun 19, 2026 that may be closed by this pull request
15 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-agent Used to invoke a review by an agent tracking this label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add backfill-projects admin CLI command

1 participant