Skip to main content

agentmux_srv/backend/storage/
lan_peer_uid_pins.rs

1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Trust-on-first-use pin of a REMOTE agent's UID key, per (name, UID)
5//! (identity M4d-6, `SPEC_AGENT_IDENTITY_CARRIED_NOT_DERIVED_2026_09_23.md`
6//! ยง6.5.10). The LAN v2 counterpart of `lan_peer_pubkey_pins.rs`, and set only
7//! after that name pin matched: UIDs are public, so a pin keyed by UID alone
8//! could be squatted by a peer answering for its own name.
9
10use rusqlite::params;
11
12use super::error::StoreError;
13use super::store::Store;
14
15use agentmux_common::time::now_secs;
16
17impl Store {
18    /// Get-or-pin, as `lan_peer_pubkey_pin_get_or_set`: an existing pin for
19    /// `(agent_id, uid)` is returned unchanged, otherwise `observed_key_b64`
20    /// is pinned and returned. A return that differs from `observed_key_b64`
21    /// means the UID's key changed under that name.
22    pub fn lan_peer_uid_pin_get_or_set(
23        &self,
24        agent_id: &str,
25        uid: &str,
26        observed_key_b64: &str,
27    ) -> Result<String, StoreError> {
28        let name = agent_id.to_lowercase();
29        let conn = self.conn.lock().unwrap();
30        conn.execute(
31            "INSERT OR IGNORE INTO db_lan_peer_uid_pins (agent_id, uid, public_key, first_seen_at) \
32             VALUES (?1, ?2, ?3, ?4)",
33            params![name, uid, observed_key_b64, now_secs()],
34        )?;
35        let mut stmt = conn.prepare("SELECT public_key FROM db_lan_peer_uid_pins WHERE agent_id = ?1 AND uid = ?2")?;
36        stmt.query_row(params![name, uid], |row| row.get(0)).map_err(Into::into)
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    fn object_store() -> Store {
45        Store::open(std::path::Path::new(":memory:")).unwrap()
46    }
47
48    #[test]
49    fn the_first_key_for_a_name_and_uid_is_pinned_and_a_later_one_is_not() {
50        let store = object_store();
51        assert_eq!(store.lan_peer_uid_pin_get_or_set("Korp", "uid-1", "key-a").unwrap(), "key-a");
52        assert_eq!(store.lan_peer_uid_pin_get_or_set("korp", "uid-1", "key-b").unwrap(), "key-a");
53    }
54
55    #[test]
56    fn the_same_uid_under_another_name_pins_separately() {
57        let store = object_store();
58        store.lan_peer_uid_pin_get_or_set("korp", "uid-1", "key-a").unwrap();
59        assert_eq!(store.lan_peer_uid_pin_get_or_set("lark", "uid-1", "key-b").unwrap(), "key-b");
60        assert_eq!(store.lan_peer_uid_pin_get_or_set("korp", "uid-2", "key-c").unwrap(), "key-c");
61    }
62}