itsgoin/IMPLEMENTATION_PLAN_0.6.md
Scott Reimers 921a0ec40a Implementation plan: drop cross-version compat, beta/stable as separate networks
Explicit stance: 0.6.x beta does not interoperate with 0.5 stable.
Removes dual-write migration machinery, mixed-version wire handlers,
and cross-track testing. Users cross tracks via export/import bundle.

Preserves: local upgrade path for user's own data; serde-default wire
field additions; identity bundle format compat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 18:26:55 -04:00

13 KiB
Raw Permalink Blame History

Implementation Plan: Identity Architecture Rollout (0.6.x beta cycle)

Context

0.5.3 is stable. The 0.6.x beta line introduces the Network-ID / Posting-ID split, multi-persona support, ephemeral rotating DM identities, file-holder CDN restructure, and CDN-only DM privacy.

Full architectural plan: /home/sologretto/.claude/plans/woolly-nibbling-glade.md Canonical reference: website/design.html §28 Memory summary: reference_identity_architecture.md

Backward-compatibility stance

Beta and stable are separate networks. A v0.6 node is not expected to interoperate with a v0.5 node. This is an explicit decision to reduce implementation overhead — no dual-writing legacy tables, no "match on either old or new field" handlers, no migration-through-mixed-network testing.

What we DO preserve:

  • Local upgrade path. A user upgrading their own install from 0.5 → 0.6 must not lose data. Existing posts, follows, keys, blobs continue to work. Migration is one-way (users don't downgrade) but must be data-safe.
  • Free wire-level compat. If a protocol field is already #[serde(default, skip_serializing_if = "Option::is_none")], new fields simply follow that pattern. Old serialized data with missing new fields deserializes fine. No special effort.
  • User data interop across versions. An exported 0.5 identity bundle must be importable into 0.6.

What we DROP:

  • Dual-writing deprecated tables during a phase transition. When a phase swaps out a data structure, the old one is gone.
  • "Works with v0.5 peers" testing. Beta is tested against beta.
  • Legacy wire-protocol tolerances beyond what serde defaults give for free.

Each phase is a standalone release. Phases are ordered to satisfy internal code dependencies, not wire-compat boundaries. After all phases ship, 0.7.0-beta consolidates and 0.7.x becomes the candidate for the next stable promotion.


Phase 1 (v0.6.0-beta): Remove direct PostPush for encrypted posts

Goal: Eliminate the sender→recipient traffic signal. Encrypted DMs propagate via the existing ManifestPush / CDN tree, indistinguishable on the wire from any other encrypted post.

Scope:

  • Remove push_post_to_recipients from crates/core/src/node.rs entirely
  • Remove the PostPush message handler from connection.rs and/or leave it as a stub that logs + ignores (avoid needing to invent a new message type ordering)
  • Ensure new encrypted posts still trigger a normal header update on neighbor posts (existing CDN logic already does this) so they propagate
  • Verify the existing ManifestPush fan-out path reaches recipients who follow the author's posting ID
  • Add a "CDN delivery SLA" doc note: expect ~seconds to tens-of-seconds latency for DMs to followers; minutes worst case for offline-then-online recipients

Verification:

  • Send an encrypted DM with two test devices. Confirm the recipient receives it without any direct-push message firing (network log inspection)
  • Measure p50/p95 delivery latency on a small mesh of 0.6.0 nodes
  • Confirm cold-contact DMs (non-follower) don't reach — expected until Phase 3 or comment-intro UX arrives

Risks:

  • Non-follower DMs don't reach until later phases. Ship with UX messaging: "DMs to new contacts require either following them or commenting on their post first."

Phase 2 (v0.6.1-beta): File-holder CDN + header-diff propagation

Goal: Replace the upstream/downstream tree with per-file flat holder sets. Prerequisite for multi-device (because upstream-toward-author doesn't work with multiple authoring network IDs).

Scope:

  • New file_holders table: (file_id, peer_id, last_interaction_ms, direction) — direction records whether we sent or received the file with this peer, for potential reuse
  • Populate on ManifestPush receive, blob fetch, blob serve, engagement diff exchange
  • Cap per file_id at 5 holders (LRU on last_interaction_ms)
  • Refactor engagement diff delivery: instead of "send to upstream," send to the file's up-to-5 known holders
  • Drop post_upstream and post_downstream tables in this phase's migration. No dual-write. (Local upgrade safety: before-drop, scan these tables to seed the new file_holders table with any existing peer relationships so we don't start empty.)
  • ManifestPush propagation logic: when header of file A changes, look up A's holders, send diff to them
  • Receiver behavior: apply header diff, pull any referenced new files

Wire protocol:

  • BlobHeaderDiff and ManifestPush messages stay the same on the wire
  • What changes is the sender's list of destinations (was: upstreams; now: holders)

Local migration (user upgrading 0.6.0 → 0.6.1):

  • On first startup with new schema, read post_upstream + post_downstream, populate file_holders with those peers as initial holders for their respective posts
  • Drop the old tables after the seed migration
  • User's existing posts, follows, blobs all untouched

Verification:

  • Mesh test: create a post, track its propagation; confirm holders accumulate up to 5 diverse peers
  • Engagement propagation: a reaction on a deeply-nested post still reaches the author (now: via the post's holders)
  • Migration test: upgrade a populated 0.6.0 DB to 0.6.1; confirm existing posts still have at least one holder in file_holders

Risks:

  • Churn in holder sets during network instability (holders going offline trigger LRU replacement). Need soak testing.
  • Potential duplicate diff delivery if holder sets overlap. Idempotent application is already required by existing code.

Phase 3 (v0.6.2-beta): Merged pull + recipient-match

Goal: Nodes can find DMs addressed to them without a distinguishable "searching for DMs" traffic pattern. Handles the case where a DM exists on a peer you're connected to but you don't follow the author.

Scope:

  • Add index on wrapped_key.recipient in storage (migration)
  • Extend PullSyncRequestPayload handling: peer returns posts matching author ∈ query_ids OR wrapped_key.recipient ∈ query_ids
  • Client: always include own NodeId in the pull query's NodeId list
  • No new message type; existing PullSync handler gets smarter
  • UX: no user-visible change (pull is internal)

Wire protocol:

  • No wire format change. Same fields, broader server-side matching logic.

Verification:

  • Send a DM from A to B where A is not followed by B. Verify B receives it on next pull cycle (via the recipient-match).
  • Benchmark pull query cost with the new OR-clause. Should be near-zero with the index.

Risks:

  • Query cost grows with posts held × recipient list length. Index is mandatory.
  • Peer can keep a log of "query included NodeId X" — tied to your connection anyway, not new info.

Phase 4 (v0.6.3-beta): Posting-key / network-key split

Goal: Decouple signing identity from network identity. Foundation for multi-device and multi-persona. This phase ships WITHOUT UI for creating multiple personas — it's the plumbing.

Scope:

  • PostingIdentity struct in crates/core/src/types.rs: { node_id, secret_seed, display_name, created_at }
  • Storage: posting_identities table (list of all held posting keys), active_default_posting_id setting
  • Storage: migrate existing identity → single posting identity with the same key as the network key (no behavior change for existing users)
  • crypto.rs: separate signing primitives — sign_with_posting_key vs sign_with_network_key. Keep existing sign_manifest working by delegating to posting-key variant when available
  • Node: load posting keys alongside network key at startup
  • BlobHeader.author: populated from posting key (was: network key, but they were equal)
  • Posts signed with posting key; connections still use network key
  • Export/import bundle includes posting key (in addition to network key)
  • Wire: no new message types; InitialExchange doesn't need to change because posting IDs are only relevant for signed content, not connection setup

Wire protocol:

  • BlobHeader already has an author field. We just populate it from posting key instead of network key.
  • First-run migration for upgrading users: existing identity becomes the first posting identity. posting_id == network_id for pre-split content. Users can create additional posting identities after the upgrade.

Verification:

  • Existing identity still works; no data loss after 0.5→0.6 migration chain
  • Posts authored before the upgrade still render and validate
  • New posts use posting key; confirm signatures verify with the posting key

Risks:

  • Signature verification regression if author field handling changes subtly. Needs unit tests for both "legacy author = network key" and "posting key ≠ network key" cases.
  • Storage migration needs transaction safety.

Phase 5 (v0.6.4-beta): Multi-persona UX

Goal: Let users create and use multiple posting identities with clean UX.

Scope:

  • IPC: list_posting_identities, create_posting_identity(name, avatar), set_default_posting_identity(id), delete_posting_identity(id)
  • Frontend: Settings > Personas page with create/list/delete
  • Compose box: persona picker (avatar + name + dropdown)
  • Contextual defaults: posting to a circle uses that circle's last-used persona
  • Feed: merged view, filter pills per persona
  • Reply/comment: default persona = whichever decrypted the post
  • Subtle per-post labels showing which persona's follow surfaced each item

Wire protocol:

  • No changes (posting keys are already supported from Phase 4)

Verification:

  • Create three personas, post from each, confirm peers see three distinct authors
  • DM each persona from a peer; confirm messages route to separate inbox threads locally
  • Social graph separation test: follow one peer from Persona A and a different peer from Persona B; confirm merged feed shows both but filter isolates each

Risks:

  • UX complexity regression — the merged feed with filters is non-trivial. Start with two-persona users and expand.
  • Existing users with single identity should see ZERO UI change until they opt in to creating a second persona.

Phase 6 (v0.6.5-beta): Ephemeral rotating DM IDs + local archive

Goal: Maximum traffic-graph concealment for DMs. Each thread gets a rotating posting ID, messages include handshake for the next ID, local archive preserves history.

Scope:

  • Per-thread ephemeral posting ID generation
  • Handshake field in encrypted post payload: next_posting_id: SecretSeed
  • Sliding window of last 10 accepted IDs per thread
  • Local archive post: encrypted-to-self, replicates across user's linked devices via the multi-device shared-posting-key mechanism
  • UX: DMs appear as continuous thread in the UI despite wire-level rotation
  • Group thread rotation

Wire protocol:

  • No new message types — ephemeral posting IDs are just short-lived posting keys. The handshake field lives inside the encrypted payload.

Verification:

  • Observer test: capture a DM thread's wire traffic; confirm no cryptographic tie between successive messages
  • Resilience: drop a message mid-thread; confirm the next message catches up via the sliding window
  • Archive test: scroll back through a 100-message thread; confirm all messages visible via local archive even though wire-level IDs are long forgotten

Risks:

  • Complexity of rotation + archive is high. Lots of state to get right.
  • UX: users need to understand that "they can't recover messages from a new device without importing the archive" — this is true already for any encrypted history, but doubly so here.

Version promotion plan

  • 0.5.3 is stable. Last stable of the pre-split architecture. Maintenance-only (critical bugs, security). No new features.
  • 0.6.0 through 0.6.5 are beta releases shipping Phases 1-6. Beta users form a network distinct from stable users.
  • 0.7.0-beta consolidates the complete new architecture; the legacy code paths are already removed as each phase shipped, so 0.7.0 is mostly polish + integration testing.
  • 0.7.x-beta gets real-world soak testing.
  • 0.7.N → stable once the new architecture is proven.

No "migration bridge" version is needed between 0.5 stable and 0.6 beta — users choose a track. Moving between tracks is possible via the existing export/import identity bundle (users can carry their content across, because the bundle format is preserved as a free compat concern).

Order-of-operations recommendations

  • Phases 1 and 2 can overlap (CDN restructure can happen while PostPush is being removed — they touch different code paths).
  • Phase 3 depends on Phase 2 being in place (the file-holder refactor touches pull handling).
  • Phase 4 is a prerequisite for 5 and 6 but doesn't need to wait for 1-3 (it's local/storage only, wire protocol unchanged).
  • Phase 5 UX should wait for Phase 4 to ship and stabilize so persona-aware code is well tested.
  • Phase 6 is the most complex; ship last.

What must not regress

  • Existing users upgrading from 0.5.3 should see no behavior change until they explicitly opt in to new features.
  • Cross-version interoperability is required at every phase boundary.
  • Data integrity: all migrations must be transaction-safe and reversible (keep old tables until N+1 phase).

Not in scope for 0.6.x

  • Search/discovery improvements (existing Worm still used)
  • Anchor/directory redesign (separate planned track)
  • Erasure-coded CDN replication (separate planned track)
  • Reciprocity / Phase 2 economic features (still deferred)