feat(lexicon): pre-publication compliance pass — align schemas to wire reality master
Prepare the social.coves.* lexicons for publication (com.atproto.lexicon.schema records + _lexicon DNS). Once published, atproto evolution rules freeze the schemas to additive-only changes, so this pass fixes everything breaking-shaped now: style-guide violations from the original Opus-era schemas, and every place a schema contradicted what the Go AppView actually reads and writes. All ~11k live records on pds.coves.me + pds.bretton.dev verified unaffected via the new validate-live sweep (only pre-existing bridge-URI failures remain). Reviewed by multi-model /second-opinion; its confirmed findings are folded in here. Schema correctness: - Record account refs pinned to DIDs (post.community, ban.community — handles are mutable/reassignable); live posts already store DIDs - Removed literal "$type" properties from rules.json union members; removed invalid additionalProperties (tagCounts -> unknown); dropped record-baked governance defaults/minimums (policy lives app-side) - knownValues normalized to kebab-case (moderator permissions, getPosts filters); Go normalizes legacy snake_case filters for old clients - 1:1 byte/grapheme caps fixed to ~10:1 across records AND procedures (embeds, post tags, community name/description, comment content, editNote; post.update content limits were inverted) - Over-required fields relaxed pre-freeze (profile createdBy/hostedBy, actor.profile createdAt, rule fields, memberView.reputation); actor.block.createdAt now required; banView.indexedAt required - name capped at 63 bytes everywhere to match the RFC 1035 check Go enforces Schemas now tell the truth about the wire: - actor.profile record/views renamed bio->description(+Facets), viewer blockUri->blocking — matching writer, firehose consumer, and getProfile; updateProfile input keeps `bio` (the actual wire name clients send) - community.update input is communityDid; create/update/updateProfile declare avatarBlob/bannerBlob as base64 strings + mime types (what handlers decode), and update only advertises fields the handler supports - New social.coves.community.post.defs holds the shared view types (moved out of the post.get query file); comment/feed refs re-pointed; dangling embed.record#view refs eliminated - New embed external#view + post#view defs; blob_transform now rewrites $type to the #view form when it URL-ifies thumbs or resolves quoted posts, so served payloads validate against the type they declare - vote.create output uri/cid optional (absent on toggle-off) instead of required-but-empty-string; handler emits omitempty; pds CreateRecord now errors on a 200 with empty uri/cid so {} can only mean toggle-off - moderation ban subset cleaned (banView instead of refs to the record type, unbanUser success boolean dropped, tribunalCase as strongRef); governance trio (ruleProposal/tribunalVote/vote) deliberately untouched and held back from publishing until tribunal design lands Tooling and enforcement: - New cmd/validate-live: sweeps every social.coves.* record on a live PDS via public XRPC and validates against local schemas; exits 2 on incomplete sweeps, guards non-advancing cursors, sorted deterministic output - cmd/validate-lexicon cross-ref list updated for the new defs - update_profile handler validates displayName/bio by graphemes+bytes (uniseg) per the published contract; fixtures and tests updated Client coordination required before next appview deploy (tracked in memory): transformed embeds now declare #view $types; vote toggle-off omits uri/cid. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>