fix(arch): unify TS/Rust types + classroom persistence registration + approval audit
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
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
- M11-03: Register ClassroomPersistence via Tauri .setup() hook with in-memory fallback. Previously missing — classroom commands would crash at runtime. - M3-02: Document BrowserHand as schema validator + TypeScript delegation passthrough (dual-path architecture explicitly documented). - M4-04: Add defense-in-depth audit logging in execute_hand() and execute_hand_with_source() when needs_approval hands bypass approval gate. - TYPE-01: Add #[serde(rename_all = "camelCase")] to Rust AgentInfo. Add missing fields to TS AgentInfo (messageCount, createdAt, updatedAt). Fix KernelStatus TS interface to match Rust KernelStatusResponse (baseUrl/model instead of defaultProvider/defaultModel). - SEC2-P1-01: Document EXTRACTION_DRIVER OnceCell as legacy path; Kernel struct field is the active path. - TriggerSource: Add #[derive(PartialEq)] for approval audit comparisons.
This commit is contained in:
@@ -1,4 +1,19 @@
|
||||
//! Hand execution and run tracking
|
||||
//!
|
||||
//! # Approval Architecture
|
||||
//!
|
||||
//! Hands with `needs_approval: true` go through a two-phase flow:
|
||||
//! 1. **Entry point** (Tauri command `hand_execute`): checks `needs_approval` flag and
|
||||
//! `autonomy_level`. If approval is required, creates a `PendingApproval` and returns
|
||||
//! immediately — the hand is NOT executed yet.
|
||||
//! 2. **Approval** (Tauri command `hand_approve`): user approves → `respond_to_approval()`
|
||||
//! spawns `hands.execute()` directly (bypassing this `execute_hand()` method).
|
||||
//!
|
||||
//! This method (`execute_hand`) is the **direct execution path** used when approval is
|
||||
//! NOT required, or when the user has opted into autonomous mode. For defense-in-depth,
|
||||
//! we log a warning if a `needs_approval` hand reaches this path — it means the approval
|
||||
//! gate was bypassed (e.g., by the scheduler or trigger manager, which intentionally bypass
|
||||
//! approval for automated triggers).
|
||||
|
||||
use std::sync::Arc;
|
||||
use zclaw_types::{Result, HandRun, HandRunId, HandRunStatus, HandRunFilter, TriggerSource};
|
||||
@@ -17,12 +32,27 @@ impl Kernel {
|
||||
self.hands.list().await
|
||||
}
|
||||
|
||||
/// Execute a hand with the given input, tracking the run
|
||||
/// Execute a hand with the given input, tracking the run.
|
||||
///
|
||||
/// **Note:** For hands with `needs_approval: true`, the Tauri command layer should
|
||||
/// route through the approval flow instead of calling this method directly. Automated
|
||||
/// triggers (scheduler, trigger manager) intentionally bypass approval.
|
||||
pub async fn execute_hand(
|
||||
&self,
|
||||
hand_id: &str,
|
||||
input: serde_json::Value,
|
||||
) -> Result<(HandResult, HandRunId)> {
|
||||
// Defense-in-depth audit: log if a needs_approval hand reaches the direct path
|
||||
let configs = self.hands.list().await;
|
||||
if let Some(config) = configs.iter().find(|c| c.id == hand_id) {
|
||||
if config.needs_approval {
|
||||
tracing::warn!(
|
||||
"[Kernel] Hand '{}' has needs_approval=true but reached direct execution path. \
|
||||
Caller should route through approval flow instead.",
|
||||
hand_id
|
||||
);
|
||||
}
|
||||
}
|
||||
let run_id = HandRunId::new();
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
|
||||
@@ -119,13 +149,31 @@ impl Kernel {
|
||||
hand_result.map(|res| (res, run_id))
|
||||
}
|
||||
|
||||
/// Execute a hand with a specific trigger source (for scheduled/event triggers)
|
||||
/// Execute a hand with a specific trigger source (for scheduled/event triggers).
|
||||
///
|
||||
/// Automated trigger sources (Scheduler, Event, System) bypass the approval gate
|
||||
/// by design — the user explicitly configured these automated triggers.
|
||||
/// Manual trigger sources should go through the approval flow at the Tauri command layer.
|
||||
pub async fn execute_hand_with_source(
|
||||
&self,
|
||||
hand_id: &str,
|
||||
input: serde_json::Value,
|
||||
trigger_source: TriggerSource,
|
||||
) -> Result<(HandResult, HandRunId)> {
|
||||
// Audit: warn if a Manual trigger bypasses approval
|
||||
if trigger_source == TriggerSource::Manual {
|
||||
let configs = self.hands.list().await;
|
||||
if let Some(config) = configs.iter().find(|c| c.id == hand_id) {
|
||||
if config.needs_approval {
|
||||
tracing::warn!(
|
||||
"[Kernel] Hand '{}' (Manual trigger) has needs_approval=true but bypassed approval. \
|
||||
This should go through the approval flow.",
|
||||
hand_id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let run_id = HandRunId::new();
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user