feat(library): attribute a run to a mission, and prove what it contributed
ci / gates (push) Failing after 7s
ci / rust (push) Skipped
ci / frontend (push) Skipped
ci / e2e (push) Skipped
ci / publish (push) Skipped

`corpus_items.mission_id` has existed since the table landed and nothing
could populate it. `POST /api/library/runs` now accepts `missionId`, which
is the seam the wizard needs: a mission-driven run is the same run, tagged.

`corpus::contributed()` answers the question a continuous mission has to
be able to answer — did THIS run add anything new. Because `record` never
reassigns mission_id on conflict, the mission that first found a source
keeps the credit, so a rerun cannot inflate its own count by re-recording
what an earlier run already held. The test asserts exactly that: two
missions see the same paper, the finder reports 1 and the rerun reports 0.

This is the check the 0030-0044 generation of continuous research did not
have. It could run weekly forever and every run looked like success.

The test also earned its FK: the first version attributed to a bare UUID
and the database refused it. Attribution to a mission that does not exist
is not attribution, so the test now seeds real mission rows.

400 tests, clippy clean.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-03 11:43:41 -07:00
co-authored by Claude Opus 5
parent 9de2cf34e4
commit ad89ef94cd
3 changed files with 93 additions and 1 deletions
+29
View File
@@ -291,6 +291,35 @@ pub async fn unseen(
.collect()) .collect())
} }
/// How many NEW sources a mission contributed.
///
/// The verification predicate for a continuous research mission. `record`
/// never reassigns `mission_id` on conflict, so the first mission to find a
/// source keeps the credit and a rerun cannot inflate its own count by
/// re-recording what an earlier run already had.
///
/// A mission whose answer is zero produced nothing, whatever its transcript
/// says — which is the check the 0030-0044 generation of this feature lacked.
pub async fn contributed(
pool: &sqlx::PgPool,
workspace_id: Uuid,
corpus_id: &str,
mission_id: Uuid,
) -> Result<i64, String> {
let row: (i64,) = sqlx::query_as(
"SELECT count(*) FROM corpus_items
WHERE workspace_id = $1 AND corpus_id = $2 AND mission_id = $3
AND kind = 'source'",
)
.bind(workspace_id)
.bind(corpus_id)
.bind(mission_id)
.fetch_one(pool)
.await
.map_err(|e| format!("contributed({mission_id}): {e}"))?;
Ok(row.0)
}
/// Index every note in a checkout. Idempotent by construction. /// Index every note in a checkout. Idempotent by construction.
pub async fn index_vault( pub async fn index_vault(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
+6 -1
View File
@@ -26,6 +26,11 @@ pub struct RunRequest {
/// library can otherwise pull hundreds of PDFs in one go. /// library can otherwise pull hundreds of PDFs in one go.
#[serde(default)] #[serde(default)]
pub per_topic: Option<usize>, pub per_topic: Option<usize>,
/// Attribute this run to a mission, so the mission can later be asked
/// what it contributed. `corpus_items.mission_id` has existed since the
/// table landed; without this field nothing could ever populate it.
#[serde(default, rename = "missionId")]
pub mission_id: Option<uuid::Uuid>,
} }
#[derive(Serialize)] #[derive(Serialize)]
@@ -80,7 +85,7 @@ pub async fn run(
&work_root, &work_root,
&topics, &topics,
per_topic, per_topic,
None, req.mission_id,
) )
.await .await
.map_err(|e| { .map_err(|e| {
+58
View File
@@ -19,6 +19,24 @@ async fn workspace(pool: &sqlx::PgPool) -> Uuid {
ws ws
} }
/// A real mission row. `corpus_items.mission_id` has a foreign key, which is
/// deliberate: attribution to a mission that does not exist is not
/// attribution. The first version of the test below used a bare UUID and was
/// correctly rejected.
async fn mission(pool: &sqlx::PgPool, ws: Uuid) -> Uuid {
let id = Uuid::now_v7();
sqlx::query(
"INSERT INTO missions (id, workspace_id, title, template_kind, schedule, status, config)
VALUES ($1,$2,'library','research_only','{}'::jsonb,'running','{}'::jsonb)",
)
.bind(id)
.bind(ws)
.execute(pool)
.await
.unwrap();
id
}
fn seed_vault(root: &std::path::Path) { fn seed_vault(root: &std::path::Path) {
std::fs::create_dir_all(root.join("50 APESS 2026/Lectures")).unwrap(); std::fs::create_dir_all(root.join("50 APESS 2026/Lectures")).unwrap();
std::fs::create_dir_all(root.join("Repos")).unwrap(); std::fs::create_dir_all(root.join("Repos")).unwrap();
@@ -226,3 +244,43 @@ async fn live_arxiv_search_and_fetch() {
assert!(pdf.starts_with(b"%PDF")); assert!(pdf.starts_with(b"%PDF"));
assert!(pdf.len() > 10_000, "suspiciously small pdf: {}", pdf.len()); assert!(pdf.len() > 10_000, "suspiciously small pdf: {}", pdf.len());
} }
/// A rerun must not be able to claim credit for work an earlier run did.
///
/// This is the verification predicate for a continuous mission: "did THIS run
/// contribute anything new". If a rerun could re-record an existing source
/// under its own mission id, every run would report success forever — the
/// failure that killed the 0030-0044 generation of this feature.
#[tokio::test]
async fn a_rerun_cannot_claim_an_earlier_missions_work() {
let pool = cm_testkit::test_pool().await;
let ws = workspace(&pool).await;
let first_mission = mission(&pool, ws).await;
let second_mission = mission(&pool, ws).await;
corpus::record(
&pool, ws, "lib", "source", "arxiv:2401.55555",
Some("Paper"), None, None, "h1", Some(first_mission),
)
.await
.unwrap();
// The second mission sees the same paper and re-records it.
corpus::record(
&pool, ws, "lib", "source", "arxiv:2401.55555",
Some("Paper"), None, None, "h2", Some(second_mission),
)
.await
.unwrap();
assert_eq!(
corpus::contributed(&pool, ws, "lib", first_mission).await.unwrap(),
1,
"the finder keeps the credit"
);
assert_eq!(
corpus::contributed(&pool, ws, "lib", second_mission).await.unwrap(),
0,
"a rerun that found nothing new must report zero, not one"
);
}