fix(audit): 修复深度审计 P2 问题 — 自主授权后端守卫、反思历史累积、心跳持久化
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

- M5-补: hand_execute/skill_execute 接收 autonomy_level 参数,后端三层守卫
  (supervised 全部审批 / assisted 尊重 needs_approval / autonomous 跳过)
- M3: hand_approve/hand_cancel 移除 _hand_name 下划线,添加审计日志
- M4-补: 反思历史累积存储到 reflection:history:{agent_id} 数组(最多20条)
  get_history 优先读持久化历史,保留 latest key 向后兼容
- 心跳历史: VikingStorage 持久化 HeartbeatResult 数组,tick() 也存历史
  heartbeat_init 恢复历史,重启后不丢失
- L2: 确认 gatewayStore 仅注释引用,无需修改
- 身份回滚: 确认 IdentityChangeProposal.tsx 已实现 HistoryItem + restoreSnapshot
- 更新 DEEP_AUDIT_REPORT.md 完成度 72% (核心 92%, 真实可用 80%)
This commit is contained in:
iven
2026-03-27 11:32:35 +08:00
parent b7bc9ddcb1
commit 7ae6990c97
7 changed files with 295 additions and 63 deletions

View File

@@ -169,12 +169,28 @@ impl HeartbeatEngine {
// Execute heartbeat tick
let result = execute_tick(&agent_id, &config, &alert_sender).await;
// Store history
// Store history in-memory
let mut hist = history.lock().await;
hist.push(result);
if hist.len() > 100 {
*hist = hist.split_off(50);
}
// Persist history to VikingStorage (fire-and-forget)
let history_to_persist: Vec<HeartbeatResult> = hist.clone();
let aid = agent_id.clone();
tokio::spawn(async move {
if let Ok(storage) = crate::viking_commands::get_storage().await {
let key = format!("heartbeat:history:{}", aid);
if let Ok(json) = serde_json::to_string(&history_to_persist) {
if let Err(e) = zclaw_growth::VikingStorage::store_metadata_json(
&*storage, &key, &json,
).await {
tracing::warn!("[heartbeat] Failed to persist history: {}", e);
}
}
}
});
}
});
}
@@ -191,9 +207,34 @@ impl HeartbeatEngine {
*self.running.lock().await
}
/// Execute a single tick manually
/// Execute a single tick manually and persist the result to history
pub async fn tick(&self) -> HeartbeatResult {
execute_tick(&self.agent_id, &self.config, &self.alert_sender).await
let result = execute_tick(&self.agent_id, &self.config, &self.alert_sender).await;
// Store in history (same as the periodic loop)
let mut hist = self.history.lock().await;
hist.push(result.clone());
if hist.len() > 100 {
*hist = hist.split_off(50);
}
// Persist to VikingStorage
let history_to_persist: Vec<HeartbeatResult> = hist.clone();
let aid = self.agent_id.clone();
tokio::spawn(async move {
if let Ok(storage) = crate::viking_commands::get_storage().await {
let key = format!("heartbeat:history:{}", aid);
if let Ok(json) = serde_json::to_string(&history_to_persist) {
if let Err(e) = zclaw_growth::VikingStorage::store_metadata_json(
&*storage, &key, &json,
).await {
tracing::warn!("[heartbeat] Failed to persist history: {}", e);
}
}
}
});
result
}
/// Subscribe to alerts
@@ -208,6 +249,37 @@ impl HeartbeatEngine {
hist.iter().rev().take(limit).cloned().collect()
}
/// Restore heartbeat history from VikingStorage metadata (called during init)
async fn restore_history(&self) {
let key = format!("heartbeat:history:{}", self.agent_id);
match crate::viking_commands::get_storage().await {
Ok(storage) => {
match zclaw_growth::VikingStorage::get_metadata_json(&*storage, &key).await {
Ok(Some(json)) => {
if let Ok(persisted) = serde_json::from_str::<Vec<HeartbeatResult>>(&json) {
let count = persisted.len();
let mut hist = self.history.lock().await;
*hist = persisted;
tracing::info!(
"[heartbeat] Restored {} history entries for {}",
count, self.agent_id
);
}
}
Ok(None) => {
tracing::debug!("[heartbeat] No persisted history for {}", self.agent_id);
}
Err(e) => {
tracing::warn!("[heartbeat] Failed to restore history: {}", e);
}
}
}
Err(e) => {
tracing::warn!("[heartbeat] Storage unavailable during init: {}", e);
}
}
}
/// Update configuration
pub async fn update_config(&self, updates: HeartbeatConfig) {
let mut config = self.config.lock().await;
@@ -648,6 +720,9 @@ pub async fn heartbeat_init(
// Restore last interaction time from VikingStorage metadata
restore_last_interaction(&agent_id).await;
// Restore heartbeat history from VikingStorage metadata
engine.restore_history().await;
let mut engines = state.lock().await;
engines.insert(agent_id, engine);
Ok(())