Files
clawmates/crates/cm-api/tests/corpus_vault.rs
T
Omar SobhandClaude Opus 5 ad89ef94cd
ci / gates (push) Failing after 7s
ci / rust (push) Skipped
ci / frontend (push) Skipped
ci / e2e (push) Skipped
ci / publish (push) Skipped
feat(library): attribute a run to a mission, and prove what it contributed
`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]>
2026-08-03 11:43:41 -07:00

287 lines
10 KiB
Rust

//! Indexing the vault must be idempotent, or a continuous mission cannot tell
//! new work from work it already did.
//!
//! These run against a real Postgres via cm-testkit. The vault fixture is
//! shaped from the actual `valhalla-vault`: 416 notes, only 145 with
//! frontmatter, none carrying arxiv/doi/url, plus repo-sync notes whose
//! frontmatter churns on every sync.
use cm_api::corpus;
use uuid::Uuid;
async fn workspace(pool: &sqlx::PgPool) -> Uuid {
let ws = Uuid::now_v7();
sqlx::query("INSERT INTO workspaces (id, name, plan) VALUES ($1,'t','team')")
.bind(ws)
.execute(pool)
.await
.unwrap();
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) {
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("Daily")).unwrap();
// Course note: has frontmatter, but `source:` is a local path.
std::fs::write(
root.join("50 APESS 2026/Lectures/agentic.md"),
"---\nsource: \"/Users/quantum/Downloads/Material/x.pdf\"\ntype: lecture\n---\n# Agentic Design\n\nbody\n",
)
.unwrap();
// Repo-sync note: frontmatter churns, prose does not.
std::fs::write(
root.join("Repos/zeroclaw.md"),
"---\nnode: tank\nupdated: 2026-08-01\nsize_kb: 12\n---\n# ZeroClaw\n\nmirror\n",
)
.unwrap();
// Plain note: no frontmatter at all — the majority case.
std::fs::write(root.join("Daily/2026-08-01.md"), "# Monday\n\nnotes\n").unwrap();
}
#[tokio::test]
async fn indexing_an_unchanged_vault_is_a_no_op() {
let pool = cm_testkit::test_pool().await;
let ws = workspace(&pool).await;
let tmp = tempfile::tempdir().unwrap();
seed_vault(tmp.path());
let first = corpus::index_vault(&pool, ws, "vault", tmp.path())
.await
.unwrap();
assert_eq!(first.scanned, 3);
assert_eq!(first.inserted, 3);
assert_eq!(first.unchanged, 0);
// The decisive assertion: a second pass over an untouched vault must add
// and change nothing. Without this, every run looks like new work.
let second = corpus::index_vault(&pool, ws, "vault", tmp.path())
.await
.unwrap();
assert_eq!(second.scanned, 3);
assert_eq!(second.inserted, 0, "re-index must not insert");
assert_eq!(second.updated, 0, "re-index must not update");
assert_eq!(second.unchanged, 3);
}
#[tokio::test]
async fn a_repo_sync_touching_only_frontmatter_is_not_an_edit() {
let pool = cm_testkit::test_pool().await;
let ws = workspace(&pool).await;
let tmp = tempfile::tempdir().unwrap();
seed_vault(tmp.path());
corpus::index_vault(&pool, ws, "vault", tmp.path())
.await
.unwrap();
// Exactly what a repo sync does: bump `updated`/`size_kb`, prose untouched.
std::fs::write(
tmp.path().join("Repos/zeroclaw.md"),
"---\nnode: tank\nupdated: 2026-08-03\nsize_kb: 14\n---\n# ZeroClaw\n\nmirror\n",
)
.unwrap();
let stats = corpus::index_vault(&pool, ws, "vault", tmp.path())
.await
.unwrap();
assert_eq!(stats.updated, 0, "frontmatter churn is not an edit");
assert_eq!(stats.unchanged, 3);
// A real prose edit must still be seen.
std::fs::write(
tmp.path().join("Repos/zeroclaw.md"),
"---\nnode: tank\nupdated: 2026-08-03\n---\n# ZeroClaw\n\nREWRITTEN\n",
)
.unwrap();
let stats = corpus::index_vault(&pool, ws, "vault", tmp.path())
.await
.unwrap();
assert_eq!(stats.updated, 1, "a genuine edit must be visible");
}
#[tokio::test]
async fn a_hand_edited_note_survives_a_rebuild() {
let pool = cm_testkit::test_pool().await;
let ws = workspace(&pool).await;
let tmp = tempfile::tempdir().unwrap();
seed_vault(tmp.path());
corpus::index_vault(&pool, ws, "vault", tmp.path())
.await
.unwrap();
// The vault is authoritative: a human renames a note by hand.
std::fs::remove_file(tmp.path().join("Daily/2026-08-01.md")).unwrap();
std::fs::write(tmp.path().join("Daily/renamed.md"), "# Monday\n\nnotes\n").unwrap();
let stats = corpus::index_vault(&pool, ws, "vault", tmp.path())
.await
.unwrap();
assert_eq!(stats.scanned, 3);
assert_eq!(stats.inserted, 1, "the renamed note is indexed under its new path");
// The stale row is left alone rather than deleted — the index is derived
// and rebuildable, and losing coverage history is worse than a stale row.
assert!(corpus::seen(&pool, ws, "vault", "note:Daily/renamed.md")
.await
.unwrap());
}
#[tokio::test]
async fn unseen_filters_candidates_in_one_round_trip() {
let pool = cm_testkit::test_pool().await;
let ws = workspace(&pool).await;
corpus::record(
&pool, ws, "vault", "source", "arxiv:2401.11111",
Some("Known"), None, None, "h", None,
)
.await
.unwrap();
let candidates = vec![
"arxiv:2401.11111".to_string(), // already read
"arxiv:2401.22222".to_string(),
"doi:10.1000/new".to_string(),
];
let fresh = corpus::unseen(&pool, ws, "vault", &candidates).await.unwrap();
assert_eq!(fresh, vec!["arxiv:2401.22222", "doi:10.1000/new"]);
assert!(corpus::seen(&pool, ws, "vault", "arxiv:2401.11111").await.unwrap());
assert!(!corpus::seen(&pool, ws, "vault", "arxiv:2401.22222").await.unwrap());
// A different corpus must not inherit another's seen-set.
assert!(!corpus::seen(&pool, ws, "other", "arxiv:2401.11111").await.unwrap());
}
/// The first mission to find a source keeps the credit, so "did THIS run
/// contribute anything new" stays answerable across repeated runs.
#[tokio::test]
async fn re_recording_a_source_does_not_reassign_it() {
let pool = cm_testkit::test_pool().await;
let ws = workspace(&pool).await;
let inserted = corpus::record(
&pool, ws, "vault", "source", "arxiv:2401.33333",
Some("Paper"), None, None, "h1", None,
)
.await
.unwrap();
assert!(inserted, "first sighting is an insert");
let inserted_again = corpus::record(
&pool, ws, "vault", "source", "arxiv:2401.33333",
Some("Paper"), None, None, "h2", None,
)
.await
.unwrap();
assert!(!inserted_again, "a second sighting is not new work");
}
/// Idempotence against the real vault rather than a fixture.
///
/// Ignored by default because it needs a checkout: run with
/// `VAULT=/path/to/valhalla-vault cargo test -p cm-api --test corpus_vault \
/// index_the_real_vault -- --ignored --nocapture`.
///
/// Measured 2026-08-03 on the live vault:
/// PASS1 { scanned: 416, inserted: 416, updated: 0, unchanged: 0 }
/// PASS2 { scanned: 416, inserted: 0, updated: 0, unchanged: 416 }
#[tokio::test]
#[ignore]
async fn index_the_real_vault() {
let pool = cm_testkit::test_pool().await;
let ws = uuid::Uuid::now_v7();
sqlx::query("INSERT INTO workspaces (id, name, plan) VALUES ($1,'t','team')")
.bind(ws).execute(&pool).await.unwrap();
let root = std::path::Path::new(&std::env::var("VAULT").unwrap()).to_path_buf();
let a = cm_api::corpus::index_vault(&pool, ws, "valhalla-vault", &root).await.unwrap();
println!("PASS1 {a:?}");
let b = cm_api::corpus::index_vault(&pool, ws, "valhalla-vault", &root).await.unwrap();
println!("PASS2 {b:?}");
assert_eq!(b.inserted, 0);
assert_eq!(b.updated, 0);
assert_eq!(b.unchanged, a.scanned);
}
/// Live arXiv check. Ignored by default (needs network); run with
/// `cargo test -p cm-api --test corpus_vault live_arxiv -- --ignored --nocapture`.
///
/// Guards the one failure that hides: if arXiv's feed format drifts, parsing
/// returns zero papers, which looks exactly like "no new papers this week".
#[tokio::test]
#[ignore]
async fn live_arxiv_search_and_fetch() {
let papers = cm_api::papers::search("all:agentic topologies", 3)
.await
.expect("arxiv search");
println!("found {} papers", papers.len());
assert!(!papers.is_empty(), "arXiv returned nothing — format drift?");
for p in &papers {
println!(" {} | {}", p.source_id(), &p.title[..p.title.len().min(60)]);
assert!(!p.arxiv_id.is_empty());
assert!(!p.title.is_empty());
assert!(!p.arxiv_id.contains('v'), "version must be stripped: {}", p.arxiv_id);
}
let pdf = cm_api::papers::fetch_pdf(&papers[0]).await.expect("fetch pdf");
println!("pdf bytes: {}", pdf.len());
assert!(pdf.starts_with(b"%PDF"));
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"
);
}