use crate::truth_package::{
ClauseFingerprint, ClauseId, JtbdClause, JtbdClauseKind, TruthPackage, TruthPackageId,
};
use converge_pack::{ContextKey, FactPayload, ProposedFact, ProvenanceSource};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct AxiomTruth;
impl ProvenanceSource for AxiomTruth {
fn as_str(&self) -> &'static str {
"axiom_truth_package"
}
}
pub const AXIOM_PROVENANCE: AxiomTruth = AxiomTruth;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TruthPackageSeedPayload {
pub package_id: TruthPackageId,
pub truth_version: String,
pub clause_id: ClauseId,
pub clause_kind: JtbdClauseKind,
pub clause_fingerprint: ClauseFingerprint,
pub clause_text: String,
}
impl TruthPackageSeedPayload {
pub fn from_clause(package: &TruthPackage, clause: &JtbdClause) -> Self {
Self {
package_id: package.package_id.clone(),
truth_version: package.truth_version.clone(),
clause_id: clause.id.clone(),
clause_kind: clause.kind,
clause_fingerprint: clause.fingerprint.clone(),
clause_text: clause.canonical_text.clone(),
}
}
}
impl FactPayload for TruthPackageSeedPayload {
const FAMILY: &'static str = "axiom.truth_package_seed";
const VERSION: u16 = 1;
}
pub fn truth_package_seed_fact(package: &TruthPackage, clause: &JtbdClause) -> ProposedFact {
AXIOM_PROVENANCE.proposed_fact(
ContextKey::Seeds,
truth_package_seed_fact_id(package, clause),
TruthPackageSeedPayload::from_clause(package, clause),
)
}
pub fn truth_package_seed_facts(package: &TruthPackage) -> Vec<ProposedFact> {
package
.source_jtbd
.clauses
.iter()
.map(|clause| truth_package_seed_fact(package, clause))
.collect()
}
fn truth_package_seed_fact_id(package: &TruthPackage, clause: &JtbdClause) -> String {
format!(
"axiom_truth_package:{}:{}",
package.package_id.as_str(),
clause.id.as_str()
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::truth_package::{ClauseInput, JtbdInput, decode_jtbd};
fn vendor_package() -> TruthPackage {
decode_jtbd(JtbdInput {
key: "Vendor Commitment".to_string(),
actor: "finance controller".to_string(),
functional_job: "approve a vendor commitment".to_string(),
so_that: "spend is traceable and policy-compliant".to_string(),
evidence_required: vec![ClauseInput::new("vendor assessment")],
failure_modes: vec![ClauseInput::new("bypassed approval")],
time_budget: None,
})
.unwrap()
}
#[test]
fn axiom_provenance_stamps_truth_package_seed_facts() {
let package = vendor_package();
let clause = package.source_jtbd.clauses.first().unwrap();
let proposal = truth_package_seed_fact(&package, clause);
let wire = proposal.to_wire().unwrap();
assert_eq!(AXIOM_PROVENANCE.as_str(), "axiom_truth_package");
assert_eq!(wire.key, ContextKey::Seeds);
assert_eq!(wire.provenance.as_str(), AXIOM_PROVENANCE.as_str());
assert_eq!(
wire.payload.family.as_str(),
TruthPackageSeedPayload::FAMILY
);
assert_eq!(wire.payload.version.get(), TruthPackageSeedPayload::VERSION);
assert_eq!(
wire.payload.payload["package_id"],
serde_json::json!(package.package_id)
);
assert_eq!(
wire.payload.payload["clause_id"],
serde_json::json!(clause.id)
);
assert_eq!(
wire.payload.payload["clause_fingerprint"],
serde_json::json!(clause.fingerprint)
);
}
#[test]
fn truth_package_seed_facts_cover_every_source_clause() {
let package = vendor_package();
let proposals = truth_package_seed_facts(&package);
assert_eq!(proposals.len(), package.source_jtbd.clauses.len());
assert!(proposals.iter().all(|proposal| {
proposal.key == ContextKey::Seeds
&& proposal.provenance.as_str() == AXIOM_PROVENANCE.as_str()
}));
}
}