Skip to content

mesh: don't resurrect a device another owner evicted#193

Merged
mrjeeves merged 1 commit into
mainfrom
claude/node-eviction-role-downgrade-k54mhp
Jul 23, 2026
Merged

mesh: don't resurrect a device another owner evicted#193
mrjeeves merged 1 commit into
mainfrom
claude/node-eviction-role-downgrade-k54mhp

Conversation

@mrjeeves

Copy link
Copy Markdown
Owner

Problem

Part of the "I evicted a node but it stays in the fleet / stays controllable, and other owners can't see it gone" report. The governance-layer half is fixed in MyOwnMesh (evicted owners/managers no longer resurrect on gossip-adopting peers). This is the AllMyStuff-side half.

An owner keeps a local, un-synced record of the devices it claimed — ownership.fleet_members. ensure_fleet_network's background loop re-signs and re-approves each of them into the fleet's signed roster on every re-assertion, to keep membership authoritative and self-sufficient. That local list is only pruned by the owner who authors a kick (kick_member).

So when a different owner evicts a device, the claiming owner's list still carries it. Its admit loop then:

  • authors a fresh RoleGrant{member} (a new member-log entry, stamped later than the evict → it wins the last-writer-wins tie), and
  • re-approves it into the roster,

resurrecting the evicted device across the whole fleet. That's exactly why co-owners "can't see it gone" and it stays controllable.

Fix

Before the admit loop re-asserts the claimed-list, reconcile it against the signed governance. Pull the daemon's authoritative removed set — GovernanceState's new evicted field (the member_log_removed projection over the signed member log) — and drop any locally listed device the fleet has evicted, then refresh the control-authorization cache and the GUI roster if anything changed.

  • Best-effort / fail-safe: an empty set (no fleet, an older daemon that doesn't report the field, or a read error) prunes nothing, so a transient failure or a version skew never drops a live member — it just falls back to the prior re-asserting behavior.
  • Uses the existing ownership.kick_member (local-list prune only; the signed roster already converged the removal) and the same GovernanceState request signed_role_holders already makes.

Paired with MyOwnMesh PR #105, which fixes the governance projection and adds the evicted field this reads. .myownmesh-rev still pins the current daemon; this activates once that daemon ships (until then, evicted is absent → no-op).

Testing

cargo check + cargo clippy --all-targets -- -D warnings (node crate, stable toolchain — matches node-check.yml) pass clean; cargo fmt --check clean. The prune is thin glue over already-tested primitives: ownership.kick_member (covered in ownership.rs tests) and the daemon's member_log_removed projection (covered in MyOwnMesh's governance tests). mesh.rs has no mock-daemon test seam, so there's no unit test for the RPC wiring itself.

🤖 Generated with Claude Code

https://claude.ai/code/session_018x1RV6ppUdMDTHxScVo26L


Generated by Claude Code

An owner keeps a local, un-synced record of the devices it claimed
(`ownership.fleet_members`), and `ensure_fleet_network`'s background loop
re-signs + re-approves each of them into the fleet's signed roster to keep
membership authoritative. That list is only pruned locally by the owner who
authors a kick — so when a *different* owner evicts a device, the claiming
owner's list still carries it, and the loop re-admits it (a fresh,
later-stamped member-log grant that wins the last-writer-wins tie),
resurrecting the evicted device across the whole fleet. That's the "other
owners can't see it gone / it stays controllable" half of the eviction bug.

Before the admit loop re-asserts the list, reconcile it against the signed
governance: pull the daemon's authoritative removed set (`GovernanceState`'s
new `evicted` field, projected from the signed member log) and drop any
locally listed device the fleet has evicted, refreshing the control-auth
cache and the GUI roster when it changes. Best-effort — an empty set (no
fleet, an older daemon without the field, or a read error) prunes nothing, so
a transient failure never drops a live member.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018x1RV6ppUdMDTHxScVo26L
@mrjeeves
mrjeeves merged commit c713a67 into main Jul 23, 2026
14 checks passed
@mrjeeves
mrjeeves deleted the claude/node-eviction-role-downgrade-k54mhp branch July 23, 2026 16:51
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.

2 participants