slancha-wire 0.16.0

Magic-wormhole for AI agents — bilateral signed-message bus over a mailbox relay
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
//! Trust state machine — v0.1 minimal subset, extended in v3.2 (RFC-001).
//!
//! Tier semantics:
//!   - UNTRUSTED: card pinned, no claim verified yet; messages ignored.
//!   - ORG_VERIFIED: (v3.2 / RFC-001 §5) peer shares a verified `org_did`
//!     with us — *organisational* trust, NOT personal. Bilateral SAS is
//!     still required to cross into VERIFIED. Promotion from UNTRUSTED is
//!     one-way.
//!   - VERIFIED: SAS confirmed bilateral; messages accepted. Promotion
//!     accepts UNTRUSTED-or-ORG_VERIFIED as source (RFC-001 §5: "a
//!     SAS-paired peer that happens to share our org is recorded at
//!     VERIFIED, not downgraded").
//!   - ATTESTED: reserved (v0.2+) — used today only for self-attest.
//!   - TRUSTED: reserved (v0.2+).
//!
//! Promotion is one-way. Demotion would be ambiguous in a bilateral setting
//! and is deliberately not modeled. RFC-001 §5 invariant:
//!   "ORG_VERIFIED never satisfies a `>= VERIFIED` policy check."
//! That invariant is captured by `tier_order` (ORG_VERIFIED=1 < VERIFIED=2)
//! and by AC2 property test (tests/trust_ceiling_prop.rs) asserting no
//! claim-event walk reaches VERIFIED without a SasConfirmed step.

use serde_json::{Value, json};
use std::collections::BTreeMap;
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;

use crate::signing::{b64encode, make_key_id};

/// Tier ranking — higher is more trusted. Useful for `>=` gating.
///
/// RFC-001 §5 invariant: ORG_VERIFIED sits strictly between UNTRUSTED and
/// VERIFIED. A policy check of `tier >= VERIFIED` MUST NOT pass for an
/// ORG_VERIFIED peer — only an explicit SAS-confirmation can cross that line.
pub fn tier_order() -> BTreeMap<&'static str, u32> {
    [
        ("UNTRUSTED", 0u32),
        ("ORG_VERIFIED", 1),
        ("VERIFIED", 2),
        ("ATTESTED", 3),
        ("TRUSTED", 4),
    ]
    .into_iter()
    .collect()
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Tier {
    Untrusted,
    OrgVerified,
    Verified,
    Attested,
    Trusted,
}

impl Tier {
    pub fn as_str(self) -> &'static str {
        match self {
            Tier::Untrusted => "UNTRUSTED",
            Tier::OrgVerified => "ORG_VERIFIED",
            Tier::Verified => "VERIFIED",
            Tier::Attested => "ATTESTED",
            Tier::Trusted => "TRUSTED",
        }
    }
}

/// Trust state — kept as a free-form JSON Value so we can persist + read with
/// any conforming impl. v0.2+ may swap this for a typed struct.
pub type Trust = Value;

pub fn empty_trust() -> Trust {
    json!({"version": 1, "agents": {}})
}

pub fn get_tier(trust: &Trust, peer_handle: &str) -> String {
    trust
        .get("agents")
        .and_then(|a| a.get(peer_handle))
        .and_then(|a| a.get("tier"))
        .and_then(Value::as_str)
        .unwrap_or("UNTRUSTED")
        .to_string()
}

/// Effective trust tier — what the daemon can ACT on, not just what
/// trust.json was promoted to.
///
/// Surface-honest. trust.json may say VERIFIED, but if relay_state
/// has no `bilateral_completed_at` AND no `slot_token`, the daemon
/// literally cannot push to that peer. Showing the operator a
/// VERIFIED tag in that case is a lie about capability — fall back
/// to PENDING_ACK so the diagnosis line + pending-push attribution
/// agree.
///
/// Originally lived in `cli.rs::effective_peer_tier`. Moved to
/// `trust.rs` 2026-06-01 so `config::compute_pending_push_breakdown`
/// can call it without a circular dep, and so any future surface
/// (web doctor, MCP wire_status, etc.) gets the same canonical
/// answer.
///
/// History: v0.14.2 (#162 fix #5) introduced the
/// `bilateral_completed_at` durable signal — pre-#162 peers fall
/// back to `slot_token` presence as a legacy probe so already-paired
/// peers keep reporting VERIFIED instead of regressing the moment
/// they're upgraded.
pub fn effective_tier(trust: &Value, relay_state: &Value, handle: &str) -> String {
    let raw = get_tier(trust, handle);
    if raw != "VERIFIED" {
        return raw;
    }
    let peer_obj = relay_state.get("peers").and_then(|p| p.get(handle));
    let bilateral_at = peer_obj
        .and_then(|p| p.get("bilateral_completed_at"))
        .and_then(Value::as_str);
    if bilateral_at.is_some() {
        return raw;
    }
    // A VERIFIED pin isn't effectively usable until we hold the peer's reply
    // slot. RFC-006 Part B: the slot lives in `endpoints[]`, not a flat
    // `slot_token` field — so check the endpoints (keeping the flat read for
    // any legacy relay-state). Pre-Part-B this only read the flat field, which
    // Part B emptied → every freshly-paired peer wrongly showed PENDING_ACK.
    let has_slot = peer_obj
        .and_then(|p| p.get("slot_token"))
        .and_then(Value::as_str)
        .map(|t| !t.is_empty())
        .unwrap_or(false)
        || crate::endpoints::peer_endpoints_in_priority_order(relay_state, handle)
            .iter()
            .any(|e| !e.slot_token.is_empty());
    if has_slot {
        raw
    } else {
        "PENDING_ACK".to_string()
    }
}

/// Resolve a bare peer handle to the full DID stored in trust. Falls back
/// to `did:wire:<peer_handle>` (the bare-handle form) when the peer isn't
/// pinned — preserves pre-pair best-effort routing for unknown peers.
///
/// v0.14.2 (#162 fix #4): without this, send paths (`cmd_send` /
/// `tool_send`) built `to: did:wire:sunlit-aurora`, but pinned peers'
/// real DIDs carry the long fingerprint suffix
/// (`did:wire:sunlit-aurora-ec6f890d`). A bare-handle `to:` mismatches
/// the receiver's self-DID and risks rejection at canonical / cursor
/// check time (honey-pine's report observed this on the first queued
/// event). Use this helper at every send-build site to canonicalize
/// against the pinned peer's actual DID.
pub fn resolve_peer_did(trust: &Value, peer_handle: &str) -> String {
    trust
        .get("agents")
        .and_then(|a| a.get(peer_handle))
        .and_then(|p| p.get("did"))
        .and_then(Value::as_str)
        .map(str::to_string)
        .unwrap_or_else(|| format!("did:wire:{peer_handle}"))
}

/// Pin a peer's card into our trust at the given tier (default UNTRUSTED).
///
/// The caller must independently run SAS confirmation (via `compute_sas`)
/// before calling `promote_to_verified`. Pinning alone DOES NOT verify.
pub fn add_agent_card_pin(trust: &mut Trust, card: &Value, tier: Option<&str>) {
    let did = card.get("did").and_then(Value::as_str).unwrap_or_default();
    // v0.5.7+: prefer the explicit `handle` field on the card (display name).
    // Fall back to stripping the DID prefix for legacy cards. For v0.5.7+
    // pubkey-suffixed DIDs (`did:wire:paul-abc12345`), the display_handle
    // helper strips the pubkey suffix back off.
    let handle = card
        .get("handle")
        .and_then(Value::as_str)
        .map(str::to_string)
        .unwrap_or_else(|| crate::agent_card::display_handle_from_did(did).to_string());
    if handle.is_empty() {
        panic!("card has no resolvable handle (did={did:?})");
    }
    let tier = tier.unwrap_or("UNTRUSTED");
    let now = now_iso();

    let mut public_keys = Vec::new();
    if let Some(vks) = card.get("verify_keys").and_then(Value::as_object) {
        for (key_id_full, key_record) in vks {
            // Strip the `ed25519:` algorithm prefix to match v3.1 trust.json shape.
            let key_id = key_id_full.strip_prefix("ed25519:").unwrap_or(key_id_full);
            public_keys.push(json!({
                "key_id": key_id,
                "key": key_record.get("key").cloned().unwrap_or(Value::Null),
                "added_at": now,
                "active": true,
            }));
        }
    }

    let agents = trust
        .as_object_mut()
        .expect("trust must be an object")
        .entry("agents")
        .or_insert_with(|| json!({}));

    agents[handle] = json!({
        "tier": tier,
        "did": did,
        "public_keys": public_keys,
        "card": card.clone(),
        "pinned_at": now,
    });
}

/// Promote UNTRUSTED or ORG_VERIFIED → VERIFIED. Returns `Err(reason)` if
/// not pinned or already past VERIFIED.
///
/// RFC-001 §5: a SAS-confirmed peer that happens to share our org is
/// recorded at VERIFIED, not downgraded — so ORG_VERIFIED is an accepted
/// source for VERIFIED promotion. ATTESTED and TRUSTED are above VERIFIED
/// and would be a downgrade; we refuse.
pub fn promote_to_verified(trust: &mut Trust, peer_handle: &str) -> Result<(), String> {
    let agents = trust
        .as_object_mut()
        .ok_or("trust is not an object")?
        .get_mut("agents")
        .and_then(Value::as_object_mut)
        .ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;

    let agent = agents
        .get_mut(peer_handle)
        .ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;

    let current = agent
        .get("tier")
        .and_then(Value::as_str)
        .unwrap_or("UNTRUSTED")
        .to_string();
    if current != "UNTRUSTED" && current != "ORG_VERIFIED" {
        return Err(format!(
            "peer {peer_handle:?} already at tier {current:?} — promotion is one-way"
        ));
    }
    agent["tier"] = json!("VERIFIED");
    agent["verified_at"] = json!(now_iso());
    Ok(())
}

/// Promote UNTRUSTED → ORG_VERIFIED. Returns `Err(reason)` if not pinned or
/// already past UNTRUSTED.
///
/// RFC-001 §5: ORG_VERIFIED is granted on cryptographic + policy grounds
/// (the peer's `member_cert` for an org we accept verifies against that
/// org's pubkey) but DOES NOT satisfy the SAS-confirmation ceremony that
/// VERIFIED requires. It is a one-way intermediate step a peer may cross
/// before or after VERIFIED, but never *instead of* VERIFIED.
///
/// This function does NOT perform the cryptographic verification of
/// `member_cert` — that lives in [`crate::identity::verify_member_cert`]
/// and the caller must run it first. The trust mutation here is the policy
/// recording: "we accept this peer as ORG_VERIFIED under our active org
/// policy."
pub fn promote_to_org_verified(trust: &mut Trust, peer_handle: &str) -> Result<(), String> {
    let agents = trust
        .as_object_mut()
        .ok_or("trust is not an object")?
        .get_mut("agents")
        .and_then(Value::as_object_mut)
        .ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;

    let agent = agents
        .get_mut(peer_handle)
        .ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;

    let current = agent
        .get("tier")
        .and_then(Value::as_str)
        .unwrap_or("UNTRUSTED")
        .to_string();
    if current != "UNTRUSTED" {
        return Err(format!(
            "peer {peer_handle:?} already at tier {current:?}\
             org_verified promotion fires from UNTRUSTED only"
        ));
    }
    agent["tier"] = json!("ORG_VERIFIED");
    agent["org_verified_at"] = json!(now_iso());
    Ok(())
}

/// RFC-001 §6 project fan-out: the pinned peer handles eligible to receive a
/// `wire send --project <tag>` broadcast.
///
/// A peer is eligible iff (a) its effective tier is **>= ORG_VERIFIED** (so we
/// never fan out to an unverified or unreachable peer) AND (b) its pinned
/// agent-card carries `project == <tag>`. `project` is **unsigned routing
/// metadata** (RFC-001 §6) — it selects recipients, it never grants trust; the
/// tier floor is the trust gate, the project tag is only the address book.
///
/// `self_handle` is excluded (our own ATTESTED self-pin must never be a
/// recipient). Pure over the two state blobs so it unit-tests without any CLI
/// or live relay. Result is sorted for deterministic output.
pub fn project_recipients(
    trust: &Value,
    relay_state: &Value,
    self_handle: &str,
    project: &str,
) -> Vec<String> {
    let order = tier_order();
    let floor = order.get("ORG_VERIFIED").copied().unwrap_or(1);
    let mut out = Vec::new();
    if let Some(agents) = trust.get("agents").and_then(Value::as_object) {
        for (handle, agent) in agents {
            if handle == self_handle {
                continue;
            }
            let tier = effective_tier(trust, relay_state, handle);
            let rank = order.get(tier.as_str()).copied().unwrap_or(0);
            if rank < floor {
                continue;
            }
            let proj = agent.get("card").and_then(crate::agent_card::card_project);
            if proj == Some(project) {
                out.push(handle.clone());
            }
        }
    }
    out.sort();
    out
}

/// Self-pin our own keypair into trust at ATTESTED. Convenience for `wire init`.
pub fn add_self_to_trust(trust: &mut Trust, handle: &str, public_key: &[u8]) {
    let agents = trust
        .as_object_mut()
        .expect("trust must be an object")
        .entry("agents")
        .or_insert_with(|| json!({}));
    let key_id = make_key_id(handle, public_key);
    agents[handle] = json!({
        "tier": "ATTESTED",
        "did": crate::agent_card::did_for_with_key(handle, public_key),
        "public_keys": [{
            "key_id": key_id,
            "key": b64encode(public_key),
            "added_at": now_iso(),
            "active": true,
        }],
    });
}

fn now_iso() -> String {
    let now = OffsetDateTime::now_utc();
    now.format(&Rfc3339)
        .unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_string())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::agent_card::{build_agent_card, sign_agent_card};
    use crate::signing::generate_keypair;

    #[test]
    fn empty_trust_shape() {
        let t = empty_trust();
        assert_eq!(t["version"], 1);
        assert!(t["agents"].is_object());
        assert_eq!(t["agents"].as_object().unwrap().len(), 0);
    }

    #[test]
    fn get_tier_unknown_returns_untrusted() {
        assert_eq!(get_tier(&empty_trust(), "ghost"), "UNTRUSTED");
    }

    #[test]
    fn resolve_peer_did_returns_pinned_did_with_full_suffix() {
        // v0.14.2 (#162 fix #4): a pinned peer's full DID includes the
        // long-fingerprint suffix; a bare-handle DID would mismatch the
        // receiver's self-DID and risk rejection at canonical/cursor
        // verification.
        let (sk, pk) = generate_keypair();
        let card = sign_agent_card(
            &build_agent_card("sunlit-aurora", &pk, None, None, None),
            &sk,
        );
        let pinned_did = card.get("did").and_then(Value::as_str).unwrap();
        assert!(
            pinned_did.starts_with("did:wire:sunlit-aurora-"),
            "test setup: card DID should carry long-hex suffix"
        );
        let mut t = empty_trust();
        add_agent_card_pin(&mut t, &card, Some("VERIFIED"));

        let resolved = resolve_peer_did(&t, "sunlit-aurora");
        assert_eq!(
            resolved, pinned_did,
            "pinned peer must resolve to its full DID, not the bare handle"
        );
    }

    #[test]
    fn resolve_peer_did_falls_back_to_bare_for_unknown_peer() {
        // Pre-pair best-effort: an unknown peer canonicalizes to the
        // bare-handle DID. cmd_send / tool_send keep working pre-pair;
        // post-pair the resolve path takes over.
        let t = empty_trust();
        assert_eq!(
            resolve_peer_did(&t, "ghost-peer"),
            "did:wire:ghost-peer",
            "unknown peer falls back to bare-handle DID"
        );
    }

    #[test]
    fn add_agent_card_pin_defaults_untrusted() {
        let (sk, pk) = generate_keypair();
        let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
        let mut t = empty_trust();
        add_agent_card_pin(&mut t, &card, None);
        assert_eq!(get_tier(&t, "paul"), "UNTRUSTED");
        // v0.5.7+: DID is pubkey-suffixed.
        let did = t["agents"]["paul"]["did"].as_str().unwrap();
        assert!(did.starts_with("did:wire:paul-"), "got: {did}");
    }

    #[test]
    fn add_pin_strips_ed25519_prefix_from_key_id() {
        let (sk, pk) = generate_keypair();
        let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
        let mut t = empty_trust();
        add_agent_card_pin(&mut t, &card, None);
        let kid = t["agents"]["paul"]["public_keys"][0]["key_id"]
            .as_str()
            .unwrap();
        assert!(kid.contains(':'));
        assert!(!kid.starts_with("ed25519:"));
    }

    #[test]
    fn promote_to_verified_one_way() {
        let (sk, pk) = generate_keypair();
        let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
        let mut t = empty_trust();
        add_agent_card_pin(&mut t, &card, None);
        promote_to_verified(&mut t, "paul").unwrap();
        assert_eq!(get_tier(&t, "paul"), "VERIFIED");
        assert!(t["agents"]["paul"]["verified_at"].is_string());
    }

    #[test]
    fn promote_to_verified_idempotent_block() {
        let (sk, pk) = generate_keypair();
        let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
        let mut t = empty_trust();
        add_agent_card_pin(&mut t, &card, None);
        promote_to_verified(&mut t, "paul").unwrap();
        let err = promote_to_verified(&mut t, "paul").unwrap_err();
        assert!(err.contains("VERIFIED"), "got: {err}");
    }

    #[test]
    fn promote_unknown_peer_fails() {
        let mut t = empty_trust();
        let err = promote_to_verified(&mut t, "ghost").unwrap_err();
        assert!(err.contains("not pinned"), "got: {err}");
    }

    #[test]
    fn add_self_to_trust_attests() {
        let (_, pk) = generate_keypair();
        let mut t = empty_trust();
        add_self_to_trust(&mut t, "paul", &pk);
        assert_eq!(get_tier(&t, "paul"), "ATTESTED");
        let did = t["agents"]["paul"]["did"].as_str().unwrap();
        assert!(did.starts_with("did:wire:paul-"), "got: {did}");
    }

    #[test]
    fn tier_order_matches_promotion_semantics() {
        let order = tier_order();
        assert!(order["UNTRUSTED"] < order["ORG_VERIFIED"]);
        assert!(order["ORG_VERIFIED"] < order["VERIFIED"]);
        assert!(order["VERIFIED"] < order["ATTESTED"]);
        assert!(order["ATTESTED"] < order["TRUSTED"]);
    }

    // ─── RFC-001 §5: Tier::OrgVerified ────────────────────────────────────

    #[test]
    fn tier_as_str_covers_org_verified() {
        assert_eq!(Tier::OrgVerified.as_str(), "ORG_VERIFIED");
    }

    #[test]
    fn promote_to_org_verified_one_way() {
        let (sk, pk) = generate_keypair();
        let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
        let mut t = empty_trust();
        add_agent_card_pin(&mut t, &card, None);
        promote_to_org_verified(&mut t, "paul").unwrap();
        assert_eq!(get_tier(&t, "paul"), "ORG_VERIFIED");
        assert!(t["agents"]["paul"]["org_verified_at"].is_string());
    }

    #[test]
    fn promote_to_org_verified_refuses_already_verified() {
        // Once a peer is VERIFIED (bilateral SAS), regressing them to
        // ORG_VERIFIED would be a downgrade. Refuse.
        let (sk, pk) = generate_keypair();
        let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
        let mut t = empty_trust();
        add_agent_card_pin(&mut t, &card, None);
        promote_to_verified(&mut t, "paul").unwrap();
        let err = promote_to_org_verified(&mut t, "paul").unwrap_err();
        assert!(err.contains("VERIFIED"), "got: {err}");
        assert_eq!(get_tier(&t, "paul"), "VERIFIED");
    }

    #[test]
    fn promote_to_org_verified_refuses_self_idempotent() {
        // Twice-applied org promotion is a no-op error, not a silent reset
        // of `org_verified_at` — keeps the audit trail intact.
        let (sk, pk) = generate_keypair();
        let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
        let mut t = empty_trust();
        add_agent_card_pin(&mut t, &card, None);
        promote_to_org_verified(&mut t, "paul").unwrap();
        let err = promote_to_org_verified(&mut t, "paul").unwrap_err();
        assert!(err.contains("ORG_VERIFIED"), "got: {err}");
    }

    #[test]
    fn promote_to_verified_accepts_org_verified_source() {
        // RFC-001 §5: a peer can be ORG_VERIFIED then later cross the SAS
        // ceremony into VERIFIED — without losing the cryptographic
        // membership claim. We preserve `org_verified_at` for audit.
        let (sk, pk) = generate_keypair();
        let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
        let mut t = empty_trust();
        add_agent_card_pin(&mut t, &card, None);
        promote_to_org_verified(&mut t, "paul").unwrap();
        promote_to_verified(&mut t, "paul").unwrap();
        assert_eq!(get_tier(&t, "paul"), "VERIFIED");
        assert!(t["agents"]["paul"]["org_verified_at"].is_string());
        assert!(t["agents"]["paul"]["verified_at"].is_string());
    }

    #[test]
    fn promote_to_verified_refuses_attested_source() {
        // ATTESTED is reserved-but-above VERIFIED; a downgrade would lose
        // information. Refuse.
        let (_, pk) = generate_keypair();
        let mut t = empty_trust();
        add_self_to_trust(&mut t, "self", &pk);
        let err = promote_to_verified(&mut t, "self").unwrap_err();
        assert!(err.contains("ATTESTED"), "got: {err}");
    }

    #[test]
    fn effective_tier_matrix() {
        use serde_json::json;
        // VERIFIED in trust + bilateral_completed_at present → stays VERIFIED.
        let trust = json!({"agents": {"a": {"tier": "VERIFIED"}}});
        let relay = json!({"peers": {"a": {"bilateral_completed_at": "t"}}});
        assert_eq!(effective_tier(&trust, &relay, "a"), "VERIFIED");
        // VERIFIED in trust + slot_token non-empty (back-compat path) → VERIFIED.
        let relay = json!({"peers": {"a": {"slot_token": "tok"}}});
        assert_eq!(effective_tier(&trust, &relay, "a"), "VERIFIED");
        // VERIFIED in trust + no bilateral_at + empty slot_token → PENDING_ACK.
        let relay = json!({"peers": {"a": {"slot_token": ""}}});
        assert_eq!(effective_tier(&trust, &relay, "a"), "PENDING_ACK");
        // VERIFIED in trust + peer missing from relay.peers entirely → PENDING_ACK.
        let relay = json!({"peers": {}});
        assert_eq!(effective_tier(&trust, &relay, "a"), "PENDING_ACK");
        // RFC-006 Part B: the slot lives in endpoints[], not a flat field. A
        // non-empty slot_token there must read as VERIFIED (the regression this
        // guards: Part B emptied the flat field, so a flat-only reader wrongly
        // downgraded every freshly-paired peer to PENDING_ACK).
        let relay = json!({"peers": {"a": {"endpoints": [
            {"relay_url": "https://r", "slot_id": "s", "slot_token": "tok", "scope": "federation"}
        ]}}});
        assert_eq!(effective_tier(&trust, &relay, "a"), "VERIFIED");
        // endpoints[] present but its slot_token empty → still PENDING_ACK.
        let relay = json!({"peers": {"a": {"endpoints": [
            {"relay_url": "https://r", "slot_id": "s", "slot_token": "", "scope": "federation"}
        ]}}});
        assert_eq!(effective_tier(&trust, &relay, "a"), "PENDING_ACK");
        // Non-VERIFIED trust tiers pass through unchanged.
        let trust = json!({"agents": {"a": {"tier": "UNTRUSTED"}}});
        assert_eq!(effective_tier(&trust, &relay, "a"), "UNTRUSTED");
        let trust = json!({"agents": {"a": {"tier": "ORG_VERIFIED"}}});
        assert_eq!(effective_tier(&trust, &relay, "a"), "ORG_VERIFIED");
    }

    #[test]
    fn project_recipients_filters_by_tier_and_project() {
        use serde_json::json;
        let trust = json!({"agents": {
            "alice":  {"tier": "ORG_VERIFIED", "card": {"project": "print-shop"}},
            "bob":    {"tier": "ORG_VERIFIED", "card": {"project": "lora-training"}},
            "carol":  {"tier": "UNTRUSTED",    "card": {"project": "print-shop"}},
            "dave":   {"tier": "VERIFIED",     "card": {"project": "print-shop"}},
            "selfie": {"tier": "ATTESTED",     "card": {"project": "print-shop"}},
            "noproj": {"tier": "ORG_VERIFIED", "card": {}},
        }});
        // VERIFIED dave needs a relay signal to read as VERIFIED (else PENDING_ACK).
        let relay = json!({"peers": {"dave": {"bilateral_completed_at": "t"}}});
        let r = project_recipients(&trust, &relay, "selfie", "print-shop");
        // alice (ORG_VERIFIED+match) and dave (VERIFIED+match) only. bob wrong
        // project; carol below floor; selfie is self; noproj has no tag.
        assert_eq!(r, vec!["alice".to_string(), "dave".to_string()]);
    }

    #[test]
    fn project_recipients_excludes_unreachable_verified() {
        use serde_json::json;
        // VERIFIED in trust but no relay signal → effective PENDING_ACK → we
        // can't actually deliver, so it must not be a fan-out recipient.
        let trust = json!({"agents": {
            "ghost": {"tier": "VERIFIED", "card": {"project": "x"}},
        }});
        let relay = json!({"peers": {}});
        assert!(project_recipients(&trust, &relay, "selfie", "x").is_empty());
    }

    #[test]
    fn org_verified_does_not_satisfy_verified_policy_check() {
        // The load-bearing RFC-001 invariant: a policy gate of
        // `tier >= VERIFIED` MUST refuse an ORG_VERIFIED peer.
        let order = tier_order();
        let verified_rank = order["VERIFIED"];
        let org_rank = order["ORG_VERIFIED"];
        assert!(
            org_rank < verified_rank,
            "ORG_VERIFIED ({org_rank}) must rank strictly below VERIFIED ({verified_rank})"
        );
    }
}