From 9f16959339e0bf854c811203848fc02c7b0a1bfd Mon Sep 17 00:00:00 2001 From: James Kane Date: Sun, 2 Aug 2026 09:48:43 -0500 Subject: [PATCH] Return the caller's own sample guid with an IBD suggestion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `/api/v1/ibd/attest` gates on `owns_sample(attester_did, claimed_sample)`, but a self-publishing Edge client has no way to learn its server-side `core.biosample.sample_guid` — the suggestions payload returned only the *candidate's* guid, so Navigator could never fill in `claimed_sample` and the attest endpoint was unreachable from the edge. `suggestions_for_did` already joins on `ms.target_sample_guid` (that is how the per-DID scope is enforced), so surfacing it costs nothing and leaks nothing: the caller owns that sample by construction. `suggested_sample_guid` supplies the `counterpart_sample` of the same report. Co-Authored-By: Claude Opus 5 (1M context) --- rust/crates/du-db/src/ibd.rs | 10 +++++++--- rust/crates/du-db/tests/ibd_suggestions.rs | 3 +++ rust/crates/du-web/src/routes/ibd.rs | 3 +++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/rust/crates/du-db/src/ibd.rs b/rust/crates/du-db/src/ibd.rs index 02733106..5caaaa52 100644 --- a/rust/crates/du-db/src/ibd.rs +++ b/rust/crates/du-db/src/ibd.rs @@ -77,9 +77,13 @@ pub struct SuggestionReport { pub suggestions_written: u64, } -/// A ranked suggestion for a sample (the reader's row). +/// A ranked suggestion for a sample (the reader's row). `target_sample_guid` is the reader's +/// **own** sample the candidate was matched against — the caller already owns it, so returning +/// it reveals nothing new, and the Edge needs it as the `claimed_sample` of an +/// [`messages::attest`] report (which [`record_attestation`] gates on ownership). #[derive(Debug, Clone, sqlx::FromRow)] pub struct SuggestionView { + pub target_sample_guid: Uuid, pub suggested_sample_guid: Uuid, pub suggestion_type: String, pub score: Option, @@ -89,7 +93,7 @@ pub struct SuggestionView { /// Serve a sample's ranked active candidates (used by the eventual consent-gated API). pub async fn suggestions_for(pool: &PgPool, sample_guid: Uuid, limit: i64) -> Result, DbError> { Ok(sqlx::query_as( - "SELECT suggested_sample_guid, suggestion_type, score, metadata \ + "SELECT target_sample_guid, suggested_sample_guid, suggestion_type, score, metadata \ FROM ibd.match_suggestion \ WHERE target_sample_guid = $1 AND status = 'ACTIVE' \ ORDER BY score DESC NULLS LAST LIMIT $2", @@ -133,7 +137,7 @@ pub mod messages { /// a counterpart DID (identity reveal stays Edge-to-Edge over D1 consent). pub async fn suggestions_for_did(pool: &PgPool, did: &str, limit: i64) -> Result, DbError> { Ok(sqlx::query_as( - "SELECT ms.suggested_sample_guid, ms.suggestion_type, ms.score, ms.metadata \ + "SELECT ms.target_sample_guid, ms.suggested_sample_guid, ms.suggestion_type, ms.score, ms.metadata \ FROM ibd.match_suggestion ms \ JOIN core.biosample b ON b.sample_guid = ms.target_sample_guid \ WHERE b.atproto->>'repo_did' = $1 AND ms.status = 'ACTIVE' \ diff --git a/rust/crates/du-db/tests/ibd_suggestions.rs b/rust/crates/du-db/tests/ibd_suggestions.rs index 240b35e7..c2fbe714 100644 --- a/rust/crates/du-db/tests/ibd_suggestions.rs +++ b/rust/crates/du-db/tests/ibd_suggestions.rs @@ -198,6 +198,9 @@ async fn suggestions_scoped_by_owner_did() { let mine = ibd::suggestions_for_did(&pool, "did:ex:owner", 50).await.unwrap(); assert_eq!(mine.len(), 1); assert_eq!(mine[0].suggested_sample_guid, suggested); + // The row also names the caller's OWN sample — the Edge attests with it as `claimed_sample`, + // and it is the only way a self-publishing client learns its server-side sample guid. + assert_eq!(mine[0].target_sample_guid, target); assert!(ibd::suggestions_for_did(&pool, "did:ex:counterpart", 50).await.unwrap().is_empty()); // Introduce authorization: true only for the owner's genuine candidate. diff --git a/rust/crates/du-web/src/routes/ibd.rs b/rust/crates/du-web/src/routes/ibd.rs index 332ea72d..ae303202 100644 --- a/rust/crates/du-web/src/routes/ibd.rs +++ b/rust/crates/du-web/src/routes/ibd.rs @@ -45,6 +45,9 @@ async fn suggestions(State(st): State, Query(q): Query