use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::fs::{self, File};
use std::io::Read;
use std::path::{Component, Path, PathBuf};
use std::sync::{Arc, OnceLock};
use crate::WyrdClient;
use crate::bifrost::{Bifrost, BifrostOptions, QueueConfig, TableConfig};
use crate::cards::{CardSelector, Cards};
use crate::observe::Run;
use crate::observe::lifecycle::{BifrostLifecycle, StartedBifrost};
use base64::Engine;
use serde::Deserialize;
use serde_json::{Value, json};
use sha2::{Digest, Sha256};
use wyrd_cards::data::DataCard;
use wyrd_cards::model::ModelCard;
use wyrd_cards::prompt::PromptCard;
use wyrd_spec::api_version::ApiVersion;
use wyrd_spec::card::agent::AgentCard;
use wyrd_spec::card::verifier::{VerificationBinding, VerifierSpec};
use wyrd_spec::envelope::{Card, CardKind, Relationships, Spec};
use wyrd_spec::error::WyrdError;
use wyrd_spec::ids::CardUid;
use wyrd_spec::reference::{
CardRef, registration_only_sibling_refs, scope_child_card_refs, unresolved_card_ref_paths,
};
#[cfg(feature = "internal")]
use wyrd_spec::registry::{
ArtifactManifestEntry, RelativeArtifactPath, canonical_artifact_manifest_hash,
};
use wyrd_spec::registry::{BundleArtifactManifest, BundleCardManifest, BundleManifest, BundleMode};
#[derive(Debug, Clone)]
struct HydratedArtifact {
relative_path: String,
local_path: PathBuf,
#[cfg(feature = "internal")]
sha256: String,
#[cfg(feature = "internal")]
size_bytes: u64,
#[cfg(feature = "internal")]
content_type: Option<String>,
}
#[derive(Default)]
struct TypedCards {
agents: BTreeMap<String, AgentCard>,
prompts: BTreeMap<String, PromptCard>,
models: BTreeMap<String, ModelCard>,
data: BTreeMap<String, DataCard>,
}
impl fmt::Debug for TypedCards {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("TypedCards")
.field("agents", &self.agents.keys().collect::<Vec<_>>())
.field("prompts", &self.prompts.keys().collect::<Vec<_>>())
.field("models", &self.models.keys().collect::<Vec<_>>())
.field("data", &self.data.keys().collect::<Vec<_>>())
.finish()
}
}
impl TypedCards {
fn agent(&self, key: &str) -> Result<HydratedAgent<'_>, WyrdError> {
let card = self.agents.get(key).ok_or_else(|| {
state_bundle_error(
"validated typed Card map is missing its exact CardRef entry",
json!({ "card_ref_key": key, "expected_kind": CardKind::Agent.wire_name() }),
)
})?;
let prompt_ref = card.spec.prompt.as_card_ref().ok_or_else(|| {
state_bundle_error(
"Agent Prompt has no registered Prompt Card identity; re-register and rehydrate",
json!({ "card_ref_key": key }),
)
})?;
let prompt = self.prompts.get(&prompt_ref.to_string()).ok_or_else(|| {
state_bundle_error(
"Agent prompt target is missing or is not a Prompt Card",
json!({ "card_ref_key": key, "card_ref": prompt_ref }),
)
})?;
Ok(HydratedAgent {
card,
prompt_ref,
prompt,
})
}
}
#[derive(Clone, Copy)]
pub struct HydratedAgent<'a> {
card: &'a AgentCard,
prompt_ref: &'a CardRef,
prompt: &'a PromptCard,
}
impl<'a> HydratedAgent<'a> {
#[must_use]
pub fn card(&self) -> &'a AgentCard {
self.card
}
#[must_use]
pub fn prompt(&self) -> &'a PromptCard {
self.prompt
}
#[must_use]
pub fn prompt_ref(&self) -> &'a CardRef {
self.prompt_ref
}
}
#[derive(Clone, Copy)]
pub enum HydratedCard<'a> {
Agent(HydratedAgent<'a>),
Prompt(&'a PromptCard),
Model(&'a ModelCard),
Data(&'a DataCard),
Envelope(&'a Card),
}
#[derive(Debug)]
struct HydratedStateIndex {
root_key: String,
root_ref: CardRef,
cards_by_ref: BTreeMap<String, Card>,
refs_by_key: BTreeMap<String, CardRef>,
aliases: BTreeMap<String, String>,
#[cfg(feature = "internal")]
aliases_by_ref: BTreeMap<String, Vec<String>>,
#[cfg(feature = "internal")]
artifacts_by_ref: BTreeMap<String, Vec<HydratedArtifact>>,
#[cfg(feature = "internal")]
artifact_dirs_by_ref: BTreeMap<String, PathBuf>,
typed: TypedCards,
}
impl HydratedStateIndex {
fn assemble(
root_ref: CardRef,
loaded: Vec<LoadedManifestCard>,
aliases: BTreeMap<String, String>,
) -> Result<Self, WyrdError> {
let root_key = root_ref.to_string();
let mut cards_by_ref = BTreeMap::new();
let mut refs_by_key = BTreeMap::new();
#[cfg(feature = "internal")]
let mut aliases_by_ref = BTreeMap::<String, Vec<String>>::new();
#[cfg(feature = "internal")]
let mut artifacts_by_ref = BTreeMap::new();
#[cfg(feature = "internal")]
let mut artifact_dirs_by_ref = BTreeMap::new();
for item in loaded {
let key = item.key;
refs_by_key.insert(key.clone(), item.card_ref);
cards_by_ref.insert(key.clone(), item.card);
#[cfg(feature = "internal")]
{
let mut item_aliases = item.aliases;
item_aliases.sort();
aliases_by_ref.insert(key.clone(), item_aliases);
if let Some(artifact_dir) = item.artifact_dir {
artifact_dirs_by_ref.insert(key.clone(), artifact_dir);
}
artifacts_by_ref.insert(key, item.artifacts);
}
}
if !cards_by_ref.contains_key(&root_key) {
return Err(state_bundle_error(
"validated root key points to a missing Card",
json!({ "root": root_ref }),
));
}
let typed = hydrate_typed_cards(&cards_by_ref)?;
Ok(Self {
root_key,
root_ref,
cards_by_ref,
refs_by_key,
aliases,
#[cfg(feature = "internal")]
aliases_by_ref,
#[cfg(feature = "internal")]
artifacts_by_ref,
#[cfg(feature = "internal")]
artifact_dirs_by_ref,
typed,
})
}
fn resolve_key(&self, alias: &str) -> Result<&str, WyrdError> {
self.aliases
.get(alias)
.map(String::as_str)
.ok_or_else(|| WyrdError::SdkUnknownAlias {
message: format!("WyrdState alias {alias:?} is not known"),
details: json!({
"alias": alias,
"available_aliases": self.aliases.keys().cloned().collect::<Vec<_>>(),
}),
})
}
fn card_by_key(&self, key: &str) -> Result<&Card, WyrdError> {
self.cards_by_ref.get(key).ok_or_else(|| {
state_bundle_error(
"validated CardRef key points to a missing Card",
json!({ "card_ref_key": key }),
)
})
}
fn card_ref_by_key(&self, key: &str) -> Result<&CardRef, WyrdError> {
self.refs_by_key.get(key).ok_or_else(|| {
state_bundle_error(
"validated CardRef key points to a missing reference",
json!({ "card_ref_key": key }),
)
})
}
#[cfg(feature = "internal")]
fn aliases_by_key(&self, key: &str) -> Result<&[String], WyrdError> {
self.aliases_by_ref
.get(key)
.map(Vec::as_slice)
.ok_or_else(|| {
state_bundle_error(
"validated CardRef key points to missing aliases",
json!({ "card_ref_key": key }),
)
})
}
#[cfg(feature = "internal")]
fn artifacts_by_key(&self, key: &str) -> Result<&[HydratedArtifact], WyrdError> {
self.artifacts_by_ref
.get(key)
.map(Vec::as_slice)
.ok_or_else(|| {
state_bundle_error(
"validated CardRef key points to missing artifacts",
json!({ "card_ref_key": key }),
)
})
}
#[cfg(feature = "internal")]
fn artifact_dir_by_key(&self, key: &str) -> Result<Option<&Path>, WyrdError> {
if !self.refs_by_key.contains_key(key) {
return Err(state_bundle_error(
"validated CardRef key points to a missing reference",
json!({ "card_ref_key": key }),
));
}
Ok(self.artifact_dirs_by_ref.get(key).map(PathBuf::as_path))
}
#[cfg(feature = "internal")]
fn artifact_manifest_hash_by_key(&self, key: &str) -> Result<Option<String>, WyrdError> {
let manifest = self
.artifacts_by_key(key)?
.iter()
.map(|artifact| {
Ok(ArtifactManifestEntry {
relative_path: RelativeArtifactPath::new(&artifact.relative_path)?,
sha256: Some(artifact.sha256.clone()),
size_bytes: Some(artifact.size_bytes),
content_type: artifact.content_type.clone(),
})
})
.collect::<Result<Vec<_>, WyrdError>>()?;
canonical_artifact_manifest_hash(&manifest)
}
}
const ROOT_ALIAS: &str = "root";
#[derive(Debug)]
pub(crate) struct BoundVerifier {
pub(crate) verifier_card_uid: CardUid,
pub(crate) card_uid: CardUid,
pub(crate) spec: VerifierSpec,
}
fn required_uid(card_ref: &CardRef) -> Result<CardUid, WyrdError> {
card_ref.uid.clone().ok_or_else(|| {
state_bundle_error(
"a hydrated Card reference carries no UID",
json!({ "card_ref": card_ref }),
)
})
}
#[derive(Debug, Clone)]
pub struct WyrdState {
index: Arc<HydratedStateIndex>,
bifrost: Arc<BifrostLifecycle>,
client: Arc<OnceLock<WyrdClient>>,
}
#[derive(Debug, Clone, Default)]
pub struct DeclareOptions {
pub client: Option<WyrdClient>,
pub bundle_dir: Option<PathBuf>,
}
#[derive(Debug)]
struct BundleReader<'a> {
requested_root: &'a Path,
root: PathBuf,
}
impl<'a> BundleReader<'a> {
fn new(bundle: &'a Path) -> Result<Self, WyrdError> {
Ok(Self {
requested_root: bundle,
root: absolute_bundle(bundle)?,
})
}
fn read_manifest(&self) -> Result<BundleManifest, WyrdError> {
let metadata_path = self.root.join("metadata.yaml");
let metadata = fs::symlink_metadata(&metadata_path).map_err(|error| {
if error.kind() == std::io::ErrorKind::NotFound {
unhydrated_error(&metadata_path, "Card bundle metadata.yaml is absent")
} else {
state_bundle_error(
"Card bundle metadata.yaml cannot be read",
json!({ "path": metadata_path, "source": error.to_string() }),
)
}
})?;
if !metadata.file_type().is_symlink() && !metadata.is_file() {
return Err(unhydrated_error(
&metadata_path,
"Card bundle metadata.yaml is not a regular file",
));
}
let metadata_path = confined_existing_file(&self.root, "metadata.yaml")?;
read_yaml(&metadata_path)
}
}
impl WyrdState {
pub fn from_path(path: impl AsRef<Path>) -> Result<Self, WyrdError> {
let reader = BundleReader::new(path.as_ref())?;
let manifest = reader.read_manifest()?;
reader.validate_manifest_header(&manifest)?;
let index = reader.load_index(manifest)?;
Ok(Self {
index: Arc::new(index),
bifrost: Arc::new(BifrostLifecycle::default()),
client: Arc::new(OnceLock::new()),
})
}
pub fn from_path_with(path: impl AsRef<Path>, client: WyrdClient) -> Result<Self, WyrdError> {
let state = Self::from_path(path)?;
let _ = state.client.set(client);
Ok(state)
}
pub async fn declare(path: impl AsRef<Path>) -> Result<Self, WyrdError> {
Self::declare_with(path, DeclareOptions::default()).await
}
pub async fn declare_with(
path: impl AsRef<Path>,
options: DeclareOptions,
) -> Result<Self, WyrdError> {
let client = match options.client {
Some(client) => client,
None => WyrdClient::from_global().map_err(WyrdError::from)?,
};
let cards = Cards::connect_with(&client).await?;
let receipt = cards.declare_from_path(path).await?;
let root = CardSelector::exact(receipt.root);
if let Some(bundle_dir) = options.bundle_dir {
cards.pull(root, &bundle_dir, BundleMode::Complete).await?;
return Self::from_path_with(&bundle_dir, client);
}
let staging = tempfile::Builder::new()
.prefix("wyrd-state-")
.tempdir()
.map_err(|error| {
state_bundle_error(
"the temporary state directory cannot be created",
json!({ "source": error.to_string() }),
)
})?;
let bundle = staging.path().join("bundle");
cards.pull(root, &bundle, BundleMode::Complete).await?;
Self::from_path_with(&bundle, client)
}
pub async fn start_bifrost(&self) -> Result<(), WyrdError> {
self.start_bifrost_with_config(None, QueueConfig::default())
.await
}
pub async fn start_bifrost_with(&self, table: Option<TableConfig>) -> Result<(), WyrdError> {
self.start_bifrost_with_config(table, QueueConfig::default())
.await
}
pub async fn start_bifrost_with_config(
&self,
table: Option<TableConfig>,
queue: QueueConfig,
) -> Result<(), WyrdError> {
let claim = self.bifrost.claim()?;
let client = self.client()?;
claim
.complete(Bifrost::connect_with(client, BifrostOptions { table, queue }).await?)
.await
}
pub fn client(&self) -> Result<&WyrdClient, WyrdError> {
if let Some(client) = self.client.get() {
return Ok(client);
}
let client = WyrdClient::from_global().map_err(WyrdError::from)?;
Ok(self.client.get_or_init(|| client))
}
pub(crate) fn bound_verifier(
&self,
subject: &CardRef,
name: &str,
) -> Result<BoundVerifier, WyrdError> {
let subject_key = subject.to_string();
let mut bindings: Vec<&VerificationBinding> = Vec::new();
if let Spec::Service(service) = &self.service().spec {
if subject_key == self.index.root_key {
bindings.extend(&service.verified_by);
}
for component in &service.components {
if component.card_ref.as_card_ref() == Some(subject) {
bindings.extend(&component.verified_by);
}
}
}
if let Spec::Agent(agent) = &self.index.card_by_key(&subject_key)?.spec {
bindings.extend(&agent.verified_by);
}
for binding in bindings {
let Some(verifier_ref) = binding.verifier.as_card_ref() else {
continue;
};
let card = self.index.card_by_key(&verifier_ref.to_string())?;
if card.metadata.name.as_str() != name {
continue;
}
let Spec::Verifier(spec) = &card.spec else {
return Err(state_bundle_error(
"a verified_by binding names a Card that is not a Verifier",
json!({ "verifier": verifier_ref }),
));
};
let spec = serde_json::to_value(spec)
.ok()
.and_then(|value| serde_json::from_value(value).ok())
.ok_or_else(|| {
state_bundle_error(
"a bound Verifier does not declare building-block inputs and requirements",
json!({ "verifier": verifier_ref }),
)
})?;
return Ok(BoundVerifier {
verifier_card_uid: required_uid(verifier_ref)?,
card_uid: required_uid(subject)?,
spec,
});
}
Err(WyrdError::SdkUnknownVerifier {
message: format!("no Verifier named {name:?} is bound to {subject}"),
details: json!({ "verifier": name, "subject": subject }),
})
}
#[must_use]
pub fn run(&self) -> Run {
Run::new(self.clone(), ROOT_ALIAS.to_owned(), self.root_ref().clone())
}
pub fn run_for_card(&self, alias: &str) -> Result<Run, WyrdError> {
let subject = self.card_ref(alias)?.clone();
Ok(Run::new(self.clone(), alias.to_owned(), subject))
}
pub async fn flush(&self) -> Result<(), WyrdError> {
Ok(self.bifrost.started()?.bifrost.flush().await?)
}
pub async fn shutdown(&self) -> Result<(), WyrdError> {
self.bifrost.shutdown().await
}
#[cfg(test)]
pub(crate) async fn adopt_started_bifrost_for_test(
&self,
bifrost: Bifrost,
) -> Result<(), WyrdError> {
self.bifrost.claim()?.complete(bifrost).await
}
pub(crate) fn started_bifrost(&self) -> Result<Arc<StartedBifrost>, WyrdError> {
self.bifrost.started()
}
#[cfg(feature = "test-support")]
#[cfg(feature = "internal")]
pub fn bifrost_metrics(&self) -> Result<crate::bifrost::BifrostMetrics, WyrdError> {
Ok(self.started_bifrost()?.bifrost.metrics())
}
#[must_use]
pub fn root_ref(&self) -> &CardRef {
&self.index.root_ref
}
#[must_use]
pub fn service(&self) -> &Card {
self.index
.card_by_key(&self.index.root_key)
.expect("validated hydrated state index root key must identify a Card")
}
pub fn aliases(&self) -> impl Iterator<Item = &str> {
self.index.aliases.keys().map(String::as_str)
}
pub fn card(&self, alias: &str) -> Result<&Card, WyrdError> {
let key = self.index.resolve_key(alias)?;
self.index.card_by_key(key).map_err(|_| {
state_bundle_error(
"validated alias points to a missing Card",
json!({ "alias": alias, "card_ref_key": key }),
)
})
}
pub fn card_ref(&self, alias: &str) -> Result<&CardRef, WyrdError> {
let key = self.index.resolve_key(alias)?;
self.index.card_ref_by_key(key).map_err(|_| {
state_bundle_error(
"validated alias points to a missing Card reference",
json!({ "alias": alias, "card_ref_key": key }),
)
})
}
#[cfg(feature = "internal")]
pub fn aliases_for(&self, card_ref: &CardRef) -> Result<&[String], WyrdError> {
let key = card_ref.to_string();
self.index.aliases_by_key(&key).map_err(|_| {
state_bundle_error(
"requested CardRef is not a member of this WyrdState graph",
json!({ "card_ref": card_ref }),
)
})
}
#[cfg(feature = "internal")]
pub fn artifact_dir(&self, alias: &str) -> Result<Option<&Path>, WyrdError> {
let key = self.index.resolve_key(alias)?;
self.artifact_dir_by_key(key)
}
pub fn agent(&self, alias: &str) -> Result<HydratedAgent<'_>, WyrdError> {
let key = self.typed_key(alias, &CardKind::Agent)?;
self.index.typed.agent(key)
}
pub fn get(&self, alias: &str) -> Result<HydratedCard<'_>, WyrdError> {
Ok(match self.card_ref(alias)?.kind {
CardKind::Agent => HydratedCard::Agent(self.agent(alias)?),
CardKind::Prompt => HydratedCard::Prompt(self.prompt(alias)?),
CardKind::Model => HydratedCard::Model(self.model(alias)?),
CardKind::Data => HydratedCard::Data(self.data(alias)?),
_ => HydratedCard::Envelope(self.card(alias)?),
})
}
pub fn prompt(&self, alias: &str) -> Result<&PromptCard, WyrdError> {
let key = self.typed_key(alias, &CardKind::Prompt)?;
self.index
.typed
.prompts
.get(key)
.ok_or_else(|| typed_map_invariant(alias, key, &CardKind::Prompt))
}
pub fn model(&self, alias: &str) -> Result<&ModelCard, WyrdError> {
let key = self.typed_key(alias, &CardKind::Model)?;
self.index
.typed
.models
.get(key)
.ok_or_else(|| typed_map_invariant(alias, key, &CardKind::Model))
}
pub fn data(&self, alias: &str) -> Result<&DataCard, WyrdError> {
let key = self.typed_key(alias, &CardKind::Data)?;
self.index
.typed
.data
.get(key)
.ok_or_else(|| typed_map_invariant(alias, key, &CardKind::Data))
}
pub fn verifier(&self, alias: &str) -> Result<&VerifierSpec, WyrdError> {
self.spec_of_kind(alias, &CardKind::Verifier, |spec| match spec {
Spec::Verifier(spec) => Some(spec),
_ => None,
})
}
fn typed_key(&self, alias: &str, expected: &CardKind) -> Result<&str, WyrdError> {
let key = self.index.resolve_key(alias)?;
let card_ref = self.index.card_ref_by_key(key)?;
if &card_ref.kind != expected {
return Err(WyrdError::SdkCardKindMismatch {
message: format!("alias `{alias}` is not a {} Card", expected.wire_name()),
details: json!({
"alias": alias,
"expected_kind": expected.wire_name(),
"actual_kind": card_ref.kind.wire_name(),
"card_ref": card_ref,
}),
});
}
Ok(key)
}
fn spec_of_kind<T, F>(
&self,
alias: &str,
expected: &CardKind,
extract: F,
) -> Result<&T, WyrdError>
where
F: FnOnce(&Spec) -> Option<&T>,
{
let key = self.typed_key(alias, expected)?;
let card = self.index.card_by_key(key)?;
extract(&card.spec).ok_or_else(|| {
state_bundle_error(
"Card envelope kind and spec variant disagree",
json!({
"alias": alias,
"expected_kind": expected.wire_name(),
"actual_kind": card.kind.wire_name(),
"card_ref": self.index.card_ref_by_key(key).ok(),
}),
)
})
}
#[cfg(feature = "internal")]
pub fn cards(&self) -> impl Iterator<Item = (&str, &Card)> {
self.index
.cards_by_ref
.iter()
.map(|(key, card)| (key.as_str(), card))
}
#[cfg(feature = "internal")]
pub fn cards_of_kind(&self, kind: CardKind) -> impl Iterator<Item = (&str, &Card)> {
self.index
.cards_by_ref
.iter()
.filter(move |(_, card)| card.kind == kind)
.map(|(key, card)| (key.as_str(), card))
}
#[cfg(feature = "internal")]
pub fn card_ref_by_key(&self, key: &str) -> Result<&CardRef, WyrdError> {
self.index.card_ref_by_key(key)
}
#[cfg(feature = "internal")]
pub fn aliases_by_key(&self, key: &str) -> Result<&[String], WyrdError> {
self.index.aliases_by_key(key)
}
#[cfg(feature = "internal")]
pub fn artifact_dir_by_key(&self, key: &str) -> Result<Option<&Path>, WyrdError> {
self.index.artifact_dir_by_key(key)
}
#[cfg(feature = "internal")]
pub fn artifact_manifest_hash_by_key(&self, key: &str) -> Result<Option<String>, WyrdError> {
self.index.artifact_manifest_hash_by_key(key)
}
}
impl BundleReader<'_> {
fn validate_manifest_header(&self, manifest: &BundleManifest) -> Result<(), WyrdError> {
if manifest.api_version != "wyrd/bundle/v1" {
return Err(state_bundle_error(
"unsupported Card bundle apiVersion",
json!({ "path": self.requested_root, "api_version": manifest.api_version }),
));
}
if manifest.mode == BundleMode::MetadataOnly {
return Err(unhydrated_error(
self.requested_root,
"Card bundle contains metadata only and cannot be used as runtime state",
));
}
if manifest.cards.is_empty()
|| manifest.card_count != manifest.cards.len()
|| manifest.artifact_count
!= manifest
.cards
.iter()
.map(|card| card.artifacts.len())
.sum::<usize>()
|| manifest.downloaded_artifact_count != manifest.artifact_count
{
return Err(state_bundle_error(
"Card bundle manifest counts are inconsistent",
json!({
"path": self.requested_root,
"card_count": manifest.card_count,
"actual_card_count": manifest.cards.len(),
"artifact_count": manifest.artifact_count,
"downloaded_artifact_count": manifest.downloaded_artifact_count,
}),
));
}
if manifest.root.kind != CardKind::Service {
return Err(state_bundle_error(
"Card bundle root must be a Service Card",
json!({ "root": manifest.root }),
));
}
if manifest.root.space.is_none() || manifest.root.uid.is_none() {
return Err(state_bundle_error(
"Card bundle root must have an exact space and UID",
json!({ "root": manifest.root }),
));
}
Ok(())
}
fn load_index(&self, manifest: BundleManifest) -> Result<HydratedStateIndex, WyrdError> {
let mut loaded = Vec::with_capacity(manifest.cards.len());
let mut known = BTreeSet::new();
let mut aliases = BTreeMap::new();
for entry in &manifest.cards {
let item = self.load_manifest_card(entry)?;
if !known.insert(item.key.clone()) {
return Err(invalid_duplicate_ref(&item.card_ref));
}
for alias in &item.aliases {
if let Some(existing) = aliases.insert(alias.clone(), item.key.clone()) {
return Err(conflicting_alias(alias, &existing, &item.key));
}
}
loaded.push(item);
}
for item in &loaded {
Self::validate_spec_refs(&item.card, &known)?;
Self::validate_relationships(&item.card, &aliases, &known)?;
}
Self::validate_root(&manifest.root, &loaded)?;
Self::validate_reachable_from_root(&manifest.root, &loaded)?;
HydratedStateIndex::assemble(manifest.root, loaded, aliases)
}
fn load_manifest_card(
&self,
entry: &BundleCardManifest,
) -> Result<LoadedManifestCard, WyrdError> {
let card_path = confined_existing_file(&self.root, &entry.card_path)?;
let relationships_path = confined_existing_file(&self.root, &entry.relationships_path)?;
let inventory_path = confined_existing_file(&self.root, &entry.artifact_inventory_path)?;
let card: Card = read_yaml(&card_path)?;
if card.api_version.as_str() != ApiVersion::V1 {
return Err(state_bundle_error(
"hydrated Card does not use apiVersion wyrd/v1",
json!({ "path": card_path, "api_version": card.api_version }),
));
}
let relationships: Relationships = read_yaml(&relationships_path)?;
let inventory: Vec<BundleArtifactManifest> = read_yaml(&inventory_path)?;
if relationships != card.relationships {
return Err(state_bundle_error(
"hydrated relationship projection does not match its Card envelope",
json!({ "card_path": card_path, "relationships_path": relationships_path }),
));
}
if inventory != entry.artifacts {
return Err(state_bundle_error(
"hydrated artifact inventory projection does not match its manifest",
json!({ "card_path": card_path, "inventory_path": inventory_path }),
));
}
let card_ref = exact_card_ref(&card, &card_path)?;
if card_ref != entry.card_ref {
return Err(state_bundle_error(
"hydrated Card envelope does not match its manifest CardRef",
json!({ "card_path": card_path, "manifest_card_ref": entry.card_ref, "actual_card_ref": card_ref }),
));
}
if card_ref.uid.is_none() {
return Err(state_bundle_error(
"hydrated Card is missing its required UID",
json!({ "card_ref": card_ref, "card_path": card_path }),
));
}
let mut artifacts = Vec::with_capacity(entry.artifacts.len());
for artifact in &entry.artifacts {
artifacts.push(self.validate_artifact(artifact)?);
}
let artifact_dir = if artifacts.is_empty() {
None
} else {
let card_dir = card_path.parent().ok_or_else(|| {
state_bundle_error(
"hydrated Card path has no parent directory",
json!({ "path": card_path }),
)
})?;
let artifact_dir = confined_existing_dir(
&self.root,
&relative_path(&self.root, &card_dir.join("artifacts"))?,
)?;
for artifact in &artifacts {
let expected = artifact_dir.join(&artifact.relative_path);
if artifact.local_path != expected {
return Err(state_bundle_error(
"hydrated artifact payload is outside its Card artifact directory",
json!({ "relative_path": artifact.relative_path, "local_path": artifact.local_path, "artifact_dir": artifact_dir }),
));
}
}
Some(artifact_dir)
};
for alias in &entry.aliases {
validate_alias(alias)?;
let alias_path = confined_existing_file(&self.root, &format!("aliases/{alias}.yaml"))?;
let record: AliasRecord = read_yaml(&alias_path)?;
if record.alias != *alias
|| record.card_ref != card_ref
|| record.card_path != entry.card_path
{
return Err(state_bundle_error(
"hydrated alias projection does not match its Card manifest",
json!({ "alias": alias, "alias_path": alias_path, "card_ref": card_ref }),
));
}
}
#[cfg(not(feature = "internal"))]
let _ = (artifact_dir, artifacts);
Ok(LoadedManifestCard {
key: card_ref.to_string(),
card_ref,
card,
aliases: entry.aliases.clone(),
#[cfg(feature = "internal")]
artifacts,
#[cfg(feature = "internal")]
artifact_dir,
})
}
fn validate_spec_refs(card: &Card, known: &BTreeSet<String>) -> Result<(), WyrdError> {
if let Some(path) = unresolved_card_ref_paths(&card.spec).first() {
return Err(state_bundle_error(
"hydrated Card contains an unresolved path reference",
json!({ "card_ref": exact_card_ref_for_error(card), "path": path }),
));
}
if let Some(sibling) = registration_only_sibling_refs(&card.spec).first() {
return Err(state_bundle_error(
"hydrated Card contains a registration-only sibling reference",
json!({
"owner": exact_card_ref_for_error(card),
"card_ref": sibling,
"reference_form": "sibling",
}),
));
}
for card_ref in scope_child_card_refs(&card.spec) {
if card_ref.uid.is_none() {
return Err(state_bundle_error(
"hydrated Card reference is missing its required UID",
json!({ "card_ref": card_ref, "owner": exact_card_ref_for_error(card) }),
));
}
let key = card_ref.to_string();
if !known.contains(&key) {
return Err(state_bundle_error(
"hydrated Card spec reference is absent from the graph",
json!({ "card_ref": card_ref, "owner": exact_card_ref_for_error(card) }),
));
}
}
Ok(())
}
fn validate_relationships(
card: &Card,
aliases: &BTreeMap<String, String>,
known: &BTreeSet<String>,
) -> Result<(), WyrdError> {
let expected = wyrd_spec::graph::relationships_from_spec(&card.spec);
if card.relationships.outbound != expected.outbound
|| card.relationships.outbound_refs != expected.outbound_refs
{
return Err(state_bundle_error(
"hydrated relationship projection does not match its resolved Card spec",
json!({ "owner": exact_card_ref_for_error(card) }),
));
}
for relationship in &card.relationships.outbound_refs {
let card_ref = &relationship.card_ref;
if card_ref.uid.is_none() {
return Err(state_bundle_error(
"hydrated relationship is missing its required UID",
json!({ "owner": exact_card_ref_for_error(card), "card_ref": card_ref }),
));
}
let key = card_ref.to_string();
if !known.contains(&key) {
return Err(state_bundle_error(
"hydrated relationship target is absent from the graph",
json!({ "owner": exact_card_ref_for_error(card), "card_ref": card_ref }),
));
}
if let Some(alias) = &relationship.alias
&& aliases.get(alias) != Some(&key)
{
return Err(state_bundle_error(
"hydrated relationship alias does not resolve to its CardRef",
json!({ "owner": exact_card_ref_for_error(card), "alias": alias, "card_ref": card_ref }),
));
}
}
Ok(())
}
fn validate_artifact(
&self,
entry: &BundleArtifactManifest,
) -> Result<HydratedArtifact, WyrdError> {
validate_artifact_path(&entry.relative_path)?;
let local_path = entry.local_path.as_deref().ok_or_else(|| {
unhydrated_error(
&self.root,
"complete hydration is missing an artifact payload path",
)
})?;
let payload = confined_existing_file(&self.root, local_path)?;
let metadata = fs::metadata(&payload).map_err(|error| {
state_bundle_error(
"hydrated artifact payload cannot be read",
json!({ "path": payload, "source": error.to_string() }),
)
})?;
let expected_size = u64::try_from(entry.size_bytes).map_err(|_| {
state_bundle_error(
"hydrated artifact inventory has a negative size",
json!({ "path": payload, "size_bytes": entry.size_bytes }),
)
})?;
if metadata.len() != expected_size {
return Err(state_bundle_error(
"hydrated artifact payload size does not match its inventory",
json!({ "path": payload, "expected_size": expected_size, "actual_size": metadata.len() }),
));
}
let digest = sha256_base64(&payload)?;
if digest != entry.sha256 {
return Err(state_bundle_error(
"hydrated artifact payload digest does not match its inventory",
json!({ "path": payload, "expected_sha256": entry.sha256, "actual_sha256": digest }),
));
}
Ok(HydratedArtifact {
relative_path: entry.relative_path.clone(),
local_path: payload,
#[cfg(feature = "internal")]
sha256: entry.sha256.clone(),
#[cfg(feature = "internal")]
size_bytes: expected_size,
#[cfg(feature = "internal")]
content_type: entry.content_type.clone(),
})
}
}
fn state_bundle_error(message: impl Into<String>, details: Value) -> WyrdError {
WyrdError::SdkInvalidStateBundle {
message: message.into(),
details,
}
}
fn hydrate_typed_cards(cards: &BTreeMap<String, Card>) -> Result<TypedCards, WyrdError> {
let mut typed = TypedCards::default();
for (key, envelope) in cards {
match envelope.kind {
CardKind::Agent => {
let holder = AgentCard::from_envelope(envelope.clone())
.map_err(|error| typed_hydration_error(envelope, &error))?;
typed.agents.insert(key.clone(), holder);
}
CardKind::Prompt => {
let holder = PromptCard::from_card(envelope.clone())
.map_err(|error| typed_hydration_error(envelope, &error))?;
typed.prompts.insert(key.clone(), holder);
}
CardKind::Model => {
let holder = ModelCard::from_card(envelope.clone())
.map_err(|error| typed_hydration_error(envelope, &error))?;
typed.models.insert(key.clone(), holder);
}
CardKind::Data => {
let holder = DataCard::from_card(envelope.clone())
.map_err(|error| typed_hydration_error(envelope, &error))?;
typed.data.insert(key.clone(), holder);
}
_ => {}
}
}
for key in typed.agents.keys() {
typed.agent(key)?;
}
Ok(typed)
}
#[must_use]
fn typed_hydration_error(card: &Card, source: &WyrdError) -> WyrdError {
WyrdError::SdkInvalidStateBundle {
message: format!(
"failed to construct typed {} Card from validated bundle envelope",
card.kind.wire_name(),
),
details: json!({
"card_ref": exact_card_ref_for_error(card),
"kind": card.kind.wire_name(),
"reason": source.to_string(),
}),
}
}
#[must_use]
fn typed_map_invariant(alias: &str, key: &str, expected: &CardKind) -> WyrdError {
state_bundle_error(
"validated typed Card map is missing its exact CardRef entry",
json!({
"alias": alias,
"card_ref_key": key,
"expected_kind": expected.wire_name(),
}),
)
}
impl BundleReader<'_> {
fn validate_root(root: &CardRef, loaded: &[LoadedManifestCard]) -> Result<(), WyrdError> {
let Some(item) = loaded.iter().find(|item| item.key == root.to_string()) else {
return Err(state_bundle_error(
"Card bundle root is not present in its Card set",
json!({ "root": root }),
));
};
if item.card_ref != *root || item.card.kind != CardKind::Service {
return Err(state_bundle_error(
"Card bundle root identity or kind does not match its manifest",
json!({ "root": root, "actual": item.card_ref, "actual_kind": item.card.kind }),
));
}
Ok(())
}
fn validate_reachable_from_root(
root: &CardRef,
loaded: &[LoadedManifestCard],
) -> Result<(), WyrdError> {
let cards = loaded
.iter()
.map(|item| (item.key.as_str(), &item.card))
.collect::<BTreeMap<_, _>>();
let mut pending = vec![root.to_string()];
let mut reachable = BTreeSet::new();
while let Some(key) = pending.pop() {
if !reachable.insert(key.clone()) {
continue;
}
let card = cards.get(key.as_str()).ok_or_else(|| {
state_bundle_error(
"hydrated root traversal encountered a missing Card",
json!({ "card_ref_key": key }),
)
})?;
pending.extend(
card.relationships
.outbound_refs
.iter()
.map(|relationship| relationship.card_ref.to_string()),
);
}
if reachable.len() != cards.len() {
let unreachable = cards
.keys()
.filter(|key| !reachable.contains::<str>(*key))
.copied()
.collect::<Vec<_>>();
return Err(state_bundle_error(
"Card bundle contains Cards unreachable from its root",
json!({ "root": root, "unreachable": unreachable }),
));
}
Ok(())
}
}
fn exact_card_ref(card: &Card, path: &Path) -> Result<CardRef, WyrdError> {
let version = card.metadata.resolved_pin().cloned().ok_or_else(|| {
state_bundle_error(
"hydrated Card is missing an exact version pin",
json!({ "path": path }),
)
})?;
let space = card.metadata.space.clone().ok_or_else(|| {
state_bundle_error(
"hydrated Card is missing a resolved space",
json!({ "path": path }),
)
})?;
let uid = card.metadata.uid.clone().ok_or_else(|| {
state_bundle_error(
"hydrated Card is missing its required UID",
json!({ "path": path }),
)
})?;
Ok(CardRef {
kind: card.kind.clone(),
name: card.metadata.name.clone(),
version,
space: Some(space),
uid: Some(uid),
})
}
fn absolute_bundle(bundle: &Path) -> Result<PathBuf, WyrdError> {
let metadata = fs::symlink_metadata(bundle).map_err(|error| {
state_bundle_error(
"Card bundle directory cannot be read",
json!({ "path": bundle, "source": error.to_string() }),
)
})?;
if !metadata.is_dir() {
return Err(state_bundle_error(
"Card bundle path is not a directory",
json!({ "path": bundle }),
));
}
fs::canonicalize(bundle).map_err(|error| {
state_bundle_error(
"Card bundle directory cannot be canonicalized",
json!({ "path": bundle, "source": error.to_string() }),
)
})
}
fn confined_path(root: &Path, relative: &str) -> Result<PathBuf, WyrdError> {
if relative.is_empty() {
return Err(state_bundle_error(
"Card bundle path is empty",
json!({ "path": relative }),
));
}
let path = Path::new(relative);
if path.is_absolute()
|| path.components().any(|component| {
matches!(
component,
Component::ParentDir | Component::RootDir | Component::Prefix(_)
)
})
{
return Err(state_bundle_error(
"Card bundle path escapes its root",
json!({ "path": relative, "root": root }),
));
}
Ok(root.join(path))
}
fn confined_existing_file(root: &Path, relative: &str) -> Result<PathBuf, WyrdError> {
let root = absolute_bundle(root)?;
let path = confined_path(&root, relative)?;
let metadata = fs::symlink_metadata(&path).map_err(|error| {
state_bundle_error(
"Card bundle file cannot be read",
json!({ "path": path, "source": error.to_string() }),
)
})?;
if !metadata.is_file() {
return Err(state_bundle_error(
"Card bundle projection is not a regular file",
json!({ "path": path }),
));
}
let canonical = fs::canonicalize(&path).map_err(|error| {
state_bundle_error(
"Card bundle file cannot be canonicalized",
json!({ "path": path, "source": error.to_string() }),
)
})?;
if !canonical.starts_with(&root) {
return Err(state_bundle_error(
"Card bundle path escapes its root",
json!({ "path": path, "root": root }),
));
}
Ok(canonical)
}
fn confined_existing_dir(root: &Path, relative: &str) -> Result<PathBuf, WyrdError> {
let root = absolute_bundle(root)?;
let path = confined_path(&root, relative)?;
let metadata = fs::symlink_metadata(&path).map_err(|error| {
state_bundle_error(
"hydrated artifact directory cannot be read",
json!({ "path": path, "source": error.to_string() }),
)
})?;
if !metadata.is_dir() {
return Err(state_bundle_error(
"hydrated artifact directory is not a directory",
json!({ "path": path }),
));
}
let canonical = fs::canonicalize(&path).map_err(|error| {
state_bundle_error(
"hydrated artifact directory cannot be canonicalized",
json!({ "path": path, "source": error.to_string() }),
)
})?;
if !canonical.starts_with(&root) {
return Err(state_bundle_error(
"hydrated artifact directory escapes its root",
json!({ "path": path, "root": root }),
));
}
Ok(canonical)
}
fn validate_alias(alias: &str) -> Result<(), WyrdError> {
if alias.is_empty()
|| alias == "."
|| alias == ".."
|| alias.contains('/')
|| alias.contains('\\')
|| alias.bytes().any(
|byte| !matches!(byte, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'.' | b'_' | b'-'),
)
{
return Err(state_bundle_error(
"Card bundle contains an unsafe alias",
json!({ "alias": alias }),
));
}
Ok(())
}
fn validate_artifact_path(path: &str) -> Result<(), WyrdError> {
if path.is_empty()
|| path.starts_with('/')
|| path.contains('\\')
|| path.split('/').any(|segment| {
segment.is_empty()
|| segment == "."
|| segment == ".."
|| segment.bytes().any(
|byte| !matches!(byte, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'.' | b'_' | b'-'),
)
})
{
return Err(state_bundle_error(
"artifact path is not confined to the Card bundle",
json!({ "relative_path": path }),
));
}
Ok(())
}
fn sha256_base64(path: &Path) -> Result<String, WyrdError> {
let mut file = File::open(path).map_err(|error| {
state_bundle_error(
"hydrated artifact cannot be opened",
json!({ "path": path, "source": error.to_string() }),
)
})?;
let mut hasher = Sha256::new();
let mut buffer = vec![0_u8; 128 * 1024];
loop {
let read = file.read(&mut buffer).map_err(|error| {
state_bundle_error(
"hydrated artifact cannot be read",
json!({ "path": path, "source": error.to_string() }),
)
})?;
if read == 0 {
break;
}
hasher.update(&buffer[..read]);
}
Ok(base64::engine::general_purpose::STANDARD.encode(hasher.finalize()))
}
fn read_yaml<T: for<'de> Deserialize<'de>>(path: &Path) -> Result<T, WyrdError> {
let contents = fs::read(path).map_err(|error| {
state_bundle_error(
"Card bundle file cannot be read",
json!({ "path": path, "source": error.to_string() }),
)
})?;
serde_yaml::from_slice(&contents).map_err(|error| {
state_bundle_error(
"Card bundle YAML is invalid",
json!({ "path": path, "source": error.to_string() }),
)
})
}
fn relative_path(root: &Path, path: &Path) -> Result<String, WyrdError> {
path.strip_prefix(root)
.map_err(|_| {
state_bundle_error(
"hydrated path is outside its bundle",
json!({ "path": path, "root": root }),
)
})
.and_then(|relative| {
relative.to_str().map(str::to_owned).ok_or_else(|| {
state_bundle_error("hydrated path is not valid UTF-8", json!({ "path": path }))
})
})
}
#[must_use]
fn exact_card_ref_for_error(card: &Card) -> Value {
json!({ "kind": card.kind, "name": card.metadata.name, "version": card.metadata.version, "space": card.metadata.space, "uid": card.metadata.uid })
}
#[must_use]
fn invalid_duplicate_ref(card_ref: &CardRef) -> WyrdError {
state_bundle_error(
"Card bundle contains a duplicate CardRef",
json!({ "card_ref": card_ref }),
)
}
#[must_use]
fn conflicting_alias(alias: &str, existing: &str, incoming: &str) -> WyrdError {
state_bundle_error(
"Card bundle contains a conflicting alias",
json!({ "alias": alias, "existing_card_ref_key": existing, "incoming_card_ref_key": incoming }),
)
}
#[must_use]
fn unhydrated_error(path: &Path, message: &str) -> WyrdError {
WyrdError::SdkUnhydratedArtifact {
message: message.to_owned(),
details: json!({ "path": path }),
}
}
#[derive(Debug)]
struct LoadedManifestCard {
key: String,
card_ref: CardRef,
card: Card,
aliases: Vec<String>,
#[cfg(feature = "internal")]
artifacts: Vec<HydratedArtifact>,
#[cfg(feature = "internal")]
artifact_dir: Option<PathBuf>,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct AliasRecord {
alias: String,
card_ref: CardRef,
card_path: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TelemetryFlushFailure {
timed_out: bool,
cause: String,
}
impl TelemetryFlushFailure {
pub fn timeout(cause: impl Into<String>) -> Self {
Self {
timed_out: true,
cause: cause.into(),
}
}
pub fn failed(cause: impl Into<String>) -> Self {
Self {
timed_out: false,
cause: cause.into(),
}
}
pub fn into_error(self, native_code: Option<&str>) -> WyrdError {
let details = json!({
"stage": "opentelemetry",
"cause": self.cause,
"native_code": native_code,
});
if self.timed_out {
WyrdError::SdkTelemetryFlushTimeout {
message: format!(
"the managed OpenTelemetry provider did not flush completed spans within its export timeout ({})",
self.cause
),
details,
}
} else {
WyrdError::SdkTelemetryFlushFailed {
message: format!(
"the managed OpenTelemetry provider failed to flush completed spans ({})",
self.cause
),
details,
}
}
}
pub fn settle(telemetry: Option<Self>, native: Result<(), WyrdError>) -> Result<(), WyrdError> {
match (telemetry, native) {
(None, native) => native,
(Some(failure), native) => {
Err(failure.into_error(native.as_ref().err().map(WyrdError::code)))
}
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
use base64::Engine;
use serde::Serialize;
use serde_json::Value;
use sha2::{Digest, Sha256};
use skald_spec::{OpenAiChatRequest, Prompt, ProviderRequest};
use tempfile::{TempDir, tempdir};
use wyrd_semver::{VersionBlock, VersionSpec};
use wyrd_spec::api_version::ApiVersion;
use wyrd_spec::card::agent::{AgentRunConfigSpec, AgentSpec};
use wyrd_spec::card::data::{CustomDataMeta, DataInterface, DataSchema, DataSpec, DataStats};
use wyrd_spec::card::field::FieldSpec;
use wyrd_spec::card::model::{CustomMeta, ModelInterface, ModelSignature, ModelSpec, TaskType};
use wyrd_spec::card::prompt::PromptSpec;
use wyrd_spec::card::service::{ServiceComponent, ServiceSpec};
use wyrd_spec::card::trigger::{TriggerActivation, TriggerSpec};
use wyrd_spec::card::verifier::{
AgentCall, Check, Requirement, VerificationBinding, VerifierSpec,
};
use wyrd_spec::envelope::{Card, CardKind, Metadata, Relationships, Spec};
use wyrd_spec::graph::relationships_from_spec;
use wyrd_spec::ids::{CardName, CardUid, SpaceName};
use wyrd_spec::reference::{CardRef, InlineableRef, Ref};
use wyrd_spec::registry::{
BundleArtifactManifest, BundleCardManifest, BundleManifest, BundleMode,
};
use super::{HydratedArtifact, HydratedCard, WyrdState};
use wyrd_spec::error::WyrdError;
#[derive(Clone)]
struct TestArtifact {
relative_path: String,
bytes: Vec<u8>,
content_type: Option<String>,
}
pub(crate) struct TestBundle {
root: TempDir,
manifest: BundleManifest,
cards: RefCell<BTreeMap<String, Card>>,
artifact_bytes: RefCell<BTreeMap<(String, String), Vec<u8>>>,
}
impl TestBundle {
pub(crate) fn complete_service() -> Self {
let root_ref = test_ref(CardKind::Service, "service", 1);
let model_ref = test_ref(CardKind::Model, "model", 2);
let backup_ref = test_ref(CardKind::Model, "backup", 3);
let service_spec = ServiceSpec {
components: vec![
ServiceComponent {
alias: "model".to_owned(),
card_ref: Ref::Ref(model_ref.clone()),
verified_by: Vec::new(),
source: None,
config: BTreeMap::new(),
credential_refs: Vec::new(),
},
ServiceComponent {
alias: "backup".to_owned(),
card_ref: Ref::Ref(backup_ref.clone()),
verified_by: Vec::new(),
source: None,
config: BTreeMap::new(),
credential_refs: Vec::new(),
},
],
..ServiceSpec::default()
};
let mut bundle = Self {
root: tempdir().expect("test bundle tempdir is available"),
manifest: BundleManifest {
api_version: "wyrd/bundle/v1".to_owned(),
mode: BundleMode::Complete,
root: root_ref.clone(),
cards: Vec::new(),
card_count: 0,
artifact_count: 0,
downloaded_artifact_count: 0,
},
cards: RefCell::new(BTreeMap::new()),
artifact_bytes: RefCell::new(BTreeMap::new()),
};
bundle.add_card(
"root",
card(
&root_ref,
CardKind::Service,
Spec::Service(service_spec.clone()),
relationships_from_spec(&Spec::Service(service_spec)),
),
&[],
);
bundle.add_card(
"model",
card(
&model_ref,
CardKind::Model,
Spec::Model(model_spec()),
Relationships::default(),
),
&[],
);
bundle.add_card(
"backup",
card(
&backup_ref,
CardKind::Model,
Spec::Model(model_spec()),
Relationships::default(),
),
&[],
);
bundle.manifest.card_count = bundle.manifest.cards.len();
bundle.manifest.artifact_count = 0;
bundle.manifest.downloaded_artifact_count = 0;
bundle.write();
bundle
}
fn with_artifact() -> Self {
let mut bundle = Self::complete_service();
let model_ref = test_ref(CardKind::Model, "model", 2);
let artifact = TestArtifact {
relative_path: "weights.bin".to_owned(),
bytes: b"weights".to_vec(),
content_type: Some("application/octet-stream".to_owned()),
};
let entry = bundle
.manifest
.cards
.iter_mut()
.find(|entry| entry.card_ref == model_ref)
.expect("model fixture exists");
bundle.artifact_bytes.borrow_mut().insert(
(model_ref.to_string(), artifact.relative_path.clone()),
artifact.bytes.clone(),
);
entry.artifacts.push(BundleArtifactManifest {
relative_path: artifact.relative_path.clone(),
sha256: digest(&artifact.bytes),
size_bytes: i64::try_from(artifact.bytes.len()).expect("fixture size fits"),
content_type: artifact.content_type,
local_path: Some("cards/model/artifacts/weights.bin".to_owned()),
});
bundle.manifest.card_count = bundle.manifest.cards.len();
bundle.manifest.artifact_count = 1;
bundle.manifest.downloaded_artifact_count = 1;
bundle.write();
bundle
}
pub(crate) fn with_typed_cards() -> Self {
let mut bundle = Self::complete_service();
let prompt_ref = test_ref(CardKind::Prompt, "triage", 4);
let agent_ref = test_ref(CardKind::Agent, "triage", 6);
let data_ref = test_ref(CardKind::Data, "training", 7);
let check_ref = test_ref(CardKind::Verifier, "quality", 8);
let drift_ref = test_ref(CardKind::Verifier, "model-drift", 9);
bundle.add_card(
"triage_prompt",
card(
&prompt_ref,
CardKind::Prompt,
Spec::Prompt(prompt_spec("triage")),
Relationships::default(),
),
&[],
);
bundle.add_card(
"agent_triage",
canonical_card(
&agent_ref,
CardKind::Agent,
registered_agent_spec(&prompt_ref),
),
&[],
);
bundle.add_card(
"training_data",
card(
&data_ref,
CardKind::Data,
Spec::Data(data_spec()),
Relationships::default(),
),
&[],
);
bundle.add_card(
"quality_check",
card(
&check_ref,
CardKind::Verifier,
Spec::Verifier(assert_verifier()),
Relationships::default(),
),
&[],
);
bundle.add_card(
"model_drift",
card(
&drift_ref,
CardKind::Verifier,
Spec::Verifier(drift_verifier()),
Relationships::default(),
),
&[],
);
let root_ref = test_ref(CardKind::Service, "service", 1);
bundle.rewrite_card(&root_ref, |card| {
let Spec::Service(spec) = &mut card.spec else {
panic!("fixture root is a Service");
};
spec.components.extend([
service_component("triage_prompt", &prompt_ref),
service_component("training_data", &data_ref),
service_component("agent_triage", &agent_ref),
]);
spec.verified_by.extend([
verification_binding(check_ref, None),
verification_binding(
drift_ref,
Some(TriggerActivation::Schedule {
cron: "0 * * * *".to_owned(),
tz: None,
}),
),
]);
});
bundle.write();
bundle
}
fn with_nested_prompt(&mut self, prompt_ref: CardRef, sibling: bool) -> CardRef {
use wyrd_spec::verification::TaskId;
let verifier_ref = test_ref(CardKind::Verifier, "judge", 4);
let check = Check::Agent(AgentCall {
agent: InlineableRef::Inline(Box::new(AgentSpec {
prompt: if sibling {
InlineableRef::Sibling {
sibling: prompt_ref,
}
} else {
InlineableRef::Ref(prompt_ref)
},
execution_route: wyrd_spec::ExecutionRoute::Native {},
tool_names: Vec::new(),
run_config: AgentRunConfigSpec::default(),
verified_by: Vec::new(),
})),
inputs: BTreeMap::new(),
workspace: BTreeMap::new(),
});
let verifier = VerifierSpec {
requirements: vec![Requirement {
id: TaskId::new("judge").expect("fixture requirement id is valid"),
statement: "The answer follows the policy.".to_owned(),
check,
}],
..assert_verifier()
};
self.add_card(
"judge",
canonical_card(&verifier_ref, CardKind::Verifier, Spec::Verifier(verifier)),
&[],
);
let root_ref = test_ref(CardKind::Service, "service", 1);
self.rewrite_card(&root_ref, |card| {
if let Spec::Service(spec) = &mut card.spec {
spec.verified_by
.push(verification_binding(verifier_ref.clone(), None));
}
});
self.write();
verifier_ref
}
fn add_card(&mut self, alias: &str, card: Card, artifacts: &[TestArtifact]) -> CardRef {
let card_ref = card_ref_from_card(&card);
let key = card_ref.to_string();
self.cards.borrow_mut().insert(key.clone(), card);
let entries = artifacts
.iter()
.map(|artifact| {
self.artifact_bytes.borrow_mut().insert(
(key.clone(), artifact.relative_path.clone()),
artifact.bytes.clone(),
);
BundleArtifactManifest {
relative_path: artifact.relative_path.clone(),
sha256: digest(&artifact.bytes),
size_bytes: i64::try_from(artifact.bytes.len()).expect("fixture size fits"),
content_type: artifact.content_type.clone(),
local_path: Some(format!(
"cards/{alias}/artifacts/{}",
artifact.relative_path
)),
}
})
.collect::<Vec<_>>();
self.manifest.cards.push(BundleCardManifest {
aliases: vec![alias.to_owned()],
card_ref: card_ref.clone(),
card_path: format!("cards/{alias}/card.yaml"),
relationships_path: format!("cards/{alias}/relationships.yaml"),
artifact_inventory_path: format!("cards/{alias}/artifacts.yaml"),
artifacts: entries,
});
card_ref
}
fn add_alias(&mut self, alias: &str, target: &CardRef) {
let entry = self
.manifest
.cards
.iter_mut()
.find(|entry| entry.card_ref == *target)
.expect("fixture target exists");
entry.aliases.push(alias.to_owned());
}
fn write(&self) {
fs::create_dir_all(self.root.path().join("aliases"))
.expect("fixture aliases dir writes");
for entry in &self.manifest.cards {
let card = self
.cards
.borrow()
.get(&entry.card_ref.to_string())
.cloned()
.expect("fixture Card exists");
write_yaml(&self.root.path().join(&entry.card_path), &card);
write_yaml(
&self.root.path().join(&entry.relationships_path),
&card.relationships,
);
write_yaml(
&self.root.path().join(&entry.artifact_inventory_path),
&entry.artifacts,
);
for artifact in &entry.artifacts {
let bytes = self
.artifact_bytes
.borrow()
.get(&(entry.card_ref.to_string(), artifact.relative_path.clone()))
.cloned()
.expect("fixture artifact bytes exist");
let path = self.root.path().join(
artifact
.local_path
.as_ref()
.expect("complete fixture artifact path"),
);
fs::create_dir_all(path.parent().expect("fixture artifact parent writes"))
.expect("fixture artifact parent exists");
fs::write(path, bytes).expect("fixture artifact writes");
}
for alias in &entry.aliases {
write_yaml(
&self
.root
.path()
.join("aliases")
.join(format!("{alias}.yaml")),
&serde_json::json!({ "alias": alias, "card_ref": entry.card_ref, "card_path": entry.card_path }),
);
}
}
let mut manifest = self.manifest.clone();
manifest.card_count = manifest.cards.len();
manifest.artifact_count = manifest
.cards
.iter()
.map(|entry| entry.artifacts.len())
.sum();
manifest.downloaded_artifact_count = manifest.artifact_count;
write_yaml(&self.root.path().join("metadata.yaml"), &manifest);
}
pub(crate) fn path(&self) -> &Path {
self.root.path()
}
fn rewrite_manifest(&mut self, f: impl FnOnce(&mut BundleManifest)) {
f(&mut self.manifest);
write_yaml(&self.root.path().join("metadata.yaml"), &self.manifest);
for entry in &self.manifest.cards {
write_yaml(
&self.root.path().join(&entry.artifact_inventory_path),
&entry.artifacts,
);
}
}
fn rewrite_card(&self, card_ref: &CardRef, f: impl FnOnce(&mut Card)) {
let mut cards = self.cards.borrow_mut();
let card = cards
.get_mut(&card_ref.to_string())
.expect("fixture Card exists");
f(card);
card.relationships = relationships_from_spec(&card.spec);
let entry = self
.manifest
.cards
.iter()
.find(|entry| entry.card_ref == *card_ref)
.expect("fixture manifest Card exists");
write_yaml(&self.root.path().join(&entry.card_path), card);
write_yaml(
&self.root.path().join(&entry.relationships_path),
&card.relationships,
);
}
fn rewrite_artifact(&self, card_ref: &CardRef, relative: &str, bytes: &[u8]) {
let entry = self
.manifest
.cards
.iter()
.find(|entry| entry.card_ref == *card_ref)
.expect("fixture artifact Card exists");
let artifact = entry
.artifacts
.iter()
.find(|artifact| artifact.relative_path == relative)
.expect("fixture artifact exists");
fs::write(
self.root
.path()
.join(artifact.local_path.as_ref().expect("fixture artifact path")),
bytes,
)
.expect("fixture artifact rewrite");
}
}
fn artifacts<'s>(state: &'s WyrdState, alias: &str) -> &'s [HydratedArtifact] {
let key = state.index.resolve_key(alias).expect("alias resolves");
state
.index
.artifacts_by_key(key)
.expect("artifacts resolve")
}
fn card(card_ref: &CardRef, kind: CardKind, spec: Spec, relationships: Relationships) -> Card {
Card {
api_version: ApiVersion::v1(),
kind,
metadata: Metadata {
name: card_ref.name.clone(),
version: Some(VersionSpec::Pin(card_ref.version.clone())),
bump: None,
space: card_ref.space.clone(),
uid: card_ref.uid.clone(),
labels: wyrd_spec::metadata::Labels::default(),
annotations: wyrd_spec::metadata::Annotations::default(),
spec_hash: None,
artifact_hash: None,
origin: None,
},
spec,
relationships,
status: None,
}
}
fn canonical_card(card_ref: &CardRef, kind: CardKind, spec: Spec) -> Card {
let relationships = relationships_from_spec(&spec);
card(card_ref, kind, spec, relationships)
}
fn service_component(alias: &str, card_ref: &CardRef) -> ServiceComponent {
ServiceComponent {
alias: alias.to_owned(),
card_ref: Ref::Ref(card_ref.clone()),
verified_by: Vec::new(),
source: None,
config: BTreeMap::new(),
credential_refs: Vec::new(),
}
}
fn model_spec() -> ModelSpec {
ModelSpec {
interface: ModelInterface::Custom(CustomMeta {
framework_version: "1".to_owned(),
model_subtype: None,
loader_module: "fixture".to_owned(),
loader_class: "Model".to_owned(),
extra: BTreeMap::new(),
}),
task_type: TaskType::Other,
signature: ModelSignature::new(
vec![FieldSpec::new(
wyrd_spec::ids::ColumnName::new("input").expect("fixture field is valid"),
"float64",
)],
vec![FieldSpec::new(
wyrd_spec::ids::ColumnName::new("output").expect("fixture field is valid"),
"float64",
)],
),
sample_input: None,
card_refs: Vec::new(),
}
}
fn card_ref_from_card(card: &Card) -> CardRef {
CardRef {
kind: card.kind.clone(),
name: card.metadata.name.clone(),
version: card
.metadata
.resolved_pin()
.cloned()
.expect("fixture version is pinned"),
space: card.metadata.space.clone(),
uid: card.metadata.uid.clone(),
}
}
fn prompt(text: &str) -> skald_spec::Prompt {
let request: OpenAiChatRequest = serde_json::from_value(serde_json::json!({
"model": "fixture-model",
"messages": [{ "role": "user", "content": text }]
}))
.expect("fixture chat request is valid");
Prompt::new(
ProviderRequest::OpenAiChatCompletion(request),
"fixture-model",
)
.expect("fixture prompt is valid")
}
fn prompt_spec(text: &str) -> PromptSpec {
PromptSpec::new(prompt(text)).expect("fixture prompt spec is valid")
}
fn registered_agent_spec(prompt_ref: &CardRef) -> Spec {
Spec::Agent(AgentSpec {
prompt: InlineableRef::Ref(prompt_ref.clone()),
execution_route: wyrd_spec::ExecutionRoute::Native {},
tool_names: Vec::new(),
run_config: AgentRunConfigSpec::default(),
verified_by: Vec::new(),
})
}
fn data_spec() -> DataSpec {
DataSpec {
interface: DataInterface::Custom(CustomDataMeta {
loader_module: "fixture".to_owned(),
loader_class: "Data".to_owned(),
extra: BTreeMap::new(),
}),
schema: DataSchema::empty(),
card_refs: Vec::new(),
splits: std::collections::HashMap::new(),
target_columns: Vec::new(),
sql: None,
stats: DataStats {
row_count: None,
col_count: None,
byte_count: 0,
sha256: "0".repeat(64),
},
}
}
fn assert_verifier() -> VerifierSpec {
serde_json::from_value(serde_json::json!({
"inputs": {"answer": {"type": "json"}},
"requirements": [{
"id": "answered",
"statement": "The agent produced an answer.",
"check": {"kind": "assert", "path": "inputs.answer", "operator": "is_not_null"},
}],
}))
.expect("static assert Verifier decodes")
}
fn drift_verifier() -> VerifierSpec {
serde_json::from_value(serde_json::json!({
"description": "fixture drift",
"inputs": {"current": {"type": "samples", "features": ["score"]}},
"requirements": [{
"id": "score-stable",
"statement": "The score stays near its baseline.",
"check": {
"kind": "drift",
"on": "inputs.current",
"method": "Custom",
"signal": {"kind": "Metric", "name": "score"},
"condition": {"kind": "Statistical"},
"profile": {
"kind": "Custom",
"metric_name": "score",
"baseline_value": 0.5,
"alert_threshold": 0.1,
},
},
}],
}))
.expect("static drift Verifier decodes")
}
fn verification_binding(
verifier: CardRef,
activation: Option<TriggerActivation>,
) -> VerificationBinding {
VerificationBinding {
verifier: Ref::Ref(verifier),
runs_on: activation.map(|activation| {
InlineableRef::Inline(Box::new(TriggerSpec {
description: None,
activation,
}))
}),
on_failure: Vec::new(),
on_verdict_change: Vec::new(),
}
}
fn test_ref(kind: CardKind, name: &str, uid_suffix: u8) -> CardRef {
let uid = format!("01890f28-7c4a-7cc3-98e7-4f4a3c2d1b{uid_suffix:02x}");
CardRef {
kind,
name: CardName::new(name).expect("fixture name is valid"),
version: VersionBlock::parse("1.0.0").expect("fixture version is valid"),
space: Some(SpaceName::new("default").expect("fixture space is valid")),
uid: Some(CardUid::new(uid).expect("fixture UID is valid")),
}
}
fn digest(bytes: &[u8]) -> String {
base64::engine::general_purpose::STANDARD.encode(Sha256::digest(bytes))
}
fn write_yaml<T: Serialize>(path: &Path, value: &T) {
fs::create_dir_all(path.parent().expect("fixture YAML path has parent"))
.expect("fixture YAML parent exists");
fs::write(
path,
serde_yaml::to_string(value).expect("fixture YAML serializes"),
)
.expect("fixture YAML writes");
}
fn problem_details(error: &WyrdError) -> Value {
error.as_problem_json()["details"].clone()
}
fn assert_error(result: Result<WyrdState, WyrdError>, code: &str, field: &str) {
let error = result.expect_err("bundle fixture must be rejected");
assert_eq!(error.code(), code);
assert!(
!problem_details(&error)[field].is_null(),
"missing detail field {field}: {}",
problem_details(&error)
);
}
fn assert_pull_recovery(result: Result<WyrdState, WyrdError>) {
let error = result.expect_err("bundle fixture must be rejected");
let remediation = error.remediation();
assert!(
remediation.contains("wyrd pull")
&& remediation.contains("--output-dir")
&& !remediation.contains("wyrd get"),
"{remediation}"
);
}
fn assert_sibling_error(result: Result<WyrdState, WyrdError>, expected_ref: &CardRef) {
let error = result.expect_err("sibling reference must be rejected");
assert_eq!(error.code(), "WYRD_SDK_400_INVALID_STATE_BUNDLE");
let details = problem_details(&error);
assert!(!details["owner"].is_null(), "owner detail is required");
assert_eq!(details["card_ref"], serde_json::json!(expected_ref));
assert_eq!(details["reference_form"], "sibling");
}
#[test]
fn loads_complete_service_graph_without_network() {
let bundle = TestBundle::complete_service();
let state = WyrdState::from_path(bundle.path()).expect("complete graph loads");
assert_eq!(state.root_ref().kind, CardKind::Service);
assert_eq!(state.service().kind, CardKind::Service);
assert_eq!(
state.card("model").expect("model alias resolves").kind,
CardKind::Model
);
}
#[test]
fn rejects_orphaned_card_outside_root_closure() {
let mut bundle = TestBundle::complete_service();
let orphan_ref = test_ref(CardKind::Model, "orphan", 9);
bundle.add_card(
"orphan",
card(
&orphan_ref,
CardKind::Model,
Spec::Model(model_spec()),
Relationships::default(),
),
&[],
);
bundle.write();
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"unreachable",
);
}
#[test]
fn typed_accessors_return_native_rust_cards() {
let bundle = TestBundle::with_typed_cards();
let state = WyrdState::from_path(bundle.path()).expect("typed graph loads");
assert_eq!(state.model("model").expect("model resolves").name, "model");
assert_eq!(
state.data("training_data").expect("data resolves").name,
"training"
);
assert_eq!(
state
.agent("agent_triage")
.expect("agent resolves")
.card()
.name,
"triage"
);
assert_eq!(
state.prompt("triage_prompt").expect("prompt resolves").name,
"triage"
);
assert_eq!(
state
.verifier("quality_check")
.expect("assert verifier resolves"),
&assert_verifier()
);
let drift = state
.verifier("model_drift")
.expect("drift verifier resolves");
assert_eq!(drift.description.as_deref(), Some("fixture drift"));
assert!(matches!(
drift.requirements.as_slice(),
[requirement] if matches!(requirement.check, Check::Drift(_))
));
}
#[test]
fn two_model_aliases_return_distinct_holders() {
let bundle = TestBundle::complete_service();
let state = WyrdState::from_path(bundle.path()).expect("typed graph loads");
let primary = state.model("model").expect("primary model resolves");
let backup = state.model("backup").expect("backup model resolves");
assert!(!std::ptr::eq(primary, backup));
assert_eq!(primary.uid, "01890f28-7c4a-7cc3-98e7-4f4a3c2d1b02");
assert_eq!(backup.uid, "01890f28-7c4a-7cc3-98e7-4f4a3c2d1b03");
}
#[test]
fn duplicate_prompt_aliases_return_same_holder_address() {
let mut bundle = TestBundle::with_typed_cards();
let target = test_ref(CardKind::Prompt, "triage", 4);
bundle.add_alias("triage_prompt_alt", &target);
bundle.write();
let state = WyrdState::from_path(bundle.path()).expect("typed graph loads");
assert!(std::ptr::eq(
state.prompt("triage_prompt").expect("prompt resolves"),
state
.prompt("triage_prompt_alt")
.expect("prompt alias resolves")
));
}
#[test]
fn agent_with_inline_prompt_is_an_invalid_bundle() {
let bundle = TestBundle::with_typed_cards();
let agent_ref = test_ref(CardKind::Agent, "triage", 6);
bundle.rewrite_card(&agent_ref, |card| {
if let Spec::Agent(spec) = &mut card.spec {
spec.prompt = InlineableRef::Inline(Box::new(prompt("inline")));
}
});
bundle.write();
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"card_ref_key",
);
}
#[test]
fn agent_prompt_returns_the_aliased_prompt_card() {
let bundle = TestBundle::with_typed_cards();
let state = WyrdState::from_path(bundle.path()).expect("typed graph loads");
let agent = state.agent("agent_triage").expect("agent resolves");
assert!(std::ptr::eq(
agent.prompt(),
state.prompt("triage_prompt").expect("prompt resolves")
));
assert_eq!(
agent.prompt_ref(),
state
.card_ref("triage_prompt")
.expect("prompt ref resolves")
);
}
#[test]
fn get_returns_the_kind_appropriate_hydrated_card() {
let bundle = TestBundle::with_typed_cards();
let state = WyrdState::from_path(bundle.path()).expect("typed graph loads");
let Ok(HydratedCard::Agent(agent)) = state.get("agent_triage") else {
panic!("agent alias returns an Agent");
};
assert!(std::ptr::eq(
agent.prompt(),
state.prompt("triage_prompt").expect("prompt resolves")
));
assert!(matches!(
state.get("triage_prompt"),
Ok(HydratedCard::Prompt(_))
));
assert!(matches!(state.get("model"), Ok(HydratedCard::Model(_))));
assert!(matches!(
state.get("training_data"),
Ok(HydratedCard::Data(_))
));
assert!(matches!(
state.get("quality_check"),
Ok(HydratedCard::Envelope(card)) if card.kind == CardKind::Verifier
));
let Err(unknown) = state.get("missing") else {
panic!("unknown alias is refused");
};
assert_eq!(unknown.code(), "WYRD_SDK_404_UNKNOWN_ALIAS");
let Err(mismatch) = state.agent("triage_prompt") else {
panic!("typed mismatch is refused");
};
assert_eq!(mismatch.code(), "WYRD_SDK_400_CARD_KIND_MISMATCH");
}
#[test]
fn agent_prompt_rejects_missing_target() {
let bundle = TestBundle::with_typed_cards();
let agent_ref = test_ref(CardKind::Agent, "triage", 6);
let missing = test_ref(CardKind::Prompt, "missing", 11);
bundle.rewrite_card(&agent_ref, |card| {
if let Spec::Agent(spec) = &mut card.spec {
spec.prompt = InlineableRef::Ref(missing);
}
});
bundle.write();
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"owner",
);
}
#[test]
fn agent_prompt_rejects_non_prompt_target() {
let bundle = TestBundle::with_typed_cards();
let agent_ref = test_ref(CardKind::Agent, "triage", 6);
let model_ref = test_ref(CardKind::Model, "model", 2);
bundle.rewrite_card(&agent_ref, |card| {
if let Spec::Agent(spec) = &mut card.spec {
spec.prompt = InlineableRef::Ref(model_ref);
}
});
bundle.write();
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"card_ref",
);
}
#[test]
fn model_and_data_artifacts_remain_external_to_holders() {
let mut bundle = TestBundle::with_artifact();
let data_ref = test_ref(CardKind::Data, "training", 12);
let data_artifact = TestArtifact {
relative_path: "training.bin".to_owned(),
bytes: b"training".to_vec(),
content_type: Some("application/octet-stream".to_owned()),
};
bundle.add_card(
"training",
card(
&data_ref,
CardKind::Data,
Spec::Data(data_spec()),
Relationships::default(),
),
&[data_artifact],
);
let root_ref = test_ref(CardKind::Service, "service", 1);
bundle.rewrite_card(&root_ref, |card| {
if let Spec::Service(spec) = &mut card.spec {
spec.components.push(ServiceComponent {
alias: "training".to_owned(),
card_ref: Ref::Ref(data_ref.clone()),
verified_by: Vec::new(),
source: None,
config: BTreeMap::new(),
credential_refs: Vec::new(),
});
}
});
bundle.write();
let state = WyrdState::from_path(bundle.path()).expect("artifact graph loads");
let model_artifact = &artifacts(&state, "model")[0];
let data_artifact = &artifacts(&state, "training")[0];
assert_eq!(
fs::read(&model_artifact.local_path).expect("model bytes read"),
b"weights"
);
assert_eq!(
fs::read(&data_artifact.local_path).expect("data bytes read"),
b"training"
);
assert_eq!(
state
.model("model")
.expect("model holder resolves")
.metadata
.card_refs
.len(),
0
);
assert_eq!(
state
.data("training")
.expect("data holder resolves")
.metadata
.stats
.byte_count,
0
);
}
#[test]
fn each_typed_accessor_reports_expected_and_actual_kind() {
let bundle = TestBundle::with_typed_cards();
let state = WyrdState::from_path(bundle.path()).expect("typed graph loads");
macro_rules! assert_wrong_kind {
($lookup:expr) => {{
let Err(error) = $lookup else {
panic!("wrong-kind lookup must fail");
};
assert_eq!(error.code(), "WYRD_SDK_400_CARD_KIND_MISMATCH");
let details = problem_details(&error);
assert!(!details["expected_kind"].is_null());
assert!(!details["actual_kind"].is_null());
assert!(!details["card_ref"].is_null());
}};
}
assert_wrong_kind!(state.agent("model"));
assert_wrong_kind!(state.prompt("model"));
assert_wrong_kind!(state.model("agent_triage"));
assert_wrong_kind!(state.data("agent_triage"));
assert_wrong_kind!(state.verifier("agent_triage"));
}
#[test]
fn generic_card_supports_unprojected_kinds() {
let bundle = TestBundle::with_typed_cards();
let state = WyrdState::from_path(bundle.path()).expect("typed graph loads");
assert_eq!(
state.card("quality_check").expect("verifier card").kind,
CardKind::Verifier
);
assert_eq!(
state.card("model_drift").expect("drift card").kind,
CardKind::Verifier
);
}
#[test]
fn aliases_for_same_card_resolve_same_address() {
let mut bundle = TestBundle::complete_service();
let target = test_ref(CardKind::Model, "model", 2);
bundle.add_alias("model-alt", &target);
bundle.write();
let state = WyrdState::from_path(bundle.path()).expect("complete graph loads");
assert!(std::ptr::eq(
state.card("model").expect("model resolves"),
state.card("model-alt").expect("alias resolves")
));
}
#[test]
fn aliases_are_stably_sorted() {
let mut bundle = TestBundle::complete_service();
bundle.add_alias("aaa", &test_ref(CardKind::Model, "model", 2));
bundle.add_alias("zzz", &test_ref(CardKind::Model, "model", 2));
bundle.write();
let state = WyrdState::from_path(bundle.path()).expect("complete graph loads");
assert_eq!(
state.aliases().collect::<Vec<_>>(),
vec!["aaa", "backup", "model", "root", "zzz"]
);
}
#[test]
fn rejects_missing_manifest_as_unhydrated() {
let bundle = tempdir().expect("test tempdir is available");
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_UNHYDRATED_ARTIFACT",
"path",
);
}
#[test]
fn rejects_metadata_only_bundle_as_unhydrated() {
let mut bundle = TestBundle::complete_service();
bundle.rewrite_manifest(|manifest| manifest.mode = BundleMode::MetadataOnly);
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_UNHYDRATED_ARTIFACT",
"path",
);
assert_pull_recovery(WyrdState::from_path(bundle.path()));
}
#[test]
fn rejects_retired_bundle_api_version() {
let mut bundle = TestBundle::complete_service();
bundle.rewrite_manifest(|manifest| {
manifest.api_version = "wyrd/hydrated-bundle/v1".to_owned();
});
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"api_version",
);
assert_pull_recovery(WyrdState::from_path(bundle.path()));
}
#[test]
fn rejects_inconsistent_manifest_counts() {
let mut bundle = TestBundle::complete_service();
bundle.rewrite_manifest(|manifest| manifest.card_count += 1);
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"card_count",
);
}
#[test]
fn rejects_non_service_root() {
let mut bundle = TestBundle::complete_service();
bundle.rewrite_manifest(|manifest| manifest.root.kind = CardKind::Model);
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"root",
);
}
#[test]
fn rejects_root_manifest_identity_mismatch() {
let mut bundle = TestBundle::complete_service();
bundle.rewrite_manifest(|manifest| {
manifest.root.name = CardName::new("other").expect("fixture name is valid");
});
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"root",
);
}
#[test]
fn rejects_root_without_uid() {
let mut bundle = TestBundle::complete_service();
bundle.rewrite_manifest(|manifest| manifest.root.uid = None);
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"root",
);
}
#[test]
fn rejects_child_without_uid() {
let bundle = TestBundle::complete_service();
let child = test_ref(CardKind::Model, "model", 2);
bundle.rewrite_card(&child, |card| card.metadata.uid = None);
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"path",
);
}
#[test]
fn rejects_manifest_card_identity_mismatch() {
let mut bundle = TestBundle::complete_service();
bundle.rewrite_manifest(|manifest| {
manifest.cards[1].card_ref.name =
CardName::new("other").expect("fixture name is valid");
});
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"manifest_card_ref",
);
}
#[test]
fn rejects_duplicate_exact_card_ref() {
let mut bundle = TestBundle::complete_service();
let model = test_ref(CardKind::Model, "model", 2);
let duplicate = bundle
.cards
.borrow()
.get(&model.to_string())
.cloned()
.expect("fixture model exists");
bundle.cards.borrow_mut().insert(
test_ref(CardKind::Model, "backup", 3).to_string(),
duplicate,
);
bundle.manifest.cards[2].card_ref = model.clone();
bundle.manifest.cards[2].aliases = vec!["backup".to_owned()];
bundle.write();
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"card_ref",
);
}
#[test]
fn rejects_conflicting_alias() {
let mut bundle = TestBundle::complete_service();
bundle.add_alias("model", &test_ref(CardKind::Model, "backup", 3));
bundle.write();
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"alias",
);
}
#[test]
fn rejects_alias_projection_mismatch() {
let bundle = TestBundle::complete_service();
write_yaml(
&bundle.path().join("aliases/model.yaml"),
&serde_json::json!({ "alias": "model", "card_ref": test_ref(CardKind::Model, "backup", 3), "card_path": "cards/model/card.yaml" }),
);
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"alias",
);
}
#[test]
fn rejects_relationship_projection_mismatch() {
let bundle = TestBundle::complete_service();
write_yaml(
&bundle.path().join("cards/root/relationships.yaml"),
&Relationships::default(),
);
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"relationships_path",
);
}
#[test]
fn rejects_artifact_inventory_projection_mismatch() {
let bundle = TestBundle::with_artifact();
write_yaml(
&bundle.path().join("cards/model/artifacts.yaml"),
&Vec::<BundleArtifactManifest>::new(),
);
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"inventory_path",
);
}
#[test]
fn rejects_missing_relationship_target() {
let bundle = TestBundle::complete_service();
let child = test_ref(CardKind::Model, "model", 2);
bundle.rewrite_card(&test_ref(CardKind::Service, "service", 1), |card| {
let Spec::Service(spec) = &mut card.spec else {
panic!("fixture root is a Service");
};
spec.components[0].card_ref = Ref::Ref(CardRef {
name: CardName::new("missing").expect("fixture name is valid"),
..child
});
});
bundle.write();
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"card_ref",
);
}
#[test]
fn rejects_unresolved_nested_path_reference() {
let bundle = TestBundle::complete_service();
bundle.rewrite_card(&test_ref(CardKind::Service, "service", 1), |card| {
if let Spec::Service(spec) = &mut card.spec {
spec.components[0].card_ref = Ref::Path(PathBuf::from("nested/model.yaml"));
}
});
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"path",
);
}
#[test]
fn rejects_top_level_sibling_reference() {
let bundle = TestBundle::complete_service();
let sibling = test_ref(CardKind::Model, "model", 2);
bundle.rewrite_card(&test_ref(CardKind::Service, "service", 1), |card| {
if let Spec::Service(spec) = &mut card.spec {
spec.components[0].card_ref = Ref::Sibling {
sibling: sibling.clone(),
};
}
});
assert_sibling_error(WyrdState::from_path(bundle.path()), &sibling);
}
#[test]
fn rejects_nested_inline_agent_prompt_sibling_reference() {
let sibling = test_ref(CardKind::Model, "model", 2);
let mut bundle = TestBundle::complete_service();
bundle.with_nested_prompt(sibling.clone(), true);
assert_sibling_error(WyrdState::from_path(bundle.path()), &sibling);
}
#[test]
fn loads_nested_inline_agent_prompt_durable_reference() {
let target = test_ref(CardKind::Model, "model", 2);
let mut bundle = TestBundle::complete_service();
let verifier_ref = bundle.with_nested_prompt(target, false);
let state = WyrdState::from_path(bundle.path()).expect("durable nested ref loads");
assert_eq!(
state.card_ref("judge").expect("judge Verifier resolves"),
&verifier_ref
);
}
#[test]
fn rejects_spec_ref_absent_from_graph() {
let bundle = TestBundle::complete_service();
bundle.rewrite_card(&test_ref(CardKind::Service, "service", 1), |card| {
if let Spec::Service(spec) = &mut card.spec {
spec.components[0].card_ref = Ref::Ref(test_ref(CardKind::Model, "missing", 4));
}
});
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"owner",
);
}
#[test]
fn rejects_escaping_manifest_path() {
let mut bundle = TestBundle::complete_service();
bundle.rewrite_manifest(|manifest| manifest.cards[0].card_path = "../card.yaml".to_owned());
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"path",
);
}
#[test]
fn rejects_unsafe_alias() {
let mut bundle = TestBundle::complete_service();
bundle
.rewrite_manifest(|manifest| manifest.cards[1].aliases = vec!["../escape".to_owned()]);
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"alias",
);
}
#[test]
fn rejects_missing_artifact_payload() {
let bundle = TestBundle::with_artifact();
fs::remove_file(bundle.path().join("cards/model/artifacts/weights.bin"))
.expect("fixture payload exists");
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"path",
);
}
#[test]
fn rejects_non_file_artifact_payload() {
let bundle = TestBundle::with_artifact();
fs::remove_file(bundle.path().join("cards/model/artifacts/weights.bin"))
.expect("fixture payload exists");
fs::create_dir(bundle.path().join("cards/model/artifacts/weights.bin"))
.expect("fixture directory creates");
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"path",
);
}
#[cfg(unix)]
#[test]
fn rejects_symlinked_artifact_escape() {
let bundle = TestBundle::with_artifact();
let payload = bundle.path().join("cards/model/artifacts/weights.bin");
fs::remove_file(&payload).expect("fixture payload exists");
std::os::unix::fs::symlink("/etc/hosts", payload).expect("fixture symlink creates");
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"path",
);
}
#[cfg(not(unix))]
#[test]
fn rejects_symlinked_artifact_escape() {}
#[cfg(unix)]
#[test]
fn rejects_external_metadata_symlink() {
let bundle = TestBundle::complete_service();
let external = tempdir().expect("external fixture tempdir is available");
let target = external.path().join("metadata.yaml");
fs::write(&target, "apiVersion: wyrd/bundle/v1\n")
.expect("external metadata fixture writes");
fs::remove_file(bundle.path().join("metadata.yaml"))
.expect("bundle metadata fixture exists");
std::os::unix::fs::symlink(&target, bundle.path().join("metadata.yaml"))
.expect("external metadata symlink creates");
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"path",
);
}
#[cfg(not(unix))]
#[test]
fn rejects_external_metadata_symlink() {}
#[test]
fn rejects_artifact_size_mismatch() {
let mut bundle = TestBundle::with_artifact();
bundle.rewrite_manifest(|manifest| manifest.cards[1].artifacts[0].size_bytes += 1);
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"expected_size",
);
}
#[test]
fn rejects_artifact_digest_mismatch() {
let bundle = TestBundle::with_artifact();
bundle.rewrite_artifact(
&test_ref(CardKind::Model, "model", 2),
"weights.bin",
b"corrupt",
);
assert_error(
WyrdState::from_path(bundle.path()),
"WYRD_SDK_400_INVALID_STATE_BUNDLE",
"expected_sha256",
);
}
#[test]
fn unknown_alias_lists_available_aliases() {
let bundle = TestBundle::complete_service();
let state = WyrdState::from_path(bundle.path()).expect("complete graph loads");
let error = state.card("missing").expect_err("unknown alias rejects");
assert_eq!(error.code(), "WYRD_SDK_404_UNKNOWN_ALIAS");
assert_eq!(
problem_details(&error)["available_aliases"],
serde_json::json!(["backup", "model", "root"])
);
}
#[test]
fn artifact_paths_are_absolute_confined_and_verified() {
let bundle = TestBundle::with_artifact();
let state = WyrdState::from_path(bundle.path()).expect("complete artifact graph loads");
let artifact = &artifacts(&state, "model")[0];
assert!(artifact.local_path.is_absolute());
assert!(
artifact
.local_path
.starts_with(fs::canonicalize(bundle.path()).expect("fixture root canonicalizes"))
);
assert_eq!(artifact.size_bytes, 7);
assert_eq!(
artifact.content_type.as_deref(),
Some("application/octet-stream")
);
assert_eq!(
state
.artifact_dir("model")
.expect("artifact dir resolves")
.expect("artifact dir exists"),
artifact
.local_path
.parent()
.expect("artifact parent exists")
);
}
#[test]
fn telemetry_flush_failure_settles_with_the_native_drain() {
use super::TelemetryFlushFailure;
let native = WyrdError::SdkBifrostClosed {
message: "closed".to_owned(),
details: serde_json::json!({}),
};
let both = TelemetryFlushFailure::settle(
Some(TelemetryFlushFailure::timeout("force_flush returned false")),
Err(native.clone()),
)
.expect_err("telemetry timeout must fail");
assert_eq!(both.code(), "WYRD_SDK_504_TELEMETRY_FLUSH_TIMEOUT");
let WyrdError::SdkTelemetryFlushTimeout { details, .. } = &both else {
panic!("expected the timeout variant, got {both:?}");
};
assert_eq!(details["native_code"], "WYRD_SDK_409_BIFROST_CLOSED");
let only = TelemetryFlushFailure::settle(
Some(TelemetryFlushFailure::failed("ValueError")),
Ok(()),
)
.expect_err("telemetry failure must fail");
assert_eq!(only.code(), "WYRD_SDK_503_TELEMETRY_FLUSH_FAILED");
let WyrdError::SdkTelemetryFlushFailed { details, .. } = &only else {
panic!("expected the failure variant, got {only:?}");
};
assert!(details["native_code"].is_null());
let native_only =
TelemetryFlushFailure::settle(None, Err(native)).expect_err("drain failure");
assert_eq!(native_only.code(), "WYRD_SDK_409_BIFROST_CLOSED");
assert!(TelemetryFlushFailure::settle(None, Ok(())).is_ok());
}
}