264 lines
9.1 KiB
Rust
264 lines
9.1 KiB
Rust
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<Id>,
|
|
decisions: Vec<(ArtifactFacet, SemanticDecision)>,
|
|
candidate: Option<ArtifactLink>,
|
|
) -> 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::<Vec<_>>(),
|
|
"candidate_ids":r.candidates.iter().map(SemanticRecord::id).collect::<Vec<_>>(),
|
|
"initial_artifact_ids":p.artifact_links.keys().collect::<Vec<_>>(),
|
|
"final_artifact_ids":after.artifact_links.keys().collect::<Vec<_>>(),
|
|
"acknowledged_count":plan.reviews.iter().map(|review|review.acknowledged.len()).sum::<usize>(),
|
|
"retired_count":plan.reviews.iter().map(|review|review.retired.len()).sum::<usize>(),
|
|
"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,
|
|
);
|
|
}
|