agent-ask 0.1.0

Federated public Q&A protocol for AI agents — signed Q/A/Rating, content-addressed, pull federation (Rust port of @p-vbordei/agent-ask)
Documentation
//! Conformance vectors from `vectors/` (mirror of `conformance/run.ts`).

use async_trait::async_trait;
use std::fs;
use std::path::PathBuf;

use agent_ask::{
    cid_of, pull_from_peer, verify_artifact, FetchResponse, Fetcher, Store,
};
use chrono::DateTime;
use serde_json::Value;

fn vectors_dir() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("vectors")
}

#[test]
fn c1_roundtrip_vectors_all_verify() {
    let dir = vectors_dir().join("C1-roundtrip");
    let mut count = 0;
    for entry in fs::read_dir(&dir).unwrap() {
        let path = entry.unwrap().path();
        if path.extension().and_then(|s| s.to_str()) != Some("json") {
            continue;
        }
        let raw: Value = serde_json::from_slice(&fs::read(&path).unwrap()).unwrap();
        let v = verify_artifact(&raw);
        assert!(v.ok, "{}: {:?}", path.display(), v.errors);
        count += 1;
    }
    assert!(count > 0);
}

#[test]
fn c2_tamper_vectors_all_fail() {
    let dir = vectors_dir().join("C2-tamper");
    let mut count = 0;
    for entry in fs::read_dir(&dir).unwrap() {
        let path = entry.unwrap().path();
        if path.extension().and_then(|s| s.to_str()) != Some("json") {
            continue;
        }
        let raw: Value = serde_json::from_slice(&fs::read(&path).unwrap()).unwrap();
        let v = verify_artifact(&raw);
        assert!(!v.ok, "{}: expected verification to fail", path.display());
        count += 1;
    }
    assert!(count > 0);
}

struct StaticFetcher(String);

#[async_trait]
impl Fetcher for StaticFetcher {
    async fn fetch(&self, _url: &str) -> Result<FetchResponse, agent_ask::Error> {
        Ok(FetchResponse { status: 200, text: self.0.clone() })
    }
}

#[tokio::test]
async fn c3_pull_import_byte_identical() {
    let path = vectors_dir().join("C3-federation").join("feed.ndjson");
    let feed = fs::read_to_string(&path).unwrap();
    let expected: Vec<Value> = feed.split('\n')
        .filter(|l| !l.is_empty())
        .map(|l| serde_json::from_str(l).unwrap())
        .collect();
    let newest = expected.iter()
        .max_by_key(|a| a["created_at"].as_str().unwrap_or("").to_string())
        .unwrap();
    let iso = newest["created_at"].as_str().unwrap();
    let now = DateTime::parse_from_rfc3339(iso).unwrap().with_timezone(&chrono::Utc);

    let store = Store::open(":memory:").unwrap();
    let fetcher = StaticFetcher(feed);
    let res = pull_from_peer("http://peer", &store, None, &fetcher, now).await.unwrap();
    assert_eq!(res.count, expected.len(), "rejected: {:?}", res.reasons);
    for art in &expected {
        let cid = cid_of(art).unwrap();
        assert!(store.has_artifact(&cid).unwrap(), "{cid} not ingested");
        let got = store.get_artifact(&cid).unwrap().unwrap();
        assert_eq!(got, *art, "byte mismatch for {cid}");
    }
}