use chrono::Utc;
use cordance_core::pack::CordancePack;
use cordance_core::receipt::{
AuthorityBoundary, CortexReceiptV1Candidate, ExecutionTrust, OperatorApproval, ReceiptBody,
RuntimeIntegrity, SourceAnchor, SourceContext, TruthCeiling,
};
use cordance_core::schema;
use tracing::debug;
const FORBIDDEN_USES: &[&str] = &[
"claim Cortex truth",
"claim Cortex admission",
"promote Cortex memory",
"promote Cortex doctrine",
"create trusted history",
"claim release acceptance",
"authorize runtime writes",
"claim P6 or P9 closure",
"accept or promote an ADR",
];
pub fn build_receipt(pack: &CordancePack) -> Result<CortexReceiptV1Candidate, crate::CortexError> {
debug!(
project = %pack.project.name,
"building cortex candidate receipt"
);
let context_id = if pack.source_lock.pack_id.is_empty() {
pack.project.name.clone()
} else {
pack.source_lock.pack_id.clone()
};
let source_anchors: Vec<SourceAnchor> = pack
.sources
.iter()
.filter(|r| !r.blocked)
.map(|r| SourceAnchor::new(r.id.clone(), r.path.clone(), r.sha256.clone()))
.collect();
let mut allowed_claim_language: Vec<String> = pack.residual_risk.clone();
allowed_claim_language
.push("cordance candidate receipt for cortex context-pack admit-cordance".into());
allowed_claim_language.push("candidate-only evidence".into());
allowed_claim_language.push("no durable promotion".into());
allowed_claim_language.push("no release acceptance".into());
let mut residual_risk: Vec<String> = vec![
"Cortex may reject or quarantine this receipt under its native parser.".into(),
"claim_ceiling=candidate_evidence_only".into(),
];
for item in &pack.residual_risk {
if !residual_risk.contains(item) {
residual_risk.push(item.clone());
}
}
let source_context = SourceContext::new(
context_id,
TruthCeiling::CandidateEvidenceOnly,
"not_cleared_by_boundary_crossing".into(),
);
let execution_trust = ExecutionTrust::new(
"local_candidate_only".into(),
"deny_authority_grant".into(),
"not_field_level_bound_for_cortex".into(),
"not_field_level_bound_for_cortex".into(),
);
let runtime_integrity = RuntimeIntegrity::new(false, false, "not_requested".into());
let operator_approval = OperatorApproval::new(
true,
"not_supplied_for_cortex_promotion".into(),
"not_supplied_for_cortex_promotion".into(),
);
let generated_at = Utc::now();
let body = ReceiptBody::new(
format!("cordance-candidate-{}", generated_at.format("%Y-%m-%d")),
generated_at,
"candidate_only".into(),
"partial_structural_evidence".into(),
"repo_only_no_runtime_write".into(),
"not_bound_for_cortex_promotion".into(),
format!("cordance-pack-{}", pack.project.name),
source_context,
execution_trust,
runtime_integrity,
operator_approval,
allowed_claim_language,
FORBIDDEN_USES.iter().map(|s| (*s).to_owned()).collect(),
source_anchors,
residual_risk,
);
let receipt = CortexReceiptV1Candidate::new(
schema::CORDANCE_CORTEX_RECEIPT_V1_CANDIDATE.into(),
1,
"candidate_only".into(),
"cortex context-pack admit-cordance".into(),
AuthorityBoundary::candidate_only(),
body,
vec![
"Cortex field-level tool_provenance consumer absent".into(),
"Cortex operator approval hash binding absent".into(),
],
);
crate::validator::validate_receipt(&receipt)?;
Ok(receipt)
}