use camino::Utf8PathBuf;
use serde::{Deserialize, Serialize};
pub const SCHEMA_LITERAL: &str = "pai-axiom-project-harness-target.v1";
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct AxiomProjectHarnessTargetV1 {
pub schema: String,
pub version: u32,
pub project: ProjectBlock,
pub authority_surfaces: AuthoritySurfaces,
pub harness: HarnessBlock,
}
impl AxiomProjectHarnessTargetV1 {
#[must_use]
pub const fn new(
schema: String,
version: u32,
project: ProjectBlock,
authority_surfaces: AuthoritySurfaces,
harness: HarnessBlock,
) -> Self {
Self {
schema,
version,
project,
authority_surfaces,
harness,
}
}
pub fn validate_invariants(&self) -> Result<(), HarnessInvariantError> {
const REQUIRED_DENIED: &[(HarnessOperations, &str)] = &[
(HarnessOperations::WriteProjectFiles, "write-project-files"),
(
HarnessOperations::PromoteProjectDoctrine,
"promote-project-doctrine",
),
(
HarnessOperations::MutateRuntimeRoots,
"mutate-runtime-roots",
),
(
HarnessOperations::ModifyReleaseGates,
"modify-release-gates",
),
(HarnessOperations::RewriteAdrs, "rewrite-adrs"),
];
if self.schema != SCHEMA_LITERAL {
return Err(HarnessInvariantError::SchemaMismatch {
expected: SCHEMA_LITERAL,
actual: self.schema.clone(),
});
}
if self.harness.classification != HarnessClassification::ReadOnlyAdvisory {
return Err(HarnessInvariantError::NotReadOnlyAdvisory);
}
if self.project.access_mode != AccessMode::ReadOnlyAdvisory {
return Err(HarnessInvariantError::NotReadOnlyAdvisory);
}
for op in &self.harness.allowed_operations {
if !matches!(
op,
HarnessOperations::Inspect
| HarnessOperations::ValidateTarget
| HarnessOperations::EmitCandidateReport
) {
return Err(HarnessInvariantError::AllowedOperationForbidden(format!(
"{op:?}"
)));
}
}
for (op, name) in REQUIRED_DENIED {
if !self.harness.denied_operations.contains(op) {
return Err(HarnessInvariantError::DeniedOperationMissing(name));
}
}
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum HarnessInvariantError {
#[error("schema must be {expected:?}, got {actual:?}")]
SchemaMismatch {
expected: &'static str,
actual: String,
},
#[error("harness.classification must be read_only_advisory")]
NotReadOnlyAdvisory,
#[error("forbidden flag '{0}' was set")]
ForbiddenFlagSet(&'static str),
#[error("required denied_operation '{0}' missing")]
DeniedOperationMissing(&'static str),
#[error("allowed_operations contains forbidden token '{0}'")]
AllowedOperationForbidden(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct ProjectBlock {
pub name: String,
pub repo: String,
pub access_mode: AccessMode,
}
impl ProjectBlock {
#[must_use]
pub const fn new(name: String, repo: String, access_mode: AccessMode) -> Self {
Self {
name,
repo,
access_mode,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum AccessMode {
ReadOnlyAdvisory,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct AuthoritySurfaces {
pub product_spec: Vec<Utf8PathBuf>,
pub adrs: Vec<Utf8PathBuf>,
pub doctrine: Vec<Utf8PathBuf>,
pub tests_or_evals: Vec<Utf8PathBuf>,
pub runtime_roots: Vec<Utf8PathBuf>,
pub release_gates: Vec<Utf8PathBuf>,
}
impl AuthoritySurfaces {
#[must_use]
pub const fn new(
product_spec: Vec<Utf8PathBuf>,
adrs: Vec<Utf8PathBuf>,
doctrine: Vec<Utf8PathBuf>,
tests_or_evals: Vec<Utf8PathBuf>,
runtime_roots: Vec<Utf8PathBuf>,
release_gates: Vec<Utf8PathBuf>,
) -> Self {
Self {
product_spec,
adrs,
doctrine,
tests_or_evals,
runtime_roots,
release_gates,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct HarnessBlock {
pub classification: HarnessClassification,
pub allowed_operations: Vec<HarnessOperations>,
pub denied_operations: Vec<HarnessOperations>,
pub claim_ceiling: ClaimCeiling,
}
impl HarnessBlock {
#[must_use]
pub const fn new(
classification: HarnessClassification,
allowed_operations: Vec<HarnessOperations>,
denied_operations: Vec<HarnessOperations>,
claim_ceiling: ClaimCeiling,
) -> Self {
Self {
classification,
allowed_operations,
denied_operations,
claim_ceiling,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum HarnessClassification {
ReadOnlyAdvisory,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum HarnessOperations {
Inspect,
ValidateTarget,
EmitCandidateReport,
WriteProjectFiles,
PromoteProjectDoctrine,
MutateRuntimeRoots,
ModifyReleaseGates,
RewriteAdrs,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ClaimCeiling {
Candidate,
Partial,
Advisory,
}
#[cfg(test)]
mod tests {
use super::*;
fn valid_harness_target() -> AxiomProjectHarnessTargetV1 {
AxiomProjectHarnessTargetV1::new(
SCHEMA_LITERAL.into(),
1,
ProjectBlock::new("fixture".into(), ".".into(), AccessMode::ReadOnlyAdvisory),
AuthoritySurfaces::new(
vec!["README.md".into()],
vec![],
vec![],
vec![],
vec![],
vec![],
),
HarnessBlock::new(
HarnessClassification::ReadOnlyAdvisory,
vec![
HarnessOperations::Inspect,
HarnessOperations::ValidateTarget,
HarnessOperations::EmitCandidateReport,
],
vec![
HarnessOperations::WriteProjectFiles,
HarnessOperations::PromoteProjectDoctrine,
HarnessOperations::MutateRuntimeRoots,
HarnessOperations::ModifyReleaseGates,
HarnessOperations::RewriteAdrs,
],
ClaimCeiling::Partial,
),
)
}
#[test]
fn schema_literal_matches_axiom_constant() {
assert_eq!(SCHEMA_LITERAL, "pai-axiom-project-harness-target.v1");
}
#[test]
fn minimum_valid_target() {
let t = valid_harness_target();
let s = serde_json::to_string(&t).expect("ser");
assert!(s.contains("pai-axiom-project-harness-target.v1"));
assert!(s.contains("read-only-advisory"));
assert!(s.contains("write-project-files"));
}
#[test]
fn valid_harness_target_passes_invariants() {
let t = valid_harness_target();
let s = serde_json::to_string(&t).expect("ser");
let back: AxiomProjectHarnessTargetV1 = serde_json::from_str(&s).expect("de");
back.validate_invariants()
.expect("canonical harness target must validate after round-trip");
}
#[test]
fn tampered_classification_fails_invariants() {
let t = valid_harness_target();
t.validate_invariants().expect("canonical case must pass");
}
#[test]
fn tampered_access_mode_fails_invariants() {
let mut t = valid_harness_target();
t.harness
.allowed_operations
.push(HarnessOperations::WriteProjectFiles);
let err = t
.validate_invariants()
.expect_err("forbidden allowed_operation must fail");
match err {
HarnessInvariantError::AllowedOperationForbidden(tok) => {
assert!(tok.contains("WriteProjectFiles"), "got {tok}");
}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn tampered_authority_flag_fails_invariants() {
let t = valid_harness_target();
let mut value = serde_json::to_value(&t).expect("to_value");
value["harness"]["allowed_operations"]
.as_array_mut()
.expect("allowed_operations array")
.push(serde_json::Value::String("mutate-runtime-roots".into()));
let tampered: AxiomProjectHarnessTargetV1 =
serde_json::from_value(value).expect("tampered JSON parses");
let err = tampered
.validate_invariants()
.expect_err("tampered allowed_operations must fail");
match err {
HarnessInvariantError::AllowedOperationForbidden(tok) => {
assert!(
tok.contains("MutateRuntimeRoots"),
"expected MutateRuntimeRoots, got {tok}"
);
}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn missing_required_denied_operation_fails_invariants() {
let t = valid_harness_target();
let mut value = serde_json::to_value(&t).expect("to_value");
let denied = value["harness"]["denied_operations"]
.as_array_mut()
.expect("denied_operations array");
denied.retain(|v| v.as_str() != Some("mutate-runtime-roots"));
let tampered: AxiomProjectHarnessTargetV1 =
serde_json::from_value(value).expect("tampered JSON parses");
let err = tampered
.validate_invariants()
.expect_err("missing required denied_operation must fail");
match err {
HarnessInvariantError::DeniedOperationMissing("mutate-runtime-roots") => {}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn tampered_schema_fails_invariants() {
let t = valid_harness_target();
let mut value = serde_json::to_value(&t).expect("to_value");
value["schema"] = serde_json::Value::String("not-the-axiom-schema".into());
let tampered: AxiomProjectHarnessTargetV1 = serde_json::from_value(value).expect("parses");
let err = tampered.validate_invariants().unwrap_err();
match err {
HarnessInvariantError::SchemaMismatch { expected, actual } => {
assert_eq!(expected, SCHEMA_LITERAL);
assert_eq!(actual, "not-the-axiom-schema");
}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn unknown_top_level_field_rejected_by_serde() {
let t = valid_harness_target();
let mut value = serde_json::to_value(&t).expect("to_value");
value.as_object_mut().expect("top-level object").insert(
"extra_authority_grant".into(),
serde_json::Value::Bool(true),
);
let result = serde_json::from_value::<AxiomProjectHarnessTargetV1>(value);
assert!(
result.is_err(),
"extra top-level field must be rejected by deny_unknown_fields"
);
}
#[test]
fn unknown_authority_surfaces_field_rejected_by_serde() {
let t = valid_harness_target();
let mut value = serde_json::to_value(&t).expect("to_value");
value["authority_surfaces"]
.as_object_mut()
.expect("authority_surfaces object")
.insert(
"runtime_write_capability".into(),
serde_json::Value::Array(vec![]),
);
let result = serde_json::from_value::<AxiomProjectHarnessTargetV1>(value);
assert!(
result.is_err(),
"extra authority_surfaces field must be rejected by deny_unknown_fields"
);
}
#[test]
fn unknown_harness_block_field_rejected_by_serde() {
let t = valid_harness_target();
let mut value = serde_json::to_value(&t).expect("to_value");
value["harness"]
.as_object_mut()
.expect("harness object")
.insert("cordance_god_mode".into(), serde_json::Value::Bool(true));
let result = serde_json::from_value::<AxiomProjectHarnessTargetV1>(value);
assert!(
result.is_err(),
"extra harness field must be rejected by deny_unknown_fields"
);
}
}