swarm-wire: take agent_icon's wire types
This commit is contained in:
parent
9222a051f5
commit
c6b1364684
9 changed files with 77 additions and 76 deletions
|
|
@ -72,7 +72,7 @@ pub fn spawn() {
|
||||||
|
|
||||||
async fn run(agent: String) {
|
async fn run(agent: String) {
|
||||||
let client = crate::swarm_queue::client().await;
|
let client = crate::swarm_queue::client().await;
|
||||||
let subject = swarm_queue_client::agent_icon::subject(&agent);
|
let subject = swarm_wire::agent_icon::subject(&agent);
|
||||||
let icon = match read_icon(Path::new(ICON_PATH)) {
|
let icon = match read_icon(Path::new(ICON_PATH)) {
|
||||||
Ok(icon) => icon,
|
Ok(icon) => icon,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
//! property of it, so there is nothing for a timestamp to mean here.
|
//! property of it, so there is nothing for a timestamp to mean here.
|
||||||
//!
|
//!
|
||||||
//! **No hive is named on this path**, because the key does not carry one —
|
//! **No hive is named on this path**, because the key does not carry one —
|
||||||
//! see `swarm_queue_client::agent_icon` for why, and for who writes it.
|
//! see `swarm_wire::agent_icon` for why, and for who writes it.
|
||||||
//!
|
//!
|
||||||
//! This reader is also what creates the bucket: the agents that write it are
|
//! This reader is also what creates the bucket: the agents that write it are
|
||||||
//! granted their own key and nothing that could create a stream.
|
//! granted their own key and nothing that could create a stream.
|
||||||
|
|
|
||||||
|
|
@ -1165,7 +1165,7 @@ pub(crate) async fn get_agent_icon(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
swarm_queue_client::agent_icon::MEDIA_TYPE.to_owned(),
|
swarm_wire::agent_icon::MEDIA_TYPE.to_owned(),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
header::CACHE_CONTROL,
|
header::CACHE_CONTROL,
|
||||||
|
|
|
||||||
|
|
@ -1964,7 +1964,7 @@ mod tests {
|
||||||
/// A verified agent may write its own icon key and no other agent's.
|
/// A verified agent may write its own icon key and no other agent's.
|
||||||
#[test]
|
#[test]
|
||||||
fn an_agent_may_write_only_its_own_icon_key() {
|
fn an_agent_may_write_only_its_own_icon_key() {
|
||||||
use swarm_queue_client::agent_icon::subject;
|
use swarm_wire::agent_icon::subject;
|
||||||
|
|
||||||
let p = policy()
|
let p = policy()
|
||||||
.with_agent_token_subjects(vec!["$KV.agent-icons.{agent}".to_owned()])
|
.with_agent_token_subjects(vec!["$KV.agent-icons.{agent}".to_owned()])
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,5 @@
|
||||||
//! The per-agent icon KV bucket: one key per agent, holding the icon the
|
//! The connection side of the per-agent icon KV bucket: opening it. The
|
||||||
//! swarm shows for it.
|
//! bucket's name, media type and subject are `swarm_wire::agent_icon`.
|
||||||
//!
|
|
||||||
//! **Keyed by the agent alone — no hive token**, which is the one way this
|
|
||||||
//! bucket deliberately differs from its otherwise-identical sibling
|
|
||||||
//! `swarm_wire::agent_status` (`{hive}.{agent}`). An agent is not fixed to a
|
|
||||||
//! hive and can move between them; an icon keyed by where the agent
|
|
||||||
//! currently runs would be stranded under the old key by a migration, and
|
|
||||||
//! a reader would have to know the placement to ask the question at all.
|
|
||||||
//! Operator ruling: *"the bucket is per agent, not per hive. agents can
|
|
||||||
//! move hives."*
|
|
||||||
//!
|
|
||||||
//! That is also what makes the icon answerable for an agent whose
|
|
||||||
//! container is stopped: the value's lifetime is the agent's, not its
|
|
||||||
//! placement's.
|
|
||||||
//!
|
|
||||||
//! **Each agent writes its own key** with its own queue credential. The auth
|
|
||||||
//! callout grants that credential `$KV.agent-icons.<agent>` and no other key.
|
|
||||||
//!
|
|
||||||
//! The writer publishes to [`crate::agent_icon::subject`] directly instead of
|
|
||||||
//! opening the bucket, so that one subject is its whole grant. Creating the
|
|
||||||
//! bucket is left to the reader, with `open_or_create`. A write that arrives
|
|
||||||
//! before the bucket exists is refused with "no stream", and the writer
|
|
||||||
//! retries.
|
|
||||||
|
|
||||||
#[cfg(feature = "kv")]
|
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
/// The KV bucket agent icons are published into, keyed by agent name.
|
|
||||||
///
|
|
||||||
/// A constant and not an option, for the reason
|
|
||||||
/// [`swarm_wire::agent_status::BUCKET`] gives: writer and reader must name the
|
|
||||||
/// same bucket, and an option is a way for the two to disagree.
|
|
||||||
pub const BUCKET: &str = "agent-icons";
|
|
||||||
|
|
||||||
/// The media type of every value in this bucket.
|
|
||||||
///
|
|
||||||
/// The value is the icon's bytes **verbatim, not a JSON envelope**: an SVG
|
|
||||||
/// carries no metadata this bucket would have to describe, and the one
|
|
||||||
/// consumer serves the bytes straight back out. So the type is fixed here
|
|
||||||
/// rather than stored per entry — a publisher that has something other
|
|
||||||
/// than an SVG does not have an agent icon.
|
|
||||||
pub const MEDIA_TYPE: &str = "image/svg+xml";
|
|
||||||
|
|
||||||
/// The subject `agent`'s entry is written on: `$KV.<bucket>.<key>`, where the
|
|
||||||
/// key is the agent name and nothing else.
|
|
||||||
///
|
|
||||||
/// A KV put is a `JetStream` publish to this subject, so a writer that
|
|
||||||
/// publishes directly has to use the same subject the bucket's `get` reads.
|
|
||||||
#[must_use]
|
|
||||||
pub fn subject(agent: &str) -> String {
|
|
||||||
format!("$KV.{BUCKET}.{agent}")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Open the agent-icon bucket, creating it if nothing has yet.
|
/// Open the agent-icon bucket, creating it if nothing has yet.
|
||||||
///
|
///
|
||||||
|
|
@ -60,26 +9,13 @@ pub fn subject(agent: &str) -> String {
|
||||||
#[cfg(feature = "kv")]
|
#[cfg(feature = "kv")]
|
||||||
pub async fn open_or_create(
|
pub async fn open_or_create(
|
||||||
client: &async_nats::Client,
|
client: &async_nats::Client,
|
||||||
) -> Result<async_nats::jetstream::kv::Store, Error> {
|
) -> Result<async_nats::jetstream::kv::Store, crate::Error> {
|
||||||
let js = async_nats::jetstream::new(client.clone());
|
let js = async_nats::jetstream::new(client.clone());
|
||||||
crate::kv::open_or_create(
|
crate::kv::open_or_create(
|
||||||
&js,
|
&js,
|
||||||
BUCKET,
|
swarm_wire::agent_icon::BUCKET,
|
||||||
"agent-icon",
|
"agent-icon",
|
||||||
"Current icon published by each agent".to_owned(),
|
"Current icon published by each agent".to_owned(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::subject;
|
|
||||||
|
|
||||||
/// One token after the bucket, the agent name alone. That is what the
|
|
||||||
/// per-agent grant `$KV.agent-icons.{agent}` expands to, and what a
|
|
||||||
/// hive-scoped grant (`$KV.<bucket>.<hive>.*`, two tokens) cannot match.
|
|
||||||
#[test]
|
|
||||||
fn the_published_subject_carries_the_agent_and_no_placement() {
|
|
||||||
assert_eq!(subject("iris"), "$KV.agent-icons.iris");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
//! agent's whole `JetStream` grant: `read_own` builds the stream handle
|
//! agent's whole `JetStream` grant: `read_own` builds the stream handle
|
||||||
//! without a `STREAM.INFO`.
|
//! without a `STREAM.INFO`.
|
||||||
//!
|
//!
|
||||||
//! Keyed by the agent alone, like [`crate::agent_icon`], and for the same
|
//! Keyed by the agent alone, like `swarm_wire::agent_icon`, and for the same
|
||||||
//! reason: an agent can move between hives.
|
//! reason: an agent can move between hives.
|
||||||
|
|
||||||
/// The KV bucket schedules are published into, keyed by agent name.
|
/// The KV bucket schedules are published into, keyed by agent name.
|
||||||
|
|
|
||||||
|
|
@ -201,9 +201,10 @@ pub mod wanted;
|
||||||
/// merge, are in `swarm_wire::agent_status`.
|
/// merge, are in `swarm_wire::agent_status`.
|
||||||
pub mod agent_status;
|
pub mod agent_status;
|
||||||
|
|
||||||
/// The bucket agent icons are published into — one key per agent, with no
|
|
||||||
/// hive in it, unlike [`agent_status`]. See the module doc for why the
|
/// Opening the bucket agent icons are published into: one key per agent, with
|
||||||
/// placement stays out of the key, and for who may write it.
|
/// no hive in it, unlike [`agent_status`]. The name, media type and subject,
|
||||||
|
/// and why the placement stays out of the key, are in `swarm_wire::agent_icon`.
|
||||||
pub mod agent_icon;
|
pub mod agent_icon;
|
||||||
|
|
||||||
/// The bucket each agent mirrors its `AGENTS.md` into, and the edit command
|
/// The bucket each agent mirrors its `AGENTS.md` into, and the edit command
|
||||||
|
|
|
||||||
63
swarm-wire/src/agent_icon.rs
Normal file
63
swarm-wire/src/agent_icon.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
//! The per-agent icon KV bucket: one key per agent, holding the icon the
|
||||||
|
//! swarm shows for it.
|
||||||
|
//!
|
||||||
|
//! **Keyed by the agent alone — no hive token**, which is the one way this
|
||||||
|
//! bucket deliberately differs from its otherwise-identical sibling
|
||||||
|
//! `swarm_wire::agent_status` (`{hive}.{agent}`). An agent is not fixed to a
|
||||||
|
//! hive and can move between them; an icon keyed by where the agent
|
||||||
|
//! currently runs would be stranded under the old key by a migration, and
|
||||||
|
//! a reader would have to know the placement to ask the question at all.
|
||||||
|
//! Operator ruling: *"the bucket is per agent, not per hive. agents can
|
||||||
|
//! move hives."*
|
||||||
|
//!
|
||||||
|
//! That is also what makes the icon answerable for an agent whose
|
||||||
|
//! container is stopped: the value's lifetime is the agent's, not its
|
||||||
|
//! placement's.
|
||||||
|
//!
|
||||||
|
//! **Each agent writes its own key** with its own queue credential. The auth
|
||||||
|
//! callout grants that credential `$KV.agent-icons.<agent>` and no other key.
|
||||||
|
//!
|
||||||
|
//! The writer publishes to [`subject`] directly instead of opening the
|
||||||
|
//! bucket, so that one subject is its whole grant. Creating the bucket is left
|
||||||
|
//! to the reader (`swarm_queue_client::agent_icon::open_or_create`). A write
|
||||||
|
//! that arrives before the bucket exists is refused with "no stream", and the
|
||||||
|
//! writer retries.
|
||||||
|
|
||||||
|
/// The KV bucket agent icons are published into, keyed by agent name.
|
||||||
|
///
|
||||||
|
/// A constant and not an option, for the reason
|
||||||
|
/// [`crate::agent_status::BUCKET`] gives: writer and reader must name the
|
||||||
|
/// same bucket, and an option is a way for the two to disagree.
|
||||||
|
pub const BUCKET: &str = "agent-icons";
|
||||||
|
|
||||||
|
/// The media type of every value in this bucket.
|
||||||
|
///
|
||||||
|
/// The value is the icon's bytes **verbatim, not a JSON envelope**: an SVG
|
||||||
|
/// carries no metadata this bucket would have to describe, and the one
|
||||||
|
/// consumer serves the bytes straight back out. So the type is fixed here
|
||||||
|
/// rather than stored per entry — a publisher that has something other
|
||||||
|
/// than an SVG does not have an agent icon.
|
||||||
|
pub const MEDIA_TYPE: &str = "image/svg+xml";
|
||||||
|
|
||||||
|
/// The subject `agent`'s entry is written on: `$KV.<bucket>.<key>`, where the
|
||||||
|
/// key is the agent name and nothing else.
|
||||||
|
///
|
||||||
|
/// A KV put is a `JetStream` publish to this subject, so a writer that
|
||||||
|
/// publishes directly has to use the same subject the bucket's `get` reads.
|
||||||
|
#[must_use]
|
||||||
|
pub fn subject(agent: &str) -> String {
|
||||||
|
format!("$KV.{BUCKET}.{agent}")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::subject;
|
||||||
|
|
||||||
|
/// One token after the bucket, the agent name alone. That is what the
|
||||||
|
/// per-agent grant `$KV.agent-icons.{agent}` expands to, and what a
|
||||||
|
/// hive-scoped grant (`$KV.<bucket>.<hive>.*`, two tokens) cannot match.
|
||||||
|
#[test]
|
||||||
|
fn the_published_subject_carries_the_agent_and_no_placement() {
|
||||||
|
assert_eq!(subject("iris"), "$KV.agent-icons.iris");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
//! names a contract (the sender, the answerer, the auth-callout responder that
|
//! names a contract (the sender, the answerer, the auth-callout responder that
|
||||||
//! grants the subject) depends on this one without linking either.
|
//! grants the subject) depends on this one without linking either.
|
||||||
|
|
||||||
|
pub mod agent_icon;
|
||||||
pub mod agent_inference;
|
pub mod agent_inference;
|
||||||
pub mod agent_login;
|
pub mod agent_login;
|
||||||
pub mod agent_status;
|
pub mod agent_status;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue