use chrono::{DateTime, Utc};
use cortex_core::attestor::{verify, Attestation};
use cortex_core::canonical::{AttestationPreimage, LineageBinding, SourceIdentity};
use cortex_core::{
AuditRecordId, DoctrineId, PolicyContribution, PolicyDecision, PolicyOutcome, PrincipleId,
};
use cortex_ledger::payload_hash;
use ed25519_dalek::VerifyingKey;
use rusqlite::{params, OptionalExtension, Row};
use serde_json::{json, Value};
use crate::{Pool, StoreError, StoreResult};
pub const CANDIDATE_FALSIFICATION_RULE_ID: &str = "principle.candidate.falsification";
pub const CANDIDATE_SUPPORTING_MEMORY_PROOF_RULE_ID: &str =
"principle.candidate.supporting_memory_proof";
pub const PROMOTION_FALSIFICATION_RULE_ID: &str = "principle_promotion.falsification";
macro_rules! principle_select_sql {
($where_clause:literal) => {
concat!(
"SELECT id, statement, status, supporting_memories_json,
contradicting_memories_json, domains_observed_json,
applies_when_json, does_not_apply_when_json, confidence,
validation, brightness, created_by_json, created_at, updated_at
FROM principles ",
$where_clause,
";"
)
};
}
#[derive(Debug, Clone, PartialEq)]
pub struct PrincipleCandidateRow {
pub id: PrincipleId,
pub statement: String,
pub status: String,
pub supporting_memories_json: Value,
pub contradicting_memories_json: Value,
pub domains_observed_json: Value,
pub applies_when_json: Value,
pub does_not_apply_when_json: Value,
pub confidence: f64,
pub validation: f64,
pub brightness: f64,
pub created_by_json: Value,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
pub type PrincipleRecord = PrincipleCandidateRow;
#[derive(Debug, Clone, PartialEq)]
pub struct DoctrineRecord {
pub id: DoctrineId,
pub source_principle: PrincipleId,
pub rule: String,
pub force: String,
pub promotion_reason: String,
pub promoted_by_json: Value,
pub composed_policy_decision_json: Value,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DoctrinePromotion {
pub doctrine_id: DoctrineId,
pub audit_id: AuditRecordId,
pub source_principle: PrincipleId,
pub force: String,
pub reason: String,
pub promoted_by_json: Value,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PromotionAttestationReplay {
pub doctrine_id: DoctrineId,
pub source_principle: PrincipleId,
pub audit_id: AuditRecordId,
pub verified: bool,
}
#[derive(Debug)]
pub struct PrincipleRepo<'a> {
pool: &'a Pool,
}
impl<'a> PrincipleRepo<'a> {
#[must_use]
pub const fn new(pool: &'a Pool) -> Self {
Self { pool }
}
pub fn insert_candidate(
&self,
candidate: &PrincipleCandidateRow,
policy: &PolicyDecision,
) -> StoreResult<()> {
require_policy_final_outcome(policy, "principle.candidate.insert")?;
require_contributor_rule(policy, CANDIDATE_FALSIFICATION_RULE_ID)?;
require_contributor_rule(policy, CANDIDATE_SUPPORTING_MEMORY_PROOF_RULE_ID)?;
require_contributor_not_break_glassed(
policy,
CANDIDATE_FALSIFICATION_RULE_ID,
"principle.candidate.insert",
)?;
validate_candidate(candidate)?;
self.pool.execute(
"INSERT INTO principles (
id, statement, status, supporting_memories_json, contradicting_memories_json,
domains_observed_json, applies_when_json, does_not_apply_when_json,
confidence, validation, brightness, created_by_json, created_at, updated_at
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14);",
params![
candidate.id.to_string(),
candidate.statement,
candidate.status,
serde_json::to_string(&candidate.supporting_memories_json)?,
serde_json::to_string(&candidate.contradicting_memories_json)?,
serde_json::to_string(&candidate.domains_observed_json)?,
serde_json::to_string(&candidate.applies_when_json)?,
serde_json::to_string(&candidate.does_not_apply_when_json)?,
candidate.confidence,
candidate.validation,
candidate.brightness,
serde_json::to_string(&candidate.created_by_json)?,
candidate.created_at.to_rfc3339(),
candidate.updated_at.to_rfc3339(),
],
)?;
Ok(())
}
pub fn get_by_id(&self, id: &PrincipleId) -> StoreResult<Option<PrincipleRecord>> {
let row = self
.pool
.query_row(
principle_select_sql!("WHERE id = ?1"),
params![id.to_string()],
principle_row,
)
.optional()?;
row.map(TryInto::try_into).transpose()
}
pub fn list_candidates(&self) -> StoreResult<Vec<PrincipleRecord>> {
let mut stmt = self.pool.prepare(principle_select_sql!(
"WHERE status = 'candidate' ORDER BY updated_at DESC, id"
))?;
let rows = stmt.query_map([], principle_row)?;
let mut principles = Vec::new();
for row in rows {
principles.push(row?.try_into()?);
}
Ok(principles)
}
pub fn list_doctrine(&self) -> StoreResult<Vec<DoctrineRecord>> {
let mut stmt = self.pool.prepare(
"SELECT id, source_principle, rule, force, promotion_reason,
promoted_by_json, created_at, composed_policy_decision_json
FROM doctrine
ORDER BY created_at ASC, id;",
)?;
let rows = stmt.query_map([], |row| {
let promoted_by_json: String = row.get(5)?;
let composed_json: Option<String> = row.get(7)?;
Ok((
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
row.get::<_, String>(3)?,
row.get::<_, String>(4)?,
promoted_by_json,
row.get::<_, String>(6)?,
composed_json,
))
})?;
let mut out = Vec::new();
for row in rows {
let (
id,
source_principle,
rule,
force,
promotion_reason,
promoted_by_json,
created_at,
composed_json,
) = row?;
out.push(DoctrineRecord {
id: id.parse()?,
source_principle: source_principle.parse()?,
rule,
force,
promotion_reason,
promoted_by_json: serde_json::from_str(&promoted_by_json)?,
composed_policy_decision_json: composed_json
.map(|s| serde_json::from_str(&s))
.transpose()?
.unwrap_or(serde_json::Value::Null),
created_at: DateTime::parse_from_rfc3339(&created_at)?.with_timezone(&Utc),
});
}
Ok(out)
}
pub fn promote_to_doctrine(
&self,
promotion: &DoctrinePromotion,
policy: &PolicyDecision,
) -> StoreResult<DoctrineRecord> {
require_policy_final_outcome(policy, "principle.promote")?;
require_contributor_not_break_glassed(
policy,
PROMOTION_FALSIFICATION_RULE_ID,
"principle.promote",
)?;
validate_promotion(promotion)?;
let composed_policy_decision_json = serde_json::to_value(policy)?;
let composed_policy_decision_text = serde_json::to_string(&composed_policy_decision_json)?;
let tx = self.pool.unchecked_transaction()?;
let row = tx
.query_row(
principle_select_sql!("WHERE id = ?1"),
params![promotion.source_principle.to_string()],
principle_row,
)
.optional()?;
let Some(principle): Option<PrincipleRecord> = row.map(TryInto::try_into).transpose()?
else {
return Err(StoreError::Validation(format!(
"principle {} not found",
promotion.source_principle
)));
};
if principle.status != "candidate" {
return Err(StoreError::Validation(format!(
"principle {} is not a candidate: {}",
promotion.source_principle, principle.status
)));
}
validate_candidate(&principle)?;
validate_promotion_falsification(&principle)?;
tx.execute(
"UPDATE principles
SET status = 'promoted_to_doctrine', updated_at = ?2
WHERE id = ?1 AND status = 'candidate';",
params![
promotion.source_principle.to_string(),
promotion.created_at.to_rfc3339(),
],
)?;
tx.execute(
"INSERT INTO doctrine (
id, source_principle, rule, force, promotion_reason, promoted_by_json,
created_at, composed_policy_decision_json
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);",
params![
promotion.doctrine_id.to_string(),
promotion.source_principle.to_string(),
principle.statement.as_str(),
promotion.force.as_str(),
promotion.reason.as_str(),
serde_json::to_string(&promotion.promoted_by_json)?,
promotion.created_at.to_rfc3339(),
composed_policy_decision_text.as_str(),
],
)?;
tx.execute(
"INSERT INTO audit_records (
id, operation, target_ref, before_hash, after_hash, reason,
actor_json, source_refs_json, created_at
) VALUES (?1, 'doctrine_promotion', ?2, ?3, ?4, ?5, ?6, ?7, ?8);",
params![
promotion.audit_id.to_string(),
promotion.source_principle.to_string(),
"principle:status:candidate",
format!(
"doctrine:{}:force:{}",
promotion.doctrine_id, promotion.force
),
promotion.reason.as_str(),
serde_json::to_string(&promotion.promoted_by_json)?,
serde_json::to_string(&serde_json::json!([
promotion.source_principle.to_string(),
promotion.doctrine_id.to_string(),
promotion.force.as_str()
]))?,
promotion.created_at.to_rfc3339(),
],
)?;
let doctrine = DoctrineRecord {
id: promotion.doctrine_id,
source_principle: promotion.source_principle,
rule: principle.statement,
force: promotion.force.clone(),
promotion_reason: promotion.reason.clone(),
promoted_by_json: promotion.promoted_by_json.clone(),
composed_policy_decision_json,
created_at: promotion.created_at,
};
tx.commit()?;
Ok(doctrine)
}
pub fn verify_promotion_attestation_replay(
&self,
source_principle: &PrincipleId,
) -> StoreResult<PromotionAttestationReplay> {
let rows = self
.pool
.prepare(
"SELECT d.id, d.source_principle, d.force, d.promotion_reason,
d.promoted_by_json, d.created_at,
a.id, a.reason, a.actor_json, a.after_hash, a.source_refs_json
FROM doctrine d
JOIN audit_records a
ON a.operation = 'doctrine_promotion'
AND a.target_ref = d.source_principle
WHERE d.source_principle = ?1;",
)?
.query_map([source_principle.to_string()], stored_promotion_replay_row)?
.collect::<Result<Vec<_>, _>>()?;
let [row] = rows.as_slice() else {
return Err(StoreError::Validation(format!(
"doctrine promotion replay requires exactly one doctrine/audit pair for {source_principle}; found {}",
rows.len()
)));
};
row.verify()
}
}
#[derive(Debug, Clone)]
struct StoredPromotionReplayRow {
doctrine_id: DoctrineId,
source_principle: PrincipleId,
force: String,
promotion_reason: String,
promoted_by_json: Value,
audit_id: AuditRecordId,
audit_reason: String,
audit_actor_json: Value,
audit_after_hash: String,
audit_source_refs_json: Value,
}
impl StoredPromotionReplayRow {
fn verify(&self) -> StoreResult<PromotionAttestationReplay> {
if self.promoted_by_json != self.audit_actor_json {
return Err(StoreError::Validation(
"doctrine promotion replay actor_json does not match promoted_by_json".into(),
));
}
if self.audit_reason != self.promotion_reason {
return Err(StoreError::Validation(
"doctrine promotion replay audit reason does not match doctrine reason".into(),
));
}
let expected_after_hash = format!("doctrine:{}:force:{}", self.doctrine_id, self.force);
if self.audit_after_hash != expected_after_hash {
return Err(StoreError::Validation(
"doctrine promotion replay audit after_hash does not match doctrine row".into(),
));
}
let expected_source_refs = json!([
self.source_principle.to_string(),
self.doctrine_id.to_string(),
self.force.as_str()
]);
if self.audit_source_refs_json != expected_source_refs {
return Err(StoreError::Validation(
"doctrine promotion replay source refs do not match doctrine row".into(),
));
}
let promotion = DoctrinePromotion {
doctrine_id: self.doctrine_id,
audit_id: self.audit_id,
source_principle: self.source_principle,
force: self.force.clone(),
reason: self.promotion_reason.clone(),
promoted_by_json: self.promoted_by_json.clone(),
created_at: Utc::now(),
};
validate_promotion_attestation(&promotion)?;
Ok(PromotionAttestationReplay {
doctrine_id: self.doctrine_id,
source_principle: self.source_principle,
audit_id: self.audit_id,
verified: true,
})
}
}
fn stored_promotion_replay_row(row: &Row<'_>) -> rusqlite::Result<StoredPromotionReplayRow> {
let promoted_by_json: String = row.get(4)?;
let actor_json: String = row.get(8)?;
let source_refs_json: String = row.get(10)?;
Ok(StoredPromotionReplayRow {
doctrine_id: row.get::<_, String>(0)?.parse().map_err(parse_sql_err)?,
source_principle: row.get::<_, String>(1)?.parse().map_err(parse_sql_err)?,
force: row.get(2)?,
promotion_reason: row.get(3)?,
promoted_by_json: serde_json::from_str(&promoted_by_json).map_err(parse_sql_err)?,
audit_id: row.get::<_, String>(6)?.parse().map_err(parse_sql_err)?,
audit_reason: row.get(7)?,
audit_actor_json: serde_json::from_str(&actor_json).map_err(parse_sql_err)?,
audit_after_hash: row.get(9)?,
audit_source_refs_json: serde_json::from_str(&source_refs_json).map_err(parse_sql_err)?,
})
}
fn parse_sql_err(err: impl std::error::Error + Send + Sync + 'static) -> rusqlite::Error {
rusqlite::Error::FromSqlConversionFailure(0, rusqlite::types::Type::Text, Box::new(err))
}
#[derive(Debug)]
struct PrincipleRow {
id: String,
statement: String,
status: String,
supporting_memories_json: String,
contradicting_memories_json: String,
domains_observed_json: String,
applies_when_json: String,
does_not_apply_when_json: String,
confidence: f64,
validation: f64,
brightness: f64,
created_by_json: String,
created_at: String,
updated_at: String,
}
fn principle_row(row: &Row<'_>) -> rusqlite::Result<PrincipleRow> {
Ok(PrincipleRow {
id: row.get(0)?,
statement: row.get(1)?,
status: row.get(2)?,
supporting_memories_json: row.get(3)?,
contradicting_memories_json: row.get(4)?,
domains_observed_json: row.get(5)?,
applies_when_json: row.get(6)?,
does_not_apply_when_json: row.get(7)?,
confidence: row.get(8)?,
validation: row.get(9)?,
brightness: row.get(10)?,
created_by_json: row.get(11)?,
created_at: row.get(12)?,
updated_at: row.get(13)?,
})
}
impl TryFrom<PrincipleRow> for PrincipleRecord {
type Error = StoreError;
fn try_from(row: PrincipleRow) -> StoreResult<Self> {
Ok(Self {
id: row.id.parse()?,
statement: row.statement,
status: row.status,
supporting_memories_json: serde_json::from_str(&row.supporting_memories_json)?,
contradicting_memories_json: serde_json::from_str(&row.contradicting_memories_json)?,
domains_observed_json: serde_json::from_str(&row.domains_observed_json)?,
applies_when_json: serde_json::from_str(&row.applies_when_json)?,
does_not_apply_when_json: serde_json::from_str(&row.does_not_apply_when_json)?,
confidence: row.confidence,
validation: row.validation,
brightness: row.brightness,
created_by_json: serde_json::from_str(&row.created_by_json)?,
created_at: DateTime::parse_from_rfc3339(&row.created_at)?.with_timezone(&Utc),
updated_at: DateTime::parse_from_rfc3339(&row.updated_at)?.with_timezone(&Utc),
})
}
}
fn validate_candidate(candidate: &PrincipleCandidateRow) -> StoreResult<()> {
if candidate.statement.trim().is_empty() {
return Err(StoreError::Validation(
"principle candidate statement must not be empty".into(),
));
}
if candidate.status != "candidate" && candidate.status != "promoted_to_doctrine" {
return Err(StoreError::Validation(format!(
"unsupported principle status `{}`",
candidate.status
)));
}
if json_array_len(&candidate.supporting_memories_json) < 3 {
return Err(StoreError::Validation(
"principle candidate requires at least three supporting memories".into(),
));
}
if json_array_len(&candidate.domains_observed_json) < 2 {
return Err(StoreError::Validation(
"principle candidate requires at least two observed domains".into(),
));
}
validate_score(candidate.confidence, "confidence")?;
validate_score(candidate.validation, "validation")?;
validate_score(candidate.brightness, "brightness")?;
Ok(())
}
fn validate_promotion(promotion: &DoctrinePromotion) -> StoreResult<()> {
if promotion.reason.trim().is_empty() {
return Err(StoreError::Validation(
"doctrine promotion reason must not be empty".into(),
));
}
validate_promotion_attestation(promotion)?;
match promotion.force.as_str() {
"Advisory" | "Conditioning" | "Gate" => Ok(()),
force => Err(StoreError::Validation(format!(
"unsupported doctrine force `{force}`"
))),
}
}
fn validate_promotion_attestation(promotion: &DoctrinePromotion) -> StoreResult<()> {
let actor = &promotion.promoted_by_json;
if actor.get("attestation_verified").and_then(Value::as_bool) != Some(true) {
return Err(StoreError::Validation(
"doctrine promotion requires verified actor attestation".into(),
));
}
let attestation = actor.get("attestation").ok_or_else(|| {
StoreError::Validation("doctrine promotion requires actor attestation".into())
})?;
require_non_empty_str(attestation, "key_id")?;
require_hex_len(attestation, "public_key_hex", 64)?;
require_hex_len(attestation, "signature_hex", 128)?;
require_non_empty_str(attestation, "signed_at")?;
require_non_empty_str(attestation, "payload_hash")?;
require_non_empty_str(attestation, "event_id")?;
require_non_empty_str(attestation, "ledger_id")?;
let schema_version = attestation
.get("schema_version")
.and_then(Value::as_u64)
.ok_or_else(|| {
StoreError::Validation(
"doctrine promotion attestation requires numeric schema_version".into(),
)
})?;
if schema_version == 0 {
return Err(StoreError::Validation(
"doctrine promotion attestation schema_version must be non-zero".into(),
));
}
let lineage = attestation.get("lineage").ok_or_else(|| {
StoreError::Validation("doctrine promotion attestation requires lineage".into())
})?;
if lineage.get("kind").and_then(Value::as_str) != Some("chain_position")
|| lineage.get("value").and_then(Value::as_u64).is_none()
{
return Err(StoreError::Validation(
"doctrine promotion attestation requires chain_position lineage".into(),
));
}
verify_promotion_attestation(promotion, attestation)?;
Ok(())
}
fn verify_promotion_attestation(
promotion: &DoctrinePromotion,
attestation: &Value,
) -> StoreResult<()> {
if string_field(attestation, "source")? != "user" {
return Err(StoreError::Validation(
"doctrine promotion attestation source must be user".into(),
));
}
let key_id = string_field(attestation, "key_id")?;
let public_key_bytes = decode_hex_array::<32>(string_field(attestation, "public_key_hex")?)?;
let public_key = VerifyingKey::from_bytes(&public_key_bytes).map_err(|err| {
StoreError::Validation(format!(
"doctrine promotion attestation public_key_hex is invalid: {err}"
))
})?;
let signature = decode_hex_array::<64>(string_field(attestation, "signature_hex")?)?;
let signed_at = chrono::DateTime::parse_from_rfc3339(string_field(attestation, "signed_at")?)
.map_err(|err| {
StoreError::Validation(format!(
"doctrine promotion attestation signed_at is invalid: {err}"
))
})?
.with_timezone(&Utc);
let expected_event_id = format!("principle_promote:{}", promotion.source_principle);
if string_field(attestation, "event_id")? != expected_event_id {
return Err(StoreError::Validation(
"doctrine promotion attestation event_id does not match source principle".into(),
));
}
let expected_payload_hash = payload_hash(&promotion_attestation_payload(promotion));
if string_field(attestation, "payload_hash")? != expected_payload_hash {
return Err(StoreError::Validation(
"doctrine promotion attestation payload_hash does not match promotion payload".into(),
));
}
let schema_version = attestation
.get("schema_version")
.and_then(Value::as_u64)
.and_then(|value| u16::try_from(value).ok())
.ok_or_else(|| {
StoreError::Validation(
"doctrine promotion attestation requires supported schema_version".into(),
)
})?;
let lineage_value = attestation
.get("lineage")
.and_then(|lineage| lineage.get("value"))
.and_then(Value::as_u64)
.ok_or_else(|| {
StoreError::Validation(
"doctrine promotion attestation requires chain_position lineage".into(),
)
})?;
let preimage = AttestationPreimage {
schema_version,
source: SourceIdentity::User,
event_id: string_field(attestation, "event_id")?.to_string(),
payload_hash: expected_payload_hash,
session_id: string_field(attestation, "session_id")?.to_string(),
ledger_id: string_field(attestation, "ledger_id")?.to_string(),
lineage: LineageBinding::ChainPosition(lineage_value),
signed_at,
key_id: key_id.to_string(),
};
let signature = Attestation {
key_id: key_id.to_string(),
signature,
signed_at,
};
verify(&preimage, &signature, &public_key, key_id).map_err(|err| {
StoreError::Validation(format!(
"doctrine promotion attestation verification failed: {err}"
))
})
}
fn promotion_attestation_payload(promotion: &DoctrinePromotion) -> Value {
json!({
"operation": "principle.promote",
"principle_id": promotion.source_principle,
"force": promotion.force,
"reason": promotion.reason,
})
}
fn validate_promotion_falsification(principle: &PrincipleRecord) -> StoreResult<()> {
let falsification = principle
.created_by_json
.get("falsification")
.ok_or_else(|| {
StoreError::Validation(
"doctrine promotion requires recorded principle falsification attempt".into(),
)
})?;
if falsification.get("status").and_then(Value::as_str) != Some("attempted") {
return Err(StoreError::Validation(
"doctrine promotion falsification status must be attempted".into(),
));
}
let failed_attempts = falsification
.get("failed_attempts")
.and_then(Value::as_array)
.ok_or_else(|| {
StoreError::Validation(
"doctrine promotion requires falsification failed_attempts".into(),
)
})?;
if failed_attempts.is_empty()
|| failed_attempts
.iter()
.any(|attempt| attempt.as_str().is_none_or(|text| text.trim().is_empty()))
{
return Err(StoreError::Validation(
"doctrine promotion requires at least one non-empty failed falsification attempt"
.into(),
));
}
let unresolved_high_risk = falsification
.get("unresolved_high_risk_counterexamples")
.and_then(Value::as_array)
.ok_or_else(|| {
StoreError::Validation(
"doctrine promotion requires unresolved_high_risk_counterexamples".into(),
)
})?;
if !unresolved_high_risk.is_empty() {
return Err(StoreError::Validation(
"doctrine promotion blocked by unresolved high-risk counterexample".into(),
));
}
Ok(())
}
fn require_non_empty_str(value: &Value, field: &str) -> StoreResult<()> {
string_field(value, field).map(|_| ())
}
fn string_field<'a>(value: &'a Value, field: &str) -> StoreResult<&'a str> {
let Some(text) = value.get(field).and_then(Value::as_str) else {
return Err(StoreError::Validation(format!(
"doctrine promotion attestation requires {field}"
)));
};
if text.trim().is_empty() {
return Err(StoreError::Validation(format!(
"doctrine promotion attestation {field} must not be empty"
)));
}
Ok(text)
}
fn require_hex_len(value: &Value, field: &str, len: usize) -> StoreResult<()> {
require_non_empty_str(value, field)?;
let text = value.get(field).and_then(Value::as_str).unwrap_or_default();
if text.len() != len || !text.chars().all(|ch| ch.is_ascii_hexdigit()) {
return Err(StoreError::Validation(format!(
"doctrine promotion attestation {field} must be {len} hex characters"
)));
}
Ok(())
}
fn decode_hex_array<const N: usize>(text: &str) -> StoreResult<[u8; N]> {
if text.len() != N * 2 || !text.chars().all(|ch| ch.is_ascii_hexdigit()) {
return Err(StoreError::Validation(format!(
"doctrine promotion attestation hex field must be {} hex characters",
N * 2
)));
}
let mut out = [0_u8; N];
for (index, byte) in out.iter_mut().enumerate() {
let start = index * 2;
*byte = u8::from_str_radix(&text[start..start + 2], 16).map_err(|err| {
StoreError::Validation(format!(
"doctrine promotion attestation hex decode failed: {err}"
))
})?;
}
Ok(out)
}
fn json_array_len(value: &Value) -> usize {
value.as_array().map_or(0, Vec::len)
}
fn validate_score(value: f64, field: &str) -> StoreResult<()> {
if value.is_finite() && (0.0..=1.0).contains(&value) {
Ok(())
} else {
Err(StoreError::Validation(format!(
"principle candidate {field} must be between 0 and 1"
)))
}
}
fn require_policy_final_outcome(policy: &PolicyDecision, surface: &str) -> StoreResult<()> {
match policy.final_outcome {
PolicyOutcome::Allow | PolicyOutcome::Warn | PolicyOutcome::BreakGlass => Ok(()),
PolicyOutcome::Quarantine | PolicyOutcome::Reject => Err(StoreError::Validation(format!(
"{surface} preflight: composed policy outcome {:?} blocks principle mutation",
policy.final_outcome,
))),
}
}
fn require_contributor_rule(policy: &PolicyDecision, rule_id: &str) -> StoreResult<()> {
let contains_rule = policy
.contributing
.iter()
.chain(policy.discarded.iter())
.any(|contribution| contribution.rule_id.as_str() == rule_id);
if contains_rule {
Ok(())
} else {
Err(StoreError::Validation(format!(
"policy decision missing required contributor `{rule_id}`; caller skipped ADR 0026 composition",
)))
}
}
fn require_contributor_not_break_glassed(
policy: &PolicyDecision,
rule_id: &str,
surface: &str,
) -> StoreResult<()> {
let contribution = policy
.contributing
.iter()
.chain(policy.discarded.iter())
.find(|contribution| contribution.rule_id.as_str() == rule_id)
.ok_or_else(|| {
StoreError::Validation(format!(
"{surface} preflight: required contributor `{rule_id}` is absent from the policy decision",
))
})?;
if contribution.outcome == PolicyOutcome::Allow {
Ok(())
} else {
Err(StoreError::Validation(format!(
"{surface} preflight: contributor `{rule_id}` returned {:?}; ADR 0026 §4 forbids BreakGlass substituting for falsification evidence",
contribution.outcome,
)))
}
}
#[must_use]
pub fn insert_candidate_policy_decision_test_allow() -> PolicyDecision {
use cortex_core::compose_policy_outcomes;
compose_policy_outcomes(
vec![
PolicyContribution::new(
CANDIDATE_FALSIFICATION_RULE_ID,
PolicyOutcome::Allow,
"test fixture: falsification record present",
)
.expect("static test contribution is valid"),
PolicyContribution::new(
CANDIDATE_SUPPORTING_MEMORY_PROOF_RULE_ID,
PolicyOutcome::Allow,
"test fixture: supporting memory proof closure satisfied",
)
.expect("static test contribution is valid"),
],
None,
)
}
#[must_use]
pub fn promote_to_doctrine_policy_decision_test_allow() -> PolicyDecision {
use cortex_core::compose_policy_outcomes;
compose_policy_outcomes(
vec![PolicyContribution::new(
PROMOTION_FALSIFICATION_RULE_ID,
PolicyOutcome::Allow,
"test fixture: promotion falsification record allowed",
)
.expect("static test contribution is valid")],
None,
)
}