43:use spt_store::trust::TrustStore;
285:    pub fn apply_node_labels(
660:pub fn repair_evict_superseded(
661:    trust: &mut TrustStore,
784:    /// `TrustStore::record` pin; `is_member` keys on `(subnet, pubkey)`).
912:        let mut trust = TrustStore::default();
919:        repair_evict_superseded(&mut trust, "home", "new-key", &intro, &snap_dir);
922:            !trust.is_trusted("home", "old-key"),
926:            trust.is_trusted("worknet", "old-key"),
949:        let mut t2 = TrustStore::default();
955:        repair_evict_superseded(&mut t2, "home", "new-key", &other, &snap_dir);
956:        assert!(t2.is_trusted("home", "unrelated"));
1187:    fn apply_node_labels_gates_and_merges() {


248:    /// immediate context freshness pull from trusted peers. Accepts a
581:    /// Exit a subnet: drop its membership and trust material from this node.
583:    /// Removes the subnet's seed, its trust rows, its serve-state, and its
585:    /// destroys trust material). The remaining members still hold the old
591:    /// Remove a dead node identity's trust rows (and registry rows).
594:    /// or is gone for good: its stale trust rows cost a dial every pump
596:    /// label. Gated behind OS elevation (trust mutation).
1305:        let trust = spt_store::trust::TrustStore::load();
1313:            &trust,
1971:/// "re-provision the QR from any still-trusted node" is actually fulfilled): code,
2281:/// trust store. Pure over its inputs so the view is unit-tested hermetically.
2293:    trust: &spt_store::trust::TrustStore,
2306:            for peer in trust.peers_in(&s.name) {
2498:/// trust-pinned peer — so a member with zero registry rows still renders
2537:    // touch the liveness math) and never override a trust/instance label
2770:    let trust = spt_store::trust::TrustStore::load();
2778:            &trust,
2816:        // Membership seed: self + trust-pinned peers (rowless members still
2822:        for peer in trust.peers_in(&sub.name) {
2862:/// trust material, the same class as join's machine enrollment — fail-safe,
2865:fn trust_mutation_refusal(elevation: Elevation, what: &str) -> Option<String> {
2869:            "ELEVATION_REQUIRED: {what} mutates this machine's trust material — re-run \
2876:/// drop the subnet's seed, its trust rows, its serve-state row, and its
2883:    if let Some(msg) = trust_mutation_refusal(elevation::current(), "leaving a subnet") {
2897:    // harmless residue (trust rows without a subnet never dial: the pump
2898:    // iterates subnets, not trust rows).
2904:    let mut trust = spt_store::trust::TrustStore::load();
2905:    let before = trust.peers.len();
2906:    trust.peers.retain(|p| p.subnet != name);
2907:    let dropped = before - trust.peers.len();
2908:    if let Err(e) = trust.save() {
2920:        "LEFT:{name} (seed removed, {dropped} trust row(s) dropped). The remaining \
2926:/// The prune target resolution (pure, REQ-SUBNET-6): distinct trusted
2931:fn prune_candidates(trust: &spt_store::trust::TrustStore, query: &str) -> Vec<String> {
2934:    for p in &trust.peers {
2947:/// `spt subnet prune <node>` — remove a dead identity's trust rows + its
2949:/// trust rows every tick, so a re-paired/retired machine's stale identity
2954:    if let Some(msg) = trust_mutation_refusal(elevation::current(), "pruning a node's trust rows") {
2961:    let mut trust = spt_store::trust::TrustStore::load();
2962:    let candidates = prune_candidates(&trust, node);
2965:            eprintln!("NO_MATCH:{node} — no trusted identity matches (hex, prefix, or label)");
2987:    let before = trust.peers.len();
2988:    let subnets_hit: Vec<String> = trust
2994:    trust.peers.retain(|p| p.pubkey_hex != hex);
2995:    let dropped = before - trust.peers.len();
2996:    if let Err(e) = trust.save() {
3021:        "PRUNED:{}… ({dropped} trust row(s) [{}], {evicted} registry row(s)). \
3022:         Note: trust-delete alone is not revocation — rotate the seed if the \
3023:         machine itself is untrusted (it still holds the subnet seed).",
3032:/// mutates no trust material (unlike `leave`/`prune`). The daemon's pump,
3075:/// (REQ-SUBNET-4): joining enrolls the MACHINE into a subnet's trust
3085:            "ELEVATION_REQUIRED: joining a subnet enrolls this machine into its trust \
3177:            // Translate the trust verdict (the wire carries the machine tag;
3182:                     trust each other."
3185:                    eprintln!("The machines already trusted each other — membership refreshed.")
3387:/// (`identity/trust/access.json`, ADR-0009). The daemon's gate re-reads the
4390:/// (`identity/trust/grants.json`, REQ-CONSENT-1). Rows are scoped to **this
6329:    fn trust_mutation_gate_refuses_without_confirmed_elevation() {
6330:        assert!(trust_mutation_refusal(Elevation::Elevated, "leaving").is_none());
6331:        assert!(trust_mutation_refusal(Elevation::NotElevated, "leaving").is_some());
6332:        assert!(trust_mutation_refusal(Elevation::Unknown, "leaving").is_some());
6340:        let mut trust = spt_store::trust::TrustStore::default();
6341:        trust.record("bignet", "09ef831e00112233", None, None);
6342:        trust.record("bignet", "09ef831e00112233", None, None); // idempotent
6343:        trust.record("worknet", "09ef831e00112233", None, None); // same id, 2nd subnet
6344:        trust.record("bignet", "09aa000011223344", Some("GRAVITY"), None);
6348:            prune_candidates(&trust, "09ef831e00112233"),
6352:            prune_candidates(&trust, "09ef"),
6357:            prune_candidates(&trust, "gravity"),
6362:        assert_eq!(prune_candidates(&trust, "09").len(), 2);
6364:        assert!(prune_candidates(&trust, "ff00").is_empty());
6428:    // expiry (ADR-0005 "re-provision the QR from any still-trusted node").
6518:        let trust = spt_store::trust::TrustStore::default();
6519:        let rows = subnet_status_rows(&store, &regs, &trust, Some("aa"), None);
6527:        // MEMBERSHIP is trust-derived, rows enrich (the re-join field bug,
6532:        let mut pinned = spt_store::trust::TrustStore::default();
6563:            &trust,
6643:            "trust label renders"
6676:    // peer: a member with no instance rows and an unlabeled trust pin still
6693:        // "idle0bb" is a trust-pinned member but its pin is UNLABELED (label None).


5://! `serve_sync` / `serve_update` / `apply_notif_feed` / `receive_wan` against
33://! | `kind=query` | update pull ([`crate::propagate::serve_update`]) |
73:use spt_store::trust::TrustStore;
78:use crate::propagate::serve_update;
326:            let trust = TrustStore::load();
327:            match serve_update(&mut brain, stream_id, origin, 0, &cache, &trust) {
425:/// origin trusted in that subnet), until the peer finishes.


Now reading the eviction fn + tests, wire.rs sigs, and the CLI sections.


270	                .unwrap()
271	                .insert(origin_node.to_string(), wall_ms());
272	        }
273	        self.write_snapshots();
274	        (verdicts, flips)
275	    }
276
277	    /// Apply inbound node-level label updates (REQ-SUBNET-3) under the SAME
278	    /// fail-closed gate as [`apply_feed_flips`]: a non-member subnet or an
279	    /// untrusted origin is refused; an admitted update merges via
280	    /// [`SubnetRegistry::merge_node_label`] (epoch lease) and stamps the
281	    /// origin's gossip-recency (an admitted label feed proves the peer is up,
282	    /// exactly like an instance feed). The endpoint-less carrier: this is how a
283	    /// peer with zero endpoints gets its hostname into our `--nodes` view.
284	    // [impl->REQ-SUBNET-3]
285	    pub fn apply_node_labels(
286	        &self,
287	        origin_node: &str,
288	        updates: &[NodeLabelUpdate],
289	        policy: &RegistryGatePolicy,
290	    ) -> Vec<RegistryApplyVerdict> {
291	        let mut regs = self.regs.lock().unwrap();
292	        let mut admitted_any = false;
293	        let verdicts = updates
294	            .iter()
295	            .map(|u| {
296	                if !policy.subnets.iter().any(|s| s == &u.subnet) {
297	                    return RegistryApplyVerdict::NotMember;
298	                }
299	                // [impl->REQ-MESH-5]
300	                if !policy.roster.is_member(&u.subnet, origin_node) {
301	                    return RegistryApplyVerdict::UntrustedOrigin;
302	                }
303	                admitted_any = true;
304	                let reg = regs.entry(u.subnet.clone()).or_default();
305	                RegistryApplyVerdict::Merged(reg.merge_node_label(
306	                    &u.node,
307	                    u.label.clone(),
308	                    u.epoch,
309	                ))
310	            })
311	            .collect();
312	        drop(regs);
313	        if admitted_any {
314	            self.heard
315	                .lock()
316	                .unwrap()
317	                .insert(origin_node.to_string(), wall_ms());
318	        }
319	        self.write_snapshots();
320	        verdicts
321	    }
322
323	    /// Scan the owlery and (re-)advertise every self perch into every member
324	    /// subnet it is visible in, each row stamped with a fresh epoch. Returns
325	    /// the updates to push to peers (the node's own rows only — peers' rows
326	    /// reconverge from their own re-advertisement, no transitive gossip in
327	    /// v1: trust is pairwise per subnet, a relayed third-party row would
328	    /// carry an origin the receiver can't gate honestly).
329	    // [impl->REQ-INST-7]
330	    pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {
331	        let subnets = SubnetStore::load();
332	        let mut vis = VisibilityStore::load();
333	        // First-join home adoption (D9-5, REQ-INST-15): within one
334	        // advertisement cadence of the node's first join (a pair lands the
335	        // subnet record; no pairing-protocol threading needed), unhomed Self
336	        // perches adopt the sole subnet + seed their sync scope. Strictly
337	        // adopt-if-unset; a multi-subnet store adopts nothing.
338	        // [impl->REQ-INST-15]
339	        if !spt_store::home::adopt_for_unset(owlery, &subnets, &mut vis).is_empty() {
340	            let _ = vis.save();
341	        }
342	        let vis = vis;
343	        // The node-level resource-blurb seed (D9-4): an endpoint that has not
344	        // authored its own blurb advertises this default (config seeds, the
345	        // agent refines — CONTEXT §resource advertisement).
346	        let node_blurb = crate::config::DaemonConfig::load().resources_blurb;
347	        // The node's subnet-broadcast shell adapters (M5-D8c, CONTEXT
348	        // §Discovery scope): registered kind="shell" adapters that chose
349	        // broadcast=subnet ride every row this node authors — the other-node
350	        // discovery arm. Computed once per scan (the registration set is
351	        // node-level).
352	        // [impl->REQ-EP-5]
353	        let shell_adapters: Vec<String> = spt_runtime::registry::registered(&perch::adapters_dir())
354	            .iter()
355	            .filter(|(r, m)| {
356	                r.kind == spt_runtime::manifest::AdapterKind::Shell
357	                    && m.shell.as_ref().and_then(|s| s.broadcast)
358	                        == Some(spt_runtime::manifest::Broadcast::Subnet)
359	            })
360	            .map(|(r, _)| r.name.clone())
361	            .collect();
362	        let mut out = Vec::new();
363	        let mut regs = self.regs.lock().unwrap();
364	        let mut epoch = self.epoch.lock().unwrap();
365
366	        for id in perch::list_self_perch_ids(owlery) {
367	            let perch_path = perch::resolve_perch_path_in(owlery, &id, ParentHint::Infer);
368	            // Advertisement follows the resting-state machine (D9-2,
369	            // REQ-INST-3): a live perch with a resting record advertises its
370	            // machine state (active / dormant / suspended); a recordless
371	            // (pre-D9-2 / interim) or dead perch keeps the legacy
372	            // liveness-derived mapping. Every advertisement stamps a fresh
373	            // epoch below, so a transition reaches peers as an epoch-bumped
374	            // update — the lease orders it (KH 4.8).
375	            // [impl->REQ-INST-3]
376	            let status = if is_perch_alive(&perch_path) {
377	                match crate::resting::read_rest(&perch_path).map(|r| r.state) {
378	                    Some(crate::resting::RestState::Active) | None => Status::Active,
379	                    Some(crate::resting::RestState::Dormant) => Status::Dormant,
380	                    Some(crate::resting::RestState::Suspended) => Status::Suspended,
381	                }
382	            } else {
383	                Status::Dormant
384	            };
385	            // Endpoint-authored blurb wins; the node seed fills the gap
386	            // (both-authored, REQ-INST-14). Rides the same epoch lease as
387	            // every instance update — a blurb edit reaches peers as an
388	            // ordered, epoch-bumped row.
389	            // [impl->REQ-INST-14]

                .unwrap()
                .insert(origin_node.to_string(), wall_ms());
        }
        self.write_snapshots();
        (verdicts, flips)
    }

    /// Apply inbound node-level label updates (REQ-SUBNET-3) under the SAME
    /// fail-closed gate as [`apply_feed_flips`]: a non-member subnet or an
    /// untrusted origin is refused; an admitted update merges via
    /// [`SubnetRegistry::merge_node_label`] (epoch lease) and stamps the
    /// origin's gossip-recency (an admitted label feed proves the peer is up,
    /// exactly like an instance feed). The endpoint-less carrier: this is how a
    /// peer with zero endpoints gets its hostname into our `--nodes` view.
    // [impl->REQ-SUBNET-3]
    pub fn apply_node_labels(
        &self,
        origin_node: &str,
        updates: &[NodeLabelUpdate],
        policy: &RegistryGatePolicy,
    ) -> Vec<RegistryApplyVerdict> {
        let mut regs = self.regs.lock().unwrap();
        let mut admitted_any = false;
        let verdicts = updates
            .iter()
            .map(|u| {
                if !policy.subnets.iter().any(|s| s == &u.subnet) {
                    return RegistryApplyVerdict::NotMember;
                }
                // [impl->REQ-MESH-5]
                if !policy.roster.is_member(&u.subnet, origin_node) {
                    return RegistryApplyVerdict::UntrustedOrigin;
                }
                admitted_any = true;
                let reg = regs.entry(u.subnet.clone()).or_default();
                RegistryApplyVerdict::Merged(reg.merge_node_label(
                    &u.node,
                    u.label.clone(),
                    u.epoch,
                ))
            })
            .collect();
        drop(regs);
        if admitted_any {
            self.heard
                .lock()
                .unwrap()
                .insert(origin_node.to_string(), wall_ms());
        }
        self.write_snapshots();
        verdicts
    }

    /// Scan the owlery and (re-)advertise every self perch into every member
    /// subnet it is visible in, each row stamped with a fresh epoch. Returns
    /// the updates to push to peers (the node's own rows only — peers' rows
    /// reconverge from their own re-advertisement, no transitive gossip in
    /// v1: trust is pairwise per subnet, a relayed third-party row would
    /// carry an origin the receiver can't gate honestly).
    // [impl->REQ-INST-7]
    pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {
        let subnets = SubnetStore::load();
        let mut vis = VisibilityStore::load();
        // First-join home adoption (D9-5, REQ-INST-15): within one
        // advertisement cadence of the node's first join (a pair lands the
        // subnet record; no pairing-protocol threading needed), unhomed Self
        // perches adopt the sole subnet + seed their sync scope. Strictly
        // adopt-if-unset; a multi-subnet store adopts nothing.
        // [impl->REQ-INST-15]
        if !spt_store::home::adopt_for_unset(owlery, &subnets, &mut vis).is_empty() {
            let _ = vis.save();
        }
        let vis = vis;
        // The node-level resource-blurb seed (D9-4): an endpoint that has not
        // authored its own blurb advertises this default (config seeds, the
        // agent refines — CONTEXT §resource advertisement).
        let node_blurb = crate::config::DaemonConfig::load().resources_blurb;
        // The node's subnet-broadcast shell adapters (M5-D8c, CONTEXT
        // §Discovery scope): registered kind="shell" adapters that chose
        // broadcast=subnet ride every row this node authors — the other-node
        // discovery arm. Computed once per scan (the registration set is
        // node-level).
        // [impl->REQ-EP-5]
        let shell_adapters: Vec<String> = spt_runtime::registry::registered(&perch::adapters_dir())
            .iter()
            .filter(|(r, m)| {
                r.kind == spt_runtime::manifest::AdapterKind::Shell
                    && m.shell.as_ref().and_then(|s| s.broadcast)
                        == Some(spt_runtime::manifest::Broadcast::Subnet)
            })
            .map(|(r, _)| r.name.clone())
            .collect();
        let mut out = Vec::new();
        let mut regs = self.regs.lock().unwrap();
        let mut epoch = self.epoch.lock().unwrap();

        for id in perch::list_self_perch_ids(owlery) {
            let perch_path = perch::resolve_perch_path_in(owlery, &id, ParentHint::Infer);
            // Advertisement follows the resting-state machine (D9-2,
            // REQ-INST-3): a live perch with a resting record advertises its
            // machine state (active / dormant / suspended); a recordless
            // (pre-D9-2 / interim) or dead perch keeps the legacy
            // liveness-derived mapping. Every advertisement stamps a fresh
            // epoch below, so a transition reaches peers as an epoch-bumped
            // update — the lease orders it (KH 4.8).
            // [impl->REQ-INST-3]
            let status = if is_perch_alive(&perch_path) {
                match crate::resting::read_rest(&perch_path).map(|r| r.state) {
                    Some(crate::resting::RestState::Active) | None => Status::Active,
                    Some(crate::resting::RestState::Dormant) => Status::Dormant,
                    Some(crate::resting::RestState::Suspended) => Status::Suspended,
                }
            } else {
                Status::Dormant
            };
            // Endpoint-authored blurb wins; the node seed fills the gap
            // (both-authored, REQ-INST-14). Rides the same epoch lease as
            // every instance update — a blurb edit reaches peers as an
            // ordered, epoch-bumped row.
            // [impl->REQ-INST-14]


620	            self.write_snapshots();
621	        }
622	        evicted
623	    }
624	}
625
626	/// The superseded identities a completed ceremony's intro reveals (pure,
627	/// M8 decision 13 / REQ-SUBNET-7): rows in `reg` advertising the SAME node
628	/// label AND machine id under a DIFFERENT node key than the re-paired
629	/// joiner. Both must match — a label alone re-pairs nothing (hostnames
630	/// collide), a machine id alone is never gossiped without its label.
631	// [impl->REQ-SUBNET-7]
632	pub fn superseded_identities(
633	    reg: &SubnetRegistry,
634	    intro: &spt_net::net::pairing::wire::NodeIntro,
635	    joiner_hex: &str,
636	) -> Vec<String> {
637	    let mut out = Vec::new();
638	    for id in reg.endpoint_ids() {
639	        for inst in reg.instances(id) {
640	            if inst.node != joiner_hex
641	                && inst.node_label.as_deref() == Some(intro.label.as_str())
642	                && inst.machine_id.as_deref() == Some(intro.machine_id.as_str())
643	                && !out.contains(&inst.node)
644	            {
645	                out.push(inst.node.clone());
646	            }
647	        }
648	    }
649	    out
650	}
651
652	/// The re-pair overwrite's effect leg (M8 decision 13, REQ-SUBNET-7), run
653	/// by the pairing host AFTER a completed ceremony: resolve the superseded
654	/// identities from the subnet's snapshot, drop their trust rows (this
655	/// subnet's scope — the ceremony proved control of THIS subnet's code,
656	/// nothing about others), rewrite the disk snapshot, and note the hexes in
657	/// the eviction marker so the pump clears the in-memory rows on its next
658	/// tick. The caller persists `trust`.
659	// [impl->REQ-SUBNET-7]
660	pub fn repair_evict_superseded(
661	    trust: &mut TrustStore,
662	    subnet: &str,
663	    joiner_hex: &str,
664	    intro: &spt_net::net::pairing::wire::NodeIntro,
665	    snap_dir: &Path,
666	) {
667	    let snap_path = RegistryHost::snapshot_path(snap_dir, subnet);
668	    let Some(mut reg) = std::fs::read_to_string(&snap_path)
669	        .ok()
670	        .and_then(|s| serde_json::from_str::<SubnetRegistry>(&s).ok())
671	    else {
672	        return; // no gossip yet — nothing to supersede
673	    };
674	    let dead = superseded_identities(&reg, intro, joiner_hex);
675	    if dead.is_empty() {
676	        return;
677	    }
678	    for hex in &dead {
679	        let dropped = trust.revoke(subnet, hex);
680	        eprintln!(
681	            "REPAIR_SUPERSEDED:{} (label '{}' re-paired as {}…; trust row {})",
682	            hex,
683	            intro.label,
684	            spt_net::net::registry::key_prefix(joiner_hex),
685	            if dropped { "dropped" } else { "absent" },
686	        );
687	    }
688	    reg.evict_nodes(|node| dead.iter().any(|d| d == node));
689	    if let Ok(json) = serde_json::to_vec_pretty(&reg) {
690	        let _ = atomic_write(&snap_path, &json);
691	    }
692	    // Merge into the marker (several re-pairs can land between pump ticks).
693	    let marker = RegistryHost::repair_evict_path(snap_dir);
694	    let mut all: std::collections::BTreeSet<String> = std::fs::read_to_string(&marker)
695	        .ok()
696	        .and_then(|s| serde_json::from_str(&s).ok())
697	        .unwrap_or_default();
698	    all.extend(dead);
699	    if let Ok(json) = serde_json::to_vec_pretty(&all) {
700	        let _ = atomic_write(&marker, &json);
701	    }
702	}
703
704	/// The event-driven advertisement marker file (M8 decision 15, REQ-CONV-2),
705	/// under `identity/` — the [`crate::resting::PULL_MARKER_FILE`] idiom applied
706	/// to the registry leg. An endpoint online/offline transition (ready-listener
707	/// start/stop, a rest edge) drops it; the pump consumes it and forces a
708	/// registry round NOW (advertise_local + peer push) instead of waiting out
709	/// the cadence. A WAKE of the existing loop, never a second advertisement
710	/// path: the forced round takes the exact same code path, so the epoch lease
711	/// and visibility gates ride unchanged. File-based because the transitions
712	/// happen in other processes (`spt ready`, `spt endpoint stop`, a wake CLI)
713	/// than the pump's daemon.
714	pub const ADVERTISE_MARKER_FILE: &str = "advertise-now";
715
716	/// Drop the advertise-now marker at the canonical identity dir (idempotent —
717	/// a second transition before the pump's next tick re-requests the same
718	/// round). Best-effort: a failed drop costs one cadence, never an error.
719	// [impl->REQ-CONV-2]
720	pub fn request_advertise_now() {
721	    let marker = spt_store::perch::identity_dir().join(ADVERTISE_MARKER_FILE);
722	    if let Some(parent) = marker.parent() {
723	        let _ = std::fs::create_dir_all(parent);
724	    }
725	    let _ = std::fs::write(&marker, b"");
726	}
727
728	/// Consume the advertise-now marker: `true` exactly once per drop (the
729	/// remove is the take — two pump ticks can never both claim one transition).
730	// [impl->REQ-CONV-2]
731	pub fn take_advertise_now(marker_path: &Path) -> bool {
732	    std::fs::remove_file(marker_path).is_ok()
733	}
734
735	/// Wall-clock ms (the heard-map stamp; display/recency only — never a merge
736	/// key, the epoch lease owns ordering).
737	fn wall_ms() -> u64 {
738	    std::time::SystemTime::now()
739	        .duration_since(std::time::UNIX_EPOCH)

            self.write_snapshots();
        }
        evicted
    }
}

/// The superseded identities a completed ceremony's intro reveals (pure,
/// M8 decision 13 / REQ-SUBNET-7): rows in `reg` advertising the SAME node
/// label AND machine id under a DIFFERENT node key than the re-paired
/// joiner. Both must match — a label alone re-pairs nothing (hostnames
/// collide), a machine id alone is never gossiped without its label.
// [impl->REQ-SUBNET-7]
pub fn superseded_identities(
    reg: &SubnetRegistry,
    intro: &spt_net::net::pairing::wire::NodeIntro,
    joiner_hex: &str,
) -> Vec<String> {
    let mut out = Vec::new();
    for id in reg.endpoint_ids() {
        for inst in reg.instances(id) {
            if inst.node != joiner_hex
                && inst.node_label.as_deref() == Some(intro.label.as_str())
                && inst.machine_id.as_deref() == Some(intro.machine_id.as_str())
                && !out.contains(&inst.node)
            {
                out.push(inst.node.clone());
            }
        }
    }
    out
}

/// The re-pair overwrite's effect leg (M8 decision 13, REQ-SUBNET-7), run
/// by the pairing host AFTER a completed ceremony: resolve the superseded
/// identities from the subnet's snapshot, drop their trust rows (this
/// subnet's scope — the ceremony proved control of THIS subnet's code,
/// nothing about others), rewrite the disk snapshot, and note the hexes in
/// the eviction marker so the pump clears the in-memory rows on its next
/// tick. The caller persists `trust`.
// [impl->REQ-SUBNET-7]
pub fn repair_evict_superseded(
    trust: &mut TrustStore,
    subnet: &str,
    joiner_hex: &str,
    intro: &spt_net::net::pairing::wire::NodeIntro,
    snap_dir: &Path,
) {
    let snap_path = RegistryHost::snapshot_path(snap_dir, subnet);
    let Some(mut reg) = std::fs::read_to_string(&snap_path)
        .ok()
        .and_then(|s| serde_json::from_str::<SubnetRegistry>(&s).ok())
    else {
        return; // no gossip yet — nothing to supersede
    };
    let dead = superseded_identities(&reg, intro, joiner_hex);
    if dead.is_empty() {
        return;
    }
    for hex in &dead {
        let dropped = trust.revoke(subnet, hex);
        eprintln!(
            "REPAIR_SUPERSEDED:{} (label '{}' re-paired as {}…; trust row {})",
            hex,
            intro.label,
            spt_net::net::registry::key_prefix(joiner_hex),
            if dropped { "dropped" } else { "absent" },
        );
    }
    reg.evict_nodes(|node| dead.iter().any(|d| d == node));
    if let Ok(json) = serde_json::to_vec_pretty(&reg) {
        let _ = atomic_write(&snap_path, &json);
    }
    // Merge into the marker (several re-pairs can land between pump ticks).
    let marker = RegistryHost::repair_evict_path(snap_dir);
    let mut all: std::collections::BTreeSet<String> = std::fs::read_to_string(&marker)
        .ok()
        .and_then(|s| serde_json::from_str(&s).ok())
        .unwrap_or_default();
    all.extend(dead);
    if let Ok(json) = serde_json::to_vec_pretty(&all) {
        let _ = atomic_write(&marker, &json);
    }
}

/// The event-driven advertisement marker file (M8 decision 15, REQ-CONV-2),
/// under `identity/` — the [`crate::resting::PULL_MARKER_FILE`] idiom applied
/// to the registry leg. An endpoint online/offline transition (ready-listener
/// start/stop, a rest edge) drops it; the pump consumes it and forces a
/// registry round NOW (advertise_local + peer push) instead of waiting out
/// the cadence. A WAKE of the existing loop, never a second advertisement
/// path: the forced round takes the exact same code path, so the epoch lease
/// and visibility gates ride unchanged. File-based because the transitions
/// happen in other processes (`spt ready`, `spt endpoint stop`, a wake CLI)
/// than the pump's daemon.
pub const ADVERTISE_MARKER_FILE: &str = "advertise-now";

/// Drop the advertise-now marker at the canonical identity dir (idempotent —
/// a second transition before the pump's next tick re-requests the same
/// round). Best-effort: a failed drop costs one cadence, never an error.
// [impl->REQ-CONV-2]
pub fn request_advertise_now() {
    let marker = spt_store::perch::identity_dir().join(ADVERTISE_MARKER_FILE);
    if let Some(parent) = marker.parent() {
        let _ = std::fs::create_dir_all(parent);
    }
    let _ = std::fs::write(&marker, b"");
}

/// Consume the advertise-now marker: `true` exactly once per drop (the
/// remove is the take — two pump ticks can never both claim one transition).
// [impl->REQ-CONV-2]
pub fn take_advertise_now(marker_path: &Path) -> bool {
    std::fs::remove_file(marker_path).is_ok()
}

/// Wall-clock ms (the heard-map stamp; display/recency only — never a merge
/// key, the epoch lease owns ordering).
fn wall_ms() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)


<persisted-output>
Output too large (20.3KB). Full output saved to: C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\81a2a62e-a5c4-4080-9c92-da268cc5c5fb\tool-results\toolu_01Ds1YWoXxnHxYiSsehuqpK3.txt

Preview (first 2KB):
crates\spt-daemon\src\broker.rs:632:    /// trust row + seed), and the TOTP code in the request must never land
crates\spt-daemon\src\config.rs:36:/// registry/notif feeds to trusted peers and pulls context syncs from them.
crates\spt-daemon\src\config.rs:137:    /// Update check cadence (D9-1; the pull rides the same trusted conns).
crates\spt-daemon\src\applyhost.rs:16://!    `release-keys.json` policy): apply never trusts the cache — a tampered
crates\spt-daemon\src\applyhost.rs:58:    /// The at-rest re-verification refused (tamper / expiry / key trust) —
crates\spt-daemon\src\applyhost.rs:89:    if policy.trusted_keys.is_empty() {
crates\spt-daemon\src\applyhost.rs:91:            "release-keys.json unprovisioned (no trusted keys)".to_string(),
crates\spt-daemon\src\adapter_update.rs:18://!   nothing for it to hash. It delegates the trust **only** when the adapter
crates\spt-daemon\src\adapter_update.rs:54:    /// unverifiable by spt-core — skipped rather than trusted blindly.
crates\spt-daemon\src\adapter_update.rs:59:    /// manifest validation, but the conductor never trusts an unverifiable pull).
crates\spt-daemon\src\adapter_update.rs:71:    /// `delegated` + attested: run the adapter's own updater (the trusted command).
crates\spt-daemon\src\adapter_update.rs:286:    // delegated to its own updater (the trusted command).
crates\spt-daemon\src\adapter_update.rs:297:    // as unverifiable — spt-core never blindly trusts an opaque updater.
crates\spt-daemon\src\access.rs:32://! The store is [`spt_store::access::AccessStore`] (`trust/access.json` —
crates\spt-daemon\src\access.rs:33://! security material next to the trust store). The gate re-reads it per check:
crates\spt-daemon\src\dispatch.rs:73:use spt_store::trust::TrustStore;
crates\spt-daemon\src\dispatch.rs:326:            let trust = TrustStore::load();
crates\spt-daemon\src\dispatch.rs:327:            match serve_update(&mut brain, stream_id, origin, 0, &cache, &trust) {
...
</persisted-output>

crates\spt-daemon\src\broker.rs:632:    /// trust row + seed), and the TOTP code in the request must never land
crates\spt-daemon\src\config.rs:36:/// registry/notif feeds to trusted peers and pulls context syncs from them.
crates\spt-daemon\src\config.rs:137:    /// Update check cadence (D9-1; the pull rides the same trusted conns).
crates\spt-daemon\src\applyhost.rs:16://!    `release-keys.json` policy): apply never trusts the cache — a tampered
crates\spt-daemon\src\applyhost.rs:58:    /// The at-rest re-verification refused (tamper / expiry / key trust) —
crates\spt-daemon\src\applyhost.rs:89:    if policy.trusted_keys.is_empty() {
crates\spt-daemon\src\applyhost.rs:91:            "release-keys.json unprovisioned (no trusted keys)".to_string(),
crates\spt-daemon\src\adapter_update.rs:18://!   nothing for it to hash. It delegates the trust **only** when the adapter
crates\spt-daemon\src\adapter_update.rs:54:    /// unverifiable by spt-core — skipped rather than trusted blindly.
crates\spt-daemon\src\adapter_update.rs:59:    /// manifest validation, but the conductor never trusts an unverifiable pull).
crates\spt-daemon\src\adapter_update.rs:71:    /// `delegated` + attested: run the adapter's own updater (the trusted command).
crates\spt-daemon\src\adapter_update.rs:286:    // delegated to its own updater (the trusted command).
crates\spt-daemon\src\adapter_update.rs:297:    // as unverifiable — spt-core never blindly trusts an opaque updater.
crates\spt-daemon\src\access.rs:32://! The store is [`spt_store::access::AccessStore`] (`trust/access.json` —
crates\spt-daemon\src\access.rs:33://! security material next to the trust store). The gate re-reads it per check:
crates\spt-daemon\src\dispatch.rs:73:use spt_store::trust::TrustStore;
crates\spt-daemon\src\dispatch.rs:326:            let trust = TrustStore::load();
crates\spt-daemon\src\dispatch.rs:327:            match serve_update(&mut brain, stream_id, origin, 0, &cache, &trust) {
crates\spt-daemon\src\dispatch.rs:425:/// origin trusted in that subnet), until the peer finishes.
crates\spt-daemon\src\grants.rs:96:/// is this node's own `trust/grants.json`; a remotely-authored grant only
crates\spt-daemon\src\lib.rs:54://! trust/revocation, channel pinning, metadata expiry, monotonic version, and a
crates\spt-daemon\src\lib.rs:64://! spt-core delegates the trust only when the adapter attests `self_verifies`,
crates\spt-daemon\src\linkhost.rs:12://! ## Trust class: remote-drive (ungated beyond the firewall)
crates\spt-daemon\src\nethost.rs:96:    /// None`). The D5 gate swap reads this in place of `is_trusted(subnet, …)`;
crates\spt-daemon\src\nethost.rs:97:    /// at D2 it is cached but not yet consumed (the `is_trusted` gates still run
crates\spt-daemon\src\nethost.rs:440:    /// trusted [`SPT_NET_ALPN`] (connections join the table as before) and
crates\spt-daemon\src\nethost.rs:441:    /// the pre-trust pairing ALPN — routed to the always-on responder
crates\spt-daemon\src\nethost.rs:445:    /// per key — the ALPN is the pre-trust/trusted boundary (ADR-0005).
crates\spt-daemon\src\nethost.rs:482:        // ALPN routes to the responder; everything else is the trusted table.
crates\spt-daemon\src\nethost.rs:577:    /// pubkey. Returns the rendered trust outcome, or the classified failure.
crates\spt-daemon\src\nethost.rs:679:    /// `is_trusted(subnet, origin)`; unused by any gate at D2.
crates\spt-daemon\src\pairhost.rs:8://!   member daemon accepts the pre-trust [`SPT_PAIR_ALPN`] beside the trusted
crates\spt-daemon\src\pairhost.rs:20://!   unchanged initiator ceremony, persist trust + the transferred seed.
crates\spt-daemon\src\pairhost.rs:24://! Pairing connections never touch the trusted stream table: the ALPN routes
crates\spt-daemon\src\pairhost.rs:39:use spt_store::trust::TrustStore;
crates\spt-daemon\src\pairhost.rs:53:/// created or joined a second ago is immediately pairable, and a trust edit
crates\spt-daemon\src\pairhost.rs:78:    let mut trust = TrustStore::load();
crates\spt-daemon\src\pairhost.rs:92:            &mut trust,
crates\spt-daemon\src\pairhost.rs:106:            // Re-pair trust overwrite (M8 decision 13, REQ-SUBNET-7): a
crates\spt-daemon\src\pairhost.rs:109:            // key — the superseded identity's trust + registry rows go.
crates\spt-daemon\src\pairhost.rs:114:                    &mut trust,
crates\spt-daemon\src\pairhost.rs:134:            match trust.save() {
crates\spt-daemon\src\pairhost.rs:135:                Ok(()) => eprintln!("PAIRED: joiner pinned ({:?})", outcome.trust),
crates\spt-daemon\src\pairhost.rs:297:/// real endpoint, and persist the results (peer trust-pinned; the
crates\spt-daemon\src\pairhost.rs:307:    let mut trust = TrustStore::load();
crates\spt-daemon\src\pairhost.rs:336:            &mut trust,
crates\spt-daemon\src\pairhost.rs:383:                    &mut trust,
crates\spt-daemon\src\pairhost.rs:407:    trust
crates\spt-daemon\src\pairhost.rs:409:        .map_err(|e| JoinFail::Other(format!("trust save: {e}")))?;
crates\spt-daemon\src\peerloop.rs:4://! One daemon-hosted loop drives three cadenced pumps against every trusted
crates\spt-daemon\src\peerloop.rs:11://!   only — no transitive gossip in v1: trust is pairwise per subnet, and a
crates\spt-daemon\src\peerloop.rs:57:use spt_store::trust::TrustStore;
crates\spt-daemon\src\peerloop.rs:92:/// Resolve a trusted peer's stable name (lowercase-hex pubkey) to a dialable
crates\spt-daemon\src\peerloop.rs:118:    /// `trust/peers.json` ([`TrustStore::load_from`]).
crates\spt-daemon\src\peerloop.rs:119:    pub trust: PathBuf,
crates\spt-daemon\src\peerloop.rs:163:            trust: identity.join("trust").join("peers.json"),
crates\spt-daemon\src\peerloop.rs:324:        // The push target stays the directly-paired trusted set at D5 (the
crates\spt-daemon\src\peerloop.rs:327:        let trust = TrustStore::load_from(&paths.trust);
crates\spt-daemon\src\peerloop.rs:364:            for peer in trust.peers_in(&sub.name) {
crates\spt-daemon\src\peerloop.rs:442:                        if !policy.trusted_keys.is_empty() {
crates\spt-daemon\src\notifsync.rs:15://! - a record whose origin is **not trusted in that subnet's trust store** is
crates\spt-daemon\src\notifsync.rs:44:    /// Load from the node's canonical stores (`subnet.json`, `trust/peers.json`).
crates\spt-daemon\src\notifsync.rs:74:    /// Dropped: the origin is not trusted in the record's subnet.
crates\spt-daemon\src\notifsync.rs:75:    DroppedUntrusted,
crates\spt-daemon\src\notifsync.rs:111:                return Ok(NotifApplyVerdict::DroppedUntrusted);
crates\spt-daemon\src\notifsync.rs:146:    fn trusting(subnets: &[&str], origin: &str) -> NotifPolicy {
crates\spt-daemon\src\notifsync.rs:174:        let policy_b = trusting(&["home"], "nodea-hex");
crates\spt-daemon\src\notifsync.rs:203:        let policy_a = trusting(&["home"], "nodeb-hex");
crates\spt-daemon\src\notifsync.rs:212:    // [unit->REQ-HAZARD-WAN-ORIGIN-AUTH] an untrusted origin's records are
crates\spt-daemon\src\notifsync.rs:213:    // dropped fail-closed with zero rows written — and trust is per-subnet:
crates\spt-daemon\src\notifsync.rs:214:    // a peer trusted in `home` cannot inject into `work` even when this node
crates\spt-daemon\src\notifsync.rs:217:    fn untrusted_and_cross_subnet_origins_are_dropped() {
crates\spt-daemon\src\notifsync.rs:229:        // Wholly untrusted origin: dropped, nothing written.
crates\spt-daemon\src\notifsync.rs:235:        assert_eq!(verdicts, vec![NotifApplyVerdict::DroppedUntrusted]);
crates\spt-daemon\src\notifsync.rs:238:        // Trusted in home only, member of both: the work record still drops.
crates\spt-daemon\src\notifsync.rs:239:        let mut policy = trusting(&["home"], "nodea-hex");
crates\spt-daemon\src\notifsync.rs:244:            vec![NotifApplyVerdict::DroppedUntrusted],
crates\spt-daemon\src\notifsync.rs:245:            "per-subnet trust"
crates\spt-daemon\src\notifsync.rs:251:    // dropped fail-closed — never materialized, whatever the origin's trust.
crates\spt-daemon\src\notifsync.rs:263:        let mut policy = trusting(&["home"], "nodea-hex");
crates\spt-daemon\src\notifsync.rs:286:        let policy_a = trusting(&["home"], "nodeb-hex");
crates\spt-daemon\src\notifsync.rs:287:        let policy_b = trusting(&["home"], "nodea-hex");
crates\spt-daemon\src\psyrelay.rs:7://! strip); this module enforces the other half of the trust boundary at relay
crates\spt-daemon\src\psyrelay.rs:11://!   `from=<psyche_id>` — the daemon is the sole trusted author of wire
crates\spt-daemon\src\propagate.rs:22://! KNOWN-HAZARDS 7.5) is a trusted peer in ≥1 subnet; an untrusted origin is
crates\spt-daemon\src\propagate.rs:35:use spt_store::trust::TrustStore;
crates\spt-daemon\src\propagate.rs:58:    /// The origin is not a trusted peer in any subnet — refused by not
crates\spt-daemon\src\propagate.rs:100:/// (REQ-HAZARD-WAN-ORIGIN-AUTH). The origin is gated against `trust`
crates\spt-daemon\src\propagate.rs:101:/// **here**, fail-closed: an untrusted peer gets the up-to-date shape and
crates\spt-daemon\src\propagate.rs:111:    trust: &TrustStore,
crates\spt-daemon\src\propagate.rs:129:                            // REQ-UPD-6): same trust gate as Query — an
crates\spt-daemon\src\propagate.rs:130:                            // untrusted origin gets the all-None shape and
crates\spt-daemon\src\propagate.rs:133:                            let trusted = trust.peers.iter().any(|p| p.pubkey_hex == origin_node);
crates\spt-daemon\src\propagate.rs:134:                            let status = if trusted {
crates\spt-daemon\src\propagate.rs:157:                            return Ok(if trusted {
crates\spt-daemon\src\propagate.rs:168:                            // The gate: only a peer trusted in ≥1 subnet is
crates\spt-daemon\src\propagate.rs:172:                            let trusted = trust.peers.iter().any(|p| p.pubkey_hex == origin_node);
crates\spt-daemon\src\propagate.rs:173:                            let staged = if trusted { cache.staged_update() } else { None };
crates\spt-daemon\src\propagate.rs:195:                                    return Ok(if trusted {
crates\spt-daemon\src\propagate.rs:525:/// responder refused (untrusted) or predates the status wire.
crates\spt-daemon\src\propagate.rs:702:            "the untrusted all-None shape classifies as unreachable"
crates\spt-daemon\src\release.rs:17://!   a trusted set (rotation = add a key), and a revoked `key_id` is rejected even
crates\spt-daemon\src\release.rs:96:    /// Optional human/package-facing asset name; not used for trust.
crates\spt-daemon\src\release.rs:112:/// fields affect trust; they answer "which bits are running where?"
crates\spt-daemon\src\release.rs:191:/// The trust policy a node verifies a release against. Pure data — no clock, no
crates\spt-daemon\src\release.rs:195:    /// Trusted release keys by `key_id`. Rotation = add a new id; old ids may
crates\spt-daemon\src\release.rs:197:    pub trusted_keys: BTreeMap<String, VerifyingKey>,
crates\spt-daemon\src\release.rs:211:/// The release trust anchor **compiled into every binary** (ADR-0015,
crates\spt-daemon\src\release.rs:236:/// On-disk trusted-release-keys config (`identity/release-keys.json`) — the
crates\spt-daemon\src\release.rs:240:/// trusted set — every offer rejects `UnknownKey`, fail-closed: a node with
crates\spt-daemon\src\release.rs:253:    /// Assemble the production policy: trusted keys + channel pin from
crates\spt-daemon\src\release.rs:269:    /// a typo must not silently widen or narrow the trusted set unnoticed.
crates\spt-daemon\src\release.rs:271:    /// The policy starts from [`BUILTIN_RELEASE_KEYS`] (the ADR-0015 trust
crates\spt-daemon\src\release.rs:293:        let mut trusted_keys = BTreeMap::new();
crates\spt-daemon\src\release.rs:297:                    trusted_keys.insert(key_id.to_string(), vk);
crates\spt-daemon\src\release.rs:306:                    trusted_keys.insert(key_id.clone(), vk);
crates\spt-daemon\src\release.rs:312:            trusted_keys,
crates\spt-daemon\src\release.rs:330:    /// The `key_id` is not in the trusted set.
crates\spt-daemon\src\release.rs:393:/// payload is trusted for a policy decision), then enforce the channel / expiry /
crates\spt-daemon\src\release.rs:408:        .trusted_keys
crates\spt-daemon\src\release.rs:448:        .trusted_keys
crates\spt-daemon\src\release.rs:474:/// returning the authenticated metadata. No trust-set lookup, no
crates\spt-daemon\src\release.rs:476:/// where the trusted key is the per-adapter key declared in the adapter's
crates\spt-daemon\src\release.rs:682:    // [unit->REQ-UPD-2] the production keys file loads: valid keys trusted,
crates\spt-daemon\src\release.rs:693:        assert_eq!(p.trusted_keys.len(), BUILTIN_RELEASE_KEYS.len());
crates\spt-daemon\src\release.rs:696:                p.trusted_keys.contains_key(*key_id),
crates\spt-daemon\src\release.rs:697:                "builtin {key_id} trusted"
crates\spt-daemon\src\release.rs:714:            p.trusted_keys.len(),
crates\spt-daemon\src\release.rs:718:        assert!(p.trusted_keys.contains_key("good"));
crates\spt-daemon\src\release.rs:726:            VerifyPolicy::load_from(&path, 0, 0).trusted_keys.len(),
crates\spt-daemon\src\release.rs:732:        let mut trusted = BTreeMap::new();
crates\spt-daemon\src\release.rs:733:        trusted.insert(KEY_ID.to_string(), sk.verifying_key());
crates\spt-daemon\src\release.rs:735:            trusted_keys: trusted,
crates\spt-daemon\src\release.rs:744:    // from a trusted key verifies, and its bytes match the signed digest.
crates\spt-daemon\src\release.rs:773:    // [unit->REQ-UPD-2] a signature from a key the node does not trust ⇒ UnknownKey.
crates\spt-daemon\src\release.rs:775:    fn untrusted_key_is_unknown() {
crates\spt-daemon\src\release.rs:779:        // Policy trusts only `real` under KEY_ID; the attacker signed under the
crates\spt-daemon\src\release.rs:780:        // same key_id but with a different key → signature won't match the trusted
crates\spt-daemon\src\release.rs:781:        // verifying key. (UnknownKey would fire if the id itself were untrusted.)
crates\spt-daemon\src\release.rs:972:    // ---- M6-D6: the embedded two-key trust anchor (ADR-0015) ----
crates\spt-daemon\src\release.rs:974:    /// Builtin pairs merge under a file overlay: builtin keys trusted with no
crates\spt-daemon\src\release.rs:991:        assert_eq!(pol.trusted_keys.len(), 2);
crates\spt-daemon\src\release.rs:992:        assert!(pol.trusted_keys.contains_key("rel-primary"));
crates\spt-daemon\src\release.rs:993:        assert!(pol.trusted_keys.contains_key("rel-recovery"));
crates\spt-daemon\src\release.rs:1007:            pol.trusted_keys.len(),
crates\spt-daemon\src\release.rs:1024:        // ADR-0015 loss/compromise story — the second already-trusted key
crates\spt-daemon\src\resthost.rs:12://! ## Trust class: remote-drive (ungated beyond the firewall)
crates\spt-daemon\src\shellchan.rs:8://!   bounds what an agent may ask, CONTEXT §binary-trust). The manifest's
crates\spt-daemon\src\shellchan.rs:48:/// (CONTEXT §binary-trust: the toolset bounds what an agent may ask). `op`
crates\spt-daemon\src\serveprobe.rs:23://! ## Trust class
crates\spt-daemon\src\registryhost.rs:12://!   trusted *in that subnet*, subject = handshake-proven stream origin —
crates\spt-daemon\src\registryhost.rs:43:use spt_store::trust::TrustStore;
crates\spt-daemon\src\registryhost.rs:54:    /// Refused: the origin is not trusted in the record's subnet.
crates\spt-daemon\src\registryhost.rs:55:    UntrustedOrigin,
crates\spt-daemon\src\registryhost.rs:237:                    return RegistryApplyVerdict::UntrustedOrigin;
crates\spt-daemon\src\registryhost.rs:265:        // stamp nothing (an untrusted sender earns no liveness credit).
crates\spt-daemon\src\registryhost.rs:279:    /// untrusted origin is refused; an admitted update merges via
crates\spt-daemon\src\registryhost.rs:301:                    return RegistryApplyVerdict::UntrustedOrigin;
crates\spt-daemon\src\registryhost.rs:327:    /// v1: trust is pairwise per subnet, a relayed third-party row would
crates\spt-daemon\src\registryhost.rs:451:        // exist (the render prefers a trust/instance label anyway), the SOLE
crates\spt-daemon\src\registryhost.rs:654:/// identities from the subnet's snapshot, drop their trust rows (this
crates\spt-daemon\src\registryhost.rs:658:/// tick. The caller persists `trust`.
crates\spt-daemon\src\registryhost.rs:661:    trust: &mut TrustStore,
crates\spt-daemon\src\registryhost.rs:679:        let dropped = trust.revoke(subnet, hex);
crates\spt-daemon\src\registryhost.rs:681:            "REPAIR_SUPERSEDED:{} (label '{}' re-paired as {}…; trust row {})",
crates\spt-daemon\src\registryhost.rs:784:    /// `TrustStore::record` pin; `is_member` keys on `(subnet, pubkey)`).
crates\spt-daemon\src\registryhost.rs:797:    fn policy(subnet: &str, trusted: &str) -> RegistryGatePolicy {
crates\spt-daemon\src\registryhost.rs:799:        roster.merge_entry(member(subnet, trusted));
crates\spt-daemon\src\registryhost.rs:886:    // superseded identity's trust row (this subnet only), rewrites the
crates\spt-daemon\src\registryhost.rs:890:    fn repair_evict_drops_trust_snapshot_and_memory() {
crates\spt-daemon\src\registryhost.rs:912:        let mut trust = TrustStore::default();
crates\spt-daemon\src\registryhost.rs:913:        trust.record("home", "old-key", None, None);
crates\spt-daemon\src\registryhost.rs:914:        trust.record("worknet", "old-key", None, None);
crates\spt-daemon\src\registryhost.rs:919:        repair_evict_superseded(&mut trust, "home", "new-key", &intro, &snap_dir);
crates\spt-daemon\src\registryhost.rs:922:            !trust.is_trusted("home", "old-key"),
crates\spt-daemon\src\registryhost.rs:923:            "ceremony subnet's trust row dropped"
crates\spt-daemon\src\registryhost.rs:926:            trust.is_trusted("worknet", "old-key"),
crates\spt-daemon\src\registryhost.rs:949:        let mut t2 = TrustStore::default();
crates\spt-daemon\src\registryhost.rs:956:        assert!(t2.is_trusted("home", "unrelated"));
crates\spt-daemon\src\registryhost.rs:961:    // and untrusted origin write nothing; an admitted row merges and the
crates\spt-daemon\src\registryhost.rs:972:        assert_eq!(v, vec![RegistryApplyVerdict::UntrustedOrigin]);
crates\spt-daemon\src\registryhost.rs:1182:    // fail-closed gate as instance feeds: a non-member subnet and an untrusted
crates\spt-daemon\src\registryhost.rs:1190:        let p = policy("home", "bb22"); // member of "home", trusts bb22 there
crates\spt-daemon\src\registryhost.rs:1204:        // Untrusted origin in a member subnet: refused.
crates\spt-daemon\src\registryhost.rs:1207:            vec![RegistryApplyVerdict::UntrustedOrigin]
crates\spt-daemon\src\registryhost.rs:1364:        // Refused (untrusted origin): no stamp, no mirror.
crates\spt-daemon\src\sync.rs:21://!   trust store** AND `synced(id, S)` holds (membership list + visibility —
crates\spt-daemon\src\sync.rs:24://! - `p-<project>` is served only to an origin trusted in at least one subnet,
crates\spt-daemon\src\sync.rs:79:    /// Load from the node's canonical stores (`subnet.json`, `trust/peers.json`,
crates\spt-daemon\src\sync.rs:532:    // only when the origin is a trusted peer in a subnet where synced(E,S)
crates\spt-daemon\src\sync.rs:534:    // and untrusted origins all refuse; p- refs need a trusted origin;
crates\spt-daemon\src\sync.rs:540:        // Unconfigured sync list: nowhere (fail-closed), even for a trusted peer.
crates\spt-daemon\src\sync.rs:549:            "listed + visible + trusted origin"
crates\spt-daemon\src\sync.rs:551:        assert!(!p.allows("a-doyle", "stranger"), "untrusted origin refused");
crates\spt-daemon\src\sync.rs:558:        // Membership on the wrong subnet doesn't leak through another's trust.
crates\spt-daemon\src\sync.rs:562:            "peer trusted in home, momo synced to work only"
crates\spt-daemon\src\sync.rs:565:        // p- tier: trusted origin suffices; strangers refused.
crates\spt-daemon\src\sync.rs:598:            "untrusted peer gets nothing"
crates\spt-daemon\src\shellwake.rs:360:/// trust class — the target's access gate decides). One dial, one request,
crates\spt-daemon\src\update.rs:150:/// [`verify_metadata`] (signature + key trust/revocation + channel + expiry +
crates\spt-daemon\src\update.rs:377:        let mut trusted = BTreeMap::new();
crates\spt-daemon\src\update.rs:378:        trusted.insert("k".to_string(), sk.verifying_key());
crates\spt-daemon\src\update.rs:380:            trusted_keys: trusted,
crates\spt-daemon\src\shellhost.rs:14://! least-trusted code in the system (the shell binary) touches SPT. The


1	crates\spt-daemon\src\broker.rs:632:    /// trust row + seed), and the TOTP code in the request must never land
2	crates\spt-daemon\src\config.rs:36:/// registry/notif feeds to trusted peers and pulls context syncs from them.
3	crates\spt-daemon\src\config.rs:137:    /// Update check cadence (D9-1; the pull rides the same trusted conns).
4	crates\spt-daemon\src\applyhost.rs:16://!    `release-keys.json` policy): apply never trusts the cache — a tampered
5	crates\spt-daemon\src\applyhost.rs:58:    /// The at-rest re-verification refused (tamper / expiry / key trust) —
6	crates\spt-daemon\src\applyhost.rs:89:    if policy.trusted_keys.is_empty() {
7	crates\spt-daemon\src\applyhost.rs:91:            "release-keys.json unprovisioned (no trusted keys)".to_string(),
8	crates\spt-daemon\src\adapter_update.rs:18://!   nothing for it to hash. It delegates the trust **only** when the adapter
9	crates\spt-daemon\src\adapter_update.rs:54:    /// unverifiable by spt-core — skipped rather than trusted blindly.
10	crates\spt-daemon\src\adapter_update.rs:59:    /// manifest validation, but the conductor never trusts an unverifiable pull).
11	crates\spt-daemon\src\adapter_update.rs:71:    /// `delegated` + attested: run the adapter's own updater (the trusted command).
12	crates\spt-daemon\src\adapter_update.rs:286:    // delegated to its own updater (the trusted command).
13	crates\spt-daemon\src\adapter_update.rs:297:    // as unverifiable — spt-core never blindly trusts an opaque updater.
14	crates\spt-daemon\src\access.rs:32://! The store is [`spt_store::access::AccessStore`] (`trust/access.json` —
15	crates\spt-daemon\src\access.rs:33://! security material next to the trust store). The gate re-reads it per check:
16	crates\spt-daemon\src\dispatch.rs:73:use spt_store::trust::TrustStore;
17	crates\spt-daemon\src\dispatch.rs:326:            let trust = TrustStore::load();
18	crates\spt-daemon\src\dispatch.rs:327:            match serve_update(&mut brain, stream_id, origin, 0, &cache, &trust) {
19	crates\spt-daemon\src\dispatch.rs:425:/// origin trusted in that subnet), until the peer finishes.
20	crates\spt-daemon\src\grants.rs:96:/// is this node's own `trust/grants.json`; a remotely-authored grant only
21	crates\spt-daemon\src\lib.rs:54://! trust/revocation, channel pinning, metadata expiry, monotonic version, and a
22	crates\spt-daemon\src\lib.rs:64://! spt-core delegates the trust only when the adapter attests `self_verifies`,
23	crates\spt-daemon\src\linkhost.rs:12://! ## Trust class: remote-drive (ungated beyond the firewall)
24	crates\spt-daemon\src\nethost.rs:96:    /// None`). The D5 gate swap reads this in place of `is_trusted(subnet, …)`;
25	crates\spt-daemon\src\nethost.rs:97:    /// at D2 it is cached but not yet consumed (the `is_trusted` gates still run
26	crates\spt-daemon\src\nethost.rs:440:    /// trusted [`SPT_NET_ALPN`] (connections join the table as before) and
27	crates\spt-daemon\src\nethost.rs:441:    /// the pre-trust pairing ALPN — routed to the always-on responder
28	crates\spt-daemon\src\nethost.rs:445:    /// per key — the ALPN is the pre-trust/trusted boundary (ADR-0005).
29	crates\spt-daemon\src\nethost.rs:482:        // ALPN routes to the responder; everything else is the trusted table.
30	crates\spt-daemon\src\nethost.rs:577:    /// pubkey. Returns the rendered trust outcome, or the classified failure.
31	crates\spt-daemon\src\nethost.rs:679:    /// `is_trusted(subnet, origin)`; unused by any gate at D2.
32	crates\spt-daemon\src\pairhost.rs:8://!   member daemon accepts the pre-trust [`SPT_PAIR_ALPN`] beside the trusted
33	crates\spt-daemon\src\pairhost.rs:20://!   unchanged initiator ceremony, persist trust + the transferred seed.
34	crates\spt-daemon\src\pairhost.rs:24://! Pairing connections never touch the trusted stream table: the ALPN routes
35	crates\spt-daemon\src\pairhost.rs:39:use spt_store::trust::TrustStore;
36	crates\spt-daemon\src\pairhost.rs:53:/// created or joined a second ago is immediately pairable, and a trust edit
37	crates\spt-daemon\src\pairhost.rs:78:    let mut trust = TrustStore::load();
38	crates\spt-daemon\src\pairhost.rs:92:            &mut trust,
39	crates\spt-daemon\src\pairhost.rs:106:            // Re-pair trust overwrite (M8 decision 13, REQ-SUBNET-7): a
40	crates\spt-daemon\src\pairhost.rs:109:            // key — the superseded identity's trust + registry rows go.
41	crates\spt-daemon\src\pairhost.rs:114:                    &mut trust,
42	crates\spt-daemon\src\pairhost.rs:134:            match trust.save() {
43	crates\spt-daemon\src\pairhost.rs:135:                Ok(()) => eprintln!("PAIRED: joiner pinned ({:?})", outcome.trust),
44	crates\spt-daemon\src\pairhost.rs:297:/// real endpoint, and persist the results (peer trust-pinned; the
45	crates\spt-daemon\src\pairhost.rs:307:    let mut trust = TrustStore::load();
46	crates\spt-daemon\src\pairhost.rs:336:            &mut trust,
47	crates\spt-daemon\src\pairhost.rs:383:                    &mut trust,
48	crates\spt-daemon\src\pairhost.rs:407:    trust
49	crates\spt-daemon\src\pairhost.rs:409:        .map_err(|e| JoinFail::Other(format!("trust save: {e}")))?;
50	crates\spt-daemon\src\peerloop.rs:4://! One daemon-hosted loop drives three cadenced pumps against every trusted
51	crates\spt-daemon\src\peerloop.rs:11://!   only — no transitive gossip in v1: trust is pairwise per subnet, and a
52	crates\spt-daemon\src\peerloop.rs:57:use spt_store::trust::TrustStore;
53	crates\spt-daemon\src\peerloop.rs:92:/// Resolve a trusted peer's stable name (lowercase-hex pubkey) to a dialable
54	crates\spt-daemon\src\peerloop.rs:118:    /// `trust/peers.json` ([`TrustStore::load_from`]).
55	crates\spt-daemon\src\peerloop.rs:119:    pub trust: PathBuf,
56	crates\spt-daemon\src\peerloop.rs:163:            trust: identity.join("trust").join("peers.json"),
57	crates\spt-daemon\src\peerloop.rs:324:        // The push target stays the directly-paired trusted set at D5 (the
58	crates\spt-daemon\src\peerloop.rs:327:        let trust = TrustStore::load_from(&paths.trust);
59	crates\spt-daemon\src\peerloop.rs:364:            for peer in trust.peers_in(&sub.name) {
60	crates\spt-daemon\src\peerloop.rs:442:                        if !policy.trusted_keys.is_empty() {
61	crates\spt-daemon\src\notifsync.rs:15://! - a record whose origin is **not trusted in that subnet's trust store** is
62	crates\spt-daemon\src\notifsync.rs:44:    /// Load from the node's canonical stores (`subnet.json`, `trust/peers.json`).
63	crates\spt-daemon\src\notifsync.rs:74:    /// Dropped: the origin is not trusted in the record's subnet.
64	crates\spt-daemon\src\notifsync.rs:75:    DroppedUntrusted,
65	crates\spt-daemon\src\notifsync.rs:111:                return Ok(NotifApplyVerdict::DroppedUntrusted);
66	crates\spt-daemon\src\notifsync.rs:146:    fn trusting(subnets: &[&str], origin: &str) -> NotifPolicy {
67	crates\spt-daemon\src\notifsync.rs:174:        let policy_b = trusting(&["home"], "nodea-hex");
68	crates\spt-daemon\src\notifsync.rs:203:        let policy_a = trusting(&["home"], "nodeb-hex");
69	crates\spt-daemon\src\notifsync.rs:212:    // [unit->REQ-HAZARD-WAN-ORIGIN-AUTH] an untrusted origin's records are
70	crates\spt-daemon\src\notifsync.rs:213:    // dropped fail-closed with zero rows written — and trust is per-subnet:
71	crates\spt-daemon\src\notifsync.rs:214:    // a peer trusted in `home` cannot inject into `work` even when this node
72	crates\spt-daemon\src\notifsync.rs:217:    fn untrusted_and_cross_subnet_origins_are_dropped() {
73	crates\spt-daemon\src\notifsync.rs:229:        // Wholly untrusted origin: dropped, nothing written.
74	crates\spt-daemon\src\notifsync.rs:235:        assert_eq!(verdicts, vec![NotifApplyVerdict::DroppedUntrusted]);
75	crates\spt-daemon\src\notifsync.rs:238:        // Trusted in home only, member of both: the work record still drops.
76	crates\spt-daemon\src\notifsync.rs:239:        let mut policy = trusting(&["home"], "nodea-hex");
77	crates\spt-daemon\src\notifsync.rs:244:            vec![NotifApplyVerdict::DroppedUntrusted],
78	crates\spt-daemon\src\notifsync.rs:245:            "per-subnet trust"
79	crates\spt-daemon\src\notifsync.rs:251:    // dropped fail-closed — never materialized, whatever the origin's trust.
80	crates\spt-daemon\src\notifsync.rs:263:        let mut policy = trusting(&["home"], "nodea-hex");
81	crates\spt-daemon\src\notifsync.rs:286:        let policy_a = trusting(&["home"], "nodeb-hex");
82	crates\spt-daemon\src\notifsync.rs:287:        let policy_b = trusting(&["home"], "nodea-hex");
83	crates\spt-daemon\src\psyrelay.rs:7://! strip); this module enforces the other half of the trust boundary at relay
84	crates\spt-daemon\src\psyrelay.rs:11://!   `from=<psyche_id>` — the daemon is the sole trusted author of wire
85	crates\spt-daemon\src\propagate.rs:22://! KNOWN-HAZARDS 7.5) is a trusted peer in ≥1 subnet; an untrusted origin is
86	crates\spt-daemon\src\propagate.rs:35:use spt_store::trust::TrustStore;
87	crates\spt-daemon\src\propagate.rs:58:    /// The origin is not a trusted peer in any subnet — refused by not
88	crates\spt-daemon\src\propagate.rs:100:/// (REQ-HAZARD-WAN-ORIGIN-AUTH). The origin is gated against `trust`
89	crates\spt-daemon\src\propagate.rs:101:/// **here**, fail-closed: an untrusted peer gets the up-to-date shape and
90	crates\spt-daemon\src\propagate.rs:111:    trust: &TrustStore,
91	crates\spt-daemon\src\propagate.rs:129:                            // REQ-UPD-6): same trust gate as Query — an
92	crates\spt-daemon\src\propagate.rs:130:                            // untrusted origin gets the all-None shape and
93	crates\spt-daemon\src\propagate.rs:133:                            let trusted = trust.peers.iter().any(|p| p.pubkey_hex == origin_node);
94	crates\spt-daemon\src\propagate.rs:134:                            let status = if trusted {
95	crates\spt-daemon\src\propagate.rs:157:                            return Ok(if trusted {
96	crates\spt-daemon\src\propagate.rs:168:                            // The gate: only a peer trusted in ≥1 subnet is
97	crates\spt-daemon\src\propagate.rs:172:                            let trusted = trust.peers.iter().any(|p| p.pubkey_hex == origin_node);
98	crates\spt-daemon\src\propagate.rs:173:                            let staged = if trusted { cache.staged_update() } else { None };
99	crates\spt-daemon\src\propagate.rs:195:                                    return Ok(if trusted {
100	crates\spt-daemon\src\propagate.rs:525:/// responder refused (untrusted) or predates the status wire.
101	crates\spt-daemon\src\propagate.rs:702:            "the untrusted all-None shape classifies as unreachable"
102	crates\spt-daemon\src\release.rs:17://!   a trusted set (rotation = add a key), and a revoked `key_id` is rejected even
103	crates\spt-daemon\src\release.rs:96:    /// Optional human/package-facing asset name; not used for trust.
104	crates\spt-daemon\src\release.rs:112:/// fields affect trust; they answer "which bits are running where?"
105	crates\spt-daemon\src\release.rs:191:/// The trust policy a node verifies a release against. Pure data — no clock, no
106	crates\spt-daemon\src\release.rs:195:    /// Trusted release keys by `key_id`. Rotation = add a new id; old ids may
107	crates\spt-daemon\src\release.rs:197:    pub trusted_keys: BTreeMap<String, VerifyingKey>,
108	crates\spt-daemon\src\release.rs:211:/// The release trust anchor **compiled into every binary** (ADR-0015,
109	crates\spt-daemon\src\release.rs:236:/// On-disk trusted-release-keys config (`identity/release-keys.json`) — the
110	crates\spt-daemon\src\release.rs:240:/// trusted set — every offer rejects `UnknownKey`, fail-closed: a node with
111	crates\spt-daemon\src\release.rs:253:    /// Assemble the production policy: trusted keys + channel pin from
112	crates\spt-daemon\src\release.rs:269:    /// a typo must not silently widen or narrow the trusted set unnoticed.
113	crates\spt-daemon\src\release.rs:271:    /// The policy starts from [`BUILTIN_RELEASE_KEYS`] (the ADR-0015 trust
114	crates\spt-daemon\src\release.rs:293:        let mut trusted_keys = BTreeMap::new();
115	crates\spt-daemon\src\release.rs:297:                    trusted_keys.insert(key_id.to_string(), vk);
116	crates\spt-daemon\src\release.rs:306:                    trusted_keys.insert(key_id.clone(), vk);
117	crates\spt-daemon\src\release.rs:312:            trusted_keys,
118	crates\spt-daemon\src\release.rs:330:    /// The `key_id` is not in the trusted set.
119	crates\spt-daemon\src\release.rs:393:/// payload is trusted for a policy decision), then enforce the channel / expiry /
120	crates\spt-daemon\src\release.rs:408:        .trusted_keys
121	crates\spt-daemon\src\release.rs:448:        .trusted_keys
122	crates\spt-daemon\src\release.rs:474:/// returning the authenticated metadata. No trust-set lookup, no
123	crates\spt-daemon\src\release.rs:476:/// where the trusted key is the per-adapter key declared in the adapter's
124	crates\spt-daemon\src\release.rs:682:    // [unit->REQ-UPD-2] the production keys file loads: valid keys trusted,
125	crates\spt-daemon\src\release.rs:693:        assert_eq!(p.trusted_keys.len(), BUILTIN_RELEASE_KEYS.len());
126	crates\spt-daemon\src\release.rs:696:                p.trusted_keys.contains_key(*key_id),
127	crates\spt-daemon\src\release.rs:697:                "builtin {key_id} trusted"
128	crates\spt-daemon\src\release.rs:714:            p.trusted_keys.len(),
129	crates\spt-daemon\src\release.rs:718:        assert!(p.trusted_keys.contains_key("good"));
130	crates\spt-daemon\src\release.rs:726:            VerifyPolicy::load_from(&path, 0, 0).trusted_keys.len(),
131	crates\spt-daemon\src\release.rs:732:        let mut trusted = BTreeMap::new();
132	crates\spt-daemon\src\release.rs:733:        trusted.insert(KEY_ID.to_string(), sk.verifying_key());
133	crates\spt-daemon\src\release.rs:735:            trusted_keys: trusted,
134	crates\spt-daemon\src\release.rs:744:    // from a trusted key verifies, and its bytes match the signed digest.
135	crates\spt-daemon\src\release.rs:773:    // [unit->REQ-UPD-2] a signature from a key the node does not trust ⇒ UnknownKey.
136	crates\spt-daemon\src\release.rs:775:    fn untrusted_key_is_unknown() {
137	crates\spt-daemon\src\release.rs:779:        // Policy trusts only `real` under KEY_ID; the attacker signed under the
138	crates\spt-daemon\src\release.rs:780:        // same key_id but with a different key → signature won't match the trusted
139	crates\spt-daemon\src\release.rs:781:        // verifying key. (UnknownKey would fire if the id itself were untrusted.)
140	crates\spt-daemon\src\release.rs:972:    // ---- M6-D6: the embedded two-key trust anchor (ADR-0015) ----
141	crates\spt-daemon\src\release.rs:974:    /// Builtin pairs merge under a file overlay: builtin keys trusted with no
142	crates\spt-daemon\src\release.rs:991:        assert_eq!(pol.trusted_keys.len(), 2);
143	crates\spt-daemon\src\release.rs:992:        assert!(pol.trusted_keys.contains_key("rel-primary"));
144	crates\spt-daemon\src\release.rs:993:        assert!(pol.trusted_keys.contains_key("rel-recovery"));
145	crates\spt-daemon\src\release.rs:1007:            pol.trusted_keys.len(),
146	crates\spt-daemon\src\release.rs:1024:        // ADR-0015 loss/compromise story — the second already-trusted key
147	crates\spt-daemon\src\resthost.rs:12://! ## Trust class: remote-drive (ungated beyond the firewall)
148	crates\spt-daemon\src\shellchan.rs:8://!   bounds what an agent may ask, CONTEXT §binary-trust). The manifest's
149	crates\spt-daemon\src\shellchan.rs:48:/// (CONTEXT §binary-trust: the toolset bounds what an agent may ask). `op`
150	crates\spt-daemon\src\serveprobe.rs:23://! ## Trust class
151	crates\spt-daemon\src\registryhost.rs:12://!   trusted *in that subnet*, subject = handshake-proven stream origin —
152	crates\spt-daemon\src\registryhost.rs:43:use spt_store::trust::TrustStore;
153	crates\spt-daemon\src\registryhost.rs:54:    /// Refused: the origin is not trusted in the record's subnet.
154	crates\spt-daemon\src\registryhost.rs:55:    UntrustedOrigin,
155	crates\spt-daemon\src\registryhost.rs:237:                    return RegistryApplyVerdict::UntrustedOrigin;
156	crates\spt-daemon\src\registryhost.rs:265:        // stamp nothing (an untrusted sender earns no liveness credit).
157	crates\spt-daemon\src\registryhost.rs:279:    /// untrusted origin is refused; an admitted update merges via
158	crates\spt-daemon\src\registryhost.rs:301:                    return RegistryApplyVerdict::UntrustedOrigin;
159	crates\spt-daemon\src\registryhost.rs:327:    /// v1: trust is pairwise per subnet, a relayed third-party row would
160	crates\spt-daemon\src\registryhost.rs:451:        // exist (the render prefers a trust/instance label anyway), the SOLE
161	crates\spt-daemon\src\registryhost.rs:654:/// identities from the subnet's snapshot, drop their trust rows (this
162	crates\spt-daemon\src\registryhost.rs:658:/// tick. The caller persists `trust`.
163	crates\spt-daemon\src\registryhost.rs:661:    trust: &mut TrustStore,
164	crates\spt-daemon\src\registryhost.rs:679:        let dropped = trust.revoke(subnet, hex);
165	crates\spt-daemon\src\registryhost.rs:681:            "REPAIR_SUPERSEDED:{} (label '{}' re-paired as {}…; trust row {})",
166	crates\spt-daemon\src\registryhost.rs:784:    /// `TrustStore::record` pin; `is_member` keys on `(subnet, pubkey)`).
167	crates\spt-daemon\src\registryhost.rs:797:    fn policy(subnet: &str, trusted: &str) -> RegistryGatePolicy {
168	crates\spt-daemon\src\registryhost.rs:799:        roster.merge_entry(member(subnet, trusted));
169	crates\spt-daemon\src\registryhost.rs:886:    // superseded identity's trust row (this subnet only), rewrites the
170	crates\spt-daemon\src\registryhost.rs:890:    fn repair_evict_drops_trust_snapshot_and_memory() {
171	crates\spt-daemon\src\registryhost.rs:912:        let mut trust = TrustStore::default();
172	crates\spt-daemon\src\registryhost.rs:913:        trust.record("home", "old-key", None, None);
173	crates\spt-daemon\src\registryhost.rs:914:        trust.record("worknet", "old-key", None, None);
174	crates\spt-daemon\src\registryhost.rs:919:        repair_evict_superseded(&mut trust, "home", "new-key", &intro, &snap_dir);
175	crates\spt-daemon\src\registryhost.rs:922:            !trust.is_trusted("home", "old-key"),
176	crates\spt-daemon\src\registryhost.rs:923:            "ceremony subnet's trust row dropped"
177	crates\spt-daemon\src\registryhost.rs:926:            trust.is_trusted("worknet", "old-key"),
178	crates\spt-daemon\src\registryhost.rs:949:        let mut t2 = TrustStore::default();
179	crates\spt-daemon\src\registryhost.rs:956:        assert!(t2.is_trusted("home", "unrelated"));
180	crates\spt-daemon\src\registryhost.rs:961:    // and untrusted origin write nothing; an admitted row merges and the
181	crates\spt-daemon\src\registryhost.rs:972:        assert_eq!(v, vec![RegistryApplyVerdict::UntrustedOrigin]);
182	crates\spt-daemon\src\registryhost.rs:1182:    // fail-closed gate as instance feeds: a non-member subnet and an untrusted
183	crates\spt-daemon\src\registryhost.rs:1190:        let p = policy("home", "bb22"); // member of "home", trusts bb22 there
184	crates\spt-daemon\src\registryhost.rs:1204:        // Untrusted origin in a member subnet: refused.
185	crates\spt-daemon\src\registryhost.rs:1207:            vec![RegistryApplyVerdict::UntrustedOrigin]
186	crates\spt-daemon\src\registryhost.rs:1364:        // Refused (untrusted origin): no stamp, no mirror.
187	crates\spt-daemon\src\sync.rs:21://!   trust store** AND `synced(id, S)` holds (membership list + visibility —
188	crates\spt-daemon\src\sync.rs:24://! - `p-<project>` is served only to an origin trusted in at least one subnet,
189	crates\spt-daemon\src\sync.rs:79:    /// Load from the node's canonical stores (`subnet.json`, `trust/peers.json`,
190	crates\spt-daemon\src\sync.rs:532:    // only when the origin is a trusted peer in a subnet where synced(E,S)
191	crates\spt-daemon\src\sync.rs:534:    // and untrusted origins all refuse; p- refs need a trusted origin;
192	crates\spt-daemon\src\sync.rs:540:        // Unconfigured sync list: nowhere (fail-closed), even for a trusted peer.
193	crates\spt-daemon\src\sync.rs:549:            "listed + visible + trusted origin"
194	crates\spt-daemon\src\sync.rs:551:        assert!(!p.allows("a-doyle", "stranger"), "untrusted origin refused");
195	crates\spt-daemon\src\sync.rs:558:        // Membership on the wrong subnet doesn't leak through another's trust.
196	crates\spt-daemon\src\sync.rs:562:            "peer trusted in home, momo synced to work only"
197	crates\spt-daemon\src\sync.rs:565:        // p- tier: trusted origin suffices; strangers refused.
198	crates\spt-daemon\src\sync.rs:598:            "untrusted peer gets nothing"
199	crates\spt-daemon\src\shellwake.rs:360:/// trust class — the target's access gate decides). One dial, one request,
200	crates\spt-daemon\src\update.rs:150:/// [`verify_metadata`] (signature + key trust/revocation + channel + expiry +
201	crates\spt-daemon\src\update.rs:377:        let mut trusted = BTreeMap::new();
202	crates\spt-daemon\src\update.rs:378:        trusted.insert("k".to_string(), sk.verifying_key());
203	crates\spt-daemon\src\update.rs:380:            trusted_keys: trusted,
204	crates\spt-daemon\src\shellhost.rs:14://! least-trusted code in the system (the shell binary) touches SPT. The


peerloop is the tension point — it reads `trust.peers_in` for the push target, but the plan says keep push narrow until D6 yet delete TrustStore in D5b. Reading peerloop's push logic.


100	pub fn production_resolver() -> PeerResolver {
101	    Box::new(|hex| addr_for_node_hex(hex).and_then(|a| serde_json::to_value(a).ok()))
102	}
103
104	/// Where this node's pump-side stores live. Production = the canonical
105	/// SPT_HOME layout; the loopback E2E gives its in-process "node" distinct
106	/// roots so two nodes can share one test process.
107	pub struct PumpPaths {
108	    /// The perch tree (registry advertisement scan).
109	    pub owlery: PathBuf,
110	    /// The context store root ([`ContextStore::open_or_init_in`]).
111	    pub ctx_root: PathBuf,
112	    /// The notif spool DB ([`NotifStore::open_at`]).
113	    pub notif_db: PathBuf,
114	    /// Transient pull scratch.
115	    pub scratch: PathBuf,
116	    /// `subnet.json` ([`SubnetStore::load_from`]).
117	    pub subnets: PathBuf,
118	    /// `trust/peers.json` ([`TrustStore::load_from`]).
119	    pub trust: PathBuf,
120	    /// `visibility.json` ([`VisibilityStore::load_from`]).
121	    pub vis: PathBuf,
122	    /// The pump's durable open-op counter (its own file — colliding with the
123	    /// registry's advertisement epochs would break neither, but a shared
124	    /// in-memory cursor pair over one file double-mints; separate files keep
125	    /// each `EpochSource` the sole writer of its counter).
126	    pub ops: PathBuf,
127	    /// The staged-release cache dir ([`ReleaseCache::open`]).
128	    pub releases: PathBuf,
129	    /// `release-keys.json` ([`VerifyPolicy::load_from`]).
130	    pub release_keys: PathBuf,
131	    /// The wake-edge freshness-pull marker (D9-2, REQ-INST-3): a
132	    /// dormant→active transition drops it; the pump consumes it and forces a
133	    /// sync round NOW instead of waiting out `cadence.sync`
134	    /// ([`crate::resting::PULL_MARKER_FILE`]).
135	    pub pull_marker: PathBuf,
136	    /// The event-driven advertisement marker (M8 decision 15, REQ-CONV-2):
137	    /// an endpoint online/offline transition drops it; the pump consumes it
138	    /// and forces a registry round NOW instead of waiting out
139	    /// `cadence.registry` ([`crate::registryhost::ADVERTISE_MARKER_FILE`]).
140	    pub advertise_marker: PathBuf,
141	    /// `peer-addrs.json` — last-known peer addresses (M8 decisions 14/20,
142	    /// REQ-CONV-1): consulted before id-only discovery, written back after
143	    /// every successful dial ([`spt_store::peeraddrs::PeerAddrStore`]).
144	    pub peer_addrs: PathBuf,
145	    /// The pump's last-tick heartbeat (M8 decision 23, REQ-DAEMON-5):
146	    /// epoch-ms written while the loop is alive, rendered by
147	    /// `spt daemon status` / the `subnet status` stall warning
148	    /// ([`crate::endpoint::pump_heartbeat_path`]).
149	    pub heartbeat: PathBuf,
150	}
151
152	impl PumpPaths {
153	    /// The canonical SPT_HOME layout.
154	    pub fn production() -> PumpPaths {
155	        let home = perch::spt_home();
156	        let identity = perch::identity_dir();
157	        PumpPaths {
158	            owlery: perch::owlery_dir(),
159	            ctx_root: home.join("context"),
160	            notif_db: identity.join("notifs.db"),
161	            scratch: home.join("scratch").join("pump"),
162	            subnets: identity.join("subnet.json"),
163	            trust: identity.join("trust").join("peers.json"),
164	            vis: identity.join("visibility.json"),
165	            ops: identity.join("pump-ops.json"),
166	            releases: home.join("releases"),
167	            release_keys: identity.join("release-keys.json"),
168	            pull_marker: identity.join(crate::resting::PULL_MARKER_FILE),
169	            advertise_marker: identity.join(crate::registryhost::ADVERTISE_MARKER_FILE),
170	            peer_addrs: identity.join("peer-addrs.json"),
171	            heartbeat: crate::endpoint::pump_heartbeat_path(),
172	        }
173	    }
174	}
175
176	/// Pump cadences (config-driven in production: [`DaemonConfig`]).
177	#[derive(Debug, Clone, Copy)]
178	pub struct PumpCadence {
179	    pub registry: Duration,

pub fn production_resolver() -> PeerResolver {
    Box::new(|hex| addr_for_node_hex(hex).and_then(|a| serde_json::to_value(a).ok()))
}

/// Where this node's pump-side stores live. Production = the canonical
/// SPT_HOME layout; the loopback E2E gives its in-process "node" distinct
/// roots so two nodes can share one test process.
pub struct PumpPaths {
    /// The perch tree (registry advertisement scan).
    pub owlery: PathBuf,
    /// The context store root ([`ContextStore::open_or_init_in`]).
    pub ctx_root: PathBuf,
    /// The notif spool DB ([`NotifStore::open_at`]).
    pub notif_db: PathBuf,
    /// Transient pull scratch.
    pub scratch: PathBuf,
    /// `subnet.json` ([`SubnetStore::load_from`]).
    pub subnets: PathBuf,
    /// `trust/peers.json` ([`TrustStore::load_from`]).
    pub trust: PathBuf,
    /// `visibility.json` ([`VisibilityStore::load_from`]).
    pub vis: PathBuf,
    /// The pump's durable open-op counter (its own file — colliding with the
    /// registry's advertisement epochs would break neither, but a shared
    /// in-memory cursor pair over one file double-mints; separate files keep
    /// each `EpochSource` the sole writer of its counter).
    pub ops: PathBuf,
    /// The staged-release cache dir ([`ReleaseCache::open`]).
    pub releases: PathBuf,
    /// `release-keys.json` ([`VerifyPolicy::load_from`]).
    pub release_keys: PathBuf,
    /// The wake-edge freshness-pull marker (D9-2, REQ-INST-3): a
    /// dormant→active transition drops it; the pump consumes it and forces a
    /// sync round NOW instead of waiting out `cadence.sync`
    /// ([`crate::resting::PULL_MARKER_FILE`]).
    pub pull_marker: PathBuf,
    /// The event-driven advertisement marker (M8 decision 15, REQ-CONV-2):
    /// an endpoint online/offline transition drops it; the pump consumes it
    /// and forces a registry round NOW instead of waiting out
    /// `cadence.registry` ([`crate::registryhost::ADVERTISE_MARKER_FILE`]).
    pub advertise_marker: PathBuf,
    /// `peer-addrs.json` — last-known peer addresses (M8 decisions 14/20,
    /// REQ-CONV-1): consulted before id-only discovery, written back after
    /// every successful dial ([`spt_store::peeraddrs::PeerAddrStore`]).
    pub peer_addrs: PathBuf,
    /// The pump's last-tick heartbeat (M8 decision 23, REQ-DAEMON-5):
    /// epoch-ms written while the loop is alive, rendered by
    /// `spt daemon status` / the `subnet status` stall warning
    /// ([`crate::endpoint::pump_heartbeat_path`]).
    pub heartbeat: PathBuf,
}

impl PumpPaths {
    /// The canonical SPT_HOME layout.
    pub fn production() -> PumpPaths {
        let home = perch::spt_home();
        let identity = perch::identity_dir();
        PumpPaths {
            owlery: perch::owlery_dir(),
            ctx_root: home.join("context"),
            notif_db: identity.join("notifs.db"),
            scratch: home.join("scratch").join("pump"),
            subnets: identity.join("subnet.json"),
            trust: identity.join("trust").join("peers.json"),
            vis: identity.join("visibility.json"),
            ops: identity.join("pump-ops.json"),
            releases: home.join("releases"),
            release_keys: identity.join("release-keys.json"),
            pull_marker: identity.join(crate::resting::PULL_MARKER_FILE),
            advertise_marker: identity.join(crate::registryhost::ADVERTISE_MARKER_FILE),
            peer_addrs: identity.join("peer-addrs.json"),
            heartbeat: crate::endpoint::pump_heartbeat_path(),
        }
    }
}

/// Pump cadences (config-driven in production: [`DaemonConfig`]).
#[derive(Debug, Clone, Copy)]
pub struct PumpCadence {
    pub registry: Duration,


315	        // config-ish store, so a flip takes effect within one tick.
316	        // [impl->REQ-SUBNET-5]
317	        let attachment = spt_store::attachment::AttachmentStore::load_from(
318	            &paths
319	                .subnets
320	                .parent()
321	                .map(|p| p.join("attachment.json"))
322	                .unwrap_or_else(|| PathBuf::from("attachment.json")),
323	        );
324	        // The push target stays the directly-paired trusted set at D5 (the
325	        // fan-out widens to all roster members only at D6); the sync *gate*,
326	        // however, now authorizes on roster membership (Mesh-D5, REQ-MESH-5).
327	        let trust = TrustStore::load_from(&paths.trust);
328	        let roster_path = paths
329	            .subnets
330	            .parent()
331	            .map(|p| p.join("roster.json"))
332	            .unwrap_or_else(|| PathBuf::from("roster.json"));
333	        let policy = SyncPolicy {
334	            subnets: subnets
335	                .subnets
336	                .iter()
337	                .map(|s| (s.name.clone(), s.hide_new_endpoints))
338	                .collect(),
339	            roster: spt_store::roster::RosterStore::load_from(&roster_path),
340	            vis: VisibilityStore::load_from(&paths.vis),
341	        };
342	        let adverts = if due_reg {
343	            // Consume the re-pair eviction marker FIRST (M8 decision 13,
344	            // REQ-SUBNET-7): a superseded identity's in-memory rows go
345	            // before this tick's snapshot mirror can resurrect the pairing
346	            // host's disk-side eviction.
347	            // [impl->REQ-SUBNET-7]
348	            registry.consume_repair_evictions();
349	            // Sweep silent-node ghost rows BEFORE re-advertising (KNOWN-
350	            // HAZARDS 4.10): a dead node identity's rows decay instead of
351	            // poisoning bare-id resolution forever; a live peer re-stamps its
352	            // heard entry with every admitted feed and is never touched.
353	            // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]
354	            registry.evict_silent_peers(cadence.registry_evict_after);
355	            registry.advertise_local(&paths.owlery)
356	        } else {
357	            Vec::new()
358	        };
359
360	        for sub in &subnets.subnets {
361	            if attachment.is_detached(&sub.name) {
362	                continue; // detached: neither advertise into nor connect to (REQ-SUBNET-5)
363	            }
364	            for peer in trust.peers_in(&sub.name) {
365	                if peer.pubkey_hex == registry.node_hex() {
366	                    continue; // never pump at ourselves
367	                }
368	                let Some(conn_id) = ensure_conn(
369	                    &mut brain,
370	                    &mut conns,
371	                    &resolver,
372	                    &peer.pubkey_hex,
373	                    &paths.peer_addrs,
374	                ) else {
375	                    continue; // unresolvable/undialable now — next tick retries
376	                };
377
378	                let mut peer_io = || -> io::Result<()> {
379	                    if due_reg {
380	                        let lines: Vec<u8> = adverts
381	                            .iter()
382	                            .filter(|u| u.subnet() == sub.name)
383	                            .flat_map(|u| u.encode_line())
384	                            .collect();
385	                        if !lines.is_empty() {
386	                            push_feed(&mut brain, conn_id, &lines)?;
387	                        }
388	                    }
389	                    if due_notif {
390	                        let store = NotifStore::open_at(&paths.notif_db)
391	                            .map_err(|e| io::Error::other(e.to_string()))?;
392	                        let feed = emit_notif_feed(&store, &sub.name).map_err(io::Error::other)?;
393	                        if !feed.is_empty() {
394	                            push_feed(&mut brain, conn_id, &feed)?;
395	                        }
396	                    }
397	                    if due_sync {
398	                        let cs = ContextStore::open_or_init_in(&paths.ctx_root)?;
399	                        let mut want = select_refs(&policy, &cs, &peer.pubkey_hex)?;
400	                        // Registry-derived bootstrap refs: minds named in the
401	                        // shared subnet that this node never held locally.
402	                        if let Some(snap) = registry.snapshot(&sub.name) {
403	                            for id in snap.endpoint_ids() {
404	                                let r = format!("a-{id}");

        // config-ish store, so a flip takes effect within one tick.
        // [impl->REQ-SUBNET-5]
        let attachment = spt_store::attachment::AttachmentStore::load_from(
            &paths
                .subnets
                .parent()
                .map(|p| p.join("attachment.json"))
                .unwrap_or_else(|| PathBuf::from("attachment.json")),
        );
        // The push target stays the directly-paired trusted set at D5 (the
        // fan-out widens to all roster members only at D6); the sync *gate*,
        // however, now authorizes on roster membership (Mesh-D5, REQ-MESH-5).
        let trust = TrustStore::load_from(&paths.trust);
        let roster_path = paths
            .subnets
            .parent()
            .map(|p| p.join("roster.json"))
            .unwrap_or_else(|| PathBuf::from("roster.json"));
        let policy = SyncPolicy {
            subnets: subnets
                .subnets
                .iter()
                .map(|s| (s.name.clone(), s.hide_new_endpoints))
                .collect(),
            roster: spt_store::roster::RosterStore::load_from(&roster_path),
            vis: VisibilityStore::load_from(&paths.vis),
        };
        let adverts = if due_reg {
            // Consume the re-pair eviction marker FIRST (M8 decision 13,
            // REQ-SUBNET-7): a superseded identity's in-memory rows go
            // before this tick's snapshot mirror can resurrect the pairing
            // host's disk-side eviction.
            // [impl->REQ-SUBNET-7]
            registry.consume_repair_evictions();
            // Sweep silent-node ghost rows BEFORE re-advertising (KNOWN-
            // HAZARDS 4.10): a dead node identity's rows decay instead of
            // poisoning bare-id resolution forever; a live peer re-stamps its
            // heard entry with every admitted feed and is never touched.
            // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]
            registry.evict_silent_peers(cadence.registry_evict_after);
            registry.advertise_local(&paths.owlery)
        } else {
            Vec::new()
        };

        for sub in &subnets.subnets {
            if attachment.is_detached(&sub.name) {
                continue; // detached: neither advertise into nor connect to (REQ-SUBNET-5)
            }
            for peer in trust.peers_in(&sub.name) {
                if peer.pubkey_hex == registry.node_hex() {
                    continue; // never pump at ourselves
                }
                let Some(conn_id) = ensure_conn(
                    &mut brain,
                    &mut conns,
                    &resolver,
                    &peer.pubkey_hex,
                    &paths.peer_addrs,
                ) else {
                    continue; // unresolvable/undialable now — next tick retries
                };

                let mut peer_io = || -> io::Result<()> {
                    if due_reg {
                        let lines: Vec<u8> = adverts
                            .iter()
                            .filter(|u| u.subnet() == sub.name)
                            .flat_map(|u| u.encode_line())
                            .collect();
                        if !lines.is_empty() {
                            push_feed(&mut brain, conn_id, &lines)?;
                        }
                    }
                    if due_notif {
                        let store = NotifStore::open_at(&paths.notif_db)
                            .map_err(|e| io::Error::other(e.to_string()))?;
                        let feed = emit_notif_feed(&store, &sub.name).map_err(io::Error::other)?;
                        if !feed.is_empty() {
                            push_feed(&mut brain, conn_id, &feed)?;
                        }
                    }
                    if due_sync {
                        let cs = ContextStore::open_or_init_in(&paths.ctx_root)?;
                        let mut want = select_refs(&policy, &cs, &peer.pubkey_hex)?;
                        // Registry-derived bootstrap refs: minds named in the
                        // shared subnet that this node never held locally.
                        if let Some(snap) = registry.snapshot(&sub.name) {
                            for id in snap.endpoint_ids() {
                                let r = format!("a-{id}");


Found a real plan gap before touching code. Let me lay it out.

**The coupling:** D5b's plan says "delete TrustStore, keep push target narrow (D6 owns the widen)." But these conflict:

- `peerloop.rs:327,364` derives the **push target** (who we dial + push rows to) from `trust.peers_in()` — the directly-paired set.
- D5b step 1 strips pairing's trust writes → `peers.json` goes empty (+ cutover deletes it) → peerloop's narrow push returns **nothing** → directly-paired flows break (regression).
- The only fix is pointing peerloop at `roster.members_in()` — but the roster is **wide** (D4 made A learn C transitively). That *is* the D6 fan-out widen → mesh turns on.

So: **stripping pairing-trust forces peerloop onto the roster, which forces the widen.** You can't delete TrustStore in D5b and keep "no new reach." The clean deletable-without-widening subset is small (CLI/eviction/warn-on-change repoint); `trust.rs` itself can't die until peerloop+pairing migrate, and that migration is the mesh-on switch.

This reshapes the D5b/D6 boundary, so it's your call.


AskUserQuestion

Question: D5b's 'delete TrustStore + keep push narrow' is internally inconsistent — stripping pairing's trust write forces peerloop onto the (wide) roster, which is the D6 mesh-on widen. How should I re-cut the boundary?

Choices:
D5b = peripheral repoint only (CLI list/status/prune + eviction → roster, demote warn-on-change to machine_id notice). KEEP trust.rs, pairing-trust write, peerloop narrow. Stays green, no reach. D6 grows: strip pairing-trust + peerloop→roster.members_in (widen) + delete trust.rs + harness flips green — still one auditable mesh-on commit.
D5b does the full cutover now: strip pairing-trust, peerloop→roster.members_in, delete trust.rs, mesh turns ON, D0 harness flips green. D6 shrinks to just un-ignore harness + the no-relay regression assert. Fewer phases, but D5b is no longer 'no new reach' and the green-flip isn't an isolated commit.
Spend more time checking whether a narrow push set can be reconstructed post-cutover (e.g. roster ∩ peer-addrs.json ≈ directly-contacted) so the plan stands as written. Riskier/implicit semantic; I'd report back before coding.


Your questions have been answered: "D5b's 'delete TrustStore + keep push narrow' is internally inconsistent — stripping pairing's trust write forces peerloop onto the (wide) roster, which is the D6 mesh-on widen. How should I re-cut the boundary?"="review the broader D5 and M9 plans, then the corresponding ADR (i think 17?), and use your best judgment based on those.". You can now continue with these answers in mind.

Question: D5b's 'delete TrustStore + keep push narrow' is internally inconsistent — stripping pairing's trust write forces peerloop onto the (wide) roster, which is the D6 mesh-on widen. How should I re-cut the boundary?

Choices:
D5b = peripheral repoint only (CLI list/status/prune + eviction → roster, demote warn-on-change to machine_id notice). KEEP trust.rs, pairing-trust write, peerloop narrow. Stays green, no reach. D6 grows: strip pairing-trust + peerloop→roster.members_in (widen) + delete trust.rs + harness flips green — still one auditable mesh-on commit.
D5b does the full cutover now: strip pairing-trust, peerloop→roster.members_in, delete trust.rs, mesh turns ON, D0 harness flips green. D6 shrinks to just un-ignore harness + the no-relay regression assert. Fewer phases, but D5b is no longer 'no new reach' and the green-flip isn't an isolated commit.
Spend more time checking whether a narrow push set can be reconstructed post-cutover (e.g. roster ∩ peer-addrs.json ≈ directly-contacted) so the plan stands as written. Riskier/implicit semantic; I'd report back before coding.

review the broader D5 and M9 plans, then the corresponding ADR (i think 17?), and use your best judgment based on those.


User defers to my judgment after I review ADR-0017 + broader plans. Reading ADR-0017 and checking for an M9 plan doc.