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]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9de2cf34e4
commit
ad89ef94cd
@@ -291,6 +291,35 @@ pub async fn unseen(
|
||||
.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.
|
||||
pub async fn index_vault(
|
||||
pool: &sqlx::PgPool,
|
||||
|
||||
Reference in New Issue
Block a user