1622 lines
50 KiB
Markdown
1622 lines
50 KiB
Markdown
# VAULTMESH-IMPLEMENTATION-SCAFFOLDS.md
|
|
|
|
**From Pattern to Code**
|
|
|
|
> *Every specification deserves a skeleton.*
|
|
|
|
This document provides implementation scaffolds for all VaultMesh engines — Rust structs for core types and Python CLI skeletons for tooling.
|
|
|
|
---
|
|
|
|
## 1. Shared Types (Rust)
|
|
|
|
### 1.1 Core Receipt Types
|
|
|
|
```rust
|
|
// vaultmesh-core/src/receipt.rs
|
|
|
|
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
|
|
/// Universal receipt header present in all receipts
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ReceiptHeader {
|
|
pub receipt_type: String,
|
|
pub timestamp: DateTime<Utc>,
|
|
pub root_hash: String,
|
|
pub tags: Vec<String>,
|
|
}
|
|
|
|
/// Receipt metadata for tracking and querying
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ReceiptMeta {
|
|
pub scroll: Scroll,
|
|
pub sequence: u64,
|
|
pub anchor_epoch: Option<u64>,
|
|
pub proof_path: Option<String>,
|
|
}
|
|
|
|
/// Scroll identifiers
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum Scroll {
|
|
Drills,
|
|
Compliance,
|
|
Guardian,
|
|
Treasury,
|
|
Mesh,
|
|
OffSec,
|
|
Identity,
|
|
Observability,
|
|
Automation,
|
|
PsiField,
|
|
}
|
|
|
|
impl Scroll {
|
|
pub fn jsonl_path(&self) -> &'static str {
|
|
match self {
|
|
Scroll::Drills => "receipts/drills/drill_runs.jsonl",
|
|
Scroll::Compliance => "receipts/compliance/oracle_answers.jsonl",
|
|
Scroll::Guardian => "receipts/guardian/anchor_events.jsonl",
|
|
Scroll::Treasury => "receipts/treasury/treasury_events.jsonl",
|
|
Scroll::Mesh => "receipts/mesh/mesh_events.jsonl",
|
|
Scroll::OffSec => "receipts/offsec/offsec_events.jsonl",
|
|
Scroll::Identity => "receipts/identity/identity_events.jsonl",
|
|
Scroll::Observability => "receipts/observability/observability_events.jsonl",
|
|
Scroll::Automation => "receipts/automation/automation_events.jsonl",
|
|
Scroll::PsiField => "receipts/psi/psi_events.jsonl",
|
|
}
|
|
}
|
|
|
|
pub fn root_file(&self) -> &'static str {
|
|
match self {
|
|
Scroll::Drills => "ROOT.drills.txt",
|
|
Scroll::Compliance => "ROOT.compliance.txt",
|
|
Scroll::Guardian => "ROOT.guardian.txt",
|
|
Scroll::Treasury => "ROOT.treasury.txt",
|
|
Scroll::Mesh => "ROOT.mesh.txt",
|
|
Scroll::OffSec => "ROOT.offsec.txt",
|
|
Scroll::Identity => "ROOT.identity.txt",
|
|
Scroll::Observability => "ROOT.observability.txt",
|
|
Scroll::Automation => "ROOT.automation.txt",
|
|
Scroll::PsiField => "ROOT.psi.txt",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Generic receipt wrapper
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Receipt<T> {
|
|
#[serde(flatten)]
|
|
pub header: ReceiptHeader,
|
|
#[serde(flatten)]
|
|
pub meta: ReceiptMeta,
|
|
#[serde(flatten)]
|
|
pub body: T,
|
|
}
|
|
```
|
|
|
|
### 1.2 DID Types
|
|
|
|
```rust
|
|
// vaultmesh-core/src/did.rs
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// VaultMesh DID
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
pub struct Did(String);
|
|
|
|
impl Did {
|
|
pub fn new(did_type: DidType, identifier: &str) -> Self {
|
|
Did(format!("did:vm:{}:{}", did_type.as_str(), identifier))
|
|
}
|
|
|
|
pub fn parse(s: &str) -> Result<Self, DidParseError> {
|
|
if !s.starts_with("did:vm:") {
|
|
return Err(DidParseError::InvalidPrefix);
|
|
}
|
|
Ok(Did(s.to_string()))
|
|
}
|
|
|
|
pub fn did_type(&self) -> Option<DidType> {
|
|
let parts: Vec<&str> = self.0.split(':').collect();
|
|
if parts.len() >= 3 {
|
|
DidType::from_str(parts[2])
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn identifier(&self) -> Option<&str> {
|
|
let parts: Vec<&str> = self.0.split(':').collect();
|
|
if parts.len() >= 4 {
|
|
Some(parts[3])
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn as_str(&self) -> &str {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum DidType {
|
|
Node,
|
|
Human,
|
|
Agent,
|
|
Service,
|
|
Mesh,
|
|
}
|
|
|
|
impl DidType {
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
DidType::Node => "node",
|
|
DidType::Human => "human",
|
|
DidType::Agent => "agent",
|
|
DidType::Service => "service",
|
|
DidType::Mesh => "mesh",
|
|
}
|
|
}
|
|
|
|
pub fn from_str(s: &str) -> Option<Self> {
|
|
match s {
|
|
"node" => Some(DidType::Node),
|
|
"human" => Some(DidType::Human),
|
|
"agent" => Some(DidType::Agent),
|
|
"service" => Some(DidType::Service),
|
|
"mesh" => Some(DidType::Mesh),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum DidParseError {
|
|
InvalidPrefix,
|
|
MissingType,
|
|
MissingIdentifier,
|
|
}
|
|
```
|
|
|
|
### 1.3 Hash Utilities
|
|
|
|
```rust
|
|
// vaultmesh-core/src/hash.rs
|
|
|
|
use blake3::Hasher;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// VaultMesh hash with algorithm prefix
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
pub struct VmHash(String);
|
|
|
|
impl VmHash {
|
|
/// Create hash from bytes using Blake3
|
|
pub fn blake3(data: &[u8]) -> Self {
|
|
let hash = blake3::hash(data);
|
|
VmHash(format!("blake3:{}", hash.to_hex()))
|
|
}
|
|
|
|
/// Create hash from JSON-serializable value
|
|
pub fn from_json<T: Serialize>(value: &T) -> Result<Self, serde_json::Error> {
|
|
let json = serde_json::to_vec(value)?;
|
|
Ok(Self::blake3(&json))
|
|
}
|
|
|
|
/// Create hash from file contents
|
|
pub fn from_file(path: &std::path::Path) -> std::io::Result<Self> {
|
|
let contents = std::fs::read(path)?;
|
|
Ok(Self::blake3(&contents))
|
|
}
|
|
|
|
/// Get the raw hex value without prefix
|
|
pub fn hex(&self) -> &str {
|
|
self.0.strip_prefix("blake3:").unwrap_or(&self.0)
|
|
}
|
|
|
|
/// Get full prefixed value
|
|
pub fn as_str(&self) -> &str {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
/// Compute Merkle root from list of hashes
|
|
pub fn merkle_root(hashes: &[VmHash]) -> VmHash {
|
|
if hashes.is_empty() {
|
|
return VmHash::blake3(b"empty");
|
|
}
|
|
if hashes.len() == 1 {
|
|
return hashes[0].clone();
|
|
}
|
|
|
|
let mut current_level: Vec<VmHash> = hashes.to_vec();
|
|
|
|
while current_level.len() > 1 {
|
|
let mut next_level = Vec::new();
|
|
|
|
for chunk in current_level.chunks(2) {
|
|
let combined = if chunk.len() == 2 {
|
|
format!("{}{}", chunk[0].hex(), chunk[1].hex())
|
|
} else {
|
|
format!("{}{}", chunk[0].hex(), chunk[0].hex())
|
|
};
|
|
next_level.push(VmHash::blake3(combined.as_bytes()));
|
|
}
|
|
|
|
current_level = next_level;
|
|
}
|
|
|
|
current_level.remove(0)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Treasury Engine (Rust)
|
|
|
|
```rust
|
|
// vaultmesh-treasury/src/lib.rs
|
|
|
|
use chrono::{DateTime, Utc};
|
|
use rust_decimal::Decimal;
|
|
use serde::{Deserialize, Serialize};
|
|
use vaultmesh_core::{Did, Receipt, ReceiptHeader, ReceiptMeta, Scroll, VmHash};
|
|
|
|
/// Treasury account
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Account {
|
|
pub account_id: String,
|
|
pub display_name: String,
|
|
pub account_type: AccountType,
|
|
pub currency: Currency,
|
|
pub balance: Decimal,
|
|
pub created_at: DateTime<Utc>,
|
|
pub tags: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum AccountType {
|
|
Operational,
|
|
Reserve,
|
|
Escrow,
|
|
External,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum Currency {
|
|
EUR,
|
|
USD,
|
|
GBP,
|
|
BTC,
|
|
ETH,
|
|
}
|
|
|
|
/// Treasury entry (single debit or credit)
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Entry {
|
|
pub entry_id: String,
|
|
pub entry_type: EntryType,
|
|
pub account: String,
|
|
pub amount: Decimal,
|
|
pub currency: Currency,
|
|
pub memo: String,
|
|
pub timestamp: DateTime<Utc>,
|
|
pub tags: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum EntryType {
|
|
Debit,
|
|
Credit,
|
|
}
|
|
|
|
/// Settlement contract
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SettlementContract {
|
|
pub settlement_id: String,
|
|
pub title: String,
|
|
pub initiated_by: Did,
|
|
pub initiated_at: DateTime<Utc>,
|
|
pub parties: Vec<Did>,
|
|
pub entries: Vec<Entry>,
|
|
pub requires_signatures: Vec<String>,
|
|
pub settlement_type: SettlementType,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum SettlementType {
|
|
InterNodeResource,
|
|
VendorPayment,
|
|
ClientInvoice,
|
|
GrantDisbursement,
|
|
}
|
|
|
|
/// Settlement state
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SettlementState {
|
|
pub settlement_id: String,
|
|
pub status: SettlementStatus,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
pub signatures: std::collections::HashMap<String, Option<Signature>>,
|
|
pub entries_applied: bool,
|
|
pub balance_snapshot_before: std::collections::HashMap<String, Decimal>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum SettlementStatus {
|
|
Draft,
|
|
PendingSignatures,
|
|
Executing,
|
|
Completed,
|
|
Disputed,
|
|
Expired,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Signature {
|
|
pub signed_at: DateTime<Utc>,
|
|
pub signature: String,
|
|
}
|
|
|
|
// Receipt types
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TreasuryCreditReceipt {
|
|
pub entry_id: String,
|
|
pub account: String,
|
|
pub amount: Decimal,
|
|
pub currency: Currency,
|
|
pub memo: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TreasuryDebitReceipt {
|
|
pub entry_id: String,
|
|
pub account: String,
|
|
pub amount: Decimal,
|
|
pub currency: Currency,
|
|
pub memo: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TreasurySettlementReceipt {
|
|
pub settlement_id: String,
|
|
pub title: String,
|
|
pub timestamp_initiated: DateTime<Utc>,
|
|
pub timestamp_completed: DateTime<Utc>,
|
|
pub status: SettlementStatus,
|
|
pub parties: Vec<Did>,
|
|
pub entries_count: usize,
|
|
pub net_flow: std::collections::HashMap<String, Decimal>,
|
|
pub currency: Currency,
|
|
pub settlement_type: SettlementType,
|
|
pub signatures_manifest: String,
|
|
}
|
|
|
|
/// Treasury engine
|
|
pub struct TreasuryEngine {
|
|
accounts: std::collections::HashMap<String, Account>,
|
|
pending_settlements: std::collections::HashMap<String, (SettlementContract, SettlementState)>,
|
|
}
|
|
|
|
impl TreasuryEngine {
|
|
pub fn new() -> Self {
|
|
TreasuryEngine {
|
|
accounts: std::collections::HashMap::new(),
|
|
pending_settlements: std::collections::HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn create_account(&mut self, account: Account) -> Result<(), TreasuryError> {
|
|
if self.accounts.contains_key(&account.account_id) {
|
|
return Err(TreasuryError::AccountExists);
|
|
}
|
|
self.accounts.insert(account.account_id.clone(), account);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn record_entry(&mut self, entry: Entry) -> Result<Receipt<TreasuryCreditReceipt>, TreasuryError> {
|
|
let account = self.accounts.get_mut(&entry.account)
|
|
.ok_or(TreasuryError::AccountNotFound)?;
|
|
|
|
match entry.entry_type {
|
|
EntryType::Credit => account.balance += entry.amount,
|
|
EntryType::Debit => {
|
|
if account.balance < entry.amount {
|
|
return Err(TreasuryError::InsufficientBalance);
|
|
}
|
|
account.balance -= entry.amount;
|
|
}
|
|
}
|
|
|
|
let receipt_body = TreasuryCreditReceipt {
|
|
entry_id: entry.entry_id.clone(),
|
|
account: entry.account.clone(),
|
|
amount: entry.amount,
|
|
currency: entry.currency,
|
|
memo: entry.memo,
|
|
};
|
|
|
|
let root_hash = VmHash::from_json(&receipt_body)
|
|
.map_err(|_| TreasuryError::SerializationError)?;
|
|
|
|
Ok(Receipt {
|
|
header: ReceiptHeader {
|
|
receipt_type: "treasury_credit".to_string(),
|
|
timestamp: entry.timestamp,
|
|
root_hash: root_hash.as_str().to_string(),
|
|
tags: entry.tags,
|
|
},
|
|
meta: ReceiptMeta {
|
|
scroll: Scroll::Treasury,
|
|
sequence: 0, // Set by receipt store
|
|
anchor_epoch: None,
|
|
proof_path: None,
|
|
},
|
|
body: receipt_body,
|
|
})
|
|
}
|
|
|
|
pub fn initiate_settlement(&mut self, contract: SettlementContract) -> Result<SettlementState, TreasuryError> {
|
|
// Validate all accounts exist
|
|
for entry in &contract.entries {
|
|
if !self.accounts.contains_key(&entry.account) {
|
|
return Err(TreasuryError::AccountNotFound);
|
|
}
|
|
}
|
|
|
|
// Capture balance snapshot
|
|
let mut snapshot = std::collections::HashMap::new();
|
|
for entry in &contract.entries {
|
|
if !snapshot.contains_key(&entry.account) {
|
|
let balance = self.accounts.get(&entry.account).unwrap().balance;
|
|
snapshot.insert(entry.account.clone(), balance);
|
|
}
|
|
}
|
|
|
|
let state = SettlementState {
|
|
settlement_id: contract.settlement_id.clone(),
|
|
status: SettlementStatus::PendingSignatures,
|
|
created_at: contract.initiated_at,
|
|
updated_at: Utc::now(),
|
|
signatures: contract.requires_signatures.iter()
|
|
.map(|s| (s.clone(), None))
|
|
.collect(),
|
|
entries_applied: false,
|
|
balance_snapshot_before: snapshot,
|
|
};
|
|
|
|
self.pending_settlements.insert(
|
|
contract.settlement_id.clone(),
|
|
(contract, state.clone()),
|
|
);
|
|
|
|
Ok(state)
|
|
}
|
|
|
|
pub fn add_signature(
|
|
&mut self,
|
|
settlement_id: &str,
|
|
signer: &str,
|
|
signature: Signature,
|
|
) -> Result<SettlementState, TreasuryError> {
|
|
let (_, state) = self.pending_settlements.get_mut(settlement_id)
|
|
.ok_or(TreasuryError::SettlementNotFound)?;
|
|
|
|
if !state.signatures.contains_key(signer) {
|
|
return Err(TreasuryError::UnauthorizedSigner);
|
|
}
|
|
|
|
state.signatures.insert(signer.to_string(), Some(signature));
|
|
state.updated_at = Utc::now();
|
|
|
|
// Check if all signatures collected
|
|
let all_signed = state.signatures.values().all(|s| s.is_some());
|
|
if all_signed {
|
|
state.status = SettlementStatus::Executing;
|
|
}
|
|
|
|
Ok(state.clone())
|
|
}
|
|
|
|
pub fn execute_settlement(&mut self, settlement_id: &str) -> Result<Receipt<TreasurySettlementReceipt>, TreasuryError> {
|
|
let (contract, state) = self.pending_settlements.get_mut(settlement_id)
|
|
.ok_or(TreasuryError::SettlementNotFound)?;
|
|
|
|
if state.status != SettlementStatus::Executing {
|
|
return Err(TreasuryError::SettlementNotReady);
|
|
}
|
|
|
|
// Apply all entries
|
|
let mut net_flow: std::collections::HashMap<String, Decimal> = std::collections::HashMap::new();
|
|
for entry in &contract.entries {
|
|
let account = self.accounts.get_mut(&entry.account).unwrap();
|
|
match entry.entry_type {
|
|
EntryType::Credit => {
|
|
account.balance += entry.amount;
|
|
*net_flow.entry(entry.account.clone()).or_insert(Decimal::ZERO) += entry.amount;
|
|
}
|
|
EntryType::Debit => {
|
|
account.balance -= entry.amount;
|
|
*net_flow.entry(entry.account.clone()).or_insert(Decimal::ZERO) -= entry.amount;
|
|
}
|
|
}
|
|
}
|
|
|
|
state.entries_applied = true;
|
|
state.status = SettlementStatus::Completed;
|
|
state.updated_at = Utc::now();
|
|
|
|
let receipt_body = TreasurySettlementReceipt {
|
|
settlement_id: contract.settlement_id.clone(),
|
|
title: contract.title.clone(),
|
|
timestamp_initiated: contract.initiated_at,
|
|
timestamp_completed: state.updated_at,
|
|
status: SettlementStatus::Completed,
|
|
parties: contract.parties.clone(),
|
|
entries_count: contract.entries.len(),
|
|
net_flow,
|
|
currency: contract.entries.first().map(|e| e.currency.clone()).unwrap_or(Currency::EUR),
|
|
settlement_type: contract.settlement_type.clone(),
|
|
signatures_manifest: format!("cases/treasury/{}/SIGNATURES.json", settlement_id),
|
|
};
|
|
|
|
let root_hash = VmHash::from_json(&receipt_body)
|
|
.map_err(|_| TreasuryError::SerializationError)?;
|
|
|
|
Ok(Receipt {
|
|
header: ReceiptHeader {
|
|
receipt_type: "treasury_settlement".to_string(),
|
|
timestamp: state.updated_at,
|
|
root_hash: root_hash.as_str().to_string(),
|
|
tags: vec!["treasury".to_string(), "settlement".to_string()],
|
|
},
|
|
meta: ReceiptMeta {
|
|
scroll: Scroll::Treasury,
|
|
sequence: 0,
|
|
anchor_epoch: None,
|
|
proof_path: Some(format!("cases/treasury/{}/PROOF.json", settlement_id)),
|
|
},
|
|
body: receipt_body,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum TreasuryError {
|
|
AccountExists,
|
|
AccountNotFound,
|
|
InsufficientBalance,
|
|
SettlementNotFound,
|
|
SettlementNotReady,
|
|
UnauthorizedSigner,
|
|
SerializationError,
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Identity Engine (Rust)
|
|
|
|
```rust
|
|
// vaultmesh-identity/src/lib.rs
|
|
|
|
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use vaultmesh_core::{Did, DidType, Receipt, ReceiptHeader, ReceiptMeta, Scroll, VmHash};
|
|
|
|
/// DID Document
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DidDocument {
|
|
#[serde(rename = "@context")]
|
|
pub context: Vec<String>,
|
|
pub id: Did,
|
|
pub controller: Option<Did>,
|
|
#[serde(rename = "verificationMethod")]
|
|
pub verification_method: Vec<VerificationMethod>,
|
|
pub authentication: Vec<String>,
|
|
#[serde(rename = "assertionMethod")]
|
|
pub assertion_method: Vec<String>,
|
|
pub service: Vec<Service>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct VerificationMethod {
|
|
pub id: String,
|
|
#[serde(rename = "type")]
|
|
pub method_type: String,
|
|
pub controller: Did,
|
|
#[serde(rename = "publicKeyMultibase")]
|
|
pub public_key_multibase: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Service {
|
|
pub id: String,
|
|
#[serde(rename = "type")]
|
|
pub service_type: String,
|
|
#[serde(rename = "serviceEndpoint")]
|
|
pub service_endpoint: String,
|
|
}
|
|
|
|
/// Verifiable Credential
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct VerifiableCredential {
|
|
pub credential_id: String,
|
|
#[serde(rename = "@context")]
|
|
pub context: Vec<String>,
|
|
#[serde(rename = "type")]
|
|
pub credential_type: Vec<String>,
|
|
pub issuer: Did,
|
|
#[serde(rename = "issuanceDate")]
|
|
pub issuance_date: DateTime<Utc>,
|
|
#[serde(rename = "expirationDate")]
|
|
pub expiration_date: Option<DateTime<Utc>>,
|
|
#[serde(rename = "credentialSubject")]
|
|
pub credential_subject: CredentialSubject,
|
|
pub proof: CredentialProof,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CredentialSubject {
|
|
pub id: Did,
|
|
#[serde(flatten)]
|
|
pub claims: HashMap<String, serde_json::Value>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CredentialProof {
|
|
#[serde(rename = "type")]
|
|
pub proof_type: String,
|
|
pub created: DateTime<Utc>,
|
|
#[serde(rename = "verificationMethod")]
|
|
pub verification_method: String,
|
|
#[serde(rename = "proofPurpose")]
|
|
pub proof_purpose: String,
|
|
#[serde(rename = "proofValue")]
|
|
pub proof_value: String,
|
|
}
|
|
|
|
/// Capability Token
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CapabilityToken {
|
|
pub capability_id: String,
|
|
pub holder: Did,
|
|
pub capability: Capability,
|
|
pub scope: CapabilityScope,
|
|
pub granted_by: Did,
|
|
pub granted_at: DateTime<Utc>,
|
|
pub expires_at: DateTime<Utc>,
|
|
pub constraints: HashMap<String, serde_json::Value>,
|
|
pub proof: CredentialProof,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum Capability {
|
|
Anchor,
|
|
Storage,
|
|
Compute,
|
|
Oracle,
|
|
Admin,
|
|
Federate,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CapabilityScope {
|
|
pub scrolls: Option<Vec<Scroll>>,
|
|
pub backends: Option<Vec<String>>,
|
|
pub max_rate: Option<String>,
|
|
}
|
|
|
|
/// Authentication Event
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AuthEvent {
|
|
pub auth_event_id: String,
|
|
pub subject: Did,
|
|
pub target: Did,
|
|
pub method: AuthMethod,
|
|
pub result: AuthResult,
|
|
pub timestamp: DateTime<Utc>,
|
|
pub source_ip: Option<String>,
|
|
pub session_id: Option<String>,
|
|
pub credential_used: Option<String>,
|
|
pub mfa_verified: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum AuthMethod {
|
|
SshKey,
|
|
Password,
|
|
Certificate,
|
|
Token,
|
|
Passkey,
|
|
Mfa,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum AuthResult {
|
|
Success,
|
|
Failure,
|
|
Denied,
|
|
Expired,
|
|
}
|
|
|
|
// Receipt bodies
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DidCreateReceipt {
|
|
pub did: Did,
|
|
pub did_type: String,
|
|
pub controller: Option<Did>,
|
|
pub created_by: Did,
|
|
pub initial_keys: Vec<String>,
|
|
pub did_document_hash: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CredentialIssueReceipt {
|
|
pub credential_id: String,
|
|
pub credential_type: String,
|
|
pub issuer: Did,
|
|
pub subject: Did,
|
|
pub expires_at: Option<DateTime<Utc>>,
|
|
pub claims_summary: HashMap<String, String>,
|
|
pub credential_hash: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CapabilityGrantReceipt {
|
|
pub capability_id: String,
|
|
pub holder: Did,
|
|
pub capability: Capability,
|
|
pub scope_summary: String,
|
|
pub granted_by: Did,
|
|
pub expires_at: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AuthEventReceipt {
|
|
pub auth_event_id: String,
|
|
pub subject: Did,
|
|
pub target: Did,
|
|
pub method: AuthMethod,
|
|
pub result: AuthResult,
|
|
pub source_ip_hash: Option<String>,
|
|
pub mfa_verified: bool,
|
|
}
|
|
|
|
/// Identity Engine
|
|
pub struct IdentityEngine {
|
|
did_documents: HashMap<Did, DidDocument>,
|
|
credentials: HashMap<String, VerifiableCredential>,
|
|
capabilities: HashMap<String, CapabilityToken>,
|
|
revoked_credentials: HashMap<String, DateTime<Utc>>,
|
|
}
|
|
|
|
impl IdentityEngine {
|
|
pub fn new() -> Self {
|
|
IdentityEngine {
|
|
did_documents: HashMap::new(),
|
|
credentials: HashMap::new(),
|
|
capabilities: HashMap::new(),
|
|
revoked_credentials: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn create_did(
|
|
&mut self,
|
|
did_type: DidType,
|
|
identifier: &str,
|
|
controller: Option<Did>,
|
|
public_key: &str,
|
|
created_by: Did,
|
|
) -> Result<Receipt<DidCreateReceipt>, IdentityError> {
|
|
let did = Did::new(did_type, identifier);
|
|
|
|
if self.did_documents.contains_key(&did) {
|
|
return Err(IdentityError::DidExists);
|
|
}
|
|
|
|
let key_id = format!("{}#key-1", did.as_str());
|
|
|
|
let doc = DidDocument {
|
|
context: vec![
|
|
"https://www.w3.org/ns/did/v1".to_string(),
|
|
"https://vaultmesh.io/ns/did/v1".to_string(),
|
|
],
|
|
id: did.clone(),
|
|
controller: controller.clone(),
|
|
verification_method: vec![VerificationMethod {
|
|
id: key_id.clone(),
|
|
method_type: "Ed25519VerificationKey2020".to_string(),
|
|
controller: did.clone(),
|
|
public_key_multibase: public_key.to_string(),
|
|
}],
|
|
authentication: vec![key_id.clone()],
|
|
assertion_method: vec![key_id.clone()],
|
|
service: vec![],
|
|
};
|
|
|
|
let doc_hash = VmHash::from_json(&doc)
|
|
.map_err(|_| IdentityError::SerializationError)?;
|
|
|
|
self.did_documents.insert(did.clone(), doc);
|
|
|
|
let receipt_body = DidCreateReceipt {
|
|
did: did.clone(),
|
|
did_type: did_type.as_str().to_string(),
|
|
controller,
|
|
created_by,
|
|
initial_keys: vec![key_id],
|
|
did_document_hash: doc_hash.as_str().to_string(),
|
|
};
|
|
|
|
let root_hash = VmHash::from_json(&receipt_body)
|
|
.map_err(|_| IdentityError::SerializationError)?;
|
|
|
|
Ok(Receipt {
|
|
header: ReceiptHeader {
|
|
receipt_type: "identity_did_create".to_string(),
|
|
timestamp: Utc::now(),
|
|
root_hash: root_hash.as_str().to_string(),
|
|
tags: vec!["identity".to_string(), "did".to_string(), "create".to_string()],
|
|
},
|
|
meta: ReceiptMeta {
|
|
scroll: Scroll::Identity,
|
|
sequence: 0,
|
|
anchor_epoch: None,
|
|
proof_path: None,
|
|
},
|
|
body: receipt_body,
|
|
})
|
|
}
|
|
|
|
pub fn resolve_did(&self, did: &Did) -> Option<&DidDocument> {
|
|
self.did_documents.get(did)
|
|
}
|
|
|
|
pub fn issue_credential(
|
|
&mut self,
|
|
credential: VerifiableCredential,
|
|
) -> Result<Receipt<CredentialIssueReceipt>, IdentityError> {
|
|
// Verify issuer exists
|
|
if !self.did_documents.contains_key(&credential.issuer) {
|
|
return Err(IdentityError::IssuerNotFound);
|
|
}
|
|
|
|
let credential_hash = VmHash::from_json(&credential)
|
|
.map_err(|_| IdentityError::SerializationError)?;
|
|
|
|
let claims_summary: HashMap<String, String> = credential.credential_subject.claims
|
|
.iter()
|
|
.map(|(k, v)| (k.clone(), v.to_string()))
|
|
.collect();
|
|
|
|
let receipt_body = CredentialIssueReceipt {
|
|
credential_id: credential.credential_id.clone(),
|
|
credential_type: credential.credential_type.join(", "),
|
|
issuer: credential.issuer.clone(),
|
|
subject: credential.credential_subject.id.clone(),
|
|
expires_at: credential.expiration_date,
|
|
claims_summary,
|
|
credential_hash: credential_hash.as_str().to_string(),
|
|
};
|
|
|
|
self.credentials.insert(credential.credential_id.clone(), credential);
|
|
|
|
let root_hash = VmHash::from_json(&receipt_body)
|
|
.map_err(|_| IdentityError::SerializationError)?;
|
|
|
|
Ok(Receipt {
|
|
header: ReceiptHeader {
|
|
receipt_type: "identity_credential_issue".to_string(),
|
|
timestamp: Utc::now(),
|
|
root_hash: root_hash.as_str().to_string(),
|
|
tags: vec!["identity".to_string(), "credential".to_string(), "issue".to_string()],
|
|
},
|
|
meta: ReceiptMeta {
|
|
scroll: Scroll::Identity,
|
|
sequence: 0,
|
|
anchor_epoch: None,
|
|
proof_path: None,
|
|
},
|
|
body: receipt_body,
|
|
})
|
|
}
|
|
|
|
pub fn verify_credential(&self, credential_id: &str) -> Result<bool, IdentityError> {
|
|
// Check if revoked
|
|
if self.revoked_credentials.contains_key(credential_id) {
|
|
return Ok(false);
|
|
}
|
|
|
|
let credential = self.credentials.get(credential_id)
|
|
.ok_or(IdentityError::CredentialNotFound)?;
|
|
|
|
// Check expiration
|
|
if let Some(expires) = credential.expiration_date {
|
|
if Utc::now() > expires {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
// In production, would verify cryptographic proof here
|
|
Ok(true)
|
|
}
|
|
|
|
pub fn check_capability(&self, holder: &Did, capability: Capability) -> bool {
|
|
self.capabilities.values().any(|cap| {
|
|
&cap.holder == holder
|
|
&& cap.capability == capability
|
|
&& Utc::now() < cap.expires_at
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum IdentityError {
|
|
DidExists,
|
|
DidNotFound,
|
|
IssuerNotFound,
|
|
CredentialNotFound,
|
|
CredentialRevoked,
|
|
CapabilityDenied,
|
|
SerializationError,
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Python CLI Skeleton
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
"""
|
|
VaultMesh Unified CLI
|
|
vm-cli - Gateway to all VaultMesh engines
|
|
"""
|
|
|
|
import click
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
import json
|
|
|
|
# ============================================================================
|
|
# Shared utilities
|
|
# ============================================================================
|
|
|
|
def emit_receipt(scroll: str, receipt_type: str, body: dict, tags: list[str]) -> dict:
|
|
"""Create and emit a receipt to the appropriate scroll."""
|
|
import hashlib
|
|
|
|
receipt = {
|
|
"type": receipt_type,
|
|
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
"tags": tags,
|
|
**body
|
|
}
|
|
|
|
# Compute root hash
|
|
receipt_json = json.dumps(receipt, sort_keys=True)
|
|
root_hash = f"blake3:{hashlib.blake3(receipt_json.encode()).hexdigest()}"
|
|
receipt["root_hash"] = root_hash
|
|
|
|
# Append to scroll
|
|
scroll_path = Path(f"receipts/{scroll}/{scroll}_events.jsonl")
|
|
scroll_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
with open(scroll_path, "a") as f:
|
|
f.write(json.dumps(receipt) + "\n")
|
|
|
|
# Update Merkle root (simplified)
|
|
root_file = Path(f"ROOT.{scroll}.txt")
|
|
root_file.write_text(root_hash)
|
|
|
|
return receipt
|
|
|
|
def load_receipts(scroll: str, filters: dict = None) -> list[dict]:
|
|
"""Load and optionally filter receipts from a scroll."""
|
|
scroll_path = Path(f"receipts/{scroll}/{scroll}_events.jsonl")
|
|
|
|
if not scroll_path.exists():
|
|
return []
|
|
|
|
receipts = []
|
|
with open(scroll_path) as f:
|
|
for line in f:
|
|
receipt = json.loads(line.strip())
|
|
|
|
# Apply filters
|
|
if filters:
|
|
match = True
|
|
for key, value in filters.items():
|
|
if key == "from_date":
|
|
if receipt.get("timestamp", "") < value:
|
|
match = False
|
|
elif key == "to_date":
|
|
if receipt.get("timestamp", "") > value:
|
|
match = False
|
|
elif key == "type":
|
|
if receipt.get("type") not in value:
|
|
match = False
|
|
elif receipt.get(key) != value:
|
|
match = False
|
|
|
|
if match:
|
|
receipts.append(receipt)
|
|
else:
|
|
receipts.append(receipt)
|
|
|
|
return receipts
|
|
|
|
# ============================================================================
|
|
# Main CLI Group
|
|
# ============================================================================
|
|
|
|
@click.group()
|
|
@click.version_option(version="0.1.0")
|
|
def cli():
|
|
"""VaultMesh Civilization Ledger CLI"""
|
|
pass
|
|
|
|
# ============================================================================
|
|
# Treasury Commands
|
|
# ============================================================================
|
|
|
|
@cli.group()
|
|
def treasury():
|
|
"""Treasury Engine - Financial operations"""
|
|
pass
|
|
|
|
@treasury.command("debit")
|
|
@click.option("--from", "from_account", required=True, help="Source account")
|
|
@click.option("--amount", required=True, type=float, help="Amount")
|
|
@click.option("--currency", default="EUR", help="Currency code")
|
|
@click.option("--memo", required=True, help="Transaction memo")
|
|
@click.option("--tags", default="", help="Comma-separated tags")
|
|
def treasury_debit(from_account: str, amount: float, currency: str, memo: str, tags: str):
|
|
"""Record a debit entry."""
|
|
entry_id = f"entry-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}"
|
|
|
|
receipt = emit_receipt(
|
|
scroll="treasury",
|
|
receipt_type="treasury_debit",
|
|
body={
|
|
"entry_id": entry_id,
|
|
"account": from_account,
|
|
"amount": amount,
|
|
"currency": currency,
|
|
"memo": memo,
|
|
},
|
|
tags=["treasury", "debit"] + (tags.split(",") if tags else [])
|
|
)
|
|
|
|
click.echo(f"✓ Debit recorded: {entry_id}")
|
|
click.echo(f" Amount: {amount} {currency}")
|
|
click.echo(f" Hash: {receipt['root_hash'][:20]}...")
|
|
|
|
@treasury.command("credit")
|
|
@click.option("--to", "to_account", required=True, help="Destination account")
|
|
@click.option("--amount", required=True, type=float, help="Amount")
|
|
@click.option("--currency", default="EUR", help="Currency code")
|
|
@click.option("--memo", required=True, help="Transaction memo")
|
|
@click.option("--tags", default="", help="Comma-separated tags")
|
|
def treasury_credit(to_account: str, amount: float, currency: str, memo: str, tags: str):
|
|
"""Record a credit entry."""
|
|
entry_id = f"entry-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}"
|
|
|
|
receipt = emit_receipt(
|
|
scroll="treasury",
|
|
receipt_type="treasury_credit",
|
|
body={
|
|
"entry_id": entry_id,
|
|
"account": to_account,
|
|
"amount": amount,
|
|
"currency": currency,
|
|
"memo": memo,
|
|
},
|
|
tags=["treasury", "credit"] + (tags.split(",") if tags else [])
|
|
)
|
|
|
|
click.echo(f"✓ Credit recorded: {entry_id}")
|
|
click.echo(f" Amount: {amount} {currency}")
|
|
click.echo(f" Hash: {receipt['root_hash'][:20]}...")
|
|
|
|
@treasury.command("query")
|
|
@click.option("--account", help="Filter by account")
|
|
@click.option("--from", "from_date", help="From date (ISO format)")
|
|
@click.option("--to", "to_date", help="To date (ISO format)")
|
|
@click.option("--type", "entry_type", help="Entry type (credit/debit)")
|
|
@click.option("--format", "output_format", default="table", type=click.Choice(["table", "json", "csv"]))
|
|
def treasury_query(account: str, from_date: str, to_date: str, entry_type: str, output_format: str):
|
|
"""Query treasury entries."""
|
|
filters = {}
|
|
if account:
|
|
filters["account"] = account
|
|
if from_date:
|
|
filters["from_date"] = from_date
|
|
if to_date:
|
|
filters["to_date"] = to_date
|
|
if entry_type:
|
|
filters["type"] = [f"treasury_{entry_type}"]
|
|
|
|
receipts = load_receipts("treasury", filters)
|
|
|
|
if output_format == "json":
|
|
click.echo(json.dumps(receipts, indent=2))
|
|
elif output_format == "csv":
|
|
if receipts:
|
|
click.echo(",".join(receipts[0].keys()))
|
|
for r in receipts:
|
|
click.echo(",".join(str(v) for v in r.values()))
|
|
else:
|
|
click.echo(f"Found {len(receipts)} entries:")
|
|
for r in receipts:
|
|
click.echo(f" {r.get('timestamp', 'N/A')[:19]} | {r.get('type', 'N/A'):20} | {r.get('amount', 'N/A'):>10} {r.get('currency', '')}")
|
|
|
|
# ============================================================================
|
|
# Mesh Commands
|
|
# ============================================================================
|
|
|
|
@cli.group()
|
|
def mesh():
|
|
"""Mesh Engine - Federation topology"""
|
|
pass
|
|
|
|
@mesh.command("node")
|
|
@click.argument("action", type=click.Choice(["list", "show", "join", "leave"]))
|
|
@click.option("--id", "node_id", help="Node identifier")
|
|
@click.option("--config", type=click.Path(exists=True), help="Node config file")
|
|
def mesh_node(action: str, node_id: str, config: str):
|
|
"""Manage mesh nodes."""
|
|
if action == "list":
|
|
receipts = load_receipts("mesh", {"type": ["mesh_node_join"]})
|
|
click.echo("Active nodes:")
|
|
for r in receipts:
|
|
click.echo(f" • {r.get('node_id', 'unknown')} ({r.get('node_type', 'unknown')})")
|
|
|
|
elif action == "show" and node_id:
|
|
receipts = load_receipts("mesh", {"node_id": f"did:vm:node:{node_id}"})
|
|
if receipts:
|
|
click.echo(json.dumps(receipts[-1], indent=2))
|
|
else:
|
|
click.echo(f"Node not found: {node_id}")
|
|
|
|
elif action == "join" and config:
|
|
with open(config) as f:
|
|
node_config = json.load(f)
|
|
|
|
receipt = emit_receipt(
|
|
scroll="mesh",
|
|
receipt_type="mesh_node_join",
|
|
body={
|
|
"node_id": node_config.get("node_id"),
|
|
"display_name": node_config.get("display_name"),
|
|
"node_type": node_config.get("node_type", "infrastructure"),
|
|
"endpoints_hash": "blake3:...", # Would compute from endpoints
|
|
},
|
|
tags=["mesh", "node", "join"]
|
|
)
|
|
|
|
click.echo(f"✓ Node joined: {node_config.get('node_id')}")
|
|
|
|
elif action == "leave" and node_id:
|
|
receipt = emit_receipt(
|
|
scroll="mesh",
|
|
receipt_type="mesh_node_leave",
|
|
body={
|
|
"node_id": f"did:vm:node:{node_id}",
|
|
"reason": "manual_leave",
|
|
},
|
|
tags=["mesh", "node", "leave"]
|
|
)
|
|
|
|
click.echo(f"✓ Node left: {node_id}")
|
|
|
|
@mesh.command("topology")
|
|
@click.option("--output", type=click.Path(), help="Output file")
|
|
def mesh_topology(output: str):
|
|
"""Show current mesh topology."""
|
|
node_receipts = load_receipts("mesh", {"type": ["mesh_node_join", "mesh_node_leave"]})
|
|
route_receipts = load_receipts("mesh", {"type": ["mesh_route_change"]})
|
|
|
|
# Build current state (simplified)
|
|
nodes = {}
|
|
for r in node_receipts:
|
|
node_id = r.get("node_id")
|
|
if r["type"] == "mesh_node_join":
|
|
nodes[node_id] = r
|
|
elif r["type"] == "mesh_node_leave" and node_id in nodes:
|
|
del nodes[node_id]
|
|
|
|
topology = {
|
|
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
"node_count": len(nodes),
|
|
"nodes": list(nodes.keys()),
|
|
}
|
|
|
|
if output:
|
|
with open(output, "w") as f:
|
|
json.dump(topology, f, indent=2)
|
|
click.echo(f"✓ Topology saved to {output}")
|
|
else:
|
|
click.echo(json.dumps(topology, indent=2))
|
|
|
|
# ============================================================================
|
|
# Identity Commands
|
|
# ============================================================================
|
|
|
|
@cli.group()
|
|
def identity():
|
|
"""Identity Engine - DIDs and credentials"""
|
|
pass
|
|
|
|
@identity.command("did")
|
|
@click.argument("action", type=click.Choice(["create", "show", "list", "rotate-key"]))
|
|
@click.option("--type", "did_type", type=click.Choice(["node", "human", "agent", "service"]))
|
|
@click.option("--id", "identifier", help="DID identifier")
|
|
@click.option("--controller", help="Controller DID")
|
|
def identity_did(action: str, did_type: str, identifier: str, controller: str):
|
|
"""Manage decentralized identifiers."""
|
|
if action == "create" and did_type and identifier:
|
|
did = f"did:vm:{did_type}:{identifier}"
|
|
|
|
receipt = emit_receipt(
|
|
scroll="identity",
|
|
receipt_type="identity_did_create",
|
|
body={
|
|
"did": did,
|
|
"did_type": did_type,
|
|
"controller": controller,
|
|
"initial_keys": [f"{did}#key-1"],
|
|
"did_document_hash": "blake3:...",
|
|
},
|
|
tags=["identity", "did", "create", did_type]
|
|
)
|
|
|
|
click.echo(f"✓ DID created: {did}")
|
|
|
|
elif action == "show" and identifier:
|
|
did = f"did:vm:node:{identifier}" # Simplified
|
|
receipts = load_receipts("identity", {"did": did})
|
|
if receipts:
|
|
click.echo(json.dumps(receipts[-1], indent=2))
|
|
else:
|
|
click.echo(f"DID not found: {did}")
|
|
|
|
elif action == "list":
|
|
receipts = load_receipts("identity", {"type": ["identity_did_create"]})
|
|
click.echo("Registered DIDs:")
|
|
for r in receipts:
|
|
click.echo(f" • {r.get('did', 'unknown')} ({r.get('did_type', 'unknown')})")
|
|
|
|
@identity.command("capability")
|
|
@click.argument("action", type=click.Choice(["grant", "check", "list", "revoke"]))
|
|
@click.option("--holder", help="Capability holder DID")
|
|
@click.option("--capability", type=click.Choice(["anchor", "storage", "compute", "oracle", "admin"]))
|
|
@click.option("--expires", help="Expiration date (ISO format)")
|
|
def identity_capability(action: str, holder: str, capability: str, expires: str):
|
|
"""Manage capability tokens."""
|
|
if action == "grant" and holder and capability:
|
|
cap_id = f"cap:vm:{datetime.utcnow().strftime('%Y-%m-%d')}:{capability}:{holder.split(':')[-1]}"
|
|
|
|
receipt = emit_receipt(
|
|
scroll="identity",
|
|
receipt_type="identity_capability_grant",
|
|
body={
|
|
"capability_id": cap_id,
|
|
"holder": holder,
|
|
"capability": capability,
|
|
"granted_by": "did:vm:node:portal-01", # Would come from context
|
|
"expires_at": expires or "2026-01-01T00:00:00Z",
|
|
},
|
|
tags=["identity", "capability", "grant", capability]
|
|
)
|
|
|
|
click.echo(f"✓ Capability granted: {cap_id}")
|
|
|
|
elif action == "check" and holder and capability:
|
|
receipts = load_receipts("identity", {"holder": holder, "capability": capability})
|
|
grants = [r for r in receipts if r["type"] == "identity_capability_grant"]
|
|
revokes = [r for r in receipts if r["type"] == "identity_capability_revoke"]
|
|
|
|
# Check if any valid grant exists
|
|
valid = False
|
|
for g in grants:
|
|
grant_id = g.get("capability_id")
|
|
if not any(r.get("capability_id") == grant_id for r in revokes):
|
|
expires = g.get("expires_at", "")
|
|
if expires > datetime.utcnow().isoformat():
|
|
valid = True
|
|
break
|
|
|
|
if valid:
|
|
click.echo(f"✓ {holder} HAS capability: {capability}")
|
|
else:
|
|
click.echo(f"✗ {holder} does NOT have capability: {capability}")
|
|
|
|
elif action == "list" and holder:
|
|
receipts = load_receipts("identity", {"holder": holder, "type": ["identity_capability_grant"]})
|
|
click.echo(f"Capabilities for {holder}:")
|
|
for r in receipts:
|
|
click.echo(f" • {r.get('capability', 'unknown')} (expires: {r.get('expires_at', 'never')[:10]})")
|
|
|
|
# ============================================================================
|
|
# Psi-Field Commands
|
|
# ============================================================================
|
|
|
|
@cli.group()
|
|
def psi():
|
|
"""Ψ-Field Engine - Alchemical consciousness"""
|
|
pass
|
|
|
|
@psi.command("phase")
|
|
@click.argument("action", type=click.Choice(["current", "history", "transition"]))
|
|
@click.option("--to", "to_phase", type=click.Choice(["nigredo", "albedo", "citrinitas", "rubedo"]))
|
|
@click.option("--trigger", help="Transition trigger description")
|
|
def psi_phase(action: str, to_phase: str, trigger: str):
|
|
"""Manage alchemical phases."""
|
|
if action == "current":
|
|
receipts = load_receipts("psi", {"type": ["psi_phase_transition"]})
|
|
if receipts:
|
|
last = receipts[-1]
|
|
phase = last.get("to_phase", "unknown")
|
|
symbols = {"nigredo": "🜁", "albedo": "🜄", "citrinitas": "🜆", "rubedo": "🜂"}
|
|
click.echo(f"Current Phase: {phase.upper()} {symbols.get(phase, '')}")
|
|
click.echo(f"Since: {last.get('timestamp', 'unknown')[:19]}")
|
|
else:
|
|
click.echo("Current Phase: NIGREDO 🜁 (initial)")
|
|
|
|
elif action == "history":
|
|
receipts = load_receipts("psi", {"type": ["psi_phase_transition"]})
|
|
click.echo("Phase History:")
|
|
for r in receipts:
|
|
click.echo(f" {r.get('timestamp', '')[:19]} | {r.get('from_phase', 'init'):10} → {r.get('to_phase', ''):10}")
|
|
|
|
elif action == "transition" and to_phase and trigger:
|
|
# Get current phase
|
|
receipts = load_receipts("psi", {"type": ["psi_phase_transition"]})
|
|
from_phase = receipts[-1].get("to_phase", "nigredo") if receipts else "nigredo"
|
|
|
|
receipt = emit_receipt(
|
|
scroll="psi",
|
|
receipt_type="psi_phase_transition",
|
|
body={
|
|
"transition_id": f"psi-trans-{datetime.utcnow().strftime('%Y-%m-%d-%H%M%S')}",
|
|
"from_phase": from_phase,
|
|
"to_phase": to_phase,
|
|
"trigger_type": "manual",
|
|
"trigger_description": trigger,
|
|
},
|
|
tags=["psi", "phase", from_phase, to_phase]
|
|
)
|
|
|
|
click.echo(f"✓ Phase transition: {from_phase} → {to_phase}")
|
|
|
|
@psi.command("transmute")
|
|
@click.option("--input", "input_ref", required=True, help="Input reference (e.g., INC-2025-12-001)")
|
|
@click.option("--input-type", required=True, type=click.Choice(["incident", "vulnerability", "drill"]))
|
|
@click.option("--title", required=True, help="Transmutation title")
|
|
def psi_transmute(input_ref: str, input_type: str, title: str):
|
|
"""Initiate a transmutation process."""
|
|
trans_id = f"psi-transmute-{datetime.utcnow().strftime('%Y-%m-%d-%H%M%S')}"
|
|
|
|
receipt = emit_receipt(
|
|
scroll="psi",
|
|
receipt_type="psi_transmutation",
|
|
body={
|
|
"transmutation_id": trans_id,
|
|
"title": title,
|
|
"input_type": input_type,
|
|
"input_reference": input_ref,
|
|
"status": "initiated",
|
|
"alchemical_phase_target": "citrinitas",
|
|
},
|
|
tags=["psi", "transmutation", input_type]
|
|
)
|
|
|
|
click.echo(f"✓ Transmutation initiated: {trans_id}")
|
|
click.echo(f" Input: {input_ref} ({input_type})")
|
|
click.echo(f" Target phase: CITRINITAS 🜆")
|
|
|
|
@psi.command("opus")
|
|
def psi_opus():
|
|
"""Display Magnum Opus status."""
|
|
# Get current phase
|
|
phase_receipts = load_receipts("psi", {"type": ["psi_phase_transition"]})
|
|
current_phase = phase_receipts[-1].get("to_phase", "nigredo") if phase_receipts else "nigredo"
|
|
|
|
# Get transmutation counts
|
|
trans_receipts = load_receipts("psi", {"type": ["psi_transmutation"]})
|
|
|
|
# Get resonance count
|
|
res_receipts = load_receipts("psi", {"type": ["psi_resonance"]})
|
|
|
|
symbols = {"nigredo": "🜁", "albedo": "🜄", "citrinitas": "🜆", "rubedo": "🜂"}
|
|
|
|
click.echo("=" * 50)
|
|
click.echo(" MAGNUM OPUS STATUS")
|
|
click.echo("=" * 50)
|
|
click.echo()
|
|
click.echo(f" Current Phase: {current_phase.upper()} {symbols.get(current_phase, '')}")
|
|
click.echo()
|
|
click.echo(" Phase Progress:")
|
|
phases = ["nigredo", "albedo", "citrinitas", "rubedo"]
|
|
for i, p in enumerate(phases):
|
|
marker = "●" if p == current_phase else ("○" if phases.index(p) > phases.index(current_phase) else "●")
|
|
click.echo(f" {marker} {p.upper():12} {symbols.get(p, '')}")
|
|
click.echo()
|
|
click.echo(f" Transmutations: {len(trans_receipts)}")
|
|
click.echo(f" Resonances: {len(res_receipts)}")
|
|
click.echo()
|
|
click.echo("=" * 50)
|
|
|
|
# ============================================================================
|
|
# Guardian Commands (bridge to existing)
|
|
# ============================================================================
|
|
|
|
@cli.group()
|
|
def guardian():
|
|
"""Guardian Engine - Anchor and sentinel"""
|
|
pass
|
|
|
|
@guardian.command("anchor-status")
|
|
def guardian_anchor_status():
|
|
"""Show current anchor status."""
|
|
receipts = load_receipts("guardian", {"type": ["anchor_success", "anchor_failure"]})
|
|
|
|
if receipts:
|
|
last = receipts[-1]
|
|
click.echo(f"Last Anchor: {last.get('timestamp', 'unknown')[:19]}")
|
|
click.echo(f"Status: {last.get('type', 'unknown')}")
|
|
click.echo(f"Root: {last.get('root_hash', 'unknown')[:30]}...")
|
|
else:
|
|
click.echo("No anchor events recorded")
|
|
|
|
@guardian.command("anchor-now")
|
|
@click.option("--scroll", multiple=True, help="Scrolls to anchor (default: all)")
|
|
def guardian_anchor_now(scroll: tuple):
|
|
"""Trigger immediate anchor cycle."""
|
|
scrolls = list(scroll) if scroll else ["drills", "compliance", "treasury", "mesh", "identity", "psi"]
|
|
|
|
click.echo(f"Anchoring scrolls: {', '.join(scrolls)}")
|
|
|
|
# Would call actual Guardian anchor cycle here
|
|
receipt = emit_receipt(
|
|
scroll="guardian",
|
|
receipt_type="anchor_success",
|
|
body={
|
|
"anchor_id": f"anchor-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}",
|
|
"scrolls_anchored": scrolls,
|
|
"backend": "local", # Would be ots/ethereum/bitcoin in production
|
|
},
|
|
tags=["guardian", "anchor", "manual"]
|
|
)
|
|
|
|
click.echo(f"✓ Anchor cycle complete")
|
|
click.echo(f" ID: {receipt.get('anchor_id')}")
|
|
|
|
# ============================================================================
|
|
# Entry Point
|
|
# ============================================================================
|
|
|
|
if __name__ == "__main__":
|
|
cli()
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Directory Structure
|
|
|
|
```
|
|
vaultmesh/
|
|
├── Cargo.toml # Rust workspace
|
|
├── pyproject.toml # Python CLI
|
|
│
|
|
├── vaultmesh-core/ # Shared Rust types
|
|
│ └── src/
|
|
│ ├── lib.rs
|
|
│ ├── receipt.rs
|
|
│ ├── did.rs
|
|
│ └── hash.rs
|
|
│
|
|
├── vaultmesh-treasury/ # Treasury engine
|
|
│ └── src/lib.rs
|
|
│
|
|
├── vaultmesh-mesh/ # Mesh engine
|
|
│ └── src/lib.rs
|
|
│
|
|
├── vaultmesh-identity/ # Identity engine
|
|
│ └── src/lib.rs
|
|
│
|
|
├── vaultmesh-offsec/ # OffSec engine
|
|
│ └── src/lib.rs
|
|
│
|
|
├── vaultmesh-observability/ # Observability engine
|
|
│ └── src/lib.rs
|
|
│
|
|
├── vaultmesh-automation/ # Automation engine
|
|
│ └── src/lib.rs
|
|
│
|
|
├── vaultmesh-psi/ # Ψ-Field engine
|
|
│ └── src/lib.rs
|
|
│
|
|
├── vaultmesh-guardian/ # Guardian (existing)
|
|
│ └── src/lib.rs
|
|
│
|
|
├── cli/ # Python CLI
|
|
│ ├── __init__.py
|
|
│ └── vm_cli.py
|
|
│
|
|
├── receipts/ # Receipt storage
|
|
│ ├── drills/
|
|
│ ├── compliance/
|
|
│ ├── guardian/
|
|
│ ├── treasury/
|
|
│ ├── mesh/
|
|
│ ├── offsec/
|
|
│ ├── identity/
|
|
│ ├── observability/
|
|
│ ├── automation/
|
|
│ └── psi/
|
|
│
|
|
├── cases/ # Case artifacts
|
|
│ ├── drills/
|
|
│ ├── treasury/
|
|
│ ├── offsec/
|
|
│ ├── identity/
|
|
│ └── psi/
|
|
│
|
|
└── ROOT.*.txt # Merkle roots
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Summary
|
|
|
|
This document provides:
|
|
|
|
1. **Rust Core Types** (`vaultmesh-core`)
|
|
- `Receipt<T>` — generic receipt wrapper
|
|
- `Scroll` enum — all 10 scrolls with paths
|
|
- `Did` — decentralized identifier type
|
|
- `VmHash` — blake3 hash with prefix
|
|
- `merkle_root()` — Merkle tree computation
|
|
|
|
2. **Treasury Engine** (`vaultmesh-treasury`)
|
|
- `Account`, `Entry`, `SettlementContract`, `SettlementState`
|
|
- Full settlement workflow with signatures
|
|
- Receipt generation for credits, debits, settlements
|
|
|
|
3. **Identity Engine** (`vaultmesh-identity`)
|
|
- `DidDocument`, `VerifiableCredential`, `CapabilityToken`
|
|
- DID creation, credential issuance, capability checks
|
|
- Authentication events
|
|
|
|
4. **Python CLI** (`vm-cli`)
|
|
- Unified CLI covering Treasury, Mesh, Identity, Ψ-Field, Guardian
|
|
- `emit_receipt()` and `load_receipts()` utilities
|
|
- Complete command structure matching engine specs
|
|
|
|
5. **Directory Structure**
|
|
- Rust workspace layout
|
|
- Receipt and case storage paths
|
|
- Merkle root file locations
|
|
|
|
All engines now have both specifications and implementation scaffolds following the Eternal Pattern.
|