diff --git a/crates/didbot-attest/src/claim.rs b/crates/didbot-attest/src/claim.rs index 870a46eb..bcafc124 100644 --- a/crates/didbot-attest/src/claim.rs +++ b/crates/didbot-attest/src/claim.rs @@ -97,7 +97,7 @@ pub enum Assurance { /// The verified result of an attestation: what a backend is willing to record. /// /// Facts, not a record. It used to carry a `$type` and go out as -/// `zone.quernstone.agent.attestation`; what stays true of an admission is now +/// `bot.did.attestation`; what stays true of an admission is now /// part of the account's registration record, composed from the ledger, and /// nothing writes this shape into a repository. Serde-serializable because it /// is stored beside the account, and camelCase because everything this server diff --git a/crates/didbot-data/src/lib.rs b/crates/didbot-data/src/lib.rs index 05dcb120..7f7acaf2 100644 --- a/crates/didbot-data/src/lib.rs +++ b/crates/didbot-data/src/lib.rs @@ -22,7 +22,7 @@ //! use didbot_data::{dag_cbor, Value}; //! //! let record = serde_json::json!({ -//! "$type": "zone.quernstone.scrobble", +//! "$type": "com.vibescrobble.scrobble", //! "text": "quernstone", //! "count": 1, //! }); diff --git a/crates/didbot-data/src/mst/store.rs b/crates/didbot-data/src/mst/store.rs index cb4f8386..f53911c6 100644 --- a/crates/didbot-data/src/mst/store.rs +++ b/crates/didbot-data/src/mst/store.rs @@ -412,7 +412,7 @@ mod tests { #[test] fn a_walk_finds_a_path_and_is_smaller_than_the_tree() { let paths: Vec = (0..400) - .map(|i| format!("zone.quernstone.scrobble/3lqm{i:04}")) + .map(|i| format!("com.vibescrobble.scrobble/3lqm{i:04}")) .collect(); let borrowed: Vec<&str> = paths.iter().map(String::as_str).collect(); let (store, root) = tree(&borrowed); @@ -427,11 +427,11 @@ mod tests { #[test] fn an_absent_path_stops_at_its_own_layer() { let paths: Vec = (0..400) - .map(|i| format!("zone.quernstone.scrobble/3lqm{i:04}")) + .map(|i| format!("com.vibescrobble.scrobble/3lqm{i:04}")) .collect(); let borrowed: Vec<&str> = paths.iter().map(String::as_str).collect(); let (store, root) = tree(&borrowed); - let missing = "zone.quernstone.scrobble/zzzzzzzz"; + let missing = "com.vibescrobble.scrobble/zzzzzzzz"; let found = store.find(&root, missing).unwrap(); assert_eq!(found.value, None); let deepest = found diff --git a/crates/didbot-data/src/mst/tree.rs b/crates/didbot-data/src/mst/tree.rs index 72d428f0..03b93581 100644 --- a/crates/didbot-data/src/mst/tree.rs +++ b/crates/didbot-data/src/mst/tree.rs @@ -519,7 +519,7 @@ mod tests { let tree = tree_of(&[ "app.bsky.feed.post/3jt6wh3ckcc2y", "app.bsky.feed.like/3jtdufrp6md2l", - "zone.quernstone.scrobble/3lqmnopqrstuv", + "com.vibescrobble.scrobble/3lqmnopqrstuv", "app.bsky.actor.profile/self", ]); for (cid, block) in tree.blocks() { @@ -557,7 +557,7 @@ mod tests { #[test] fn a_path_is_shorter_than_the_tree() { let keys: Vec = (0..400) - .map(|i| format!("zone.quernstone.scrobble/3lqm{i:04}")) + .map(|i| format!("com.vibescrobble.scrobble/3lqm{i:04}")) .collect(); let leaves: BTreeMap = keys.iter().map(|key| (key.clone(), leaf(key))).collect(); diff --git a/crates/didbot-hookd/src/config.rs b/crates/didbot-hookd/src/config.rs index ef0e56af..220a930b 100644 --- a/crates/didbot-hookd/src/config.rs +++ b/crates/didbot-hookd/src/config.rs @@ -214,13 +214,13 @@ mod tests { debug: false, }; assert_eq!( - config.xrpc_url("zone.quernstone.provisionAgent"), - "http://localhost:3000/xrpc/zone.quernstone.provisionAgent" + config.xrpc_url("bot.did.provisionAgent"), + "http://localhost:3000/xrpc/bot.did.provisionAgent" ); config.pds_url = "http://localhost:3000".to_string(); assert_eq!( - config.xrpc_url("zone.quernstone.deleteAgent"), - "http://localhost:3000/xrpc/zone.quernstone.deleteAgent" + config.xrpc_url("bot.did.deleteAgent"), + "http://localhost:3000/xrpc/bot.did.deleteAgent" ); } } diff --git a/crates/didbot-hookd/src/pds.rs b/crates/didbot-hookd/src/pds.rs index 0cd896a7..16f4d5ea 100644 --- a/crates/didbot-hookd/src/pds.rs +++ b/crates/didbot-hookd/src/pds.rs @@ -1,7 +1,7 @@ //! The two XRPC calls this hook makes. //! -//! Provisioning and deletion, over `zone.quernstone.provisionAgent` and -//! `zone.quernstone.deleteAgent`. The request bodies are built here rather +//! Provisioning and deletion, over `bot.did.provisionAgent` and +//! `bot.did.deleteAgent`. The request bodies are built here rather //! than borrowed from `didbot-serve`, so that the hook does not depend //! on the server crate to talk to the server over HTTP. //! @@ -22,10 +22,10 @@ use time::OffsetDateTime; use crate::config::{Config, REQUEST_TIMEOUT_SECS}; /// XRPC method that mints an account. -pub const PROVISION_NSID: &str = "zone.quernstone.provisionAgent"; +pub const PROVISION_NSID: &str = "bot.did.provisionAgent"; /// XRPC method that removes one. -pub const DELETE_NSID: &str = "zone.quernstone.deleteAgent"; +pub const DELETE_NSID: &str = "bot.did.deleteAgent"; /// Why a call to the personal data server did not produce what was asked for. /// @@ -106,7 +106,7 @@ pub struct WireRegistration { pub parent: Option, } -/// Body of `zone.quernstone.provisionAgent`. +/// Body of `bot.did.provisionAgent`. #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct ProvisionRequest { @@ -115,7 +115,7 @@ struct ProvisionRequest { registration: WireRegistration, } -/// Body of `zone.quernstone.deleteAgent`. +/// Body of `bot.did.deleteAgent`. #[derive(Debug, Serialize)] struct DeleteRequest { did: String, diff --git a/crates/didbot-hookd/tests/hook_events.rs b/crates/didbot-hookd/tests/hook_events.rs index 598a9d24..b38e7a9a 100644 --- a/crates/didbot-hookd/tests/hook_events.rs +++ b/crates/didbot-hookd/tests/hook_events.rs @@ -109,7 +109,7 @@ fn serve(mut stream: TcpStream, mode: Mode, seen: &Mutex>) "500 Internal Server Error", json!({"error": "InternalServerError", "message": "vellicate"}), ), - (Mode::Healthy, "/xrpc/zone.quernstone.provisionAgent") => { + (Mode::Healthy, "/xrpc/bot.did.provisionAgent") => { let agent_id = parsed .get("agentId") .and_then(Value::as_str) @@ -258,7 +258,7 @@ async fn session_start_provisions_and_remembers_the_did() { let output = handle(&config, &session_start()).await; assert!(does_not_block(&output)); - let bodies = stub.bodies_for("zone.quernstone.provisionAgent"); + let bodies = stub.bodies_for("bot.did.provisionAgent"); assert_eq!(bodies.len(), 1, "expected exactly one provisioning call"); let agent_id = bodies[0]["agentId"].as_str().expect("agentId"); assert!(agent_id.starts_with("9f1c4e-"), "{agent_id}"); @@ -283,7 +283,7 @@ async fn session_start_twice_provisions_once() { handle(&config, &session_start()).await; handle(&config, &session_start()).await; - assert_eq!(stub.bodies_for("zone.quernstone.provisionAgent").len(), 1); + assert_eq!(stub.bodies_for("bot.did.provisionAgent").len(), 1); } #[tokio::test] @@ -294,7 +294,7 @@ async fn a_subagent_gets_its_own_account() { handle(&config, &session_start()).await; handle(&config, &subagent_start("agent-abc123")).await; - let bodies = stub.bodies_for("zone.quernstone.provisionAgent"); + let bodies = stub.bodies_for("bot.did.provisionAgent"); assert_eq!(bodies.len(), 2); assert_ne!(bodies[0]["agentId"], bodies[1]["agentId"]); @@ -462,7 +462,7 @@ async fn an_unreadable_transcript_costs_the_scrobble_nothing_but_its_model() { /// The `profile` block on a provisioning body, if there was one. fn provisioned_profile(stub: &Stub) -> Value { - stub.bodies_for("zone.quernstone.provisionAgent") + stub.bodies_for("bot.did.provisionAgent") .first() .and_then(|body| body.get("registration").cloned()) .unwrap_or(Value::Null) @@ -495,7 +495,7 @@ async fn a_subagent_is_provisioned_with_its_type_and_its_parent() { handle(&config, &session_start()).await; handle(&config, &subagent_start("aa36b4894bbc2bc20")).await; - let bodies = stub.bodies_for("zone.quernstone.provisionAgent"); + let bodies = stub.bodies_for("bot.did.provisionAgent"); let child = bodies.last().expect("the subagent was provisioned"); assert_eq!(child["registration"]["agentType"], "Explore"); assert_eq!(child["registration"]["harness"], "claude-code"); @@ -615,7 +615,7 @@ async fn an_agent_that_missed_session_start_is_provisioned_on_its_first_scrobble .await; assert_eq!( - stub.bodies_for("zone.quernstone.provisionAgent").len(), + stub.bodies_for("bot.did.provisionAgent").len(), 1, "one account, minted on the way through" ); @@ -657,7 +657,7 @@ async fn an_unprovisioned_agent_is_not_provisioned_by_an_ordinary_tool_call() { ) .await; - assert!(stub.bodies_for("zone.quernstone.provisionAgent").is_empty()); + assert!(stub.bodies_for("bot.did.provisionAgent").is_empty()); assert!(does_not_block(&output)); } @@ -688,7 +688,7 @@ async fn an_account_from_another_server_is_replaced_rather_than_stamped() { ) .await; - assert_eq!(stub.bodies_for("zone.quernstone.provisionAgent").len(), 1); + assert_eq!(stub.bodies_for("bot.did.provisionAgent").len(), 1); let stamped = updated_input(&output).expect("the call is stamped"); assert_ne!( stamped[STAMP_KEY].as_str(), @@ -728,7 +728,7 @@ async fn an_account_the_configured_server_already_holds_is_adopted() { .await; assert!( - stub.bodies_for("zone.quernstone.provisionAgent").is_empty(), + stub.bodies_for("bot.did.provisionAgent").is_empty(), "a second account was minted for one that already exists" ); let stamped = updated_input(&output).expect("the call is stamped"); @@ -760,7 +760,7 @@ async fn an_agent_that_already_has_an_account_here_is_not_reprovisioned() { } assert_eq!( - stub.bodies_for("zone.quernstone.provisionAgent").len(), + stub.bodies_for("bot.did.provisionAgent").len(), 1, "one account for the session, however many scrobbles" ); @@ -790,7 +790,7 @@ async fn stopping_a_subagent_deletes_only_that_account() { let output = handle(&config, &payload).await; assert!(does_not_block(&output)); - let deletes = stub.bodies_for("zone.quernstone.deleteAgent"); + let deletes = stub.bodies_for("bot.did.deleteAgent"); assert_eq!(deletes.len(), 1); assert_eq!(deletes[0]["did"], sub_did); @@ -817,7 +817,7 @@ async fn ending_a_session_deletes_the_session_and_its_subagents() { let output = handle(&config, &payload).await; assert!(does_not_block(&output)); - assert_eq!(stub.bodies_for("zone.quernstone.deleteAgent").len(), 3); + assert_eq!(stub.bodies_for("bot.did.deleteAgent").len(), 3); assert_eq!( StateStore::new(&config.state_path).load().agents.len(), 0, diff --git a/crates/didbot-index/src/bin/didbot-index.rs b/crates/didbot-index/src/bin/didbot-index.rs index 823c5b31..157e615b 100644 --- a/crates/didbot-index/src/bin/didbot-index.rs +++ b/crates/didbot-index/src/bin/didbot-index.rs @@ -540,7 +540,7 @@ async fn firehose_loop( .await; let collection = commit.collection.clone(); if collection != didbot_index::nsid::SCROBBLE - && collection != didbot_index::nsid::ACTOR_REGISTRATION + && collection != didbot_index::nsid::REGISTRATION { debug!(collection, "nothing this index reads; ignored"); continue; @@ -561,7 +561,7 @@ async fn firehose_loop( survey_now.notify_one(); continue; } - if collection == didbot_index::nsid::ACTOR_REGISTRATION { + if collection == didbot_index::nsid::REGISTRATION { match read_profile(&commit.did, &commit.uri, commit.record) { Ok(profile) => { let announced = shared.view.lock().await.see_profile(&profile); diff --git a/crates/didbot-index/src/directory.rs b/crates/didbot-index/src/directory.rs index 41469e31..b52de0b0 100644 --- a/crates/didbot-index/src/directory.rs +++ b/crates/didbot-index/src/directory.rs @@ -228,7 +228,7 @@ impl ServerDirectory for StaticDirectory { /// to. /// /// The chain is: a voucher's DID resolves to their personal data server, that -/// server holds their `zone.quernstone.agent.vouch` records, each live vouch +/// server holds their `bot.did.vouch` records, each live vouch /// names a subject, and the subject's DID resolves to the didbot server /// to read and bounds what may be read from it. /// @@ -343,7 +343,7 @@ impl VouchDirectory { voucher: &str, server: &ServerRef, ) -> Result { - let url = server.list_records_url(voucher, nsid::AGENT_VOUCH, VOUCH_PAGE_LIMIT); + let url = server.list_records_url(voucher, nsid::VOUCH, VOUCH_PAGE_LIMIT); fetch_json(self.resolver.http(), voucher, &url).await } diff --git a/crates/didbot-index/src/firehose.rs b/crates/didbot-index/src/firehose.rs index 203398f2..1d5dfefd 100644 --- a/crates/didbot-index/src/firehose.rs +++ b/crates/didbot-index/src/firehose.rs @@ -336,9 +336,9 @@ mod tests { "seq": 12, "instance": "18e4a2f", "did": "did:web:a.agents.localhost%3A3000", - "collection": "zone.quernstone.scrobble", + "collection": "com.vibescrobble.scrobble", "rkey": "3lq", - "uri": "at://did:web:a/zone.quernstone.scrobble/3lq", + "uri": "at://did:web:a/com.vibescrobble.scrobble/3lq", "cid": "bafyreiarecord", "commit": "bafyreiacommit", "rev": "3lqmnopqrstuv", @@ -364,9 +364,9 @@ mod tests { "seq": 1, "instance": "18e4a2f", "did": "did:web:a", - "collection": "zone.quernstone.scrobble", + "collection": "com.vibescrobble.scrobble", "rkey": "3lq", - "uri": "at://did:web:a/zone.quernstone.scrobble/3lq", + "uri": "at://did:web:a/com.vibescrobble.scrobble/3lq", "record": {}, }) .to_string(), diff --git a/crates/didbot-index/src/lib.rs b/crates/didbot-index/src/lib.rs index 7a4053d9..dc03137a 100644 --- a/crates/didbot-index/src/lib.rs +++ b/crates/didbot-index/src/lib.rs @@ -17,7 +17,7 @@ //! [`VouchDirectory`] resolves the voucher, reads their vouches, and //! resolves each subject to the server it names. //! 2. **Which accounts on that server does the vouch actually cover?** The -//! server answers `zone.quernstone.listAgents` with the accounts it claims +//! server answers `bot.did.listAgents` with the accounts it claims //! to host. A claim is not a fact. [`Indexer::survey`] admits an account //! only when it sits at or below both the zone the server mints under and //! the name the vouch bounded it to. diff --git a/crates/didbot-index/src/nsid.rs b/crates/didbot-index/src/nsid.rs index ae17fa56..0c6b55c0 100644 --- a/crates/didbot-index/src/nsid.rs +++ b/crates/didbot-index/src/nsid.rs @@ -12,15 +12,15 @@ /// Intent-level status update. One per interesting turn, and the record the /// feed is built from. -pub const SCROBBLE: &str = "zone.quernstone.scrobble"; +pub const SCROBBLE: &str = "com.vibescrobble.scrobble"; /// Signed ownership or delegation attestation. Read to decide which accounts /// an authority vouches for. -pub const AGENT_VOUCH: &str = "zone.quernstone.agent.vouch"; +pub const VOUCH: &str = "bot.did.vouch"; /// The hosting server's record of registering an account. Read for the account /// metadata the directory and the canvas display. -pub const ACTOR_REGISTRATION: &str = "zone.quernstone.actor.registration"; +pub const REGISTRATION: &str = "bot.did.registration"; #[cfg(test)] mod tests { @@ -36,7 +36,7 @@ mod tests { use didbot_lexicon::nsid as defined; assert_eq!(SCROBBLE, defined::SCROBBLE); - assert_eq!(AGENT_VOUCH, defined::AGENT_VOUCH); - assert_eq!(ACTOR_REGISTRATION, defined::ACTOR_REGISTRATION); + assert_eq!(VOUCH, defined::VOUCH); + assert_eq!(REGISTRATION, defined::REGISTRATION); } } diff --git a/crates/didbot-index/src/registration.rs b/crates/didbot-index/src/registration.rs index 7fb51a69..cd9f6a0e 100644 --- a/crates/didbot-index/src/registration.rs +++ b/crates/didbot-index/src/registration.rs @@ -34,7 +34,7 @@ use crate::survey::{FeedSource, Indexer}; /// The record key a profile always sits at. /// -/// `zone.quernstone.actor.registration` declares `key: "literal:self"`, so this is +/// `bot.did.registration` declares `key: "literal:self"`, so this is /// not a convention: a repository has exactly one profile and it is here. A /// record served from this collection under any other key is a server that /// does not implement the lexicon, and is refused rather than read. @@ -76,7 +76,7 @@ impl Indexer { pub async fn profile(&self, source: &FeedSource) -> Option { let server = source.server(); let did = source.did().as_str(); - let url = server.list_records_url(did, nsid::ACTOR_REGISTRATION, 1); + let url = server.list_records_url(did, nsid::REGISTRATION, 1); let listing: RecordsView = match fetch_json(self.http(), server.base_url(), &url).await { Ok(listing) => listing, @@ -115,10 +115,10 @@ pub fn read_profile(did: &str, uri: &str, value: Value) -> Result String { - format!("at://did:web:a.example/{}/{rkey}", nsid::ACTOR_REGISTRATION) + format!("at://did:web:a.example/{}/{rkey}", nsid::REGISTRATION) } #[test] fn reads_the_fields_the_index_acts_on() { let value = json!({ - "$type": nsid::ACTOR_REGISTRATION, + "$type": nsid::REGISTRATION, "did": "did:web:a.example", "provisionedAt": "2024-01-01T00:00:00Z", "operator": "did:web:agents.example", @@ -175,7 +175,7 @@ mod tests { "assurance": "shared-secret", "revision": 1, "actor": { - "$type": "zone.quernstone.actor.registration#agent", + "$type": "bot.did.registration#agent", "harness": "claude-code", "agentType": "Explore", "parent": "did:web:parent.example", diff --git a/crates/didbot-index/src/replay.rs b/crates/didbot-index/src/replay.rs index 76fc23c9..0c976c26 100644 --- a/crates/didbot-index/src/replay.rs +++ b/crates/didbot-index/src/replay.rs @@ -254,7 +254,7 @@ pub fn router(recording: Recording, pace: Pace) -> Router { Router::new() .route("/firehose", get(firehose)) .route("/health", get(health)) - .route("/xrpc/zone.quernstone.listAgents", get(list_agents)) + .route("/xrpc/bot.did.listAgents", get(list_agents)) .with_state(state) } diff --git a/crates/didbot-index/src/server.rs b/crates/didbot-index/src/server.rs index 211f4bb2..ba7491f2 100644 --- a/crates/didbot-index/src/server.rs +++ b/crates/didbot-index/src/server.rs @@ -125,7 +125,7 @@ impl ServerRef { /// Where this server lists the accounts it claims to host. pub fn list_agents_url(&self) -> String { - self.xrpc_url("zone.quernstone.listAgents") + self.xrpc_url("bot.did.listAgents") } /// Where one repository's records in one collection are read. @@ -247,14 +247,14 @@ mod tests { let server = ServerRef::parse("http://localhost:3000").expect("valid"); let url = server.list_records_url( "did:web:kestrel.agents.localhost%3A3000", - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", 20, ); assert_eq!( url, "http://localhost:3000/xrpc/com.atproto.repo.listRecords\ ?repo=did%3Aweb%3Akestrel.agents.localhost%253A3000\ - &collection=zone.quernstone.scrobble&limit=20" + &collection=com.vibescrobble.scrobble&limit=20" ); } } diff --git a/crates/didbot-index/src/survey.rs b/crates/didbot-index/src/survey.rs index 68d2f2f8..480fdac1 100644 --- a/crates/didbot-index/src/survey.rs +++ b/crates/didbot-index/src/survey.rs @@ -482,7 +482,7 @@ struct HealthView { zone: Option, } -/// The `zone.quernstone.listAgents` response. +/// The `bot.did.listAgents` response. #[derive(Debug, Deserialize)] struct AgentsView { #[serde(default)] diff --git a/crates/didbot-index/src/tests.rs b/crates/didbot-index/src/tests.rs index 21793584..31051044 100644 --- a/crates/didbot-index/src/tests.rs +++ b/crates/didbot-index/src/tests.rs @@ -387,7 +387,7 @@ async fn a_configured_directory_feeds_the_survey() { // --------------------------------------------------------------------------- fn vouch_uri(rkey: &str) -> String { - format!("at://{VOUCHER}/{}/{rkey}", nsid::AGENT_VOUCH) + format!("at://{VOUCHER}/{}/{rkey}", nsid::VOUCH) } /// One record as the voucher's personal data server would serve it. @@ -398,7 +398,7 @@ fn vouch_record(rkey: &str, value: Value) -> Value { /// A vouch for `subject` with no expiry. fn plain_vouch(subject: &str) -> Value { json!({ - "$type": nsid::AGENT_VOUCH, + "$type": nsid::VOUCH, "subject": subject, "createdAt": "2026-08-01T00:00:00Z", }) @@ -450,7 +450,7 @@ fn chain_stub(records: Vec, agents: Value) -> StubHttp { stub_with(ZONE, agents) .json(&format!("{PLC}/{VOUCHER}"), voucher_document) .json( - &voucher_pds().list_records_url(VOUCHER, nsid::AGENT_VOUCH, VOUCH_PAGE_LIMIT), + &voucher_pds().list_records_url(VOUCHER, nsid::VOUCH, VOUCH_PAGE_LIMIT), json!({ "records": records }), ) .json(&did_web_url(SERVICE_DID), service_document()) @@ -502,7 +502,7 @@ async fn an_expired_vouch_is_not_followed() { vec![vouch_record( "a", json!({ - "$type": nsid::AGENT_VOUCH, + "$type": nsid::VOUCH, "subject": SERVICE_DID, "createdAt": "2026-08-01T00:00:00Z", "expiresAt": "2026-08-10T00:00:00Z", @@ -527,7 +527,7 @@ async fn a_revoked_vouch_is_not_followed() { vouch_record( "newer", json!({ - "$type": nsid::AGENT_VOUCH, + "$type": nsid::VOUCH, "subject": MARMOT, "createdAt": "2026-08-02T00:00:00Z", "revokes": vouch_uri("older"), @@ -658,9 +658,9 @@ async fn reads_a_feed_from_a_discovered_source() { let records = json!({ "records": [ { - "uri": format!("at://{KESTREL}/zone.quernstone.scrobble/3mtufhkqapfi5"), + "uri": format!("at://{KESTREL}/com.vibescrobble.scrobble/3mtufhkqapfi5"), "value": { - "$type": "zone.quernstone.scrobble", + "$type": "com.vibescrobble.scrobble", "text": "quernstone placeholder work", "emoji": "🧭", "createdAt": "2026-08-24T18:00:00Z", @@ -670,13 +670,13 @@ async fn reads_a_feed_from_a_discovered_source() { }, // No emoji: something else in the scrobble collection. { - "uri": "at://x/zone.quernstone.scrobble/2", + "uri": "at://x/com.vibescrobble.scrobble/2", "value": { "text": "half a record", "createdAt": "2026-08-24T18:01:00Z" }, }, // A record answering a question nobody asked. { - "uri": "at://x/zone.quernstone.scrobble/3", - "value": { "$type": "zone.quernstone.memory", "text": "m", "emoji": "x", + "uri": "at://x/com.vibescrobble.scrobble/3", + "value": { "$type": "bot.did.memory", "text": "m", "emoji": "x", "createdAt": "2026-08-24T18:02:00Z" }, }, ] diff --git a/crates/didbot-index/src/view.rs b/crates/didbot-index/src/view.rs index 91fe080f..ea7023a6 100644 --- a/crates/didbot-index/src/view.rs +++ b/crates/didbot-index/src/view.rs @@ -30,7 +30,7 @@ use crate::work::{WorkGroup, WorkIndex}; pub enum Lineage { /// From the copy on a record the account wrote. Record, - /// From the account's `zone.quernstone.actor.registration`. + /// From the account's `bot.did.registration`. Profile, } diff --git a/crates/didbot-index/src/vouch.rs b/crates/didbot-index/src/vouch.rs index 460c14c2..ff5548d8 100644 --- a/crates/didbot-index/src/vouch.rs +++ b/crates/didbot-index/src/vouch.rs @@ -82,7 +82,7 @@ impl Vouch { // The collection was named in the request, so an absent `$type` is // accepted and a disagreeing one is not. if let Some(declared) = object.get("$type").and_then(Value::as_str) { - if declared != nsid::AGENT_VOUCH { + if declared != nsid::VOUCH { return Err(VouchError::WrongType(declared.to_owned())); } } @@ -185,7 +185,7 @@ mod tests { const SUBJECT: &str = "did:web:agents.localhost%3A3000"; fn uri(rkey: &str) -> String { - format!("at://{VOUCHER}/{}/{rkey}", nsid::AGENT_VOUCH) + format!("at://{VOUCHER}/{}/{rkey}", nsid::VOUCH) } fn vouch(rkey: &str, value: Value) -> Vouch { @@ -196,7 +196,7 @@ mod tests { vouch( rkey, json!({ - "$type": nsid::AGENT_VOUCH, + "$type": nsid::VOUCH, "subject": SUBJECT, "createdAt": "2026-08-01T00:00:00Z", }), @@ -208,9 +208,9 @@ mod tests { let read = vouch( "a", json!({ - "$type": nsid::AGENT_VOUCH, + "$type": nsid::VOUCH, "subject": SUBJECT, - "scopes": ["repo:zone.quernstone.scrobble", 7], + "scopes": ["repo:com.vibescrobble.scrobble", 7], "createdAt": "2026-08-01T00:00:00Z", "expiresAt": "2027-08-01T00:00:00Z", "revokes": uri("older"), @@ -219,7 +219,7 @@ mod tests { assert_eq!(read.subject, SUBJECT); assert_eq!(read.voucher, VOUCHER); - assert_eq!(read.scopes, ["repo:zone.quernstone.scrobble"]); + assert_eq!(read.scopes, ["repo:com.vibescrobble.scrobble"]); assert_eq!(read.created_at, datetime!(2026-08-01 00:00 UTC)); assert_eq!(read.expires_at, Some(datetime!(2027-08-01 00:00 UTC))); assert_eq!(read.revokes.as_deref(), Some(uri("older").as_str())); @@ -311,7 +311,7 @@ mod tests { let mine = plain("mine"); let theirs = Vouch::read( "did:plc:aaaaaaaaaaaaaaaaaaaaaaaa", - "at://did:plc:aaaaaaaaaaaaaaaaaaaaaaaa/zone.quernstone.agent.vouch/x", + "at://did:plc:aaaaaaaaaaaaaaaaaaaaaaaa/bot.did.vouch/x", &json!({ "subject": SUBJECT, "createdAt": "2026-08-02T00:00:00Z", diff --git a/crates/didbot-index/tests/firehose_wire.rs b/crates/didbot-index/tests/firehose_wire.rs index f1a8aff1..394bb14e 100644 --- a/crates/didbot-index/tests/firehose_wire.rs +++ b/crates/didbot-index/tests/firehose_wire.rs @@ -66,7 +66,7 @@ fn a_write_reads_with_every_field_a_server_sent() { assert_eq!(event, "commit"); assert_eq!(commit.seq, 42); assert_eq!(commit.did, "did:web:kestrel.agents.localhost"); - assert_eq!(commit.collection, "zone.quernstone.scrobble"); + assert_eq!(commit.collection, "com.vibescrobble.scrobble"); assert_eq!(commit.rkey, "3lqm0000abcd2"); assert_eq!(commit.cursor(), "01JEXAMPLEINSTANCE:42"); assert_eq!(commit.record["text"], "reading the firehose reconnect path"); diff --git a/crates/didbot-index/tests/reconnect.rs b/crates/didbot-index/tests/reconnect.rs index 8d04eef2..80f6666c 100644 --- a/crates/didbot-index/tests/reconnect.rs +++ b/crates/didbot-index/tests/reconnect.rs @@ -35,7 +35,7 @@ const PATIENCE: Duration = Duration::from_secs(20); const INSTANCE: &str = "stub-instance"; const DID: &str = "did:web:stormcock.agents.localhost"; -const COLLECTION: &str = "zone.quernstone.scrobble"; +const COLLECTION: &str = "com.vibescrobble.scrobble"; /// A firehose and nothing else, on a port nobody chose. /// diff --git a/crates/didbot-lexicon/src/lexicon.rs b/crates/didbot-lexicon/src/lexicon.rs index 9a922c06..a327897a 100644 --- a/crates/didbot-lexicon/src/lexicon.rs +++ b/crates/didbot-lexicon/src/lexicon.rs @@ -86,10 +86,10 @@ macro_rules! embed { } embed! { - "zone/quernstone/scrobble.json", - "zone/quernstone/memory.json", - "zone/quernstone/actor/registration.json", - "zone/quernstone/agent/vouch.json", + "com/vibescrobble/scrobble.json", + "bot/did/memory.json", + "bot/did/registration.json", + "bot/did/vouch.json", } /// Parses every embedded document. @@ -120,8 +120,8 @@ mod tests { #[test] fn document_id_matches_its_path() { - // lexicons/zone/quernstone/agent/vouch.json must declare - // id = zone.quernstone.agent.vouch. A mismatch here is the failure + // lexicons/bot/did/vouch.json must declare + // id = bot.did.vouch. A mismatch here is the failure // mode where a resolver fetches one schema and gets another. for embedded in EMBEDDED { let doc = embedded.parse().expect("parses"); @@ -205,7 +205,7 @@ mod tests { // A registration is one-per-account by design. A tid key there would // silently permit two of them. for doc in docs() { - if doc.id == nsid::ACTOR_REGISTRATION { + if doc.id == nsid::REGISTRATION { assert_eq!(doc.record_key(), Some("literal:self"), "{}", doc.id); } } diff --git a/crates/didbot-lexicon/src/nsid.rs b/crates/didbot-lexicon/src/nsid.rs index 5308d6e0..11755370 100644 --- a/crates/didbot-lexicon/src/nsid.rs +++ b/crates/didbot-lexicon/src/nsid.rs @@ -1,47 +1,50 @@ //! Namespace identifiers for this project's lexicons. //! -//! Every NSID this project defines is built from a single authority constant. -//! That constant is a deliberate placeholder: no domain has been registered, -//! and renaming it later is a one-line change here plus a data migration, -//! rather than a search across the tree. +//! Two authorities, because the records have two owners. What an account *is* +//! is this server's to define and lives under [`IDENTITY_AUTHORITY`]. What an +//! account *says* belongs to the feed that displays it, under +//! [`FEED_AUTHORITY`], whose repository is not this one. +//! +//! A record's authority is a fact about who defines its shape, not about who +//! writes it: this server writes scrobbles it did not define, which is +//! ordinary β€” a lexicon is the world's rather than its author's. -/// Reversed-domain authority for every lexicon this project defines. -/// -/// This is a placeholder. The word `quernstone` is chosen to be meaningless -/// and greppable: finding every place the namespace leaks into code, fixtures -/// or documentation is `grep -r quernstone`. -/// -/// Changing this constant changes the NSID of every record type, which is a -/// breaking protocol change and a data migration, not a rename. -pub const AUTHORITY: &str = "zone.quernstone"; +/// Reversed-domain authority for the records this server defines. +pub const IDENTITY_AUTHORITY: &str = "bot.did"; + +/// Reversed-domain authority for the feed's records, defined elsewhere. +pub const FEED_AUTHORITY: &str = "com.vibescrobble"; + +/// Every authority whose records this project holds a schema for. +pub const AUTHORITIES: &[&str] = &[IDENTITY_AUTHORITY, FEED_AUTHORITY]; -/// Builds an NSID under [`AUTHORITY`]. +/// Builds an NSID under [`IDENTITY_AUTHORITY`]. /// /// ``` /// use didbot_lexicon::nsid; -/// assert_eq!(nsid::under("scrobble"), "zone.quernstone.scrobble"); -/// assert_eq!(nsid::under("agent.vouch"), "zone.quernstone.agent.vouch"); +/// assert_eq!(nsid::under("registration"), "bot.did.registration"); +/// assert_eq!(nsid::under("vouch"), "bot.did.vouch"); /// ``` pub fn under(name: &str) -> String { - format!("{AUTHORITY}.{name}") + format!("{IDENTITY_AUTHORITY}.{name}") } /// Intent-level status update, one per interesting turn. -pub const SCROBBLE: &str = "zone.quernstone.scrobble"; +pub const SCROBBLE: &str = "com.vibescrobble.scrobble"; /// Structured agent memory. Not intended for federation. -pub const MEMORY: &str = "zone.quernstone.memory"; +pub const MEMORY: &str = "bot.did.memory"; /// The hosting server's record of registering an account and keeping it, /// whatever kind of account it is. Not an identity β€” that is the DID and the /// keys in its document β€” and not a credential. It replaced -/// `zone.quernstone.agent.identity`, which described agents alone, and -/// absorbed `zone.quernstone.agent.attestation`, whose subject β€” how an +/// `bot.did.identity`, which described agents alone, and +/// absorbed `bot.did.attestation`, whose subject β€” how an /// account was admitted β€” is part of what the server registered. -pub const ACTOR_REGISTRATION: &str = "zone.quernstone.actor.registration"; +pub const REGISTRATION: &str = "bot.did.registration"; /// Signed ownership or delegation attestation. -pub const AGENT_VOUCH: &str = "zone.quernstone.agent.vouch"; +pub const VOUCH: &str = "bot.did.vouch"; /// Every record NSID this project defines. /// @@ -50,35 +53,43 @@ pub const AGENT_VOUCH: &str = "zone.quernstone.agent.vouch"; /// in any collection that is a well-formed NSID and validates it against a /// schema only when it holds one. This list is what "holds one" means, /// and nothing else. -pub const ALL: &[&str] = &[SCROBBLE, MEMORY, ACTOR_REGISTRATION, AGENT_VOUCH]; +pub const ALL: &[&str] = &[SCROBBLE, MEMORY, REGISTRATION, VOUCH]; #[cfg(test)] mod tests { use super::*; #[test] - fn every_constant_sits_under_the_authority() { + fn every_constant_sits_under_a_known_authority() { for id in ALL { assert!( - id.starts_with(&format!("{AUTHORITY}.")), - "{id} is not under {AUTHORITY}: the authority constant is not the single source" + AUTHORITIES.iter().any(|a| id.starts_with(&format!("{a}."))), + "{id} is under no authority this crate names" ); } } #[test] fn under_agrees_with_the_constants() { - assert_eq!(under("scrobble"), SCROBBLE); assert_eq!(under("memory"), MEMORY); - assert_eq!(under("actor.registration"), ACTOR_REGISTRATION); - assert_eq!(under("agent.vouch"), AGENT_VOUCH); + assert_eq!(under("registration"), REGISTRATION); + assert_eq!(under("vouch"), VOUCH); } + /// The feed's record is not this server's to name. #[test] - fn authority_is_a_reversed_domain_with_at_least_two_segments() { + fn the_scrobble_is_not_under_this_server_s_authority() { + assert!(SCROBBLE.starts_with(FEED_AUTHORITY)); + assert!(!SCROBBLE.starts_with(IDENTITY_AUTHORITY)); + } + + #[test] + fn every_authority_is_a_reversed_domain_with_at_least_two_segments() { // An NSID authority must have at least two segments, so that a record // collection always contains at least two dots. atproto Spaces relies // on exactly this to tell a collection apart from a space marker. - assert!(AUTHORITY.split('.').count() >= 2); + for authority in AUTHORITIES { + assert!(authority.split('.').count() >= 2); + } } } diff --git a/crates/didbot-mcp/src/lib.rs b/crates/didbot-mcp/src/lib.rs index f04e6657..c3283948 100644 --- a/crates/didbot-mcp/src/lib.rs +++ b/crates/didbot-mcp/src/lib.rs @@ -113,14 +113,14 @@ pub use didbot_hook::MODEL_MAX_LENGTH; /// The lexicon's `maxLength` for a scrobble's `agentType`. /// -/// Matches the same field on `zone.quernstone.actor.registration`, which is the +/// Matches the same field on `bot.did.registration`, which is the /// canonical home for an account's type; a copy of a value must not be able to /// hold more than the original. pub const AGENT_TYPE_MAX_LENGTH: usize = 256; /// The lexicon's `maxLength` for a scrobble's `parent`. /// -/// Matches the same field on `zone.quernstone.actor.registration`, for the reason +/// Matches the same field on `bot.did.registration`, for the reason /// [`AGENT_TYPE_MAX_LENGTH`] gives: a copy of a value must not be able to hold /// more than the original. Far above any DID this project mints, which is a /// `did:web` over one DNS label and a zone. @@ -508,7 +508,7 @@ fn stamped_string(arguments: &JsonObject, key: &str, max_length: usize) -> Optio } } -/// Builds the `zone.quernstone.scrobble` record body for a call. +/// Builds the `com.vibescrobble.scrobble` record body for a call. /// /// `created_at` is passed in rather than read from the clock so that the shape /// of the record can be tested without the test depending on the time of day. @@ -925,7 +925,7 @@ impl PdsClient { impl PdsClient { /// The `listAgents` endpoint the name book reads. pub fn list_agents_url(&self) -> String { - format!("{}/xrpc/zone.quernstone.listAgents", self.base_url) + format!("{}/xrpc/bot.did.listAgents", self.base_url) } /// Asks the server what its accounts are called. @@ -1001,10 +1001,10 @@ fn error_message(body: &str) -> Option { /// ``` /// use didbot_mcp::rkey_from_uri; /// assert_eq!( -/// rkey_from_uri("at://did:web:x/zone.quernstone.scrobble/3lab2c"), +/// rkey_from_uri("at://did:web:x/com.vibescrobble.scrobble/3lab2c"), /// Some("3lab2c") /// ); -/// assert_eq!(rkey_from_uri("at://did:web:x/zone.quernstone.scrobble"), None); +/// assert_eq!(rkey_from_uri("at://did:web:x/com.vibescrobble.scrobble"), None); /// ``` pub fn rkey_from_uri(uri: &str) -> Option<&str> { let rest = uri.strip_prefix("at://")?; @@ -1751,7 +1751,7 @@ mod tests { // actually trying to say. let embedded = didbot_lexicon::lexicon::EMBEDDED .iter() - .find(|e| e.path.ends_with("zone/quernstone/scrobble.json")) + .find(|e| e.path.ends_with("com/vibescrobble/scrobble.json")) .expect("the scrobble lexicon is embedded"); let doc = embedded.parse().expect("the scrobble lexicon parses"); let properties = doc @@ -1899,7 +1899,7 @@ mod tests { #[test] fn rkey_is_read_off_the_uri() { assert_eq!( - rkey_from_uri("at://did:web:x/zone.quernstone.scrobble/3lab2c7"), + rkey_from_uri("at://did:web:x/com.vibescrobble.scrobble/3lab2c7"), Some("3lab2c7") ); assert_eq!(rkey_from_uri("https://example/a/b/c"), None); @@ -2245,7 +2245,7 @@ mod tests { // record is validated against another. let embedded = didbot_lexicon::lexicon::EMBEDDED .iter() - .find(|e| e.path.ends_with("zone/quernstone/scrobble.json")) + .find(|e| e.path.ends_with("com/vibescrobble/scrobble.json")) .expect("the scrobble lexicon is embedded"); let doc = embedded.parse().expect("the scrobble lexicon parses"); let properties = doc @@ -2277,7 +2277,7 @@ mod tests { fn the_lexicon_requires_the_emoji_and_describes_it() { let embedded = didbot_lexicon::lexicon::EMBEDDED .iter() - .find(|e| e.path.ends_with("zone/quernstone/scrobble.json")) + .find(|e| e.path.ends_with("com/vibescrobble/scrobble.json")) .expect("the scrobble lexicon is embedded"); let doc = embedded.parse().expect("the scrobble lexicon parses"); let record = doc diff --git a/crates/didbot-mcp/tests/create_record.rs b/crates/didbot-mcp/tests/create_record.rs index f36075bc..c84bde30 100644 --- a/crates/didbot-mcp/tests/create_record.rs +++ b/crates/didbot-mcp/tests/create_record.rs @@ -87,7 +87,7 @@ async fn stub(status: u16, body: &'static str) -> (String, Seen) { async fn a_successful_write_posts_to_create_record_and_returns_the_rkey() { let (base, seen) = stub( 200, - r#"{"uri":"at://did:web:localhost%3A3000:agent:a1/zone.quernstone.scrobble/3lzz9q"}"#, + r#"{"uri":"at://did:web:localhost%3A3000:agent:a1/com.vibescrobble.scrobble/3lzz9q"}"#, ) .await; let client = PdsClient::new(&base); @@ -194,7 +194,7 @@ async fn an_unstamped_call_never_reaches_the_personal_data_server() { async fn a_stamped_call_writes_the_record_the_contract_describes() { let (base, seen) = stub( 200, - r#"{"uri":"at://did:web:x/zone.quernstone.scrobble/3ab"}"#, + r#"{"uri":"at://did:web:x/com.vibescrobble.scrobble/3ab"}"#, ) .await; let server = ScrobbleServer::new(PdsClient::new(&base)); @@ -257,7 +257,7 @@ async fn an_agent_repeating_its_previous_emoji_is_refused() { // never reaches the personal data server: the stub only ever answers once. let (base, _seen) = stub( 200, - r#"{"uri":"at://did:web:x/zone.quernstone.scrobble/3ab"}"#, + r#"{"uri":"at://did:web:x/com.vibescrobble.scrobble/3ab"}"#, ) .await; let server = ScrobbleServer::new(PdsClient::new(&base)); diff --git a/crates/didbot-pds/src/commit.rs b/crates/didbot-pds/src/commit.rs index 419941cc..f5ca5f8d 100644 --- a/crates/didbot-pds/src/commit.rs +++ b/crates/didbot-pds/src/commit.rs @@ -154,14 +154,14 @@ mod tests { fn a_commit_names_its_record_as_an_at_uri() { let commit = CommitEvent::new( "did:web:kestrel.agents.localhost%3A3000", - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", "3lqmnop", serde_json::json!({"text": "reading the cart"}), somewhere(), ); assert_eq!( commit.uri(), - "at://did:web:kestrel.agents.localhost%3A3000/zone.quernstone.scrobble/3lqmnop" + "at://did:web:kestrel.agents.localhost%3A3000/com.vibescrobble.scrobble/3lqmnop" ); } diff --git a/crates/didbot-pds/src/export.rs b/crates/didbot-pds/src/export.rs index 150b9b88..a53c3edb 100644 --- a/crates/didbot-pds/src/export.rs +++ b/crates/didbot-pds/src/export.rs @@ -183,7 +183,7 @@ mod tests { fn scrobble(text: &str) -> serde_json::Value { serde_json::json!({ - "$type": "zone.quernstone.scrobble", + "$type": "com.vibescrobble.scrobble", "text": text, "emoji": "🜁", "createdAt": "2026-08-26T00:00:00Z", @@ -209,7 +209,7 @@ mod tests { store .put( "did:web:a.example", - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", None, scrobble(text), &Precondition::Unconditional, @@ -250,7 +250,7 @@ mod tests { store .put( "did:web:a.example", - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", None, scrobble("a"), &Precondition::Unconditional, @@ -259,7 +259,7 @@ mod tests { store .put( "did:web:b.example", - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", None, scrobble("b"), &Precondition::Unconditional, @@ -281,7 +281,7 @@ mod tests { store .put( "did:web:a.example", - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", None, scrobble("quernstone"), &Precondition::Unconditional, @@ -304,7 +304,7 @@ mod tests { let written = store .put( "did:web:a.example", - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", None, record.clone(), &Precondition::Unconditional, @@ -312,7 +312,7 @@ mod tests { .expect("valid"); let mut builder = Builder::new(); let inserted = builder - .insert("zone.quernstone.scrobble", &written.rkey, &record) + .insert("com.vibescrobble.scrobble", &written.rkey, &record) .expect("valid"); assert_eq!(written.cid, inserted); assert_eq!(content_id(&record).expect("a data-model record"), inserted); diff --git a/crates/didbot-pds/src/provision.rs b/crates/didbot-pds/src/provision.rs index 11a361da..9759ace4 100644 --- a/crates/didbot-pds/src/provision.rs +++ b/crates/didbot-pds/src/provision.rs @@ -50,7 +50,7 @@ pub struct ProvisionRequest { pub claim: AttestationClaim, /// What the harness says about the agent this account is for. /// - /// Written into the account's `zone.quernstone.actor.registration` as + /// Written into the account's `bot.did.registration` as /// part of provisioning, by way of the ledger the record is composed from. /// Every field is optional, and every one of them is an admission fact /// rather than present state; see [`RegistrationFacts`]. diff --git a/crates/didbot-pds/src/records.rs b/crates/didbot-pds/src/records.rs index f590568f..f0155466 100644 --- a/crates/didbot-pds/src/records.rs +++ b/crates/didbot-pds/src/records.rs @@ -568,7 +568,7 @@ pub trait RecordStore: Send + Sync { /// /// ``` /// use didbot_pds::records::validate_collection; -/// assert!(validate_collection("zone.quernstone.scrobble").is_ok()); +/// assert!(validate_collection("com.vibescrobble.scrobble").is_ok()); /// // A lexicon this project did not author, and does not hold. /// assert!(validate_collection("app.bsky.feed.post").is_ok()); /// // Not an NSID at all, so it could never address anything. @@ -591,7 +591,7 @@ pub fn validate_collection(collection: &str) -> Result<(), RecordError> { /// /// ``` /// use didbot_pds::records::schema_is_held; -/// assert!(schema_is_held("zone.quernstone.scrobble")); +/// assert!(schema_is_held("com.vibescrobble.scrobble")); /// assert!(!schema_is_held("app.bsky.feed.post")); /// ``` pub fn schema_is_held(collection: &str) -> bool { @@ -655,14 +655,14 @@ pub fn validation_status(collection: &str, stance: Stance) -> &'static str { /// the shape of the record, it is this deployment's rule about its own /// repositories. Putting it in the document would be publishing a claim about /// somebody else's server. -pub const SERVER_AUTHORED: &[&str] = &[didbot_lexicon::nsid::ACTOR_REGISTRATION]; +pub const SERVER_AUTHORED: &[&str] = &[didbot_lexicon::nsid::REGISTRATION]; /// Whether a collection is one only this server writes. /// /// ``` /// use didbot_pds::records::server_authored; -/// assert!(server_authored("zone.quernstone.actor.registration")); -/// assert!(!server_authored("zone.quernstone.scrobble")); +/// assert!(server_authored("bot.did.registration")); +/// assert!(!server_authored("com.vibescrobble.scrobble")); /// ``` pub fn server_authored(collection: &str) -> bool { SERVER_AUTHORED.contains(&collection) @@ -704,8 +704,8 @@ pub fn server_authored(collection: &str) -> bool { /// /// ``` /// use didbot_pds::records::guard_server_authored; -/// assert!(guard_server_authored("zone.quernstone.scrobble").is_ok()); -/// assert!(guard_server_authored("zone.quernstone.actor.registration").is_err()); +/// assert!(guard_server_authored("com.vibescrobble.scrobble").is_ok()); +/// assert!(guard_server_authored("bot.did.registration").is_err()); /// ``` pub fn guard_server_authored(collection: &str) -> Result<(), RecordError> { if server_authored(collection) { @@ -727,8 +727,8 @@ pub fn guard_server_authored(collection: &str) -> Result<(), RecordError> { /// /// ``` /// use didbot_pds::records::validate_removal; -/// assert!(validate_removal("zone.quernstone.scrobble").is_ok()); -/// assert!(validate_removal("zone.quernstone.actor.registration").is_err()); +/// assert!(validate_removal("com.vibescrobble.scrobble").is_ok()); +/// assert!(validate_removal("bot.did.registration").is_err()); /// assert!(validate_removal("example.post").is_err()); /// ``` pub fn validate_removal(collection: &str) -> Result<(), RecordError> { @@ -826,11 +826,11 @@ pub fn validate_shape(collection: &str, record: &Value) -> Result<(), RecordErro /// /// let good = json!({"text": "reading the MST encoder", "emoji": "πŸ”", /// "createdAt": "2024-01-01T00:00:00Z"}); -/// assert!(validate("zone.quernstone.scrobble", &good).is_ok()); +/// assert!(validate("com.vibescrobble.scrobble", &good).is_ok()); /// /// // No emoji, and `text` is a number. A schema is held, so this is refused. /// let bad = json!({"text": 12345, "createdAt": "2024-01-01T00:00:00Z"}); -/// assert!(validate("zone.quernstone.scrobble", &bad).is_err()); +/// assert!(validate("com.vibescrobble.scrobble", &bad).is_err()); /// /// // No schema is held for this one, so it is stored as written. /// let foreign = json!({"$type": "app.bsky.feed.post", "text": "hello", @@ -1033,14 +1033,14 @@ pub enum ResolvedKey { /// /// // A singleton lands at `self` whether or not the caller says so. /// assert_eq!( -/// resolve_key(nsid::ACTOR_REGISTRATION, None).unwrap(), +/// resolve_key(nsid::REGISTRATION, None).unwrap(), /// ResolvedKey::Fixed("self".to_owned()) /// ); /// assert_eq!( -/// resolve_key(nsid::ACTOR_REGISTRATION, Some("self")).unwrap(), +/// resolve_key(nsid::REGISTRATION, Some("self")).unwrap(), /// ResolvedKey::Fixed("self".to_owned()) /// ); -/// assert!(resolve_key(nsid::ACTOR_REGISTRATION, Some("elsewhere")).is_err()); +/// assert!(resolve_key(nsid::REGISTRATION, Some("elsewhere")).is_err()); /// /// // A scrobble gets a minted TID. /// assert_eq!(resolve_key(nsid::SCROBBLE, None).unwrap(), ResolvedKey::Mint); diff --git a/crates/didbot-pds/src/registration.rs b/crates/didbot-pds/src/registration.rs index 7ced68c9..248a4852 100644 --- a/crates/didbot-pds/src/registration.rs +++ b/crates/didbot-pds/src/registration.rs @@ -1,6 +1,6 @@ //! The public registration record, composed from the ledger it mirrors. //! -//! `zone.quernstone.actor.registration` is the hosting server's own record of +//! `bot.did.registration` is the hosting server's own record of //! admitting an account and keeping it: the identifier it minted, when it //! minted it, who is accountable for it, which backend admitted the request, //! and every handle the account has held. It sits at `self` in the account's @@ -65,7 +65,7 @@ use crate::account::AgentAccount; use crate::ledger::{AgentLedger, LedgerEvent}; /// The collection this module writes. -pub const COLLECTION: &str = didbot_lexicon::nsid::ACTOR_REGISTRATION; +pub const COLLECTION: &str = didbot_lexicon::nsid::REGISTRATION; /// The union member this server composes today. /// @@ -73,7 +73,7 @@ pub const COLLECTION: &str = didbot_lexicon::nsid::ACTOR_REGISTRATION; /// kind provisioning knows how to be asked for. A pipeline or a host is the /// same record with a different member, and the work is in the request rather /// than here. -const AGENT: &str = "zone.quernstone.actor.registration#agent"; +const AGENT: &str = "bot.did.registration#agent"; /// The record key it is written under, which its lexicon fixes. pub const RKEY: &str = "self"; @@ -276,10 +276,7 @@ mod tests { let record = compose(&ledger, &account(), "did:web:agents.localhost", OWNER).expect("a record"); assert_eq!(record["owner"], OWNER); - assert_eq!( - record["actor"]["$type"], - "zone.quernstone.actor.registration#agent" - ); + assert_eq!(record["actor"]["$type"], "bot.did.registration#agent"); assert_eq!(record["generation"], 1); } diff --git a/crates/didbot-pds/src/schema/mod.rs b/crates/didbot-pds/src/schema/mod.rs index fad5dbff..c1abc51b 100644 --- a/crates/didbot-pds/src/schema/mod.rs +++ b/crates/didbot-pds/src/schema/mod.rs @@ -782,7 +782,7 @@ mod tests { Some("tid") ); assert_eq!( - catalog.record_key(didbot_lexicon::nsid::ACTOR_REGISTRATION), + catalog.record_key(didbot_lexicon::nsid::REGISTRATION), Some("literal:self") ); assert_eq!(catalog.record_key("com.example.nothing"), None); diff --git a/crates/didbot-pds/src/server.rs b/crates/didbot-pds/src/server.rs index cafec971..027bf503 100644 --- a/crates/didbot-pds/src/server.rs +++ b/crates/didbot-pds/src/server.rs @@ -9,7 +9,7 @@ //! # Why it is not born the way agents are //! //! An agent account is born populated: a ledger entry, a -//! `zone.quernstone.actor.registration` composed from it, and a +//! `bot.did.registration` composed from it, and a //! `app.bsky.actor.profile` an ordinary client can read. None of //! the three fits here. There is no provisioning to record, because nothing //! provisioned this account. There is no attestation to mirror, because the diff --git a/crates/didbot-pds/tests/actor_registration.rs b/crates/didbot-pds/tests/actor_registration.rs index 85589a7f..079cb80d 100644 --- a/crates/didbot-pds/tests/actor_registration.rs +++ b/crates/didbot-pds/tests/actor_registration.rs @@ -1,4 +1,4 @@ -//! The union in `zone.quernstone.actor.registration` is what enforces per-kind +//! The union in `bot.did.registration` is what enforces per-kind //! fields. //! //! An account's kind decides which facts it must carry, and a lexicon cannot @@ -17,7 +17,7 @@ use serde_json::{json, Value}; /// The fields every kind carries, with `actor` left to the caller. fn registration(actor: Value) -> Value { json!({ - "$type": nsid::ACTOR_REGISTRATION, + "$type": nsid::REGISTRATION, "did": "did:web:mossy-vole.agents.example", "provisionedAt": "2026-08-28T00:00:00Z", "operator": "did:web:agents.example", @@ -31,7 +31,7 @@ fn registration(actor: Value) -> Value { fn refusal(actor: Value) -> String { let record = registration(actor); - let error = validate_shape(nsid::ACTOR_REGISTRATION, &record) + let error = validate_shape(nsid::REGISTRATION, &record) .expect_err("the record is missing a field its own kind requires"); error.to_string() } @@ -44,25 +44,25 @@ fn an_agent_requires_nothing_of_its_own() { // absent rather than invented β€” so the member carries the discriminator // and requires none of them. for actor in [ - json!({"$type": "zone.quernstone.actor.registration#agent"}), + json!({"$type": "bot.did.registration#agent"}), json!({ - "$type": "zone.quernstone.actor.registration#agent", + "$type": "bot.did.registration#agent", "harness": "claude-code", "agentType": "Explore", "parent": "did:web:basalt-otter.agents.example", "admittedOn": "node-alpha", }), ] { - assert!(validate_shape(nsid::ACTOR_REGISTRATION, ®istration(actor)).is_ok()); + assert!(validate_shape(nsid::REGISTRATION, ®istration(actor)).is_ok()); } } #[test] fn a_pipeline_carries_a_binding_and_a_platform() { assert!(validate_shape( - nsid::ACTOR_REGISTRATION, + nsid::REGISTRATION, ®istration(json!({ - "$type": "zone.quernstone.actor.registration#pipeline", + "$type": "bot.did.registration#pipeline", "platform": "github-actions", "project": "permadeath/didbot", "projectId": "123456789", @@ -79,7 +79,7 @@ fn a_pipeline_carries_a_binding_and_a_platform() { .is_ok()); let error = refusal(json!({ - "$type": "zone.quernstone.actor.registration#pipeline", + "$type": "bot.did.registration#pipeline", "platform": "github-actions", })); assert!(error.contains("binding"), "{error}"); @@ -88,9 +88,9 @@ fn a_pipeline_carries_a_binding_and_a_platform() { #[test] fn a_host_carries_a_stable_identifier() { assert!(validate_shape( - nsid::ACTOR_REGISTRATION, + nsid::REGISTRATION, ®istration(json!({ - "$type": "zone.quernstone.actor.registration#host", + "$type": "bot.did.registration#host", "platform": "aws-ec2", "hostId": "i-0123456789abcdef0", "hostLabel": "builder-01", @@ -104,7 +104,7 @@ fn a_host_carries_a_stable_identifier() { .is_ok()); let error = refusal(json!({ - "$type": "zone.quernstone.actor.registration#host", + "$type": "bot.did.registration#host", "platform": "aws-ec2", })); assert!(error.contains("hostId"), "{error}"); @@ -113,7 +113,7 @@ fn a_host_carries_a_stable_identifier() { #[test] fn a_binding_without_a_subject_is_refused() { let error = refusal(json!({ - "$type": "zone.quernstone.actor.registration#pipeline", + "$type": "bot.did.registration#pipeline", "platform": "gitlab-ci", "binding": {"verifiedAt": "2026-08-28T00:00:00Z"}, })); @@ -126,9 +126,9 @@ fn an_unrecognized_kind_is_carried_rather_than_refused() { // predates, and an index or a second server that refused the record would // have to be upgraded before anybody could mint one. assert!(validate_shape( - nsid::ACTOR_REGISTRATION, + nsid::REGISTRATION, ®istration(json!({ - "$type": "zone.quernstone.actor.registration#satellite", + "$type": "bot.did.registration#satellite", "orbit": "geostationary", })) ) @@ -138,10 +138,10 @@ fn an_unrecognized_kind_is_carried_rather_than_refused() { #[test] fn the_kind_itself_is_required() { let mut record = registration(json!({ - "$type": "zone.quernstone.actor.registration#agent" + "$type": "bot.did.registration#agent" })); record.as_object_mut().expect("an object").remove("actor"); - let error = validate_shape(nsid::ACTOR_REGISTRATION, &record) + let error = validate_shape(nsid::REGISTRATION, &record) .expect_err("a record with no kind says nothing about what it is"); assert!(error.to_string().contains("actor"), "{error}"); } @@ -151,10 +151,9 @@ fn the_kind_itself_is_required() { #[test] fn an_account_with_no_owner_is_refused() { let mut record = registration(json!({ - "$type": "zone.quernstone.actor.registration#agent" + "$type": "bot.did.registration#agent" })); record.as_object_mut().expect("an object").remove("owner"); - let error = - validate_shape(nsid::ACTOR_REGISTRATION, &record).expect_err("an owner is required"); + let error = validate_shape(nsid::REGISTRATION, &record).expect_err("an owner is required"); assert!(error.to_string().contains("owner"), "{error}"); } diff --git a/crates/didbot-pds/tests/authored.rs b/crates/didbot-pds/tests/authored.rs index 2f5280bd..08c15be3 100644 --- a/crates/didbot-pds/tests/authored.rs +++ b/crates/didbot-pds/tests/authored.rs @@ -69,7 +69,7 @@ fn account(pds: &impl Registry, agent_id: &str) -> String { /// for being malformed. The refusals here have to be about authorship. fn well_formed() -> Value { json!({ - "$type": nsid::ACTOR_REGISTRATION, + "$type": nsid::REGISTRATION, "did": DID, "provisionedAt": "2026-08-25T10:00:00Z", "operator": "did:web:agents.localhost", @@ -77,13 +77,13 @@ fn well_formed() -> Value { "admittedBy": "tpm", "assurance": "hardware-attested", "revision": 99, - "actor": {"$type": "zone.quernstone.actor.registration#agent"}, + "actor": {"$type": "bot.did.registration#agent"}, }) } fn identity_of(pds: &impl Registry, did: &str) -> Value { let records = pds - .list_records(did, nsid::ACTOR_REGISTRATION, &ListParams::new(10)) + .list_records(did, nsid::REGISTRATION, &ListParams::new(10)) .expect("the repository should exist"); assert_eq!(records.len(), 1); records[0].1.clone() @@ -97,9 +97,9 @@ fn identity_of(pds: &impl Registry, did: &str) -> Value { /// stops naming it the whole guard is inert, and nothing else would say so. #[test] fn the_identity_collection_is_server_authored() { - assert!(server_authored(nsid::ACTOR_REGISTRATION)); + assert!(server_authored(nsid::REGISTRATION)); assert!( - SERVER_AUTHORED.contains(&nsid::ACTOR_REGISTRATION), + SERVER_AUTHORED.contains(&nsid::REGISTRATION), "the identity record must be on the list or nothing is protected" ); } @@ -108,7 +108,7 @@ fn the_identity_collection_is_server_authored() { /// stay writable, or the guard has broken the server rather than protected it. #[test] fn nothing_an_agent_writes_is_server_authored() { - for collection in [nsid::SCROBBLE, nsid::MEMORY, nsid::AGENT_VOUCH] { + for collection in [nsid::SCROBBLE, nsid::MEMORY, nsid::VOUCH] { assert!( !server_authored(collection), "{collection} must stay writable by the account" @@ -144,13 +144,13 @@ fn an_account_cannot_overwrite_its_own_identity_record() { match pds.put_record( &did, - nsid::ACTOR_REGISTRATION, + nsid::REGISTRATION, None, well_formed(), &Swap::default(), ) { Err(ProvisionError::Record(RecordError::ServerAuthored { collection })) => { - assert_eq!(collection, nsid::ACTOR_REGISTRATION); + assert_eq!(collection, nsid::REGISTRATION); } other => panic!("the write should have been refused: {other:?}"), } @@ -171,7 +171,7 @@ fn naming_the_key_does_not_help() { for rkey in [Some(registration::RKEY), Some("elsewhere"), None] { match pds.put_record( &did, - nsid::ACTOR_REGISTRATION, + nsid::REGISTRATION, rkey, well_formed(), &Swap::default(), @@ -198,7 +198,7 @@ fn the_refusal_does_not_depend_on_the_record_being_valid() { ] { match pds.put_record( &did, - nsid::ACTOR_REGISTRATION, + nsid::REGISTRATION, None, record.clone(), &Swap::default(), @@ -220,12 +220,12 @@ fn an_account_cannot_delete_its_own_identity_record() { match pds.delete_record( &did, - nsid::ACTOR_REGISTRATION, + nsid::REGISTRATION, registration::RKEY, &Swap::default(), ) { Err(ProvisionError::Record(RecordError::ServerAuthored { collection })) => { - assert_eq!(collection, nsid::ACTOR_REGISTRATION); + assert_eq!(collection, nsid::REGISTRATION); } other => panic!("the deletion should have been refused: {other:?}"), } @@ -244,12 +244,7 @@ fn an_account_cannot_delete_its_own_identity_record() { fn deleting_a_key_that_holds_nothing_is_still_refused() { let pds = provisioner(); let did = account(&pds, "kestrel"); - match pds.delete_record( - &did, - nsid::ACTOR_REGISTRATION, - "nothing-here", - &Swap::default(), - ) { + match pds.delete_record(&did, nsid::REGISTRATION, "nothing-here", &Swap::default()) { Err(ProvisionError::Record(RecordError::ServerAuthored { .. })) => {} other => panic!("expected a refusal: {other:?}"), } @@ -288,7 +283,7 @@ fn the_memory_store_refuses_it() { assert!(matches!( store.put( DID, - nsid::ACTOR_REGISTRATION, + nsid::REGISTRATION, None, well_formed(), &Precondition::Unconditional @@ -298,7 +293,7 @@ fn the_memory_store_refuses_it() { assert!(matches!( store.remove( DID, - nsid::ACTOR_REGISTRATION, + nsid::REGISTRATION, registration::RKEY, &Precondition::Unconditional ), @@ -307,7 +302,7 @@ fn the_memory_store_refuses_it() { // And the server's own path still works, or the record could never be // written at all. store - .put_authored(DID, nsid::ACTOR_REGISTRATION, well_formed()) + .put_authored(DID, nsid::REGISTRATION, well_formed()) .expect("the server writes it through its own door"); } @@ -321,7 +316,7 @@ fn the_durable_store_refuses_it() { assert!(matches!( store.put( DID, - nsid::ACTOR_REGISTRATION, + nsid::REGISTRATION, None, well_formed(), &Precondition::Unconditional @@ -331,14 +326,14 @@ fn the_durable_store_refuses_it() { assert!(matches!( store.remove( DID, - nsid::ACTOR_REGISTRATION, + nsid::REGISTRATION, registration::RKEY, &Precondition::Unconditional ), Err(RecordError::ServerAuthored { .. }) )); store - .put_authored(DID, nsid::ACTOR_REGISTRATION, well_formed()) + .put_authored(DID, nsid::REGISTRATION, well_formed()) .expect("the server writes it through its own door"); let _ = std::fs::remove_dir_all(&dir); @@ -358,7 +353,7 @@ fn a_batch_containing_one_is_refused_before_anything_is_stored() { // A create half. let refusal = [ didbot_pds::validate(nsid::SCROBBLE, &ordinary), - didbot_pds::validate(nsid::ACTOR_REGISTRATION, &well_formed()), + didbot_pds::validate(nsid::REGISTRATION, &well_formed()), ] .into_iter() .find_map(Result::err); @@ -373,7 +368,7 @@ fn a_batch_containing_one_is_refused_before_anything_is_stored() { // refused. let refusal = [ didbot_pds::validate(nsid::SCROBBLE, &ordinary), - validate_removal(nsid::ACTOR_REGISTRATION), + validate_removal(nsid::REGISTRATION), ] .into_iter() .find_map(Result::err); @@ -397,8 +392,8 @@ fn the_removal_pre_flight_agrees_with_the_store() { for collection in [ nsid::SCROBBLE, nsid::MEMORY, - nsid::ACTOR_REGISTRATION, - nsid::AGENT_VOUCH, + nsid::REGISTRATION, + nsid::VOUCH, ] { assert_eq!( validate_removal(collection).is_err(), @@ -418,7 +413,7 @@ fn the_removal_pre_flight_agrees_with_the_store() { fn dropping_a_whole_repository_still_takes_it() { let store = MemoryRecordStore::new(); store - .put_authored(DID, nsid::ACTOR_REGISTRATION, well_formed()) + .put_authored(DID, nsid::REGISTRATION, well_formed()) .expect("write"); assert_eq!(store.stats().records, 1); store.remove_repo(DID); diff --git a/crates/didbot-pds/tests/born.rs b/crates/didbot-pds/tests/born.rs index 417d80a4..8b83b25f 100644 --- a/crates/didbot-pds/tests/born.rs +++ b/crates/didbot-pds/tests/born.rs @@ -1,7 +1,7 @@ //! What a repository holds the moment it exists. //! //! Two records, and neither is the account's. The server composes -//! `zone.quernstone.actor.registration` from its own bookkeeping, and writes +//! `bot.did.registration` from its own bookkeeping, and writes //! an `app.bsky.actor.profile` so an ordinary atproto client can read the //! account at all. There used to be a third, ours, between them; what it //! carried that was worth keeping was never presentation. @@ -79,7 +79,7 @@ fn facts(parent: Option<&str>) -> RegistrationFacts { /// The account's registration record, and the key it is filed under. fn registration(pds: &impl Registry, did: &str) -> (String, Value) { let mut records = pds - .list_records(did, nsid::ACTOR_REGISTRATION, &ListParams::new(10)) + .list_records(did, nsid::REGISTRATION, &ListParams::new(10)) .expect("the repository exists"); assert_eq!( records.len(), @@ -105,7 +105,7 @@ fn provisioning_writes_a_registration_and_a_bluesky_profile() { let (rkey, record) = registration(&pds, &did); assert_eq!(rkey, "self", "the lexicon says literal:self"); - assert_eq!(record["$type"], nsid::ACTOR_REGISTRATION); + assert_eq!(record["$type"], nsid::REGISTRATION); assert_eq!(record["owner"], "did:web:owner.example"); assert_eq!(record["actor"]["harness"], "claude-code"); assert_eq!(record["actor"]["agentType"], "Explore"); @@ -254,7 +254,7 @@ fn the_born_records_are_announced_when_the_account_is_provisioned() { let pds = provisioner().with_commit_sink(commits.clone()); let did = account(&pds, "gralloch", facts(None)); - for collection in [nsid::ACTOR_REGISTRATION, bsky::ACTOR_PROFILE] { + for collection in [nsid::REGISTRATION, bsky::ACTOR_PROFILE] { let announced: Vec<_> = commits .commits() .into_iter() diff --git a/crates/didbot-pds/tests/durability.rs b/crates/didbot-pds/tests/durability.rs index 85a05828..9e1c641e 100644 --- a/crates/didbot-pds/tests/durability.rs +++ b/crates/didbot-pds/tests/durability.rs @@ -27,7 +27,7 @@ use time::{Duration, OffsetDateTime}; const SECRET: &[u8] = b"development-secret-not-for-production"; const NODE: &str = "node-alpha"; const ZONE_HOST: &str = "agents.localhost"; -const SCROBBLE: &str = "zone.quernstone.scrobble"; +const SCROBBLE: &str = "com.vibescrobble.scrobble"; const PROFILE: &str = didbot_pds::bsky::ACTOR_PROFILE; /// A directory of this test's own, emptied first so a previous run cannot diff --git a/crates/didbot-pds/tests/records.rs b/crates/didbot-pds/tests/records.rs index e683239e..8f4e7914 100644 --- a/crates/didbot-pds/tests/records.rs +++ b/crates/didbot-pds/tests/records.rs @@ -54,14 +54,14 @@ fn provisioner() -> Provisioner usize { @@ -115,7 +115,7 @@ fn account(pds: &impl Registry, agent_id: &str) -> String { } } -/// A scrobble that satisfies `zone.quernstone.scrobble`. +/// A scrobble that satisfies `com.vibescrobble.scrobble`. /// /// Every required field is here because the store now checks for them. A /// helper that omitted one would fail every test that writes, which is the @@ -125,7 +125,7 @@ fn scrobble(text: &str) -> serde_json::Value { json!({ "text": text, "emoji": "\u{1f527}", "createdAt": "2023-11-14T22:13:20Z" }) } -/// A memory that satisfies `zone.quernstone.memory`. +/// A memory that satisfies `bot.did.memory`. fn memory(text: &str) -> serde_json::Value { json!({ "text": text, "createdAt": "2023-11-14T22:13:20Z" }) } @@ -698,7 +698,7 @@ fn stats_total_across_collections_and_accounts() { "and one bluesky profile per account" ); assert_eq!( - stats.records.collections.get(nsid::ACTOR_REGISTRATION), + stats.records.collections.get(nsid::REGISTRATION), Some(&2), "one registration record per repository, written at provisioning" ); @@ -1010,16 +1010,16 @@ fn a_literal_key_strategy_admits_its_own_key_and_nothing_else() { use didbot_pds::records::{resolve_key, ResolvedKey}; assert_eq!( - resolve_key(nsid::ACTOR_REGISTRATION, None), + resolve_key(nsid::REGISTRATION, None), Ok(ResolvedKey::Fixed("self".to_owned())), "a literal strategy names the key when the caller does not" ); assert_eq!( - resolve_key(nsid::ACTOR_REGISTRATION, Some("self")), + resolve_key(nsid::REGISTRATION, Some("self")), Ok(ResolvedKey::Fixed("self".to_owned())), "and agrees when the caller names the same one" ); - match resolve_key(nsid::ACTOR_REGISTRATION, Some("elsewhere")) { + match resolve_key(nsid::REGISTRATION, Some("elsewhere")) { Err(RecordError::KeyRefusedByStrategy { rkey, .. }) => assert_eq!(rkey, "elsewhere"), other => panic!("expected the strategy to refuse the key, got {other:?}"), } @@ -1185,7 +1185,7 @@ fn a_repository_reports_only_the_collections_it_holds_something_in() { pds.collections(&did), Ok(vec![ didbot_pds::bsky::ACTOR_PROFILE.to_owned(), - nsid::ACTOR_REGISTRATION.to_owned(), + nsid::REGISTRATION.to_owned(), ]) ); @@ -1195,8 +1195,8 @@ fn a_repository_reports_only_the_collections_it_holds_something_in() { pds.collections(&did), Ok(vec![ didbot_pds::bsky::ACTOR_PROFILE.to_owned(), - nsid::ACTOR_REGISTRATION.to_owned(), nsid::MEMORY.to_owned(), + nsid::REGISTRATION.to_owned(), nsid::SCROBBLE.to_owned() ]) ); @@ -1212,7 +1212,7 @@ fn a_repository_reports_only_the_collections_it_holds_something_in() { pds.collections(&did), Ok(vec![ didbot_pds::bsky::ACTOR_PROFILE.to_owned(), - nsid::ACTOR_REGISTRATION.to_owned(), + nsid::REGISTRATION.to_owned(), nsid::SCROBBLE.to_owned() ]) ); diff --git a/crates/didbot-pds/tests/registration.rs b/crates/didbot-pds/tests/registration.rs index c474ca8f..7c86e9c7 100644 --- a/crates/didbot-pds/tests/registration.rs +++ b/crates/didbot-pds/tests/registration.rs @@ -108,7 +108,7 @@ fn boot(dir: &Path) -> (Durable, Pds) { /// The identity record of a repository, read the way any client would. fn identity_of(pds: &impl Registry, did: &str) -> Value { let records = pds - .list_records(did, nsid::ACTOR_REGISTRATION, &ListParams::new(10)) + .list_records(did, nsid::REGISTRATION, &ListParams::new(10)) .expect("the repository should exist"); assert_eq!( records.len(), @@ -150,7 +150,7 @@ fn the_record_says_what_admitted_the_account_and_who_operates_it() { .did; let record = identity_of(&pds, did.as_str()); - assert_eq!(record["$type"], nsid::ACTOR_REGISTRATION); + assert_eq!(record["$type"], nsid::REGISTRATION); assert_eq!(record["admittedBy"], "dev-shared-secret"); assert_eq!(record["assurance"], "shared-secret"); assert_eq!(record["operator"], pds.service_did()); @@ -243,7 +243,7 @@ fn refreshing_replaces_rather_than_accumulates() { pds.set_pinned(did.as_str(), false).expect("unpin"); let records = pds - .list_records(did.as_str(), nsid::ACTOR_REGISTRATION, &ListParams::new(10)) + .list_records(did.as_str(), nsid::REGISTRATION, &ListParams::new(10)) .expect("the repository exists"); assert_eq!(records.len(), 1, "one identity record, rewritten in place"); } @@ -318,7 +318,7 @@ fn deleting_the_account_takes_the_record_and_leaves_the_bookkeeping() { pds.delete(did.as_str()).expect("delete"); assert!( - pds.list_records(did.as_str(), nsid::ACTOR_REGISTRATION, &ListParams::new(10)) + pds.list_records(did.as_str(), nsid::REGISTRATION, &ListParams::new(10)) .is_err(), "the repository is gone with the account" ); diff --git a/crates/didbot-pds/tests/server.rs b/crates/didbot-pds/tests/server.rs index ca6b6d59..8565d023 100644 --- a/crates/didbot-pds/tests/server.rs +++ b/crates/didbot-pds/tests/server.rs @@ -139,7 +139,7 @@ fn the_server_is_born_with_a_profile_a_client_can_read() { .collections(&did) .expect("the repository exists") .iter() - .all(|collection| !collection.starts_with("zone.quernstone.agent."))); + .all(|collection| !collection.starts_with("bot.did.agent."))); } /// The account is not an attested one, and says so by having no provenance. diff --git a/crates/didbot-repo/src/lib.rs b/crates/didbot-repo/src/lib.rs index 96bcafe4..dba9a6fd 100644 --- a/crates/didbot-repo/src/lib.rs +++ b/crates/didbot-repo/src/lib.rs @@ -24,7 +24,7 @@ //! let key = SigningKey::generate(); //! let mut repo = Builder::new(); //! repo.insert( -//! "zone.quernstone.scrobble", +//! "com.vibescrobble.scrobble", //! "3lqmnopqrstuv", //! &serde_json::json!({"text": "quernstone", "emoji": "🜁"}), //! )?; diff --git a/crates/didbot-repo/src/repository.rs b/crates/didbot-repo/src/repository.rs index fe920035..99a8d41a 100644 --- a/crates/didbot-repo/src/repository.rs +++ b/crates/didbot-repo/src/repository.rs @@ -367,7 +367,7 @@ mod tests { fn scrobble(text: &str) -> serde_json::Value { serde_json::json!({ - "$type": "zone.quernstone.scrobble", + "$type": "com.vibescrobble.scrobble", "text": text, "emoji": "🜁", "createdAt": "2026-08-26T00:00:00Z", @@ -379,13 +379,21 @@ mod tests { fn pair() -> (Repository, Repository) { let key = SigningKey::generate(); let mut one = Builder::new(); - one.insert("zone.quernstone.scrobble", "3lqma", &scrobble("quernstone")) - .expect("valid"); + one.insert( + "com.vibescrobble.scrobble", + "3lqma", + &scrobble("quernstone"), + ) + .expect("valid"); let mut two = Builder::new(); - two.insert("zone.quernstone.scrobble", "3lqma", &scrobble("quernstone")) - .expect("valid"); two.insert( - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", + "3lqma", + &scrobble("quernstone"), + ) + .expect("valid"); + two.insert( + "com.vibescrobble.scrobble", "3lqmb", &scrobble("farthingale"), ) @@ -474,7 +482,7 @@ mod tests { let mut builder = Builder::new(); for (rkey, text) in [("3lqma", "quernstone"), ("3lqmb", "farthingale")] { builder - .insert("zone.quernstone.scrobble", rkey, &scrobble(text)) + .insert("com.vibescrobble.scrobble", rkey, &scrobble(text)) .expect("a valid record"); } builder.commit("did:web:a.example", "3lqmnopqrstuv", None, &key) @@ -489,13 +497,21 @@ mod tests { fn a_new_record_moves_the_commit() { let key = SigningKey::generate(); let mut one = Builder::new(); - one.insert("zone.quernstone.scrobble", "3lqma", &scrobble("quernstone")) - .expect("valid"); + one.insert( + "com.vibescrobble.scrobble", + "3lqma", + &scrobble("quernstone"), + ) + .expect("valid"); let mut two = Builder::new(); - two.insert("zone.quernstone.scrobble", "3lqma", &scrobble("quernstone")) - .expect("valid"); two.insert( - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", + "3lqma", + &scrobble("quernstone"), + ) + .expect("valid"); + two.insert( + "com.vibescrobble.scrobble", "3lqmb", &scrobble("farthingale"), ) @@ -513,7 +529,7 @@ mod tests { fn one_rkey_in_two_collections_is_two_records() { let mut builder = Builder::new(); builder - .insert("zone.quernstone.scrobble", "self", &scrobble("one")) + .insert("com.vibescrobble.scrobble", "self", &scrobble("one")) .expect("valid"); builder .insert("app.bsky.actor.profile", "self", &scrobble("two")) @@ -526,13 +542,13 @@ mod tests { let mut builder = Builder::new(); let error = builder .insert( - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", "3lqma", &serde_json::json!({"ratio": 1.5}), ) .expect_err("a float has no encoding"); assert!( - matches!(&error, RepoError::Record { key, .. } if key == "zone.quernstone.scrobble/3lqma"), + matches!(&error, RepoError::Record { key, .. } if key == "com.vibescrobble.scrobble/3lqma"), "{error}" ); } diff --git a/crates/didbot-repo/tests/car_framing.rs b/crates/didbot-repo/tests/car_framing.rs index 3fb69c02..82a712e1 100644 --- a/crates/didbot-repo/tests/car_framing.rs +++ b/crates/didbot-repo/tests/car_framing.rs @@ -83,7 +83,7 @@ fn walk(car: &[u8]) -> (Vec, Vec
) { fn scrobble(text: &str) -> serde_json::Value { serde_json::json!({ - "$type": "zone.quernstone.scrobble", + "$type": "com.vibescrobble.scrobble", "text": text, "emoji": "🜁", "createdAt": "2026-08-26T00:00:00Z", @@ -97,7 +97,7 @@ fn exported() -> Repository { for index in 0..200 { builder .insert( - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", &format!("3lqmnop{index:06}"), &scrobble(&format!("farthingale {index}")), ) diff --git a/crates/didbot-repo/tests/did_document.rs b/crates/didbot-repo/tests/did_document.rs index 44b3dc6d..4a9ebc5b 100644 --- a/crates/didbot-repo/tests/did_document.rs +++ b/crates/didbot-repo/tests/did_document.rs @@ -34,10 +34,10 @@ fn account() -> (Repository, String) { let mut builder = Builder::new(); builder .insert( - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", "3lqmnopqrstuv", &serde_json::json!({ - "$type": "zone.quernstone.scrobble", + "$type": "com.vibescrobble.scrobble", "text": "quernstone", "emoji": "🜁", "createdAt": "2026-08-26T00:00:00Z", diff --git a/crates/didbot-serve/src/bin/didbot-dev.rs b/crates/didbot-serve/src/bin/didbot-dev.rs index e2c8b09b..438bf89c 100644 --- a/crates/didbot-serve/src/bin/didbot-dev.rs +++ b/crates/didbot-serve/src/bin/didbot-dev.rs @@ -627,7 +627,7 @@ fn banner(args: &Args, pds_endpoint: &str, service_did: String, signer: &SharedS ); println!(" curl -N {base}/events"); println!(" curl -N {base}/firehose"); - println!(" curl {base}/xrpc/zone.quernstone.listAgents"); + println!(" curl {base}/xrpc/bot.did.listAgents"); match sample_claim(signer) { Some(claim) => { @@ -636,7 +636,7 @@ fn banner(args: &Args, pds_endpoint: &str, service_did: String, signer: &SharedS " # claim below was signed at startup, is good once, and expires in a few minutes" ); println!( - " curl -X POST {base}/xrpc/zone.quernstone.provisionAgent \\\n \ + " curl -X POST {base}/xrpc/bot.did.provisionAgent \\\n \ -H 'content-type: application/json' \\\n -d '{claim}'" ); } diff --git a/crates/didbot-serve/src/blobs.rs b/crates/didbot-serve/src/blobs.rs index 8fd9281b..9dc15fea 100644 --- a/crates/didbot-serve/src/blobs.rs +++ b/crates/didbot-serve/src/blobs.rs @@ -45,7 +45,7 @@ use crate::wire::{GetBlobQuery, ListBlobsQuery}; /// This is not the protocol's answer to the question β€” the protocol's answer /// is the authenticated session β€” and a name that looked official would /// invite a client to expect it somewhere else. -pub const REPO_HEADER: &str = "quernstone-repo"; +pub const REPO_HEADER: &str = "didbot-repo"; /// How much of a blob is read from the store per chunk on the way out. /// diff --git a/crates/didbot-serve/src/error.rs b/crates/didbot-serve/src/error.rs index f96b1ec5..bd92ae35 100644 --- a/crates/didbot-serve/src/error.rs +++ b/crates/didbot-serve/src/error.rs @@ -205,7 +205,7 @@ impl From<&ProvisionError> for ApiError { /// The same mapping, as the `com.atproto.repo.*` surface reports it. /// /// One renaming and nothing else: an account this deployment does not hold is -/// `UnknownAccount` to `zone.quernstone.*`, which is a statement about an +/// `UnknownAccount` to `bot.did.*`, which is a statement about an /// account, and `RepoNotFound` to `com.atproto.repo.*`, which is a statement /// about a repository. They are the same condition seen from two surfaces, /// and each surface uses the name its own clients are written against. diff --git a/crates/didbot-serve/src/firehose.rs b/crates/didbot-serve/src/firehose.rs index cecd08e5..38724afd 100644 --- a/crates/didbot-serve/src/firehose.rs +++ b/crates/didbot-serve/src/firehose.rs @@ -520,7 +520,7 @@ mod tests { fn commit(rkey: &str) -> CommitEvent { CommitEvent::new( "did:web:a.agents.localhost%3A3000", - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", rkey, serde_json::json!({"text": "reading the cart", "emoji": "πŸ”"}), didbot_pds::Position { @@ -547,7 +547,7 @@ mod tests { let frame = firehose.publish(&commit("3lq")); assert_eq!( frame.uri, - "at://did:web:a.agents.localhost%3A3000/zone.quernstone.scrobble/3lq" + "at://did:web:a.agents.localhost%3A3000/com.vibescrobble.scrobble/3lq" ); assert_eq!(frame.record["emoji"], "πŸ”"); assert_eq!(frame.cursor(), format!("{}:1", firehose.instance())); diff --git a/crates/didbot-serve/src/lib.rs b/crates/didbot-serve/src/lib.rs index 549defd9..0a0985ed 100644 --- a/crates/didbot-serve/src/lib.rs +++ b/crates/didbot-serve/src/lib.rs @@ -29,10 +29,10 @@ //! | `GET /.well-known/atproto-did` | the DID of the account whose handle is the request's Host, 404 when nobody holds it | //! | `GET /events` | live lifecycle events as Server-Sent Events | //! | `GET /firehose` | every record written, as Server-Sent Events, resumable | -//! | `POST /xrpc/zone.quernstone.provisionAgent` | mint an account | -//! | `POST /xrpc/zone.quernstone.deleteAgent` | remove one | -//! | `POST /xrpc/zone.quernstone.setAgentPinned` | pin or unpin one | -//! | `GET /xrpc/zone.quernstone.listAgents` | list them | +//! | `POST /xrpc/bot.did.provisionAgent` | mint an account | +//! | `POST /xrpc/bot.did.deleteAgent` | remove one | +//! | `POST /xrpc/bot.did.setAgentPinned` | pin or unpin one | +//! | `GET /xrpc/bot.did.listAgents` | list them | //! | `POST /xrpc/com.atproto.repo.createRecord` | write a record to a repository | //! | `POST /xrpc/com.atproto.repo.putRecord` | write one at a key you choose | //! | `POST /xrpc/com.atproto.repo.deleteRecord` | remove one, or make sure it is gone | @@ -40,7 +40,7 @@ //! | `GET /xrpc/com.atproto.repo.getRecord` | one record by collection and key | //! | `GET /xrpc/com.atproto.repo.listRecords` | read a collection back, newest first | //! | `GET /xrpc/com.atproto.repo.describeRepo` | the account, its document and its collections | -//! | `GET /xrpc/zone.quernstone.stats` | accounts, records, bytes, per collection | +//! | `GET /xrpc/bot.did.stats` | accounts, records, bytes, per collection | //! //! Every `repo` above is an at-identifier: a DID or a handle this deployment //! issued. diff --git a/crates/didbot-serve/src/routes.rs b/crates/didbot-serve/src/routes.rs index 5960f165..8ee6720d 100644 --- a/crates/didbot-serve/src/routes.rs +++ b/crates/didbot-serve/src/routes.rs @@ -184,23 +184,14 @@ pub fn app_with_streams( .route("/.well-known/atproto-did", get(atproto_did)) .route("/events", get(events_stream)) .route("/firehose", get(firehose_stream)) - .route( - "/xrpc/zone.quernstone.provisionAgent", - post(provision_agent), - ) - .route("/xrpc/zone.quernstone.deleteAgent", post(delete_agent)) - .route("/xrpc/zone.quernstone.setAgentPinned", post(set_pinned)) - .route( - "/xrpc/zone.quernstone.setAgentScrobbling", - post(set_scrobbling), - ) - .route("/xrpc/zone.quernstone.listAgents", get(list_agents)) - .route( - "/xrpc/zone.quernstone.getAgentLedger", - get(get_agent_ledger), - ) - .route("/xrpc/zone.quernstone.listAgentLedgers", get(list_ledgers)) - .route("/xrpc/zone.quernstone.stats", get(stats)) + .route("/xrpc/bot.did.provisionAgent", post(provision_agent)) + .route("/xrpc/bot.did.deleteAgent", post(delete_agent)) + .route("/xrpc/bot.did.setAgentPinned", post(set_pinned)) + .route("/xrpc/bot.did.setAgentScrobbling", post(set_scrobbling)) + .route("/xrpc/bot.did.listAgents", get(list_agents)) + .route("/xrpc/bot.did.getAgentLedger", get(get_agent_ledger)) + .route("/xrpc/bot.did.listAgentLedgers", get(list_ledgers)) + .route("/xrpc/bot.did.stats", get(stats)) .merge(atproto_routes()) .with_state(state) // A path nothing serves, and a method nothing serves on a path that @@ -337,7 +328,7 @@ async fn firehose_stream( Sse::new(state.firehose.subscribe(query.cursor.as_deref())).keep_alive(KeepAlive::default()) } -/// `POST /xrpc/zone.quernstone.provisionAgent` +/// `POST /xrpc/bot.did.provisionAgent` async fn provision_agent(State(state): State, body: Bytes) -> Response { let request: ProvisionAgentRequest = match parse_body(&body) { Ok(request) => request, @@ -369,7 +360,7 @@ async fn provision_agent(State(state): State, body: Bytes) -> Response } } -/// `POST /xrpc/zone.quernstone.deleteAgent` +/// `POST /xrpc/bot.did.deleteAgent` async fn delete_agent(State(state): State, body: Bytes) -> Response { let request: DidRequest = match parse_body(&body) { Ok(request) => request, @@ -381,9 +372,9 @@ async fn delete_agent(State(state): State, body: Bytes) -> Response { } } -/// `POST /xrpc/zone.quernstone.setAgentPinned` +/// `POST /xrpc/bot.did.setAgentPinned` /// -/// `POST /xrpc/zone.quernstone.setAgentPinned` +/// `POST /xrpc/bot.did.setAgentPinned` async fn set_pinned(State(state): State, body: Bytes) -> Response { let request: SetPinnedRequest = match parse_body(&body) { Ok(request) => request, @@ -395,7 +386,7 @@ async fn set_pinned(State(state): State, body: Bytes) -> Response { } } -/// `POST /xrpc/zone.quernstone.setAgentScrobbling` +/// `POST /xrpc/bot.did.setAgentScrobbling` /// /// Turns one account's scrobbling on or off. Later writes to the scrobble /// collection are refused with 403 `ScrobblingDisabled` while it is off; @@ -414,7 +405,7 @@ async fn set_scrobbling(State(state): State, body: Bytes) -> Response } } -/// `GET /xrpc/zone.quernstone.listAgents` +/// `GET /xrpc/bot.did.listAgents` async fn list_agents(State(state): State) -> Response { let agents: Vec = state .registry @@ -458,7 +449,7 @@ fn resolve_repo(state: &AppState, repo: &str) -> Result { did.ok_or_else(|| ApiError::repo_not_found(repo)) } -/// `GET /xrpc/zone.quernstone.getAgentLedger` +/// `GET /xrpc/bot.did.getAgentLedger` /// /// The bookkeeping for one agent, which is the one read here that deliberately /// answers about accounts that no longer exist. A 404 means this deployment @@ -483,7 +474,7 @@ async fn get_agent_ledger( } } -/// `GET /xrpc/zone.quernstone.listAgentLedgers` +/// `GET /xrpc/bot.did.listAgentLedgers` /// /// Every agent this deployment has ever provisioned, which is longer than /// `listAgents` by exactly the ones it has taken away again. @@ -905,7 +896,7 @@ fn unnameable_record(subject: &str, error: &didbot_pds::RecordError) -> ApiError /// The list is a report of what this account has written and never a menu of /// what it may write, because there is no longer any such menu. A collection /// nobody has heard of appearing in it is a client that wrote one, which is -/// also where a typo now shows up: `zone.quernstone.scrobbel` is a second +/// also where a typo now shows up: `com.vibescrobble.scrobbel` is a second /// collection in this list rather than a refusal on the write. async fn describe_repo( State(state): State, @@ -1243,7 +1234,7 @@ async fn subscribe_repos() -> Response { .into_response() } -/// `GET /xrpc/zone.quernstone.stats` +/// `GET /xrpc/bot.did.stats` async fn stats(State(state): State) -> Response { Json(StatsResponse::from_stats(&state.registry.stats())).into_response() } diff --git a/crates/didbot-serve/src/tests.rs b/crates/didbot-serve/src/tests.rs index bd7f7e8c..2da24be6 100644 --- a/crates/didbot-serve/src/tests.rs +++ b/crates/didbot-serve/src/tests.rs @@ -666,7 +666,7 @@ async fn provisioning_returns_a_did_and_its_document() { let body = json!({ "agentId": "kestrel", "claim": wire_claim() }).to_string(); let (status, _, body) = call( Arc::new(FakeRegistry::new()), - post("/xrpc/zone.quernstone.provisionAgent", &body), + post("/xrpc/bot.did.provisionAgent", &body), ) .await; @@ -692,7 +692,7 @@ async fn a_provisioning_body_carries_the_harness_s_account_of_the_agent() { .to_string(); let (status, _, body) = call( registry.clone(), - post("/xrpc/zone.quernstone.provisionAgent", &body), + post("/xrpc/bot.did.provisionAgent", &body), ) .await; @@ -715,7 +715,7 @@ async fn a_provisioning_body_without_a_profile_is_still_accepted() { let body = json!({ "agentId": "kestrel", "claim": wire_claim() }).to_string(); let (status, _, body) = call( registry.clone(), - post("/xrpc/zone.quernstone.provisionAgent", &body), + post("/xrpc/bot.did.provisionAgent", &body), ) .await; @@ -730,7 +730,7 @@ async fn refused_attestation_is_a_403_in_the_atproto_shape() { let body = json!({ "agentId": "kestrel", "claim": wire_claim() }).to_string(); let (status, _, body) = call( Arc::new(FakeRegistry::refusing(AttestError::InvalidEvidence)), - post("/xrpc/zone.quernstone.provisionAgent", &body), + post("/xrpc/bot.did.provisionAgent", &body), ) .await; @@ -746,7 +746,7 @@ async fn refused_attestation_is_a_403_in_the_atproto_shape() { async fn malformed_json_is_a_400() { let (status, _, body) = call( Arc::new(FakeRegistry::new()), - post("/xrpc/zone.quernstone.provisionAgent", "{not json"), + post("/xrpc/bot.did.provisionAgent", "{not json"), ) .await; @@ -848,7 +848,7 @@ async fn deleting_an_unknown_account_is_a_404() { let (status, _, body) = call( Arc::new(FakeRegistry::seeded("kestrel")), post( - "/xrpc/zone.quernstone.deleteAgent", + "/xrpc/bot.did.deleteAgent", r#"{"did":"did:web:nobody.agents.localhost"}"#, ), ) @@ -862,7 +862,7 @@ async fn deleting_an_unknown_account_is_a_404() { async fn listing_reports_stored_accounts() { let (status, _, body) = call( Arc::new(FakeRegistry::seeded("kestrel")), - get("/xrpc/zone.quernstone.listAgents"), + get("/xrpc/bot.did.listAgents"), ) .await; @@ -890,7 +890,7 @@ async fn a_silenced_agent_is_refused_its_scrobbles_and_nothing_else() { let (status, _, _) = call( registry.clone(), post( - "/xrpc/zone.quernstone.setAgentScrobbling", + "/xrpc/bot.did.setAgentScrobbling", &json!({ "did": SEEDED_DID, "scrobbling": false }).to_string(), ), ) @@ -913,13 +913,13 @@ async fn a_silenced_agent_is_refused_its_scrobbles_and_nothing_else() { registry.clone(), post( "/xrpc/com.atproto.repo.createRecord", - &create_body("zone.quernstone.memory", memory("still fine")), + &create_body("bot.did.memory", memory("still fine")), ), ) .await; assert_eq!(status, StatusCode::OK); - let (_, _, listed) = call(registry, get("/xrpc/zone.quernstone.listAgents")).await; + let (_, _, listed) = call(registry, get("/xrpc/bot.did.listAgents")).await; assert_eq!(listed["agents"][0]["scrobbling"], false); } @@ -929,7 +929,7 @@ async fn setting_scrobbling_on_an_unknown_account_is_a_404() { let (status, _, body) = call( registry, post( - "/xrpc/zone.quernstone.setAgentScrobbling", + "/xrpc/bot.did.setAgentScrobbling", &json!({ "did": "did:web:nobody.agents.localhost", "scrobbling": false }).to_string(), ), ) @@ -1019,7 +1019,7 @@ fn frame_data(text: &str) -> T { fn scrobble_commit(rkey: &str) -> CommitEvent { CommitEvent::new( "did:web:kestrel.agents.localhost", - "zone.quernstone.scrobble", + "com.vibescrobble.scrobble", rkey, json!({"text": "reading the cart", "emoji": "\u{1f50d}", "createdAt": "2026-08-25T10:00:00Z"}), didbot_pds::Position { @@ -1055,7 +1055,7 @@ async fn the_firehose_opens_with_an_info_frame_and_then_carries_records() { assert!(text.contains("event: commit"), "unexpected frame: {text}"); let frame: CommitFrame = frame_data(&text); assert_eq!(frame.seq, 1); - assert_eq!(frame.collection, "zone.quernstone.scrobble"); + assert_eq!(frame.collection, "com.vibescrobble.scrobble"); assert_eq!(frame.record["text"], "reading the cart"); } @@ -1174,18 +1174,18 @@ fn ports_are_stripped_from_host_headers() { // --------------------------------------------------------------------------- const SEEDED_DID: &str = "did:web:kestrel.agents.localhost"; -const SCROBBLE: &str = "zone.quernstone.scrobble"; +const SCROBBLE: &str = "com.vibescrobble.scrobble"; /// A `literal:self` collection, which is where a chosen key is legal. /// A collection whose lexicon fixes the key at `self`. /// -/// Bluesky's, because after `zone.quernstone.agent.profile` was retired the +/// Bluesky's, because after `bot.did.profile` was retired the /// only `literal:self` lexicon this server holds is the registration record, /// which no caller may write at all. This server holds no lexicon for /// Bluesky's either, so the key is the caller's to name rather than the /// strategy's to impose β€” which is why every write below names it. const PROFILE: &str = didbot_pds::bsky::ACTOR_PROFILE; -/// A scrobble body that satisfies `zone.quernstone.scrobble`. +/// A scrobble body that satisfies `com.vibescrobble.scrobble`. /// /// The server validates every record against its lexicon now, so a fixture /// missing a required field is refused before it reaches the code the test is @@ -1194,7 +1194,7 @@ fn scrobble(text: &str) -> serde_json::Value { json!({ "text": text, "emoji": "\u{1f50d}", "createdAt": "2026-08-25T10:00:00Z" }) } -/// A memory body that satisfies `zone.quernstone.memory`. +/// A memory body that satisfies `bot.did.memory`. fn memory(text: &str) -> serde_json::Value { json!({ "text": text, "createdAt": "2026-08-25T10:00:00Z" }) } @@ -1589,7 +1589,7 @@ async fn a_record_the_data_model_cannot_hold_is_a_400() { #[tokio::test] async fn a_type_disagreeing_with_the_collection_is_a_400() { - let record = json!({ "$type": "zone.quernstone.memory", "text": "mislabelled" }); + let record = json!({ "$type": "bot.did.memory", "text": "mislabelled" }); let (status, _, body) = call( Arc::new(FakeRegistry::seeded("kestrel")), post( @@ -1621,7 +1621,7 @@ async fn writing_the_registration_record_is_a_403_with_its_own_name() { Arc::new(FakeRegistry::seeded("kestrel")), post( "/xrpc/com.atproto.repo.createRecord", - &create_body("zone.quernstone.actor.registration", record), + &create_body("bot.did.registration", record), ), ) .await; @@ -1632,7 +1632,7 @@ async fn writing_the_registration_record_is_a_403_with_its_own_name() { body["message"] .as_str() .unwrap_or_default() - .contains("zone.quernstone.actor.registration"), + .contains("bot.did.registration"), "the message should name the collection: {body}" ); } @@ -1644,7 +1644,7 @@ async fn writing_the_registration_record_is_a_403_with_its_own_name() { /// while the next reader is still served it. #[tokio::test] async fn no_route_can_destroy_the_registration_record() { - const REGISTRATION: &str = "zone.quernstone.actor.registration"; + const REGISTRATION: &str = "bot.did.registration"; let cases = [ ( "putRecord", @@ -2757,7 +2757,7 @@ async fn stats_report_accounts_records_bytes_and_collections() { for (collection, record) in [ (SCROBBLE, scrobble("one")), (SCROBBLE, scrobble("two")), - ("zone.quernstone.memory", memory("three")), + ("bot.did.memory", memory("three")), ] { let (status, _, _) = call( registry.clone(), @@ -2770,7 +2770,7 @@ async fn stats_report_accounts_records_bytes_and_collections() { assert_eq!(status, StatusCode::OK); } - let (status, _, body) = call(registry, get("/xrpc/zone.quernstone.stats")).await; + let (status, _, body) = call(registry, get("/xrpc/bot.did.stats")).await; assert_eq!(status, StatusCode::OK); assert_eq!(body["accounts"], json!(1)); assert_eq!(body["records"], json!(3)); @@ -2780,17 +2780,13 @@ async fn stats_report_accounts_records_bytes_and_collections() { ); assert_eq!( body["collections"], - json!({ "zone.quernstone.scrobble": 2, "zone.quernstone.memory": 1 }) + json!({ "com.vibescrobble.scrobble": 2, "bot.did.memory": 1 }) ); } #[tokio::test] async fn an_empty_deployment_reports_zeroes() { - let (status, _, body) = call( - Arc::new(FakeRegistry::new()), - get("/xrpc/zone.quernstone.stats"), - ) - .await; + let (status, _, body) = call(Arc::new(FakeRegistry::new()), get("/xrpc/bot.did.stats")).await; assert_eq!(status, StatusCode::OK); assert_eq!( @@ -3296,7 +3292,7 @@ mod sync { for route in [ "getRepo?did=did:web:nobody.agents.localhost", "getLatestCommit?did=did:web:nobody.agents.localhost", - "getRecord?did=did:web:nobody.agents.localhost&collection=zone.quernstone.scrobble&rkey=3lq", + "getRecord?did=did:web:nobody.agents.localhost&collection=com.vibescrobble.scrobble&rkey=3lq", "getBlocks?did=did:web:nobody.agents.localhost&cids=bafyreie5737gdxlw5i64vzichcalba3z2v5n6icifvx5xytvske7mr3hpm", ] { let (status, _, body) = diff --git a/crates/didbot-serve/src/wire.rs b/crates/didbot-serve/src/wire.rs index 91f38bf4..d754826a 100644 --- a/crates/didbot-serve/src/wire.rs +++ b/crates/didbot-serve/src/wire.rs @@ -63,7 +63,7 @@ impl WireClaim { } } -/// Body of `zone.quernstone.provisionAgent`. +/// Body of `bot.did.provisionAgent`. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ProvisionAgentRequest { @@ -130,14 +130,14 @@ impl ProvisionAgentRequest { } } -/// Body of `zone.quernstone.deleteAgent`, and of the pin route. +/// Body of `bot.did.deleteAgent`, and of the pin route. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DidRequest { /// The account to act on. pub did: String, } -/// Query of `zone.quernstone.getAgentLedger`. +/// Query of `bot.did.getAgentLedger`. /// /// A query rather than a body because it reads, and a `did` rather than a /// handle because a handle is released when the account is: the one caller @@ -149,7 +149,7 @@ pub struct LedgerQuery { pub did: String, } -/// Body of `zone.quernstone.setAgentPinned`. +/// Body of `bot.did.setAgentPinned`. /// /// Not in the original route list. It is here because [`Registry`] exposes /// pinning and there was otherwise no way to reach it over HTTP. @@ -163,7 +163,7 @@ pub struct SetPinnedRequest { pub pinned: bool, } -/// Body of `zone.quernstone.setAgentScrobbling`. +/// Body of `bot.did.setAgentScrobbling`. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SetScrobblingRequest { /// The account to act on. @@ -742,7 +742,7 @@ pub fn record_uri(did: &str, collection: &str, rkey: &str) -> String { format!("at://{did}/{collection}/{rkey}") } -/// Response of `zone.quernstone.stats`. +/// Response of `bot.did.stats`. /// /// Flat rather than nested: it is read by a person running `curl` and by a /// health line in the log, and neither wants to reach through a wrapper. diff --git a/crates/didbot-serve/tests/firehose_wire.rs b/crates/didbot-serve/tests/firehose_wire.rs index 3bb2ec32..7762089b 100644 --- a/crates/didbot-serve/tests/firehose_wire.rs +++ b/crates/didbot-serve/tests/firehose_wire.rs @@ -65,16 +65,16 @@ fn a_write_is_framed_as_what_was_recorded() { seq: 42, instance: "01JEXAMPLEINSTANCE".to_owned(), did: "did:web:kestrel.agents.localhost".to_owned(), - collection: "zone.quernstone.scrobble".to_owned(), + collection: "com.vibescrobble.scrobble".to_owned(), rkey: "3lqm0000abcd2".to_owned(), - uri: "at://did:web:kestrel.agents.localhost/zone.quernstone.scrobble/3lqm0000abcd2" + uri: "at://did:web:kestrel.agents.localhost/com.vibescrobble.scrobble/3lqm0000abcd2" .to_owned(), cid: "bafyreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy".to_owned(), commit: "bafyreidfuxdmqvxvtqcyvzkxpvbdcvwjxlrqvbn5xk3vg6mfumzsrxvz4a".to_owned(), rev: "3lqm0000abce4".to_owned(), time: "2026-08-24T12:00:00Z".to_owned(), record: serde_json::json!({ - "$type": "zone.quernstone.scrobble", + "$type": "com.vibescrobble.scrobble", "text": "reading the firehose reconnect path", "emoji": "🧡", "createdAt": "2026-08-24T12:00:00Z" diff --git a/crates/didbot-setup/README.md b/crates/didbot-setup/README.md index 2dd5e8e5..b2b812e4 100644 --- a/crates/didbot-setup/README.md +++ b/crates/didbot-setup/README.md @@ -178,7 +178,7 @@ heard of, and nothing else says why. ## Verify, rather than "the port answered" `verify` provisions an agent through the same client the hook uses, writes a -`zone.quernstone.scrobble` under it, reads the record back, asks the query +`com.vibescrobble.scrobble` under it, reads the record back, asks the query service whether it arrived, and deletes the account. It is the only way to find out whether a harness restart took effect, and its exit status is the answer. diff --git a/crates/didbot-setup/src/verify.rs b/crates/didbot-setup/src/verify.rs index 94c4a7f2..c1b1288e 100644 --- a/crates/didbot-setup/src/verify.rs +++ b/crates/didbot-setup/src/verify.rs @@ -83,7 +83,7 @@ pub async fn run(pds_url: &str, query_url: Option<&str>) -> Vec { let record = json!({ "repo": did, - "collection": "zone.quernstone.scrobble", + "collection": "com.vibescrobble.scrobble", "record": { "text": "quernstone setup verification, written by didbot-setup", "emoji": "\u{1FAB5}", @@ -111,7 +111,7 @@ pub async fn run(pds_url: &str, query_url: Option<&str>) -> Vec { .get(format!("{pds_url}/xrpc/com.atproto.repo.getRecord")) .query(&[ ("repo", did.as_str()), - ("collection", "zone.quernstone.scrobble"), + ("collection", "com.vibescrobble.scrobble"), ("rkey", rkey.as_str()), ]) .send() diff --git a/crates/didbot-swarm/src/lib.rs b/crates/didbot-swarm/src/lib.rs index f803aef8..97938821 100644 --- a/crates/didbot-swarm/src/lib.rs +++ b/crates/didbot-swarm/src/lib.rs @@ -5,7 +5,7 @@ //! and until agents are provisioned by a harness in anger the only way to make //! something happen is to make it up. The swarm makes it up through the front //! door: it signs its own attestation claims, provisions accounts through -//! `zone.quernstone.provisionAgent`, and writes records through +//! `bot.did.provisionAgent`, and writes records through //! `com.atproto.repo.createRecord`, so a run exercises the same path a real //! agent takes rather than a shortcut around it. //! @@ -236,7 +236,7 @@ impl Pds { agent_id: &str, ) -> Result { let claim = signer.produce_claim(node, nonce(agent_id), OffsetDateTime::now_utc())?; - let url = format!("{}/xrpc/zone.quernstone.provisionAgent", self.base_url); + let url = format!("{}/xrpc/bot.did.provisionAgent", self.base_url); let body = serde_json::json!({ "agentId": agent_id, "claim": { @@ -267,7 +267,7 @@ impl Pds { /// Deletes an account, which is how a session ends. pub async fn delete(&self, did: &str) -> Result<(), SwarmError> { - let url = format!("{}/xrpc/zone.quernstone.deleteAgent", self.base_url); + let url = format!("{}/xrpc/bot.did.deleteAgent", self.base_url); let _: serde_json::Value = self.post(&url, serde_json::json!({ "did": did })).await?; Ok(()) } diff --git a/crates/didbot-swarm/src/tests.rs b/crates/didbot-swarm/src/tests.rs index abb5aeb0..3a8c6779 100644 --- a/crates/didbot-swarm/src/tests.rs +++ b/crates/didbot-swarm/src/tests.rs @@ -116,7 +116,7 @@ fn a_record_is_a_scrobble_the_lexicon_would_recognise() { // rather than by a field-by-field approximation of the lexicon written // here. The two used to be able to disagree; now they cannot. didbot_pds::records::validate(nsid::SCROBBLE, &record) - .expect("a generated scrobble must satisfy zone.quernstone.scrobble"); + .expect("a generated scrobble must satisfy com.vibescrobble.scrobble"); } #[test] diff --git a/crates/didbot/tests/conformance/emit.rs b/crates/didbot/tests/conformance/emit.rs index 6c002b24..2bb8441a 100644 --- a/crates/didbot/tests/conformance/emit.rs +++ b/crates/didbot/tests/conformance/emit.rs @@ -35,7 +35,7 @@ fn sample(collection: &str) -> serde_json::Value { didbot::nsid::MEMORY => serde_json::json!({ "text": "what the minting run looked like", "createdAt": created_at }), - didbot::nsid::ACTOR_REGISTRATION => serde_json::json!({ + didbot::nsid::REGISTRATION => serde_json::json!({ "did": "did:web:agent.example.com", "provisionedAt": created_at, "operator": "did:web:agents.example.com", @@ -45,12 +45,12 @@ fn sample(collection: &str) -> serde_json::Value { "revision": 1, "generation": 1, "actor": { - "$type": "zone.quernstone.actor.registration#agent", + "$type": "bot.did.registration#agent", "harness": "claude-code", "admittedOn": "node-alpha", }, }), - didbot::nsid::AGENT_VOUCH => serde_json::json!({ + didbot::nsid::VOUCH => serde_json::json!({ "subject": "did:web:child.agents.example.com", "createdAt": created_at }), other => panic!("no sample record for {other}; add one beside the lexicon"), diff --git a/crates/didbot/tests/conformance/wire.rs b/crates/didbot/tests/conformance/wire.rs index faac9d3e..acdf8f13 100644 --- a/crates/didbot/tests/conformance/wire.rs +++ b/crates/didbot/tests/conformance/wire.rs @@ -812,14 +812,14 @@ async fn a_record_read_back_satisfies_the_lexicon_it_was_written_under() { "com.atproto.repo.getRecord", &q(&[ ("repo", &did), - ("collection", nsid::ACTOR_REGISTRATION), + ("collection", nsid::REGISTRATION), ("rkey", "self"), ]), ) .await .conforms("com.atproto.repo.getRecord"); Catalog::embedded() - .validate_record(nsid::ACTOR_REGISTRATION, ®istration["value"]) + .validate_record(nsid::REGISTRATION, ®istration["value"]) .expect("the registration record satisfies its own lexicon"); } @@ -863,7 +863,7 @@ async fn the_turn_header_does_not_reach_the_record() { .body(Body::from( json!({ "repo": did, - "collection": "zone.quernstone.scrobble", + "collection": "com.vibescrobble.scrobble", "record": scrobble("checking that a header stays a header"), }) .to_string(), @@ -879,7 +879,7 @@ async fn the_turn_header_does_not_reach_the_record() { "com.atproto.repo.getRecord", &q(&[ ("repo", did.as_str()), - ("collection", "zone.quernstone.scrobble"), + ("collection", "com.vibescrobble.scrobble"), ("rkey", rkey), ]), ) diff --git a/crates/didbot/tests/end_to_end.rs b/crates/didbot/tests/end_to_end.rs index 5e206ce7..7d6cad2c 100644 --- a/crates/didbot/tests/end_to_end.rs +++ b/crates/didbot/tests/end_to_end.rs @@ -154,7 +154,7 @@ async fn an_agent_is_provisioned_served_resolved_and_deleted() { // 1. Provision. let (status, body) = post( &app, - "/xrpc/zone.quernstone.provisionAgent", + "/xrpc/bot.did.provisionAgent", provision_body("scratch"), ) .await; @@ -178,7 +178,7 @@ async fn an_agent_is_provisioned_served_resolved_and_deleted() { let (status, body) = call( &app, Request::builder() - .uri("/xrpc/zone.quernstone.listAgents") + .uri("/xrpc/bot.did.listAgents") .body(Body::empty()) .expect("request builds"), ) @@ -191,7 +191,7 @@ async fn an_agent_is_provisioned_served_resolved_and_deleted() { // it β€” not that the wider network has forgotten it. let (status, _) = post( &app, - "/xrpc/zone.quernstone.deleteAgent", + "/xrpc/bot.did.deleteAgent", serde_json::json!({ "did": did_string }).to_string(), ) .await; @@ -213,12 +213,7 @@ async fn a_refused_attestation_provisions_nothing() { serde_json::from_str(&provision_body("impostor")).expect("body parses"); body["claim"]["nodeId"] = serde_json::Value::String("not-allowlisted".into()); - let (status, _) = post( - &app, - "/xrpc/zone.quernstone.provisionAgent", - body.to_string(), - ) - .await; + let (status, _) = post(&app, "/xrpc/bot.did.provisionAgent", body.to_string()).await; assert_eq!(status, StatusCode::FORBIDDEN); // The interesting assertion is not the status code. It is that a refused @@ -251,16 +246,11 @@ async fn two_agents_get_two_identifiers_and_two_hostnames() { let (first, _) = post( &app, - "/xrpc/zone.quernstone.provisionAgent", + "/xrpc/bot.did.provisionAgent", provision_body("alpha"), ) .await; - let (second, _) = post( - &app, - "/xrpc/zone.quernstone.provisionAgent", - provision_body("beta"), - ) - .await; + let (second, _) = post(&app, "/xrpc/bot.did.provisionAgent", provision_body("beta")).await; assert_eq!(first, StatusCode::OK); assert_eq!(second, StatusCode::OK); diff --git a/crates/didbot/tests/hook_to_record.rs b/crates/didbot/tests/hook_to_record.rs index d5bcabe9..9fe8ec3e 100644 --- a/crates/didbot/tests/hook_to_record.rs +++ b/crates/didbot/tests/hook_to_record.rs @@ -133,7 +133,7 @@ async fn read_record(server: &Server, did: &str, rkey: &str) -> (u16, Value) { .get(format!("{}/xrpc/com.atproto.repo.getRecord", server.url)) .query(&[ ("repo", did), - ("collection", "zone.quernstone.scrobble"), + ("collection", "com.vibescrobble.scrobble"), ("rkey", rkey), ]) .send() @@ -200,7 +200,7 @@ async fn a_hook_payload_becomes_a_record_and_the_account_goes_away() { .get(format!("{}/xrpc/com.atproto.repo.listRecords", server.url)) .query(&[ ("repo", did.as_str()), - ("collection", "zone.quernstone.scrobble"), + ("collection", "com.vibescrobble.scrobble"), ]) .send() .await diff --git a/docs/architecture.md b/docs/architecture.md index 5dfd712a..ab272f5d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -54,12 +54,12 @@ off the apex so a wildcard certificate cannot shadow the server's own name. Apex any client_id, anywhere dns zone - agents.quernstone.zone + agents.did.bot delegated Β· acme dns-01 for the wildcard cert - PDS HOST Β· quernstone.zone - agents under *.agents.quernstone.zone β€” at or below its own name + PDS HOST Β· did.bot + agents under *.agents.did.bot β€” at or below its own name a compromise here reaches keys, accounts and records β€” not policy HTTP SURFACE @@ -154,8 +154,8 @@ Record writes go to the XRPC surface and are not on this path. ONE HARNESS PROCESS β€” subagents share it session agent - did:web:7f3a9c.agents.quernstone.zone - handle: basalt-otter.agents.quernstone.zone + did:web:7f3a9c.agents.did.bot + handle: basalt-otter.agents.did.bot subagent did:web:1d4e02.agents… @@ -198,7 +198,7 @@ Record writes go to the XRPC surface and are not on this path. dns zone - agents.quernstone.zone + agents.did.bot external, api-writable create diff --git a/docs/running-locally.md b/docs/running-locally.md index 0f36c2d4..ddd31c9d 100644 --- a/docs/running-locally.md +++ b/docs/running-locally.md @@ -187,12 +187,12 @@ after that one is quiet. ### The record an agent is born with -Every repository holds a `zone.quernstone.actor.registration` record at `self` +Every repository holds a `bot.did.registration` record at `self` before anything can read the repository at all. The server writes it, from its own bookkeeping, as part of provisioning: ```sh -curl -s "localhost:3000/xrpc/com.atproto.repo.listRecords?repo=$DID&collection=zone.quernstone.actor.registration" | jq '.records[0].value' +curl -s "localhost:3000/xrpc/com.atproto.repo.listRecords?repo=$DID&collection=bot.did.registration" | jq '.records[0].value' ``` It says the DID, when the server provisioned it, which server that was, who is @@ -215,7 +215,7 @@ on the way to the store: ```sh curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:3000/xrpc/com.atproto.repo.createRecord \ -H 'content-type: application/json' \ - -d "{\"repo\":\"$DID\",\"collection\":\"zone.quernstone.actor.registration\",\"record\":{}}" + -d "{\"repo\":\"$DID\",\"collection\":\"bot.did.registration\",\"record\":{}}" ``` 403, and `ServerAuthoredRecord` in the body, and the same for a `putRecord`, a @@ -277,8 +277,8 @@ DID β€” provisioned, named, released, pinned, unpinned, deprovisioned β€” writte beside the account and kept after it: ```sh -curl -s "localhost:3000/xrpc/zone.quernstone.getAgentLedger?did=$DID" | jq . -curl -s localhost:3000/xrpc/zone.quernstone.listAgentLedgers | jq '.ledgers | length' +curl -s "localhost:3000/xrpc/bot.did.getAgentLedger?did=$DID" | jq . +curl -s localhost:3000/xrpc/bot.did.listAgentLedgers | jq '.ledgers | length' ``` Delete the agent and ask again. `listAgents` no longer has it and the ledger @@ -572,7 +572,7 @@ CARv1 file: a version 3 commit, signed by the account's key, over the root of a Merkle search tree keyed by every record it holds. ```sh -did=$(curl -s localhost:3000/xrpc/zone.quernstone.listAgents | jq -r '.agents[0].did') +did=$(curl -s localhost:3000/xrpc/bot.did.listAgents | jq -r '.agents[0].did') curl -sG localhost:3000/xrpc/com.atproto.sync.getRepo \ --data-urlencode "did=$did" -o repo.car ``` @@ -628,7 +628,7 @@ process and the log disappears into its debug output, which defeats the point. ```sh curl -s localhost:3000/health -curl -s -X POST localhost:3000/xrpc/zone.quernstone.provisionAgent \ +curl -s -X POST localhost:3000/xrpc/bot.did.provisionAgent \ -H 'content-type: application/json' \ -d '{"agentId":"scratch","claim":{...}}' @@ -648,7 +648,7 @@ REPO=did:web:kestrel.agents.localhost%3A3000 # or kestrel.agents.localhost curl -s -X POST localhost:3000/xrpc/com.atproto.repo.createRecord \ -H 'content-type: application/json' \ - -d "{\"repo\":\"$REPO\",\"collection\":\"zone.quernstone.scrobble\", + -d "{\"repo\":\"$REPO\",\"collection\":\"com.vibescrobble.scrobble\", \"record\":{\"text\":\"driving it by hand\",\"emoji\":\"πŸ”\", \"createdAt\":\"2026-08-26T10:00:00Z\"}}" @@ -661,7 +661,7 @@ curl -s -X POST localhost:3000/xrpc/com.atproto.repo.applyWrites -d '{"repo": curl -s "localhost:3000/xrpc/com.atproto.repo.getRecord?repo=$REPO\ &collection=app.bsky.actor.profile&rkey=self" curl -s "localhost:3000/xrpc/com.atproto.repo.listRecords?repo=$REPO\ -&collection=zone.quernstone.scrobble&limit=5" +&collection=com.vibescrobble.scrobble&limit=5" curl -s "localhost:3000/xrpc/com.atproto.repo.describeRepo?repo=$REPO" ``` @@ -711,8 +711,8 @@ locally every agent hostname arrives at the same listener. ### Where a record lands The record key comes from the collection's lexicon, not from the caller. -`zone.quernstone.scrobble` declares `key: "tid"` and gets a fresh sortable key -each time; `zone.quernstone.actor.registration` declares `key: "literal:self"` +`com.vibescrobble.scrobble` declares `key: "tid"` and gets a fresh sortable key +each time; `bot.did.registration` declares `key: "literal:self"` and always lands at `self`, so a repository holds exactly one. A collection this server holds no lexicon for β€” `app.bsky.actor.profile` is the one every account has β€” leaves the key to the caller, which is why the writes below name @@ -747,10 +747,10 @@ where the key sits, and the record itself. ```sh uri=$(curl -sG localhost:3000/xrpc/com.atproto.repo.listRecords \ - --data-urlencode "repo=$did" --data-urlencode collection=zone.quernstone.scrobble \ + --data-urlencode "repo=$did" --data-urlencode collection=com.vibescrobble.scrobble \ --data-urlencode limit=1 | jq -r '.records[0].uri') curl -sG localhost:3000/xrpc/com.atproto.sync.getRecord \ - --data-urlencode "did=$did" --data-urlencode collection=zone.quernstone.scrobble \ + --data-urlencode "did=$did" --data-urlencode collection=com.vibescrobble.scrobble \ --data-urlencode "rkey=${uri##*/}" -o proof.car goat repo inspect proof.car # the same commit as the export, same tree root ``` @@ -790,12 +790,12 @@ reference a record puts in a field: ```sh curl -s -X POST localhost:3000/xrpc/com.atproto.repo.uploadBlob \ -H 'content-type: image/png' \ - -H "quernstone-repo: $did" \ + -H "didbot-repo: $did" \ --data-binary @some.png | jq # {"blob":{"$type":"blob","ref":{"$link":"bafkrei…"},"mimeType":"image/png","size":2852}} ``` -The `quernstone-repo` header is which account the blob is for. +The `didbot-repo` header is which account the blob is for. `com.atproto.repo.uploadBlob` takes no parameters, because in atproto the account comes from the request's authentication and this server has none yet; It is a header rather than a query parameter. diff --git a/lexicons/zone/quernstone/memory.json b/lexicons/bot/did/memory.json similarity index 97% rename from lexicons/zone/quernstone/memory.json rename to lexicons/bot/did/memory.json index 9700bdf1..20b24705 100644 --- a/lexicons/zone/quernstone/memory.json +++ b/lexicons/bot/did/memory.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "zone.quernstone.memory", + "id": "bot.did.memory", "defs": { "main": { "type": "record", diff --git a/lexicons/zone/quernstone/actor/registration.json b/lexicons/bot/did/registration.json similarity index 99% rename from lexicons/zone/quernstone/actor/registration.json rename to lexicons/bot/did/registration.json index d5a9b34a..3da2d339 100644 --- a/lexicons/zone/quernstone/actor/registration.json +++ b/lexicons/bot/did/registration.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "zone.quernstone.actor.registration", + "id": "bot.did.registration", "defs": { "main": { "type": "record", diff --git a/lexicons/zone/quernstone/agent/vouch.json b/lexicons/bot/did/vouch.json similarity index 98% rename from lexicons/zone/quernstone/agent/vouch.json rename to lexicons/bot/did/vouch.json index 94ed08a2..33983a48 100644 --- a/lexicons/zone/quernstone/agent/vouch.json +++ b/lexicons/bot/did/vouch.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "zone.quernstone.agent.vouch", + "id": "bot.did.vouch", "defs": { "main": { "type": "record", diff --git a/lexicons/zone/quernstone/scrobble.json b/lexicons/com/vibescrobble/scrobble.json similarity index 82% rename from lexicons/zone/quernstone/scrobble.json rename to lexicons/com/vibescrobble/scrobble.json index 8bf1be1e..64032d2b 100644 --- a/lexicons/zone/quernstone/scrobble.json +++ b/lexicons/com/vibescrobble/scrobble.json @@ -1,6 +1,6 @@ { "lexicon": 1, - "id": "zone.quernstone.scrobble", + "id": "com.vibescrobble.scrobble", "defs": { "main": { "type": "record", @@ -59,12 +59,12 @@ "parent": { "type": "string", "format": "did", - "description": "DID of the agent account that spawned the agent which produced this scrobble. Copied here from zone.quernstone.agent.profile, which remains authoritative, so a consumer reading the firehose can place a scrobble in its spawning tree without fetching the author's profile. Absent for an agent nothing spawned, such as a session started by a human, and absent when the harness gave no way to tell; neither absence is a claim that the agent has no parent.", + "description": "DID of the agent account that spawned the agent which produced this scrobble. Copied here from bot.did.profile, which remains authoritative, so a consumer reading the firehose can place a scrobble in its spawning tree without fetching the author's profile. Absent for an agent nothing spawned, such as a session started by a human, and absent when the harness gave no way to tell; neither absence is a claim that the agent has no parent.", "maxLength": 512 }, "agentType": { "type": "string", - "description": "Harness-defined type or name of the agent that produced this scrobble, such as a named subagent type. Constant for the life of an agent account, and copied here from zone.quernstone.agent.profile so a consumer reading the firehose can render or filter a scrobble without fetching the author's profile; the profile remains authoritative. Open set: subagent types are defined per installation, so no enumeration is given and unrecognized values must be preserved. Absent for an agent whose harness gives it no type.", + "description": "Harness-defined type or name of the agent that produced this scrobble, such as a named subagent type. Constant for the life of an agent account, and copied here from bot.did.profile so a consumer reading the firehose can render or filter a scrobble without fetching the author's profile; the profile remains authoritative. Open set: subagent types are defined per installation, so no enumeration is given and unrecognized values must be preserved. Absent for an agent whose harness gives it no type.", "maxLength": 256 } } diff --git a/plan/README.md b/plan/README.md index bb06df64..53e71354 100644 --- a/plan/README.md +++ b/plan/README.md @@ -169,7 +169,7 @@ Answered no. Nothing yet. ## Where the lexicons are -There is no lexicon epic. A `zone.quernstone.*` schema is a deliverable of +There is no lexicon epic. A `bot.did.*` schema is a deliverable of whichever epic needs the record. Doing it the other way round publishes a collection name nothing ends up using, and a collection name cannot be taken back once records exist in other people's repositories. diff --git a/plan/account-types.md b/plan/account-types.md index b7357a80..02eab7e3 100644 --- a/plan/account-types.md +++ b/plan/account-types.md @@ -110,7 +110,7 @@ account carries the values, rather than the kind deciding behaviour directly. ## Done - [x] **One lexicon describes an account, and it is the registration.** - `zone.quernstone.agent.profile` is retired. Its presentation half was + `bot.did.profile` is retired. Its presentation half was always `app.bsky.actor.profile`'s job and that record is written already; its harness and agent type were admission facts and are the registration record's agent member; its parent was the weaker of two copies of the @@ -165,7 +165,7 @@ account carries the values, rather than the kind deciding behaviour directly. defined by what it does *not* have, and every fact a harness reports is one it may have failed to read. The union still does the work it was chosen for on the two kinds with real requirements. -- [x] **The `zone.quernstone.actor.registration` lexicon, with the kind as an +- [x] **The `bot.did.registration` lexicon, with the kind as an open union.** One record for every kind of account, which is what keeps `revision`, `handles` and `keys` from being written twice. The per-kind fields sit in a union member rather than behind a `kind` string, because diff --git a/plan/attestation.md b/plan/attestation.md index 424d928f..e688c04e 100644 --- a/plan/attestation.md +++ b/plan/attestation.md @@ -42,7 +42,7 @@ otherwise. ## Done -- [x] The `zone.quernstone.agent.attestation` lexicon. +- [x] The `bot.did.attestation` lexicon. - [x] The backend interface, and the development shared-secret backend, whose record says what it does not prove. - [x] A node-credential backend, so a shared-secret holder cannot claim any diff --git a/plan/labels.md b/plan/labels.md index 82146362..8f75e5ea 100644 --- a/plan/labels.md +++ b/plan/labels.md @@ -20,7 +20,7 @@ client puts it on the profile and on every post. So this epic buys reach, not assurance. The facts do not become truer for being signed a second time β€” a label from a service we run is our claim, exactly as -`zone.quernstone.actor.registration` is. What changes is that somebody else's +`bot.did.registration` is. What changes is that somebody else's software shows it to somebody who never asked us anything. ## The server is its own first account diff --git a/plan/memory.md b/plan/memory.md index 64d60c18..65ffae34 100644 --- a/plan/memory.md +++ b/plan/memory.md @@ -32,4 +32,4 @@ exists for. ## Done -- [x] The `zone.quernstone.memory` lexicon draft. +- [x] The `bot.did.memory` lexicon draft. diff --git a/plan/ownership.md b/plan/ownership.md index 52f36354..4b5c1035 100644 --- a/plan/ownership.md +++ b/plan/ownership.md @@ -46,7 +46,7 @@ ownership to the human's direction alone. ## Done -- [x] The `zone.quernstone.agent.vouch` lexicon, with scope, expiry and +- [x] The `bot.did.vouch` lexicon, with scope, expiry and revocation. - [x] Discovery along the chain: voucher, repository, vouch, server, with containment checked on the reading side. diff --git a/plan/pds-xrpc.md b/plan/pds-xrpc.md index 117b3a2f..4ee7434e 100644 --- a/plan/pds-xrpc.md +++ b/plan/pds-xrpc.md @@ -31,7 +31,7 @@ checked today. - [ ] **Rate limits, and what they do to a swarm** sharing one address. - [ ] **Relay posture.** Whether federated volume needs its own relay. Not live until federation is. -- [ ] **A real namespace.** `zone.quernstone` is a placeholder and the domain +- [ ] **A real namespace.** `bot.did` is a placeholder and the domain is not registered. A collection name cannot be taken back once records exist elsewhere. - [ ] **Resolve a lexicon we do not hold.** The stance is optimistic, so diff --git a/plan/provenance.md b/plan/provenance.md index 4d93f26c..d72611fe 100644 --- a/plan/provenance.md +++ b/plan/provenance.md @@ -19,7 +19,7 @@ model. Two are carried. The third is not. Blocked on two things nobody knows yet: what a harness can actually be asked for at startup, and what policies anyone wants to write about the answers. Both records this was deferring have since gone: - `zone.quernstone.agent.attestation` and `zone.quernstone.agent.profile` + `bot.did.attestation` and `bot.did.profile` are one registration record in [account-types](account-types.md) plus `app.bsky.actor.profile`, which this project did not invent. What is left here is the question rather than the records β€” what a harness can be @@ -77,8 +77,8 @@ model. Two are carried. The third is not. - [x] **A server-authored record every repository is born holding**, written into every repository before the repository is readable and recomposed from the bookkeeping whenever the bookkeeping moves. It was - `zone.quernstone.agent.identity` and is now - `zone.quernstone.actor.registration`; see + `bot.did.identity` and is now + `bot.did.registration`; see [account-types](account-types.md) for why the name changed and what the record absorbed. It carries only what this server can attest and what stays true: the DID, because a record @@ -127,6 +127,6 @@ model. Two are carried. The third is not. account's quota spent on pictures nothing points at. - [x] The harness, the agent type and a parent this deployment hosts, written at provisioning and read by the index. They were a - `zone.quernstone.agent.profile` and are now the agent member of the + `bot.did.profile` and are now the agent member of the registration record; the model went with neither, because it changes every turn and is already stamped on the records a turn writes. diff --git a/plan/scrobble.md b/plan/scrobble.md index e9324098..6d63e9b9 100644 --- a/plan/scrobble.md +++ b/plan/scrobble.md @@ -25,7 +25,7 @@ The last hop is missing β€” the tool does not write to a real server. ## Done -- [x] The `zone.quernstone.scrobble` lexicon, with text and a single-grapheme +- [x] The `com.vibescrobble.scrobble` lexicon, with text and a single-grapheme emoji. - [x] The MCP server, and the `PreToolUse` rewrite that stamps the acting agent's DID into the call. diff --git a/plan/zone-scale.md b/plan/zone-scale.md index ea0f0f08..e163e4be 100644 --- a/plan/zone-scale.md +++ b/plan/zone-scale.md @@ -33,7 +33,7 @@ the fix is cheap and knowing when to apply it is not free. - [ ] **Bigger pools, and say what each spec is worth.** A third fragment multiplies rather than adds. The choice is a deployment's and the arithmetic should not be. -- [ ] **Publish occupancy.** `zone.quernstone.stats` can carry how much of the +- [ ] **Publish occupancy.** `bot.did.stats` can carry how much of the pool is spoken for, so exhaustion is something an operator watched approaching rather than something a provisioning discovered. - [ ] **Decide what happens at exhaustion.** Numbered variants, a wider spec, or diff --git a/scripts/dev-pds.sh b/scripts/dev-pds.sh index f13cb0b9..d49a6da1 100755 --- a/scripts/dev-pds.sh +++ b/scripts/dev-pds.sh @@ -82,7 +82,7 @@ cargo install --quiet --force --debug --path crates/didbot-hookd # invokes it by name, so installing it would only add a second full build and a # second copy to go stale. say "personal data server on port ${PORT}, zone ${ZONE}, names ${NAMES}" -echo " stats curl -s http://localhost:${PORT}/xrpc/zone.quernstone.stats" +echo " stats curl -s http://localhost:${PORT}/xrpc/bot.did.stats" echo " events curl -N http://localhost:${PORT}/events" echo DATA_ARGS=() diff --git a/vectors/firehose/commit-sparse.json b/vectors/firehose/commit-sparse.json index e5cc2a0c..4aa3959d 100644 --- a/vectors/firehose/commit-sparse.json +++ b/vectors/firehose/commit-sparse.json @@ -5,11 +5,11 @@ "seq": 43, "instance": "01JEXAMPLEINSTANCE", "did": "did:web:kestrel.agents.localhost", - "collection": "zone.quernstone.scrobble", + "collection": "com.vibescrobble.scrobble", "rkey": "3lqm0000abcf6", - "uri": "at://did:web:kestrel.agents.localhost/zone.quernstone.scrobble/3lqm0000abcf6", + "uri": "at://did:web:kestrel.agents.localhost/com.vibescrobble.scrobble/3lqm0000abcf6", "record": { - "$type": "zone.quernstone.scrobble", + "$type": "com.vibescrobble.scrobble", "text": "a frame from an older server", "emoji": "πŸ•°οΈ", "createdAt": "2026-08-24T12:00:01Z" diff --git a/vectors/firehose/commit.json b/vectors/firehose/commit.json index d9208bd6..ea779c39 100644 --- a/vectors/firehose/commit.json +++ b/vectors/firehose/commit.json @@ -5,15 +5,15 @@ "seq": 42, "instance": "01JEXAMPLEINSTANCE", "did": "did:web:kestrel.agents.localhost", - "collection": "zone.quernstone.scrobble", + "collection": "com.vibescrobble.scrobble", "rkey": "3lqm0000abcd2", - "uri": "at://did:web:kestrel.agents.localhost/zone.quernstone.scrobble/3lqm0000abcd2", + "uri": "at://did:web:kestrel.agents.localhost/com.vibescrobble.scrobble/3lqm0000abcd2", "cid": "bafyreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy", "commit": "bafyreidfuxdmqvxvtqcyvzkxpvbdcvwjxlrqvbn5xk3vg6mfumzsrxvz4a", "rev": "3lqm0000abce4", "time": "2026-08-24T12:00:00Z", "record": { - "$type": "zone.quernstone.scrobble", + "$type": "com.vibescrobble.scrobble", "text": "reading the firehose reconnect path", "emoji": "🧡", "createdAt": "2026-08-24T12:00:00Z" diff --git a/web/detail.js b/web/detail.js index 01383091..b9e246a7 100644 --- a/web/detail.js +++ b/web/detail.js @@ -352,7 +352,7 @@ const Detail = (() => { // scrobble collection, and the zone its account was minted under. Shown // rather than linked, because there is no appview at the other end of // them yet and a dead link is worse than a string you can paste. - set(nodes.collection, `at://${agent.did}/zone.quernstone.scrobble`); + set(nodes.collection, `at://${agent.did}/com.vibescrobble.scrobble`); set(nodes.zone, operator ? operator.zone : "β€”"); panel.hidden = false;