feat(serialization)!: forward-compatible models — open unions and unknown-field round-tripping #115
Labels
No labels
breaking-change
bug
documentation
duplicate
enhancement
good first issue
help wanted
performance
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/ATProto.NET#115
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of the v0.7.0 update cycle. Lexicon unions are open by spec, but the SDK's are closed, and it has no way to round-trip unknown fields. One unrecognized
$typeanywhere in a response fails the whole call, and read-modify-write drops data.Live bugs this fixes
app.bsky.embed.gallery(upstream since 2026-06-03) is not registered onEmbedBase/EmbedView. One gallery post fails the whole timeline, feed, author-feed, thread, search orgetPostspage withJsonException. In a 40-second Jetstream sample, 0.27% of new posts used it, which is about 1 in 8 fifty-post pages.accountEvent,identityEvent,recordEvent, …), soQueryEventsAsyncthrows on a real Ozone.Label.Sigis a barebyte[]withoutLexBytesJsonConverter, so every signed label ({"$bytes": …}) throws.QueryLabelsAsyncagainstmod.bsky.appfails.UpdateProfileAsyncdeletes profile data. It rebuildsProfileRecordfrom 5 fields, droppingpinnedPost,pronouns,website,labels,joinedViaStarterPackand anything newer. It also writes withoutswapRecord.Scope
[JsonPolymorphic]union base gets anUnknown{Base}variant carrying$typeand the raw JSON, which serializes back byte-for-byte (for example anAtProtoUnionconverter factory driven by the registered variants). Unions the Lexicon marksclosed: truestay strict. Unions currently exposed as rawJsonElement(FeedViewPost.Reason,PostView.Threadgate, …) are not retyped here; the lexicon-fidelity issue does that. LeaveLexicon/Com/AtProto/Space/*andSimpleSpace/*unions for the Spaces work (a parallel PR is changing those files), and note it in the PR.LexObjectwith[JsonExtensionData] IDictionary<string, JsonElement>? ExtensionData) for records and views, includingAtProtoRecord, so unknown fields survive deserialize→serialize. Same exclusion for Space/SimpleSpace models.LexiconTypeRegistry: registered union variants are currently ignored by every sub-client, because they only affectCreateOptions(), notAtProtoJsonDefaults.Options. Apply the registry to the options the clients actually use. Delete the write-only record-type registry, the unused_pluginslist and the duplicateCreateOptions. Keep or removeLexiconPluginAttribute/LoadPluginsFromAssemblydepending on whether they still do anything useful.AtProtoJsonDefaults.Options: freeze it (MakeReadOnly()) after construction, and fix its XML remark that points to non-existent constructor parameters.#main,#image,#view,#viewImage) on every union that references them (feed.post#embed,feed.defs#postView.embed,embed.record#viewRecord.embeds,embed.recordWithMedia#media/#view.media).Label.SiggetsLexBytesJsonConverter. Share one pad-unpadded-base64 helper betweenLexBytesJsonConverterandDagCborEncoder, which also fixes the latent padding bug inJetstreamEventParser/JetstreamSegmentReader(Convert.FromBase64Stringrejects unpadded input).UpdateProfileAsync: read-modify-write that preserves every field (via the extension data), writes withswapRecord, and retries a bounded number of times onInvalidSwap. Suggested shape:UpdateProfileAsync(Func<ProfileRecord, ProfileRecord> update, …). Add the missingProfileRecordfields (pronouns,website,labels,joinedViaStarterPack).IAtProtoUnionmarker if nothing needs it (keep the custom open-union test).Acceptance
Unknown*and re-serializes identically; unknown record fields round-trip; a timeline page with a gallery post deserializes; a signed label deserializes;UpdateProfileAsyncpreserves unknown fields.Fixed(the crashes and the data loss);Breaking changes(union base shapes,UpdateProfileAsyncsignature, frozen options) with migration notes.