fix: Follows waren über den Client nicht anlegbar

Der Client legt Follows über createRecord mit app.bsky.graph.follow an. Das
Lexicon war in der PDS aber nicht registriert, und createRecord validiert
per Default — jede Anfrage kam mit

  400 lex validation failed: unknown lexicon: app.bsky.graph.follow

zurück. Der Follow-Button kann also nie funktioniert haben, auch wenn der
Commit, der ihn eingeführt hat, "end-to-end follow / unfollow" heißt. Beim
Gegenprüfen des Firehose-Pfads aufgefallen: der Testaufbau scheiterte schon
am Anlegen des Follows.

Das Lexicon ist jetzt da (subject als DID-String, nicht als strongRef —
genau das, was der Client schickt und was follow_subject_did in der AppView
liest) und registriert. Live geprüft: anlegen, in der AppView indiziert,
löschen, Zeile weg.

Dazu ein zweiter Grund, warum das nie auffiel: create_record_with nahm einen
Parameter `_validate` entgegen und verwarf ihn. Der eine Aufrufer, der
`false` übergab, bekam trotzdem Validierung. Der Parameter wird jetzt
tatsächlich mitgeschickt; der Repost-Pfad steht auf `true`, weil genau das
bisher schon passiert ist und funktioniert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HC9HLrUU1LNwkzp8nkDLX
This commit is contained in:
tomdebone
2026-09-10 07:08:46 +02:00
co-authored by Claude Opus 5
parent 2b695d6892
commit 6fbea4fe6f
3 changed files with 37 additions and 2 deletions
+6 -1
View File
@@ -250,7 +250,12 @@ async fn repost_post(
});
let resp = state
.pds
.create_record_with(&sess.did, "app.bsky.feed.repost", record, false, &sess.access_jwt)
// `true`: `app.bsky.feed.repost` is in the PDS's lexicon registry
// and this record passes it (verified against a live PDS). The
// `false` that stood here was inert — the flag was dropped before
// the request — so validating is what has actually been happening
// all along; saying so keeps the behaviour and drops the fiction.
.create_record_with(&sess.did, "app.bsky.feed.repost", record, true, &sess.access_jwt)
.await
.map_err(|e| e.to_string())?;
Ok(serde_json::json!({
+13 -1
View File
@@ -41,6 +41,10 @@ pub struct CreateRecordReq {
pub repo: String,
pub collection: String,
pub record: serde_json::Value,
/// Omitted rather than sent as `null` when the caller has no
/// opinion — the PDS's own default (`true`) then applies.
#[serde(skip_serializing_if = "Option::is_none")]
pub validate: Option<bool>,
}
/// Strong reference as defined by
@@ -184,12 +188,19 @@ impl PdsHttpClient {
Ok(r.json().await?)
}
/// `validate` is forwarded to the PDS, which defaults it to `true`.
///
/// It used to be `_validate` — accepted and silently dropped, so a
/// caller asking for `false` still got server-side validation. That
/// made no difference in practice (every collection the client writes
/// is in the PDS's lexicon registry and passes), but a parameter that
/// does nothing is a trap for the next caller who relies on it.
pub async fn create_record_with(
&self,
repo: &str,
collection: &str,
record: serde_json::Value,
_validate: bool,
validate: bool,
jwt: &str,
) -> Result<CreateRecordResp> {
let r = self
@@ -200,6 +211,7 @@ impl PdsHttpClient {
repo: repo.to_string(),
collection: collection.to_string(),
record,
validate: Some(validate),
})
.send()
.await?;