feat(library): clone the vault, harvest our topics, push the catalogue

Completes the loop: the notes now land in the real vault. Topics come
from what the project is actually working on — papers/dynamic-agentic-
topologies.md (topology search, ADAS/Darwin-Godel/SwarmAgentic) plus the
two problems this week ran into, verifying what an agent did and giving
a long-running agent memory of what it covered.

Never pushes to main. The vault is a live Obsidian vault a human edits
and syncs; pushing to main races that sync and can lose hand-written
work. Every run lands on its own branch for a human to merge, the same
rule the mission delivery path was validated 20/20 under.

PDFs are NOT committed. A few hundred papers is gigabytes and would make
the vault painful to clone and slow to open, so they stay on the blob
store shelf and the note carries the key.

My own test caught me repeating this week's branch-collision bug: I named
branches from the HEAD of a UUIDv7, which is a 48-bit timestamp, so two
runs in the same millisecond produce the identical name — exactly what
hit mission 019fc42b. Fixed by taking the tail. The test now loops 100
ids instead of sampling two (a one-shot check passes by luck whenever the
millisecond ticks between calls) and additionally asserts the head-based
scheme DOES collide, so it cannot rot into a no-op.

Live against the real vault:
  10 candidates, 1 already held, 9 shelved, 0 failed
  branch clawmates/library-019fc82292e8, pushed
  9 notes verified on the forge, 9 PDFs verified %PDF on the shelf
  (the "1 already held" is cross-topic dedupe inside a single run)

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-03 08:03:14 -07:00
co-authored by Claude Opus 5
parent 30eaa50c50
commit 09c6496725
5 changed files with 316 additions and 3 deletions
+49
View File
@@ -151,3 +151,52 @@ async fn live_end_to_end_run_then_rerun() {
);
assert_eq!(second.already_had, second.candidates);
}
/// THE REAL RUN. Clones the live vault, harvests our current topics, pushes a
/// branch. Ignored by default — needs network, Postgres and GITEA_TOKEN:
/// `GITEA_TOKEN=… VAULT_URL=… cargo test -p cm-api --test harvest_run \
/// live_library_run -- --ignored --nocapture`
#[tokio::test]
#[ignore]
async fn live_library_run() {
let pool = cm_testkit::test_pool().await;
let ws = workspace(&pool).await;
let tmp = tempfile::tempdir().unwrap();
let blobs: Arc<dyn cm_files::BlobStore> =
Arc::new(cm_files::LocalBlobStore::new(tmp.path().join("shelf")));
let url = std::env::var("VAULT_URL").unwrap();
let topics = cm_api::library::default_topics();
for t in &topics {
println!("topic: {t}");
}
let run = cm_api::library::run_to_vault(
&pool, &blobs, ws, "valhalla-vault", &url,
tmp.path(), &topics, 2, None,
)
.await
.unwrap();
println!("\nRESULT {}", run.harvest.summary());
println!("branch: {} pushed: {}", run.branch, run.pushed);
if let Some(e) = &run.error {
println!("error: {e}");
}
for n in &run.harvest.notes_written {
println!(" note: {n}");
}
for (sid, why) in &run.harvest.failed {
println!(" FAILED {sid}: {why}");
}
// Every shelved paper must have its PDF really on the shelf.
for sid in &run.harvest.shelved {
let id = sid.trim_start_matches("arxiv:");
let key = format!("papers/arxiv/{id}.pdf");
let bytes = blobs.get(&key).await.expect("pdf on the shelf");
assert!(bytes.starts_with(b"%PDF"), "{key} is not a PDF");
println!(" shelf: {key} ({} bytes)", bytes.len());
}
assert!(run.harvest.healthy(), "failures: {:?}", run.harvest.failed);
}