feat: zeroclaw agentic agency architecture — agents, skills, Rust crates

- 18 agent specs across Software, Support, Marketing, Cross-cutting
  (orchestrator + 7 software + 5 support + 3 cross-cutting + leads-finder)
- 8 reusable skills (handoff, pr-review, dokploy-deploy, campaign-structure,
  content-calendar, ticket-triage, license-lifecycle, invoice-draft)
- 3 JSON schemas: handoff, deliverable, approval
- Policies: autonomy levels, tool risk classification, compliance (DSGVO/GoBD/UWG)
- routing.toml: model tiers T1/T2/T3 + provider config (PII gating)
- Rust workspace (5 crates, all compile + 16 tests pass):
  - aos-mcp: tool registry + risk tagging
  - aos-policy: autonomy levels, budget enforcement, approval requests
  - aos-license: ed25519 license issue/verify (real crypto + CLI binary)
  - aos-ledger: integer-cents money math, gapless numbering, VAT, audit chain
  - aos-sync: frontmatter validator (18/18 specs pass), CLI binary
- CLAUDE.md entry point with architecture overview + roadmap
This commit is contained in:
Z User committed 2026-10-30 13:01:03 +00:00
1 parent d18c463ffd
commit 136379c666
60 files changed
+4776

No files matched your search

@@ -0,0 +1,22 @@
[package]
name = "aos-license"
version.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
serde.workspace = true
serde_json.workspace = true
anyhow.workspace = true
thiserror.workspace = true
ed25519-dalek = { version = "2", features = ["rand_core"] }
rand = "0.8"
base64 = "0.22"
clap = { version = "4", features = ["derive"] }
[lib]
path = "src/lib.rs"
[[bin]]
name = "aos-license"
path = "src/main.rs"
@@ -0,0 +1,69 @@
//! License issuance: builds an ed25519-signed key from a payload.
//!
//! The payload is the customer-facing license metadata (product, plan, domain,
//! expiry). The key is `base64(payload_json).base64(signature)` — verifiable
//! offline by anyone holding the public key.
use anyhow::Result;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use ed25519_dalek::{Signer, SigningKey};
use serde::{Deserialize, Serialize};
/// The customer-facing license metadata that gets signed.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LicensePayload {
pub product: String,
pub plan: String,
pub domain: String,
/// ISO-8601 expiry, or null for perpetual.
pub expires_at: Option<String>,
/// Issued-at timestamp.
pub issued_at: String,
}
/// A signed license key: the URL-safe-base64 payload + signature.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LicenseKey {
/// `base64url(payload_json).base64url(signature)`
pub key: String,
pub payload: LicensePayload,
}
/// Issues a license key signed by `signing_key`.
///
/// The signing key is platform-held; agents never see it. They call this
/// function via the MCP tool `aos-license:issue`.
pub fn issue_license(payload: LicensePayload, signing_key: &SigningKey) -> Result<LicenseKey> {
let payload_json = serde_json::to_vec(&payload)?;
let payload_b64 = URL_SAFE_NO_PAD.encode(&payload_json);
let signature = signing_key.sign(&payload_json);
let sig_b64 = URL_SAFE_NO_PAD.encode(signature.to_bytes());
Ok(LicenseKey {
key: format!("{payload_b64}.{sig_b64}"),
payload,
})
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::SigningKey;
use rand::rngs::OsRng;
#[test]
fn issued_key_round_trips() {
let mut csprng = OsRng;
let signing = SigningKey::generate(&mut csprng);
let payload = LicensePayload {
product: "aos-cms".into(),
plan: "pro".into(),
domain: "example.dfmpanel.eu".into(),
expires_at: Some("2027-01-01T00:00:00Z".into()),
issued_at: "2026-01-01T00:00:00Z".into(),
};
let key = issue_license(payload.clone(), &signing).unwrap();
// Verify with the corresponding public key.
let verifying = signing.verifying_key();
assert!(crate::verify::verify_license(&key.key, &verifying).is_ok());
}
}
@@ -0,0 +1,14 @@
// aos-license — license key issue/verify with ed25519.
//
// Agents call this crate; they never invent or edit keys. The signing key is
// held by the platform (env-configured), never shipped to agents. Keys are
// offline-verifiable: a customer's instance can validate a license without
// calling home.
//
// See `skills/license-lifecycle/SKILL.md` for the agent procedure.
pub mod key;
pub mod verify;
pub use key::{issue_license, LicenseKey, LicensePayload};
pub use verify::verify_license;
@@ -0,0 +1,93 @@
// aos-license CLI: issue + verify subcommands.
//
// Usage:
// aos-license issue --product aos-cms --plan pro --domain example.com [--expires 2027-01-01]
// aos-license verify --key <key-string> --pubkey <base64-public-key>
//
// The signing key for `issue` is read from the AOS_LICENSE_SIGNING_KEY env
// var (base64 32-byte seed). Not having it set is an error — agents must not
// issue keys from the CLI directly in production.
use anyhow::Result;
use base64::{engine::general_purpose::STANDARD, Engine};
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "aos-license", version, about = "Ed25519 license key issue/verify")]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
/// Issue a signed license key (requires AOS_LICENSE_SIGNING_KEY env).
Issue {
#[arg(long)]
product: String,
#[arg(long)]
plan: String,
#[arg(long)]
domain: String,
#[arg(long)]
expires: Option<String>,
},
/// Verify a license key against a public key.
Verify {
#[arg(long)]
key: String,
#[arg(long, help = "base64 ed25519 public key")]
pubkey: String,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Command::Issue {
product,
plan,
domain,
expires,
} => {
let seed_b64 = std::env::var("AOS_LICENSE_SIGNING_KEY")
.expect("AOS_LICENSE_SIGNING_KEY env var required to issue licenses");
let seed = STANDARD.decode(seed_b64)?;
let signing = ed25519_dalek::SigningKey::from_bytes(
seed.as_slice().try_into().expect("seed must be 32 bytes"),
);
let payload = aos_license::LicensePayload {
product,
plan,
domain,
expires_at: expires,
issued_at: chrono_now_iso(),
};
let key = aos_license::issue_license(payload, &signing)?;
println!("{}", key.key);
Ok(())
}
Command::Verify { key, pubkey } => {
let pubkey_bytes = STANDARD.decode(pubkey)?;
let verifying = ed25519_dalek::VerifyingKey::from_bytes(
pubkey_bytes.as_slice().try_into().expect("pubkey must be 32 bytes"),
)?;
let payload = aos_license::verify_license(&key, &verifying)?;
println!(
"valid: product={}, plan={}, domain={}, expires={:?}",
payload.product, payload.plan, payload.domain, payload.expires_at
);
Ok(())
}
}
}
fn chrono_now_iso() -> String {
// Avoid pulling chrono just for a timestamp — use std.
use std::time::{SystemTime, UNIX_EPOCH};
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
format!("{secs}")
}
@@ -0,0 +1,26 @@
//! Offline license verification.
use anyhow::Result;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
/// Verifies a license key string against the platform's public key.
///
/// Returns the decoded `LicensePayload` on success. Offline-verifiable: the
/// customer's instance needs only the public key, no network call.
pub fn verify_license(key: &str, public_key: &VerifyingKey) -> Result<crate::LicensePayload> {
let (payload_b64, sig_b64) = key
.split_once('.')
.ok_or_else(|| anyhow::anyhow!("invalid license key format (no '.' separator)"))?;
let payload_bytes = URL_SAFE_NO_PAD.decode(payload_b64)?;
let sig_bytes = URL_SAFE_NO_PAD.decode(sig_b64)?;
let signature = Signature::from_slice(&sig_bytes)?;
public_key
.verify(&payload_bytes, &signature)
.map_err(|_| anyhow::anyhow!("license signature verification failed"))?;
let payload: crate::LicensePayload = serde_json::from_slice(&payload_bytes)?;
Ok(payload)
}