#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StackManifest {
pub name: String,
pub version: String,
pub description: Option<String>,
pub extends: Option<String>,
pub require_claims: Vec<StackClaim>,
pub invariant_claims: Vec<StackClaim>,
pub ensure_claims: Vec<StackClaim>,
pub rescue_blocks: Vec<StackRescue>,
pub thresholds: HashMap<String, f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StackClaim {
pub id: String,
pub description: String,
pub check: String,
pub timeout: Option<u64>,
pub threshold: Option<StackThreshold>,
pub metric_pattern: Option<String>,
pub metric_on_no_match: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StackThreshold {
pub metric: String,
pub op: String,
pub value: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StackRescue {
pub for_clause: String,
pub strategy: String,
pub command: Option<String>,
pub guidance: Option<String>,
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StackTrustRecord {
pub sha256: String,
pub trusted_at: String,
pub trusted_by: String,
pub commands_reviewed: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CommandRestriction {
PipeToShell,
BacktickSubstitution,
DollarSubstitution,
NetworkFetchExecute,
RedirectToExecutable,
}
impl std::fmt::Display for CommandRestriction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CommandRestriction::PipeToShell => write!(f, "pipe to shell (| sh/bash)"),
CommandRestriction::BacktickSubstitution => write!(f, "backtick substitution"),
CommandRestriction::DollarSubstitution => write!(f, "$() command substitution"),
CommandRestriction::NetworkFetchExecute => write!(f, "network fetch + execute"),
CommandRestriction::RedirectToExecutable => write!(f, "redirect to executable"),
}
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn validate_command(cmd: &str) -> Option<CommandRestriction> {
if cmd.contains('`') {
return Some(CommandRestriction::BacktickSubstitution);
}
if cmd.contains("$(") {
return Some(CommandRestriction::DollarSubstitution);
}
let pipe_shell = regex::Regex::new(r"\|\s*(sh|bash|zsh|dash)\b").ok();
if let Some(re) = &pipe_shell {
if re.is_match(cmd) {
return Some(CommandRestriction::PipeToShell);
}
}
let net_exec_patterns = [
r"(curl|wget)\s+.*\|\s*(sh|bash|python|perl|ruby)",
r"(curl|wget)\s+-[^\s]*O\s*-\s*.*\|\s*",
];
for pat in &net_exec_patterns {
if let Ok(re) = regex::Regex::new(pat) {
if re.is_match(cmd) {
return Some(CommandRestriction::NetworkFetchExecute);
}
}
}
if cmd.contains("chmod +x") && (cmd.contains("curl") || cmd.contains("wget")) {
return Some(CommandRestriction::RedirectToExecutable);
}
None
}
impl StackManifest {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn parse(content: &str) -> Result<Self> {
let table: toml::Table = content.parse().context("Failed to parse .dbc-stack.toml")?;
let stack = table
.get("stack")
.and_then(|v| v.as_table())
.context("Missing [stack] section")?;
let name = stack
.get("name")
.and_then(|v| v.as_str())
.context("Missing stack.name")?
.to_string();
let version = stack
.get("version")
.and_then(|v| v.as_str())
.unwrap_or("0.1.0")
.to_string();
let description = stack.get("description").and_then(|v| v.as_str()).map(String::from);
let extends = stack.get("extends").and_then(|v| v.as_str()).map(String::from);
let require_claims = Self::parse_claims(&table, "require")?;
let invariant_claims = Self::parse_claims(&table, "invariant")?;
let ensure_claims = Self::parse_claims(&table, "ensure")?;
let rescue_blocks = Self::parse_rescues(&table)?;
let thresholds = table
.get("thresholds")
.and_then(|v| v.as_table())
.map(|t| {
t.iter()
.filter_map(|(k, v)| v.as_float().map(|f| (k.clone(), f)))
.collect()
})
.unwrap_or_default();
Ok(Self {
name,
version,
description,
extends,
require_claims,
invariant_claims,
ensure_claims,
rescue_blocks,
thresholds,
})
}
fn parse_claims(table: &toml::Table, kind: &str) -> Result<Vec<StackClaim>> {
let arr = match table.get(kind) {
Some(toml::Value::Array(a)) => a,
_ => return Ok(vec![]),
};
arr.iter()
.map(|entry| {
let t = entry.as_table().context("Claim must be a table")?;
let id = t.get("id").and_then(|v| v.as_str()).context("Missing claim id")?.to_string();
let description = t.get("description").and_then(|v| v.as_str()).unwrap_or("").to_string();
let check = t.get("check").and_then(|v| v.as_str()).context("Missing claim check command")?.to_string();
let timeout = t.get("timeout").and_then(|v| v.as_integer()).map(|v| v as u64);
let metric_pattern = t.get("metric_pattern").and_then(|v| v.as_str()).map(String::from);
let metric_on_no_match = t.get("metric_on_no_match").and_then(|v| v.as_str()).map(String::from);
let threshold = t.get("threshold").and_then(|v| v.as_table()).map(|th| {
StackThreshold {
metric: th.get("metric").and_then(|v| v.as_str()).unwrap_or("").to_string(),
op: th.get("op").and_then(|v| v.as_str()).unwrap_or("Gte").to_string(),
value: th.get("value").and_then(|v| v.as_float()).unwrap_or(0.0),
}
});
Ok(StackClaim { id, description, check, timeout, threshold, metric_pattern, metric_on_no_match })
})
.collect()
}
fn parse_rescues(table: &toml::Table) -> Result<Vec<StackRescue>> {
let arr = match table.get("rescue") {
Some(toml::Value::Array(a)) => a,
_ => return Ok(vec![]),
};
arr.iter()
.map(|entry| {
let t = entry.as_table().context("Rescue must be a table")?;
Ok(StackRescue {
for_clause: t.get("for_clause").and_then(|v| v.as_str()).unwrap_or("").to_string(),
strategy: t.get("strategy").and_then(|v| v.as_str()).unwrap_or("manual").to_string(),
command: t.get("command").and_then(|v| v.as_str()).map(String::from),
guidance: t.get("guidance").and_then(|v| v.as_str()).map(String::from),
description: t.get("description").and_then(|v| v.as_str()).map(String::from),
})
})
.collect()
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn validate_commands(&self) -> Vec<(String, CommandRestriction)> {
let mut violations = Vec::new();
let all_claims = self.require_claims.iter()
.chain(self.invariant_claims.iter())
.chain(self.ensure_claims.iter());
for claim in all_claims {
if let Some(restriction) = validate_command(&claim.check) {
violations.push((claim.id.clone(), restriction));
}
}
for rescue in &self.rescue_blocks {
if let Some(cmd) = &rescue.command {
if let Some(restriction) = validate_command(cmd) {
violations.push((rescue.for_clause.clone(), restriction));
}
}
}
violations
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn to_contract_clauses(&self) -> Vec<ContractClause> {
let mut clauses = Vec::new();
let stack_name = self.name.clone();
for claim in &self.require_claims {
clauses.push(stack_claim_to_clause(claim, ClauseKind::Require, &stack_name));
}
for claim in &self.invariant_claims {
clauses.push(stack_claim_to_clause(claim, ClauseKind::Invariant, &stack_name));
}
for claim in &self.ensure_claims {
clauses.push(stack_claim_to_clause(claim, ClauseKind::Ensure, &stack_name));
}
clauses
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn resolve_base_claims(&self, config: &DbcConfig) -> Vec<ContractClause> {
let base_claims = match self.extends.as_deref() {
Some("universal") => claims_for_profile(&ContractProfile::Universal, config),
Some("rust") => claims_for_profile(&ContractProfile::Rust, config),
Some("pmat") => claims_for_profile(&ContractProfile::Pmat, config),
_ => Vec::new(),
};
let stack_claims = self.to_contract_clauses();
let stack_ids: std::collections::HashSet<&str> =
stack_claims.iter().map(|c| c.id.as_str()).collect();
let mut merged: Vec<ContractClause> = base_claims
.into_iter()
.filter(|c| !stack_ids.contains(c.id.as_str()))
.collect();
merged.extend(stack_claims);
merged
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn claim_count(&self) -> usize {
self.require_claims.len() + self.invariant_claims.len() + self.ensure_claims.len()
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn content_hash(content: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(content.as_bytes());
format!("{:x}", hasher.finalize())
}
}
fn stack_claim_to_clause(claim: &StackClaim, kind: ClauseKind, stack_name: &str) -> ContractClause {
let threshold = claim.threshold.as_ref().map(|t| {
let op = match t.op.as_str() {
"Gte" | ">=" => ThresholdOp::Gte,
"Lte" | "<=" => ThresholdOp::Lte,
"Gt" | ">" => ThresholdOp::Gt,
"Lt" | "<" => ThresholdOp::Lt,
"Eq" | "==" => ThresholdOp::Eq,
_ => ThresholdOp::Gte,
};
ClauseThreshold::Numeric {
metric: t.metric.clone(),
op,
value: t.value,
}
});
ContractClause {
id: claim.id.clone(),
kind,
description: claim.description.clone(),
falsification_method: FalsificationMethod::MetaFalsification, threshold,
blocking: true,
source: ClauseSource::Stack {
manifest_name: stack_name.to_string(),
},
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn load_trust_records(project_path: &Path) -> HashMap<String, StackTrustRecord> {
let trust_path = project_path.join(".pmat-work").join("trusted-stacks.json");
if let Ok(content) = std::fs::read_to_string(&trust_path) {
serde_json::from_str(&content).unwrap_or_default()
} else {
HashMap::new()
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn save_trust_records(project_path: &Path, records: &HashMap<String, StackTrustRecord>) -> Result<()> {
let trust_dir = project_path.join(".pmat-work");
std::fs::create_dir_all(&trust_dir)?;
let trust_path = trust_dir.join("trusted-stacks.json");
let json = serde_json::to_string_pretty(records)?;
std::fs::write(&trust_path, json)?;
Ok(())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn is_manifest_trusted(project_path: &Path, manifest_path: &str, content: &str) -> bool {
let records = load_trust_records(project_path);
if let Some(record) = records.get(manifest_path) {
record.sha256 == StackManifest::content_hash(content)
} else {
false
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn execute_stack_check(cmd: &str, project_path: &Path, timeout_secs: u64) -> Result<(bool, String)> {
let parts: Vec<&str> = cmd.split("&&").collect();
let mut combined_output = String::new();
for part in parts {
let trimmed = part.trim();
let args: Vec<&str> = trimmed.split_whitespace().collect();
if args.is_empty() {
continue;
}
let result = std::process::Command::new(args[0])
.args(&args[1..])
.current_dir(project_path)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.output();
match result {
Ok(output) => {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
combined_output.push_str(&stdout);
combined_output.push_str(&stderr);
if !output.status.success() {
return Ok((false, combined_output));
}
}
Err(e) => {
return Ok((false, format!("Command '{}' failed to execute: {}", args[0], e)));
}
}
}
let _ = timeout_secs; Ok((true, combined_output))
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn extract_metric(output: &str, pattern: &str) -> Result<Option<f64>> {
let re = regex::Regex::new(pattern).context("Invalid metric_pattern regex")?;
if let Some(captures) = re.captures(output) {
if let Some(m) = captures.get(1) {
let val: f64 = m.as_str().parse().context("Captured value is not a number")?;
return Ok(Some(val));
}
}
Ok(None)
}