Skip to main content

Module discussion_sync

Module discussion_sync 

Source
Expand description

Hosted discussion sync bridge.

Local discussions live in the append-only CollaborationStore op-log (.heddle/collaboration/ops). The hosted weft CollaborationService speaks a different, per-state DiscussionsBlob model (id-keyed discussions with a linear turn list). This module bridges the two:

  • Push (write path): after a successful heddle push, replay local symbol-anchored discussion turns we authored to the server via the caller-authenticated OpenDiscussion / AppendTurn RPCs (enforce-mode signed). #549 rejects attachments in the pack, so they cannot ride it.
  • Pull/clone (read path): after a successful clone/pull, ListByState the head state’s discussions and materialize any turns we do not already hold into the local op-log so discuss list / discuss show see them.

§Turn identity

Local turn order is op-log materialization order; server turn order is push/append order. They diverge the moment both sides append, so a single “N turns synced” prefix count is a lie. Instead the per-repo mirror map (.heddle/collaboration/hosted-mirror.json) records, per discussion, an explicit set of turn links: a local turn id ↔ a server turn ordinal.

A local turn id is (CollabOpId, index-within-op) — NOT the CollabOpId alone, because a LegacyImported op (a migrated blob→op-log discussion) materializes all its turns under one shared CollabOpId. Keying on the op alone would give turns 2..N the same idempotency key with different bodies (a weft with_idempotency conflict) and collapse them into one link, so the rest would be silently dropped on exactly the migrated repos.

Push sends only turns that are self-authored AND unlinked; pull materializes only server ordinals not yet linked. Client operation ids are derived from the stable turn id, so a retry replays instead of conflicting.

§Reconciliation is author-aware, never body-alone

When the mirror map is lost/rebuilt, an unlinked server turn is reconciled against an unlinked local turn only under an explicit author rule — never body equality alone, which would cross-link two different authors’ identical bodies ("lgtm", "+1") and silently drop one:

  • (i) a turn WE pushed — the local turn is self-authored AND the server turn’s author is our own hosted username (weft stamps Principal::new(username, "")); or
  • (ii) a turn we previously PULLED — the local op’s author (written as Principal::new(author_name, author_email)) and occurred_at_ms exactly equal the server turn’s author and posted_at.

Anything matching neither rule materializes as a new, distinct turn. Distinguishing “a turn I pushed” from “a turn another clone of the SAME user pushed” is impossible client-side without server-minted turn ids (weft#640); rule (i) is precise across distinct hosted principals, which is the real multi-party case.

The mirror is saved after each discussion and on the error path, with collect-and-continue per discussion — one wedged discussion (e.g. weft#638’s no-HEAD AppendTurn) cannot abort the rest, and a mid-run failure never leaves durable writes without their mapping.

Scope: discussions only; context/review share the same seam (not built). resolve/reopen are not yet mirrored (turns only).

Functions§

pull_discussions
Fetch hosted discussions for the repository head and materialize any turns we do not already hold. Saves the mirror after each discussion and continues past a per-discussion failure.
push_discussions
Publish local symbol-anchored discussion turns we authored to the hosted CollaborationService. Saves the mirror after each discussion and continues past a per-discussion failure (warn-and-skip).