Skip to content

fix: drive mesh convergence from the replication feed - #271

Open
LiranCohen wants to merge 4 commits into
fix/messages-feed-wirefrom
fix/feed-driven-refresh
Open

fix: drive mesh convergence from the replication feed#271
LiranCohen wants to merge 4 commits into
fix/messages-feed-wirefrom
fix/feed-driven-refresh

Conversation

@LiranCohen

Copy link
Copy Markdown
Contributor

Fixes #267

Stacked on #270 (fix/messages-feed-wire) — will retarget to main automatically when that merges. Review only the top 4 commits.

What changed

The daemon's control plane now converges via the replication feed wherever it can, replacing blind full reloads with fingerprint-verified freshness:

  • Feed mode (owner/anchor nodes via tenant bypass; wallet delegates via active protocol-root Messages.Read grants — which meshd has requested at connect since forever but never invoked): the two Records WebSocket streams collapse into one MessagesSubscribe stream filtered to the mesh protocol. The server's core-protocol shadow filters mean this single stream also carries $encryption/delivery control records and mesh-tagged permission/encryption records. Events remain invalidation-only wake hints — never checkpoint evidence.
  • Fingerprint-gated anti-entropy: periodic refreshes first fetch the feed fingerprint (cids-only MessagesQuery, limit 1); if unchanged since the last successful load, the full 8-phase LoadState is skipped. Event/startup/manual/endpoint reasons always load, and a full load is forced at least every 30 minutes regardless.
  • Persisted checkpoints (feedsync.json in the profile state dir, atomic write + owner alignment): resume cursor + fingerprint bound to a link key (tenant^endpoint^projectionId^authorizationEpoch). Grant churn or expiry changes the epoch → cursor discarded; 410 ProgressGap clears it; restart resumes instead of cold full-refresh.
  • Grant-revocation surfacing: a feed subscription that terminates with the delivery-authorization sentinel drops to fallback polling AND surfaces a degraded reason through daemon status ("wallet session grants expired or revoked — run 'meshd auth connect' to refresh them"). Generic delivery failures deliberately do not claim grant expiry (covered by tests).
  • Role-only invite-joined nodes are untouched: no Messages.Read grants means no Messages interface (tenant bypass is owner-only), so they keep the existing topology+delivery Records streams and polling exactly as today. The RefreshCoordinator's stream set is now constructor-configurable to support both shapes; subscribe-before-repair and sequence-fenced repair semantics are preserved.

Verification

  • go build ./... — clean
  • go vet ./... — clean
  • go test ./... -count=1 -race — all packages pass, no data races
  • New tests: feed-mode eligibility, link-key determinism + churn discard, cursor persist/adopt/mismatch/gap-clear/debounce, fingerprint gating (skip / change / 30m force / probe failure), configurable coordinator streams, auth-terminal vs plain-terminal classification, daemon status mapping. Integration tests remain gated on DWN_ENDPOINT.

🤖 Generated with Claude Code

LiranCohen and others added 4 commits July 26, 2026 15:53
Add feedsync.json ({version, linkKey, cursor, fingerprint, updatedAt}) to
the profile state dir with the same atomic-write + alignOwnerToDir rules
as network.json, so a root daemon leaves it readable by the non-root CLI.
Checkpoints from an incompatible schema version are discarded on load,
and clearing is wired for network leave.

Export ProgressToken.Valid so restorers can discard unusable persisted
cursors instead of failing the subscribe.

Refs #267

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ti-entropy

Nodes that can speak the Messages interface — the anchor owner (tenant
bypass) and wallet delegates holding active protocol-root Messages.Read
grants — now run ONE 'feed' subscription stream (MessagesSubscribe,
filters [{protocol: mesh}], plural permissionGrantIds, persisted resume
cursor) instead of the topology+delivery Records streams. The server's
core-protocol shadow filters carry delivery and permission records on
the same stream, so it subsumes both. Events remain invalidation-only
wake hints; role-only nodes keep the existing two-stream behavior.

- RefreshCoordinator: constructor-configurable stream set ([feed] vs
  [topology, delivery]); operations on unknown streams are ignored;
  health/mode semantics unchanged. New DegradedReason status field.
- SubscriptionWatcher: feed mode with cursor persistence (debounced on
  events, immediate on EOSE, cleared on 410 ProgressGap) through a
  feedSyncRecorder that owns the checkpoint; the auth-terminal sentinel
  (grants revoked/expired mid-stream) drops the stream to uncovered
  polling fallback and surfaces an operator-facing reason.
- DWNControl: periodic-only refresh batches probe the remote feed
  fingerprint (cids-only MessagesQuery via control.FeedFingerprint) and
  skip the full 8-phase LoadState when it matches the fingerprint
  recorded immediately before the last successful load; a full rebuild
  is still forced at least every 30 minutes. Event-driven, startup,
  manual, and endpoint reasons always rebuild.
- Engine.Config: feed grant IDs + epoch tuples and FeedSyncLoad/Save
  hooks (the engine never learns file paths); the link key
  (tenant^endpoint^projectionId^authorizationEpoch) gates cursor resume
  so grant churn discards stale checkpoints.

Refs #267

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tatus

DelegateSession now resolves the active protocol-root Messages.Read
grants for the mesh protocol into canonical grant IDs plus the
{id, dateGranted, dateExpires} epoch tuples; sessions without feed
coverage stay on the Records streams. cmd/meshd threads them (and the
feedsync.json load/save hooks) into engine.Config like the existing
grant plumbing, and clears the feed checkpoint on network leave.

daemon.SnapshotStatus carries the coordinator's DegradedReason so
'meshd status' can tell operators when wallet session grants expired
mid-stream and a reconnect is needed.

Refs #267

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MessagesSubscribeDeliveryFailed is the server's generic non-DwnError
delivery fault, unrelated to grants, but it was wrapped in
ErrSubscriptionAuthorizationRevoked alongside the authorization code.
That made handleFeedLifecycle drop the feed stream to uncovered polling
and surface the wallet-grant degraded reason ('run meshd auth connect')
— categorically wrong on anchor-owner nodes, which have no wallet
session, and misleading for delegates. Only
MessagesSubscribeDeliveryAuthorizationFailed now wraps the sentinel;
DeliveryFailed takes the plain terminal path (coverage retained,
live=false, fallback cadence), matching the enbox reference agent,
which pauses only on auth codes and repairs/retries otherwise.

Refs #267

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant