use crate::marketplace::ownership::{OwnershipMap, OwnershipTarget};
use crate::pipeline_engine::guard::{GuardAction, GuardSet, GuardViolation};
use crate::pipeline_engine::pass::{Pass, PassContext, PassResult, PassType};
use crate::resolver::TemplateResolver;
use crate::utils::error::{Error, Result};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::path::PathBuf;
use std::time::Instant;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmissionRule {
pub name: String,
pub template_path: PathBuf,
#[serde(default)]
pub inline_template: Option<String>,
pub output_pattern: String,
pub binding_key: String,
pub iterate: bool,
pub skip_empty: bool,
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmissionReceipt {
pub files: Vec<EmittedFile>,
pub duration_ms: u64,
pub determinism_verified: bool,
pub idempotence_verified: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmittedFile {
pub path: PathBuf,
pub hash: String,
pub size: usize,
pub rule_name: String,
}
#[derive(Debug, Clone)]
pub struct EmissionPass {
rules: Vec<EmissionRule>,
guards: GuardSet,
enable_determinism_check: bool,
receipt: Option<EmissionReceipt>,
ownership_map: Option<OwnershipMap>,
}
impl EmissionPass {
pub fn new() -> Self {
Self {
rules: Vec::new(),
guards: GuardSet::default_v26(),
enable_determinism_check: true,
receipt: None,
ownership_map: None,
}
}
pub fn receipt(&self) -> Option<&EmissionReceipt> {
self.receipt.as_ref()
}
pub fn add_rule(&mut self, rule: EmissionRule) {
self.rules.push(rule);
}
pub fn with_rules(mut self, rules: Vec<EmissionRule>) -> Self {
self.rules = rules;
self
}
pub fn extend_with_pack_templates(
&mut self, templates: &[crate::pack_resolver::TemplateDef],
) -> Result<()> {
for template in templates {
let path_str = template.path.display().to_string();
let parts: Vec<&str> = path_str.split('/').collect();
let pack_id = parts.first().unwrap_or(&"unknown");
let template_name = parts
.get(2)
.and_then(|s| s.strip_suffix(".tera"))
.unwrap_or("unknown");
let rule_name = format!("pack:{}::{}", pack_id, template_name);
let binding_key = format!(
"{}{}",
template_name,
if template_name.ends_with('y') {
"ies"
} else {
"s"
}
);
let output_pattern = "{% if name %}{{ name | lower }}{% endif %}".to_string();
self.rules.push(EmissionRule {
name: rule_name,
template_path: PathBuf::from(&template.path),
inline_template: Some(template.content.clone()),
output_pattern,
binding_key,
iterate: true,
skip_empty: true,
description: Some(format!("Pack-contributed template from {}", path_str)),
});
}
Ok(())
}
pub fn with_guards(mut self, guards: GuardSet) -> Self {
self.guards = guards;
self
}
pub fn with_determinism_check(mut self, enabled: bool) -> Self {
self.enable_determinism_check = enabled;
self
}
pub fn with_ownership_map(mut self, map: OwnershipMap) -> Self {
self.ownership_map = Some(map);
self
}
fn check_ownership_conflict(
&self, artifact_path: &std::path::Path, emitting_pack: &str,
) -> Result<()> {
let ownership_map = match &self.ownership_map {
Some(map) => map,
None => return Ok(()), };
let target = OwnershipTarget::FilePath(artifact_path.to_path_buf());
let declarations = match ownership_map.get_declarations(&target) {
Some(decls) => decls,
None => return Ok(()), };
for declaration in declarations {
if declaration.owner_pack != emitting_pack {
match declaration.class {
crate::marketplace::ownership::OwnershipClass::Exclusive => {
return Err(Error::new(&format!(
"Ownership conflict: '{}' claims exclusive ownership of '{}', but '{}' is attempting to emit to it",
declaration.owner_pack,
artifact_path.display(),
emitting_pack,
)));
}
crate::marketplace::ownership::OwnershipClass::ForbiddenOverlap => {
return Err(Error::new(&format!(
"Forbidden overlap: '{}' and '{}' both target '{}'",
declaration.owner_pack,
emitting_pack,
artifact_path.display(),
)));
}
crate::marketplace::ownership::OwnershipClass::Mergeable
| crate::marketplace::ownership::OwnershipClass::Overlay => {
}
}
}
}
Ok(())
}
fn extract_pack_id(rule_name: &str) -> &str {
if let Some(rest) = rule_name.strip_prefix("pack:") {
rest.split("::").next().unwrap_or("core")
} else {
"core"
}
}
fn verify_determinism(
&self, _ctx: &PassContext<'_>, path: &std::path::Path, content: &str,
) -> Result<()> {
if !self.enable_determinism_check {
return Ok(());
}
let non_deterministic_patterns = [
(
"timestamp",
vec![
"now()",
"Utc::now()",
"Local::now()",
"SystemTime::now()",
"chrono::Utc::now",
"std::time::SystemTime::now",
"Instant::now()",
"OffsetDateTime::now",
"current_time()",
"get_timestamp()",
],
),
(
"random",
vec![
"rand()",
"random()",
"uuid()",
"Uuid::new_v4()",
"thread_rng()",
"rand::random",
"OsRng",
"RandomNumberGenerator",
"rand::thread_rng",
],
),
(
"process",
vec![
"pid()",
"getpid()",
"thread_id()",
"std::process::id()",
"ThreadId::current()",
"current_thread_id()",
"std::thread::current().id()",
"thread::current().id()",
],
),
(
"network",
vec![
"reqwest::",
"hyper::",
"tokio::net::",
"std::net::TcpStream",
"UdpSocket",
"fetch(",
"http_get(",
"download(",
],
),
(
"filesystem_metadata",
vec![
".metadata()",
"fs::metadata",
"DirEntry",
"modified()",
"accessed()",
"created()",
"file_modified_time",
],
),
(
"ordering",
vec![
"HashMap::",
"std::collections::HashMap",
"use std::collections::HashMap",
"HashSet::",
"use std::collections::HashSet",
],
),
(
"grouping",
vec![
".group_by(",
"GROUP BY",
"groupBy",
"aggregate(",
"AGGREGATE",
],
),
(
"joins",
vec![
".join(",
"JOIN ",
"INNER JOIN",
"LEFT JOIN",
"OUTER JOIN",
"RIGHT JOIN",
"CROSS JOIN",
],
),
];
for (category, patterns) in &non_deterministic_patterns {
for pattern in patterns {
if content.contains(pattern) {
return Err(Error::new(&format!(
"🚨 Non-Deterministic Pattern Detected in μ₃:emission\n\n\
μ₃:emission STOPPED THE LINE (Andon Protocol)\n\n\
File '{}' contains non-deterministic {} pattern: '{}'\n\n\
μ₃ output must be deterministic for reproducible builds.\n\n\
Fix: Remove {} pattern or use deterministic alternative.\n\n\
For ordering: Use BTreeMap/BTreeSet instead of HashMap/HashSet.\n\
For timestamps: Use fixed epoch or SOURCE_DATE_EPOCH.\n\
For random: Use fixed seed with StdRng::seed_from_u64.\n\
For grouping/joins: Use pure SELECT extraction in μ₂.",
path.display(),
category,
pattern,
pattern
)));
}
}
}
Ok(())
}
fn verify_idempotence(
&self, ctx: &PassContext<'_>, rule: &EmissionRule, template_content: &str,
item_context: &serde_json::Value,
) -> Result<()> {
if !self.enable_determinism_check {
return Ok(());
}
let (_, content1) = self.render_file(ctx, rule, template_content, item_context)?;
let (_, content2) = self.render_file(ctx, rule, template_content, item_context)?;
if content1 != content2 {
return Err(Error::new(&format!(
"🚨 Non-Idempotent Rendering Detected in μ₃:emission\n\n\
μ₃:emission STOPPED THE LINE (Andon Protocol)\n\n\
Rule '{}' produced different outputs on subsequent renders.\n\n\
μ₃ must be idempotent: μ∘μ = μ\n\n\
Fix: Remove stateful operations from template or filter.",
rule.name
)));
}
Ok(())
}
fn verify_ordered_iteration(&self, _ctx: &PassContext<'_>) -> Result<()> {
Ok(())
}
fn record_file_hash(&self, _path: &PathBuf, content: &str) -> String {
format!("{:x}", Sha256::digest(content.as_bytes()))
}
fn render_file(
&self, ctx: &PassContext<'_>, rule: &EmissionRule, template_content: &str,
item_context: &serde_json::Value,
) -> Result<(PathBuf, String)> {
let mut tera = tera::Tera::default();
tera.add_raw_template("template", template_content)
.map_err(|e| Error::new(&format!("Template parse error in '{}': {}", rule.name, e)))?;
let mut context = tera::Context::new();
context.insert("project_name", &ctx.project_name);
context.insert("project_version", &ctx.project_version);
for (key, value) in &ctx.bindings {
context.insert(key, value);
}
if rule.iterate {
context.insert("item", item_context);
if let Some(obj) = item_context.as_object() {
for (key, value) in obj {
context.insert(key, value);
}
}
}
let content = tera
.render("template", &context)
.map_err(|e| Error::new(&format!("Template render error in '{}': {}", rule.name, e)))?;
let output_path_str = tera
.render_str(&rule.output_pattern, &context)
.map_err(|e| {
Error::new(&format!(
"Output path template error in '{}': {}",
rule.name, e
))
})?;
let output_path = PathBuf::from(output_path_str.trim());
Ok((output_path, content))
}
fn execute_rule(
&self, ctx: &PassContext<'_>, rule: &EmissionRule, emitted_files: &mut Vec<EmittedFile>,
) -> Result<Vec<PathBuf>> {
let mut generated_files = Vec::new();
let template_content = if let Some(ref inline) = rule.inline_template {
inline.clone()
} else {
let template_path_str = rule.template_path.to_string_lossy();
if TemplateResolver::is_pack_reference(&template_path_str) {
tracing::info!("Loading template from pack cache: {}", template_path_str);
let resolver = TemplateResolver::new()?;
let template_source = resolver.resolve(&template_path_str)?;
tracing::info!(
"Loaded template from pack cache: {} -> {}",
template_path_str,
template_source.full_path.display()
);
template_source.content
} else {
let template_path = ctx.base_path.join(&rule.template_path);
std::fs::read_to_string(&template_path).map_err(|e| {
Error::new(&format!(
"Failed to read template '{}': {}",
template_path.display(),
e
))
})?
}
};
let binding = ctx.bindings.get(&rule.binding_key).cloned();
if rule.iterate {
if let Some(serde_json::Value::Array(items)) = &binding {
if items.is_empty() && rule.skip_empty {
return Ok(generated_files);
}
for item in items {
self.verify_idempotence(ctx, rule, &template_content, item)?;
let (output_path, content) =
self.render_file(ctx, rule, &template_content, item)?;
self.verify_determinism(ctx, &output_path, &content)?;
let violations = self.guards.check(&output_path, &content);
self.handle_violations(&violations)?;
let emitting_pack = Self::extract_pack_id(&rule.name);
self.check_ownership_conflict(&output_path, emitting_pack)?;
let hash = self.record_file_hash(&output_path, &content);
emitted_files.push(EmittedFile {
path: output_path.clone(),
hash,
size: content.len(),
rule_name: rule.name.clone(),
});
let full_output_path = ctx.output_dir.join(&output_path);
self.write_output(&full_output_path, &content)?;
generated_files.push(output_path);
}
}
} else {
let context_value = binding.unwrap_or(serde_json::Value::Null);
if context_value.is_null() && rule.skip_empty {
return Ok(generated_files);
}
self.verify_idempotence(ctx, rule, &template_content, &context_value)?;
let (output_path, content) =
self.render_file(ctx, rule, &template_content, &context_value)?;
self.verify_determinism(ctx, &output_path, &content)?;
let violations = self.guards.check(&output_path, &content);
self.handle_violations(&violations)?;
let emitting_pack = Self::extract_pack_id(&rule.name);
self.check_ownership_conflict(&output_path, emitting_pack)?;
let hash = self.record_file_hash(&output_path, &content);
emitted_files.push(EmittedFile {
path: output_path.clone(),
hash,
size: content.len(),
rule_name: rule.name.clone(),
});
let full_output_path = ctx.output_dir.join(&output_path);
self.write_output(&full_output_path, &content)?;
generated_files.push(output_path);
}
Ok(generated_files)
}
fn handle_violations(&self, violations: &[GuardViolation]) -> Result<()> {
for violation in violations {
match violation.action {
GuardAction::Reject => {
return Err(Error::new(&format!(
"Guard '{}' violation: {}",
violation.guard_name, violation.message
)));
}
GuardAction::Warn => {
eprintln!("WARNING [{}]: {}", violation.guard_name, violation.message);
}
GuardAction::RequireApproval => {
return Err(Error::new(&format!(
"Guard '{}' requires approval: {}",
violation.guard_name, violation.message
)));
}
}
}
Ok(())
}
fn write_output(&self, path: &std::path::Path, content: &str) -> Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
Error::new(&format!(
"Failed to create directory '{}': {}",
parent.display(),
e
))
})?;
}
std::fs::write(path, content)
.map_err(|e| Error::new(&format!("Failed to write file '{}': {}", path.display(), e)))
}
}
impl Default for EmissionPass {
fn default() -> Self {
Self::new()
}
}
impl Pass for EmissionPass {
fn pass_type(&self) -> PassType {
PassType::Emission
}
fn name(&self) -> &str {
"μ₃:emission"
}
fn execute(&self, ctx: &mut PassContext<'_>) -> Result<PassResult> {
let start = Instant::now();
let mut all_generated = Vec::new();
let mut emitted_files = Vec::new();
self.verify_ordered_iteration(ctx)?;
for rule in &self.rules {
let generated = self.execute_rule(ctx, rule, &mut emitted_files)?;
all_generated.extend(generated);
}
ctx.generated_files.extend(all_generated.clone());
let duration = start.elapsed();
let _receipt = EmissionReceipt {
files: emitted_files,
duration_ms: duration.as_millis() as u64,
determinism_verified: self.enable_determinism_check,
idempotence_verified: self.enable_determinism_check,
};
Ok(PassResult::success()
.with_files(all_generated)
.with_duration(duration))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::Graph;
use crate::marketplace::ownership::{OwnershipClass, OwnershipDeclaration};
use tempfile::TempDir;
#[test]
fn test_emission_pass_empty() {
let graph = Graph::new().unwrap();
let pass = EmissionPass::new();
let temp_dir = TempDir::new().unwrap();
let mut ctx = PassContext::new(
&graph,
temp_dir.path().to_path_buf(),
temp_dir.path().join("output"),
);
let result = pass.execute(&mut ctx).unwrap();
assert!(result.success);
}
#[test]
fn test_emission_single_file() {
let graph = Graph::new().unwrap();
let temp_dir = TempDir::new().unwrap();
let template_dir = temp_dir.path().join("templates");
std::fs::create_dir_all(&template_dir).unwrap();
std::fs::write(
template_dir.join("domain.ttl.tera"),
"@prefix ex: <http://example.org/> .\n# Generated for {{ project_name }}\n",
)
.unwrap();
let output_dir = temp_dir.path().join("ontology").join("output");
std::fs::create_dir_all(&output_dir).unwrap();
let mut pass = EmissionPass::new();
pass.guards = GuardSet::new();
pass.add_rule(EmissionRule {
inline_template: None,
name: "generate-ontology".to_string(),
template_path: PathBuf::from("templates/domain.ttl.tera"),
output_pattern: "domain.ttl".to_string(),
binding_key: "data".to_string(),
iterate: false,
skip_empty: false,
description: None,
});
let mut ctx = PassContext::new(&graph, temp_dir.path().to_path_buf(), output_dir.clone())
.with_project("TestProject".to_string(), "1.0.0".to_string());
let result = pass.execute(&mut ctx).unwrap();
assert!(result.success);
assert_eq!(result.files_generated.len(), 1);
assert!(output_dir.join("domain.ttl").exists());
}
#[test]
fn test_emission_with_iteration() {
let graph = Graph::new().unwrap();
let temp_dir = TempDir::new().unwrap();
let template_dir = temp_dir.path().join("templates");
std::fs::create_dir_all(&template_dir).unwrap();
std::fs::write(
template_dir.join("entity.rs.tera"),
"pub struct {{ name }} {}",
)
.unwrap();
let output_dir = temp_dir.path().join("output");
let mut pass = EmissionPass::new();
pass.guards = GuardSet::new();
pass.add_rule(EmissionRule {
name: "generate-entities".to_string(),
template_path: PathBuf::from("templates/entity.rs.tera"),
output_pattern: "{{ name | lower }}.rs".to_string(),
binding_key: "entities".to_string(),
inline_template: None,
iterate: true,
skip_empty: true,
description: None,
});
let mut ctx = PassContext::new(&graph, temp_dir.path().to_path_buf(), output_dir.clone());
ctx.bindings.insert(
"entities".to_string(),
serde_json::json!([
{"name": "User"},
{"name": "Order"}
]),
);
let result = pass.execute(&mut ctx).unwrap();
assert!(result.success);
assert_eq!(result.files_generated.len(), 2);
}
#[test]
fn test_extract_pack_id_pack_rule() {
assert_eq!(
EmissionPass::extract_pack_id("pack:serde-utils::serialize"),
"serde-utils"
);
}
#[test]
fn test_extract_pack_id_non_pack_rule() {
assert_eq!(EmissionPass::extract_pack_id("generate-ontology"), "core");
}
#[test]
fn test_ownership_conflict_no_map() {
let pass = EmissionPass::new();
let path = std::path::Path::new("src/main.rs");
assert!(pass.check_ownership_conflict(path, "pack-a").is_ok());
}
#[test]
fn test_ownership_conflict_exclusive_owner_allowed() {
let mut map = OwnershipMap::new();
map.add(OwnershipDeclaration::exclusive(
OwnershipTarget::FilePath(PathBuf::from("src/main.rs")),
"pack-a".to_string(),
))
.unwrap();
let pass = EmissionPass::new().with_ownership_map(map);
assert!(pass
.check_ownership_conflict(std::path::Path::new("src/main.rs"), "pack-a")
.is_ok());
}
#[test]
fn test_ownership_conflict_exclusive_blocked() {
let mut map = OwnershipMap::new();
map.add(OwnershipDeclaration::exclusive(
OwnershipTarget::FilePath(PathBuf::from("src/main.rs")),
"pack-a".to_string(),
))
.unwrap();
let pass = EmissionPass::new().with_ownership_map(map);
let result = pass.check_ownership_conflict(std::path::Path::new("src/main.rs"), "pack-b");
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("exclusive ownership"));
assert!(err_msg.contains("pack-a"));
assert!(err_msg.contains("pack-b"));
}
#[test]
fn test_ownership_conflict_forbidden_overlap_blocked() {
let mut map = OwnershipMap::new();
map.add(OwnershipDeclaration::new(
OwnershipTarget::FilePath(PathBuf::from("src/config.rs")),
OwnershipClass::ForbiddenOverlap,
"pack-a".to_string(),
))
.unwrap();
let pass = EmissionPass::new().with_ownership_map(map);
let result = pass.check_ownership_conflict(std::path::Path::new("src/config.rs"), "pack-b");
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("Forbidden overlap"));
}
#[test]
fn test_ownership_conflict_mergeable_allowed() {
use crate::marketplace::ownership::MergeStrategy;
let mut map = OwnershipMap::new();
map.add(OwnershipDeclaration::mergeable(
OwnershipTarget::FilePath(PathBuf::from("src/config.rs")),
"pack-a".to_string(),
MergeStrategy::Concat,
))
.unwrap();
let pass = EmissionPass::new().with_ownership_map(map);
assert!(pass
.check_ownership_conflict(std::path::Path::new("src/config.rs"), "pack-b")
.is_ok());
}
#[test]
fn test_ownership_conflict_undeclared_path() {
let map = OwnershipMap::new();
let pass = EmissionPass::new().with_ownership_map(map);
assert!(pass
.check_ownership_conflict(std::path::Path::new("src/undeclared.rs"), "pack-a")
.is_ok());
}
}