fix: resolve repo rkey to human-readable name for issues/pulls AT-URI decomposition produces the repo rkey, not the human name. The issue/pull list endpoint queries by name, so we resolve rkey → name via the repos table at ingestion time.

Author: Aaron Steven White
Commit 8459b2715bde43bf252d4ad4a86a1a555839bef5
Parent: c89616e9f3
Structural diff unavailable

These commits were pushed via plain git push, so no pre-parsed schemas are available. Install git-remote-cospan and re-push via panproto:// to see scope-level changes, breaking change detection, and semantic diffs.

brew install panproto/tap/git-remote-cospan
2 files changed +24 -0
@@ -146,6 +146,23 @@ pub async fn list_by_source_popular(
146146     }
147147 }
148148 
149+/// Resolve a repo rkey to its human-readable name.
150+/// Returns the rkey unchanged if no repo is found.
151+pub async fn resolve_rkey_to_name(
152+    pool: &PgPool,
153+    did: &str,
154+    rkey: &str,
155+) -> Result<String, sqlx::Error> {
156+    let row: Option<(String,)> = sqlx::query_as(
157+        "SELECT name FROM repos WHERE did = $1 AND rkey = $2",
158+    )
159+    .bind(did)
160+    .bind(rkey)
161+    .fetch_optional(pool)
162+    .await?;
163+    Ok(row.map(|r| r.0).unwrap_or_else(|| rkey.to_string()))
164+}
165+
149166 /// Insert a stub repo row if one doesn't already exist for (did, name).
150167 /// Used during backfill when child records (issues, pulls, etc.) arrive
151168 /// before their parent repo. The stub will be overwritten with full data
@@ -137,6 +137,10 @@ async fn dispatch_special_upsert(
137137             row.did = did.to_string();
138138             row.rkey = rkey.to_string();
139139             row.indexed_at = Utc::now();
140+            // AT-URI decomposition yields rkey, not name — resolve to human-readable name
141+            if !row.repo_did.is_empty() && !row.repo_name.is_empty() {
142+                row.repo_name = db::repo::resolve_rkey_to_name(&state.db, &row.repo_did, &row.repo_name).await?;
143+            }
140144             let source = if collection.starts_with("sh.tangled.") { "tangled" } else { "cospan" };
141145             db::repo::ensure_exists(&state.db, &row.repo_did, &row.repo_name, source).await?;
142146             db::issue::upsert(&state.db, &row).await?;
@@ -236,6 +240,9 @@ async fn dispatch_special_upsert(
236240             row.did = did.to_string();
237241             row.rkey = rkey.to_string();
238242             row.indexed_at = Utc::now();
243+            if !row.repo_did.is_empty() && !row.repo_name.is_empty() {
244+                row.repo_name = db::repo::resolve_rkey_to_name(&state.db, &row.repo_did, &row.repo_name).await?;
245+            }
239246             let source = if collection.starts_with("sh.tangled.") { "tangled" } else { "cospan" };
240247             db::repo::ensure_exists(&state.db, &row.repo_did, &row.repo_name, source).await?;
241248             db::pull::upsert(&state.db, &row).await?;
cospan · schematic version control on atproto built on AT Protocol