use oa_core::{transitions::*, *}; use std::{collections::BTreeMap, fs, path::Path}; const META: [ArtifactFacet; 7] = [ ArtifactFacet::Name, ArtifactFacet::Description, ArtifactFacet::Owner, ArtifactFacet::Relation, ArtifactFacet::Uri, ArtifactFacet::Revision, ArtifactFacet::Fragment, ]; fn request( p: &Project, trace: &Trace, keys: KeyPair, decisions: Vec<(ArtifactFacet, SemanticDecision)>, candidate: Option, ) -> SemanticTransitionRequest { let mut t = TransitionRequest::new(TransitionDirection::OaToSa, vec![trace.oa]); t.mappings.push(TransitionMapping { source: trace.oa, target: Some(trace.sa), target_kind: None, }); let mut r = SemanticTransitionRequest::new(t); r.traces.insert(trace.oa, trace.id); r.references.insert(trace.oa, trace.sa); if let Some((oa, sa)) = keys.oa.zip(keys.sa) { r.references.insert(oa, sa); } r.reviews.push(SemanticReviewRequest { source: trace.oa, resolutions: decisions .into_iter() .map(|(facet, decision)| FacetResolution { facet: FacetSelector::Artifact { keys: keys.clone(), facet, }, decision, }) .collect(), reset: vec![], }); if let Some(c) = candidate { r.candidates.push(SemanticRecord::Artifact(c)); } validate_candidate(p).unwrap(); r } fn emit(dir: &Path, p: &Project, r: SemanticTransitionRequest, name: &str, preserve_base: bool) { let plan = plan_semantic_transition(p, r.clone()).unwrap(); assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts); let after = prepare_semantic_transition(p, &plan).unwrap().project; let logical = format!("{name}.arcadia"); let source = if preserve_base { fs::read_to_string(dir.join(&logical)).expect("Read unchanged fixture source") } else { format!( "// Exact artifact rationale: no implicit metadata review\r\n{}", language::format(p) ) }; let bundle = source_project::SourceBundle { entry: logical.clone(), files: BTreeMap::from([(logical.clone(), source.clone())]), }; let compiled = source_project::compile_bundle(&bundle, source_project::ProjectLimits::default()).unwrap(); let updated = source_project::update_bundle(&compiled, &after, source_project::ProjectLimits::default()) .unwrap(); for (suffix, project, text) in [ ("", p, &source), (".after", &after, &updated.bundle.files[&logical]), ] { assert_eq!(language::parse(text).unwrap(), *project); if preserve_base && suffix.is_empty() { continue; } fs::write(dir.join(format!("{name}{suffix}.arcadia")), text).unwrap(); fs::write( dir.join(format!("{name}{suffix}.json")), serde_json::to_vec_pretty(project).unwrap(), ) .unwrap(); } fs::write( dir.join(format!("{name}.request.json")), serde_json::to_vec_pretty(&r).unwrap(), ) .unwrap(); let manifest = serde_json::json!({ "fixture":name,"schema":p.schema_version,"project":p.id, "trace_ids":r.traces.values().collect::>(), "candidate_ids":r.candidates.iter().map(SemanticRecord::id).collect::>(), "initial_artifact_ids":p.artifact_links.keys().collect::>(), "final_artifact_ids":after.artifact_links.keys().collect::>(), "acknowledged_count":plan.reviews.iter().map(|review|review.acknowledged.len()).sum::(), "retired_count":plan.reviews.iter().map(|review|review.retired.len()).sum::(), "impact_count":plan.impacts.len(),"native_acceptance":"pending" }); fs::write( dir.join(format!("{name}.manifest.json")), serde_json::to_vec_pretty(&manifest).unwrap(), ) .unwrap(); println!( "{name}: {} artifacts -> {}; {} reviewed facts, {} retired, {} impacts", p.artifact_links.len(), after.artifact_links.len(), plan.reviews[0].acknowledged.len(), plan.reviews[0].retired.len(), plan.impacts.len() ); } fn main() { let args: Vec<_> = std::env::args().collect(); assert!( (2..=3).contains(&args.len()), "Usage: generate-semantic-artifacts OUTPUT_DIRECTORY [RESERVED_CREATION_UUID]. Without UUID, creates fresh fixture identities; with UUID, preserves the existing creation base and writes its exact after oracle." ); let dir = Path::new(&args[1]); fs::create_dir_all(dir).unwrap(); if args.len() == 3 { let p: Project = serde_json::from_slice(&fs::read(dir.join("semantic-artifacts-create.json")).unwrap()) .unwrap(); let trace = p .traces .values() .find(|t| t.alias == "artifact_review") .unwrap(); let source = p .artifact_links .values() .find(|a| a.owner == trace.oa) .unwrap(); let mut candidate = source.clone(); candidate.id = args[2].parse().unwrap(); candidate.alias = format!("artifact_{}", candidate.id.simple()); candidate.owner = trace.sa; let keys = KeyPair { oa: Some(source.id), sa: Some(candidate.id), }; let mut decisions = vec![(ArtifactFacet::Presence, SemanticDecision::TakeSource)]; decisions.extend(META.map(|facet| (facet, SemanticDecision::Candidate))); emit( dir, &p, request(&p, trace, keys, decisions, Some(candidate)), "semantic-artifacts-create", true, ); return; } let mut p = Project::empty(); p.name = "Explicit requirement-link review".into(); let mut owners = vec![]; let mut others = vec![]; let mut links = vec![]; for layer in [Layer::Oa, Layer::Sa] { let prefix = if layer == Layer::Oa { "OA" } else { "SA" }; let kind = if layer == Layer::Oa { Kind::Activity } else { Kind::Function }; let owner = p .add_element(kind, layer, &format!("{prefix} Startup coordination"), None) .unwrap(); let other = p .add_element(kind, layer, &format!("{prefix} Safety coordination"), None) .unwrap(); let id = Id::new_v4(); p.artifact_links.insert( id, ArtifactLink { id, alias: format!("artifact_{}", id.simple()), name: format!("{prefix} Interlock requirement"), description: "Reviewed safety rationale\r\nSecond exact line".into(), owner, relation: ArtifactRelation::Satisfies, uri: "requirements/interlock".into(), revision: None, fragment: Some("section-4".into()), }, ); owners.push(owner); others.push(other); links.push(id); } let id = Id::new_v4(); let trace = Trace { id, alias: "artifact_review".into(), name: "Interlock requirement rationale".into(), description: String::new(), oa: owners[0], sa: owners[1], kind: TraceKind::Manual, baseline: None, }; p.traces.insert(id, trace.clone()); let keys = KeyPair { oa: Some(links[0]), sa: Some(links[1]), }; let mut moved = p.artifact_links[&links[1]].clone(); moved.owner = others[1]; moved.revision = Some(String::new()); moved.fragment = None; emit( dir, &p, request( &p, &trace, keys.clone(), vec![ (ArtifactFacet::Owner, SemanticDecision::Candidate), (ArtifactFacet::Revision, SemanticDecision::Candidate), (ArtifactFacet::Fragment, SemanticDecision::Candidate), ], Some(moved), ), "semantic-artifacts-move", false, ); let mut deleted = p.clone(); deleted.artifact_links.get_mut(&links[0]).unwrap().owner = others[0]; emit( dir, &deleted, request( &deleted, &trace, keys.clone(), vec![(ArtifactFacet::Presence, SemanticDecision::TakeSource)], None, ), "semantic-artifacts-delete", false, ); let mut create = p.clone(); create.artifact_links.remove(&links[1]); let source = create.artifact_links[&links[0]].clone(); let mut candidate = source; candidate.id = links[1]; candidate.alias = format!("artifact_{}", candidate.id.simple()); candidate.owner = owners[1]; let mut decisions = vec![(ArtifactFacet::Presence, SemanticDecision::TakeSource)]; decisions.extend(META.map(|facet| (facet, SemanticDecision::Candidate))); emit( dir, &create, request(&create, &trace, keys, decisions, Some(candidate)), "semantic-artifacts-create", false, ); }