You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-on design ticket from #5682 — its decomposition sketch names this as a separate design, and the parent's acceptance requires these tickets to be filed and linked. Filed in the 2026-11-04 gap pass (WS-F) so the design work exists and can be prioritised on its own merits; implementation timing is a separate decision, possibly months out.
What this design must settle
Protocol extensions on #2036/#2051 unifying chat and talk pages, including retroactive anchoring of v1 chats.
Context from the parent
The parent design fixes chat's growth from v1 (#2051) — conversation↔object linking, talk pages, and terminal participation — on the #2036 protocol. The parent issue carries the surrounding decisions this ticket must not re-decide; where this ticket's answer would change a parent decision, record that explicitly rather than silently forking it.
Decision record expected
Close this design with a recorded decision (on this issue or in a doc/ record linked from it): the chosen shape, the alternatives rejected and why, and the invariants implementation must hold. If the design splits further, file the splits and link them here rather than growing this issue.
Relations
Parent: #5682. Programme: epic #2056. Filed by WS-F (2026-11-04 ID design gap & expansion pass).
No blocker, no Drew decision needed to start. Implementation timing being a separate, possibly-months-out decision gates implementation, not this design. The only remaining work is the design itself, so the follow-up enqueued in this pass is the decision-record task for this same issue (standard triage dedupe key, enqueued after this triage completes).
Disposition: triaged; design work proceeds as the follow-up task named in the addendum. Issue #5872 stays open for it.
id: design: ID chat v1 #2051 v1 as implemented at current main (pkgs/id/src/chat.rs): a message is a signed chat.message.v1 event in a channel group; authorship is the signing key; causal order is the after frontier + author backlink, tie-broken deterministically by event ID; ChatLink kinds are exactly blob and event, a link names content by hash + author and never embeds a capability.
Decision: unification by additive binding events on the one conversation log
One abstraction. A conversation is a channel group, exactly as in v1. There is no talk-page entity, store, or protocol. A talk page is a conversation whose log contains a talk_for binding (below). Chat clients and object-embedded views are two projections of the same event log.
New event kind chat.anchor.v1 (chained permanent, in the conversation's group): binds, unbinds, or re-locates a conversation↔object anchor. Payload: target object (ID + author key), link type (vocabulary owned by id: design: chat anchor data model (from #5682) #5870: about, anchored-to-part, derived-from, resolved-into), and for part anchors a by-reference selector (object ID + part identifier per id: design: chat anchor data model (from #5682) #5870's grammar) — never embedded object content, never a capability.
Conversation-level talk_for binding: a chat.anchor.v1 event with link type talk-for designates the conversation as the object's talk page. Exactly one canonical binding per object: the first valid binding in causal order wins; a later conflicting binding folds to a visible disputed state on all replicas, never a silent fork. Binding authority (who may bind — object owner / members) is enforced at admission under the object's policy, with the detailed permission rules owned by id: design: talk-page semantics (from #5682) #5871.
ChatLink gains a third kind, object: an addressable ID object named by ID + author, alongside v1's blob/event. This is the in-message anchor and the paste-a-link upgrade path. Recorded explicitly, per this issue's instructions: this extends the implemented v1 in chat.rs ("only blob and event are v1 link kinds") — it is an additive extension decided here, not a silent fork of id: design: ID chat v1 #2051; pre-extension clients treat it under invariant 7 below.
What stays application-layer (per id: design: Chat next steps — conversation↔object linking, talk pages, terminal participation #5682 §4): anchor indexes ("conversations about this object"), search, presence, and notification fan-out are projections/indexes over the log, not protocol state. The protocol carries only anchor records, the talk_for binding, and thread-object bindings as they fall out of (2)–(3).
Decision: retroactive anchoring of v1 chats — additive only, confirmed by a member
No history is rewritten, ever. Anchoring a v1 conversation or message is a new signed event appended now, referencing existing messages by event ID. V1 events are immutable (chained permanent); any design requiring re-signing or migrating them is disqualified on its face.
Designation: an existing v1 conversation becomes an object's talk page by a talk_for binding event (subject to the one-canonical rule and binding authority above). It keeps its history, membership, and identity.
Recognition proposes, members dispose: clients may scan existing message text for object URLs/IDs and propose anchors, but a proposal has no protocol effect. An anchor exists only when a conversation member confirms it with a signed chat.anchor.v1 event. No scanner silently rewrites a conversation's meaning after the fact.
A. Separate talk-page system (wiki-style per-object comment store): forks moderation, search, presence, and the terminal client, and contradicts the parent identity decision. Rejected.
B. Migrate v1 history into a new anchored v2 format: breaks event signatures and causal history, orphans v1-only clients, and buys nothing the additive events don't. Rejected.
C. Anchors as client-local metadata only: never syncs, replicas diverge, the object-side anchor index is impossible, and terminal clients can't participate on equal terms. Rejected.
D. Talk page embedded in the object (object owns a comment list): couples discussion storage to object versioning/tombstoning, makes cross-object threads inexpressible, and breaks conversation-first clients. Rejected.
Invariants an implementation must hold (testable)
Single log: talk view and chat view are projections of one event log; no message exists in one and not the other.
Additivity: every extension is a new event kind or a new link kind; a log with no anchor events projects identically to v1.
No history mutation: retroactive anchoring never edits, re-signs, or reorders existing events.
Determinism: anchor and talk_for state is a pure fold over the log under the existing causal order + event-ID tie-break; all replicas converge, including on disputed bindings.
One canonical talk page per object, with conflicts visible, never silently forked.
Boundaries — what this record deliberately does not decide
Anchor part-selector grammar, anchor survival/drift under edits → id: design: chat anchor data model (from #5682) #5870 (still open; this record references its vocabulary and constrains it only to by-reference selectors).
Talk-page lifecycle (lazy vs eager creation, moderation, ownership transfer) and thread-visibility rules → id: design: talk-page semantics (from #5682) #5871. This record constrains it in one respect only: creation must be representable as a binding event, because retroactive designation depends on it.
Implementation filed as #6222, gated on (a) the separate implementation-timing decision and (b) #5870 and #5871 landing their decisions. No queue task was spawned for it — starting it now would be premature on both gates. No further design splits: the remaining #5682 decomposition items already exist as #5870/#5871/#5873/#5874/#5875.
Queue: task 10499 completes with this record as its outcome (triaged outcome class; the design work named in the packet is this record).
Activity
developing-today commentedon Nov 5, 2026
CODE-1555
dezren39 commentedon Nov 5, 2026
🤖 Agent note · done (triage)
Triage findings (verified fresh 2026-11-05):
Disposition: triaged; design work proceeds as the follow-up task named in the addendum. Issue #5872 stays open for it.
dezren39 commentedon Nov 5, 2026
🤖 Agent note · addendum to task 10274 triage
dezren39 commentedon Nov 5, 2026
Decision record — chat ↔ talk-page unification (task 10499)
This closes the design question on this issue. Chosen shape, alternatives, and invariants below; implementation is a separate, gated track (#6222).
Context fixed by the parents (not re-decided here)
main(pkgs/id/src/chat.rs): a message is a signedchat.message.v1event in a channel group; authorship is the signing key; causal order is theafterfrontier + author backlink, tie-broken deterministically by event ID;ChatLinkkinds are exactlyblobandevent, a link names content by hash + author and never embeds a capability.Decision: unification by additive binding events on the one conversation log
talk_forbinding (below). Chat clients and object-embedded views are two projections of the same event log.chat.anchor.v1(chained permanent, in the conversation's group): binds, unbinds, or re-locates a conversation↔object anchor. Payload: target object (ID + author key), link type (vocabulary owned by id: design: chat anchor data model (from #5682) #5870: about, anchored-to-part, derived-from, resolved-into), and for part anchors a by-reference selector (object ID + part identifier per id: design: chat anchor data model (from #5682) #5870's grammar) — never embedded object content, never a capability.talk_forbinding: achat.anchor.v1event with link type talk-for designates the conversation as the object's talk page. Exactly one canonical binding per object: the first valid binding in causal order wins; a later conflicting binding folds to a visible disputed state on all replicas, never a silent fork. Binding authority (who may bind — object owner / members) is enforced at admission under the object's policy, with the detailed permission rules owned by id: design: talk-page semantics (from #5682) #5871.ChatLinkgains a third kind,object: an addressable ID object named by ID + author, alongside v1'sblob/event. This is the in-message anchor and the paste-a-link upgrade path. Recorded explicitly, per this issue's instructions: this extends the implemented v1 inchat.rs("onlyblobandeventare v1 link kinds") — it is an additive extension decided here, not a silent fork of id: design: ID chat v1 #2051; pre-extension clients treat it under invariant 7 below.talk_forbinding, and thread-object bindings as they fall out of (2)–(3).Decision: retroactive anchoring of v1 chats — additive only, confirmed by a member
talk_forbinding event (subject to the one-canonical rule and binding authority above). It keeps its history, membership, and identity.chat.anchor.v1event. No scanner silently rewrites a conversation's meaning after the fact.Alternatives rejected
Invariants an implementation must hold (testable)
talk_forstate is a pure fold over the log under the existing causal order + event-ID tie-break; all replicas converge, including on disputed bindings.chat.anchor.v1events andobjectlinks without failing projection.Boundaries — what this record deliberately does not decide
objectlink kind, item 4) is recorded above rather than forked silently.Follow-on
Implementation filed as #6222, gated on (a) the separate implementation-timing decision and (b) #5870 and #5871 landing their decisions. No queue task was spawned for it — starting it now would be premature on both gates. No further design splits: the remaining #5682 decomposition items already exist as #5870/#5871/#5873/#5874/#5875.
Queue: task 10499 completes with this record as its outcome (
triagedoutcome class; the design work named in the packet is this record).