Files
clawmates/crates/cm-db/tests/mission_tasks_upsert.rs
T
Omar SobhandClaude Opus 5 18dc0b964b fix(missions): the security scan phase now scans, and task upserts work
Four defects, found by checking the audit's claims instead of trusting
them. Two of the audit's own findings turned out to be wrong, and the
registry that exists to record which config keys are read was itself
inaccurate — so the corrections are part of the change.

upsert_task raised 42P10 on every call, for every caller
  `mission_tasks_external_uniq` is a PARTIAL unique index (WHERE
  external_id IS NOT NULL). Postgres will not match a partial index to an
  ON CONFLICT target unless the statement repeats the predicate, so the
  upsert failed on its first row. Both callers — the task-card parser that
  turns INT markers into tasks, and the security scanner — map the error to
  a string their caller logs. Two features were broken and nothing was red.
  Regression test in cm-db with a negative control: reverting the WHERE
  reproduces 42P10 exactly.

the security scan never ran
  `security_scan::run` was reachable only from an operator button, so
  security_hardening.toml — a workflow whose entire first phase is a scan —
  ran an agent that was never told to scan and never fired the scanner
  either. phase_runner now sweeps finished security_scan phases, mirroring
  the benchmark baseline sweep that was added for the identical defect.
  Guarded on a new completion marker rather than on findings: a clean scan
  writes no findings, so a findings-guard would rescan forever. The marker
  also answers the question an operator actually asks, which is not "how
  many findings" but "was this looked at, by what, and when".

two recipes could not fail
  security_hardening.toml and benchmark.toml carried no `task` and no
  `done_when` on any phase. A phase without done_when never enters
  evaluating, is never judged, and reports completed whatever it did — so a
  security mission could scan nothing and go green, and a benchmark mission
  could record no baseline that the next refactor would then compare
  against. Both now state the work and the condition, with inert keys
  annotated inline rather than deleted, so the gap between what a recipe
  asks for and what a phase receives stays visible.

the config registry was wrong in both directions
  `harness` was listed NOT IMPLEMENTED while benchmark_runner reads it and
  phase_runner runs a baseline through it. `tools` was listed NOT
  IMPLEMENTED while security_scan::run reads it. A registry that exists so
  an operator can trust what a recipe does is worse than useless when it is
  inaccurate. Both corrected, `bench_name` and `cmd` added, and
  `test_command` deleted — it had neither a reader nor a writer, so it
  described a situation that could not arise.

Also: CLAWMATES_JUDGE_MODEL had two different defaults (opus-4-8 in
routes/topology.rs vs opus-5 in cm_runtime::judge_model) and a doc comment
naming a third; topology now calls the one function. GITEA_TOKEN's absence
in mission_plan is stated rather than degrading to the same "could not be
read" string a private repo produces.

BRAINHUB_API_KEY needed no change — hub::push already rejects an unset key
with a named error. That half of the finding was overstated.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-19 08:08:27 -07:00

108 lines
3.6 KiB
Rust

//! `upsert_task` against the partial unique index it depends on.
//!
//! `mission_tasks_external_uniq` is declared `WHERE external_id IS NOT NULL`.
//! Postgres will not match a partial index to an `ON CONFLICT` target unless
//! the statement repeats that predicate, so the upsert raised 42P10 on its
//! first row for every caller — the task-card parser (INT markers) and the
//! security scanner. Both map the error to a string their caller logs, so the
//! feature was broken and the platform stayed green.
//!
//! The insert half and the update half are tested separately because they fail
//! differently: a missing conflict target breaks both, but a wrong DO UPDATE
//! breaks only the second write, which is the one that happens on re-run.
use cm_db::repo::missions::{upsert_task, UpsertTask};
use cm_domain::{Workspace, WorkspaceId};
use uuid::Uuid;
async fn seed_phase(pool: &sqlx::PgPool) -> (Uuid, Uuid) {
let ws = Workspace {
id: WorkspaceId::new(),
name: "Task Upsert Test".into(),
plan: "team".into(),
};
cm_db::repo::workspaces::insert(pool, &ws).await.unwrap();
let mission = Uuid::now_v7();
sqlx::query(
"INSERT INTO missions (id, workspace_id, title, template_kind, status)
VALUES ($1, $2, 'upsert test', 'security_hardening', 'running')",
)
.bind(mission)
.bind(ws.id.as_uuid())
.execute(pool)
.await
.unwrap();
let phase = Uuid::now_v7();
sqlx::query(
"INSERT INTO mission_phases (id, mission_id, kind, order_idx, status)
VALUES ($1, $2, 'security_scan', 0, 'completed')",
)
.bind(phase)
.bind(mission)
.execute(pool)
.await
.unwrap();
(mission, phase)
}
#[tokio::test]
async fn the_same_external_id_updates_in_place_instead_of_erroring() {
let pool = cm_testkit::test_pool().await;
let (mission_id, phase_id) = seed_phase(&pool).await;
let first = upsert_task(
&pool,
UpsertTask {
mission_id,
phase_id,
external_id: "RUSTSEC-2026-0001",
title: "advisory: something",
assigned_agent_id: None,
status: "created",
run_id: None,
},
)
.await
.expect("first upsert must succeed — 42P10 here means the ON CONFLICT \
target no longer matches the partial unique index");
// The re-run. A scanner that finds the same advisory twice must not
// duplicate the row, and must not fail.
let second = upsert_task(
&pool,
UpsertTask {
mission_id,
phase_id,
external_id: "RUSTSEC-2026-0001",
title: "advisory: something (rescanned)",
assigned_agent_id: None,
status: "complete",
run_id: None,
},
)
.await
.expect("second upsert must succeed");
assert_eq!(first, second, "the same (phase_id, external_id) must be one row");
let (title, status, completed): (String, String, Option<time::OffsetDateTime>) =
sqlx::query_as("SELECT title, status, completed_at FROM mission_tasks WHERE id = $1")
.bind(first)
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(title, "advisory: something (rescanned)");
assert_eq!(status, "complete");
assert!(
completed.is_some(),
"a task upserted as `complete` must get its completed_at stamped"
);
let n: i64 = sqlx::query_scalar("SELECT count(*) FROM mission_tasks WHERE phase_id = $1")
.bind(phase_id)
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(n, 1, "two upserts of one external_id left {n} rows");
}