This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Build all crates
cargo build --workspace
# Build release
cargo build --workspace --release
# Run all tests (includes test-support for mocks)
cargo test --workspace --features ans-verify/test-support,ans-verify/rustls
# Run tests for specific crate
cargo test -p ans-verify --features test-support
cargo test -p ans-types
cargo test -p ans-client
# Run single test
cargo test -p ans-verify test_server_verification_success
# Run tests with logging
RUST_LOG=ans_verify=debug cargo test --workspace -- --nocapture
# Run examples
RUST_LOG=ans_verify=debug cargo run -p ans-verify --example verify_server
RUST_LOG=ans_verify=debug cargo run -p ans-verify --example verify_mtls_client
# Check formatting and lints
cargo fmt --all -- --check
cargo clippy --workspace --features ans-verify/test-support,ans-verify/rustls
# Fix formatting automatically
cargo fmt --all
# Fix clippy warnings automatically
cargo clippy --fix --workspace --features ans-verify/test-support,ans-verify/rustls --allow-dirtyAll changes must pass before merging:
cargo fmt --all -- --check # Formatting
cargo clippy --workspace --features ans-verify/test-support,ans-verify/rustls # No warnings
cargo test --workspace --features ans-verify/test-support,ans-verify/rustls # All tests passReleases are fully automated via release-please. No local tooling required — everything happens in CI.
How it works:
- Merge PRs to
mainusing Conventional Commits (feat:,fix:, etc.) - Release-please automatically creates/updates a Release PR with version bumps and CHANGELOG
- When the team merges the Release PR, release-please creates a git tag + GitHub Release
- The release workflow then runs full CI and publishes to crates.io
Commit message conventions:
feat: add new verification mode→ bumps minor version, appears under "Features"fix: handle empty DNS response→ bumps patch version, appears under "Bug Fixes"feat!: redesign verifier APIorBREAKING CHANGE:footer → bumps major versionchore:,ci:,docs:,test:→ no version bump, no changelog entry
What CI does on release (.github/workflows/release.yml):
- Runs full CI gate (fmt, clippy, test, MSRV, audit, cargo-deny)
- Dry-run publish check
- Publishes to crates.io in dependency order:
ans-types→ans-verify+ans-client
Prerequisites:
CARGO_REGISTRY_TOKENsecret configured in thecrates-ioGitHub environment
This is a Cargo workspace with three crates following a layered dependency model:
ans-types (foundation)
↓
ans-verify (depends on ans-types)
ans-client (depends on ans-types)
ans-types — Shared domain types with no external dependencies beyond serialization:
Fqdn: Validated FQDN withans_badge_name()/ra_badge_name()methods for DNS queriesAnsName: URI format parser (ans://v1.0.0.agent.example.com)Version: Semantic version comparisonBadge,BadgePayload,BadgeStatus: Transparency log badge structuresCertFingerprint: SHA-256 certificate fingerprint withmatches()comparison
ans-verify — Trust verification with DNS and transparency log integration:
AnsVerifier: High-level facade combining server and client verificationServerVerifier: Client-side TLS verification (verifies server certificates)ClientVerifier: Server-side mTLS verification (verifies client identity certificates)DnsResolvertrait +HickoryDnsResolver: DNS_ans-badge/_ra-badgeTXT record lookupTransparencyLogClienttrait +HttpTransparencyLogClient: Badge API clientBadgeCache: TTL-based Moka cache for badge responses- DANE/TLSA verification via
DanePolicyandTlsaRecord
ans-client — ANS Registry API client for agent registration and management:
AnsClient: HTTP client with builder pattern for API configurationAnsClientBuilder: Fluent builder supporting JWT and API key auth- Registration:
register_agent(),verify_acme(),verify_dns() - Discovery:
get_agent(),search_agents(),resolve_agent() - Certificates:
get_server_certificates(),get_identity_certificates(),submit_*_csr() - Revocation:
revoke_agent()with RFC 5280 reason codes - Models: Complete API request/response types matching OpenAPI spec
Server verification (verify_server):
- Check badge cache by FQDN
- DNS lookup:
_ans-badge.{fqdn}TXT record (fallback:_ra-badge.{fqdn}) → transparency log URL - Fetch badge from transparency log API
- Validate badge status (
Active/Warning/Deprecatedallowed) - Compare server certificate fingerprint to badge's
attestations.server_cert.fingerprint - Compare certificate CN to badge's
agent.host - Optional: DANE/TLSA verification if policy enabled
Client verification (verify_client) for mTLS:
- Extract FQDN from certificate CN, version from URI SAN (
ans://...) - DNS lookup by FQDN, match badge to certificate version
- Compare identity certificate fingerprint to badge's
attestations.identity_cert.fingerprint - Compare ANS name from URI SAN to badge's
ans_name
All async traits use #[async_trait]:
// DNS resolution - implement for custom resolvers
pub trait DnsResolver: Send + Sync {
async fn lookup_badge(&self, fqdn: &Fqdn) -> Result<DnsLookupResult<BadgeRecord>, DnsError>;
async fn find_badge_for_version(&self, fqdn: &Fqdn, version: &Version) -> Result<Option<BadgeRecord>, DnsError>;
async fn get_tlsa_records(&self, fqdn: &Fqdn, port: u16) -> Result<Vec<TlsaRecord>, DaneError>;
}
// Transparency log client - implement for custom backends
pub trait TransparencyLogClient: Send + Sync {
async fn fetch_badge(&self, url: &Url) -> Result<Badge, TlogError>;
}Mock implementations are provided behind the test-support feature flag:
use ans_verify::{MockDnsResolver, MockTransparencyLogClient};
let dns = Arc::new(MockDnsResolver::new()
.with_records("agent.example.com", vec![badge_record])
.with_tlsa_records("agent.example.com", 443, vec![tlsa_record]));
let tlog = Arc::new(MockTransparencyLogClient::new()
.with_badge("https://tlog.example.com/badge", badge));
let verifier = ServerVerifier::builder()
.dns_resolver(dns)
.tlog_client(tlog)
.build()
.await?;Test fixtures use rstest for parameterized tests and test-log for tracing output.
- MSRV: 1.88
- Edition: 2024
- Error handling:
thiserrorfor error definitions,Result<T, AnsError>return types - Async:
tokioruntime,async-traitfor trait methods - Logging:
tracingwith structured fields (tracing::debug!(fqdn = %fqdn, ...)) - HTTP:
reqwestwithrustls-tls(no OpenSSL dependency) - DNS:
hickory-resolverwith DNSSEC support - Runtime compatibility: must handle real-world DNS records including versionless
_ra-badgeTXT records from legacy deployments
- Badge: Registration record from the transparency log containing agent metadata and certificate fingerprints
_ans-badgerecord: DNS TXT record at_ans-badge.{fqdn}pointing to badge URL (legacy:_ra-badge)- ANS Name: URI format
ans://v{major}.{minor}.{patch}.{fqdn}in certificate URI SAN - Server cert: Public CA certificate for TLS server identity
- Identity cert: ANS Private CA certificate for mTLS client authentication
- TLSA record: DANE certificate binding at
_{port}._tcp.{fqdn}