Expand description
Hosted context (annotation) sync bridge.
Local context annotations live in per-state Context attachments
(ContextBlobs keyed by target). The hosted weft RepositoryService speaks
an id-keyed annotation model with the SAME Annotation shape, mutated
through the caller-authenticated SetContext/ReviseContext/
SupersedeContext RPCs and read back through ListContext/
GetContextHistory. This module bridges the two, mirroring
crate::client::discussion_sync.
§Identity: annotation id AND per-revision id links
A context annotation carries a globally-unique annotation_id, and each
revision a revision_id. When the server ships an annotation back on a pull,
the pack carries its Annotation verbatim, so a pulled annotation’s local id
(and each pulled revision’s id) IS the server id. A locally-authored
annotation/revision is minted with a fresh uuid, so its id differs from the
server id the RPC assigns. The mirror map records BOTH:
local_annotation_id ↔ server_annotation_id and, per annotation, a set of
local_revision_id ↔ server_revision_id links.
A prefix COUNT of “revisions synced” is wrong: with two clones concurrently revising one annotation, the linear server list interleaves their revisions, so a count both drops one author’s revision and duplicates the other’s on the next pull. Reconciliation is therefore by revision-id set difference in server order, and pull rebuilds the local revision list to match server order so the “current” revision agrees across clones.
§Reconciliation is author-aware, never body-alone
Where an id link is missing (a lost/rebuilt mirror, or recovering a
server-minted id), an unlinked server revision is matched to an unlinked
local revision only under an explicit author rule — never body equality
alone, which would cross-link two different authors’ identical bodies
("lgtm") and silently drop one:
- (i) a revision WE pushed — the local revision is self-authored (its
attribution equals our local attribution) AND the server stamped it with
our own hosted username (
"{username} <>"); or - (ii) a revision we previously PULLED — the local attribution +
created_atexactly equal the server revision’s. Minted-annotation-id recovery afterSetContextuses the same author rule (weft returns only a count, not the id): it adopts the annotation at the target whose attribution is our hosted username, whose content matches, and whose id is not already linked.
§Idempotent, crash-safe create
SetContext mints the id server-side and does not return it. Before the RPC
the mirror records a pending_create_op (write-ahead, persisted to disk) used
as the client_operation_id, so a retry after a crash replays (weft dedup)
instead of duplicating, and recovery re-adopts the already-created annotation
by author+content among ids NOT already linked — closing the “could not
recover minted id” wedge.
§Supersession chains through the mirror
supersedes_annotation_id is a LOCAL id; it is resolved to the SERVER id via
the mirror before calling SupersedeContext, so superseding a previously
pushed annotation marks the right server annotation superseded (rather than
degrading to a plain create that leaves the old one Active on every clone).
§weft#638 limit (degrade gracefully, don’t fix here)
Each RPC advances the server head (context lives per-state); a no-HEAD repo is
skipped. The mirror is saved after every annotation and on the error path,
collect-and-continue, so one wedged annotation never aborts the rest or
orphans a durable write. Scope: annotations only (rm is local-only).
Functions§
- pull_
context - Fetch hosted annotations for the head and reconcile them into the local
Contextattachment, rebuilding revision order to match the server. - push_
context - Publish local annotations we authored to the hosted
RepositoryService.