228	            .map(|b| format!("{b:02x}")).collect::<String>() },
229	        "channel": "stable",
230	    });
231	    std::fs::write(
232	        &a_paths.release_keys,
233	        serde_json::to_string(&keys_json).unwrap(),
234	    )
235	    .unwrap();
236	    let perch_path = perch::resolve_perch_path_in(&a_paths.owlery, "pump-ag", ParentHint::Infer);
237	    std::fs::create_dir_all(&perch_path).unwrap();
238	    let rec = InfoJson::new("pump-ag", "now", std::process::id(), "s", "ready_agent");
239	    info::write_info(&perch_path, &rec).unwrap();
240
241	    let mut a_subnets = SubnetStore::default();
242	    a_subnets.create_subnet(subnet).expect("A subnet");
243	    a_subnets.save_to(&a_paths.subnets).expect("save A subnets");
244	    let mut a_trust = TrustStore::default();
245	    a_trust.record(subnet, &b_hex, None, None);
246	    a_trust.save_to(&a_paths.trust).expect("save A trust");
247	    // Mesh-D5: A's inbound gates authorize on roster membership — A must roster
248	    // B. The pump derives the roster path as a sibling of `subnets` (subnet.json
249	    // → roster.json), so write it there.
250	    let mut a_roster = spt_store::roster::RosterStore::default();
251	    a_roster.merge_entry(spt_store::roster::RosterEntry {
252	        pubkey_hex: b_hex.clone(),
253	        subnet: subnet.to_string(),
254	        label: String::new(),
255	        machine_id: String::new(),
256	        address: None,
257	        last_seen: String::new(),
258	        lease_epoch: 1,
259	    });
260	    a_roster
261	        .save_to(&a_root.join("roster.json"))
262	        .expect("save A roster");
263	    let mut a_vis = VisibilityStore::default();
264	    a_vis.set_sync_subnets("ling-pumped", vec![subnet.to_string()]);
265	    a_vis.save_to(&a_paths.vis).expect("save A visibility");
266
267	    let a_store = NotifStore::open_at(&a_paths.notif_db).expect("A notif store");
268	    a_store
269	        .merge_row(&NotifRow {
270	            notif_id: format!("{a_hex}:1"),
271	            subnet: subnet.to_string(),
272	            kind: "agent".to_string(),
273	            from_id: format!("pump-ag@{a_hex}"),
274	            body: "pumped across".to_string(),
275	            created_ms: 1_000,
276	            dismissed: false,
277	            seen: Default::default(),
278	            last_surfaced_ms: 0,
279	        })
280	        .expect("seed A notif row");
281
282	    let a_registry = Arc::new(RegistryHost::new_at(
283	        &a_hex,
284	        EpochSource::load_from(&a_root.join("epoch.json")),
285	        a_root.join("registry"),
286	    ));
287	    // A learned of B's endpoint via an earlier inbound feed (seeded directly
288	    // here — B runs no pump in this test): the bootstrap-ref source.
289	    {
290	        let mut roster = spt_store::roster::RosterStore::default();
291	        roster.merge_entry(spt_store::roster::RosterEntry {
292	            pubkey_hex: b_hex.clone(),
293	            subnet: subnet.to_string(),
294	            label: String::new(),
295	            machine_id: String::new(),
296	            address: None,
297	            last_seen: String::new(),
298	            lease_epoch: 1,
299	        });
300	        let pol = RegistryGatePolicy {
301	            subnets: vec![subnet.to_string()],
302	            roster,
303	        };
304	        a_registry.apply_feed(
305	            &b_hex,
306	            &[RegistryUpdate {
307	                subnet: subnet.to_string(),
308	                endpoint_id: "ling-pumped".to_string(),
309	                instance: Instance {
310	                    node: b_hex.clone(),
311	                    status: Status::Active,
312	                    epoch: 1,
313	                    resources: None,
314	                    last_active_ms: None,
315	                    shell_adapters: Vec::new(),
316	                    node_label: None,
317	                    machine_id: None,
318	                },
319	            }],
320	            &pol,
321	        );
322	    }
323
324	    // ── The pump: production loop, injected resolver, fast cadences. ─────
325	    let resolver: PeerResolver = {
326	        let b_hex = b_hex.clone();
327	        let b_addr = b_addr.clone();
328	        Box::new(move |hex| {
329	            if hex == b_hex {
330	                Some(b_addr.clone())
331	            } else {
332	                None
333	            }
334	        })
335	    };
336	    {
337	        let name = a_name.clone();
338	        let reg = Arc::clone(&a_registry);
339	        let flag = Arc::clone(&stop);
340	        let paths = PumpPaths {
341	            owlery: a_paths.owlery.clone(),
342	            ctx_root: a_paths.ctx_root.clone(),
343	            notif_db: a_paths.notif_db.clone(),
344	            scratch: a_paths.scratch.clone(),
345	            subnets: a_paths.subnets.clone(),
346	            trust: a_paths.trust.clone(),
347	            vis: a_paths.vis.clone(),
348	            ops: a_paths.ops.clone(),
349	            releases: a_paths.releases.clone(),
350	            release_keys: a_paths.release_keys.clone(),
351	            pull_marker: a_paths.pull_marker.clone(),
352	            advertise_marker: a_paths.advertise_marker.clone(),
353	            peer_addrs: a_paths.peer_addrs.clone(),
354	            heartbeat: a_paths.heartbeat.clone(),
355	        };
356	        thread::spawn(move || {
357	            let config = PumpConfig {

            .map(|b| format!("{b:02x}")).collect::<String>() },
        "channel": "stable",
    });
    std::fs::write(
        &a_paths.release_keys,
        serde_json::to_string(&keys_json).unwrap(),
    )
    .unwrap();
    let perch_path = perch::resolve_perch_path_in(&a_paths.owlery, "pump-ag", ParentHint::Infer);
    std::fs::create_dir_all(&perch_path).unwrap();
    let rec = InfoJson::new("pump-ag", "now", std::process::id(), "s", "ready_agent");
    info::write_info(&perch_path, &rec).unwrap();

    let mut a_subnets = SubnetStore::default();
    a_subnets.create_subnet(subnet).expect("A subnet");
    a_subnets.save_to(&a_paths.subnets).expect("save A subnets");
    let mut a_trust = TrustStore::default();
    a_trust.record(subnet, &b_hex, None, None);
    a_trust.save_to(&a_paths.trust).expect("save A trust");
    // Mesh-D5: A's inbound gates authorize on roster membership — A must roster
    // B. The pump derives the roster path as a sibling of `subnets` (subnet.json
    // → roster.json), so write it there.
    let mut a_roster = spt_store::roster::RosterStore::default();
    a_roster.merge_entry(spt_store::roster::RosterEntry {
        pubkey_hex: b_hex.clone(),
        subnet: subnet.to_string(),
        label: String::new(),
        machine_id: String::new(),
        address: None,
        last_seen: String::new(),
        lease_epoch: 1,
    });
    a_roster
        .save_to(&a_root.join("roster.json"))
        .expect("save A roster");
    let mut a_vis = VisibilityStore::default();
    a_vis.set_sync_subnets("ling-pumped", vec![subnet.to_string()]);
    a_vis.save_to(&a_paths.vis).expect("save A visibility");

    let a_store = NotifStore::open_at(&a_paths.notif_db).expect("A notif store");
    a_store
        .merge_row(&NotifRow {
            notif_id: format!("{a_hex}:1"),
            subnet: subnet.to_string(),
            kind: "agent".to_string(),
            from_id: format!("pump-ag@{a_hex}"),
            body: "pumped across".to_string(),
            created_ms: 1_000,
            dismissed: false,
            seen: Default::default(),
            last_surfaced_ms: 0,
        })
        .expect("seed A notif row");

    let a_registry = Arc::new(RegistryHost::new_at(
        &a_hex,
        EpochSource::load_from(&a_root.join("epoch.json")),
        a_root.join("registry"),
    ));
    // A learned of B's endpoint via an earlier inbound feed (seeded directly
    // here — B runs no pump in this test): the bootstrap-ref source.
    {
        let mut roster = spt_store::roster::RosterStore::default();
        roster.merge_entry(spt_store::roster::RosterEntry {
            pubkey_hex: b_hex.clone(),
            subnet: subnet.to_string(),
            label: String::new(),
            machine_id: String::new(),
            address: None,
            last_seen: String::new(),
            lease_epoch: 1,
        });
        let pol = RegistryGatePolicy {
            subnets: vec![subnet.to_string()],
            roster,
        };
        a_registry.apply_feed(
            &b_hex,
            &[RegistryUpdate {
                subnet: subnet.to_string(),
                endpoint_id: "ling-pumped".to_string(),
                instance: Instance {
                    node: b_hex.clone(),
                    status: Status::Active,
                    epoch: 1,
                    resources: None,
                    last_active_ms: None,
                    shell_adapters: Vec::new(),
                    node_label: None,
                    machine_id: None,
                },
            }],
            &pol,
        );
    }

    // ── The pump: production loop, injected resolver, fast cadences. ─────
    let resolver: PeerResolver = {
        let b_hex = b_hex.clone();
        let b_addr = b_addr.clone();
        Box::new(move |hex| {
            if hex == b_hex {
                Some(b_addr.clone())
            } else {
                None
            }
        })
    };
    {
        let name = a_name.clone();
        let reg = Arc::clone(&a_registry);
        let flag = Arc::clone(&stop);
        let paths = PumpPaths {
            owlery: a_paths.owlery.clone(),
            ctx_root: a_paths.ctx_root.clone(),
            notif_db: a_paths.notif_db.clone(),
            scratch: a_paths.scratch.clone(),
            subnets: a_paths.subnets.clone(),
            trust: a_paths.trust.clone(),
            vis: a_paths.vis.clone(),
            ops: a_paths.ops.clone(),
            releases: a_paths.releases.clone(),
            release_keys: a_paths.release_keys.clone(),
            pull_marker: a_paths.pull_marker.clone(),
            advertise_marker: a_paths.advertise_marker.clone(),
            peer_addrs: a_paths.peer_addrs.clone(),
            heartbeat: a_paths.heartbeat.clone(),
        };
        thread::spawn(move || {
            let config = PumpConfig {


Bash

grep -n "a_paths = \|let a_paths\|PumpPaths {\|trust:\|\.trust\b\|TrustStore\|is_trusted\|a_trust" "C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/tests/peerloop.rs"

Locate a_paths + trust refs in peerloop test


37:use spt_store::trust::TrustStore;
135:    let mut trust = TrustStore::load();
208:    let a_paths = PumpPaths {
214:        trust: a_root.join("trust.json"),
244:    let mut a_trust = TrustStore::default();
245:    a_trust.record(subnet, &b_hex, None, None);
246:    a_trust.save_to(&a_paths.trust).expect("save A trust");
340:        let paths = PumpPaths {
346:            trust: a_paths.trust.clone(),


spt-store: delete trust.rs, drop mod + trust_file

Delete crates/spt-store/src/trust.rs, remove `pub mod trust` from lib.rs, remove perch::trust_file (keep trust_dir for access/grants).

spt-net wire.rs: strip trust from ceremony

Remove trust param + TrustOutcome from run_responder/run_initiator/responder_admitted; drop ResponderOutcome.trust field; migrate wire.rs tests to roster membership.

pairhost.rs: drop trust write

Remove TrustStore load/save/args in both legs; update run_responder/run_initiator calls; demote warn-on-change to machine_id notif at repair_evict_superseded point.

propagate.rs serve gate trust->roster

serve_update signature trust:&TrustStore -> roster:&RosterStore; both is_member-any checks; dispatch.rs:326 caller.

peerloop.rs THE WIDEN

push target trust.peers_in -> roster.members_in; reuse roster loaded for sync gate; drop PumpPaths.trust + TrustStore load.

registryhost.rs: repair_evict drop trust

repair_evict_superseded drop &mut TrustStore param (roster-only now); migrate its unit tests.

CLI cli.rs repoint to roster

subnet status/list/prune trust->roster (prune=local tombstone); 1305 load; render unit tests.

xtask + misc comment refs

xtask main.rs:897 trust dump -> roster; nethost/notifsync/sync/access doc comment refs.

Migrate daemon tests trust->roster

twohost.rs, propagate.rs, peerloop.rs, dispatch.rs tests: TrustStore -> RosterStore membership.

mesh.rs: un-ignore + regression assert

Un-ignore D0 staggered+star harness; migrate trust seeding to roster; add no-third-party-row origin==author assert.

Activate REQ-MESH-3 + complete REQ-MESH-5

traceable-reqs.toml: REQ-MESH-3 [impl,unit,int], REQ-MESH-5 [impl,unit]; run traceable-reqs check; clippy; daemon suite.


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\tests\peerloop.rs has been updated successfully. (file state is current in your context — no need to Read it back)


Edit

    let mut trust = TrustStore::load();
    trust.record(subnet, &a_hex, None, None);
    trust.save().expect("save trust");
    // Mesh-D5: the inbound gates now authorize on roster membership, so B must
    // roster A (the push target stays the trust set — see the pump). Seed the
    // canonical roster to mirror the trust pin.
    let mut roster = spt_store::roster::RosterStore::load();

    // Mesh-D6: membership is the roster — B rosters A (both the inbound gate and
    // the pump's push target read it). Seed the canonical roster.
    let mut roster = spt_store::roster::RosterStore::load();


    let mut trust = TrustStore::load();
    trust.record(subnet, &a_hex, None, None);
    trust.save().expect("save trust");
    // Mesh-D5: the inbound gates now authorize on roster membership, so B must
    // roster A (the push target stays the trust set — see the pump). Seed the
    // canonical roster to mirror the trust pin.
    let mut roster = spt_store::roster::RosterStore::load();

    // Mesh-D6: membership is the roster — B rosters A (both the inbound gate and
    // the pump's push target read it). Seed the canonical roster.
    let mut roster = spt_store::roster::RosterStore::load();

-    let mut trust = TrustStore::load();

-    trust.record(subnet, &a_hex, None, None);

-    // Mesh-D5: the inbound gates now authorize on roster membership, so B must

-    // roster A (the push target stays the trust set — see the pump). Seed the

-    // canonical roster to mirror the trust pin.

+    // Mesh-D6: membership is the roster — B rosters A (both the inbound gate and

+    // the pump's push target read it). Seed the canonical roster.


203	    let a_paths = PumpPaths {
204	        owlery: a_root.join("owlery"),
205	        ctx_root: a_root.join("context"),
206	        notif_db: a_root.join("notifs.db"),
207	        scratch: a_root.join("scratch"),
208	        subnets: a_root.join("subnet.json"),
209	        trust: a_root.join("trust.json"),
210	        vis: a_root.join("visibility.json"),
211	        ops: a_root.join("pump-ops.json"),
212	        releases: a_root.join("releases"),
213	        release_keys: a_root.join("release-keys.json"),
214	        pull_marker: a_root.join("sync-pull-now"),
215	        advertise_marker: a_root.join("advertise-now"),
216	        peer_addrs: a_root.join("peer-addrs.json"),
217	        heartbeat: a_root.join("pump-heartbeat.json"),
218	    };
219	    // A trusts B's release signer — the update leg's production gate.
220	    std::fs::create_dir_all(&a_root).unwrap();
221	    let keys_json = serde_json::json!({
222	        "keys": { "k": sk.verifying_key().as_bytes().iter()
223	            .map(|b| format!("{b:02x}")).collect::<String>() },
224	        "channel": "stable",
225	    });
226	    std::fs::write(

    let a_paths = PumpPaths {
        owlery: a_root.join("owlery"),
        ctx_root: a_root.join("context"),
        notif_db: a_root.join("notifs.db"),
        scratch: a_root.join("scratch"),
        subnets: a_root.join("subnet.json"),
        trust: a_root.join("trust.json"),
        vis: a_root.join("visibility.json"),
        ops: a_root.join("pump-ops.json"),
        releases: a_root.join("releases"),
        release_keys: a_root.join("release-keys.json"),
        pull_marker: a_root.join("sync-pull-now"),
        advertise_marker: a_root.join("advertise-now"),
        peer_addrs: a_root.join("peer-addrs.json"),
        heartbeat: a_root.join("pump-heartbeat.json"),
    };
    // A trusts B's release signer — the update leg's production gate.
    std::fs::create_dir_all(&a_root).unwrap();
    let keys_json = serde_json::json!({
        "keys": { "k": sk.verifying_key().as_bytes().iter()
            .map(|b| format!("{b:02x}")).collect::<String>() },
        "channel": "stable",
    });
    std::fs::write(


        subnets: a_root.join("subnet.json"),
        trust: a_root.join("trust.json"),
        vis: a_root.join("visibility.json"),

        subnets: a_root.join("subnet.json"),
        vis: a_root.join("visibility.json"),


        subnets: a_root.join("subnet.json"),
        trust: a_root.join("trust.json"),
        vis: a_root.join("visibility.json"),

        subnets: a_root.join("subnet.json"),
        vis: a_root.join("visibility.json"),

-        trust: a_root.join("trust.json"),


    let mut a_trust = TrustStore::default();
    a_trust.record(subnet, &b_hex, None, None);
    a_trust.save_to(&a_paths.trust).expect("save A trust");
    // Mesh-D5: A's inbound gates authorize on roster membership — A must roster
    // B. The pump derives the roster path as a sibling of `subnets` (subnet.json
    // → roster.json), so write it there.
    let mut a_roster = spt_store::roster::RosterStore::default();

    // Mesh-D6: A's membership IS the roster — both its inbound gates and its
    // pump push target read it. The pump derives the roster path as a sibling of
    // `subnets` (subnet.json → roster.json), so write it there.
    let mut a_roster = spt_store::roster::RosterStore::default();


    let mut a_trust = TrustStore::default();
    a_trust.record(subnet, &b_hex, None, None);
    a_trust.save_to(&a_paths.trust).expect("save A trust");
    // Mesh-D5: A's inbound gates authorize on roster membership — A must roster
    // B. The pump derives the roster path as a sibling of `subnets` (subnet.json
    // → roster.json), so write it there.
    let mut a_roster = spt_store::roster::RosterStore::default();

    // Mesh-D6: A's membership IS the roster — both its inbound gates and its
    // pump push target read it. The pump derives the roster path as a sibling of
    // `subnets` (subnet.json → roster.json), so write it there.
    let mut a_roster = spt_store::roster::RosterStore::default();

let mut a_subnets = SubnetStore::default();

-    let mut a_trust = TrustStore::default();

-    a_trust.record(subnet, &b_hex, None, None);

-    a_trust.save_to(&a_paths.trust).expect("save A trust");

-    // Mesh-D5: A's inbound gates authorize on roster membership — A must roster

-    // B. The pump derives the roster path as a sibling of `subnets` (subnet.json

-    // → roster.json), so write it there.

+    // Mesh-D6: A's membership IS the roster — both its inbound gates and its

+    // pump push target read it. The pump derives the roster path as a sibling of

+    // `subnets` (subnet.json → roster.json), so write it there.


            subnets: a_paths.subnets.clone(),
            trust: a_paths.trust.clone(),
            vis: a_paths.vis.clone(),

            subnets: a_paths.subnets.clone(),
            vis: a_paths.vis.clone(),


            subnets: a_paths.subnets.clone(),
            trust: a_paths.trust.clone(),
            vis: a_paths.vis.clone(),

            subnets: a_paths.subnets.clone(),
            vis: a_paths.vis.clone(),

-            trust: a_paths.trust.clone(),


twohost.rs next.


270	}
271
272	/// The canonical-store pump paths for this host (the production locations the
273	/// daemon itself would use — both rig sides run real homes, not test roots).
274	fn canonical_pump_paths(scratch: PathBuf) -> PumpPaths {
275	    let home = perch::spt_home();
276	    PumpPaths {
277	        owlery: perch::owlery_dir(),
278	        ctx_root: perch::tracked_dir(),
279	        notif_db: perch::notifs_file(),
280	        scratch,
281	        subnets: perch::subnet_file(),
282	        trust: perch::trust_file(),
283	        vis: perch::visibility_file(),
284	        ops: home.join("pump-ops.json"),
285	        releases: home.join("releases"),
286	        release_keys: home.join("release-keys.json"),
287	        pull_marker: home.join("identity").join("sync-pull-now"),
288	        advertise_marker: home.join("identity").join("advertise-now"),
289	        peer_addrs: home.join("identity").join("peer-addrs.json"),
290	        heartbeat: home.join("identity").join("pump-heartbeat.json"),
291	    }

}

/// The canonical-store pump paths for this host (the production locations the
/// daemon itself would use — both rig sides run real homes, not test roots).
fn canonical_pump_paths(scratch: PathBuf) -> PumpPaths {
    let home = perch::spt_home();
    PumpPaths {
        owlery: perch::owlery_dir(),
        ctx_root: perch::tracked_dir(),
        notif_db: perch::notifs_file(),
        scratch,
        subnets: perch::subnet_file(),
        trust: perch::trust_file(),
        vis: perch::visibility_file(),
        ops: home.join("pump-ops.json"),
        releases: home.join("releases"),
        release_keys: home.join("release-keys.json"),
        pull_marker: home.join("identity").join("sync-pull-now"),
        advertise_marker: home.join("identity").join("advertise-now"),
        peer_addrs: home.join("identity").join("peer-addrs.json"),
        heartbeat: home.join("identity").join("pump-heartbeat.json"),
    }


        subnets: perch::subnet_file(),
        trust: perch::trust_file(),
        vis: perch::visibility_file(),

        subnets: perch::subnet_file(),
        vis: perch::visibility_file(),


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\tests\twohost.rs has been updated successfully. (file state is current in your context — no need to Read it back)

        subnets: perch::subnet_file(),
        trust: perch::trust_file(),
        vis: perch::visibility_file(),

        subnets: perch::subnet_file(),
        vis: perch::visibility_file(),


565
566	    // ── Rung 1 — pair (responder): the PRODUCT surface (M7 D5). The
567	    // daemon-hosted ALWAYS-ON responder — the broker NetHost speaking the
568	    // pre-trust pairing ALPN beside the trusted surface (pairhost) — answers
569	    // A's CLI-path ceremony. This test arms NOTHING: no dedicated endpoint,
570	    // no manual run_responder loop; the rung passing proves a stock member
571	    // daemon pairs a joiner. Trust lands in the canonical store the
572	    // dispatcher gates read.
573	    // [int->REQ-PAIR-1] [int->REQ-SUBNET-2]
574	    rig_wait(
575	        "pairing: A pinned via the daemon-hosted responder",
576	        rig.wait,
577	        || TrustStore::load().is_trusted(&rig.subnet, &rig.a_hex()),
578	    );
579	    println!("TWOHOST OK: pairing (responder, daemon-hosted)");
580
581	    // ── Production loops: dispatcher (serves A's pulls/feeds) + pump (pushes
582	    // B's registry/notif/mind, bootstrap-pulls A's). ──
583	    let (registry, stop) = start_loops(
584	        &broker_name,
585	        &rig.b_hex(),
586	        rig.a_hex(),
587	        rig.peer_broker_addr("b"),
588	        scratch.path(),
589	    );

    // ── Rung 1 — pair (responder): the PRODUCT surface (M7 D5). The
    // daemon-hosted ALWAYS-ON responder — the broker NetHost speaking the
    // pre-trust pairing ALPN beside the trusted surface (pairhost) — answers
    // A's CLI-path ceremony. This test arms NOTHING: no dedicated endpoint,
    // no manual run_responder loop; the rung passing proves a stock member
    // daemon pairs a joiner. Trust lands in the canonical store the
    // dispatcher gates read.
    // [int->REQ-PAIR-1] [int->REQ-SUBNET-2]
    rig_wait(
        "pairing: A pinned via the daemon-hosted responder",
        rig.wait,
        || TrustStore::load().is_trusted(&rig.subnet, &rig.a_hex()),
    );
    println!("TWOHOST OK: pairing (responder, daemon-hosted)");

    // ── Production loops: dispatcher (serves A's pulls/feeds) + pump (pushes
    // B's registry/notif/mind, bootstrap-pulls A's). ──
    let (registry, stop) = start_loops(
        &broker_name,
        &rig.b_hex(),
        rig.a_hex(),
        rig.peer_broker_addr("b"),
        scratch.path(),
    );


pairhost.rs: drop trust write

propagate.rs serve gate trust->roster

peerloop.rs THE WIDEN

CLI cli.rs repoint to roster

xtask + misc comment refs

Migrate daemon tests trust->roster

mesh.rs: un-ignore + regression assert


825	    }
826	    let subnets_now = SubnetStore::load();
827	    assert_eq!(
828	        subnets_now.find(&rig.subnet).and_then(|r| r.seed_bytes()),
829	        Some(*rig.totp.as_bytes()),
830	        "the ceremony transferred the subnet seed to the joiner (REQ-PAIR-5)"
831	    );
832	    assert!(
833	        TrustStore::load().is_trusted(&rig.subnet, &rig.b_hex()),
834	        "the ceremony pinned B in A's canonical trust store"
835	    );
836
837	    // ── A's local seeds: perch (registry-advertised), mind (B pulls it),
838	    // the marker notif (fires on B), the trusted release-keys file. ──
839	    seed_common_stores(&rig);
840	    let _perch_a = seed_perch(ID_A);

    }
    let subnets_now = SubnetStore::load();
    assert_eq!(
        subnets_now.find(&rig.subnet).and_then(|r| r.seed_bytes()),
        Some(*rig.totp.as_bytes()),
        "the ceremony transferred the subnet seed to the joiner (REQ-PAIR-5)"
    );
    assert!(
        TrustStore::load().is_trusted(&rig.subnet, &rig.b_hex()),
        "the ceremony pinned B in A's canonical trust store"
    );

    // ── A's local seeds: perch (registry-advertised), mind (B pulls it),
    // the marker notif (fires on B), the trusted release-keys file. ──
    seed_common_stores(&rig);
    let _perch_a = seed_perch(ID_A);


Bash

Check twohost roster/trust refs


85:use spt_store::roster::RosterStore;
577:        || TrustStore::load().is_trusted(&rig.subnet, &rig.a_hex()),
833:        TrustStore::load().is_trusted(&rig.subnet, &rig.b_hex()),


Edit

        "pairing: A pinned via the daemon-hosted responder",
        rig.wait,
        || TrustStore::load().is_trusted(&rig.subnet, &rig.a_hex()),
    );

        "pairing: A rostered via the daemon-hosted responder",
        rig.wait,
        || RosterStore::load().is_member(&rig.subnet, &rig.a_hex()),
    );


        "pairing: A pinned via the daemon-hosted responder",
        rig.wait,
        || TrustStore::load().is_trusted(&rig.subnet, &rig.a_hex()),
    );

        "pairing: A rostered via the daemon-hosted responder",
        rig.wait,
        || RosterStore::load().is_member(&rig.subnet, &rig.a_hex()),
    );

-        "pairing: A pinned via the daemon-hosted responder",

+        "pairing: A rostered via the daemon-hosted responder",

-        || TrustStore::load().is_trusted(&rig.subnet, &rig.a_hex()),

+        || RosterStore::load().is_member(&rig.subnet, &rig.a_hex()),

println!("TWOHOST OK: pairing (responder, daemon-hosted)");


    assert!(
        TrustStore::load().is_trusted(&rig.subnet, &rig.b_hex()),
        "the ceremony pinned B in A's canonical trust store"
    );

    assert!(
        RosterStore::load().is_member(&rig.subnet, &rig.b_hex()),
        "the ceremony recorded B in A's canonical roster"
    );


    assert!(
        TrustStore::load().is_trusted(&rig.subnet, &rig.b_hex()),
        "the ceremony pinned B in A's canonical trust store"
    );

    assert!(
        RosterStore::load().is_member(&rig.subnet, &rig.b_hex()),
        "the ceremony recorded B in A's canonical roster"
    );

"the ceremony transferred the subnet seed to the joiner (REQ-PAIR-5)"

-        TrustStore::load().is_trusted(&rig.subnet, &rig.b_hex()),

-        "the ceremony pinned B in A's canonical trust store"

+        RosterStore::load().is_member(&rig.subnet, &rig.b_hex()),

+        "the ceremony recorded B in A's canonical roster"

// ── A's local seeds: perch (registry-advertised), mind (B pulls it),


propagate.rs test next.


175	        current_version,
176	        now_ms: 1_000,
177	    }
178	}
179
180	/// A trust store that pins `peers` (in subnet "home") — the serve gate's input.
181	fn trust_for(peers: &[&str]) -> TrustStore {
182	    let mut t = TrustStore::default();
183	    for p in peers {
184	        t.record("home", p, None, None);
185	    }
186	    t
187	}
188
189	/// One node of the propagation chain: a live broker + its release cache.
190	struct Node {
191	    name: String,
192	    cache: ReleaseCache,
193	}
194
195	impl Node {
196	    fn new(dir: &std::path::Path, tag: &str) -> Node {
197	        let name = unique_name();
198	        let _broker = net_broker(&name, &dir.join(tag));
199	        Node {
200	            name,
201	            cache: ReleaseCache::open(&dir.join(tag).join("releases")),
202	        }
203	    }
204
205	    fn node_id(&self) -> String {
206	        connect_retry(&self.name)
207	            .net_status()
208	            .unwrap()
209	            .node_id_hex
210	            .unwrap()
211	    }
212	}
213
214	/// Run one pull: `requester` ← `responder`, with the responder serving under
215	/// `trust`. Returns `(pull outcome, serve outcome)`.
216	fn pull(
217	    requester: &Node,
218	    responder: &Node,
219	    trust: &TrustStore,
220	    pol: &VerifyPolicy,
221	    scratch: &std::path::Path,
222	) -> (std::io::Result<UpdatePullOutcome>, UpdateServeOutcome) {
223	    let mut serve_brain = connect_retry(&responder.name);
224	    let addr = serve_brain.net_status().expect("status").addr;
225	    // Snapshot BEFORE the requester opens — the table holds locally-opened
226	    // streams too; grabbing a stale row would hang the join forever.
227	    let skip: Vec<u64> = serve_brain
228	        .net_streams()
229	        .expect("stream snapshot")
230	        .streams
231	        .iter()
232	        .map(|s| s.stream_id)
233	        .collect();
234

        current_version,
        now_ms: 1_000,
    }
}

/// A trust store that pins `peers` (in subnet "home") — the serve gate's input.
fn trust_for(peers: &[&str]) -> TrustStore {
    let mut t = TrustStore::default();
    for p in peers {
        t.record("home", p, None, None);
    }
    t
}

/// One node of the propagation chain: a live broker + its release cache.
struct Node {
    name: String,
    cache: ReleaseCache,
}

impl Node {
    fn new(dir: &std::path::Path, tag: &str) -> Node {
        let name = unique_name();
        let _broker = net_broker(&name, &dir.join(tag));
        Node {
            name,
            cache: ReleaseCache::open(&dir.join(tag).join("releases")),
        }
    }

    fn node_id(&self) -> String {
        connect_retry(&self.name)
            .net_status()
            .unwrap()
            .node_id_hex
            .unwrap()
    }
}

/// Run one pull: `requester` ← `responder`, with the responder serving under
/// `trust`. Returns `(pull outcome, serve outcome)`.
fn pull(
    requester: &Node,
    responder: &Node,
    trust: &TrustStore,
    pol: &VerifyPolicy,
    scratch: &std::path::Path,
) -> (std::io::Result<UpdatePullOutcome>, UpdateServeOutcome) {
    let mut serve_brain = connect_retry(&responder.name);
    let addr = serve_brain.net_status().expect("status").addr;
    // Snapshot BEFORE the requester opens — the table holds locally-opened
    // streams too; grabbing a stale row would hang the join forever.
    let skip: Vec<u64> = serve_brain
        .net_streams()
        .expect("stream snapshot")
        .streams
        .iter()
        .map(|s| s.stream_id)
        .collect();


234
235	    let mut req_brain = connect_retry(&requester.name);
236	    let conn = req_brain.net_dial(addr, Some(op())).expect("dial");
237	    let open_op = op();
238	    let running = BrokerAbi::current();
239	    let req_cache = requester.cache.clone();
240	    let req_pol = pol.clone();
241	    let req_scratch = scratch.join("req");
242	    let puller = thread::spawn(move || {
243	        request_update(
244	            &mut req_brain,
245	            conn.conn_id,
246	            open_op,
247	            &running,
248	            &req_pol,
249	            &req_cache,
250	            &req_scratch,
251	        )
252	    });
253
254	    let (stream, origin) = wait_for_stream_except(&mut serve_brain, &skip);
255	    let outcome = serve_update(
256	        &mut serve_brain,
257	        stream,
258	        &origin,
259	        0,
260	        &responder.cache,
261	        trust,
262	    )
263	    .expect("serve");
264	    (puller.join().expect("pull thread"), outcome)
265	}
266
267	// [unit->REQ-UPD-1] the self-heal chain: A holds the verified v6 release; B
268	// (running v5) pulls it through the gate and stages it; C then pulls FROM B —
269	// verified-pull ⇒ staged ⇒ servable, the gate running at every hop. Both
270	// pulls classify brain-only and land byte-identical artifacts.
271	#[test]
272	fn chain_self_heal_propagates_with_gate_at_every_hop() {
273	    let dir = tempfile::tempdir().expect("tempdir");
274	    let a = Node::new(dir.path(), "a");
275	    let b = Node::new(dir.path(), "b");
276	    let c = Node::new(dir.path(), "c");
277	    let scratch = dir.path().join("scratch");
278
279	    let artifact = vec![0xA5u8; 150 * 1024]; // >2 chunks
280	    a.cache
281	        .stage(&signed_release(6, &artifact), &artifact)
282	        .unwrap();
283
284	    // B ← A.
285	    let trust_a = trust_for(&[&b.node_id()]);
286	    let (got, served) = pull(&b, &a, &trust_a, &policy(5), &scratch);
287	    match got.expect("pull b<-a") {
288	        UpdatePullOutcome::Updated(plan) => assert_eq!(plan.class, UpdateClass::BrainOnly),
289	        other => panic!("expected Updated at B, got {other:?}"),
290	    }
291	    assert_eq!(
292	        served,
293	        UpdateServeOutcome::Served {

    let mut req_brain = connect_retry(&requester.name);
    let conn = req_brain.net_dial(addr, Some(op())).expect("dial");
    let open_op = op();
    let running = BrokerAbi::current();
    let req_cache = requester.cache.clone();
    let req_pol = pol.clone();
    let req_scratch = scratch.join("req");
    let puller = thread::spawn(move || {
        request_update(
            &mut req_brain,
            conn.conn_id,
            open_op,
            &running,
            &req_pol,
            &req_cache,
            &req_scratch,
        )
    });

    let (stream, origin) = wait_for_stream_except(&mut serve_brain, &skip);
    let outcome = serve_update(
        &mut serve_brain,
        stream,
        &origin,
        0,
        &responder.cache,
        trust,
    )
    .expect("serve");
    (puller.join().expect("pull thread"), outcome)
}

// [unit->REQ-UPD-1] the self-heal chain: A holds the verified v6 release; B
// (running v5) pulls it through the gate and stages it; C then pulls FROM B —
// verified-pull ⇒ staged ⇒ servable, the gate running at every hop. Both
// pulls classify brain-only and land byte-identical artifacts.
#[test]
fn chain_self_heal_propagates_with_gate_at_every_hop() {
    let dir = tempfile::tempdir().expect("tempdir");
    let a = Node::new(dir.path(), "a");
    let b = Node::new(dir.path(), "b");
    let c = Node::new(dir.path(), "c");
    let scratch = dir.path().join("scratch");

    let artifact = vec![0xA5u8; 150 * 1024]; // >2 chunks
    a.cache
        .stage(&signed_release(6, &artifact), &artifact)
        .unwrap();

    // B ← A.
    let trust_a = trust_for(&[&b.node_id()]);
    let (got, served) = pull(&b, &a, &trust_a, &policy(5), &scratch);
    match got.expect("pull b<-a") {
        UpdatePullOutcome::Updated(plan) => assert_eq!(plan.class, UpdateClass::BrainOnly),
        other => panic!("expected Updated at B, got {other:?}"),
    }
    assert_eq!(
        served,
        UpdateServeOutcome::Served {


Bash

grep -n "trust_for\|trust_a\|trust:\|&trust\|, trust\|TrustStore\|untrusted\|trust_none\|fn pull\|serve gate\|is a trusted\|trust\b" "C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/tests/propagate.rs"

All trust refs in propagate test


5://! untrusted origin) is rejected loud.
33:use spt_store::trust::TrustStore;
180:/// A trust store that pins `peers` (in subnet "home") — the serve gate's input.
181:fn trust_for(peers: &[&str]) -> TrustStore {
182:    let mut t = TrustStore::default();
215:/// `trust`. Returns `(pull outcome, serve outcome)`.
216:fn pull(
219:    trust: &TrustStore,
261:        trust,
285:    let trust_a = trust_for(&[&b.node_id()]);
286:    let (got, served) = pull(&b, &a, &trust_a, &policy(5), &scratch);
304:    let trust_b = trust_for(&[&c.node_id()]);
305:    let (got, served) = pull(&c, &b, &trust_b, &policy(5), &scratch);
323:    let (got, served) = pull(&c, &b, &trust_b, &policy(6), &scratch);
357:    let trust_a = trust_for(&[&b.node_id()]);
358:    let (got, served) = pull(&b, &a, &trust_a, &policy(5), &scratch);
380:    let trust_b = trust_for(&[&c.node_id()]);
381:    let (got, served) = pull(&c, &b, &trust_b, &policy(5), &scratch);
418:    let trust_b = trust_for(&[&c.node_id()]);
419:    let (got, served) = pull(&c, &b, &trust_b, &policy(5), &scratch);
525:/// Run one status-only exchange: `requester` asks `responder` under `trust`.
530:    trust: &TrustStore,
553:        trust,
584:    let trust = trust_for(&[&coord.node_id()]);
587:    let (report, served) = status_pull(&coord, &lab, &trust);
614:    let (report, _) = status_pull(&coord, &lab, &trust);
625:    let (report, _) = status_pull(&coord, &lab, &trust);
639:    let (report, _) = status_pull(&coord, &reject, &trust);
654:    let (report, _) = status_pull(&coord, &lab, &trust);
663:    // unreachable, and the serve gate observed the refusal.
664:    let stranger_trust = trust_for(&["feedfacecafebeef"]);
665:    let (report, served) = status_pull(&coord, &lab, &stranger_trust);
676:// trust store gets the up-to-date shape (no offer, fail-closed) even though a
681:fn untrusted_origin_gets_no_offer() {
693:    let trust = trust_for(&["feedfacecafebeef"]);
694:    let (got, served) = pull(&b, &a, &trust, &policy(5), &scratch);
702:        "the serve gate observed the refusal"


python - <<'PY'
p=r'C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\tests\propagate.rs'
s=open(p,encoding='utf-8').read();b=s
s=s.replace('use spt_store::trust::TrustStore;','use spt_store::roster::{RosterEntry, RosterStore};')
s=s.replace('''/// A trust store that pins `peers` (in subnet "home") — the serve gate's input.
fn trust_for(peers: &[&str]) -> TrustStore {
    let mut t = TrustStore::default();
    for p in peers {
        t.record("home", p, None, None);
    }
    t
}''','''/// A roster that lists `peers` as members (in subnet "home") — the serve gate's input.
fn roster_for(peers: &[&str]) -> RosterStore {
    let mut r = RosterStore::default();
    for p in peers {
        r.merge_entry(RosterEntry {
            pubkey_hex: (*p).to_string(),
            subnet: "home".to_string(),
            label: String::new(),
            machine_id: String::new(),
            address: None,
            last_seen: String::new(),
            lease_epoch: 1,
        });
    }
    r
}''')
s=s.replace('    trust: &TrustStore,','    roster: &RosterStore,')
s=s.replace('        &responder.cache,\n        trust,\n    )','        &responder.cache,\n        roster,\n    )')
# status_pull serve_update arg (different preceding line) — replace remaining lone "        trust,"
s=s.replace('\n        trust,\n','\n        roster,\n')
s=s.replace('trust_for(','roster_for(')
open(p,'w',encoding='utf-8').write(s)
import re
print('TrustStore left:', s.count('TrustStore'))
print('trust_for left:', s.count('trust_for'))
print('changed' if s!=b else 'NOCHANGE')
PY

Migrate propagate test to roster


TrustStore left: 0
trust_for left: 0
changed


dispatch.rs test:


110	    });
111	    stop
112	}
113
114	/// Seed B's canonical gate stores: a subnet, A's pubkey trusted in it, and
115	/// (optionally) endpoint ids synced there. The dispatcher's serve arms load
116	/// these from SPT_HOME — the production read path.
117	fn seed_gates(subnet: &str, peer_hex: &str, synced_ids: &[&str]) {
118	    let mut subnets = SubnetStore::load();
119	    if !subnets.subnets.iter().any(|s| s.name == subnet) {
120	        subnets.create_subnet(subnet).expect("create subnet");
121	        subnets.save().expect("save subnets");
122	    }
123	    let mut trust = TrustStore::load();
124	    trust.record(subnet, peer_hex, None, None);
125	    trust.save().expect("save trust");
126	    // Mesh-D5: the inbound gates authorize on roster membership now — seed the
127	    // canonical roster to mirror the trust pin.
128	    let mut roster = spt_store::roster::RosterStore::load();
129	    roster.merge_entry(spt_store::roster::RosterEntry {
130	        pubkey_hex: peer_hex.to_string(),
131	        subnet: subnet.to_string(),
132	        label: String::new(),
133	        machine_id: String::new(),
134	        address: None,
135	        last_seen: String::new(),
136	        lease_epoch: 1,
137	    });
138	    roster.save().expect("save roster");
139	    let mut vis = VisibilityStore::load();
140	    for id in synced_ids {
141	        vis.set_sync_subnets(id, vec![subnet.to_string()]);
142	    }
143	    vis.save().expect("save visibility");
144	}
145
146	fn node_hex(brain: &mut Brain) -> String {
147	    brain
148	        .net_status()
149	        .expect("status")

    });
    stop
}

/// Seed B's canonical gate stores: a subnet, A's pubkey trusted in it, and
/// (optionally) endpoint ids synced there. The dispatcher's serve arms load
/// these from SPT_HOME — the production read path.
fn seed_gates(subnet: &str, peer_hex: &str, synced_ids: &[&str]) {
    let mut subnets = SubnetStore::load();
    if !subnets.subnets.iter().any(|s| s.name == subnet) {
        subnets.create_subnet(subnet).expect("create subnet");
        subnets.save().expect("save subnets");
    }
    let mut trust = TrustStore::load();
    trust.record(subnet, peer_hex, None, None);
    trust.save().expect("save trust");
    // Mesh-D5: the inbound gates authorize on roster membership now — seed the
    // canonical roster to mirror the trust pin.
    let mut roster = spt_store::roster::RosterStore::load();
    roster.merge_entry(spt_store::roster::RosterEntry {
        pubkey_hex: peer_hex.to_string(),
        subnet: subnet.to_string(),
        label: String::new(),
        machine_id: String::new(),
        address: None,
        last_seen: String::new(),
        lease_epoch: 1,
    });
    roster.save().expect("save roster");
    let mut vis = VisibilityStore::load();
    for id in synced_ids {
        vis.set_sync_subnets(id, vec![subnet.to_string()]);
    }
    vis.save().expect("save visibility");
}

fn node_hex(brain: &mut Brain) -> String {
    brain
        .net_status()
        .expect("status")


Edit

    let mut trust = TrustStore::load();
    trust.record(subnet, peer_hex, None, None);
    trust.save().expect("save trust");
    // Mesh-D5: the inbound gates authorize on roster membership now — seed the
    // canonical roster to mirror the trust pin.
    let mut roster = spt_store::roster::RosterStore::load();

    // Mesh-D6: membership is the roster — seed the canonical roster (the inbound
    // gates and the update serve gate both read it).
    let mut roster = spt_store::roster::RosterStore::load();


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\tests\dispatch.rs has been updated successfully. (file state is current in your context — no need to Read it back)

    let mut trust = TrustStore::load();
    trust.record(subnet, peer_hex, None, None);
    trust.save().expect("save trust");
    // Mesh-D5: the inbound gates authorize on roster membership now — seed the
    // canonical roster to mirror the trust pin.
    let mut roster = spt_store::roster::RosterStore::load();

    // Mesh-D6: membership is the roster — seed the canonical roster (the inbound
    // gates and the update serve gate both read it).
    let mut roster = spt_store::roster::RosterStore::load();

-    let mut trust = TrustStore::load();

-    trust.record(subnet, peer_hex, None, None);

-    // Mesh-D5: the inbound gates authorize on roster membership now — seed the

-    // canonical roster to mirror the trust pin.

+    // Mesh-D6: membership is the roster — seed the canonical roster (the inbound

+    // gates and the update serve gate both read it).


33	use spt_store::notif::{NotifRow, NotifStore};
34	use spt_store::perch::{self, ParentHint};
35	use spt_store::spool;
36	use spt_store::subnet::SubnetStore;
37	use spt_store::trust::TrustStore;
38	use spt_store::visibility::VisibilityStore;

use spt_store::notif::{NotifRow, NotifStore};
use spt_store::perch::{self, ParentHint};
use spt_store::spool;
use spt_store::subnet::SubnetStore;
use spt_store::trust::TrustStore;
use spt_store::visibility::VisibilityStore;


use spt_store::subnet::SubnetStore;
use spt_store::trust::TrustStore;
use spt_store::visibility::VisibilityStore;

use spt_store::subnet::SubnetStore;
use spt_store::visibility::VisibilityStore;


use spt_store::subnet::SubnetStore;
use spt_store::trust::TrustStore;
use spt_store::visibility::VisibilityStore;

use spt_store::subnet::SubnetStore;
use spt_store::visibility::VisibilityStore;


205	}
206
207	// [int->REQ-UPD-1] an update pull is served end to end by the dispatcher:
208	// B stages a release, A queries/fetches/verifies/stages — no hand-wired
209	// serve_update. The trust gate ran from B's canonical TrustStore.
210	#[test]
211	fn dispatcher_serves_an_update_pull_undriven() {
212	    init_home();
213	    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());
214	    let dir = TempDir::new().unwrap();
215	    let (a_name, b_name) = (unique_name(), unique_name());
216	    let _a = net_broker(&a_name, dir.path());

}

// [int->REQ-UPD-1] an update pull is served end to end by the dispatcher:
// B stages a release, A queries/fetches/verifies/stages — no hand-wired
// serve_update. The trust gate ran from B's canonical TrustStore.
#[test]
fn dispatcher_serves_an_update_pull_undriven() {
    init_home();
    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());
    let dir = TempDir::new().unwrap();
    let (a_name, b_name) = (unique_name(), unique_name());
    let _a = net_broker(&a_name, dir.path());


// B stages a release, A queries/fetches/verifies/stages — no hand-wired
// serve_update. The trust gate ran from B's canonical TrustStore.

// B stages a release, A queries/fetches/verifies/stages — no hand-wired
// serve_update. The serve gate ran from B's canonical roster.


// B stages a release, A queries/fetches/verifies/stages — no hand-wired
// serve_update. The trust gate ran from B's canonical TrustStore.

// B stages a release, A queries/fetches/verifies/stages — no hand-wired
// serve_update. The serve gate ran from B's canonical roster.

// [int->REQ-UPD-1] an update pull is served end to end by the dispatcher:

-// serve_update. The trust gate ran from B's canonical TrustStore.

+// serve_update. The serve gate ran from B's canonical roster.


Now the big one — mesh.rs: un-ignore, roster-seed, regression assert. Update module doc + imports + node setup.


//! Mesh-D0 — the staggered + star subnet-mesh repro, RED.
//!
//! These two tests assert the *desired* full-mesh convergence the subnet does
//! NOT yet have (`SUBNET-MESH-PLAN.md`, ADR-0017). They are `#[ignore]`d: the
//! acceptance that flips green at **Mesh-D6** when the registry push target
//! widens from directly-paired peers to the full roster. No production change
//! lands with this file — it locks the bug as an executable repro. See
//! `MESH-D0-PLAN.md` for the fidelity decision.
//!
//! The bug, in one seam: `peerloop.rs` pushes a node's own rows to
//! `trust.peers_in(subnet)` — *directly-paired* peers only, no relay. So when
//! `C` joins `B` (C pins B, not A) and `A` joins `B`, A and C never pin each
//! other; C's pump never targets A and A's never targets C. They stay mutually
//! invisible even when both are online. The mesh restores reach by widening
//! that push target to every roster member (Mesh-D6).
//!
//! Fidelity: the **push side is the real loop** (`run_peer_pump` with explicit
//! per-node `PumpPaths` whose `trust` is the pairwise pin) — so the D6
//! target-widening flips these green untouched. The **gate side runs the real
//! dispatch loop** (`run_dispatch_loop`) under a permissive canonical
//! `SPT_HOME` trust, isolating the red strictly to the push-target seam (the
//! "subnet = pairing graph, not mesh" bug); the gate swap is Mesh-D5's own
//! unit. Addresses are omniscient (a node still only *pushes* where its
//! push-target says, so this does not mask the bug); address *discovery* is the
//! roster's job, tested at D3/D4.
//!
//! Refinement note: once the roster store exists (D3/D4) this harness seeds
//! rosters the way it seeds trust today; the `#[ignore]` lifts at D6 and the
//! file becomes the REQ-MESH-3 `int` evidence (tagged then, not now — D0
//! activates no REQ).

//! Mesh-D6 — the staggered + star subnet-mesh acceptance, GREEN.
//!
//! These two tests assert full-mesh convergence (`SUBNET-MESH-PLAN.md`,
//! ADR-0017). Authored RED at Mesh-D0 (the push target was `trust.peers_in` —
//! directly-paired peers only, so A and C never reached each other); they flip
//! green at **Mesh-D6**, where the registry push target widens from the
//! directly-paired set to the full roster (`peerloop.rs` push =
//! `roster.members_in`). This is the REQ-MESH-3 `int` evidence.
//!
//! The mesh, in one seam: each node's roster holds ALL members (the converged
//! D3/D4 state — seeded here, since this harness disables seed-proof so the
//! on-connect roster exchange does not run). The widened push makes a node dial
//! and push its OWN rows to every roster member directly — a wider DIRECT
//! fan-out, never a relay. So when `A` and `C` are both online (and `B`, the
//! only node they each paired with, is offline), A reaches C and C reaches A
//! without B forwarding anything.
//!
//! Fidelity: the **push side is the real loop** (`run_peer_pump` with explicit
//! per-node `PumpPaths`, the roster as the push target) and the **gate side
//! runs the real dispatch loop** (`run_dispatch_loop`) under a permissive
//! canonical `SPT_HOME` roster. Addresses are omniscient (a node still only
//! *pushes* where its roster says); address *discovery* is the roster's job,
//! tested at D3/D4.
//!
//! Regression (REQ-MESH-3): rows stay own-authored — every registry row a node
//! holds is authored by the node that owns that perch (origin == author); B
//! never relays a third party's row.


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\tests\mesh.rs has been updated successfully. (file state is current in your context — no need to Read it back)

//! Mesh-D0 — the staggered + star subnet-mesh repro, RED.
//!
//! These two tests assert the *desired* full-mesh convergence the subnet does
//! NOT yet have (`SUBNET-MESH-PLAN.md`, ADR-0017). They are `#[ignore]`d: the
//! acceptance that flips green at **Mesh-D6** when the registry push target
//! widens from directly-paired peers to the full roster. No production change
//! lands with this file — it locks the bug as an executable repro. See
//! `MESH-D0-PLAN.md` for the fidelity decision.
//!
//! The bug, in one seam: `peerloop.rs` pushes a node's own rows to
//! `trust.peers_in(subnet)` — *directly-paired* peers only, no relay. So when
//! `C` joins `B` (C pins B, not A) and `A` joins `B`, A and C never pin each
//! other; C's pump never targets A and A's never targets C. They stay mutually
//! invisible even when both are online. The mesh restores reach by widening
//! that push target to every roster member (Mesh-D6).
//!
//! Fidelity: the **push side is the real loop** (`run_peer_pump` with explicit
//! per-node `PumpPaths` whose `trust` is the pairwise pin) — so the D6
//! target-widening flips these green untouched. The **gate side runs the real
//! dispatch loop** (`run_dispatch_loop`) under a permissive canonical
//! `SPT_HOME` trust, isolating the red strictly to the push-target seam (the
//! "subnet = pairing graph, not mesh" bug); the gate swap is Mesh-D5's own
//! unit. Addresses are omniscient (a node still only *pushes* where its
//! push-target says, so this does not mask the bug); address *discovery* is the
//! roster's job, tested at D3/D4.
//!
//! Refinement note: once the roster store exists (D3/D4) this harness seeds
//! rosters the way it seeds trust today; the `#[ignore]` lifts at D6 and the
//! file becomes the REQ-MESH-3 `int` evidence (tagged then, not now — D0
//! activates no REQ).

//! Mesh-D6 — the staggered + star subnet-mesh acceptance, GREEN.
//!
//! These two tests assert full-mesh convergence (`SUBNET-MESH-PLAN.md`,
//! ADR-0017). Authored RED at Mesh-D0 (the push target was `trust.peers_in` —
//! directly-paired peers only, so A and C never reached each other); they flip
//! green at **Mesh-D6**, where the registry push target widens from the
//! directly-paired set to the full roster (`peerloop.rs` push =
//! `roster.members_in`). This is the REQ-MESH-3 `int` evidence.
//!
//! The mesh, in one seam: each node's roster holds ALL members (the converged
//! D3/D4 state — seeded here, since this harness disables seed-proof so the
//! on-connect roster exchange does not run). The widened push makes a node dial
//! and push its OWN rows to every roster member directly — a wider DIRECT
//! fan-out, never a relay. So when `A` and `C` are both online (and `B`, the
//! only node they each paired with, is offline), A reaches C and C reaches A
//! without B forwarding anything.
//!
//! Fidelity: the **push side is the real loop** (`run_peer_pump` with explicit
//! per-node `PumpPaths`, the roster as the push target) and the **gate side
//! runs the real dispatch loop** (`run_dispatch_loop`) under a permissive
//! canonical `SPT_HOME` roster. Addresses are omniscient (a node still only
//! *pushes* where its roster says); address *discovery* is the roster's job,
//! tested at D3/D4.
//!
//! Regression (REQ-MESH-3): rows stay own-authored — every registry row a node
//! holds is authored by the node that owns that perch (origin == author); B
//! never relays a third party's row.

-//! Mesh-D0 — the staggered + star subnet-mesh repro, RED.

+//! Mesh-D6 — the staggered + star subnet-mesh acceptance, GREEN.

-//! These two tests assert the *desired* full-mesh convergence the subnet does

-//! NOT yet have (`SUBNET-MESH-PLAN.md`, ADR-0017). They are `#[ignore]`d: the

-//! acceptance that flips green at **Mesh-D6** when the registry push target

-//! widens from directly-paired peers to the full roster. No production change

-//! lands with this file — it locks the bug as an executable repro. See

-//! `MESH-D0-PLAN.md` for the fidelity decision.

+//! These two tests assert full-mesh convergence (`SUBNET-MESH-PLAN.md`,

+//! ADR-0017). Authored RED at Mesh-D0 (the push target was `trust.peers_in` —

+//! directly-paired peers only, so A and C never reached each other); they flip

+//! green at **Mesh-D6**, where the registry push target widens from the

+//! directly-paired set to the full roster (`peerloop.rs` push =

+//! `roster.members_in`). This is the REQ-MESH-3 `int` evidence.

-//! The bug, in one seam: `peerloop.rs` pushes a node's own rows to

-//! `trust.peers_in(subnet)` — *directly-paired* peers only, no relay. So when

-//! `C` joins `B` (C pins B, not A) and `A` joins `B`, A and C never pin each

-//! other; C's pump never targets A and A's never targets C. They stay mutually

-//! invisible even when both are online. The mesh restores reach by widening

-//! that push target to every roster member (Mesh-D6).

+//! The mesh, in one seam: each node's roster holds ALL members (the converged

+//! D3/D4 state — seeded here, since this harness disables seed-proof so the

+//! on-connect roster exchange does not run). The widened push makes a node dial

+//! and push its OWN rows to every roster member directly — a wider DIRECT

+//! fan-out, never a relay. So when `A` and `C` are both online (and `B`, the

+//! only node they each paired with, is offline), A reaches C and C reaches A

-//! per-node `PumpPaths` whose `trust` is the pairwise pin) — so the D6

-//! target-widening flips these green untouched. The **gate side runs the real

-//! dispatch loop** (`run_dispatch_loop`) under a permissive canonical

-//! `SPT_HOME` trust, isolating the red strictly to the push-target seam (the

-//! "subnet = pairing graph, not mesh" bug); the gate swap is Mesh-D5's own

-//! unit. Addresses are omniscient (a node still only *pushes* where its

-//! push-target says, so this does not mask the bug); address *discovery* is the

+//! per-node `PumpPaths`, the roster as the push target) and the **gate side

+//! runs the real dispatch loop** (`run_dispatch_loop`) under a permissive

+//! canonical `SPT_HOME` roster. Addresses are omniscient (a node still only

+//! *pushes* where its roster says); address *discovery* is the roster's job,

-//! Refinement note: once the roster store exists (D3/D4) this harness seeds

-//! rosters the way it seeds trust today; the `#[ignore]` lifts at D6 and the

-//! file becomes the REQ-MESH-3 `int` evidence (tagged then, not now — D0

+//! Regression (REQ-MESH-3): rows stay own-authored — every registry row a node

+//! holds is authored by the node that owns that perch (origin == author); B


use spt_store::subnet::SubnetStore;
use spt_store::trust::TrustStore;
use tempfile::TempDir;

use spt_store::roster::{RosterEntry, RosterStore};
use spt_store::subnet::SubnetStore;
use tempfile::TempDir;


use spt_store::subnet::SubnetStore;
use spt_store::trust::TrustStore;
use tempfile::TempDir;

use spt_store::roster::{RosterEntry, RosterStore};
use spt_store::subnet::SubnetStore;
use tempfile::TempDir;


pairhost.rs: drop trust write

propagate.rs serve gate trust->roster

peerloop.rs THE WIDEN

CLI cli.rs repoint to roster

xtask + misc comment refs

Migrate daemon tests trust->roster

mesh.rs: un-ignore + regression assert


    /// Stand up a node under `root`: broker, a single advertised perch, a
    /// registry, and pump paths whose `trust` pins exactly `paired` (the
    /// pairwise reality — NOT a full mesh).
    fn new(root: &std::path::Path, perch_id: &str, paired: &[&str], dir: &std::path::Path) -> Self {

    /// Stand up a node under `root`: broker, a single advertised perch, a
    /// registry, and pump paths whose roster lists exactly `members` (the push
    /// target — seed the full mesh membership for the converged-roster state).
    fn new(root: &std::path::Path, perch_id: &str, members: &[&str], dir: &std::path::Path) -> Self {


    /// Stand up a node under `root`: broker, a single advertised perch, a
    /// registry, and pump paths whose `trust` pins exactly `paired` (the
    /// pairwise reality — NOT a full mesh).
    fn new(root: &std::path::Path, perch_id: &str, paired: &[&str], dir: &std::path::Path) -> Self {

    /// Stand up a node under `root`: broker, a single advertised perch, a
    /// registry, and pump paths whose roster lists exactly `members` (the push
    /// target — seed the full mesh membership for the converged-roster state).
    fn new(root: &std::path::Path, perch_id: &str, members: &[&str], dir: &std::path::Path) -> Self {

-    /// registry, and pump paths whose `trust` pins exactly `paired` (the

-    /// pairwise reality — NOT a full mesh).

-    fn new(root: &std::path::Path, perch_id: &str, paired: &[&str], dir: &std::path::Path) -> Self {

+    /// registry, and pump paths whose roster lists exactly `members` (the push

+    /// target — seed the full mesh membership for the converged-roster state).

+    fn new(root: &std::path::Path, perch_id: &str, members: &[&str], dir: &std::path::Path) -> Self {

let _broker = net_broker(&name, &root.join("broker"));


            subnets: root.join("subnet.json"),
            trust: root.join("trust.json"),
            vis: root.join("visibility.json"),

            subnets: root.join("subnet.json"),
            vis: root.join("visibility.json"),


            subnets: root.join("subnet.json"),
            trust: root.join("trust.json"),
            vis: root.join("visibility.json"),

            subnets: root.join("subnet.json"),
            vis: root.join("visibility.json"),

-            trust: root.join("trust.json"),


        let mut subnets = SubnetStore::default();
        subnets.create_subnet(SUBNET).expect("node subnet");
        subnets.save_to(&paths.subnets).expect("save node subnet");
        let mut trust = TrustStore::default();
        for p in paired {
            trust.record(SUBNET, p, None, None);
        }
        trust.save_to(&paths.trust).expect("save node trust");

        let mut subnets = SubnetStore::default();
        subnets.create_subnet(SUBNET).expect("node subnet");
        subnets.save_to(&paths.subnets).expect("save node subnet");
        // The push target = the roster (Mesh-D6). Sibling of subnet.json so the
        // pump derives it (subnet.json → roster.json). Seeded empty here; the
        // caller rerosters with the full membership once hexes are known.
        write_roster(root, members);


        let mut subnets = SubnetStore::default();
        subnets.create_subnet(SUBNET).expect("node subnet");
        subnets.save_to(&paths.subnets).expect("save node subnet");
        let mut trust = TrustStore::default();
        for p in paired {
            trust.record(SUBNET, p, None, None);
        }
        trust.save_to(&paths.trust).expect("save node trust");

        let mut subnets = SubnetStore::default();
        subnets.create_subnet(SUBNET).expect("node subnet");
        subnets.save_to(&paths.subnets).expect("save node subnet");
        // The push target = the roster (Mesh-D6). Sibling of subnet.json so the
        // pump derives it (subnet.json → roster.json). Seeded empty here; the
        // caller rerosters with the full membership once hexes are known.
        write_roster(root, members);

-        let mut trust = TrustStore::default();

-            trust.record(SUBNET, p, None, None);

-        trust.save_to(&paths.trust).expect("save node trust");

+        // The push target = the roster (Mesh-D6). Sibling of subnet.json so the

+        // pump derives it (subnet.json → roster.json). Seeded empty here; the

+        // caller rerosters with the full membership once hexes are known.

let registry = Arc::new(RegistryHost::new_at(


Now `canonical_full_trust`, `clone_paths`, `repin`:


/// Establish the permissive canonical gate trust: every member trusts every
/// other in `SUBNET`, so the dispatch-side `RegistryGatePolicy::load()` admits
/// any member feed. The pairwise pin that reproduces the bug lives in each
/// node's *explicit* `paths.trust` (the push target), not here.
fn canonical_full_trust(hexes: &[&str]) {
    let mut subnets = SubnetStore::load();
    let _ = subnets.create_subnet(SUBNET); // idempotent across tests
    subnets.save().expect("save canonical subnet");
    let mut trust = TrustStore::load();
    for h in hexes {
        trust.record(SUBNET, h, None, None);
    }
    trust.save().expect("save canonical trust");
}

/// Establish the permissive canonical gate roster: every member is a roster
/// member in `SUBNET`, so the dispatch-side `RegistryGatePolicy::load()` (which
/// reads the canonical `roster.json`) admits any member feed. The per-node push
/// target lives in each node's *explicit* `root/roster.json`, not here.
fn canonical_full_roster(hexes: &[&str]) {
    let mut subnets = SubnetStore::load();
    let _ = subnets.create_subnet(SUBNET); // idempotent across tests
    subnets.save().expect("save canonical subnet");
    let mut roster = RosterStore::load();
    for h in hexes {
        roster.merge_entry(roster_entry(h));
    }
    roster.save().expect("save canonical roster");
}

/// A bare roster member entry for `hex` in `SUBNET` (identity only — the mesh
/// dials by pubkey; addresses come from the omniscient resolver here).
fn roster_entry(hex: &str) -> RosterEntry {
    RosterEntry {
        pubkey_hex: hex.to_string(),
        subnet: SUBNET.to_string(),
        label: String::new(),
        machine_id: String::new(),
        address: None,
        last_seen: String::new(),
        lease_epoch: 1,
    }
}

/// Write a node's push-target roster (`root/roster.json`) listing `members`.
fn write_roster(root: &std::path::Path, members: &[&str]) {
    let mut roster = RosterStore::default();
    for m in members {
        roster.merge_entry(roster_entry(m));
    }
    roster
        .save_to(&root.join("roster.json"))
        .expect("save node roster");
}


/// Establish the permissive canonical gate trust: every member trusts every
/// other in `SUBNET`, so the dispatch-side `RegistryGatePolicy::load()` admits
/// any member feed. The pairwise pin that reproduces the bug lives in each
/// node's *explicit* `paths.trust` (the push target), not here.
fn canonical_full_trust(hexes: &[&str]) {
    let mut subnets = SubnetStore::load();
    let _ = subnets.create_subnet(SUBNET); // idempotent across tests
    subnets.save().expect("save canonical subnet");
    let mut trust = TrustStore::load();
    for h in hexes {
        trust.record(SUBNET, h, None, None);
    }
    trust.save().expect("save canonical trust");
}

/// Establish the permissive canonical gate roster: every member is a roster
/// member in `SUBNET`, so the dispatch-side `RegistryGatePolicy::load()` (which
/// reads the canonical `roster.json`) admits any member feed. The per-node push
/// target lives in each node's *explicit* `root/roster.json`, not here.
fn canonical_full_roster(hexes: &[&str]) {
    let mut subnets = SubnetStore::load();
    let _ = subnets.create_subnet(SUBNET); // idempotent across tests
    subnets.save().expect("save canonical subnet");
    let mut roster = RosterStore::load();
    for h in hexes {
        roster.merge_entry(roster_entry(h));
    }
    roster.save().expect("save canonical roster");
}

/// A bare roster member entry for `hex` in `SUBNET` (identity only — the mesh
/// dials by pubkey; addresses come from the omniscient resolver here).
fn roster_entry(hex: &str) -> RosterEntry {
    RosterEntry {
        pubkey_hex: hex.to_string(),
        subnet: SUBNET.to_string(),
        label: String::new(),
        machine_id: String::new(),
        address: None,
        last_seen: String::new(),
        lease_epoch: 1,
    }
}

/// Write a node's push-target roster (`root/roster.json`) listing `members`.
fn write_roster(root: &std::path::Path, members: &[&str]) {
    let mut roster = RosterStore::default();
    for m in members {
        roster.merge_entry(roster_entry(m));
    }
    roster
        .save_to(&root.join("roster.json"))
        .expect("save node roster");
}

-/// Establish the permissive canonical gate trust: every member trusts every

-/// other in `SUBNET`, so the dispatch-side `RegistryGatePolicy::load()` admits

-/// any member feed. The pairwise pin that reproduces the bug lives in each

-/// node's *explicit* `paths.trust` (the push target), not here.

-fn canonical_full_trust(hexes: &[&str]) {

+/// Establish the permissive canonical gate roster: every member is a roster

+/// member in `SUBNET`, so the dispatch-side `RegistryGatePolicy::load()` (which

+/// reads the canonical `roster.json`) admits any member feed. The per-node push

+/// target lives in each node's *explicit* `root/roster.json`, not here.

+fn canonical_full_roster(hexes: &[&str]) {

-    let mut trust = TrustStore::load();

+    let mut roster = RosterStore::load();

-        trust.record(SUBNET, h, None, None);

+        roster.merge_entry(roster_entry(h));

-    trust.save().expect("save canonical trust");

+    roster.save().expect("save canonical roster");

+/// A bare roster member entry for `hex` in `SUBNET` (identity only — the mesh

+/// dials by pubkey; addresses come from the omniscient resolver here).

+fn roster_entry(hex: &str) -> RosterEntry {

+/// Write a node's push-target roster (`root/roster.json`) listing `members`.

+fn write_roster(root: &std::path::Path, members: &[&str]) {

+    let mut roster = RosterStore::default();

+        roster.merge_entry(roster_entry(m));

+        .save_to(&root.join("roster.json"))

/// An omniscient resolver over the whole fleet (hex → loopback addr).

fn resolver_over(addrs: &HashMap<String, serde_json::Value>) -> PeerResolver {


        subnets: p.subnets.clone(),
        trust: p.trust.clone(),
        vis: p.vis.clone(),

        subnets: p.subnets.clone(),
        vis: p.vis.clone(),


        subnets: p.subnets.clone(),
        trust: p.trust.clone(),
        vis: p.vis.clone(),

        subnets: p.subnets.clone(),
        vis: p.vis.clone(),

notif_db: p.notif_db.clone(),
scratch: p.scratch.clone(),
subnets: p.subnets.clone(),
-        trust: p.trust.clone(),
vis: p.vis.clone(),
ops: p.ops.clone(),
releases: p.releases.clone(),


/// Overwrite a node's explicit push-target trust to pin exactly `paired`.
fn repin(node: &MeshNode, paired: &[&str]) {
    let mut trust = TrustStore::default();
    for p in paired {
        trust.record(SUBNET, p, None, None);
    }
    trust.save_to(&node.paths.trust).expect("re-pin trust");
}

/// Overwrite a node's explicit push-target roster to list exactly `members`.
fn reroster(node: &MeshNode, members: &[&str]) {
    let root = node
        .paths
        .subnets
        .parent()
        .expect("subnet.json has a parent");
    write_roster(root, members);
}


/// Overwrite a node's explicit push-target trust to pin exactly `paired`.
fn repin(node: &MeshNode, paired: &[&str]) {
    let mut trust = TrustStore::default();
    for p in paired {
        trust.record(SUBNET, p, None, None);
    }
    trust.save_to(&node.paths.trust).expect("re-pin trust");
}

/// Overwrite a node's explicit push-target roster to list exactly `members`.
fn reroster(node: &MeshNode, members: &[&str]) {
    let root = node
        .paths
        .subnets
        .parent()
        .expect("subnet.json has a parent");
    write_roster(root, members);
}

-/// Overwrite a node's explicit push-target trust to pin exactly `paired`.

-fn repin(node: &MeshNode, paired: &[&str]) {

-    let mut trust = TrustStore::default();

-        trust.record(SUBNET, p, None, None);

-    trust.save_to(&node.paths.trust).expect("re-pin trust");

+/// Overwrite a node's explicit push-target roster to list exactly `members`.

+fn reroster(node: &MeshNode, members: &[&str]) {

+        .expect("subnet.json has a parent");

// Silence unused-field warnings: these are part of the node model the harness


Now un-ignore + reroster both tests and add the no-relay regression. Staggered first:


#[test]
#[ignore = "mesh lands at Mesh-D6 — this is the Mesh-D0 red repro"]
fn staggered_offliner_still_meshes() {
    init_home();
    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());
    let dir = TempDir::new().unwrap();

    // Pairwise pins: A↔B, B↔C. A and C never pin each other.
    let a = MeshNode::new(&dir.path().join("a"), "perchA", &[], dir.path());
    let b = MeshNode::new(&dir.path().join("b"), "perchB", &[], dir.path());
    let c = MeshNode::new(&dir.path().join("c"), "perchC", &[], dir.path());
    // Re-pin now that hexes are known: A pins B; B pins A,C; C pins B.
    repin(&a, &[&b.hex]);
    repin(&b, &[&a.hex, &c.hex]);
    repin(&c, &[&b.hex]);

    canonical_full_trust(&[&a.hex, &b.hex, &c.hex]);

#[test]
fn staggered_offliner_still_meshes() {
    init_home();
    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());
    let dir = TempDir::new().unwrap();

    let a = MeshNode::new(&dir.path().join("a"), "perchA", &[], dir.path());
    let b = MeshNode::new(&dir.path().join("b"), "perchB", &[], dir.path());
    let c = MeshNode::new(&dir.path().join("c"), "perchC", &[], dir.path());
    // The converged mesh roster (Mesh-D6): every node lists ALL members, so the
    // widened push fans out directly to each. A paired only B and C paired only
    // B, but the roster D3/D4 would propagate is seeded full here.
    let all = [a.hex.as_str(), b.hex.as_str(), c.hex.as_str()];
    reroster(&a, &all);
    reroster(&b, &all);
    reroster(&c, &all);

    canonical_full_roster(&[&a.hex, &b.hex, &c.hex]);


#[test]
#[ignore = "mesh lands at Mesh-D6 — this is the Mesh-D0 red repro"]
fn staggered_offliner_still_meshes() {
    init_home();
    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());
    let dir = TempDir::new().unwrap();

    // Pairwise pins: A↔B, B↔C. A and C never pin each other.
    let a = MeshNode::new(&dir.path().join("a"), "perchA", &[], dir.path());
    let b = MeshNode::new(&dir.path().join("b"), "perchB", &[], dir.path());
    let c = MeshNode::new(&dir.path().join("c"), "perchC", &[], dir.path());
    // Re-pin now that hexes are known: A pins B; B pins A,C; C pins B.
    repin(&a, &[&b.hex]);
    repin(&b, &[&a.hex, &c.hex]);
    repin(&c, &[&b.hex]);

    canonical_full_trust(&[&a.hex, &b.hex, &c.hex]);

#[test]
fn staggered_offliner_still_meshes() {
    init_home();
    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());
    let dir = TempDir::new().unwrap();

    let a = MeshNode::new(&dir.path().join("a"), "perchA", &[], dir.path());
    let b = MeshNode::new(&dir.path().join("b"), "perchB", &[], dir.path());
    let c = MeshNode::new(&dir.path().join("c"), "perchC", &[], dir.path());
    // The converged mesh roster (Mesh-D6): every node lists ALL members, so the
    // widened push fans out directly to each. A paired only B and C paired only
    // B, but the roster D3/D4 would propagate is seeded full here.
    let all = [a.hex.as_str(), b.hex.as_str(), c.hex.as_str()];
    reroster(&a, &all);
    reroster(&b, &all);
    reroster(&c, &all);

    canonical_full_roster(&[&a.hex, &b.hex, &c.hex]);

// A↔C (see AND reach) with B absent. RED today: A pins only B, C pins only B,

// so neither ever pushes to the other. Flips green at Mesh-D6.

-#[ignore = "mesh lands at Mesh-D6 — this is the Mesh-D0 red repro"]

-    // Pairwise pins: A↔B, B↔C. A and C never pin each other.

-    // Re-pin now that hexes are known: A pins B; B pins A,C; C pins B.

+    // The converged mesh roster (Mesh-D6): every node lists ALL members, so the

+    // widened push fans out directly to each. A paired only B and C paired only

+    // B, but the roster D3/D4 would propagate is seeded full here.

+    let all = [a.hex.as_str(), b.hex.as_str(), c.hex.as_str()];

-    canonical_full_trust(&[&a.hex, &b.hex, &c.hex]);

+    canonical_full_roster(&[&a.hex, &b.hex, &c.hex]);

let addrs: HashMap<String, serde_json::Value> = [


    let a_stop2 = a.online(resolver_over(&addrs));
    converge("A meshes to C (sees perchC)", || a.sees("perchC", &c.hex));
    converge("C meshes to A (sees perchA)", || c.sees("perchA", &a.hex));

    // Reach: each end holds a dialable address for the other.
    assert!(addrs.contains_key(&c.hex) && addrs.contains_key(&a.hex));

    a_stop2.store(true, Ordering::Relaxed);
    c_stop.store(true, Ordering::Relaxed);
}

    let a_stop2 = a.online(resolver_over(&addrs));
    converge("A meshes to C (sees perchC)", || a.sees("perchC", &c.hex));
    converge("C meshes to A (sees perchA)", || c.sees("perchA", &a.hex));

    // Reach: each end holds a dialable address for the other.
    assert!(addrs.contains_key(&c.hex) && addrs.contains_key(&a.hex));

    // Regression (REQ-MESH-3): no row was relayed — every registry row A and C
    // hold is authored by the node that owns that perch (origin == author).
    assert_no_relay(&a, &[("perchA", &a.hex), ("perchB", &b.hex), ("perchC", &c.hex)]);
    assert_no_relay(&c, &[("perchA", &a.hex), ("perchB", &b.hex), ("perchC", &c.hex)]);

    a_stop2.store(true, Ordering::Relaxed);
    c_stop.store(true, Ordering::Relaxed);
}


    let a_stop2 = a.online(resolver_over(&addrs));
    converge("A meshes to C (sees perchC)", || a.sees("perchC", &c.hex));
    converge("C meshes to A (sees perchA)", || c.sees("perchA", &a.hex));

    // Reach: each end holds a dialable address for the other.
    assert!(addrs.contains_key(&c.hex) && addrs.contains_key(&a.hex));

    a_stop2.store(true, Ordering::Relaxed);
    c_stop.store(true, Ordering::Relaxed);
}

    let a_stop2 = a.online(resolver_over(&addrs));
    converge("A meshes to C (sees perchC)", || a.sees("perchC", &c.hex));
    converge("C meshes to A (sees perchA)", || c.sees("perchA", &a.hex));

    // Reach: each end holds a dialable address for the other.
    assert!(addrs.contains_key(&c.hex) && addrs.contains_key(&a.hex));

    // Regression (REQ-MESH-3): no row was relayed — every registry row A and C
    // hold is authored by the node that owns that perch (origin == author).
    assert_no_relay(&a, &[("perchA", &a.hex), ("perchB", &b.hex), ("perchC", &c.hex)]);
    assert_no_relay(&c, &[("perchA", &a.hex), ("perchB", &b.hex), ("perchC", &c.hex)]);

    a_stop2.store(true, Ordering::Relaxed);
    c_stop.store(true, Ordering::Relaxed);
}

+    // Regression (REQ-MESH-3): no row was relayed — every registry row A and C

+    // hold is authored by the node that owns that perch (origin == author).

+    assert_no_relay(&a, &[("perchA", &a.hex), ("perchB", &b.hex), ("perchC", &c.hex)]);

+    assert_no_relay(&c, &[("perchA", &a.hex), ("perchB", &b.hex), ("perchC", &c.hex)]);


Now the star test + the `assert_no_relay` helper:


#[test]
#[ignore = "mesh lands at Mesh-D6 — this is the Mesh-D0 red repro"]
fn all_online_star_a_reaches_c_b_never_relays() {
    init_home();
    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());
    let dir = TempDir::new().unwrap();

    let a = MeshNode::new(&dir.path().join("a"), "perchA", &[], dir.path());
    let b = MeshNode::new(&dir.path().join("b"), "perchB", &[], dir.path());
    let c = MeshNode::new(&dir.path().join("c"), "perchC", &[], dir.path());
    repin(&a, &[&b.hex]);
    repin(&b, &[&a.hex, &c.hex]);
    repin(&c, &[&b.hex]);

    canonical_full_trust(&[&a.hex, &b.hex, &c.hex]);

#[test]
fn all_online_star_a_reaches_c_b_never_relays() {
    init_home();
    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());
    let dir = TempDir::new().unwrap();

    let a = MeshNode::new(&dir.path().join("a"), "perchA", &[], dir.path());
    let b = MeshNode::new(&dir.path().join("b"), "perchB", &[], dir.path());
    let c = MeshNode::new(&dir.path().join("c"), "perchC", &[], dir.path());
    let all = [a.hex.as_str(), b.hex.as_str(), c.hex.as_str()];
    reroster(&a, &all);
    reroster(&b, &all);
    reroster(&c, &all);

    canonical_full_roster(&[&a.hex, &b.hex, &c.hex]);


#[test]
#[ignore = "mesh lands at Mesh-D6 — this is the Mesh-D0 red repro"]
fn all_online_star_a_reaches_c_b_never_relays() {
    init_home();
    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());
    let dir = TempDir::new().unwrap();

    let a = MeshNode::new(&dir.path().join("a"), "perchA", &[], dir.path());
    let b = MeshNode::new(&dir.path().join("b"), "perchB", &[], dir.path());
    let c = MeshNode::new(&dir.path().join("c"), "perchC", &[], dir.path());
    repin(&a, &[&b.hex]);
    repin(&b, &[&a.hex, &c.hex]);
    repin(&c, &[&b.hex]);

    canonical_full_trust(&[&a.hex, &b.hex, &c.hex]);

#[test]
fn all_online_star_a_reaches_c_b_never_relays() {
    init_home();
    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());
    let dir = TempDir::new().unwrap();

    let a = MeshNode::new(&dir.path().join("a"), "perchA", &[], dir.path());
    let b = MeshNode::new(&dir.path().join("b"), "perchB", &[], dir.path());
    let c = MeshNode::new(&dir.path().join("c"), "perchC", &[], dir.path());
    let all = [a.hex.as_str(), b.hex.as_str(), c.hex.as_str()];
    reroster(&a, &all);
    reroster(&b, &all);
    reroster(&c, &all);

    canonical_full_roster(&[&a.hex, &b.hex, &c.hex]);

// A-row reaching C can only have come straight from A; KH 4.10/7.5 preserved).

// RED today: A pushes only to B, C only to B; B forwards neither.

-#[ignore = "mesh lands at Mesh-D6 — this is the Mesh-D0 red repro"]


    // The mesh: spokes see each other WITHOUT B relaying. Red today.
    converge("A sees C directly", || a.sees("perchC", &c.hex));
    converge("C sees A directly", || c.sees("perchA", &a.hex));

    a_stop.store(true, Ordering::Relaxed);
    b_stop.store(true, Ordering::Relaxed);
    c_stop.store(true, Ordering::Relaxed);
}

    // The mesh: spokes see each other WITHOUT B relaying.
    converge("A sees C directly", || a.sees("perchC", &c.hex));
    converge("C sees A directly", || c.sees("perchA", &a.hex));

    // Regression (REQ-MESH-3): B never relays — every row each node holds is
    // own-authored (the perchC row in A came straight from C, not via B).
    let owners: [(&str, &str); 3] =
        [("perchA", &a.hex), ("perchB", &b.hex), ("perchC", &c.hex)];
    assert_no_relay(&a, &owners);
    assert_no_relay(&b, &owners);
    assert_no_relay(&c, &owners);

    a_stop.store(true, Ordering::Relaxed);
    b_stop.store(true, Ordering::Relaxed);
    c_stop.store(true, Ordering::Relaxed);
}

/// REQ-MESH-3 no-relay regression: every registry row `node` holds for each
/// named perch is authored by that perch's true owner (origin == author) — a
/// relayed third-party row (a perchC row authored by B) would fail this.
fn assert_no_relay(node: &MeshNode, owners: &[(&str, &str)]) {
    for (perch_id, owner) in owners {
        for inst in node.registry.rows(SUBNET, perch_id) {
            assert_eq!(
                &inst.node, owner,
                "{} row in a node must be own-authored ({}), never relayed (got {})",
                perch_id, owner, inst.node
            );
        }
    }
}


    // The mesh: spokes see each other WITHOUT B relaying. Red today.
    converge("A sees C directly", || a.sees("perchC", &c.hex));
    converge("C sees A directly", || c.sees("perchA", &a.hex));

    a_stop.store(true, Ordering::Relaxed);
    b_stop.store(true, Ordering::Relaxed);
    c_stop.store(true, Ordering::Relaxed);
}

    // The mesh: spokes see each other WITHOUT B relaying.
    converge("A sees C directly", || a.sees("perchC", &c.hex));
    converge("C sees A directly", || c.sees("perchA", &a.hex));

    // Regression (REQ-MESH-3): B never relays — every row each node holds is
    // own-authored (the perchC row in A came straight from C, not via B).
    let owners: [(&str, &str); 3] =
        [("perchA", &a.hex), ("perchB", &b.hex), ("perchC", &c.hex)];
    assert_no_relay(&a, &owners);
    assert_no_relay(&b, &owners);
    assert_no_relay(&c, &owners);

    a_stop.store(true, Ordering::Relaxed);
    b_stop.store(true, Ordering::Relaxed);
    c_stop.store(true, Ordering::Relaxed);
}

/// REQ-MESH-3 no-relay regression: every registry row `node` holds for each
/// named perch is authored by that perch's true owner (origin == author) — a
/// relayed third-party row (a perchC row authored by B) would fail this.
fn assert_no_relay(node: &MeshNode, owners: &[(&str, &str)]) {
    for (perch_id, owner) in owners {
        for inst in node.registry.rows(SUBNET, perch_id) {
            assert_eq!(
                &inst.node, owner,
                "{} row in a node must be own-authored ({}), never relayed (got {})",
                perch_id, owner, inst.node
            );
        }
    }
}

converge("B sees A", || b.sees("perchA", &a.hex));

converge("B sees C", || b.sees("perchC", &c.hex));

-    // The mesh: spokes see each other WITHOUT B relaying. Red today.

+    // The mesh: spokes see each other WITHOUT B relaying.

+    // Regression (REQ-MESH-3): B never relays — every row each node holds is

+    // own-authored (the perchC row in A came straight from C, not via B).

+        [("perchA", &a.hex), ("perchB", &b.hex), ("perchC", &c.hex)];

+/// REQ-MESH-3 no-relay regression: every registry row `node` holds for each

+/// named perch is authored by that perch's true owner (origin == author) — a

+/// relayed third-party row (a perchC row authored by B) would fail this.

+fn assert_no_relay(node: &MeshNode, owners: &[(&str, &str)]) {

+        for inst in node.registry.rows(SUBNET, perch_id) {

+                "{} row in a node must be own-authored ({}), never relayed (got {})",

+                perch_id, owner, inst.node

/// Overwrite a node's explicit push-target roster to list exactly `members`.

fn reroster(node: &MeshNode, members: &[&str]) {


All test files migrated. Compile the daemon test target.


Bash

cargo test -p spt-daemon --no-run 2>&1 | tail -40; echo "===EXIT:${PIPESTATUS[0]}==="

Compile spt-daemon tests


   Compiling spt-net v0.2.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
   Compiling spt-daemon v0.2.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 29.48s
  Executable unittests src\lib.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\spt_daemon-26a17645c4a18da2.exe)
  Executable tests\access.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\access-84d0786d0249d742.exe)
  Executable tests\attach.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\attach-ea7d331574a66ff3.exe)
  Executable tests\brain_swap.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\brain_swap-1d1f3f5e61950cf6.exe)
  Executable tests\broker.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\broker-ad85fed5a547eeb3.exe)
  Executable tests\budget.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\budget-f180c973248d0288.exe)
  Executable tests\daemon_e2e.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\daemon_e2e-e7fe5d2ac3167bdf.exe)
  Executable tests\digest.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\digest-a24880b43c971fca.exe)
  Executable tests\dispatch.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\dispatch-6370db0e31a26238.exe)
  Executable tests\handoff.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\handoff-15699eff0feab998.exe)
  Executable tests\idempotent.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\idempotent-1eea720937c6908c.exe)
  Executable tests\mesh.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\mesh-3206d171070508e8.exe)
  Executable tests\netbroker.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\netbroker-ce984e5fa1f2aa37.exe)
  Executable tests\netstream.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\netstream-4ff2b23e110f436e.exe)
  Executable tests\notifsync.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\notifsync-6c8768e8b9615aa0.exe)
  Executable tests\pairjoin.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\pairjoin-a1cf22e8da745a62.exe)
  Executable tests\peerloop.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\peerloop-c3fbcc98b93ab6bc.exe)
  Executable tests\presence.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\presence-88b2ea173e3c0c8a.exe)
  Executable tests\propagate.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\propagate-b447ef5588280d7b.exe)
  Executable tests\replicate.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\replicate-e9b77b78a079d445.exe)
  Executable tests\rosterprop.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\rosterprop-79961f6ff50ad702.exe)
  Executable tests\seedproofx.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\seedproofx-f675754b303e7255.exe)
  Executable tests\shellchan.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\shellchan-91441d4ab4274fc4.exe)
  Executable tests\sync.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\sync-a49c0850ee96b3b2.exe)
  Executable tests\twohost.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\twohost-113e0798f339bbab.exe)
  Executable tests\wanmsg.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\wanmsg-5daa95b45e277726.exe)
  Executable tests\xfer.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\xfer-86841a764bedec6d.exe)
===EXIT:0===


All targets compile. Run the acceptance (mesh) + migrated suites.


cargo test -p spt-daemon --test mesh 2>&1 | tail -20; echo "===EXIT:${PIPESTATUS[0]}==="

Run mesh acceptance tests


---- staggered_offliner_still_meshes stdout ----
PAIR_MEET_UP:mesh0 step=59365297 id=99e01d14b294ef4e4b02b3cef675a292b951422f9838e13c716f18d0c037a9ca addr=EndpointAddr { id: PublicKey(99e01d14b294ef4e4b02b3cef675a292b951422f9838e13c716f18d0c037a9ca), addrs: {Ip(127.0.0.1:61444), Ip([::1]:61445)} }
PAIR_MEET_UP:mesh0 step=59365297 id=99e01d14b294ef4e4b02b3cef675a292b951422f9838e13c716f18d0c037a9ca addr=EndpointAddr { id: PublicKey(99e01d14b294ef4e4b02b3cef675a292b951422f9838e13c716f18d0c037a9ca), addrs: {Ip(127.0.0.1:61450), Ip([::1]:61451)} }
PAIR_MEET_UP:mesh0 step=59365297 id=99e01d14b294ef4e4b02b3cef675a292b951422f9838e13c716f18d0c037a9ca addr=EndpointAddr { id: PublicKey(99e01d14b294ef4e4b02b3cef675a292b951422f9838e13c716f18d0c037a9ca), addrs: {Ip(127.0.0.1:61456), Ip([::1]:61457)} }
PAIR_MEET_UP:mesh0 step=59365298 id=1418b0ad6d97131980c931feb496ea384b1a105b4af946c27cf4fa93a87b4fa9 addr=EndpointAddr { id: PublicKey(1418b0ad6d97131980c931feb496ea384b1a105b4af946c27cf4fa93a87b4fa9), addrs: {Ip(127.0.0.1:64081), Ip([::1]:64082)} }
PAIR_MEET_UP:mesh0 step=59365298 id=1418b0ad6d97131980c931feb496ea384b1a105b4af946c27cf4fa93a87b4fa9 addr=EndpointAddr { id: PublicKey(1418b0ad6d97131980c931feb496ea384b1a105b4af946c27cf4fa93a87b4fa9), addrs: {Ip(127.0.0.1:64084), Ip([::1]:64085)} }
PAIR_MEET_UP:mesh0 step=59365298 id=1418b0ad6d97131980c931feb496ea384b1a105b4af946c27cf4fa93a87b4fa9 addr=EndpointAddr { id: PublicKey(1418b0ad6d97131980c931feb496ea384b1a105b4af946c27cf4fa93a87b4fa9), addrs: {Ip(127.0.0.1:64087), Ip([::1]:64088)} }

thread 'staggered_offliner_still_meshes' (40212) panicked at crates\spt-daemon\tests\mesh.rs:117:5:
never converged: B sees C's perch
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    staggered_offliner_still_meshes

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 15.73s

error: test failed, to rerun pass `-p spt-daemon --test mesh`
===EXIT:101===


Star passed; staggered failed at phase 2. Run staggered alone to isolate (the two share canonical HOME).


cargo test -p spt-daemon --test mesh staggered_offliner_still_meshes -- --exact 2>&1 | grep -v "PAIR_MEET\|SYNC_CONFLICT" | tail -25; echo "===EXIT:${PIPESTATUS[0]}==="

Run staggered test alone


    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.46s
     Running tests\mesh.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\mesh-3206d171070508e8.exe)

running 1 test
test staggered_offliner_still_meshes ... FAILED

failures:

---- staggered_offliner_still_meshes stdout ----

thread 'staggered_offliner_still_meshes' (43292) panicked at crates\spt-daemon\tests\mesh.rs:117:5:
never converged: B sees C's perch
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    staggered_offliner_still_meshes

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1 filtered out; finished in 15.29s

error: test failed, to rerun pass `-p spt-daemon --test mesh`
===EXIT:101===


Star passes (same C→B push), staggered fails phase 2 — diff is the offline dance. Check stderr for pump errors.


cargo test -p spt-daemon --test mesh staggered_offliner_still_meshes -- --exact --nocapture 2>&1 | grep -iE "PUMP_PEER_FAIL|PEER_PUMP|DISPATCH:|fail|panic|refus|reject" | grep -v PAIR_MEET | sort | uniq -c | head -40; echo "===EXIT done==="

Look for pump/dispatch errors


      1 error: test failed, to rerun pass `-p spt-daemon --test mesh`
      2 failures:
      1 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1 filtered out; finished in 15.29s
      1 test staggered_offliner_still_meshes ... FAILED
      1 thread 'staggered_offliner_still_meshes' (35904) panicked at crates\spt-daemon\tests\mesh.rs:117:5:
===EXIT done===


pairhost.rs: drop trust write

propagate.rs serve gate trust->roster

peerloop.rs THE WIDEN

CLI cli.rs repoint to roster

xtask + misc comment refs

Migrate daemon tests trust->roster

mesh.rs: un-ignore + regression assert


No errors logged. Confirm star passes in isolation (rules out shared-state luck):


cargo test -p spt-daemon --test mesh all_online_star_a_reaches_c_b_never_relays -- --exact 2>&1 | grep -v "PAIR_MEET\|SYNC_CONFLICT" | tail -8; echo "===EXIT:${PIPESTATUS[0]}==="

Run star test alone


    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.45s
     Running tests\mesh.rs (C:\Users\decid\Documents\projects\spt-core\target\debug\deps\mesh-3206d171070508e8.exe)

running 1 test
test all_online_star_a_reaches_c_b_never_relays ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.36s

===EXIT:0===


Star alone is green and fast (0.36s) — C→B push works. Staggered's phase-2 failure is offline-dance-specific. Instrument phase 2.


329	// so neither ever pushes to the other. Flips green at Mesh-D6.
330	#[test]
331	fn staggered_offliner_still_meshes() {
332	    init_home();
333	    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());
334	    let dir = TempDir::new().unwrap();
335
336	    let a = MeshNode::new(&dir.path().join("a"), "perchA", &[], dir.path());
337	    let b = MeshNode::new(&dir.path().join("b"), "perchB", &[], dir.path());
338	    let c = MeshNode::new(&dir.path().join("c"), "perchC", &[], dir.path());
339	    // The converged mesh roster (Mesh-D6): every node lists ALL members, so the
340	    // widened push fans out directly to each. A paired only B and C paired only
341	    // B, but the roster D3/D4 would propagate is seeded full here.
342	    let all = [a.hex.as_str(), b.hex.as_str(), c.hex.as_str()];
343	    reroster(&a, &all);
344	    reroster(&b, &all);
345	    reroster(&c, &all);
346
347	    canonical_full_roster(&[&a.hex, &b.hex, &c.hex]);
348	    let addrs: HashMap<String, serde_json::Value> = [
349	        (a.hex.clone(), a.addr.clone()),
350	        (b.hex.clone(), b.addr.clone()),
351	        (c.hex.clone(), c.addr.clone()),
352	    ]
353	    .into_iter()
354	    .collect();
355
356	    // Phase 1 — A joins B (both online): B comes to see A, A to see B.
357	    let a_stop = a.online(resolver_over(&addrs));
358	    let b_stop = b.online(resolver_over(&addrs));

// so neither ever pushes to the other. Flips green at Mesh-D6.
#[test]
fn staggered_offliner_still_meshes() {
    init_home();
    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());
    let dir = TempDir::new().unwrap();

    let a = MeshNode::new(&dir.path().join("a"), "perchA", &[], dir.path());
    let b = MeshNode::new(&dir.path().join("b"), "perchB", &[], dir.path());
    let c = MeshNode::new(&dir.path().join("c"), "perchC", &[], dir.path());
    // The converged mesh roster (Mesh-D6): every node lists ALL members, so the
    // widened push fans out directly to each. A paired only B and C paired only
    // B, but the roster D3/D4 would propagate is seeded full here.
    let all = [a.hex.as_str(), b.hex.as_str(), c.hex.as_str()];
    reroster(&a, &all);
    reroster(&b, &all);
    reroster(&c, &all);

    canonical_full_roster(&[&a.hex, &b.hex, &c.hex]);
    let addrs: HashMap<String, serde_json::Value> = [
        (a.hex.clone(), a.addr.clone()),
        (b.hex.clone(), b.addr.clone()),
        (c.hex.clone(), c.addr.clone()),
    ]
    .into_iter()
    .collect();

    // Phase 1 — A joins B (both online): B comes to see A, A to see B.
    let a_stop = a.online(resolver_over(&addrs));
    let b_stop = b.online(resolver_over(&addrs));


358	    let b_stop = b.online(resolver_over(&addrs));
359	    converge("B sees A's perch", || b.sees("perchA", &a.hex));
360	    converge("A sees B's perch", || a.sees("perchB", &b.hex));
361	    a_stop.store(true, Ordering::Relaxed); // A offline
362
363	    // Phase 2 — C joins B (B still online): B comes to see C.
364	    let c_stop = c.online(resolver_over(&addrs));
365	    converge("B sees C's perch", || b.sees("perchC", &c.hex));
366	    b_stop.store(true, Ordering::Relaxed); // B offline
367
368	    // Phase 3 — A online again; C still online; B offline. The mesh must
369	    // converge A↔C with B gone. This is the red assertion.
370	    let a_stop2 = a.online(resolver_over(&addrs));
371	    converge("A meshes to C (sees perchC)", || a.sees("perchC", &c.hex));
372	    converge("C meshes to A (sees perchA)", || c.sees("perchA", &a.hex));
373
374	    // Reach: each end holds a dialable address for the other.
375	    assert!(addrs.contains_key(&c.hex) && addrs.contains_key(&a.hex));
376
377	    // Regression (REQ-MESH-3): no row was relayed — every registry row A and C
378	    // hold is authored by the node that owns that perch (origin == author).
379	    assert_no_relay(&a, &[("perchA", &a.hex), ("perchB", &b.hex), ("perchC", &c.hex)]);

    let b_stop = b.online(resolver_over(&addrs));
    converge("B sees A's perch", || b.sees("perchA", &a.hex));
    converge("A sees B's perch", || a.sees("perchB", &b.hex));
    a_stop.store(true, Ordering::Relaxed); // A offline

    // Phase 2 — C joins B (B still online): B comes to see C.
    let c_stop = c.online(resolver_over(&addrs));
    converge("B sees C's perch", || b.sees("perchC", &c.hex));
    b_stop.store(true, Ordering::Relaxed); // B offline

    // Phase 3 — A online again; C still online; B offline. The mesh must
    // converge A↔C with B gone. This is the red assertion.
    let a_stop2 = a.online(resolver_over(&addrs));
    converge("A meshes to C (sees perchC)", || a.sees("perchC", &c.hex));
    converge("C meshes to A (sees perchA)", || c.sees("perchA", &a.hex));

    // Reach: each end holds a dialable address for the other.
    assert!(addrs.contains_key(&c.hex) && addrs.contains_key(&a.hex));

    // Regression (REQ-MESH-3): no row was relayed — every registry row A and C
    // hold is authored by the node that owns that perch (origin == author).
    assert_no_relay(&a, &[("perchA", &a.hex), ("perchB", &b.hex), ("perchC", &c.hex)]);


Let me instrument phase 2 directly to see what C pushes and what B receives.


Edit

    // Phase 2 — C joins B (B still online): B comes to see C.
    let c_stop = c.online(resolver_over(&addrs));
    converge("B sees C's perch", || b.sees("perchC", &c.hex));

    // Phase 2 — C joins B (B still online): B comes to see C.
    let c_stop = c.online(resolver_over(&addrs));
    {
        let rp = c.paths.subnets.parent().unwrap().join("roster.json");
        let rr = spt_store::roster::RosterStore::load_from(&rp);
        eprintln!("DBG c roster members={}", rr.members_in(SUBNET).count());
        for _ in 0..20 {
            eprintln!(
                "DBG b perchC rows: {:?}",
                b.registry.rows(SUBNET, "perchC").iter().map(|i| i.node.clone()).collect::<Vec<_>>()
            );
            std::thread::sleep(Duration::from_millis(300));
            if b.sees("perchC", &c.hex) { eprintln!("DBG converged"); break; }
        }
    }
    converge("B sees C's perch", || b.sees("perchC", &c.hex));