use std::path::{Path, PathBuf};
use std::process::Command;
use chrono::{TimeZone, Utc};
use cortex_core::{
Attestor, ContradictionId, Event, EventSource, EventType, InMemoryAttestor, KeyLifecycleState,
OutcomeMemoryRelation, PrincipleId, TrustTier, SCHEMA_VERSION,
};
use cortex_ledger::signed_row::{b64_decode, b64_encode};
use cortex_ledger::{
append_signed_policy_decision_test_allow, verify_signed_chain, FailureReason, HashKind,
JsonlLog, SignedRow,
};
use cortex_store::migrate::apply_pending;
use cortex_store::repo::contradictions::insert_policy_decision_test_allow as contradiction_insert_policy_test_allow;
use cortex_store::repo::memories::{
accept_candidate_policy_decision_test_allow,
record_outcome_relation_policy_decision_test_allow,
OUTCOME_UTILITY_TO_TRUTH_PROMOTION_UNAUTHORIZED_INVARIANT,
};
use cortex_store::repo::{
AuthorityRepo, ContradictionRecord, ContradictionRepo, ContradictionStatus, EventRepo,
KeyTimelineRecord, MemoryAcceptanceAudit, MemoryCandidate, MemoryRepo,
OutcomeMemoryRelationRecord, PrincipalTimelineRecord, PrincipleCandidateRow, PrincipleRepo,
};
use rusqlite::Connection;
use serde_json::json;
fn cortex_bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_cortex"))
}
fn run_in(cwd: &Path, args: &[&str]) -> std::process::Output {
Command::new(cortex_bin())
.current_dir(cwd)
.env("XDG_DATA_HOME", cwd.join("xdg"))
.env("HOME", cwd)
.args(args)
.output()
.expect("spawn cortex")
}
fn assert_exit(out: &std::process::Output, expected: i32) {
let code = out.status.code().expect("process exited via signal");
assert_eq!(
code,
expected,
"expected exit {expected}, got {code}\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr),
);
}
fn init(tmp: &Path) -> PathBuf {
let out = run_in(tmp, &["init"]);
assert_exit(&out, 0);
let stdout = String::from_utf8_lossy(&out.stdout);
let db_line = stdout
.lines()
.find(|line| line.starts_with("cortex init: db"))
.expect("init stdout includes db path");
let path = db_line
.split_once('=')
.expect("db line has equals")
.1
.trim()
.split_once(" (")
.expect("db line has status suffix")
.0;
PathBuf::from(path)
}
fn at(second: u32) -> chrono::DateTime<Utc> {
Utc.with_ymd_and_hms(2026, 1, 1, 12, 0, second).unwrap()
}
fn insert_candidate(db_path: &Path) -> String {
insert_candidate_with(
db_path,
"mem_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"SQLite retrieval explains durable memory search.",
&["store", "retrieval"],
1,
)
}
fn insert_candidate_with(
db_path: &Path,
memory_id: &str,
claim: &str,
domains: &[&str],
second: u32,
) -> String {
let pool = Connection::open(db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
let repo = MemoryRepo::new(&pool);
let candidate = MemoryCandidate {
id: memory_id.parse().unwrap(),
memory_type: "semantic".into(),
claim: claim.into(),
source_episodes_json: json!([]),
source_events_json: json!(["evt_01ARZ3NDEKTSV4RRFFQ69G5FAV"]),
domains_json: json!(domains),
salience_json: json!({"score": 0.8, "validation": 0.6}),
confidence: 0.9,
authority: "user".into(),
applies_when_json: json!(["querying stored memories"]),
does_not_apply_when_json: json!([]),
created_at: at(second),
updated_at: at(second),
};
let id = candidate.id.to_string();
repo.insert_candidate(&candidate).expect("insert candidate");
id
}
fn ensure_source_event(pool: &Connection, second: u32) {
let event_id = "evt_01ARZ3NDEKTSV4RRFFQ69G5FAV".parse().unwrap();
let repo = EventRepo::new(pool);
if repo
.get_by_id(&event_id)
.expect("query source event")
.is_some()
{
return;
}
repo.append(&Event {
id: event_id,
schema_version: SCHEMA_VERSION,
observed_at: at(second),
recorded_at: at(second),
source: EventSource::Tool {
name: "phase2-cli-test".into(),
},
event_type: EventType::ToolResult,
trace_id: None,
session_id: Some("phase2-cli-test".into()),
domain_tags: vec!["phase2".into(), "test".into()],
payload: json!({"source": "phase2-cli-test", "second": second}),
payload_hash: format!("payload-source-{second}"),
prev_event_hash: None,
event_hash: format!("event-source-{second}"),
})
.expect("append source event");
}
fn insert_active_memory(db_path: &Path) -> String {
insert_active_memory_with(
db_path,
"mem_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"SQLite retrieval explains durable memory search.",
&["store", "retrieval"],
1,
)
}
fn insert_active_memory_with(
db_path: &Path,
memory_id: &str,
claim: &str,
domains: &[&str],
second: u32,
) -> String {
let id = insert_candidate_with(db_path, memory_id, claim, domains, second);
let pool = Connection::open(db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
ensure_source_event(&pool, second);
let repo = MemoryRepo::new(&pool);
let audit = MemoryAcceptanceAudit {
id: format!("aud_01ARZ3NDEKTSV4RRFFQ69G5FA{second}")
.parse()
.unwrap(),
actor_json: json!({"kind": "test"}),
reason: "test active memory".into(),
source_refs_json: json!([id]),
created_at: at(second + 1),
};
let memory_id = memory_id.parse().unwrap();
repo.accept_candidate(
&memory_id,
at(3),
&audit,
&accept_candidate_policy_decision_test_allow(),
)
.expect("accept candidate");
id
}
fn insert_principle_fixture_memories(db_path: &Path) -> Vec<String> {
vec![
insert_active_memory_with(
db_path,
"mem_01ARZ3NDEKTSV4RRFFQ69G5FAW",
"Evidence must be inspected before claiming state.",
&["agents"],
4,
),
insert_active_memory_with(
db_path,
"mem_01ARZ3NDEKTSV4RRFFQ69G5FAX",
"Audit reports need direct observed anchors.",
&["audit"],
6,
),
insert_active_memory_with(
db_path,
"mem_01ARZ3NDEKTSV4RRFFQ69G5FAY",
"Candidate output must stay separate from promotion.",
&["agents"],
8,
),
]
}
fn insert_principle_candidate(db_path: &Path, memory_ids: &[String]) -> PrincipleId {
insert_principle_candidate_with_created_by(
db_path,
memory_ids,
json!({
"kind": "test",
"falsification": {
"status": "attempted",
"failed_attempts": [
"counterexample search found no high-risk blocker in the fixture window"
],
"unresolved_high_risk_counterexamples": []
}
}),
)
}
fn insert_principle_candidate_without_falsification(
db_path: &Path,
memory_ids: &[String],
) -> PrincipleId {
insert_principle_candidate_with_created_by(db_path, memory_ids, json!({"kind": "test"}))
}
fn insert_principle_candidate_with_created_by(
db_path: &Path,
memory_ids: &[String],
created_by_json: serde_json::Value,
) -> PrincipleId {
let pool = Connection::open(db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
let repo = PrincipleRepo::new(&pool);
let principle_id = "prn_01ARZ3NDEKTSV4RRFFQ69G5FAV"
.parse()
.expect("valid principle id");
repo.insert_candidate(
&PrincipleCandidateRow {
id: principle_id,
statement: "Preserve evidence-bound guidance before doctrine promotion.".into(),
status: "candidate".into(),
supporting_memories_json: json!(memory_ids),
contradicting_memories_json: json!([]),
domains_observed_json: json!(["agents", "audit"]),
applies_when_json: json!(["the pattern recurs across domains"]),
does_not_apply_when_json: json!(["support is below threshold"]),
confidence: 0.72,
validation: 0.6,
brightness: 0.7,
created_by_json,
created_at: at(10),
updated_at: at(10),
},
&cortex_store::repo::principles::insert_candidate_policy_decision_test_allow(),
)
.expect("insert principle candidate");
principle_id
}
fn count(db_path: &Path, sql: &str) -> i64 {
let pool = Connection::open(db_path).expect("open initialized sqlite db");
pool.query_row(sql, [], |row| row.get(0)).unwrap()
}
fn valid_axiom_admission_envelope() -> serde_json::Value {
json!({
"candidate_state": "candidate",
"evidence_class": "observed",
"phase_context": "work_record",
"tool_provenance": {
"tool_name": "codex",
"invocation_id": "cli_phase2_axiom_admission",
"import_class": "agent_procedure"
},
"source_anchors": [{
"reference": "cmd:cargo test -p cortex --test cli_phase2 memory",
"kind": "artifact"
}],
"redaction_status": "abstracted",
"proof_state": "partial",
"contradiction_scan": "scanned_clean",
"explicit_non_promotion": true
})
}
fn promotion_store_snapshot(db_path: &Path, principle_id: &PrincipleId) -> (i64, i64, String) {
let pool = Connection::open(db_path).expect("open initialized sqlite db");
let doctrine_count: i64 = pool
.query_row("SELECT COUNT(*) FROM doctrine;", [], |row| row.get(0))
.unwrap();
let promotion_audit_count: i64 = pool
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE operation = 'doctrine_promotion';",
[],
|row| row.get(0),
)
.unwrap();
let principle_status: String = pool
.query_row(
"SELECT status FROM principles WHERE id = ?1;",
[&principle_id.to_string()],
|row| row.get(0),
)
.unwrap();
(doctrine_count, promotion_audit_count, principle_status)
}
fn attestation_key_fixture() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join("attested-user-event-key.bin")
}
fn read_signed_rows(path: &Path) -> Vec<SignedRow> {
let raw = std::fs::read_to_string(path).expect("read signed JSONL");
raw.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| serde_json::from_str(line).expect("signed row JSON"))
.collect()
}
fn write_signed_rows(path: &Path, rows: &[SignedRow]) {
let mut raw = String::new();
for row in rows {
raw.push_str(&serde_json::to_string(row).expect("serialize signed row"));
raw.push('\n');
}
std::fs::write(path, raw).expect("rewrite signed JSONL fixture");
}
fn signed_run_attestor(path: &Path) -> InMemoryAttestor {
let key_bytes = std::fs::read(path).expect("read attestation key");
let seed: [u8; 32] = key_bytes
.as_slice()
.try_into()
.expect("fixture key is exactly 32 bytes");
InMemoryAttestor::from_seed(&seed)
}
fn signed_run_verification_key(tmp: &Path, attestation_key: &Path) -> PathBuf {
let path = tmp.join("signed-run-verification-key.bin");
let attestor = signed_run_attestor(attestation_key);
std::fs::write(&path, attestor.verifying_key().to_bytes()).expect("write verification key");
path
}
fn seed_operator_authority(db_path: &Path, attestation_key: &Path) {
let attestor = signed_run_attestor(attestation_key);
let pool = Connection::open(db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
let repo = AuthorityRepo::new(&pool);
repo.append_principal_state(
&PrincipalTimelineRecord {
principal_id: "operator-principal".into(),
trust_tier: TrustTier::Operator,
effective_at: at(1),
trust_review_due_at: None,
removed_at: None,
audit_ref: None,
},
&cortex_store::repo::authority::principal_state_policy_decision_test_allow(),
)
.expect("append operator trust state");
repo.append_key_state(
&KeyTimelineRecord {
key_id: attestor.key_id().to_string(),
principal_id: "operator-principal".into(),
state: KeyLifecycleState::Active,
effective_at: at(1),
reason: None,
audit_ref: None,
},
&cortex_store::repo::authority::key_state_policy_decision_test_allow(),
)
.expect("append active operator key state");
}
fn revoke_operator_authority(db_path: &Path, attestation_key: &Path) {
let attestor = signed_run_attestor(attestation_key);
let pool = Connection::open(db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
AuthorityRepo::new(&pool)
.append_key_state(
&KeyTimelineRecord {
key_id: attestor.key_id().to_string(),
principal_id: "operator-principal".into(),
state: KeyLifecycleState::Revoked,
effective_at: at(2),
reason: Some("test revocation".into()),
audit_ref: None,
},
&cortex_store::repo::authority::key_state_policy_decision_test_allow(),
)
.expect("append revoked operator key state");
}
fn run_trusted_history(tmp: &Path, attestation_key: &Path) -> serde_json::Value {
let out = run_in(
tmp,
&[
"run",
"--task",
"summarize current trace",
"--trusted-history",
"--attestation",
attestation_key.to_str().expect("attestation path utf8"),
],
);
assert_exit(&out, 0);
serde_json::from_slice(&out.stdout).expect("run stdout is JSON")
}
fn assert_signed_audit_fails(tmp: &Path, event_log_path: &Path, attestation_key: &Path) {
let audit = run_in(
tmp,
&[
"audit",
"verify",
"--signed",
"--attestation",
attestation_key.to_str().expect("attestation path utf8"),
"--event-log",
event_log_path.to_str().expect("event log path utf8"),
],
);
assert_exit(&audit, 3);
}
#[test]
fn memory_admit_axiom_json_reports_candidate_admission_without_persistence() {
let tmp = tempfile::tempdir().unwrap();
let envelope = valid_axiom_admission_envelope().to_string();
let out = run_in(tmp.path(), &["memory", "admit-axiom", "--json", &envelope]);
assert_exit(&out, 0);
assert!(
out.stderr.is_empty(),
"successful admission report should not emit stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let report: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("admission stdout is JSON");
assert_eq!(report["command"], "memory admit-axiom");
assert_eq!(report["decision"], "admit_candidate");
assert_eq!(report["policy_outcome"], "allow");
assert_eq!(report["candidate_state"], "candidate");
assert_eq!(report["semantic_intended_use"], "candidate_memory");
assert_eq!(report["provenance_class"], "runtime_derived");
assert_eq!(report["semantic_trust"], "unknown");
assert_eq!(report["semantic_policy_outcome"], "warn");
assert_eq!(report["semantic_claim_ceiling"], "dev_only");
assert!(
report["semantic_reasons"]
.as_array()
.expect("semantic_reasons is an array")
.iter()
.any(|reason| reason == "unknown provenance retained as candidate-only"),
"semantic reasons should describe candidate-only weak posture: {report}"
);
assert_eq!(report["explicit_non_promotion"], true);
assert_eq!(report["persisted"], false);
assert_eq!(
report["policy_contributing"][0]["rule_id"],
"memory.admission.allow"
);
assert!(
!tmp.path().join("xdg").exists(),
"admission validation must not open or create the default store"
);
}
#[test]
fn memory_admit_axiom_file_rejects_active_state_before_persistence() {
let tmp = tempfile::tempdir().unwrap();
let mut envelope = valid_axiom_admission_envelope();
envelope["candidate_state"] = json!("active");
let envelope_path = tmp.path().join("axiom-active.json");
std::fs::write(&envelope_path, envelope.to_string()).expect("write AXIOM envelope fixture");
let out = run_in(
tmp.path(),
&[
"memory",
"admit-axiom",
"--file",
envelope_path.to_str().expect("fixture path utf8"),
],
);
assert_exit(&out, 5);
assert!(
out.stderr.is_empty(),
"parsed rejection should report through stdout JSON, not stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let report: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("admission stdout is JSON");
assert_eq!(report["decision"], "reject");
assert_eq!(report["policy_outcome"], "reject");
assert_eq!(report["candidate_state"], "active");
assert_eq!(report["semantic_intended_use"], "candidate_memory");
assert_eq!(report["provenance_class"], "runtime_derived");
assert_eq!(report["semantic_trust"], "unknown");
assert_eq!(report["semantic_policy_outcome"], "warn");
assert_eq!(report["semantic_claim_ceiling"], "dev_only");
assert_eq!(report["explicit_non_promotion"], true);
assert_eq!(report["persisted"], false);
assert_eq!(
report["policy_contributing"][0]["rule_id"],
"memory.admission.candidate_state_required"
);
assert!(
!tmp.path().join("xdg").exists(),
"rejected admission validation must not open or create the default store"
);
}
#[test]
fn memory_admit_axiom_rejects_malformed_json_before_persistence() {
let tmp = tempfile::tempdir().unwrap();
let out = run_in(
tmp.path(),
&["memory", "admit-axiom", "--json", "{not-json"],
);
assert_exit(&out, 5);
assert!(
out.stdout.is_empty(),
"malformed admission should not emit a report that could imply persistence"
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("invalid AXIOM admission envelope"),
"stderr should name malformed admission envelope: {stderr}"
);
assert!(
!tmp.path().join("xdg").exists(),
"malformed admission validation must not open or create the default store"
);
}
fn pai_axiom_fixture(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join("pai-axiom")
.join(name)
}
#[test]
fn memory_admit_axiom_trust_exchange_admits_valid_fixtures() {
let tmp = tempfile::tempdir().unwrap();
let ctx_fixture = pai_axiom_fixture("valid-cortex-context-trust.json");
let exec_fixture = pai_axiom_fixture("valid-axiom-execution-trust.json");
let loop_fixture = pai_axiom_fixture("valid-authority-feedback-loop.json");
let out = run_in(
tmp.path(),
&[
"memory",
"admit-axiom",
"--cortex-context-trust",
ctx_fixture.to_str().unwrap(),
"--axiom-execution-trust",
exec_fixture.to_str().unwrap(),
"--authority-feedback-loop",
loop_fixture.to_str().unwrap(),
"--lifecycle",
"candidate_only",
],
);
assert_exit(&out, 0);
let stdout = String::from_utf8_lossy(&out.stdout);
let report: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("trust exchange stdout is JSON");
assert_eq!(report["mode"], "trust_exchange");
assert_eq!(report["decision"], "admit_candidate");
assert_eq!(report["policy_outcome"], "allow");
let forbidden = report["forbidden_uses"]
.as_array()
.expect("forbidden_uses is array");
assert!(forbidden.iter().any(|value| value == "durable_promotion"));
assert!(forbidden.iter().any(|value| value == "trusted_history"));
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("decision=admit_candidate"),
"stderr should mirror decision: {stderr}"
);
}
#[test]
fn memory_admit_axiom_trust_exchange_rejects_lifecycle_not_candidate_only() {
let tmp = tempfile::tempdir().unwrap();
let ctx_fixture = pai_axiom_fixture("valid-cortex-context-trust.json");
let exec_fixture = pai_axiom_fixture("valid-axiom-execution-trust.json");
let loop_fixture = pai_axiom_fixture("valid-authority-feedback-loop.json");
let out = run_in(
tmp.path(),
&[
"memory",
"admit-axiom",
"--cortex-context-trust",
ctx_fixture.to_str().unwrap(),
"--axiom-execution-trust",
exec_fixture.to_str().unwrap(),
"--authority-feedback-loop",
loop_fixture.to_str().unwrap(),
"--lifecycle",
"promoted",
],
);
assert_exit(&out, 5);
let report: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("trust exchange stdout is JSON");
assert_eq!(report["decision"], "reject");
let failing_edges = report["failing_edges"]
.as_array()
.expect("failing_edges is array");
assert!(failing_edges
.iter()
.any(|edge| edge == "axiom.admission.lifecycle.must_be_candidate_only"));
}
#[test]
fn memory_admit_axiom_trust_exchange_requires_authority_feedback_loop() {
let tmp = tempfile::tempdir().unwrap();
let ctx_fixture = pai_axiom_fixture("valid-cortex-context-trust.json");
let exec_fixture = pai_axiom_fixture("valid-axiom-execution-trust.json");
let out = run_in(
tmp.path(),
&[
"memory",
"admit-axiom",
"--cortex-context-trust",
ctx_fixture.to_str().unwrap(),
"--axiom-execution-trust",
exec_fixture.to_str().unwrap(),
"--lifecycle",
"candidate_only",
],
);
assert_exit(&out, 2);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("axiom.admission.authority_feedback_loop.missing"),
"stderr should surface the new fail-closed invariant: {stderr}"
);
assert!(
stderr.contains("--authority-feedback-loop is required"),
"stderr should explain the operator action: {stderr}"
);
}
#[test]
fn memory_admit_axiom_trust_exchange_requires_cortex_context_trust() {
let tmp = tempfile::tempdir().unwrap();
let exec_fixture = pai_axiom_fixture("valid-axiom-execution-trust.json");
let loop_fixture = pai_axiom_fixture("valid-authority-feedback-loop.json");
let out = run_in(
tmp.path(),
&[
"memory",
"admit-axiom",
"--axiom-execution-trust",
exec_fixture.to_str().unwrap(),
"--authority-feedback-loop",
loop_fixture.to_str().unwrap(),
"--lifecycle",
"candidate_only",
],
);
assert_exit(&out, 2);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("axiom.admission.cortex_context_trust.missing"),
"stderr should surface the new fail-closed invariant: {stderr}"
);
assert!(
stderr.contains("--cortex-context-trust is required"),
"stderr should explain the operator action: {stderr}"
);
assert!(
stderr.contains("quarantine propagation"),
"stderr should name gate 1 (quarantine propagation): {stderr}"
);
assert!(
stderr.contains("redaction-state-blocks-critical-premise"),
"stderr should name gate 2 (redaction-state-blocks-critical-premise): {stderr}"
);
assert!(
stderr.contains("proof-state-failed"),
"stderr should name gate 3 (proof-state-failed): {stderr}"
);
assert!(
stderr.contains("proof-state-missing"),
"stderr should name gate 4 (proof-state-missing): {stderr}"
);
}
#[test]
fn memory_accept_activates_candidate_and_writes_audit() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let id = insert_candidate(&db_path);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
ensure_source_event(&pool, 1);
drop(pool);
let out = run_in(tmp.path(), &["memory", "accept", &id]);
assert_exit(&out, 0);
assert!(
String::from_utf8_lossy(&out.stdout).contains("accepted"),
"stdout should report acceptance"
);
let pool = Connection::open(db_path).expect("open initialized sqlite db");
let status: String = pool
.query_row("SELECT status FROM memories WHERE id = ?1;", [&id], |row| {
row.get(0)
})
.unwrap();
let audit_count: i64 = pool
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE operation = 'memory.accept' AND target_ref = ?1;",
[&id],
|row| row.get(0),
)
.unwrap();
assert_eq!(status, "active");
assert_eq!(audit_count, 1);
}
#[test]
fn memory_search_explain_returns_all_score_components() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let id = insert_active_memory(&db_path);
let out = run_in(
tmp.path(),
&["memory", "search", "sqlite retrieval", "--explain"],
);
assert_exit(&out, 0);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains(&id));
for field in [
"lexical_match",
"semantic_similarity",
"brightness",
"domain_overlap",
"validation",
"authority_weight",
"contradiction_risk",
"staleness_penalty",
"proof_state",
"provenance_class",
"semantic_trust",
"claim_ceiling",
] {
assert!(stdout.contains(field), "missing score component {field}");
}
assert!(stdout.contains("proof_state: full_chain_verified"));
assert!(stdout.contains("provenance_class: operator_attested"));
assert!(stdout.contains("semantic_trust: single_family"));
assert!(stdout.contains("claim_ceiling: local_unsigned"));
}
#[test]
fn memory_search_rejects_active_memory_with_partial_proof_closure() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let id = insert_candidate(&db_path);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
let repo = MemoryRepo::new(&pool);
let audit = MemoryAcceptanceAudit {
id: "aud_01ARZ3NDEKTSV4RRFFQ69G5FAY".parse().unwrap(),
actor_json: json!({"kind": "test"}),
reason: "test partial proof active memory".into(),
source_refs_json: json!([id]),
created_at: at(2),
};
repo.accept_candidate(
&id.parse().unwrap(),
at(3),
&audit,
&accept_candidate_policy_decision_test_allow(),
)
.expect("accept candidate without source event");
let out = run_in(
tmp.path(),
&["memory", "search", "sqlite retrieval", "--explain"],
);
assert_exit(&out, 7);
assert!(
out.stdout.is_empty(),
"failed default memory search must not emit retrieval results"
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("proof closure current use blocked"),
"stderr should name proof closure preflight: {stderr}"
);
}
#[test]
fn proof_memory_reports_named_failing_edges() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let id = insert_candidate(&db_path);
let out = run_in(tmp.path(), &["proof", "memory", &id]);
assert_exit(&out, 0);
let report: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("proof stdout is JSON");
assert_eq!(report["proof_state"], "partial");
assert_eq!(report["runtime_mode"], "local_unsigned");
assert_eq!(report["claim_ceiling"], "local_unsigned");
assert!(report["claim_allowed"].as_bool().unwrap_or_default());
assert_eq!(report["policy_outcome"], "quarantine");
assert_eq!(
report["policy_contributing"][0]["rule_id"],
"proof_closure.state"
);
assert!(
report["downgrade_reasons"]
.as_array()
.expect("downgrade_reasons is an array")
.iter()
.any(|reason| reason
.as_str()
.unwrap_or_default()
.contains("proof state Partial")),
"proof-bearing CLI output should explain claim downgrade context"
);
let failing_edges = report["failing_edges"]
.as_array()
.expect("failing_edges is an array");
assert_eq!(failing_edges.len(), 1);
assert_eq!(failing_edges[0]["failure"], "missing");
assert!(
failing_edges[0]["reason"]
.as_str()
.unwrap_or_default()
.contains("source event not found"),
"failing edge should name the missing source event"
);
}
#[test]
fn memory_search_rejects_open_durable_conflict_before_default_results() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let left_id = insert_active_memory_with(
&db_path,
"mem_01ARZ3NDEKTSV4RRFFQ69G5FB1",
"The billing gate is blocked by provider spend limits.",
&["billing"],
4,
);
let right_id = insert_active_memory_with(
&db_path,
"mem_01ARZ3NDEKTSV4RRFFQ69G5FB2",
"The billing gate is blocked by missing repository tests.",
&["billing"],
6,
);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
ContradictionRepo::new(&pool)
.insert(
&ContradictionRecord {
id: "con_01ARZ3NDEKTSV4RRFFQ69G5FB1"
.parse::<ContradictionId>()
.unwrap(),
left_ref: left_id,
right_ref: right_id,
contradiction_type: "conflicting_causal_claim".into(),
status: ContradictionStatus::Unresolved,
interpretation: None,
created_at: at(7),
updated_at: at(7),
},
&contradiction_insert_policy_test_allow(),
)
.expect("insert open contradiction");
let out = run_in(tmp.path(), &["memory", "search", "billing", "--explain"]);
assert_exit(&out, 7);
assert!(
String::from_utf8_lossy(&out.stderr).contains("open contradiction"),
"stderr should explain conflict gate"
);
assert!(
String::from_utf8_lossy(&out.stdout).trim().is_empty(),
"default retrieval must not emit rows when conflict gate fails"
);
}
#[test]
fn principles_extract_prints_candidates_without_doctrine_mutation() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let memory_ids = insert_principle_fixture_memories(&db_path);
let doctrine_before = count(&db_path, "SELECT COUNT(*) FROM doctrine;");
let principles_before = count(&db_path, "SELECT COUNT(*) FROM principles;");
let out = run_in(
tmp.path(),
&[
"principles",
"extract",
"--window",
"30d",
"--model",
"replay",
],
);
assert_exit(&out, 0);
let batch: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("principles stdout is JSON");
let candidates = batch["candidate_principles"]
.as_array()
.expect("candidate_principles is an array");
assert_eq!(candidates.len(), 1);
for memory_id in memory_ids {
assert!(
candidates[0]["supporting_memory_ids"]
.as_array()
.unwrap()
.iter()
.any(|value| value.as_str() == Some(&memory_id)),
"candidate should cite fixture active memory {memory_id}"
);
}
assert_eq!(
count(&db_path, "SELECT COUNT(*) FROM doctrine;"),
doctrine_before
);
assert_eq!(
count(&db_path, "SELECT COUNT(*) FROM principles;"),
principles_before
);
}
#[test]
fn principle_promote_requires_attestation_then_writes_doctrine_and_audit() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let memory_ids = insert_principle_fixture_memories(&db_path);
let principle_id = insert_principle_candidate(&db_path, &memory_ids);
let missing_attestation = run_in(
tmp.path(),
&[
"principle",
"promote",
&principle_id.to_string(),
"--force",
"advisory",
"--reason",
"operator reviewed support",
],
);
assert_exit(&missing_attestation, 7);
assert!(
String::from_utf8_lossy(&missing_attestation.stderr).contains("--attestation"),
"missing attestation error should name the required gate"
);
assert_eq!(count(&db_path, "SELECT COUNT(*) FROM doctrine;"), 0);
assert_eq!(
count(
&db_path,
"SELECT COUNT(*) FROM audit_records WHERE operation = 'doctrine_promotion';"
),
0
);
let bad_key = tmp.path().join("bad-attestation-key.bin");
std::fs::write(&bad_key, b"not-32-bytes").expect("write bad key");
let invalid_attestation = run_in(
tmp.path(),
&[
"principle",
"promote",
&principle_id.to_string(),
"--force",
"advisory",
"--reason",
"operator reviewed support",
"--attestation",
bad_key.to_str().expect("bad key path utf8"),
],
);
assert_exit(&invalid_attestation, 7);
assert_eq!(count(&db_path, "SELECT COUNT(*) FROM doctrine;"), 0);
assert_eq!(
count(
&db_path,
"SELECT COUNT(*) FROM audit_records WHERE operation = 'doctrine_promotion';"
),
0
);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
let out = run_in(
tmp.path(),
&[
"principle",
"promote",
&principle_id.to_string(),
"--force",
"advisory",
"--reason",
"operator reviewed support",
"--attestation",
attestation_key.to_str().expect("attestation key path utf8"),
],
);
assert_exit(&out, 0);
assert!(String::from_utf8_lossy(&out.stdout).contains("promoted"));
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
let doctrine_count: i64 = pool
.query_row(
"SELECT COUNT(*) FROM doctrine WHERE source_principle = ?1 AND force = 'Advisory';",
[&principle_id.to_string()],
|row| row.get(0),
)
.unwrap();
let audit_count: i64 = pool
.query_row(
"SELECT COUNT(*) FROM audit_records WHERE operation = 'doctrine_promotion' AND target_ref = ?1 AND reason <> '';",
[&principle_id.to_string()],
|row| row.get(0),
)
.unwrap();
let status: String = pool
.query_row(
"SELECT status FROM principles WHERE id = ?1;",
[&principle_id.to_string()],
|row| row.get(0),
)
.unwrap();
let promoted_by_json: String = pool
.query_row(
"SELECT promoted_by_json FROM doctrine WHERE source_principle = ?1;",
[&principle_id.to_string()],
|row| row.get(0),
)
.unwrap();
let promoted_by: serde_json::Value =
serde_json::from_str(&promoted_by_json).expect("promoted_by_json parses");
let actor_json: String = pool
.query_row(
"SELECT actor_json FROM audit_records WHERE operation = 'doctrine_promotion' AND target_ref = ?1;",
[&principle_id.to_string()],
|row| row.get(0),
)
.unwrap();
let actor: serde_json::Value = serde_json::from_str(&actor_json).expect("actor_json parses");
assert_eq!(doctrine_count, 1);
assert_eq!(audit_count, 1);
assert_eq!(status, "promoted_to_doctrine");
assert_eq!(promoted_by["attestation_verified"], true);
assert_eq!(actor["attestation_verified"], true);
for field in [
"key_id",
"public_key_hex",
"signature_hex",
"signed_at",
"payload_hash",
"event_id",
"ledger_id",
] {
assert!(
promoted_by["attestation"][field].is_string(),
"promoted_by attestation should include {field}"
);
}
let invalid = run_in(
tmp.path(),
&[
"principle",
"promote",
"prn_01ARZ3NDEKTSV4RRFFQ69G5FAZ",
"--force",
"gate",
"--reason",
"operator reviewed support",
"--attestation",
attestation_key.to_str().expect("attestation key path utf8"),
],
);
assert_exit(&invalid, 7);
assert_eq!(count(&db_path, "SELECT COUNT(*) FROM doctrine;"), 1);
assert_eq!(
count(
&db_path,
"SELECT COUNT(*) FROM audit_records WHERE operation = 'doctrine_promotion';"
),
1
);
}
#[test]
fn principle_promote_requires_supporting_memory_proof_closure() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let memory_ids = insert_principle_fixture_memories(&db_path);
let principle_id = insert_principle_candidate(&db_path, &memory_ids);
let before = promotion_store_snapshot(&db_path, &principle_id);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
pool.execute(
"DELETE FROM events WHERE id = ?1;",
["evt_01ARZ3NDEKTSV4RRFFQ69G5FAV"],
)
.expect("remove supporting source event");
let attestation_key = attestation_key_fixture();
let out = run_in(
tmp.path(),
&[
"principle",
"promote",
&principle_id.to_string(),
"--force",
"advisory",
"--reason",
"operator reviewed support",
"--attestation",
attestation_key.to_str().expect("attestation key path utf8"),
],
);
assert_exit(&out, 7);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("promotion preflight failed closed"),
"stderr should name the preflight gate"
);
assert!(
stderr.contains("principle_promotion.supporting_memory_proof"),
"stderr should name the supporting memory proof policy: {stderr}"
);
assert_eq!(promotion_store_snapshot(&db_path, &principle_id), before);
}
#[test]
fn principle_promote_rejects_runtime_only_semantic_support() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let memory_ids = insert_principle_fixture_memories(&db_path);
let principle_id = insert_principle_candidate(&db_path, &memory_ids);
let before = promotion_store_snapshot(&db_path, &principle_id);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
for memory_id in &memory_ids {
pool.execute(
"UPDATE memories SET authority = 'runtime' WHERE id = ?1;",
[memory_id],
)
.expect("mark support as runtime-derived");
}
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
let out = run_in(
tmp.path(),
&[
"principle",
"promote",
&principle_id.to_string(),
"--force",
"advisory",
"--reason",
"operator reviewed support",
"--attestation",
attestation_key.to_str().expect("attestation key path utf8"),
],
);
assert_exit(&out, 7);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("principle_promotion.semantic_support"),
"stderr should name the semantic support policy: {stderr}"
);
assert!(
stderr.contains("runtime-only") || stderr.contains("weak-only"),
"stderr should explain the runtime-only semantic support block: {stderr}"
);
assert_eq!(promotion_store_snapshot(&db_path, &principle_id), before);
}
#[test]
fn principle_promote_requires_supporting_memory_conflict_resolution() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let memory_ids = insert_principle_fixture_memories(&db_path);
let conflicting_id = insert_active_memory_with(
&db_path,
"mem_01ARZ3NDEKTSV4RRFFQ69G5FB3",
"Evidence may be inferred without inspecting source state.",
&["agents"],
9,
);
let principle_id = insert_principle_candidate(&db_path, &memory_ids);
let before = promotion_store_snapshot(&db_path, &principle_id);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
ContradictionRepo::new(&pool)
.insert(
&ContradictionRecord {
id: "con_01ARZ3NDEKTSV4RRFFQ69G5FB3"
.parse::<ContradictionId>()
.unwrap(),
left_ref: memory_ids[0].clone(),
right_ref: conflicting_id,
contradiction_type: "conflicting_evidence_standard".into(),
status: ContradictionStatus::Unresolved,
interpretation: None,
created_at: at(10),
updated_at: at(10),
},
&contradiction_insert_policy_test_allow(),
)
.expect("insert open contradiction");
let attestation_key = attestation_key_fixture();
let out = run_in(
tmp.path(),
&[
"principle",
"promote",
&principle_id.to_string(),
"--force",
"advisory",
"--reason",
"operator reviewed support",
"--attestation",
attestation_key.to_str().expect("attestation key path utf8"),
],
);
assert_exit(&out, 7);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("principle_promotion.supporting_memory_conflict_resolution"),
"stderr should name the conflict resolver policy: {stderr}"
);
assert_eq!(promotion_store_snapshot(&db_path, &principle_id), before);
}
#[test]
fn principle_promote_feeds_contradiction_memory_proof_state_to_resolver() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let memory_ids = insert_principle_fixture_memories(&db_path);
let conflicting_id = insert_active_memory_with(
&db_path,
"mem_01ARZ3NDEKTSV4RRFFQ69G5FB4",
"Evidence may be promoted from unresolved inferred state.",
&["agents"],
9,
);
let principle_id = insert_principle_candidate(&db_path, &memory_ids);
let before = promotion_store_snapshot(&db_path, &principle_id);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
let missing_source_events = json!(["evt_01ARZ3NDEKTSV4RRFFQ69G5FB4"]).to_string();
pool.execute(
"UPDATE memories SET source_events_json = ?1 WHERE id = ?2;",
[&missing_source_events, &conflicting_id],
)
.expect("make contradiction peer proof closure partial");
ContradictionRepo::new(&pool)
.insert(
&ContradictionRecord {
id: "con_01ARZ3NDEKTSV4RRFFQ69G5FB4"
.parse::<ContradictionId>()
.unwrap(),
left_ref: memory_ids[0].clone(),
right_ref: conflicting_id,
contradiction_type: "conflicting_evidence_standard".into(),
status: ContradictionStatus::Unresolved,
interpretation: None,
created_at: at(10),
updated_at: at(10),
},
&contradiction_insert_policy_test_allow(),
)
.expect("insert open contradiction");
let attestation_key = attestation_key_fixture();
let out = run_in(
tmp.path(),
&[
"principle",
"promote",
&principle_id.to_string(),
"--force",
"advisory",
"--reason",
"operator reviewed support",
"--attestation",
attestation_key.to_str().expect("attestation key path utf8"),
],
);
assert_exit(&out, 7);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("retrieval.resolve.partial_proof"),
"stderr should prove the promotion gate consumed resolver proof state: {stderr}"
);
assert_eq!(promotion_store_snapshot(&db_path, &principle_id), before);
}
#[test]
fn principle_promote_rejects_revoked_operator_key_before_doctrine_mutation() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let memory_ids = insert_principle_fixture_memories(&db_path);
let principle_id = insert_principle_candidate(&db_path, &memory_ids);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
revoke_operator_authority(&db_path, &attestation_key);
let before = promotion_store_snapshot(&db_path, &principle_id);
let out = run_in(
tmp.path(),
&[
"principle",
"promote",
&principle_id.to_string(),
"--force",
"advisory",
"--reason",
"operator reviewed support",
"--attestation",
attestation_key.to_str().expect("attestation key path utf8"),
],
);
assert_exit(&out, 7);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("temporal authority preflight failed closed"),
"stderr should name temporal authority gate: {stderr}"
);
assert!(
stderr.contains("temporal_authority_preflight_failed"),
"stderr should include machine-readable temporal failure: {stderr}"
);
assert_eq!(promotion_store_snapshot(&db_path, &principle_id), before);
}
fn promote_principle_fixture() -> (tempfile::TempDir, PathBuf, String) {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let memory_ids = insert_principle_fixture_memories(&db_path);
let principle_id = insert_principle_candidate(&db_path, &memory_ids);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
let out = run_in(
tmp.path(),
&[
"principle",
"promote",
&principle_id.to_string(),
"--force",
"advisory",
"--reason",
"operator reviewed support",
"--attestation",
attestation_key.to_str().expect("attestation key path utf8"),
],
);
assert_exit(&out, 0);
(tmp, db_path, principle_id.to_string())
}
fn tamper_promotion_actor_json(
db_path: &Path,
principle_id: &str,
tamper: impl FnOnce(&mut serde_json::Value),
) {
let pool = Connection::open(db_path).expect("open initialized sqlite db");
let promoted_by_json: String = pool
.query_row(
"SELECT promoted_by_json FROM doctrine WHERE source_principle = ?1;",
[principle_id],
|row| row.get(0),
)
.unwrap();
let mut actor: serde_json::Value =
serde_json::from_str(&promoted_by_json).expect("promoted_by_json parses");
tamper(&mut actor);
let serialized = serde_json::to_string(&actor).expect("serialize tampered actor");
pool.execute(
"UPDATE doctrine SET promoted_by_json = ?2 WHERE source_principle = ?1;",
rusqlite::params![principle_id, serialized],
)
.unwrap();
pool.execute(
"UPDATE audit_records SET actor_json = ?2
WHERE operation = 'doctrine_promotion' AND target_ref = ?1;",
rusqlite::params![principle_id, serialized],
)
.unwrap();
}
fn verify_promotion_fails_after(tamper: impl FnOnce(&Path, &str), expected_error_fragment: &str) {
let (tmp, db_path, principle_id) = promote_principle_fixture();
tamper(&db_path, &principle_id);
let out = run_in(
tmp.path(),
&["principle", "verify-promotion", &principle_id],
);
assert_exit(&out, 7);
assert!(
String::from_utf8_lossy(&out.stderr).contains(expected_error_fragment),
"stderr should contain `{expected_error_fragment}`; got {}",
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn principle_promotion_attestation_replays() {
let (tmp, db_path, principle_id) = promote_principle_fixture();
let out = run_in(
tmp.path(),
&["principle", "verify-promotion", &principle_id],
);
assert_exit(&out, 0);
let replay: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("verify-promotion stdout is JSON");
assert_eq!(replay["principle_id"], principle_id);
assert_eq!(replay["promotion_attestation_replay_verified"], true);
verify_promotion_fails_after(
|db_path, principle_id| {
tamper_promotion_actor_json(db_path, principle_id, |actor| {
actor["attestation"]["signature_hex"] = json!("0".repeat(128));
});
},
"verification failed",
);
verify_promotion_fails_after(
|db_path, principle_id| {
tamper_promotion_actor_json(db_path, principle_id, |actor| {
actor["attestation"]["payload_hash"] = json!("0".repeat(64));
});
},
"payload_hash",
);
verify_promotion_fails_after(
|db_path, principle_id| {
let pool = Connection::open(db_path).expect("open initialized sqlite db");
pool.execute(
"UPDATE doctrine SET force = 'Gate' WHERE source_principle = ?1;",
[principle_id],
)
.unwrap();
},
"after_hash",
);
verify_promotion_fails_after(
|db_path, principle_id| {
let pool = Connection::open(db_path).expect("open initialized sqlite db");
pool.execute(
"UPDATE doctrine SET promotion_reason = 'tampered reason' WHERE source_principle = ?1;",
[principle_id],
)
.unwrap();
},
"reason",
);
verify_promotion_fails_after(
|db_path, principle_id| {
tamper_promotion_actor_json(db_path, principle_id, |actor| {
actor["attestation"]["event_id"] =
json!("principle_promote:prn_01ARZ3NDEKTSV4RRFFQ69G5FAZ");
});
},
"event_id",
);
assert_eq!(
count(
&db_path,
"SELECT COUNT(*) FROM audit_records WHERE operation = 'doctrine_promotion';"
),
1
);
}
#[test]
fn principle_promote_requires_falsification_record() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let memory_ids = insert_principle_fixture_memories(&db_path);
let principle_id = insert_principle_candidate_without_falsification(&db_path, &memory_ids);
let attestation_key = attestation_key_fixture();
let before = promotion_store_snapshot(&db_path, &principle_id);
let out = run_in(
tmp.path(),
&[
"principle",
"promote",
&principle_id.to_string(),
"--force",
"advisory",
"--reason",
"operator reviewed support",
"--attestation",
attestation_key.to_str().expect("attestation key path utf8"),
],
);
assert_exit(&out, 7);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("promotion preflight failed closed"),
"stderr should name the preflight gate"
);
assert!(
stderr.contains("falsification"),
"stderr should name falsification gate"
);
let preflight: serde_json::Value = stderr
.lines()
.find_map(|line| serde_json::from_str(line).ok())
.expect("stderr includes machine-readable promotion preflight failure");
assert_eq!(preflight["error"], "promotion_preflight_failed");
assert_eq!(preflight["claim_ceiling"], "dev_only");
assert_eq!(preflight["required_ceiling"], "authority_grade");
assert_eq!(preflight["policy_outcome"], "reject");
assert_eq!(
preflight["policy_contributing"][0]["rule_id"],
"principle_promotion.falsification"
);
assert!(
preflight["policy_contributing"][0]["reason"]
.as_str()
.unwrap_or_default()
.contains("falsification"),
"machine-readable policy reason should name falsification"
);
assert_eq!(promotion_store_snapshot(&db_path, &principle_id), before);
}
#[test]
fn context_build_emits_redacted_pack_with_refs() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let id = insert_active_memory(&db_path);
let out = run_in(
tmp.path(),
&["context", "build", "--task", "answer with citations"],
);
assert_exit(&out, 0);
let pack: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("context stdout is JSON");
assert_eq!(pack["pack_mode"], "external");
assert_eq!(pack["redaction_policy"]["raw_event_payloads"], "excluded");
assert_eq!(pack["selected_refs"][0]["ref_id"]["memory_id"], id);
assert!(
!out.stdout
.windows("payload_json".len())
.any(|window| window == b"payload_json"),
"external pack should not expose raw payload_json"
);
}
#[test]
fn context_build_rejects_active_memory_with_partial_proof_closure() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let id = insert_candidate(&db_path);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
let repo = MemoryRepo::new(&pool);
let audit = MemoryAcceptanceAudit {
id: "aud_01ARZ3NDEKTSV4RRFFQ69G5FAZ".parse().unwrap(),
actor_json: json!({"kind": "test"}),
reason: "test partial proof active memory".into(),
source_refs_json: json!([id]),
created_at: at(2),
};
repo.accept_candidate(
&id.parse().unwrap(),
at(3),
&audit,
&accept_candidate_policy_decision_test_allow(),
)
.expect("accept candidate without source event");
let out = run_in(
tmp.path(),
&["context", "build", "--task", "answer with citations"],
);
assert_exit(&out, 7);
assert!(
out.stdout.is_empty(),
"failed default context build must not emit a pack"
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("proof closure current use blocked"),
"stderr should name proof closure preflight: {stderr}"
);
}
#[test]
fn context_build_rejects_open_durable_conflict_before_default_pack() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let left_id = insert_active_memory_with(
&db_path,
"mem_01ARZ3NDEKTSV4RRFFQ69G5FC1",
"The context pack should use billing provider limits as the blocker.",
&["billing"],
4,
);
let right_id = insert_active_memory_with(
&db_path,
"mem_01ARZ3NDEKTSV4RRFFQ69G5FC2",
"The context pack should use missing repository tests as the blocker.",
&["billing"],
6,
);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
ContradictionRepo::new(&pool)
.insert(
&ContradictionRecord {
id: "con_01ARZ3NDEKTSV4RRFFQ69G5FC1"
.parse::<ContradictionId>()
.unwrap(),
left_ref: left_id,
right_ref: right_id,
contradiction_type: "conflicting_context_claim".into(),
status: ContradictionStatus::Unresolved,
interpretation: None,
created_at: at(7),
updated_at: at(7),
},
&contradiction_insert_policy_test_allow(),
)
.expect("insert open contradiction");
let out = run_in(
tmp.path(),
&[
"context",
"build",
"--task",
"answer with billing citations",
],
);
assert_exit(&out, 7);
assert!(
out.stdout.is_empty(),
"failed default context build must not emit a pack"
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("open contradiction blocks default context-pack use"),
"stderr should explain conflict-aware resolver gate: {stderr}"
);
}
#[test]
fn context_build_axiom_constraints_export_caps_agent_claims() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
insert_active_memory(&db_path);
let out = run_in(
tmp.path(),
&[
"context",
"build",
"--task",
"answer with citations",
"--axiom-constraints",
],
);
assert_exit(&out, 0);
let export: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("AXIOM constraint stdout is JSON");
let constraints = export["constraints"]
.as_array()
.expect("export includes constraints");
assert!(export["context_pack_id"].as_str().is_some());
assert_eq!(export["task"], "answer with citations");
assert_eq!(export["claim_ceiling"], "local_unsigned");
assert_eq!(export["policy_outcome"], "allow");
assert_eq!(
export["unknown_refs"]
.as_array()
.expect("export includes unknown refs")
.len(),
0
);
assert!(
constraints
.iter()
.any(|constraint| constraint["kind"] == "truth_ceiling"),
"AXIOM export should include a truth ceiling"
);
assert!(
constraints
.iter()
.any(|constraint| constraint["kind"] == "no_execution_authority"),
"AXIOM export should not grant execution authority"
);
assert!(
constraints
.iter()
.any(|constraint| constraint["kind"] == "forbid_promotion_shaped_output"),
"unsigned context should forbid promotion-shaped output"
);
assert!(
export["redaction_limits"]
.as_array()
.expect("export includes redaction limits")
.iter()
.any(|limit| limit == "raw_event_payloads_excluded"),
"AXIOM export should carry the raw payload redaction boundary"
);
assert!(
!out.stdout
.windows("payload_json".len())
.any(|window| window == b"payload_json"),
"AXIOM export should not expose raw payload fields"
);
}
#[test]
fn run_emits_agent_response_event_linked_to_pack() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let out = run_in(tmp.path(), &["run", "--task", "summarize current trace"]);
assert_exit(&out, 0);
let report: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("run stdout is JSON");
let context_pack_id = report["context_pack_id"]
.as_str()
.expect("report has context pack id");
let correlation_id = report["correlation_id"]
.as_str()
.expect("report has correlation id");
assert!(
correlation_id.starts_with("cor_"),
"run report correlation id should use cor_ prefix"
);
assert_eq!(
report["run_observability"]["correlation_id"],
correlation_id
);
assert_eq!(
report["run_observability"]["context_pack_id"],
context_pack_id
);
assert_eq!(report["run_observability"]["audit_schema_version"], 1);
assert_eq!(report["run_observability"]["operation"], "runtime.run");
assert_eq!(report["run_observability"]["status"], "completed");
assert_eq!(report["run_observability"]["adapter_id"], "cli-offline");
assert_eq!(report["run_observability"]["model"], "replay");
assert_eq!(
report["run_observability"]["runtime_mode"],
"local_unsigned"
);
assert_eq!(report["run_observability"]["proof_state"], "partial");
assert_eq!(
report["run_observability"]["claim_ceiling"],
"local_unsigned"
);
assert_eq!(report["run_observability"]["trusted_run_history"], false);
assert_eq!(
report["run_observability"]["context_policy_outcome"],
"allow"
);
assert_eq!(
report["run_observability"]["response_hash"],
report["raw_hash"]
);
for forbidden_key in [
"task",
"system",
"messages",
"prompt",
"prompt_hash",
"context_pack",
"selected_refs",
"raw_context",
"raw_event_payload",
"response_text",
"text",
] {
assert!(
report["run_observability"].get(forbidden_key).is_none(),
"run observability should not expose {forbidden_key}"
);
}
assert!(
!out.stdout
.windows("\"context_pack\":".len())
.any(|window| window == b"\"context_pack\":"),
"run report should not serialize the raw context pack"
);
for forbidden_fragment in [
b"\"selected_refs\":".as_slice(),
b"\"raw_event_payload\"".as_slice(),
] {
assert!(
!out.stdout
.windows(forbidden_fragment.len())
.any(|window| window == forbidden_fragment),
"run report should not serialize raw context fragment {}",
String::from_utf8_lossy(forbidden_fragment)
);
}
assert_eq!(report["runtime_mode"], "local_unsigned");
assert_eq!(report["proof_state"], "partial");
assert_eq!(report["claim_ceiling"], "local_unsigned");
assert_eq!(report["context_policy_outcome"], "allow");
assert!(
report["downgrade_reasons"]
.as_array()
.expect("run report includes downgrade reasons")
.iter()
.any(|reason| reason
.as_str()
.unwrap_or_default()
.contains("proof state Partial")),
"run report should explain why stronger authority is unavailable"
);
assert_eq!(
report["agent_response_event"]["payload"]["correlation_id"],
correlation_id
);
assert_eq!(
report["agent_response_event"]["payload"]["context_pack_id"],
context_pack_id
);
assert_eq!(
report["agent_response_event"]["event_type"],
"cortex.event.agent_response.v1"
);
assert_eq!(
report["agent_response_event"]["payload"]["ledger_authority"],
"development"
);
assert_eq!(
report["agent_response_event"]["payload"]["signed_ledger_authority"],
false
);
for forbidden_use in [
"audit_export",
"compliance_evidence",
"cross_system_trust_decision",
"external_reporting",
] {
assert!(
report["agent_response_event"]["payload"]["forbidden_uses"]
.as_array()
.expect("forbidden_uses is an array")
.iter()
.any(|value| value.as_str() == Some(forbidden_use)),
"development ledger event should forbid {forbidden_use}"
);
}
let event_id = report["agent_response_event"]["id"]
.as_str()
.expect("report includes persisted event id");
let event_hash = report["agent_response_event"]["event_hash"]
.as_str()
.expect("report includes event_hash");
let payload_hash = report["agent_response_event"]["payload_hash"]
.as_str()
.expect("report includes payload_hash");
assert!(!event_hash.is_empty(), "run report event must be sealed");
assert!(
!payload_hash.is_empty(),
"run report event must hash payload"
);
let log = JsonlLog::open(&event_log_path).expect("open JSONL event log");
let jsonl_events = log
.iter()
.expect("iterate JSONL log")
.collect::<Result<Vec<_>, _>>()
.expect("read JSONL events");
assert_eq!(jsonl_events.len(), 1, "run appends one durable event");
assert_eq!(jsonl_events[0].id.to_string(), event_id);
assert_eq!(jsonl_events[0].event_hash, event_hash);
assert_eq!(
jsonl_events[0].payload["context_pack_id"],
json!(context_pack_id)
);
assert_eq!(jsonl_events[0].payload["ledger_authority"], "development");
assert_eq!(jsonl_events[0].payload["signed_ledger_authority"], false);
let pool = Connection::open(db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
let stored_event = EventRepo::new(&pool)
.get_by_id(&event_id.parse().expect("report event id parses"))
.expect("query stored event")
.expect("run ingests event row into sqlite");
assert_eq!(stored_event.event_hash, event_hash);
assert_eq!(stored_event.payload_hash, payload_hash);
assert_eq!(
stored_event.payload["context_pack_id"],
json!(context_pack_id)
);
}
#[test]
fn run_rejects_tag_only_ollama_model_before_state_access() {
let tmp = tempfile::tempdir().unwrap();
let out = run_in(
tmp.path(),
&[
"run",
"--task",
"summarize current trace",
"--model",
"ollama:llama3",
],
);
assert_exit(&out, 7);
assert!(
out.stdout.is_empty(),
"rejected Ollama preflight should not write stdout"
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("digest-pinned with @sha256:<64 hex chars>"),
"stderr should explain the Ollama digest-pin requirement: {stderr}"
);
assert!(
stderr.contains("no state was changed"),
"stderr should report fail-closed no-mutation semantics: {stderr}"
);
assert!(
!tmp.path().join("xdg").exists(),
"Ollama model preflight should run before data-dir access"
);
}
#[test]
fn run_trusted_history_appends_signed_local_row_after_verification() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
let report = run_trusted_history(tmp.path(), &attestation_key);
assert_eq!(report["runtime_mode"], "signed_local_ledger");
assert_eq!(report["proof_state"], "full_chain_verified");
assert_eq!(report["claim_ceiling"], "signed_local_ledger");
assert_eq!(report["trusted_run_history"], true);
assert_eq!(
report["agent_response_event"]["payload"]["ledger_authority"],
"signed_local"
);
assert_eq!(
report["agent_response_event"]["payload"]["signed_ledger_authority"],
true
);
assert_eq!(
report["agent_response_event"]["payload"]["trusted_run_history"],
true
);
let attestor = signed_run_attestor(&attestation_key);
let signed = verify_signed_chain(
&event_log_path,
&attestor.verifying_key(),
attestor.key_id(),
)
.expect("signed chain verification runs");
assert!(
signed.report.ok(),
"signed trusted run history row should verify: {:?}",
signed.report.failures
);
let audit = run_in(
tmp.path(),
&[
"audit",
"verify",
"--signed",
"--attestation",
attestation_key.to_str().expect("attestation path utf8"),
"--event-log",
event_log_path.to_str().expect("event log path utf8"),
],
);
assert_exit(&audit, 0);
}
#[test]
fn signed_audit_accepts_public_verification_key_without_signing_seed() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let verification_key = signed_run_verification_key(tmp.path(), &attestation_key);
let audit = run_in(
tmp.path(),
&[
"audit",
"verify",
"--signed",
"--verification-key",
verification_key
.to_str()
.expect("verification key path utf8"),
"--event-log",
event_log_path.to_str().expect("event log path utf8"),
],
);
assert_exit(&audit, 0);
}
#[test]
fn signed_audit_rejects_trusted_run_payload_tamper() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let mut rows = read_signed_rows(&event_log_path);
rows[0].event.payload["response"] = json!("tampered response");
write_signed_rows(&event_log_path, &rows);
assert_signed_audit_fails(tmp.path(), &event_log_path, &attestation_key);
let attestor = signed_run_attestor(&attestation_key);
let outcome = verify_signed_chain(
&event_log_path,
&attestor.verifying_key(),
attestor.key_id(),
)
.expect("signed chain verification runs");
assert!(
outcome.report.failures.iter().any(|failure| {
matches!(
failure.reason,
FailureReason::HashBreak {
which: HashKind::PayloadHashMismatch,
..
}
)
}),
"payload tamper should break payload hash: {:?}",
outcome.report.failures
);
}
#[test]
fn signed_audit_rejects_trusted_run_context_pack_id_tamper() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let mut rows = read_signed_rows(&event_log_path);
rows[0].event.payload["context_pack_id"] = json!("ctx_tampered");
write_signed_rows(&event_log_path, &rows);
assert_signed_audit_fails(tmp.path(), &event_log_path, &attestation_key);
let attestor = signed_run_attestor(&attestation_key);
let outcome = verify_signed_chain(
&event_log_path,
&attestor.verifying_key(),
attestor.key_id(),
)
.expect("signed chain verification runs");
assert!(
outcome.report.failures.iter().any(|failure| {
matches!(
failure.reason,
FailureReason::HashBreak {
which: HashKind::PayloadHashMismatch,
..
}
)
}),
"context_pack_id tamper should break payload hash: {:?}",
outcome.report.failures
);
}
#[test]
fn signed_audit_rejects_trusted_run_prior_signature_tamper() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let mut rows = read_signed_rows(&event_log_path);
assert_eq!(rows.len(), 2, "test requires two signed rows");
let sig = rows[0]
.signature
.as_mut()
.expect("first signed run row has a signature");
let mut bytes = b64_decode(&sig.bytes).expect("signature bytes decode");
bytes[0] ^= 0x01;
sig.bytes = b64_encode(&bytes);
write_signed_rows(&event_log_path, &rows);
assert_signed_audit_fails(tmp.path(), &event_log_path, &attestation_key);
let attestor = signed_run_attestor(&attestation_key);
let outcome = verify_signed_chain(
&event_log_path,
&attestor.verifying_key(),
attestor.key_id(),
)
.expect("signed chain verification runs");
assert!(
outcome
.report
.failures
.iter()
.any(|failure| matches!(failure.reason, FailureReason::BadSignature { .. })),
"signature tamper should break signed-chain verification: {:?}",
outcome.report.failures
);
}
#[test]
fn run_trusted_history_requires_attestation_before_append() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let out = run_in(
tmp.path(),
&[
"run",
"--task",
"summarize current trace",
"--trusted-history",
],
);
assert_exit(&out, 7);
assert!(
String::from_utf8_lossy(&out.stderr).contains("requires --attestation"),
"stderr should explain missing attestation"
);
let log = JsonlLog::open(&event_log_path).expect("open event log");
assert_eq!(log.len(), 0, "missing attestation must not append JSONL");
}
#[test]
fn run_trusted_history_rejects_invalid_attestation_before_append() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let bad_key = tmp.path().join("bad-run-attestation-key.bin");
std::fs::write(&bad_key, b"not-32-bytes").expect("write invalid key");
let out = run_in(
tmp.path(),
&[
"run",
"--task",
"summarize current trace",
"--trusted-history",
"--attestation",
bad_key.to_str().expect("bad key path utf8"),
],
);
assert_exit(&out, 7);
assert!(
String::from_utf8_lossy(&out.stderr).contains("exactly 32 raw bytes"),
"stderr should explain invalid attestation key"
);
let log = JsonlLog::open(&event_log_path).expect("open event log");
assert_eq!(log.len(), 0, "invalid attestation must not append JSONL");
}
#[test]
fn run_trusted_history_rejects_key_switch_without_rotation_before_append() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let before_len = JsonlLog::open(&event_log_path)
.expect("open event log")
.len();
assert_eq!(before_len, 1);
let alternate_key = tmp.path().join("alternate-attestation-key.bin");
std::fs::write(&alternate_key, [8u8; 32]).expect("write alternate valid seed");
let out = run_in(
tmp.path(),
&[
"run",
"--task",
"summarize current trace",
"--trusted-history",
"--attestation",
alternate_key.to_str().expect("alternate key path utf8"),
],
);
assert_exit(&out, 7);
assert!(
String::from_utf8_lossy(&out.stderr)
.contains("existing ledger is not a clean signed chain"),
"stderr should explain key-switch preflight failure"
);
let after_len = JsonlLog::open(&event_log_path)
.expect("open event log")
.len();
assert_eq!(after_len, before_len, "failed key switch must not append");
}
#[test]
fn signed_audit_requires_valid_attestation_key() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let bad_key = tmp.path().join("bad-audit-attestation-key.bin");
std::fs::write(&bad_key, b"not-32-bytes").expect("write invalid key");
let out = run_in(
tmp.path(),
&[
"audit",
"verify",
"--signed",
"--attestation",
bad_key.to_str().expect("bad key path utf8"),
"--event-log",
event_log_path.to_str().expect("event log path utf8"),
],
);
assert_exit(&out, 7);
assert!(
String::from_utf8_lossy(&out.stderr).contains("exactly 32 raw bytes"),
"stderr should explain invalid signed audit key"
);
}
#[test]
fn signed_audit_requires_signed_flag_and_attestation_together() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let missing_key = run_in(
tmp.path(),
&[
"audit",
"verify",
"--signed",
"--event-log",
event_log_path.to_str().expect("event log path utf8"),
],
);
assert_exit(&missing_key, 7);
assert!(
String::from_utf8_lossy(&missing_key.stderr).contains("requires --verification-key"),
"stderr should explain signed audit attestation requirement"
);
let stray_key = run_in(
tmp.path(),
&[
"audit",
"verify",
"--attestation",
attestation_key.to_str().expect("attestation path utf8"),
"--event-log",
event_log_path.to_str().expect("event log path utf8"),
],
);
assert_exit(&stray_key, 2);
assert!(
String::from_utf8_lossy(&stray_key.stderr).contains("only valid with --signed"),
"stderr should explain stray attestation usage"
);
let verification_key = signed_run_verification_key(tmp.path(), &attestation_key);
let both_keys = run_in(
tmp.path(),
&[
"audit",
"verify",
"--signed",
"--attestation",
attestation_key.to_str().expect("attestation path utf8"),
"--verification-key",
verification_key
.to_str()
.expect("verification key path utf8"),
"--event-log",
event_log_path.to_str().expect("event log path utf8"),
],
);
assert_exit(&both_keys, 2);
assert!(
String::from_utf8_lossy(&both_keys.stderr).contains("use only one"),
"stderr should explain mutually exclusive verification key inputs"
);
}
#[test]
fn signed_audit_rejects_wrong_key_for_existing_signed_log() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let alternate_key = tmp.path().join("alternate-audit-key.bin");
std::fs::write(&alternate_key, [8u8; 32]).expect("write alternate valid seed");
let out = run_in(
tmp.path(),
&[
"audit",
"verify",
"--signed",
"--attestation",
alternate_key.to_str().expect("alternate key path utf8"),
"--event-log",
event_log_path.to_str().expect("event log path utf8"),
],
);
assert_exit(&out, 3);
assert!(
String::from_utf8_lossy(&out.stderr).contains("BadSignature"),
"stderr should report bad signature under wrong verification key"
);
}
#[test]
fn audit_export_blocks_development_ledger_unless_local_diagnostic() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
insert_active_memory(&db_path);
let run = run_in(tmp.path(), &["run", "--task", "summarize current trace"]);
assert_exit(&run, 0);
let blocked = run_in(tmp.path(), &["audit", "export"]);
assert_exit(&blocked, 7);
let blocked_stdout = String::from_utf8_lossy(&blocked.stdout);
let blocked_stderr = String::from_utf8_lossy(&blocked.stderr);
assert!(
blocked_stdout.trim().is_empty(),
"blocked audit export must not emit a JSON artifact on stdout: {blocked_stdout}"
);
assert!(
blocked_stderr.contains("ledger rows"),
"audit export should name the ledger precondition"
);
assert!(
!blocked_stdout.contains("trusted-history") && !blocked_stderr.contains("trusted-history"),
"blocked audit export must not use trusted-history wording"
);
let diagnostic = run_in(tmp.path(), &["audit", "export", "--local-diagnostic"]);
assert_exit(&diagnostic, 0);
let artifact: serde_json::Value =
serde_json::from_slice(&diagnostic.stdout).expect("diagnostic stdout is JSON");
assert_eq!(artifact["artifact_kind"], "development_ledger_diagnostic");
assert_eq!(artifact["runtime_mode"], "local_unsigned");
assert_eq!(artifact["proof_state"], "partial");
assert_eq!(artifact["claim_ceiling"], "local_unsigned");
assert!(artifact["downgrade_reasons"]
.as_array()
.expect("artifact includes downgrade reasons")
.iter()
.any(|reason| reason
.as_str()
.unwrap_or_default()
.contains("proof state Partial")));
assert_eq!(artifact["trusted_run_history"], false);
assert_eq!(artifact["development_ledger_rows"], 1);
assert!(artifact["forbidden_uses_enforced"]
.as_array()
.expect("artifact includes forbidden uses")
.iter()
.any(|value| value.as_str() == Some("audit_export")));
for (surface, label) in [
("compliance-evidence", "compliance evidence"),
("cross-system-trust-decision", "cross-system trust decision"),
("external-reporting", "external reporting"),
] {
let blocked = run_in(tmp.path(), &["audit", "export", "--surface", surface]);
assert_exit(&blocked, 7);
let stdout = String::from_utf8_lossy(&blocked.stdout);
let stderr = String::from_utf8_lossy(&blocked.stderr);
assert!(
stdout.trim().is_empty(),
"{surface} must not emit a trusted artifact on stdout: {stdout}"
);
assert!(
stderr.contains("ledger rows") && stderr.contains(label),
"{surface} stderr should name the blocked authority surface: {stderr}"
);
}
}
#[test]
fn audit_export_blocks_signed_local_rows_without_forbidden_uses() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
let attestation_key = attestation_key_fixture();
let attestor = signed_run_attestor(&attestation_key);
let mut log = JsonlLog::open(&event_log_path).expect("open JSONL event log");
let now = at(10);
log.append_signed(
Event {
id: "evt_01ARZ3NDEKTSV4RRFFQ69G5FYY".parse().unwrap(),
schema_version: SCHEMA_VERSION,
observed_at: now,
recorded_at: now,
source: EventSource::ChildAgent {
model: "phase2-cli-test".into(),
},
event_type: EventType::AgentResponse,
trace_id: None,
session_id: Some("phase2-cli-test".into()),
domain_tags: vec!["phase2".into(), "test".into()],
payload: json!({
"task": "signed-local audit export boundary",
"context_pack_id": "ctx_signed_local_boundary",
"ledger_authority": "signed_local",
"signed_ledger_authority": true,
"trusted_run_history": true
}),
payload_hash: String::new(),
prev_event_hash: None,
event_hash: String::new(),
},
&attestor,
&append_signed_policy_decision_test_allow(),
)
.expect("append signed-local row");
let blocked = run_in(tmp.path(), &["audit", "export"]);
assert_exit(&blocked, 7);
let blocked_stdout = String::from_utf8_lossy(&blocked.stdout);
let blocked_stderr = String::from_utf8_lossy(&blocked.stderr);
assert!(
blocked_stdout.trim().is_empty(),
"blocked audit export must not emit a JSON artifact on stdout: {blocked_stdout}"
);
assert!(
blocked_stderr.contains("ledger rows"),
"audit export should block signed-local rows by authority class"
);
for (surface, label) in [
("compliance-evidence", "compliance evidence"),
("cross-system-trust-decision", "cross-system trust decision"),
("external-reporting", "external reporting"),
] {
let blocked = run_in(tmp.path(), &["audit", "export", "--surface", surface]);
assert_exit(&blocked, 7);
let stdout = String::from_utf8_lossy(&blocked.stdout);
let stderr = String::from_utf8_lossy(&blocked.stderr);
assert!(
stdout.trim().is_empty(),
"{surface} must not emit a trusted artifact on stdout: {stdout}"
);
assert!(
stderr.contains("ledger rows") && stderr.contains(label),
"{surface} stderr should name the blocked authority surface: {stderr}"
);
}
let diagnostic = run_in(tmp.path(), &["audit", "export", "--local-diagnostic"]);
assert_exit(&diagnostic, 0);
let artifact: serde_json::Value =
serde_json::from_slice(&diagnostic.stdout).expect("diagnostic stdout is JSON");
assert_eq!(artifact["artifact_kind"], "development_ledger_diagnostic");
assert_eq!(artifact["trusted_run_history"], false);
assert_eq!(artifact["development_ledger_rows"], 1);
}
#[test]
fn audit_export_non_audit_surfaces_fail_runtime_preflight_without_rows() {
let tmp = tempfile::tempdir().unwrap();
init(tmp.path());
for (surface, label) in [
("compliance-evidence", "compliance evidence"),
("cross-system-trust-decision", "cross-system trust decision"),
("external-reporting", "external reporting"),
] {
let blocked = run_in(tmp.path(), &["audit", "export", "--surface", surface]);
assert_exit(&blocked, 7);
let stdout = String::from_utf8_lossy(&blocked.stdout);
let stderr = String::from_utf8_lossy(&blocked.stderr);
assert!(
stdout.trim().is_empty(),
"{surface} must not emit a trusted artifact on stdout: {stdout}"
);
assert!(
stderr.contains("preflight failed") && stderr.contains(label),
"{surface} stderr should report runtime preflight denial: {stderr}"
);
}
let diagnostic = run_in(
tmp.path(),
&[
"audit",
"export",
"--surface",
"compliance-evidence",
"--local-diagnostic",
],
);
assert_exit(&diagnostic, 0);
let artifact: serde_json::Value =
serde_json::from_slice(&diagnostic.stdout).expect("diagnostic stdout is JSON");
assert_eq!(artifact["artifact_kind"], "local_diagnostic");
assert_eq!(artifact["authority_preflight_allowed"], false);
assert_eq!(artifact["trusted_run_history"], false);
}
#[test]
fn run_ledger_drill_reports_recovered_development_window() {
let tmp = tempfile::tempdir().unwrap();
let db_path = tmp.path().join("drill").join("cortex.db");
let event_log_path = tmp.path().join("drill").join("events.jsonl");
let report_path = tmp.path().join("drill").join("report.json");
let out = run_in(
tmp.path(),
&[
"run-ledger-drill",
"--db",
db_path.to_str().expect("db path utf8"),
"--event-log",
event_log_path.to_str().expect("event log path utf8"),
"--iteration",
"1",
"--kill-window",
"jsonl-ack-before-sqlite-commit",
"--report-json",
report_path.to_str().expect("report path utf8"),
],
);
assert_exit(&out, 0);
let report: serde_json::Value =
serde_json::from_slice(&std::fs::read(&report_path).expect("read drill report"))
.expect("report parses as JSON");
assert_eq!(report["iteration"], 1);
assert_eq!(report["killed_phase"], "jsonl-ack-before-sqlite-commit");
assert_eq!(report["acknowledged_count"], 1);
assert_eq!(report["recovered_count"], 1);
assert!(report["chain_head"].as_str().unwrap_or_default().len() > 16);
assert_eq!(report["event_set_parity"], true);
assert_eq!(report["partial_tail_rejected"], true);
assert_eq!(report["divergent_duplicate_rejected"], true);
assert_eq!(report["ledger_authority"], "development");
assert_eq!(report["signed_ledger_authority"], false);
assert_eq!(report["external_sigkill_proof"], false);
assert_eq!(count(&db_path, "SELECT COUNT(*) FROM events;"), 1);
let log = JsonlLog::open(&event_log_path).expect("open drill JSONL event log");
let jsonl_events = log
.iter()
.expect("iterate drill JSONL log")
.collect::<Result<Vec<_>, _>>()
.expect("read drill JSONL events");
assert_eq!(jsonl_events.len(), 1);
assert_eq!(jsonl_events[0].payload["ledger_authority"], "development");
assert_eq!(jsonl_events[0].payload["signed_ledger_authority"], false);
}
#[test]
fn phase_2_6_d1_d2_d3_end_to_end_through_durable_paths() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let id = insert_active_memory(&db_path);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
let repo = MemoryRepo::new(&pool);
let memory_id: cortex_core::MemoryId = id.parse().unwrap();
let unscoped = OutcomeMemoryRelationRecord {
outcome_ref: "outcome:phase26-d1-unscoped".into(),
memory_id,
relation: OutcomeMemoryRelation::Validated,
recorded_at: at(10),
source_event_id: None,
validation_scope: None,
validating_principal_id: None,
evidence_ref: None,
};
let err = repo
.record_outcome_relation(&unscoped, None)
.expect_err("ungated Validated must fail closed");
assert!(
err.to_string()
.contains(OUTCOME_UTILITY_TO_TRUTH_PROMOTION_UNAUTHORIZED_INVARIANT),
"expected stable invariant, got: {err}"
);
let search_before = run_in(
tmp.path(),
&["memory", "search", "sqlite retrieval", "--explain"],
);
assert_exit(&search_before, 0);
let stdout_before = String::from_utf8_lossy(&search_before.stdout);
assert!(
stdout_before.contains(&id),
"search must hit the active memory: {stdout_before}"
);
assert!(
stdout_before.contains("validation: raw=0.0000"),
"before any Validated edge, retrieval validation must read 0 from durable column: {stdout_before}"
);
let scoped = OutcomeMemoryRelationRecord {
outcome_ref: "outcome:phase26-d1-scoped".into(),
memory_id,
relation: OutcomeMemoryRelation::Validated,
recorded_at: at(11),
source_event_id: None,
validation_scope: Some("scope:e2e-test".into()),
validating_principal_id: Some("principal:e2e-operator".into()),
evidence_ref: Some("aud:e2e-test".into()),
};
repo.record_outcome_relation(
&scoped,
Some(&record_outcome_relation_policy_decision_test_allow()),
)
.expect("scoped Validated edge admits with composed policy");
let epoch = repo
.validation_epoch_for(&memory_id)
.expect("read validation_epoch")
.expect("memory exists");
assert_eq!(epoch, 1, "Validated edge must advance validation_epoch");
let search_after = run_in(
tmp.path(),
&["memory", "search", "sqlite retrieval", "--explain"],
);
assert_exit(&search_after, 0);
let stdout_after = String::from_utf8_lossy(&search_after.stdout);
assert!(
stdout_after.contains("validation: raw=1.0000"),
"after Validated edge, retrieval validation must read 1.0 from durable column: {stdout_after}"
);
}
#[test]
fn run_trusted_history_refuses_when_operator_key_revoked() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
revoke_operator_authority(&db_path, &attestation_key);
let out = run_in(
tmp.path(),
&[
"run",
"--task",
"summarize current trace",
"--trusted-history",
"--attestation",
attestation_key.to_str().expect("attestation path utf8"),
],
);
assert_exit(&out, 7);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("run.trusted_history.operator_temporal_authority.revalidation_failed"),
"stderr should carry the stable invariant: {stderr}"
);
let log = JsonlLog::open(&event_log_path).expect("open event log");
assert_eq!(
log.len(),
0,
"revoked operator key must not append a signed row"
);
}
#[test]
fn audit_verify_signed_reports_temporal_authority_when_key_unknown() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
pool.execute("DELETE FROM authority_key_timeline;", [])
.expect("clear key timeline");
pool.execute("DELETE FROM authority_principal_timeline;", [])
.expect("clear principal timeline");
let audit = run_in(
tmp.path(),
&[
"audit",
"verify",
"--signed",
"--attestation",
attestation_key.to_str().expect("attestation path utf8"),
"--event-log",
event_log_path.to_str().expect("event log path utf8"),
],
);
let stderr = String::from_utf8_lossy(&audit.stderr);
assert!(
stderr.contains("audit.verify.operator_temporal_authority.revalidation_failed"),
"audit verify must surface the temporal authority invariant when the key is unknown: {stderr}"
);
let code = audit.status.code().expect("audit verify exited via signal");
assert_ne!(
code, 0,
"audit verify must fail closed when the temporal authority axis is `Reject`: stderr={stderr}"
);
}
#[test]
fn audit_verify_signed_temporal_authority_allows_when_key_current() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let event_log_path = db_path
.parent()
.expect("db has parent data dir")
.join("events.jsonl");
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let audit = run_in(
tmp.path(),
&[
"audit",
"verify",
"--signed",
"--attestation",
attestation_key.to_str().expect("attestation path utf8"),
"--event-log",
event_log_path.to_str().expect("event log path utf8"),
],
);
assert_exit(&audit, 0);
let stderr = String::from_utf8_lossy(&audit.stderr);
assert!(
stderr.contains("audit.verify.temporal_authority")
&& stderr.contains("policy_contributing=audit.verify.temporal_authority outcome=Allow"),
"audit verify happy path must emit Allow for the temporal authority contributor: {stderr}"
);
}
#[test]
fn audit_export_temporal_authority_allows_when_signed_key_current() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let out = run_in(tmp.path(), &["--json", "audit", "export"]);
let stdout = String::from_utf8_lossy(&out.stdout);
let envelope: serde_json::Value = serde_json::from_str(stdout.trim()).expect("json envelope");
let policy = envelope["policy_outcome"]
.as_object()
.expect("audit export envelope carries policy_outcome");
let contributing = policy["contributing"].as_array().expect("contributing");
let discarded = policy["discarded"].as_array().expect("discarded");
let mut all_axes: Vec<&serde_json::Value> = Vec::new();
all_axes.extend(contributing.iter());
all_axes.extend(discarded.iter());
let temporal = all_axes
.iter()
.find(|c| c["rule_id"] == "audit.export.temporal_authority")
.copied()
.expect("audit.export.temporal_authority must be observed");
assert_eq!(
temporal["outcome"], "allow",
"temporal authority must be Allow when signed key is current: {envelope}"
);
}
#[test]
fn audit_export_temporal_authority_rejects_when_key_unknown() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
pool.execute("DELETE FROM authority_key_timeline;", [])
.expect("clear key timeline");
pool.execute("DELETE FROM authority_principal_timeline;", [])
.expect("clear principal timeline");
drop(pool);
let out = run_in(tmp.path(), &["--json", "audit", "export"]);
let stdout = String::from_utf8_lossy(&out.stdout);
let envelope: serde_json::Value = serde_json::from_str(stdout.trim()).expect("json envelope");
let policy = envelope["policy_outcome"]
.as_object()
.expect("audit export envelope carries policy_outcome");
let contributing = policy["contributing"].as_array().expect("contributing");
let discarded = policy["discarded"].as_array().expect("discarded");
let mut all_axes: Vec<&serde_json::Value> = Vec::new();
all_axes.extend(contributing.iter());
all_axes.extend(discarded.iter());
let temporal = all_axes
.iter()
.find(|c| c["rule_id"] == "audit.export.temporal_authority")
.copied()
.expect("audit.export.temporal_authority must be observed");
assert_eq!(
temporal["outcome"], "reject",
"temporal authority must Reject when key timeline is dropped: {envelope}"
);
let reason = temporal["reason"].as_str().expect("reason string");
assert!(
reason.contains("audit.export.operator_temporal_authority.revalidation_failed"),
"Reject reason must carry the stable invariant: {reason}"
);
}
#[test]
fn audit_anchor_temporal_authority_allows_when_signed_key_current() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let out = run_in(tmp.path(), &["--json", "audit", "anchor"]);
let stdout = String::from_utf8_lossy(&out.stdout);
let envelope: serde_json::Value = serde_json::from_str(stdout.trim()).expect("json envelope");
let policy = envelope["policy_outcome"]
.as_object()
.expect("audit anchor envelope carries policy_outcome");
let contributing = policy["contributing"].as_array().expect("contributing");
let discarded = policy["discarded"].as_array().expect("discarded");
let mut all_axes: Vec<&serde_json::Value> = Vec::new();
all_axes.extend(contributing.iter());
all_axes.extend(discarded.iter());
let temporal = all_axes
.iter()
.find(|c| c["rule_id"] == "audit.anchor.temporal_authority")
.copied()
.expect("audit.anchor.temporal_authority must be observed");
assert_eq!(
temporal["outcome"], "allow",
"temporal authority must be Allow when signed key is current: {envelope}"
);
}
#[test]
fn audit_anchor_temporal_authority_rejects_when_key_unknown() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
insert_active_memory(&db_path);
let attestation_key = attestation_key_fixture();
seed_operator_authority(&db_path, &attestation_key);
run_trusted_history(tmp.path(), &attestation_key);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
pool.execute("DELETE FROM authority_key_timeline;", [])
.expect("clear key timeline");
pool.execute("DELETE FROM authority_principal_timeline;", [])
.expect("clear principal timeline");
drop(pool);
let out = run_in(tmp.path(), &["--json", "audit", "anchor"]);
let stdout = String::from_utf8_lossy(&out.stdout);
let envelope: serde_json::Value = serde_json::from_str(stdout.trim()).expect("json envelope");
let policy = envelope["policy_outcome"]
.as_object()
.expect("audit anchor envelope carries policy_outcome");
let contributing = policy["contributing"].as_array().expect("contributing");
let discarded = policy["discarded"].as_array().expect("discarded");
let mut all_axes: Vec<&serde_json::Value> = Vec::new();
all_axes.extend(contributing.iter());
all_axes.extend(discarded.iter());
let temporal = all_axes
.iter()
.find(|c| c["rule_id"] == "audit.anchor.temporal_authority")
.copied()
.expect("audit.anchor.temporal_authority must be observed");
assert_eq!(
temporal["outcome"], "reject",
"temporal authority must Reject when key timeline is dropped: {envelope}"
);
let reason = temporal["reason"].as_str().expect("reason string");
assert!(
reason.contains("audit.anchor.operator_temporal_authority.revalidation_failed"),
"Reject reason must carry the stable invariant: {reason}"
);
}
#[test]
fn memory_accept_emits_warn_temporal_authority_invariant_on_honest_floor() {
let tmp = tempfile::tempdir().unwrap();
let db_path = init(tmp.path());
let id = insert_candidate(&db_path);
let pool = Connection::open(&db_path).expect("open initialized sqlite db");
apply_pending(&pool).expect("apply migrations");
ensure_source_event(&pool, 1);
drop(pool);
let out = run_in(tmp.path(), &["--json", "memory", "accept", &id]);
assert_exit(&out, 0);
let stdout = String::from_utf8_lossy(&out.stdout);
let envelope: serde_json::Value = serde_json::from_str(stdout.trim()).expect("json envelope");
let policy = envelope["policy_outcome"]
.as_object()
.expect("memory accept envelope carries policy_outcome");
let contributing = policy["contributing"].as_array().expect("contributing");
let discarded = policy["discarded"].as_array().expect("discarded");
let mut all_axes: Vec<&serde_json::Value> = Vec::new();
all_axes.extend(contributing.iter());
all_axes.extend(discarded.iter());
let temporal = all_axes
.iter()
.find(|c| c["rule_id"] == "memory.accept.operator_temporal_use")
.copied()
.expect("memory.accept.operator_temporal_use must be observed");
assert_eq!(
temporal["outcome"], "warn",
"operator temporal use must be Warn at the honest floor: {envelope}"
);
let reason = temporal["reason"].as_str().expect("reason string");
assert!(
reason.contains("memory.accept.operator_temporal_authority.warn_no_attestation"),
"Warn reason must carry the stable invariant: {reason}"
);
let verify_pool = Connection::open(&db_path).expect("reopen db");
let status: String = verify_pool
.query_row("SELECT status FROM memories WHERE id = ?1;", [&id], |row| {
row.get(0)
})
.unwrap();
assert_eq!(
status, "active",
"memory accept must remain operational at the honest floor"
);
}