GO-4284 Remove deprecated object-level relationLinks - #3175
Conversation
debugtree -j now includes the signing identity (account) and timestamp for each change, so tree authorship can be traced per change. Used to diagnose cross-participant relationLinks writes.
Object-level relationLinks (the model.RelationLink list on an object, distinct from dataview relations) are no longer read by any client or by the local indexer — relations are tracked by detail keys. They were kept alive only by auto-add writers, which caused a cross-participant bug: when one device set a detail whose relation link was missing (e.g. discussionId), every other online participant's client independently emitted a RelationAdd change under its own identity and pushed it, producing phantom history entries authored by everyone in the space. Changes: - Stop writing relation-link data into trees: remove RelationAdd/RelationRemove change generation, the Apply-diff ObjectRelationsAmend/Remove events + undo, and snapshot population. No client emits these changes anymore. - Neutralize writers with no caller churn: SetDetailAndBundledRelation is now a thin alias for SetDetail; AddRelationLinks / AddBundledRelationLinks are no-ops. - Remove the state relationLinks field and read accessors (Pick/pick/get), strip link logic from RemoveRelation and filterRelations, remove dead FetchRelationByLinks. - Keep pb types and make changeRelationAdd/Remove no-op parsers so existing trees still load (backward compatible). - Update/remove obsolete tests. The separate, still-live model.ObjectType.RelationLinks (a type's recommended relations) is intentionally left in place; see docs/GO-4284-relationlinks-removal.md.
Follow-up from review of the relationLinks removal: - history: drop isRelationsChange and filterLocalAndDerivedRelations* — they processed ObjectRelationsAmend/Remove events that are no longer produced now that relation-link change generation is gone. Fix DiffVersions tests: a legacy tree's relation-link add/remove changes no longer surface in diffs (only detail changes do), so the mixed case yields its 2 detail changes and the relation-link-only case yields none. - smartblock.ResetToVersion: drop the GO-7217 bundled-relation-link guard. It is structurally obsolete because RelationRemove changes are never generated anymore, so a version restore can no longer emit one that wipes a detail. - import: drop stale comment about auto-injected relationLinks.
Mixed-version blast radius (old clients still write relationLinks)Origin. A non-local relation written with plain A storm needs BOTH: (1) link written absent, and (2) a reconciler that re-adds that specific key. Reconcilers are fixed lists only (
No loop / no amplification. Adding a link is idempotent: once any client pushes it, others see it present and stop. Worst case is a one-time concurrent burst of ≤(online old clients) per object, then quiescent. No data risk. Links only, never detail values; new clients no-op incoming Verdict: acceptable. Finite, self-clearing cosmetic history noise + tiny tree bloat during the mixed-version window; gone once old clients leave the space. The only multi-object effect is the 5 universal keys on new-version-created objects, once each. |
Why
Object-level
relationLinks(themodel.RelationLinklist on an object, distinct from dataview relations) are no longer read by any client or by the local indexer — relations are tracked by detail keys (FetchRelationByLinkshad zero callers;state.HasRelation/iterateKeysread details). They survived only via auto-add writers.This caused a real cross-participant bug. When one device sets a detail whose relation link is missing (e.g.
discussionIdviaObjectAddDiscussion), every other online participant's client runs the link reconciler, sees "detail present, link missing", and emits aRelationAddchange under its own identity, then pushes it. Result: a single local edit produces phantom version-history entries authored by everyone in the space.Diagnosed from a debug export of a chat object: 6×
relationAdd discussionIdemitted by 4 different identities within the same second, right after one device'sdetailsSet discussionId.What changed
Per design decisions, the system is gutted centrally rather than editing 130+ call sites:
RelationAdd/RelationRemovechange generation, the Apply-diffObjectRelationsAmend/Removeevents + undo, and snapshot population. No client emits these changes anymore — this fixes the bug.SetDetailAndBundledRelation→ thin alias forSetDetail;AddRelationLinks/AddBundledRelationLinks→ no-ops.relationLinksfield + read accessors (Pick/pick/getRelationLinks), strip link logic fromRemoveRelation/filterRelations, remove deadFetchRelationByLinks.changeRelationAdd/changeRelationRemoveno-op parsers so existing trees still load.debugtree -jnow emits per-change identity + timestamp (used to trace authorship).Net −527 lines. Full inventory and execution log:
docs/GO-4284-relationlinks-removal.md.Out of scope (separate, still-live)
model.ObjectType.RelationLinks(a type's recommended relations) is a different concept and is actively consumed (relationutils/objecttype.go,bundledobjecttype.go). Left in place; removing it needs migrating those consumers to readrecommendedRelationsdetails.Verification
go build ./...→ exit 0go test ./... -run NONE) → exit 0state,objectlink,templateimplpackage tests pass; edited files gofmt-clean🤖 Generated with Claude Code