use super::platform::{self, events};
use super::resource::pressure;
use super::{agent, health, monitor, monitor_service, service, state};
use anyhow::{bail, Context, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::env;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
#[cfg(unix)]
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use uuid::Uuid;
const STATE_PATH: &str = ".yana-ai/os/supervisor-state.json";
const RECEIPTS_PATH: &str = ".yana-ai/os/supervisor-receipts.jsonl";
const HALT_PATH: &str = ".claude/state/GIAMTHI_HALT.lock";
const QUARANTINE_PATH: &str = ".claude/state/GIAMTHI_QUARANTINE.json";
const EVIDENCE_DEGRADED_PATH: &str = ".yana-ai/os/evidence-degraded.jsonl";
const HEARTBEAT_SLO_SECS: i64 = 180;
const MAX_SERVICE_DEFINITION_BYTES: u64 = 256 * 1024;
const RECONCILIATION_STATE_PATH: &str = ".yana-ai/os/supervisor-reconciliation.json";
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, clap::ValueEnum)]
#[serde(rename_all = "kebab-case")]
pub enum QuarantineMode {
ReadOnly,
NoShell,
NoNetwork,
}
impl QuarantineMode {
pub fn as_str(self) -> &'static str {
match self {
Self::ReadOnly => "read-only",
Self::NoShell => "no-shell",
Self::NoNetwork => "no-network",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SupervisorState {
pub schema_version: u32,
#[serde(default = "default_component")]
pub component: String,
#[serde(default)]
pub process_started_at: Option<String>,
#[serde(default = "runtime_version")]
pub runtime_version: String,
pub last_heartbeat: String,
pub last_tick_id: String,
pub pid: u32,
pub platform: String,
pub health_level: String,
pub managed_agents: usize,
pub chat_sessions: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuarantineRecord {
pub schema_version: u32,
pub mode: QuarantineMode,
pub reason: String,
pub actor: String,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ReceiptPayload {
schema_version: u32,
sequence: u64,
timestamp: String,
event: String,
actor: String,
detail: String,
previous_hash: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Receipt {
#[serde(flatten)]
payload: ReceiptPayload,
hash: String,
}
#[derive(Debug, Serialize)]
pub struct SupervisorDashboard {
pub project_root: String,
pub mode: String,
pub halt_reason: Option<String>,
pub quarantine: Option<QuarantineRecord>,
pub heartbeat: Option<SupervisorState>,
pub heartbeat_age_secs: Option<i64>,
pub heartbeat_slo_secs: i64,
pub heartbeat_healthy: bool,
pub service: monitor_service::ServiceReport,
pub periodic_scheduler: monitor_service::ServiceReport,
pub resident_service: service::manager::ServiceStatus,
pub compatibility_watcher: CompatibilityWatcherStatus,
pub service_definition_drift: ServiceDefinitionDriftStatus,
pub latest_health: Option<monitor::SystemHealthSnapshot>,
pub health_checks: health::HealthReport,
pub receipt_chain_valid: bool,
pub receipt_count: usize,
pub evidence_degraded_count: usize,
pub last_evidence_degradation: Option<String>,
pub managed_agents: usize,
pub chat_sessions: usize,
pub native_helper: NativeHelperStatus,
pub resource_pressure: pressure::ResourcePressure,
pub host_capabilities: Option<platform::capabilities::PlatformCapabilities>,
pub last_reconciliation: Option<events::ReconciliationState>,
}
#[derive(Debug, Serialize)]
pub struct NativeHelperStatus {
pub binary: String,
pub runtime_version: String,
pub native_scheduler: bool,
pub signature_status: String,
pub detail: String,
}
#[derive(Debug, Serialize)]
pub struct CompatibilityWatcherStatus {
pub script_present: bool,
pub heartbeat_present: bool,
pub heartbeat_age_secs: Option<u64>,
pub state: String,
pub detail: String,
}
#[derive(Debug, Serialize)]
pub struct ServiceDefinitionDriftStatus {
pub state: String,
pub findings: Vec<ServiceDefinitionFinding>,
pub detail: String,
}
#[derive(Debug, Serialize, PartialEq, Eq)]
pub struct ServiceDefinitionFinding {
pub component: String,
pub path: String,
pub target: Option<String>,
pub reason: String,
}
#[derive(Debug, Serialize)]
pub struct SelfTestReport {
pub passed: bool,
pub checks: Vec<SelfTestCheck>,
}
#[derive(Debug, Serialize)]
pub struct SelfTestCheck {
pub name: String,
pub passed: bool,
pub detail: String,
}
#[derive(Debug, PartialEq, Eq)]
pub struct HookCheckResult {
pub stdout: String,
pub stderr: String,
pub exit_code: i32,
}
enum SafetyFile {
Missing,
Present(String),
Unreadable,
}
pub fn hook_check(root: &Path, input: &str) -> HookCheckResult {
let halt = safety_file(&root.join(HALT_PATH));
let quarantine = safety_file(&root.join(QUARANTINE_PATH));
if matches!(halt, SafetyFile::Missing) && matches!(quarantine, SafetyFile::Missing) {
return hook_allow();
}
let event: serde_json::Value = match serde_json::from_str(input) {
Ok(value) => value,
Err(_) => {
return hook_deny(
"PreToolUse",
"Giám thị could not interpret the hook payload while a safety state is active. Failing closed.",
)
}
};
let event_name = event
.get("hook_event_name")
.and_then(serde_json::Value::as_str)
.unwrap_or("PreToolUse");
match halt {
SafetyFile::Present(body) => {
let body: String = body.chars().take(1500).collect();
let body = if body.trim().is_empty() {
"(the halt lock exists but contains no readable reason)"
} else {
body.as_str()
};
return hook_deny(
event_name,
&format!(
"Giám thị has halted this project. Only a human may unlock it after review. Halt record: {body}"
),
);
}
SafetyFile::Unreadable => {
return hook_deny(
event_name,
"Giám thị halt state exists but cannot be read safely. Failing closed; human review is required.",
)
}
SafetyFile::Missing => {}
}
let record = match quarantine {
SafetyFile::Present(text) => match serde_json::from_str::<QuarantineRecord>(&text) {
Ok(record) => record,
Err(_) => {
return hook_deny(
event_name,
"Giám thị quarantine state is malformed. Failing closed; human review is required.",
)
}
},
SafetyFile::Unreadable => {
return hook_deny(
event_name,
"Giám thị quarantine state cannot be read safely. Failing closed; human review is required.",
)
}
SafetyFile::Missing => return hook_allow(),
};
let tool_name = ["tool_name", "toolName", "name"]
.iter()
.find_map(|key| event.get(key).and_then(serde_json::Value::as_str))
.unwrap_or("");
let denied = matches!(
(record.mode, tool_name),
(
QuarantineMode::ReadOnly,
"Write" | "Edit" | "NotebookEdit" | "Bash"
) | (QuarantineMode::NoShell, "Bash")
| (QuarantineMode::NoNetwork, "WebFetch" | "WebSearch")
);
if !denied {
return hook_allow();
}
hook_deny(
event_name,
&format!(
"Giám thị quarantine '{}' blocked tool '{}'. A human must review and clear quarantine.",
record.mode.as_str(),
tool_name
),
)
}
fn safety_file(path: &Path) -> SafetyFile {
match read_regular_text(path) {
Ok(Some(text)) => SafetyFile::Present(text),
Ok(None) => SafetyFile::Missing,
Err(_) => SafetyFile::Unreadable,
}
}
fn hook_allow() -> HookCheckResult {
HookCheckResult {
stdout: String::new(),
stderr: String::new(),
exit_code: 0,
}
}
fn hook_deny(event_name: &str, reason: &str) -> HookCheckResult {
let (value, exit_code) = match event_name {
"SessionStart" => (
serde_json::json!({"continue": false, "stopReason": reason}),
0,
),
"UserPromptSubmit" => (
serde_json::json!({"decision": "block", "reason": reason}),
0,
),
_ => (
serde_json::json!({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": reason
}
}),
2,
),
};
HookCheckResult {
stdout: format!("{}\n", value),
stderr: if exit_code == 2 {
format!("{reason}\n")
} else {
String::new()
},
exit_code,
}
}
pub fn tick(root: &Path) -> Result<SupervisorState> {
tick_for_component(root, "supervisor-tick", None)
}
pub fn tick_resident(root: &Path, process_started_at: &str) -> Result<SupervisorState> {
tick_for_component(
root,
"giamthi-resident",
Some(process_started_at.to_string()),
)
}
fn tick_for_component(
root: &Path,
component: &str,
process_started_at: Option<String>,
) -> Result<SupervisorState> {
let snapshot = monitor::collect(root);
monitor::persist(root, &snapshot)?;
let inventory =
agent::inventory(root, true, usize::MAX).unwrap_or_else(|_| agent::AgentInventory {
managed: Vec::new(),
chat_sessions: Vec::new(),
actors: Vec::new(),
});
let report = health::inspect(root);
let tick_id = Uuid::new_v4().to_string();
let current = SupervisorState {
schema_version: 1,
component: component.into(),
process_started_at,
runtime_version: runtime_version(),
last_heartbeat: state::now(),
last_tick_id: tick_id.clone(),
pid: std::process::id(),
platform: std::env::consts::OS.into(),
health_level: format!("{:?}", report.overall).to_lowercase(),
managed_agents: inventory.managed.len(),
chat_sessions: inventory.chat_sessions.len(),
};
write_json_atomic(&root.join(STATE_PATH), ¤t)?;
append_receipt(
root,
"supervisor.tick",
"yana-rt",
&format!(
"tick={tick_id} health={} agents={} sessions={}",
current.health_level, current.managed_agents, current.chat_sessions
),
)?;
reconcile_events(root);
Ok(current)
}
fn reconcile_events(root: &Path) {
let path = root.join(RECONCILIATION_STATE_PATH);
let previous = read_json_optional::<events::ReconciliationState>(&path)
.ok()
.flatten()
.unwrap_or_default();
let expected_interval_secs = monitor_service::status(root)
.ok()
.and_then(|report| report.interval_secs)
.unwrap_or(HEARTBEAT_SLO_SECS as u64);
let (detected, next) = events::reconcile(root, previous, expected_interval_secs);
for event in &detected {
let detail = serde_json::to_string(event).unwrap_or_else(|_| format!("{event:?}"));
if let Err(error) = append_receipt(root, "supervisor.event", "yana-rt", &detail) {
record_evidence_degradation(root, "supervisor.event", "yana-rt", &detail, &error);
}
}
let _ = write_json_atomic(&path, &next);
}
fn default_component() -> String {
"supervisor-tick".into()
}
fn runtime_version() -> String {
env!("CARGO_PKG_VERSION").into()
}
pub fn dashboard(root: &Path) -> Result<SupervisorDashboard> {
let halt_reason = read_regular_text(&root.join(HALT_PATH))?;
let quarantine = read_json_optional::<QuarantineRecord>(&root.join(QUARANTINE_PATH))?;
let heartbeat = read_json_optional::<SupervisorState>(&root.join(STATE_PATH))?;
let heartbeat_age_secs = heartbeat
.as_ref()
.and_then(|item| age_seconds(&item.last_heartbeat));
let heartbeat_healthy = heartbeat_age_secs.is_some_and(|age| age <= HEARTBEAT_SLO_SECS);
let (receipt_chain_valid, receipt_count) = verify_receipts(root)?;
let (evidence_degraded_count, last_evidence_degradation) = evidence_degradation_summary(root);
let last_evidence_degradation = last_evidence_degradation.map(|record| {
format!(
"{} event='{}' actor='{}' error={}",
record.timestamp, record.event, record.actor, record.error
)
});
let inventory =
agent::inventory(root, true, usize::MAX).unwrap_or_else(|_| agent::AgentInventory {
managed: Vec::new(),
chat_sessions: Vec::new(),
actors: Vec::new(),
});
let mode = if halt_reason.is_some() {
"halted".to_string()
} else if let Some(record) = &quarantine {
format!("quarantine:{}", record.mode.as_str())
} else {
"normal".to_string()
};
let periodic_scheduler = monitor_service::status(root)?;
let resident_service = service::runtime::manager(root, 60)?.status()?;
Ok(SupervisorDashboard {
project_root: root.display().to_string(),
mode,
halt_reason,
quarantine,
heartbeat,
heartbeat_age_secs,
heartbeat_slo_secs: HEARTBEAT_SLO_SECS,
heartbeat_healthy,
service: periodic_scheduler.clone(),
periodic_scheduler,
resident_service,
compatibility_watcher: compatibility_watcher_status(root),
service_definition_drift: service_definition_drift_status(root),
latest_health: monitor::load(root).ok(),
health_checks: health::inspect(root),
receipt_chain_valid,
receipt_count,
evidence_degraded_count,
last_evidence_degradation,
managed_agents: inventory.managed.len(),
chat_sessions: inventory.chat_sessions.len(),
native_helper: native_helper_status(),
resource_pressure: pressure::collect(root),
host_capabilities: {
use platform::contract::TelemetryBackend;
platform::backend()
.host_profile()
.ok()
.map(|profile| profile.capabilities)
},
last_reconciliation: read_json_optional::<events::ReconciliationState>(
&root.join(RECONCILIATION_STATE_PATH),
)?,
})
}
pub fn halt(root: &Path, reason: &str, actor: &str) -> Result<()> {
let reason = required("reason", reason)?;
let actor = required("actor", actor)?;
let path = root.join(HALT_PATH);
if path.exists() {
bail!("Giám Thị halt already exists at {}", path.display());
}
write_private_new(
&path,
format!(
"actor: {actor}\nreason: {reason}\ncreated_at: {}\n",
state::now()
)
.as_bytes(),
)?;
if let Err(error) = append_receipt(root, "supervisor.halt", actor, reason) {
record_evidence_degradation(root, "supervisor.halt", actor, reason, &error);
}
Ok(())
}
pub fn unlock(root: &Path, approve: bool, reason: &str, actor: &str) -> Result<()> {
if !approve {
bail!("human unlock requires --approve");
}
let reason = required("reason", reason)?;
let actor = required("actor", actor)?;
let path = root.join(HALT_PATH);
let prior = read_regular_text(&path)?
.ok_or_else(|| anyhow::anyhow!("no Giám Thị halt exists at {}", path.display()))?;
append_receipt(
root,
"supervisor.unlock",
actor,
&format!("reason={reason}; prior={}", prior.replace('\n', " | ")),
)?;
fs::remove_file(&path).with_context(|| format!("removing {}", path.display()))
}
pub fn set_quarantine(
root: &Path,
mode: QuarantineMode,
reason: &str,
actor: &str,
) -> Result<QuarantineRecord> {
let record = QuarantineRecord {
schema_version: 1,
mode,
reason: required("reason", reason)?.to_string(),
actor: required("actor", actor)?.to_string(),
created_at: state::now(),
};
write_json_atomic(&root.join(QUARANTINE_PATH), &record)?;
let detail = format!("mode={} reason={}", record.mode.as_str(), record.reason);
if let Err(error) = append_receipt(root, "supervisor.quarantine.set", &record.actor, &detail) {
record_evidence_degradation(
root,
"supervisor.quarantine.set",
&record.actor,
&detail,
&error,
);
}
Ok(record)
}
pub fn clear_quarantine(root: &Path, approve: bool, reason: &str, actor: &str) -> Result<()> {
if !approve {
bail!("clearing quarantine requires --approve");
}
let reason = required("reason", reason)?;
let actor = required("actor", actor)?;
let path = root.join(QUARANTINE_PATH);
let record = read_json_optional::<QuarantineRecord>(&path)?
.ok_or_else(|| anyhow::anyhow!("no quarantine exists at {}", path.display()))?;
append_receipt(
root,
"supervisor.quarantine.clear",
actor,
&format!("reason={reason}; prior_mode={}", record.mode.as_str()),
)?;
fs::remove_file(&path).with_context(|| format!("removing {}", path.display()))
}
pub fn self_test(root: &Path) -> SelfTestReport {
let sandbox = root
.join(".yana-ai/os/self-test")
.join(Uuid::new_v4().to_string());
let mut checks = Vec::new();
let fixture = sandbox.join("atomic.json");
let atomic = write_json_atomic(&fixture, &serde_json::json!({"ok": true}))
.and_then(|_| read_json_optional::<serde_json::Value>(&fixture))
.map(|value| value == Some(serde_json::json!({"ok": true})));
checks.push(check_result("private-atomic-state", atomic));
let receipt = append_receipt_at(
&sandbox.join("receipts.jsonl"),
"self-test",
"yana-rt",
"synthetic",
)
.and_then(|_| verify_receipts_at(&sandbox.join("receipts.jsonl")))
.map(|(valid, count)| valid && count == 1);
checks.push(check_result("receipt-chain", receipt));
let hook_paths = [
root.join("core/hooks/giamthi-halt-check.sh"),
root.join(".claude/hooks/giamthi-halt-check.sh"),
root.join(".codex/hooks/giamthi-halt-check.sh"),
];
let mirrors = if hook_paths.iter().all(|path| path.is_file()) {
fs::read(&hook_paths[0]).ok().is_some_and(|canonical| {
hook_paths[1..]
.iter()
.all(|path| fs::read(path).ok().as_deref() == Some(canonical.as_slice()))
})
} else {
false
};
checks.push(SelfTestCheck {
name: "cross-engine-hook-mirrors".into(),
passed: mirrors,
detail: if mirrors {
"canonical, Claude, and Codex hooks match"
} else {
"hook mirrors are missing or differ"
}
.into(),
});
let _ = fs::remove_dir_all(&sandbox);
SelfTestReport {
passed: checks.iter().all(|item| item.passed),
checks,
}
}
pub fn print<T: Serialize>(value: &T, json: bool, title: &str) -> Result<()> {
if json {
println!("{}", serde_json::to_string_pretty(value)?);
} else {
println!("{title}");
println!("{}", serde_json::to_string_pretty(value)?);
}
Ok(())
}
fn native_helper_status() -> NativeHelperStatus {
let binary = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("yana-rt"));
#[cfg(target_os = "macos")]
let (signature_status, detail) = {
let status = std::process::Command::new("codesign")
.args(["--verify", "--deep", "--strict"])
.arg(&binary)
.output();
match status {
Ok(value) if value.status.success() => {
("verified".into(), "codesign verification passed".into())
}
Ok(_) => (
"unverified".into(),
"binary is native but lacks a trusted production signature".into(),
),
Err(error) => ("unknown".into(), format!("codesign unavailable: {error}")),
}
};
#[cfg(not(target_os = "macos"))]
let (signature_status, detail) = (
"not-checked".into(),
"platform signing verification is not implemented; scheduler still invokes the native binary directly".into(),
);
NativeHelperStatus {
binary: binary.display().to_string(),
runtime_version: env!("CARGO_PKG_VERSION").into(),
native_scheduler: true,
signature_status,
detail,
}
}
fn compatibility_watcher_status(root: &Path) -> CompatibilityWatcherStatus {
let script_present = root.join(".claude/scripts/giamthi-watch.sh").is_file();
let heartbeat = root.join(".claude/state/giamthi-heartbeat.log");
let heartbeat_age_secs = fs::metadata(&heartbeat)
.and_then(|metadata| metadata.modified())
.ok()
.and_then(|modified| SystemTime::now().duration_since(modified).ok())
.map(|age| age.as_secs());
let heartbeat_present = heartbeat_age_secs.is_some();
let (state, detail) = match (script_present, heartbeat_age_secs) {
(false, _) => ("not-installed", "compatibility watcher script is absent"),
(true, None) => (
"unknown",
"watcher script exists but no heartbeat evidence is available",
),
(true, Some(age)) if age <= 86_400 => (
"observed",
"compatibility watcher heartbeat was observed within 24 hours",
),
(true, Some(_)) => (
"stale",
"compatibility watcher heartbeat is older than 24 hours",
),
};
CompatibilityWatcherStatus {
script_present,
heartbeat_present,
heartbeat_age_secs,
state: state.into(),
detail: detail.into(),
}
}
fn service_definition_drift_status(root: &Path) -> ServiceDefinitionDriftStatus {
let home = match service::manager::home() {
Ok(home) => home,
Err(error) => {
return ServiceDefinitionDriftStatus {
state: "unknown".into(),
findings: Vec::new(),
detail: format!("service definition discovery unavailable: {error}"),
}
}
};
match service_definition_findings_at(root, &home, env::consts::OS) {
Ok(findings) if findings.is_empty() => ServiceDefinitionDriftStatus {
state: "clear".into(),
findings,
detail: "no service definitions for another checkout were discovered".into(),
},
Ok(findings) => ServiceDefinitionDriftStatus {
state: "detected".into(),
detail: format!(
"{} service definition(s) point at another checkout or could not be inspected; no definition was modified",
findings.len()
),
findings,
},
Err(error) => ServiceDefinitionDriftStatus {
state: "unknown".into(),
findings: Vec::new(),
detail: format!("service definition discovery failed: {error}"),
},
}
}
fn service_definition_findings_at(
root: &Path,
home: &Path,
platform: &str,
) -> Result<Vec<ServiceDefinitionFinding>> {
let mut directories = Vec::new();
match platform {
"macos" => directories.push(home.join("Library/LaunchAgents")),
"linux" => {
if let Some(config) = env::var_os("XDG_CONFIG_HOME") {
directories.push(PathBuf::from(config).join("systemd/user"));
}
let fallback = home.join(".config/systemd/user");
if !directories.contains(&fallback) {
directories.push(fallback);
}
}
"windows" => {
let base = env::var_os("LOCALAPPDATA")
.map(PathBuf::from)
.unwrap_or_else(|| home.join("AppData/Local"));
directories.push(base.join("YanaAI/Service"));
directories.push(base.join("YanaAI/Monitor"));
directories.push(home.join(".yana-ai/giamthi"));
}
_ => return Ok(Vec::new()),
}
let mut paths = Vec::new();
for directory in directories {
let entries = match fs::read_dir(&directory) {
Ok(entries) => entries,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
Err(error) => {
return Err(error)
.with_context(|| format!("reading service directory {}", directory.display()))
}
};
for entry in entries {
paths.push(entry?.path());
}
}
paths.sort();
paths.dedup();
let mut findings = Vec::new();
for path in paths {
let Some(component) = definition_component(&path, platform) else {
continue;
};
match definition_target(&path, component) {
Ok(Some(target)) if !same_checkout(root, &target) => {
findings.push(ServiceDefinitionFinding {
component: component.into(),
path: path.display().to_string(),
target: Some(target.display().to_string()),
reason: "definition points at another checkout".into(),
});
}
Ok(Some(_)) => {}
Ok(None) => findings.push(ServiceDefinitionFinding {
component: component.into(),
path: path.display().to_string(),
target: None,
reason: "definition target is missing or unparseable".into(),
}),
Err(error) => findings.push(ServiceDefinitionFinding {
component: component.into(),
path: path.display().to_string(),
target: None,
reason: format!("definition cannot be inspected: {error}"),
}),
}
}
Ok(findings)
}
fn definition_component(path: &Path, platform: &str) -> Option<&'static str> {
let name = path.file_name()?.to_str()?;
match platform {
"macos" if name.starts_with("com.yana.service.") && name.ends_with(".plist") => {
Some("resident-service")
}
"macos" if name.starts_with("com.yana.system-health.") && name.ends_with(".plist") => {
Some("periodic-scheduler")
}
"macos" if name.starts_with("com.yanaai.giamthi-watch") && name.ends_with(".plist") => {
Some("compatibility-watcher")
}
"linux" if name.starts_with("yana-service-") && name.ends_with(".service") => {
Some("resident-service")
}
"linux" if name.starts_with("yana-system-health-") && name.ends_with(".service") => {
Some("periodic-scheduler")
}
"linux" if name.starts_with("yana-giamthi-") && name.ends_with(".service") => {
Some("compatibility-watcher")
}
"windows" if name.starts_with("YanaService-") && name.ends_with(".xml") => {
Some("resident-service")
}
"windows" if name.starts_with("YanaSystemHealth-") && name.ends_with(".xml") => {
Some("periodic-scheduler")
}
"windows"
if name.ends_with(".json")
&& path
.parent()
.and_then(Path::file_name)
.and_then(|value| value.to_str())
== Some("giamthi") =>
{
Some("compatibility-watcher")
}
_ => None,
}
}
fn definition_target(path: &Path, component: &str) -> Result<Option<PathBuf>> {
let metadata =
fs::symlink_metadata(path).with_context(|| format!("inspecting {}", path.display()))?;
if metadata.file_type().is_symlink() || !metadata.is_file() {
bail!("not a regular file");
}
if metadata.len() > MAX_SERVICE_DEFINITION_BYTES {
bail!("definition exceeds {MAX_SERVICE_DEFINITION_BYTES} bytes");
}
let text = fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?;
if path.extension().and_then(|value| value.to_str()) == Some("json") {
let value: serde_json::Value =
serde_json::from_str(&text).with_context(|| format!("parsing {}", path.display()))?;
return Ok(value
.get("target")
.and_then(|value| value.as_str())
.map(PathBuf::from));
}
if path.extension().and_then(|value| value.to_str()) == Some("service") {
return Ok(text.lines().find_map(|line| {
let value = line.trim().strip_prefix("WorkingDirectory=")?;
Some(PathBuf::from(unquote_service_value(value)))
}));
}
if component == "compatibility-watcher" {
return Ok(xml_strings(&text)
.into_iter()
.find(|value| value.ends_with("/.claude/scripts/giamthi-watch.sh"))
.and_then(|script| {
PathBuf::from(script)
.parent()
.and_then(Path::parent)
.and_then(Path::parent)
.map(Path::to_path_buf)
}));
}
Ok(xml_value_after_key(&text, "WorkingDirectory").map(PathBuf::from))
}
fn xml_strings(text: &str) -> Vec<String> {
let mut values = Vec::new();
let mut remaining = text;
while let Some(start) = remaining.find("<string>") {
let value = &remaining[start + "<string>".len()..];
let Some(end) = value.find("</string>") else {
break;
};
values.push(xml_unescape(&value[..end]));
remaining = &value[end + "</string>".len()..];
}
values
}
fn xml_value_after_key(text: &str, key: &str) -> Option<String> {
let marker = format!("<key>{key}</key>");
let remainder = text.split_once(&marker)?.1;
xml_strings(remainder).into_iter().next()
}
fn xml_unescape(value: &str) -> String {
value
.replace("<", "<")
.replace(">", ">")
.replace(""", "\"")
.replace("'", "'")
.replace("&", "&")
}
fn unquote_service_value(value: &str) -> String {
let value = value
.strip_prefix('"')
.and_then(|value| value.strip_suffix('"'))
.unwrap_or(value);
value.replace("\\\"", "\"").replace("\\\\", "\\")
}
fn same_checkout(current: &Path, candidate: &Path) -> bool {
match (current.canonicalize(), candidate.canonicalize()) {
(Ok(current), Ok(candidate)) => current == candidate,
_ => current == candidate,
}
}
fn check_result(name: &str, result: Result<bool>) -> SelfTestCheck {
match result {
Ok(true) => SelfTestCheck {
name: name.into(),
passed: true,
detail: "passed".into(),
},
Ok(false) => SelfTestCheck {
name: name.into(),
passed: false,
detail: "assertion failed".into(),
},
Err(error) => SelfTestCheck {
name: name.into(),
passed: false,
detail: error.to_string(),
},
}
}
fn required<'a>(label: &str, value: &'a str) -> Result<&'a str> {
let value = value.trim();
if value.is_empty() {
bail!("{label} must not be empty");
}
Ok(value)
}
fn age_seconds(timestamp: &str) -> Option<i64> {
let timestamp = DateTime::parse_from_rfc3339(timestamp).ok()?;
Some(
(Utc::now() - timestamp.with_timezone(&Utc))
.num_seconds()
.max(0),
)
}
fn append_receipt(root: &Path, event: &str, actor: &str, detail: &str) -> Result<()> {
append_receipt_at(&root.join(RECEIPTS_PATH), event, actor, detail)
}
#[derive(Debug, Serialize, Deserialize)]
struct EvidenceDegradationRecord {
timestamp: String,
event: String,
actor: String,
detail: String,
error: String,
}
fn record_evidence_degradation(
root: &Path,
event: &str,
actor: &str,
detail: &str,
error: &anyhow::Error,
) {
eprintln!(
"Yana Giám Thị: safety event '{event}' by '{actor}' succeeded but its receipt could \
not be recorded: {error:#}. This event is logged to {EVIDENCE_DEGRADED_PATH} instead \
of the hash chain; review it manually."
);
let record = EvidenceDegradationRecord {
timestamp: state::now(),
event: event.to_string(),
actor: actor.to_string(),
detail: detail.to_string(),
error: format!("{error:#}"),
};
let path = root.join(EVIDENCE_DEGRADED_PATH);
if ensure_parent(&path).is_err() {
return;
}
let mut options = OpenOptions::new();
options.append(true).create(true);
#[cfg(unix)]
options.mode(0o600).custom_flags(libc::O_NOFOLLOW);
let Ok(mut file) = options.open(&path) else {
return;
};
if let Ok(line) = serde_json::to_string(&record) {
let _ = writeln!(file, "{line}");
let _ = file.sync_all();
}
}
fn evidence_degradation_summary(root: &Path) -> (usize, Option<EvidenceDegradationRecord>) {
let Ok(Some(text)) = read_regular_text(&root.join(EVIDENCE_DEGRADED_PATH)) else {
return (0, None);
};
let records: Vec<EvidenceDegradationRecord> = text
.lines()
.filter(|line| !line.trim().is_empty())
.filter_map(|line| serde_json::from_str(line).ok())
.collect();
let count = records.len();
(count, records.into_iter().next_back())
}
const RECEIPTS_ROTATION_THRESHOLD_BYTES: u64 = 5 * 1024 * 1024;
struct ReceiptsLock {
#[cfg(unix)]
file: File,
#[cfg(windows)]
file: File,
}
impl Drop for ReceiptsLock {
fn drop(&mut self) {
#[cfg(unix)]
unsafe {
libc::flock(
std::os::unix::io::AsRawFd::as_raw_fd(&self.file),
libc::LOCK_UN,
);
}
}
}
fn receipts_lock_path(receipts_path: &Path) -> PathBuf {
let file_name = receipts_path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("supervisor-receipts.jsonl");
receipts_path.with_file_name(format!("{file_name}.chainlock"))
}
const RECEIPTS_LOCK_TIMEOUT: Duration = Duration::from_secs(10);
const RECEIPTS_LOCK_POLL_INTERVAL: Duration = Duration::from_millis(25);
#[cfg(unix)]
fn acquire_receipts_lock(receipts_path: &Path) -> Result<ReceiptsLock> {
let lock_path = receipts_lock_path(receipts_path);
ensure_parent(&lock_path)?;
let file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.mode(0o600)
.custom_flags(libc::O_NOFOLLOW)
.open(&lock_path)
.with_context(|| format!("opening receipts lock {}", lock_path.display()))?;
let deadline = std::time::Instant::now() + RECEIPTS_LOCK_TIMEOUT;
loop {
let result = unsafe {
libc::flock(
std::os::unix::io::AsRawFd::as_raw_fd(&file),
libc::LOCK_EX | libc::LOCK_NB,
)
};
if result == 0 {
return Ok(ReceiptsLock { file });
}
let error = std::io::Error::last_os_error();
let errno = error.raw_os_error();
if errno != Some(libc::EWOULDBLOCK) && errno != Some(libc::EAGAIN) {
return Err(error).context("acquiring receipts chain lock");
}
if std::time::Instant::now() >= deadline {
bail!(
"timed out acquiring receipts chain lock at {} after {:?}",
lock_path.display(),
RECEIPTS_LOCK_TIMEOUT
);
}
std::thread::sleep(RECEIPTS_LOCK_POLL_INTERVAL);
}
}
#[cfg(windows)]
fn acquire_receipts_lock(receipts_path: &Path) -> Result<ReceiptsLock> {
use std::os::windows::fs::OpenOptionsExt;
let lock_path = receipts_lock_path(receipts_path);
ensure_parent(&lock_path)?;
let deadline = std::time::Instant::now() + RECEIPTS_LOCK_TIMEOUT;
loop {
let mut options = OpenOptions::new();
options.create(true).read(true).write(true).share_mode(0);
match options.open(&lock_path) {
Ok(file) => return Ok(ReceiptsLock { file }),
Err(error) => {
if error.raw_os_error() != Some(32) {
return Err(error)
.with_context(|| format!("opening receipts lock {}", lock_path.display()));
}
}
}
if std::time::Instant::now() >= deadline {
bail!(
"timed out acquiring receipts chain lock at {} after {:?}",
lock_path.display(),
RECEIPTS_LOCK_TIMEOUT
);
}
std::thread::sleep(RECEIPTS_LOCK_POLL_INTERVAL);
}
}
#[cfg(not(any(unix, windows)))]
fn acquire_receipts_lock(_receipts_path: &Path) -> Result<ReceiptsLock> {
bail!("receipts chain locking is only implemented for unix and windows")
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct RotationAnchor {
previous_hash: String,
previous_sequence: u64,
archived_path: String,
}
fn anchor_path(receipts_path: &Path) -> PathBuf {
let file_name = receipts_path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("supervisor-receipts.jsonl");
receipts_path.with_file_name(format!("{file_name}.anchor"))
}
fn maybe_rotate_receipts(path: &Path) -> Result<()> {
maybe_rotate_receipts_over(path, RECEIPTS_ROTATION_THRESHOLD_BYTES)
}
fn maybe_rotate_receipts_over(path: &Path, threshold_bytes: u64) -> Result<()> {
let metadata = match fs::metadata(path) {
Ok(metadata) => metadata,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(error) => return Err(error).with_context(|| format!("inspecting {}", path.display())),
};
if metadata.len() < threshold_bytes {
return Ok(());
}
let now_unix_secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let file_name = path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("supervisor-receipts.jsonl");
let archived = path.with_file_name(format!("{file_name}.{now_unix_secs}.rotated"));
if archived.exists() {
bail!(
"refusing to rotate receipts: archive target already exists: {}",
archived.display()
);
}
if let Some((last_hash, last_sequence)) = last_receipt_seed(path)? {
let anchor = RotationAnchor {
previous_hash: last_hash,
previous_sequence: last_sequence,
archived_path: archived.display().to_string(),
};
write_json_atomic(&anchor_path(path), &anchor)?;
}
fs::rename(path, &archived)
.with_context(|| format!("rotating {} to {}", path.display(), archived.display()))
}
fn last_receipt_seed(path: &Path) -> Result<Option<(String, u64)>> {
let Some(text) = read_regular_text(path)? else {
return Ok(None);
};
let mut last = None;
for line in text.lines().filter(|line| !line.trim().is_empty()) {
let receipt: Receipt = serde_json::from_str(line)
.with_context(|| format!("invalid receipt chain {}", path.display()))?;
last = Some((receipt.hash, receipt.payload.sequence));
}
Ok(last)
}
fn append_receipt_at(path: &Path, event: &str, actor: &str, detail: &str) -> Result<()> {
let _lock = acquire_receipts_lock(path)?;
maybe_rotate_receipts(path)?;
let (valid, _, previous_hash, sequence) = verify_active_segment(path)?;
if !valid {
bail!(
"refusing to append to tampered receipt chain: {}",
path.display()
);
}
let payload = ReceiptPayload {
schema_version: 1,
sequence,
timestamp: state::now(),
event: event.into(),
actor: actor.into(),
detail: detail.into(),
previous_hash,
};
let hash = hash_payload(&payload)?;
let receipt = Receipt { payload, hash };
ensure_parent(path)?;
let mut options = OpenOptions::new();
options.append(true).create(true);
#[cfg(unix)]
options.mode(0o600).custom_flags(libc::O_NOFOLLOW);
let mut file = options
.open(path)
.with_context(|| format!("opening {}", path.display()))?;
writeln!(file, "{}", serde_json::to_string(&receipt)?)?;
file.sync_all()?;
Ok(())
}
fn verify_receipts(root: &Path) -> Result<(bool, usize)> {
let active_path = root.join(RECEIPTS_PATH);
let segments = archived_segments(&active_path)?;
let mut previous_hash = "GENESIS".to_string();
let mut expected_sequence: u64 = 1;
let mut total = 0usize;
for segment_path in segments.iter().chain(std::iter::once(&active_path)) {
let (valid, count, next_hash, next_sequence) =
verify_segment_from(segment_path, previous_hash, expected_sequence)?;
total += count;
if !valid {
return Ok((false, total));
}
previous_hash = next_hash;
expected_sequence = next_sequence;
}
Ok((true, total))
}
fn archived_segments(active_path: &Path) -> Result<Vec<PathBuf>> {
let Some(parent) = active_path.parent() else {
return Ok(Vec::new());
};
let Some(file_name) = active_path.file_name().and_then(|name| name.to_str()) else {
return Ok(Vec::new());
};
let prefix = format!("{file_name}.");
let entries = match fs::read_dir(parent) {
Ok(entries) => entries,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(error) => return Err(error).with_context(|| format!("listing {}", parent.display())),
};
let mut segments: Vec<(u64, PathBuf)> = Vec::new();
for entry in entries {
let entry = entry?;
let Some(name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
let Some(rest) = name.strip_prefix(&prefix) else {
continue;
};
let Some(timestamp_text) = rest.strip_suffix(".rotated") else {
continue;
};
let Ok(timestamp) = timestamp_text.parse::<u64>() else {
continue;
};
segments.push((timestamp, entry.path()));
}
segments.sort_by_key(|(timestamp, _)| *timestamp);
Ok(segments.into_iter().map(|(_, path)| path).collect())
}
fn verify_segment_from(
path: &Path,
mut previous_hash: String,
mut expected_sequence: u64,
) -> Result<(bool, usize, String, u64)> {
let Some(text) = read_regular_text(path)? else {
return Ok((true, 0, previous_hash, expected_sequence));
};
let mut count = 0;
for line in text.lines().filter(|line| !line.trim().is_empty()) {
let receipt: Receipt = serde_json::from_str(line)
.with_context(|| format!("invalid receipt chain {}", path.display()))?;
if receipt.payload.sequence != expected_sequence
|| receipt.payload.previous_hash != previous_hash
|| receipt.hash != hash_payload(&receipt.payload)?
{
return Ok((false, count, previous_hash, expected_sequence));
}
previous_hash = receipt.hash;
expected_sequence += 1;
count += 1;
}
Ok((true, count, previous_hash, expected_sequence))
}
fn active_segment_seed(path: &Path) -> Result<(String, u64)> {
match read_json_optional::<RotationAnchor>(&anchor_path(path))? {
Some(anchor) => Ok((anchor.previous_hash, anchor.previous_sequence + 1)),
None => Ok(("GENESIS".to_string(), 1)),
}
}
fn verify_active_segment(path: &Path) -> Result<(bool, usize, String, u64)> {
let (genesis_hash, genesis_sequence) = active_segment_seed(path)?;
verify_segment_from(path, genesis_hash, genesis_sequence)
}
fn verify_receipts_at(path: &Path) -> Result<(bool, usize)> {
let (valid, count, _, _) = verify_active_segment(path)?;
Ok((valid, count))
}
fn hash_payload(payload: &ReceiptPayload) -> Result<String> {
let digest = Sha256::digest(serde_json::to_vec(payload)?);
Ok(digest.iter().map(|byte| format!("{byte:02x}")).collect())
}
fn read_json_optional<T: for<'de> Deserialize<'de>>(path: &Path) -> Result<Option<T>> {
let Some(text) = read_regular_text(path)? else {
return Ok(None);
};
serde_json::from_str(&text)
.with_context(|| format!("invalid JSON {}", path.display()))
.map(Some)
}
fn read_regular_text(path: &Path) -> Result<Option<String>> {
let metadata = match fs::symlink_metadata(path) {
Ok(metadata) => metadata,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(error) => return Err(error).with_context(|| format!("inspecting {}", path.display())),
};
if metadata.file_type().is_symlink() || !metadata.is_file() {
bail!(
"refusing non-regular supervisor evidence: {}",
path.display()
);
}
let mut options = OpenOptions::new();
options.read(true);
#[cfg(unix)]
options.custom_flags(libc::O_NOFOLLOW);
let mut file = options.open(path)?;
let mut text = String::new();
file.read_to_string(&mut text)?;
Ok(Some(text))
}
fn write_json_atomic<T: Serialize>(path: &Path, value: &T) -> Result<()> {
write_atomic(path, &serde_json::to_vec_pretty(value)?)
}
fn write_private_new(path: &Path, bytes: &[u8]) -> Result<()> {
ensure_parent(path)?;
let mut options = OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
options.mode(0o600).custom_flags(libc::O_NOFOLLOW);
let mut file = options.open(path)?;
file.write_all(bytes)?;
file.sync_all()?;
Ok(())
}
fn write_atomic(path: &Path, bytes: &[u8]) -> Result<()> {
ensure_parent(path)?;
if let Ok(metadata) = fs::symlink_metadata(path) {
if metadata.file_type().is_symlink() || !metadata.is_file() {
bail!("refusing non-regular supervisor state: {}", path.display());
}
}
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let temporary = path.with_extension(format!("tmp.{}.{}", std::process::id(), nonce));
write_private_new(&temporary, bytes)?;
#[cfg(target_os = "windows")]
if path.exists() {
fs::remove_file(path)?;
}
let result = fs::rename(&temporary, path);
if result.is_err() {
let _ = fs::remove_file(&temporary);
}
result.with_context(|| format!("replacing {}", path.display()))
}
fn ensure_parent(path: &Path) -> Result<()> {
let parent = path
.parent()
.ok_or_else(|| anyhow::anyhow!("path has no parent: {}", path.display()))?;
fs::create_dir_all(parent)?;
#[cfg(unix)]
fs::set_permissions(parent, fs::Permissions::from_mode(0o700))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn root() -> PathBuf {
std::env::temp_dir().join(format!("yana-supervisor-{}", Uuid::new_v4()))
}
#[test]
fn receipt_chain_detects_tampering() {
let root = root();
fs::create_dir_all(&root).unwrap();
append_receipt(&root, "one", "test", "first").unwrap();
append_receipt(&root, "two", "test", "second").unwrap();
assert_eq!(verify_receipts(&root).unwrap(), (true, 2));
let path = root.join(RECEIPTS_PATH);
let text = fs::read_to_string(&path)
.unwrap()
.replace("first", "forged");
fs::write(&path, text).unwrap();
assert_eq!(verify_receipts(&root).unwrap().0, false);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn concurrent_appenders_do_not_corrupt_the_chain() {
let root = root();
fs::create_dir_all(&root).unwrap();
append_receipt(&root, "seed", "test", "seed").unwrap();
let barrier = std::sync::Arc::new(std::sync::Barrier::new(2));
let handles: Vec<_> = ["race-a", "race-b"]
.into_iter()
.map(|event| {
let root = root.clone();
let barrier = barrier.clone();
std::thread::spawn(move || {
barrier.wait();
append_receipt(&root, event, "racer", "concurrent")
})
})
.collect();
for handle in handles {
handle.join().unwrap().expect("append must not error");
}
let (valid, count) = verify_receipts(&root).unwrap();
assert!(valid, "concurrent appends must not corrupt the chain");
assert_eq!(
count, 3,
"both concurrent entries must be present, serialized"
);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn halt_still_engages_when_the_receipt_chain_is_already_corrupted() {
let root = root();
fs::create_dir_all(&root).unwrap();
append_receipt(&root, "one", "test", "first").unwrap();
let receipts_path = root.join(RECEIPTS_PATH);
let corrupted = fs::read_to_string(&receipts_path)
.unwrap()
.replace("first", "forged");
fs::write(&receipts_path, corrupted).unwrap();
assert_eq!(
verify_receipts(&root).unwrap().0,
false,
"precondition: chain must actually be corrupted before this test means anything"
);
let result = halt(&root, "adversarial test", "auditor");
assert!(
result.is_ok(),
"HALT must engage even when the receipt chain is corrupted, got: {result:?}"
);
assert!(
root.join(HALT_PATH).is_file(),
"the HALT lock file itself must exist"
);
let (degraded_count, last) = evidence_degradation_summary(&root);
assert_eq!(
degraded_count, 1,
"the failed receipt must be recorded in the degraded-evidence trail"
);
assert_eq!(last.unwrap().event, "supervisor.halt");
fs::remove_dir_all(root).unwrap();
}
#[test]
fn receipts_rotate_once_the_active_file_crosses_the_threshold_and_preserve_all_evidence() {
let root = root();
fs::create_dir_all(&root).unwrap();
append_receipt(&root, "one", "test", "first").unwrap();
append_receipt(&root, "two", "test", "second").unwrap();
let path = root.join(RECEIPTS_PATH);
let current_size = fs::metadata(&path).unwrap().len();
maybe_rotate_receipts_over(&path, current_size).unwrap();
assert!(
!path.exists(),
"active file must be archived, not left in place"
);
let archived: Vec<_> = fs::read_dir(path.parent().unwrap())
.unwrap()
.filter_map(|entry| entry.ok())
.filter(|entry| entry.file_name().to_string_lossy().ends_with(".rotated"))
.collect();
assert_eq!(archived.len(), 1, "exactly one archived segment expected");
let (archived_valid, archived_count) = verify_receipts_at(&archived[0].path()).unwrap();
assert!(archived_valid, "archived segment must still verify cleanly");
assert_eq!(
archived_count, 2,
"both original entries preserved in the archive, not lost"
);
append_receipt(&root, "three", "test", "third").unwrap();
assert_eq!(verify_receipts(&root).unwrap(), (true, 3));
assert_eq!(verify_receipts_at(&path).unwrap(), (true, 1));
fs::remove_dir_all(root).unwrap();
}
#[test]
fn the_entry_written_after_rotation_cryptographically_references_the_archived_segments_last_entry(
) {
let root = root();
fs::create_dir_all(&root).unwrap();
append_receipt(&root, "one", "test", "first").unwrap();
append_receipt(&root, "two", "test", "second").unwrap();
let path = root.join(RECEIPTS_PATH);
let size = fs::metadata(&path).unwrap().len();
maybe_rotate_receipts_over(&path, size).unwrap();
let (_, archived_count, archived_last_hash, _) =
verify_segment_from(&archived_segments(&path).unwrap()[0], "GENESIS".into(), 1)
.unwrap();
assert_eq!(archived_count, 2);
append_receipt(&root, "three", "test", "third").unwrap();
let new_first_line = fs::read_to_string(&path).unwrap();
let receipt: Receipt =
serde_json::from_str(new_first_line.lines().next().unwrap()).unwrap();
assert_eq!(
receipt.payload.previous_hash, archived_last_hash,
"the first entry in the new segment must reference the archived segment's real last hash, not a fresh GENESIS"
);
assert_eq!(
receipt.payload.sequence, 3,
"sequence numbers continue across the rotation boundary, they do not reset to 1"
);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn tampering_with_an_archived_segment_is_caught_by_the_full_chain_walk_even_though_the_active_segment_alone_looks_fine(
) {
let root = root();
fs::create_dir_all(&root).unwrap();
append_receipt(&root, "one", "test", "first").unwrap();
append_receipt(&root, "two", "test", "second").unwrap();
let path = root.join(RECEIPTS_PATH);
let size = fs::metadata(&path).unwrap().len();
maybe_rotate_receipts_over(&path, size).unwrap();
append_receipt(&root, "three", "test", "third").unwrap();
let archived_path = archived_segments(&path)
.unwrap()
.into_iter()
.next()
.unwrap();
let tampered = fs::read_to_string(&archived_path)
.unwrap()
.replace("first", "forged");
fs::write(&archived_path, tampered).unwrap();
assert!(!verify_receipts(&root).unwrap().0);
assert!(verify_receipts_at(&path).unwrap().0);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn rotation_is_a_no_op_below_the_threshold() {
let root = root();
fs::create_dir_all(&root).unwrap();
append_receipt(&root, "one", "test", "first").unwrap();
let path = root.join(RECEIPTS_PATH);
let size = fs::metadata(&path).unwrap().len();
maybe_rotate_receipts_over(&path, size + 1).unwrap();
assert!(path.exists(), "file must not rotate while under threshold");
assert_eq!(verify_receipts(&root).unwrap(), (true, 1));
fs::remove_dir_all(root).unwrap();
}
#[test]
fn unlock_requires_explicit_human_approval() {
let root = root();
fs::create_dir_all(&root).unwrap();
halt(&root, "unsafe action", "human").unwrap();
assert!(unlock(&root, false, "reviewed", "human").is_err());
assert!(root.join(HALT_PATH).is_file());
unlock(&root, true, "reviewed", "human").unwrap();
assert!(!root.join(HALT_PATH).exists());
fs::remove_dir_all(root).unwrap();
}
#[test]
fn quarantine_is_explicit_and_human_cleared() {
let root = root();
fs::create_dir_all(&root).unwrap();
let record =
set_quarantine(&root, QuarantineMode::NoShell, "investigating", "human").unwrap();
assert_eq!(record.mode, QuarantineMode::NoShell);
assert!(clear_quarantine(&root, false, "done", "human").is_err());
clear_quarantine(&root, true, "done", "human").unwrap();
assert!(!root.join(QUARANTINE_PATH).exists());
fs::remove_dir_all(root).unwrap();
}
#[test]
fn self_test_never_creates_production_halt() {
let root = root();
fs::create_dir_all(root.join("core/hooks")).unwrap();
fs::create_dir_all(root.join(".claude/hooks")).unwrap();
fs::create_dir_all(root.join(".codex/hooks")).unwrap();
for path in [
root.join("core/hooks/giamthi-halt-check.sh"),
root.join(".claude/hooks/giamthi-halt-check.sh"),
root.join(".codex/hooks/giamthi-halt-check.sh"),
] {
fs::write(path, "same").unwrap();
}
assert!(self_test(&root).passed);
assert!(!root.join(HALT_PATH).exists());
fs::remove_dir_all(root).unwrap();
}
#[test]
fn dashboard_distinguishes_scheduler_resident_and_compatibility_watcher() {
let root = root();
fs::create_dir_all(&root).unwrap();
let report = dashboard(&root).unwrap();
assert_eq!(
report.service.installed,
report.periodic_scheduler.installed
);
assert!(!report.resident_service.installed);
assert_eq!(report.resident_service.registered, Some(false));
assert_eq!(report.compatibility_watcher.state, "not-installed");
assert_eq!(
report.native_helper.runtime_version,
env!("CARGO_PKG_VERSION")
);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn dashboard_discovery_reports_other_checkout_definitions_without_deleting_them() {
let sandbox = root();
let current = sandbox.join("current checkout");
let old = sandbox.join("old checkout");
let home = sandbox.join("home");
let definitions = home.join("Library/LaunchAgents");
fs::create_dir_all(¤t).unwrap();
fs::create_dir_all(&old).unwrap();
fs::create_dir_all(&definitions).unwrap();
let current_definition = definitions.join("com.yana.service.current.plist");
fs::write(
¤t_definition,
format!(
"<plist><dict><key>WorkingDirectory</key><string>{}</string></dict></plist>",
current.display()
),
)
.unwrap();
let old_definition = definitions.join("com.yana.system-health.old.plist");
fs::write(
&old_definition,
format!(
"<plist><dict><key>WorkingDirectory</key><string>{}</string></dict></plist>",
old.display()
),
)
.unwrap();
let watcher_definition = definitions.join("com.yanaai.giamthi-watch.old.plist");
fs::write(
&watcher_definition,
format!(
"<plist><dict><key>ProgramArguments</key><array><string>/bin/bash</string><string>{}/.claude/scripts/giamthi-watch.sh</string></array></dict></plist>",
old.display()
),
)
.unwrap();
let findings = service_definition_findings_at(¤t, &home, "macos").unwrap();
assert_eq!(findings.len(), 2);
assert!(findings
.iter()
.all(|finding| finding.target.as_deref() == Some(old.to_string_lossy().as_ref())));
assert!(findings
.iter()
.any(|finding| finding.component == "periodic-scheduler"));
assert!(findings
.iter()
.any(|finding| finding.component == "compatibility-watcher"));
assert!(current_definition.is_file());
assert!(old_definition.is_file());
assert!(watcher_definition.is_file());
fs::remove_dir_all(sandbox).unwrap();
}
#[test]
fn compatibility_definition_targets_parse_on_linux_and_windows() {
let sandbox = root();
let target = sandbox.join("checkout with spaces");
fs::create_dir_all(&target).unwrap();
let linux = sandbox.join("yana-giamthi-old.service");
fs::write(
&linux,
format!("[Service]\nWorkingDirectory=\"{}\"\n", target.display()),
)
.unwrap();
assert_eq!(
definition_component(&linux, "linux"),
Some("compatibility-watcher")
);
assert_eq!(
definition_target(&linux, "compatibility-watcher").unwrap(),
Some(target.clone())
);
let windows_dir = sandbox.join(".yana-ai/giamthi");
fs::create_dir_all(&windows_dir).unwrap();
let windows = windows_dir.join("old.json");
fs::write(&windows, serde_json::json!({"target": target}).to_string()).unwrap();
assert_eq!(
definition_component(&windows, "windows"),
Some("compatibility-watcher")
);
assert_eq!(
definition_target(&windows, "compatibility-watcher").unwrap(),
Some(target)
);
fs::remove_dir_all(sandbox).unwrap();
}
#[test]
fn hook_check_uses_event_specific_halt_shapes_without_jq() {
let root = root();
fs::create_dir_all(root.join(".claude/state")).unwrap();
fs::write(root.join(HALT_PATH), "line one\nline two\\quoted\n").unwrap();
let pre = hook_check(&root, r#"{"hook_event_name":"PreToolUse"}"#);
assert_eq!(pre.exit_code, 2);
assert_eq!(
serde_json::from_str::<serde_json::Value>(&pre.stdout).unwrap()["hookSpecificOutput"]
["permissionDecision"],
"deny"
);
let session = hook_check(&root, r#"{"hook_event_name":"SessionStart"}"#);
assert_eq!(session.exit_code, 0);
assert_eq!(
serde_json::from_str::<serde_json::Value>(&session.stdout).unwrap()["continue"],
false
);
let prompt = hook_check(&root, r#"{"hook_event_name":"UserPromptSubmit"}"#);
assert_eq!(prompt.exit_code, 0);
assert_eq!(
serde_json::from_str::<serde_json::Value>(&prompt.stdout).unwrap()["decision"],
"block"
);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn hook_check_preserves_tool_scoped_quarantine() {
let root = root();
fs::create_dir_all(root.join(".claude/state")).unwrap();
set_quarantine(&root, QuarantineMode::NoShell, "investigating", "human").unwrap();
assert_eq!(
hook_check(
&root,
r#"{"hook_event_name":"PreToolUse","tool_name":"Read"}"#
),
hook_allow()
);
assert_eq!(
hook_check(
&root,
r#"{"hook_event_name":"PreToolUse","tool_name":"Bash"}"#
)
.exit_code,
2
);
assert_eq!(
hook_check(&root, r#"{"hook_event_name":"SessionStart"}"#),
hook_allow()
);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn hook_check_fails_closed_on_active_malformed_state_or_input() {
let root = root();
fs::create_dir_all(root.join(".claude/state")).unwrap();
fs::write(root.join(QUARANTINE_PATH), "not-json").unwrap();
assert_eq!(
hook_check(&root, r#"{"hook_event_name":"PreToolUse"}"#).exit_code,
2
);
assert_eq!(hook_check(&root, "not-json").exit_code, 2);
fs::remove_file(root.join(QUARANTINE_PATH)).unwrap();
fs::create_dir(root.join(HALT_PATH)).unwrap();
assert_eq!(
hook_check(&root, r#"{"hook_event_name":"SessionStart"}"#).exit_code,
0
);
assert_eq!(
serde_json::from_str::<serde_json::Value>(
&hook_check(&root, r#"{"hook_event_name":"SessionStart"}"#).stdout
)
.unwrap()["continue"],
false
);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn hook_check_allows_when_no_safety_state_exists() {
let root = root();
fs::create_dir_all(&root).unwrap();
assert_eq!(hook_check(&root, "not-json"), hook_allow());
fs::remove_dir_all(root).unwrap();
}
#[test]
fn dashboard_surfaces_phase_3_5_8_evidence_without_deciding_mode() {
let root = root();
fs::create_dir_all(&root).unwrap();
let report = dashboard(&root).unwrap();
assert_eq!(report.mode, "normal");
assert!(report.host_capabilities.is_some());
assert!(report.last_reconciliation.is_none());
fs::remove_dir_all(root).unwrap();
}
#[test]
fn new_dashboard_evidence_never_overrides_an_active_halt() {
let root = root();
fs::create_dir_all(&root).unwrap();
halt(&root, "unsafe action", "human").unwrap();
let report = dashboard(&root).unwrap();
assert_eq!(report.mode, "halted");
assert!(report.host_capabilities.is_some());
fs::remove_dir_all(root).unwrap();
}
#[test]
fn tick_persists_reconciliation_state_for_the_next_tick_to_diff_against() {
let root = root();
fs::create_dir_all(&root).unwrap();
assert!(!root.join(RECONCILIATION_STATE_PATH).exists());
tick(&root).unwrap();
assert!(root.join(RECONCILIATION_STATE_PATH).is_file());
let state: events::ReconciliationState =
read_json_optional(&root.join(RECONCILIATION_STATE_PATH))
.unwrap()
.unwrap();
assert!(state.last_tick_unix_secs.is_some());
assert!(state.last_pressure.is_some());
fs::remove_dir_all(root).unwrap();
}
#[test]
fn a_genuine_pressure_change_between_ticks_is_recorded_as_a_receipt() {
let root = root();
fs::create_dir_all(&root).unwrap();
let seeded = events::ReconciliationState {
last_tick_unix_secs: Some(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
),
last_pressure: Some(pressure::PressureLevel::Unknown),
};
write_json_atomic(&root.join(RECONCILIATION_STATE_PATH), &seeded).unwrap();
let before = verify_receipts(&root).unwrap().1;
tick(&root).unwrap();
let after = verify_receipts(&root).unwrap().1;
assert!(after > before);
assert!(verify_receipts(&root).unwrap().0);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn reconcile_events_records_degradation_instead_of_silently_losing_a_detected_event() {
let root = root();
fs::create_dir_all(&root).unwrap();
let seeded = events::ReconciliationState {
last_tick_unix_secs: Some(1),
last_pressure: None,
};
write_json_atomic(&root.join(RECONCILIATION_STATE_PATH), &seeded).unwrap();
append_receipt(&root, "seed", "test", "seed").unwrap();
let receipts_path = root.join(RECEIPTS_PATH);
let tampered = fs::read_to_string(&receipts_path)
.unwrap()
.replace("seed", "forged");
fs::write(&receipts_path, tampered).unwrap();
assert!(!verify_receipts(&root).unwrap().0, "chain must be tampered before this test's real assertion — otherwise it isn't testing the failure path");
reconcile_events(&root);
let advanced: events::ReconciliationState =
read_json_optional(&root.join(RECONCILIATION_STATE_PATH))
.unwrap()
.unwrap();
assert_ne!(
advanced.last_tick_unix_secs, seeded.last_tick_unix_secs,
"reconciliation state should still advance even when the receipt \
append for a detected event fails -- this is the correct half \
of the existing behavior"
);
let (degraded_count, last) = evidence_degradation_summary(&root);
assert!(
degraded_count > 0,
"a detected reconciliation event whose receipt failed to append \
must be recorded to the evidence-degradation trail, not \
silently dropped -- see record_evidence_degradation, already \
used by halt()/set_quarantine() for the identical class of \
failure"
);
assert_eq!(
degraded_count, 2,
"expected both the Sleep and Wake events to be individually \
recorded to the degradation trail"
);
let last = last.expect("degraded_count > 0 implies a record exists");
assert_eq!(last.event, "supervisor.event");
assert!(
last.detail.contains("sleep") || last.detail.contains("wake"),
"degraded record should describe the actual lost event, not a \
generic message: {}",
last.detail
);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn reconcile_events_degrades_gracefully_on_a_corrupt_state_file() {
let root = root();
fs::create_dir_all(root.join(".yana-ai/os")).unwrap();
fs::write(root.join(RECONCILIATION_STATE_PATH), "not json").unwrap();
let result = tick(&root);
assert!(result.is_ok());
assert!(root.join(STATE_PATH).is_file());
fs::remove_dir_all(root).unwrap();
}
}