refactor: rip out the legacy TrackedRepository / repositories path
CI / Check (pull_request) Successful in 5m29s
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped

Onboarded targets are now the sole persisted entity. The legacy
`TrackedRepository` model, the `repositories` collection, the `/repositories`
API, the Repositories dashboard page, the one-shot migration, and the
`UNIFIED_PIPELINE` transition flag are all removed. Net −1.7k LOC.

Agent
- New internal `pipeline::repo_view::RepoView` (non-persisted) replaces the
  `TrackedRepository` model; it's projected from an `OnboardedTarget` + its code
  artifact by `RepoView::from_target` (the old `repo_view_from_target`), so the
  scan/PR-review pipeline is byte-for-byte the same behaviour it already ran on
  the unified path — only the type's origin changed.
- `run_scan` always runs the unified `run_target`; the legacy `orchestrator::run`
  and the `unified_pipeline` flag are gone. `run_pr_review` resolves the target
  from `onboarded_targets`.
- Webhooks (github/gitea/gitlab), the CVE monitor, graph build, chat embeddings,
  health stats, and the pentest repo lookup all read `onboarded_targets`.
- `delete_target` now cascades the full downstream set (findings, sbom, scans,
  cve, tracker issues, graph, embeddings, DAST targets + pentest sessions and
  their children) — matching the old repository delete.
- `get_ssh_public_key` moved to the health handler; `repositories()` accessor,
  `repos.rs`, and `migrate/` deleted.

Core
- `TrackedRepository` removed; `ScanTrigger` stays. `unified_pipeline` config
  field removed.

Dashboard
- Repositories page + route deleted; overview / graph / chat / pentest-wizard
  read onboarded targets; `infrastructure/repositories.rs` trimmed to just the
  SSH-key fetch.

Tests
- Legacy repositories-API and migration integration tests removed; tenant
  isolation, cascade-delete, and stats tests repointed to `/targets` /
  `onboarded_targets`. `git.rs` gains a `sanitize_repo_dir` unit test.

Local: fmt clean; agent/mcp clippy clean; dashboard server+web compile; core +
agent lib tests (32) pass; integration tests compile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-07-13 21:17:41 +02:00
co-authored by Claude Opus 4.8
parent 87f240b26f
commit dadccb9264
46 changed files with 386 additions and 2123 deletions
@@ -113,15 +113,16 @@ async fn delete_repo_cascades_to_dast_and_pentest_data() {
// Create a repo
let resp = server
.post(
"/api/v1/repositories",
"/api/v1/targets",
&json!({
"name": "cascade-test",
"git_url": "https://github.com/example/cascade-test.git",
"target_type": "web_app",
"artifacts": [{ "kind": "git_repo", "source_ref": "https://github.com/example/cascade-test.git", "branch": "main" }],
}),
)
.await;
let body: serde_json::Value = resp.json().await.unwrap();
let repo_id = body["data"]["id"].as_str().unwrap().to_string();
let repo_id = body["data"]["_id"]["$oid"].as_str().unwrap().to_string();
// Insert DAST target linked to repo
let target_id = insert_dast_target(&server, &repo_id, "cascade-target").await;
@@ -140,9 +141,7 @@ async fn delete_repo_cascades_to_dast_and_pentest_data() {
assert_eq!(count_docs(&server, "dast_findings").await, 1);
// Delete the repo
let resp = server
.delete(&format!("/api/v1/repositories/{repo_id}"))
.await;
let resp = server.delete(&format!("/api/v1/targets/{repo_id}")).await;
assert_eq!(resp.status(), 200);
// All downstream data should be gone
@@ -161,15 +160,16 @@ async fn delete_repo_cascades_sast_findings_and_sbom() {
// Create a repo
let resp = server
.post(
"/api/v1/repositories",
"/api/v1/targets",
&json!({
"name": "sast-cascade",
"git_url": "https://github.com/example/sast-cascade.git",
"target_type": "web_app",
"artifacts": [{ "kind": "git_repo", "source_ref": "https://github.com/example/sast-cascade.git", "branch": "main" }],
}),
)
.await;
let body: serde_json::Value = resp.json().await.unwrap();
let repo_id = body["data"]["id"].as_str().unwrap().to_string();
let repo_id = body["data"]["_id"]["$oid"].as_str().unwrap().to_string();
// Insert SAST finding and SBOM entry
let mongodb_uri = std::env::var("TEST_MONGODB_URI")
@@ -209,9 +209,7 @@ async fn delete_repo_cascades_sast_findings_and_sbom() {
assert_eq!(count_docs(&server, "sbom_entries").await, 1);
// Delete repo
server
.delete(&format!("/api/v1/repositories/{repo_id}"))
.await;
server.delete(&format!("/api/v1/targets/{repo_id}")).await;
// Both should be gone
assert_eq!(count_docs(&server, "findings").await, 0);
@@ -3,5 +3,4 @@ mod dast;
mod findings;
mod health;
mod onboarding;
mod repositories;
mod stats;
@@ -1,110 +0,0 @@
use crate::common::TestServer;
use serde_json::json;
#[tokio::test]
async fn add_and_list_repository() {
let server = TestServer::start().await;
// Initially empty
let resp = server.get("/api/v1/repositories").await;
assert_eq!(resp.status(), 200);
let body: serde_json::Value = resp.json().await.unwrap();
assert_eq!(body["data"].as_array().unwrap().len(), 0);
// Add a repository
let resp = server
.post(
"/api/v1/repositories",
&json!({
"name": "test-repo",
"git_url": "https://github.com/example/test-repo.git",
}),
)
.await;
assert_eq!(resp.status(), 200);
let body: serde_json::Value = resp.json().await.unwrap();
let repo_id = body["data"]["id"].as_str().unwrap().to_string();
assert!(!repo_id.is_empty());
// List should now return 1
let resp = server.get("/api/v1/repositories").await;
let body: serde_json::Value = resp.json().await.unwrap();
let repos = body["data"].as_array().unwrap();
assert_eq!(repos.len(), 1);
assert_eq!(repos[0]["name"], "test-repo");
server.cleanup().await;
}
#[tokio::test]
async fn add_duplicate_repository_fails() {
let server = TestServer::start().await;
let payload = json!({
"name": "dup-repo",
"git_url": "https://github.com/example/dup-repo.git",
});
// First add succeeds
let resp = server.post("/api/v1/repositories", &payload).await;
assert_eq!(resp.status(), 200);
// Second add with same git_url should fail (unique index)
let resp = server.post("/api/v1/repositories", &payload).await;
assert_ne!(resp.status(), 200);
server.cleanup().await;
}
#[tokio::test]
async fn delete_repository() {
let server = TestServer::start().await;
// Add a repo
let resp = server
.post(
"/api/v1/repositories",
&json!({
"name": "to-delete",
"git_url": "https://github.com/example/to-delete.git",
}),
)
.await;
let body: serde_json::Value = resp.json().await.unwrap();
let repo_id = body["data"]["id"].as_str().unwrap();
// Delete it
let resp = server
.delete(&format!("/api/v1/repositories/{repo_id}"))
.await;
assert_eq!(resp.status(), 200);
// List should be empty again
let resp = server.get("/api/v1/repositories").await;
let body: serde_json::Value = resp.json().await.unwrap();
assert_eq!(body["data"].as_array().unwrap().len(), 0);
server.cleanup().await;
}
#[tokio::test]
async fn delete_nonexistent_repository_returns_404() {
let server = TestServer::start().await;
let resp = server
.delete("/api/v1/repositories/000000000000000000000000")
.await;
assert_eq!(resp.status(), 404);
server.cleanup().await;
}
#[tokio::test]
async fn delete_invalid_id_returns_400() {
let server = TestServer::start().await;
let resp = server.delete("/api/v1/repositories/not-a-valid-id").await;
assert_eq!(resp.status(), 400);
server.cleanup().await;
}
@@ -5,13 +5,14 @@ use serde_json::json;
async fn stats_overview_reflects_inserted_data() {
let server = TestServer::start().await;
// Add a repo
// Add a target
server
.post(
"/api/v1/repositories",
"/api/v1/targets",
&json!({
"name": "stats-repo",
"git_url": "https://github.com/example/stats-repo.git",
"target_type": "web_app",
"artifacts": [{ "kind": "git_repo", "source_ref": "https://github.com/example/stats-repo.git", "branch": "main" }],
}),
)
.await;