use camino::Utf8PathBuf;
use cordance_core::harness_target::{
AccessMode, AuthoritySurfaces, AxiomProjectHarnessTargetV1, ClaimCeiling, HarnessBlock,
HarnessClassification, HarnessOperations, ProjectBlock, SCHEMA_LITERAL,
};
use cordance_core::pack::CordancePack;
use cordance_core::source::SurfaceCategory;
use crate::{EmitError, TargetEmitter};
pub struct HarnessTargetEmitter;
impl TargetEmitter for HarnessTargetEmitter {
fn name(&self) -> &'static str {
"axiom-harness-target"
}
fn render(&self, pack: &CordancePack) -> Result<Vec<(Utf8PathBuf, Vec<u8>)>, EmitError> {
let mut product_spec: Vec<Utf8PathBuf> = vec![];
let mut adrs: Vec<Utf8PathBuf> = vec![];
let mut doctrine: Vec<Utf8PathBuf> = vec![];
let mut tests_or_evals: Vec<Utf8PathBuf> = vec![];
let mut release_gates: Vec<Utf8PathBuf> = vec![];
for source in &pack.sources {
if source.blocked {
continue;
}
if let Some(cat) = source.class.surface_category() {
match cat {
SurfaceCategory::ProductSpec => product_spec.push(source.path.clone()),
SurfaceCategory::Adrs => adrs.push(source.path.clone()),
SurfaceCategory::Doctrine => doctrine.push(source.path.clone()),
SurfaceCategory::TestsOrEvals => tests_or_evals.push(source.path.clone()),
SurfaceCategory::ReleaseGates => release_gates.push(source.path.clone()),
SurfaceCategory::RuntimeRoots => {}
}
}
}
let target = build_harness_target(
pack.project.name.clone(),
AuthoritySurfaces::new(
product_spec,
adrs,
doctrine,
tests_or_evals,
vec![],
release_gates,
),
);
target.validate_invariants().map_err(|e| {
EmitError::Io(std::io::Error::other(format!(
"harness target validation failed at emit time: {e}"
)))
})?;
let bytes = serde_json::to_vec_pretty(&target)?;
Ok(vec![(
"pai-axiom-project-harness-target.json".into(),
bytes,
)])
}
}
fn build_harness_target(
project_name: String,
surfaces: AuthoritySurfaces,
) -> AxiomProjectHarnessTargetV1 {
AxiomProjectHarnessTargetV1::new(
SCHEMA_LITERAL.into(),
1,
ProjectBlock::new(project_name, ".".into(), AccessMode::ReadOnlyAdvisory),
surfaces,
HarnessBlock::new(
HarnessClassification::ReadOnlyAdvisory,
vec![
HarnessOperations::Inspect,
HarnessOperations::ValidateTarget,
HarnessOperations::EmitCandidateReport,
],
vec![
HarnessOperations::WriteProjectFiles,
HarnessOperations::PromoteProjectDoctrine,
HarnessOperations::MutateRuntimeRoots,
HarnessOperations::ModifyReleaseGates,
HarnessOperations::RewriteAdrs,
],
ClaimCeiling::Candidate,
),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn canonical_harness_target_passes_validate_invariants() {
let target = build_harness_target(
"test-project".into(),
AuthoritySurfaces::new(vec![], vec![], vec![], vec![], vec![], vec![]),
);
target
.validate_invariants()
.expect("canonical emitter output must validate");
}
#[test]
fn emitter_refuses_broken_harness_target_at_construct_time() {
let mut target = build_harness_target(
"test-project".into(),
AuthoritySurfaces::new(vec![], vec![], vec![], vec![], vec![], vec![]),
);
target
.harness
.allowed_operations
.push(HarnessOperations::WriteProjectFiles);
let err = target
.validate_invariants()
.expect_err("forbidden token must trigger validation failure");
let err_text = format!("{err}");
assert!(
err_text.contains("forbidden"),
"validation error must mention forbidden token; got: {err_text}"
);
let io_err = std::io::Error::other(format!(
"harness target validation failed at emit time: {err}"
));
let msg = format!("{io_err}");
assert!(
msg.contains("harness target validation failed at emit time"),
"EmitError message must identify the validate-invariants step; got: {msg}"
);
}
}