use crate::config::LlmBackend;
use async_trait::async_trait;
use clap::Args;
use cortex_core::{
compose_policy_outcomes, Attestor, AuthorityClass, BreakGlassAuthorization,
BreakGlassReasonCode, ClaimCeiling, ClaimProofState, InMemoryAttestor, PolicyContribution,
PolicyDecision, PolicyOutcome, RuntimeMode, TrustTier,
};
use cortex_ledger::{
verify_signed_chain, JsonlLog, APPEND_ATTESTATION_REQUIRED_RULE_ID,
APPEND_EVENT_SOURCE_TIER_GATE_RULE_ID, APPEND_RUNTIME_MODE_RULE_ID,
APPEND_SIGNED_KEY_STATE_CURRENT_USE_RULE_ID, APPEND_SIGNED_TRUST_TIER_MINIMUM_RULE_ID,
};
use cortex_llm::{
blake3_hex, validate_ollama_model_ref, LlmAdapter, LlmError, LlmMessage, LlmRequest,
LlmResponse, LlmRole, MaxSensitivity, OllamaHttpAdapter, OpenAiCompatAdapter,
SensitivityGateResult,
};
use cortex_runtime::{compile_runtime_claim, run_configured, Run, RuntimeClaimKind};
use cortex_store::mirror::{self, MIRROR_APPEND_PARITY_INVARIANT_RULE_ID};
use cortex_store::repo::memories::MemoryRepo;
use cortex_store::Pool;
use std::fs;
use std::path::{Path, PathBuf};
use crate::cmd::context::BuildArgs;
use crate::cmd::open_default_store;
use crate::cmd::temporal::{revalidate_operator_temporal_authority, revalidation_failed_invariant};
use crate::exit::Exit;
use crate::output::{self, Envelope};
use crate::paths::DataLayout;
pub const RUN_PERSIST_CONTEXT_POLICY_OUTCOME_RULE_ID: &str = "run.persist.context_policy_outcome";
pub const RUN_PERSIST_RUNTIME_MODE_GATE_RULE_ID: &str = "run.persist.runtime_mode_gate";
pub const RUN_PERSIST_DEVELOPMENT_LEDGER_AUTHORITY_RULE_ID: &str =
"run.persist.development_ledger_authority";
#[derive(Debug, Args)]
pub struct RunArgs {
#[arg(long)]
pub task: String,
#[arg(long, default_value = "replay")]
pub model: String,
#[arg(long = "trusted-history")]
pub trusted_history: bool,
#[arg(long, value_name = "KEY_PATH")]
pub attestation: Option<PathBuf>,
#[arg(long = "break-glass", value_name = "JSON")]
pub break_glass: Option<String>,
#[arg(long)]
pub stream: bool,
}
pub fn run(args: RunArgs) -> Exit {
if args.task.trim().is_empty() {
eprintln!("cortex run: --task must not be empty");
return run_failure_envelope(Exit::Usage, "--task must not be empty");
}
if args.model.trim().is_empty() {
eprintln!("cortex run: --model must not be empty");
return run_failure_envelope(Exit::Usage, "--model must not be empty");
}
if let Some(model) = args.model.trim().strip_prefix("ollama:") {
if let Err(err) = validate_ollama_model_ref(model) {
eprintln!("cortex run: {err}; no state was changed");
return run_failure_envelope(
Exit::PreconditionUnmet,
&format!("invalid ollama model ref: {err}"),
);
}
}
let break_glass = match parse_break_glass_flag(args.break_glass.as_deref()) {
Ok(authorization) => authorization,
Err(exit) => return run_failure_envelope(exit, "invalid --break-glass envelope"),
};
let pack = match crate::cmd::context::build_pack(BuildArgs {
task: args.task.clone(),
max_tokens: 4096,
axiom_constraints: false,
tag: Vec::new(),
fuzzy: false,
include_doctrine: false,
}) {
Ok(pack) => {
if let Err(err) = pack.require_default_use_allowed() {
eprintln!("cortex run: {err}");
return run_failure_envelope(Exit::PreconditionUnmet, &err.to_string());
}
pack
}
Err(exit) => return run_failure_envelope(exit, "context pack build failed"),
};
let mut run = match Run::new(args.task, pack) {
Ok(run) => run,
Err(err) => {
eprintln!("cortex run: {err}");
return run_failure_envelope(Exit::PreconditionUnmet, &err.to_string());
}
};
run.model = args.model;
let runtime = match tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
{
Ok(runtime) => runtime,
Err(err) => {
eprintln!("cortex run: failed to create runtime: {err}");
return run_failure_envelope(
Exit::Internal,
&format!("failed to create runtime: {err}"),
);
}
};
let backend = if let Some(model) = run.model.strip_prefix("ollama:") {
let endpoint = std::env::var("CORTEX_LLM_ENDPOINT")
.unwrap_or_else(|_| "http://localhost:11434".to_string());
LlmBackend::Ollama {
endpoint,
model: model.to_string(),
timeout_ms: 30_000,
}
} else {
LlmBackend::resolve()
};
let offline_adapter_storage;
let ollama_adapter_storage;
let claude_adapter_storage;
let openai_compat_adapter_storage;
let adapter: &dyn LlmAdapter = match &backend {
LlmBackend::Offline => {
offline_adapter_storage = OfflineAdapter;
&offline_adapter_storage
}
LlmBackend::Claude {
model,
max_sensitivity,
..
} => {
let sensitivity = max_sensitivity
.parse::<MaxSensitivity>()
.unwrap_or(MaxSensitivity::Medium);
match cortex_llm::ClaudeHttpAdapter::new(model.clone(), Some(sensitivity)) {
Ok(a) => {
claude_adapter_storage = a;
&claude_adapter_storage
}
Err(err) => {
eprintln!(
"cortex run: ClaudeHttpAdapter init failed: {err}; no state was changed"
);
return run_failure_envelope(Exit::PreconditionUnmet, &err.to_string());
}
}
}
LlmBackend::Ollama {
endpoint,
model,
timeout_ms: _timeout_ms,
} => {
use cortex_llm::OllamaConfig;
let config = OllamaConfig {
endpoint_url: endpoint.clone(),
model: model.clone(),
};
match OllamaHttpAdapter::new(config) {
Ok(a) => {
ollama_adapter_storage = a;
&ollama_adapter_storage
}
Err(err) => {
eprintln!("cortex run: invalid ollama config: {err}; falling back to offline");
offline_adapter_storage = OfflineAdapter;
&offline_adapter_storage
}
}
}
LlmBackend::OpenAiCompat {
base_url,
model,
api_key,
timeout_ms,
max_sensitivity,
} => {
let parsed_sensitivity = max_sensitivity
.parse::<MaxSensitivity>()
.unwrap_or(MaxSensitivity::Medium);
match OpenAiCompatAdapter::new(
base_url.clone(),
model.clone(),
api_key.clone(),
*timeout_ms,
Some(parsed_sensitivity),
) {
Ok(a) => {
openai_compat_adapter_storage = a;
&openai_compat_adapter_storage
}
Err(err) => {
eprintln!(
"cortex run: invalid openai-compat config: {err}; no state was changed"
);
return run_failure_envelope(Exit::PreconditionUnmet, &err.to_string());
}
}
}
};
if let LlmBackend::Claude {
max_sensitivity, ..
} = &backend
{
let configured_max = max_sensitivity
.parse::<MaxSensitivity>()
.unwrap_or(MaxSensitivity::Medium);
match open_default_store("run") {
Ok(pool) => {
let repo = MemoryRepo::new(&pool);
match repo.max_sensitivity_for_active_memories() {
Ok(memory_max_str) => {
let gate = SensitivityGateResult::evaluate(&memory_max_str, configured_max);
tracing::info!(
max_memory_sensitivity = %gate.max_memory_sensitivity,
configured_max = ?gate.configured_max,
allowed = gate.allowed,
"remote prompt domain-tag sensitivity gate"
);
if !gate.allowed {
let msg = format!(
"sensitivity_exceeds_remote_threshold: memories at {} exceed configured max_sensitivity {}; remote dispatch refused",
gate.max_memory_sensitivity,
max_sensitivity,
);
eprintln!("cortex run: {msg}");
return run_failure_envelope(Exit::PreconditionUnmet, &msg);
}
}
Err(err) => {
eprintln!(
"cortex run: domain-tag sensitivity gate: store query failed: {err}; refusing remote dispatch"
);
return run_failure_envelope(
Exit::Internal,
&format!("sensitivity gate store query failed: {err}"),
);
}
}
}
Err(exit) => return run_failure_envelope(exit, "sensitivity gate: store open failed"),
}
}
if args.stream {
let stream_request = LlmRequest {
model: run.model.clone(),
system: run.system.clone(),
messages: vec![LlmMessage {
role: LlmRole::User,
content: serde_json::json!({
"task": run.task,
"context_pack_id": run.pack.context_pack_id,
"context_pack": run.pack,
})
.to_string(),
}],
temperature: run.temperature,
max_tokens: run.max_tokens,
json_schema: None,
timeout_ms: run.timeout_ms,
};
let stream_result = runtime.block_on(async {
use futures::StreamExt as _;
let stream = adapter.stream_boxed(stream_request);
futures::pin_mut!(stream);
while let Some(chunk_result) = stream.next().await {
match chunk_result {
Ok(chunk) => {
use std::io::Write as _;
print!("{}", chunk.delta);
let _ = std::io::stdout().flush();
}
Err(e) => return Err(e),
}
}
Ok(())
});
match stream_result {
Ok(()) => {
println!();
}
Err(e) => {
eprintln!("cortex run --stream: stream error: {e}");
return run_failure_envelope(Exit::PreconditionUnmet, &e.to_string());
}
}
}
match runtime.block_on(run_configured(run, adapter)) {
Ok(mut report) => {
let persist_result = if args.trusted_history {
match args.attestation.as_ref() {
Some(path) => match load_attestor_from_key_file(path) {
Ok(attestor) => persist_signed_agent_response_event(&mut report, &attestor),
Err(exit) => Err(exit),
},
None => {
eprintln!(
"cortex run: --trusted-history requires --attestation <KEY_PATH>; no state was changed"
);
Err(Exit::PreconditionUnmet)
}
}
} else if args.attestation.is_some() {
eprintln!(
"cortex run: --attestation is only valid with --trusted-history; no state was changed"
);
Err(Exit::Usage)
} else {
persist_agent_response_event(&mut report, break_glass.as_ref())
};
match persist_result {
Ok(sealed_event) => {
report.agent_response_event = sealed_event;
if output::json_enabled() {
let payload = match serde_json::to_value(&report) {
Ok(value) => value,
Err(err) => {
eprintln!("cortex run: failed to serialize run report: {err}");
return Exit::Internal;
}
};
let envelope = Envelope::new("cortex.run", Exit::Ok, payload);
return output::emit(&envelope, Exit::Ok);
}
match serde_json::to_string_pretty(&report) {
Ok(serialized) => {
println!("{serialized}");
Exit::Ok
}
Err(err) => {
eprintln!("cortex run: failed to serialize run report: {err}");
Exit::Internal
}
}
}
Err(exit) => run_failure_envelope(exit, "persistence failed"),
}
}
Err(err) => {
eprintln!("cortex run: {err}");
run_failure_envelope(Exit::PreconditionUnmet, &err.to_string())
}
}
}
fn run_failure_envelope(exit: Exit, detail: &str) -> Exit {
if !output::json_enabled() {
return exit;
}
let payload = serde_json::json!({
"status": "error",
"detail": detail,
});
let envelope = Envelope::new("cortex.run", exit, payload);
output::emit(&envelope, exit)
}
pub const RUN_PERSIST_OPERATOR_DIAGNOSTIC_OVERRIDE_RULE_ID: &str =
"run.persist.operator_diagnostic_override";
#[must_use]
pub fn run_persist_policy_decision(
context_policy_outcome: PolicyOutcome,
runtime_mode: RuntimeMode,
break_glass: Option<&BreakGlassAuthorization>,
) -> PolicyDecision {
let context_contribution = PolicyContribution::new(
RUN_PERSIST_CONTEXT_POLICY_OUTCOME_RULE_ID,
context_policy_outcome,
format!(
"context pack composed policy outcome {context_policy_outcome:?} replays into persistence"
),
)
.expect("static policy contribution is valid")
.allow_break_glass_override();
let (runtime_outcome, runtime_reason) = runtime_mode_persist_outcome(runtime_mode);
let runtime_contribution = PolicyContribution::new(
RUN_PERSIST_RUNTIME_MODE_GATE_RULE_ID,
runtime_outcome,
runtime_reason,
)
.expect("static policy contribution is valid")
.allow_break_glass_override();
let development_contribution = PolicyContribution::new(
RUN_PERSIST_DEVELOPMENT_LEDGER_AUTHORITY_RULE_ID,
PolicyOutcome::Allow,
"development ledger floor allows unsigned append; trusted-history requires the signed path",
)
.expect("static policy contribution is valid")
.allow_break_glass_override();
let mut contributions = vec![
context_contribution,
runtime_contribution,
development_contribution,
];
if let Some(authorization) = break_glass {
if authorization.is_valid() {
contributions.push(
PolicyContribution::new(
RUN_PERSIST_OPERATOR_DIAGNOSTIC_OVERRIDE_RULE_ID,
PolicyOutcome::BreakGlass,
"operator opted in to diagnostic-only persistence via --break-glass",
)
.expect("static policy contribution is valid"),
);
}
}
compose_policy_outcomes(contributions, break_glass.cloned())
}
fn runtime_mode_persist_outcome(runtime_mode: RuntimeMode) -> (PolicyOutcome, String) {
match runtime_mode {
RuntimeMode::Unknown => (
PolicyOutcome::Reject,
"runtime mode unknown; ADR 0037 forbids treating absent runtime mode as durable authority".into(),
),
RuntimeMode::Dev => (
PolicyOutcome::Quarantine,
"runtime mode dev produces diagnostic-only artifacts and must not promote to trusted run-history".into(),
),
RuntimeMode::RemoteUnsigned => (
PolicyOutcome::Quarantine,
"runtime mode remote_unsigned: remote API response is unsigned and cannot be locally verified; must not persist as trusted local evidence without operator opt-in".into(),
),
RuntimeMode::LocalUnsigned => (
PolicyOutcome::Allow,
"runtime mode local_unsigned permits default unsigned persistence".into(),
),
RuntimeMode::SignedLocalLedger
| RuntimeMode::ExternallyAnchored
| RuntimeMode::AuthorityGrade => (
PolicyOutcome::Warn,
format!(
"runtime mode {runtime_mode:?} expected the signed persistence path; the unsigned mirror is being used regardless"
),
),
}
}
fn parse_break_glass_flag(raw: Option<&str>) -> Result<Option<BreakGlassAuthorization>, Exit> {
let Some(value) = raw else {
return Ok(None);
};
let trimmed = value.trim();
if trimmed.is_empty() {
eprintln!("cortex run: --break-glass JSON must not be empty; no state was changed");
return Err(Exit::Usage);
}
let parsed: BreakGlassAuthorization = serde_json::from_str(trimmed).map_err(|err| {
eprintln!(
"cortex run: --break-glass JSON does not parse as BreakGlassAuthorization: {err}; no state was changed"
);
Exit::Usage
})?;
if parsed.reason_code != BreakGlassReasonCode::DiagnosticOnly {
eprintln!(
"cortex run: --break-glass reason_code must be diagnostic_only for run persistence; got {:?}; no state was changed",
parsed.reason_code
);
return Err(Exit::Usage);
}
if !parsed.is_valid() {
eprintln!(
"cortex run: --break-glass authorization is not bound (operation_type, artifact_refs, attested, permitted required); no state was changed"
);
return Err(Exit::PreconditionUnmet);
}
Ok(Some(parsed))
}
fn persist_agent_response_event(
report: &mut cortex_runtime::RunReport,
break_glass: Option<&BreakGlassAuthorization>,
) -> Result<cortex_core::Event, Exit> {
let decision = run_persist_policy_decision(
report.context_policy_outcome,
report.runtime_mode,
break_glass,
);
match decision.final_outcome {
PolicyOutcome::Allow | PolicyOutcome::Warn => {}
PolicyOutcome::BreakGlass => {
mark_event_as_diagnostic_only(&mut report.agent_response_event, &decision);
}
PolicyOutcome::Reject | PolicyOutcome::Quarantine => {
emit_persist_policy_refusal(&decision);
return Err(Exit::PreconditionUnmet);
}
}
let layout = DataLayout::resolve(None, None)?;
let mut pool = open_default_store("run")?;
let mut log = JsonlLog::open(&layout.event_log_path).map_err(|err| {
eprintln!(
"cortex run: failed to open event log {}: {err}",
layout.event_log_path.display()
);
Exit::Internal
})?;
let ledger_policy = local_development_ledger_policy();
let mirror_policy = mirror_parity_satisfied_policy();
mirror::append_event(
&mut log,
&mut pool,
report.agent_response_event.clone(),
&ledger_policy,
&mirror_policy,
)
.map_err(|err| {
eprintln!("cortex run: failed to persist agent response event: {err}");
Exit::Internal
})
}
fn persist_signed_agent_response_event(
report: &mut cortex_runtime::RunReport,
attestor: &InMemoryAttestor,
) -> Result<cortex_core::Event, Exit> {
let layout = DataLayout::resolve(None, None)?;
let mut pool = open_default_store("run")?;
let mut log = JsonlLog::open(&layout.event_log_path).map_err(|err| {
eprintln!(
"cortex run: failed to open event log {}: {err}",
layout.event_log_path.display()
);
Exit::Internal
})?;
let preflight = verify_signed_chain(
&layout.event_log_path,
&attestor.verifying_key(),
attestor.key_id(),
)
.map_err(|err| {
eprintln!(
"cortex run: trusted run history denied: signed ledger verification failed before append: {err}"
);
Exit::PreconditionUnmet
})?;
if !preflight.report.ok() {
eprintln!(
"cortex run: trusted run history denied: existing ledger is not a clean signed chain; no state was changed"
);
return Err(Exit::PreconditionUnmet);
}
let claim = compile_runtime_claim(
"trusted run history",
RuntimeClaimKind::TrustedHistory,
RuntimeMode::SignedLocalLedger,
AuthorityClass::Verified,
ClaimProofState::FullChainVerified,
ClaimCeiling::SignedLocalLedger,
);
if !claim.allowed {
eprintln!(
"cortex run: trusted run history denied: {}; no state was changed",
claim
.reasons
.last()
.cloned()
.unwrap_or_else(|| "claim preflight failed".to_string())
);
return Err(Exit::PreconditionUnmet);
}
report.runtime_mode = claim.runtime_mode;
report.proof_state = claim.proof_state;
report.claim_ceiling = claim.effective_ceiling;
report.trusted_run_history = true;
report.downgrade_reasons = claim.reasons;
mark_event_as_signed_local(&mut report.agent_response_event);
report.refresh_observability();
let _ = preflight;
let signed_ledger_policy = build_signed_local_ledger_policy(&pool, attestor)?;
let mirror_policy = mirror_parity_satisfied_policy();
let sealed = mirror::append_signed_event(
&mut log,
&mut pool,
report.agent_response_event.clone(),
attestor,
&signed_ledger_policy,
&mirror_policy,
)
.map_err(|err| {
eprintln!("cortex run: failed to persist signed agent response event: {err}");
Exit::Internal
})?;
let postflight = verify_signed_chain(
&layout.event_log_path,
&attestor.verifying_key(),
attestor.key_id(),
)
.map_err(|err| {
eprintln!(
"cortex run: trusted run history denied: signed ledger verification failed after append: {err}"
);
Exit::PreconditionUnmet
})?;
if !postflight.report.ok() {
eprintln!("cortex run: trusted run history denied: signed ledger verification reported failures after append");
return Err(Exit::PreconditionUnmet);
}
Ok(sealed)
}
fn mark_event_as_signed_local(event: &mut cortex_core::Event) {
if let Some(payload) = event.payload.as_object_mut() {
payload.insert(
"ledger_authority".to_string(),
serde_json::json!("signed_local"),
);
payload.insert(
"signed_ledger_authority".to_string(),
serde_json::json!(true),
);
payload.insert("trusted_run_history".to_string(), serde_json::json!(true));
payload.insert(
"forbidden_uses".to_string(),
serde_json::json!([
"audit_export",
"compliance_evidence",
"cross_system_trust_decision",
"external_reporting"
]),
);
}
}
fn mark_event_as_diagnostic_only(event: &mut cortex_core::Event, decision: &PolicyDecision) {
if let Some(payload) = event.payload.as_object_mut() {
payload.insert(
"ledger_authority".to_string(),
serde_json::json!("development_diagnostic_only"),
);
payload.insert(
"policy_outcome".to_string(),
serde_json::json!(decision.final_outcome),
);
payload.insert(
"policy_break_glass".to_string(),
serde_json::to_value(&decision.break_glass).unwrap_or(serde_json::Value::Null),
);
payload.insert(
"forbidden_uses".to_string(),
serde_json::json!([
"trusted_run_history",
"audit_export",
"compliance_evidence",
"cross_system_trust_decision",
"external_reporting"
]),
);
}
}
fn emit_persist_policy_refusal(decision: &PolicyDecision) {
let contributing_rules: Vec<&str> = decision
.contributing
.iter()
.map(|contribution| contribution.rule_id.as_str())
.collect();
eprintln!(
"cortex run: persistence refused by ADR 0026 policy outcome {:?}; contributing rules: [{}]; no state was changed",
decision.final_outcome,
contributing_rules.join(", ")
);
match serde_json::to_string(decision) {
Ok(serialized) => eprintln!("{serialized}"),
Err(err) => {
eprintln!("cortex run: failed to serialize policy refusal explainability: {err}")
}
}
}
fn local_development_ledger_policy() -> PolicyDecision {
compose_policy_outcomes(
vec![
PolicyContribution::new(
APPEND_EVENT_SOURCE_TIER_GATE_RULE_ID,
PolicyOutcome::Allow,
"cortex run: agent-response source tier gate satisfied",
)
.expect("static policy contribution is valid"),
PolicyContribution::new(
APPEND_ATTESTATION_REQUIRED_RULE_ID,
PolicyOutcome::Allow,
"cortex run: non-user agent-response does not require user attestation",
)
.expect("static policy contribution is valid"),
PolicyContribution::new(
APPEND_RUNTIME_MODE_RULE_ID,
PolicyOutcome::Warn,
"cortex run: unsigned local-development ledger row (ADR 0037 §2 DevOnly)",
)
.expect("static policy contribution is valid"),
],
None,
)
}
fn build_signed_local_ledger_policy(
pool: &Pool,
attestor: &InMemoryAttestor,
) -> Result<PolicyDecision, Exit> {
let invariant = revalidation_failed_invariant("run.trusted_history");
let now = chrono::Utc::now();
let key_state = revalidate_operator_temporal_authority(
pool,
APPEND_SIGNED_KEY_STATE_CURRENT_USE_RULE_ID,
attestor.key_id(),
now,
TrustTier::Verified,
)
.map_err(|err| {
eprintln!(
"cortex run: {invariant}: failed to read authority timeline for key {}: {err}; no state was changed",
attestor.key_id(),
);
Exit::PreconditionUnmet
})?;
if !key_state.report.valid_now {
let reasons = key_state
.report
.reasons
.iter()
.map(|reason| reason.wire_str())
.collect::<Vec<_>>()
.join(",");
eprintln!(
"cortex run: {invariant}: operator temporal authority current use blocked for key {} (reasons: {reasons}); no state was changed",
key_state.report.key_id,
);
return Err(Exit::PreconditionUnmet);
}
let trust_tier = revalidate_operator_temporal_authority(
pool,
APPEND_SIGNED_TRUST_TIER_MINIMUM_RULE_ID,
attestor.key_id(),
now,
TrustTier::Verified,
)
.map_err(|err| {
eprintln!(
"cortex run: {invariant}: failed to read principal timeline for key {}: {err}; no state was changed",
attestor.key_id(),
);
Exit::PreconditionUnmet
})?;
let contributions = vec![key_state.contribution(), trust_tier.contribution()];
Ok(compose_policy_outcomes(contributions, None))
}
fn mirror_parity_satisfied_policy() -> PolicyDecision {
compose_policy_outcomes(
vec![PolicyContribution::new(
MIRROR_APPEND_PARITY_INVARIANT_RULE_ID,
PolicyOutcome::Allow,
"cortex run: mirror parity preflight passes for an empty-or-consistent ledger",
)
.expect("static policy contribution is valid")],
None,
)
}
fn load_attestor_from_key_file(path: &Path) -> Result<InMemoryAttestor, Exit> {
let bytes = fs::read(path).map_err(|err| {
eprintln!(
"cortex run: cannot read --attestation key file `{}`: {err}; no state was changed",
path.display()
);
Exit::PreconditionUnmet
})?;
if bytes.len() != 32 {
eprintln!(
"cortex run: --attestation key file `{}` must be exactly 32 raw bytes (Ed25519 seed); got {} bytes; no state was changed",
path.display(),
bytes.len()
);
return Err(Exit::PreconditionUnmet);
}
let mut seed = [0u8; 32];
seed.copy_from_slice(&bytes);
Ok(InMemoryAttestor::from_seed(&seed))
}
#[derive(Debug)]
struct OfflineAdapter;
#[async_trait]
impl LlmAdapter for OfflineAdapter {
fn adapter_id(&self) -> &'static str {
"cli-offline"
}
async fn complete(&self, req: LlmRequest) -> Result<LlmResponse, LlmError> {
let text = format!("offline response for {}", req.model);
Ok(LlmResponse {
text: text.clone(),
parsed_json: None,
model: req.model,
usage: None,
raw_hash: blake3_hex(text.as_bytes()),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Utc;
use cortex_core::{
BreakGlassAuthorization, BreakGlassReasonCode, BreakGlassScope, Event, EventId,
EventSource, EventType, PolicyOutcome, RuntimeMode, SCHEMA_VERSION,
};
use cortex_runtime::{RunObservability, RunReport, RunTraceStatus};
fn fake_event(text: &str) -> Event {
Event {
id: EventId::new(),
schema_version: SCHEMA_VERSION,
observed_at: Utc::now(),
recorded_at: Utc::now(),
source: EventSource::ChildAgent {
model: "replay".into(),
},
event_type: EventType::AgentResponse,
trace_id: None,
session_id: Some("test".into()),
domain_tags: vec![],
payload: serde_json::json!({"text": text}),
payload_hash: String::new(),
prev_event_hash: None,
event_hash: String::new(),
}
}
fn fake_report(context_policy_outcome: PolicyOutcome, runtime_mode: RuntimeMode) -> RunReport {
RunReport {
correlation_id: cortex_core::CorrelationId::new(),
task: "diagnose".into(),
context_pack_id: cortex_core::ContextPackId::new(),
run_observability: RunObservability {
audit_schema_version: 1,
operation: "runtime.run".into(),
status: RunTraceStatus::Completed,
correlation_id: cortex_core::CorrelationId::new(),
context_pack_id: cortex_core::ContextPackId::new(),
adapter_id: "cli-offline".into(),
model: "replay".into(),
runtime_mode,
proof_state: ClaimProofState::Partial,
claim_ceiling: ClaimCeiling::LocalUnsigned,
trusted_run_history: false,
context_policy_outcome,
response_hash: "deadbeef".into(),
},
adapter_id: "cli-offline".into(),
model: "replay".into(),
raw_hash: "deadbeef".into(),
usage: None,
prompt_hash: "feedface".into(),
runtime_mode,
proof_state: ClaimProofState::Partial,
claim_ceiling: ClaimCeiling::LocalUnsigned,
trusted_run_history: false,
downgrade_reasons: vec![],
context_policy_outcome,
agent_response_event: fake_event("offline response for replay"),
}
}
fn diagnostic_break_glass() -> BreakGlassAuthorization {
BreakGlassAuthorization {
permitted: true,
attested: true,
scope: BreakGlassScope {
operation_type: "run.persist".into(),
artifact_refs: vec!["agent_response_event".into()],
not_before: None,
not_after: None,
},
reason_code: BreakGlassReasonCode::DiagnosticOnly,
}
}
#[test]
fn allow_pack_composes_allow_persistence_decision() {
let decision =
run_persist_policy_decision(PolicyOutcome::Allow, RuntimeMode::LocalUnsigned, None);
assert_eq!(decision.final_outcome, PolicyOutcome::Allow);
assert!(decision.break_glass.is_none());
let rule_ids: Vec<&str> = decision
.contributing
.iter()
.map(|c| c.rule_id.as_str())
.collect();
assert!(rule_ids.contains(&RUN_PERSIST_CONTEXT_POLICY_OUTCOME_RULE_ID));
assert!(rule_ids.contains(&RUN_PERSIST_RUNTIME_MODE_GATE_RULE_ID));
assert!(rule_ids.contains(&RUN_PERSIST_DEVELOPMENT_LEDGER_AUTHORITY_RULE_ID));
}
#[test]
fn reject_pack_composes_reject_persistence_decision_without_break_glass() {
let decision =
run_persist_policy_decision(PolicyOutcome::Reject, RuntimeMode::LocalUnsigned, None);
assert_eq!(decision.final_outcome, PolicyOutcome::Reject);
assert!(decision.break_glass.is_none());
}
#[test]
fn quarantine_pack_composes_quarantine_persistence_decision_without_break_glass() {
let decision = run_persist_policy_decision(
PolicyOutcome::Quarantine,
RuntimeMode::LocalUnsigned,
None,
);
assert_eq!(decision.final_outcome, PolicyOutcome::Quarantine);
assert!(decision.break_glass.is_none());
}
#[test]
fn diagnostic_break_glass_elevates_quarantine_to_break_glass_with_forbidden_uses() {
let break_glass = diagnostic_break_glass();
let decision = run_persist_policy_decision(
PolicyOutcome::Quarantine,
RuntimeMode::LocalUnsigned,
Some(&break_glass),
);
assert_eq!(decision.final_outcome, PolicyOutcome::BreakGlass);
assert!(decision.break_glass.is_some());
assert_eq!(
decision.break_glass.as_ref().unwrap().reason_code,
BreakGlassReasonCode::DiagnosticOnly
);
}
#[test]
fn diagnostic_break_glass_elevates_reject_to_break_glass() {
let break_glass = diagnostic_break_glass();
let decision = run_persist_policy_decision(
PolicyOutcome::Reject,
RuntimeMode::LocalUnsigned,
Some(&break_glass),
);
assert_eq!(decision.final_outcome, PolicyOutcome::BreakGlass);
}
#[test]
fn unbound_break_glass_does_not_elevate_reject() {
let mut bg = diagnostic_break_glass();
bg.attested = false;
let decision = run_persist_policy_decision(
PolicyOutcome::Reject,
RuntimeMode::LocalUnsigned,
Some(&bg),
);
assert_eq!(decision.final_outcome, PolicyOutcome::Reject);
assert!(decision.break_glass.is_none());
}
#[test]
fn unknown_runtime_mode_rejects_persistence() {
let decision =
run_persist_policy_decision(PolicyOutcome::Allow, RuntimeMode::Unknown, None);
assert_eq!(decision.final_outcome, PolicyOutcome::Reject);
}
#[test]
fn dev_runtime_mode_quarantines_persistence() {
let decision = run_persist_policy_decision(PolicyOutcome::Allow, RuntimeMode::Dev, None);
assert_eq!(decision.final_outcome, PolicyOutcome::Quarantine);
}
#[test]
fn mark_event_as_diagnostic_only_sets_forbidden_uses() {
let mut event = fake_event("diag");
let decision = run_persist_policy_decision(
PolicyOutcome::Reject,
RuntimeMode::LocalUnsigned,
Some(&diagnostic_break_glass()),
);
mark_event_as_diagnostic_only(&mut event, &decision);
let payload = event.payload.as_object().expect("payload object");
assert_eq!(payload["ledger_authority"], "development_diagnostic_only");
assert_eq!(payload["policy_outcome"], "break_glass");
let forbidden = payload["forbidden_uses"]
.as_array()
.expect("forbidden_uses array");
let names: Vec<&str> = forbidden
.iter()
.filter_map(serde_json::Value::as_str)
.collect();
assert!(names.contains(&"trusted_run_history"));
assert!(names.contains(&"audit_export"));
}
#[test]
fn parse_break_glass_flag_rejects_wrong_reason_code() {
let bad = serde_json::json!({
"permitted": true,
"attested": true,
"scope": {
"operation_type": "run.persist",
"artifact_refs": ["agent_response_event"],
"not_before": null,
"not_after": null
},
"reason_code": "operator_correction"
})
.to_string();
let err = parse_break_glass_flag(Some(&bad)).expect_err("non-diagnostic reason rejected");
assert_eq!(err, Exit::Usage);
}
#[test]
fn parse_break_glass_flag_rejects_unbound_scope() {
let bad = serde_json::json!({
"permitted": true,
"attested": true,
"scope": {
"operation_type": "",
"artifact_refs": [],
"not_before": null,
"not_after": null
},
"reason_code": "diagnostic_only"
})
.to_string();
let err = parse_break_glass_flag(Some(&bad)).expect_err("unbound scope rejected");
assert_eq!(err, Exit::PreconditionUnmet);
}
#[test]
fn parse_break_glass_flag_accepts_diagnostic_envelope() {
let bg = serde_json::to_string(&diagnostic_break_glass()).unwrap();
let parsed = parse_break_glass_flag(Some(&bg)).expect("diagnostic envelope parses");
let parsed = parsed.expect("envelope produced");
assert_eq!(parsed.reason_code, BreakGlassReasonCode::DiagnosticOnly);
assert!(parsed.is_valid());
}
#[test]
fn fake_report_helper_default_is_allow_outcome() {
let report = fake_report(PolicyOutcome::Allow, RuntimeMode::LocalUnsigned);
assert_eq!(report.context_policy_outcome, PolicyOutcome::Allow);
assert_eq!(report.runtime_mode, RuntimeMode::LocalUnsigned);
}
}