use chrono::{DateTime, Utc};
use cortex_core::{
compose_policy_outcomes, AdvisoryFlag, ConsumerAdvisory, ContextPackId, ExecutionTrustClass,
PolicyContribution, PolicyDecision, PolicyOutcome, RenderTrustClass,
};
use rusqlite::{params, OptionalExtension, Row};
use serde_json::Value;
use crate::{Pool, StoreError, StoreResult};
macro_rules! context_pack_select_sql {
($where_clause:literal) => {
concat!(
"SELECT id, task, pack_json, selection_audit, created_at FROM context_packs ",
$where_clause,
";"
)
};
}
pub const INSERT_DEFAULT_USE_ALLOWED_RULE_ID: &str = "contextpack.insert.default_use_allowed";
pub const INSERT_BUILDER_PROVENANCE_RULE_ID: &str = "contextpack.insert.builder_provenance";
pub const CONSUMER_ADVISORY_RENDER_VS_EXEC_RULE_ID: &str =
"contextpack.consumer_advisory.render_vs_exec";
pub const CONSUMER_ADVISORY_TIER_GATE_RULE_ID: &str = "contextpack.consumer_advisory.tier_gate";
#[derive(Debug, Clone, PartialEq)]
pub struct ContextPackRecord {
pub id: ContextPackId,
pub task: String,
pub pack_json: Value,
pub selection_audit: String,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConsumerAdvisorySafety {
SafeDefault,
UnsafeDefault,
}
impl ConsumerAdvisorySafety {
#[must_use]
pub fn classify(advisory: &ConsumerAdvisory) -> Self {
let has_exec_shaped = advisory.flags.contains(&AdvisoryFlag::ContainsExecShaped);
let has_unattested = advisory
.flags
.contains(&AdvisoryFlag::ContainsUnattestedSources);
let has_cross_session_unvalidated = advisory
.flags
.contains(&AdvisoryFlag::ContainsCrossSessionUnvalidated);
let execution_elevated = matches!(
advisory.execution_trust,
ExecutionTrustClass::OperatorExecutionTrusted
);
let render_elevated = matches!(
advisory.render_trust,
RenderTrustClass::OperatorRenderingTrusted
);
if execution_elevated
&& (has_exec_shaped || has_unattested || has_cross_session_unvalidated)
{
return Self::UnsafeDefault;
}
if render_elevated && (has_exec_shaped || has_unattested) {
return Self::UnsafeDefault;
}
Self::SafeDefault
}
}
#[derive(Debug)]
pub struct ContextPackRepo<'a> {
pool: &'a Pool,
}
impl<'a> ContextPackRepo<'a> {
#[must_use]
pub const fn new(pool: &'a Pool) -> Self {
Self { pool }
}
pub fn insert(&self, pack: &ContextPackRecord, policy: &PolicyDecision) -> StoreResult<()> {
require_policy_final_outcome(policy, "contextpack.insert")?;
require_contributor_rule(policy, INSERT_DEFAULT_USE_ALLOWED_RULE_ID)?;
require_contributor_rule(policy, INSERT_BUILDER_PROVENANCE_RULE_ID)?;
validate_context_pack(pack)?;
self.pool.execute(
"INSERT INTO context_packs (
id, task, pack_json, selection_audit, created_at
) VALUES (?1, ?2, ?3, ?4, ?5);",
params![
pack.id.to_string(),
pack.task,
serde_json::to_string(&pack.pack_json)?,
pack.selection_audit,
pack.created_at.to_rfc3339(),
],
)?;
Ok(())
}
pub fn insert_with_consumer_advisory(
&self,
pack: &ContextPackRecord,
advisory: &ConsumerAdvisory,
policy: &PolicyDecision,
) -> StoreResult<()> {
require_policy_final_outcome(policy, "contextpack.insert_with_consumer_advisory")?;
require_contributor_rule(policy, INSERT_DEFAULT_USE_ALLOWED_RULE_ID)?;
require_contributor_rule(policy, INSERT_BUILDER_PROVENANCE_RULE_ID)?;
require_contributor_rule(policy, CONSUMER_ADVISORY_RENDER_VS_EXEC_RULE_ID)?;
require_contributor_rule(policy, CONSUMER_ADVISORY_TIER_GATE_RULE_ID)?;
require_advisory_safe_default(advisory)?;
validate_context_pack(pack)?;
self.pool.execute(
"INSERT INTO context_packs (
id, task, pack_json, selection_audit, created_at, consumer_advisory_json
) VALUES (?1, ?2, ?3, ?4, ?5, ?6);",
params![
pack.id.to_string(),
pack.task,
serde_json::to_string(&pack.pack_json)?,
pack.selection_audit,
pack.created_at.to_rfc3339(),
serde_json::to_string(advisory)?,
],
)?;
Ok(())
}
pub fn get_by_id(&self, id: &ContextPackId) -> StoreResult<Option<ContextPackRecord>> {
let row = self
.pool
.query_row(
context_pack_select_sql!("WHERE id = ?1"),
params![id.to_string()],
context_pack_row,
)
.optional()?;
row.map(TryInto::try_into).transpose()
}
}
#[derive(Debug)]
struct ContextPackRow {
id: String,
task: String,
pack_json: String,
selection_audit: String,
created_at: String,
}
fn context_pack_row(row: &Row<'_>) -> rusqlite::Result<ContextPackRow> {
Ok(ContextPackRow {
id: row.get(0)?,
task: row.get(1)?,
pack_json: row.get(2)?,
selection_audit: row.get(3)?,
created_at: row.get(4)?,
})
}
impl TryFrom<ContextPackRow> for ContextPackRecord {
type Error = StoreError;
fn try_from(row: ContextPackRow) -> StoreResult<Self> {
Ok(Self {
id: row.id.parse()?,
task: row.task,
pack_json: serde_json::from_str(&row.pack_json)?,
selection_audit: row.selection_audit,
created_at: DateTime::parse_from_rfc3339(&row.created_at)?.with_timezone(&Utc),
})
}
}
fn validate_context_pack(pack: &ContextPackRecord) -> StoreResult<()> {
if pack.task.trim().is_empty() {
return Err(StoreError::Validation(
"context pack requires non-empty task".into(),
));
}
if pack.selection_audit.trim().is_empty() {
return Err(StoreError::Validation(
"context pack requires non-empty selection audit".into(),
));
}
Ok(())
}
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 context-pack persistence",
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_advisory_safe_default(advisory: &ConsumerAdvisory) -> StoreResult<()> {
match ConsumerAdvisorySafety::classify(advisory) {
ConsumerAdvisorySafety::SafeDefault => Ok(()),
ConsumerAdvisorySafety::UnsafeDefault => Err(StoreError::Validation(format!(
"contextpack.insert_with_consumer_advisory preflight: ConsumerAdvisory is unsafe by default \
(render_trust={:?}, execution_trust={:?}, flags={:?}); ADR 0016 forbids durable persistence of \
an advisory whose elevation contradicts its own flags",
advisory.render_trust, advisory.execution_trust, advisory.flags,
))),
}
}
#[must_use]
pub fn insert_policy_decision_test_allow() -> PolicyDecision {
compose_policy_outcomes(
vec![
PolicyContribution::new(
INSERT_DEFAULT_USE_ALLOWED_RULE_ID,
PolicyOutcome::Allow,
"test fixture: upstream ContextPack::policy_decision allows default use",
)
.expect("static test contribution is valid"),
PolicyContribution::new(
INSERT_BUILDER_PROVENANCE_RULE_ID,
PolicyOutcome::Allow,
"test fixture: builder provenance validated",
)
.expect("static test contribution is valid"),
],
None,
)
}
#[must_use]
pub fn insert_with_consumer_advisory_policy_decision_test_allow() -> PolicyDecision {
compose_policy_outcomes(
vec![
PolicyContribution::new(
INSERT_DEFAULT_USE_ALLOWED_RULE_ID,
PolicyOutcome::Allow,
"test fixture: upstream ContextPack::policy_decision allows default use",
)
.expect("static test contribution is valid"),
PolicyContribution::new(
INSERT_BUILDER_PROVENANCE_RULE_ID,
PolicyOutcome::Allow,
"test fixture: builder provenance validated",
)
.expect("static test contribution is valid"),
PolicyContribution::new(
CONSUMER_ADVISORY_RENDER_VS_EXEC_RULE_ID,
PolicyOutcome::Allow,
"test fixture: render-vs-exec split composed",
)
.expect("static test contribution is valid"),
PolicyContribution::new(
CONSUMER_ADVISORY_TIER_GATE_RULE_ID,
PolicyOutcome::Allow,
"test fixture: trust tier gate satisfied",
)
.expect("static test contribution is valid"),
],
None,
)
}
#[cfg(test)]
mod tests {
use super::*;
fn safe_default_advisory() -> ConsumerAdvisory {
ConsumerAdvisory::untrusted_default()
}
#[test]
fn safe_default_advisory_classifies_as_safe() {
assert_eq!(
ConsumerAdvisorySafety::classify(&safe_default_advisory()),
ConsumerAdvisorySafety::SafeDefault
);
}
#[test]
fn execution_elevated_with_exec_shaped_is_unsafe_default() {
let advisory = ConsumerAdvisory {
render_trust: RenderTrustClass::UntrustedRendering,
execution_trust: ExecutionTrustClass::OperatorExecutionTrusted,
flags: vec![AdvisoryFlag::ContainsExecShaped],
advisory_text: "operator elevated execution but exec-shaped strings present".into(),
};
assert_eq!(
ConsumerAdvisorySafety::classify(&advisory),
ConsumerAdvisorySafety::UnsafeDefault
);
}
#[test]
fn execution_elevated_with_unattested_sources_is_unsafe_default() {
let advisory = ConsumerAdvisory {
render_trust: RenderTrustClass::UntrustedRendering,
execution_trust: ExecutionTrustClass::OperatorExecutionTrusted,
flags: vec![AdvisoryFlag::ContainsUnattestedSources],
advisory_text: "operator elevated execution despite unattested sources".into(),
};
assert_eq!(
ConsumerAdvisorySafety::classify(&advisory),
ConsumerAdvisorySafety::UnsafeDefault
);
}
#[test]
fn render_elevated_with_exec_shaped_is_unsafe_default() {
let advisory = ConsumerAdvisory {
render_trust: RenderTrustClass::OperatorRenderingTrusted,
execution_trust: ExecutionTrustClass::UntrustedExecution,
flags: vec![AdvisoryFlag::ContainsExecShaped],
advisory_text: "render elevated but exec-shaped strings present".into(),
};
assert_eq!(
ConsumerAdvisorySafety::classify(&advisory),
ConsumerAdvisorySafety::UnsafeDefault
);
}
#[test]
fn execution_elevated_without_hazardous_flags_is_safe_default() {
let advisory = ConsumerAdvisory {
render_trust: RenderTrustClass::UntrustedRendering,
execution_trust: ExecutionTrustClass::OperatorExecutionTrusted,
flags: vec![AdvisoryFlag::RedactedDefaultPolicy],
advisory_text: "operator elevated execution after vetting".into(),
};
assert_eq!(
ConsumerAdvisorySafety::classify(&advisory),
ConsumerAdvisorySafety::SafeDefault
);
}
}