use serde::{Deserialize, Serialize};
use crate::perspective::state::{LockDiffResult, LockScope, LockState, WatchStrategy};
#[derive(Clone, Debug, Deserialize)]
pub struct LockCreateInput {
pub agent_id: String,
pub scope: LockScope,
pub root_nodes: Vec<String>,
#[serde(default)]
pub radius: Option<u32>,
#[serde(default)]
pub query: Option<String>,
#[serde(default)]
pub path_nodes: Option<Vec<String>>,
}
#[derive(Clone, Debug, Serialize)]
pub struct LockCreateOutput {
pub lock_id: String,
pub scope: LockScope,
pub baseline_nodes: usize,
pub baseline_edges: usize,
pub graph_generation: u64,
pub created_at_ms: u64,
pub proof_state: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_suggested_tool: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_suggested_target: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_step_hint: Option<String>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct LockWatchInput {
pub agent_id: String,
pub lock_id: String,
pub strategy: WatchStrategy,
}
#[derive(Clone, Debug, Serialize)]
pub struct LockWatchOutput {
pub lock_id: String,
pub strategy: WatchStrategy,
#[serde(skip_serializing_if = "Option::is_none")]
pub previous_strategy: Option<WatchStrategy>,
pub proof_state: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_suggested_tool: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_suggested_target: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_step_hint: Option<String>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct LockDiffInput {
pub agent_id: String,
pub lock_id: String,
}
#[derive(Clone, Debug, Serialize)]
pub struct LockDiffOutput {
pub diff: LockDiffResult,
pub watcher_events_drained: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub rebase_suggested: Option<String>,
pub proof_state: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_suggested_tool: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_suggested_target: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_step_hint: Option<String>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct LockRebaseInput {
pub agent_id: String,
pub lock_id: String,
}
#[derive(Clone, Debug, Serialize)]
pub struct LockRebaseOutput {
pub lock_id: String,
pub previous_generation: u64,
pub new_generation: u64,
pub baseline_nodes: usize,
pub baseline_edges: usize,
pub watcher_preserved: bool,
pub proof_state: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_suggested_tool: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_suggested_target: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_step_hint: Option<String>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct LockReleaseInput {
pub agent_id: String,
pub lock_id: String,
}
#[derive(Clone, Debug, Serialize)]
pub struct LockReleaseOutput {
pub lock_id: String,
pub released: bool,
pub proof_state: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_suggested_tool: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_suggested_target: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_step_hint: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lock_create_input_deserializes_node_scope() {
let json = r#"{
"agent_id": "jimi",
"scope": "node",
"root_nodes": ["session.rs"]
}"#;
let input: LockCreateInput = serde_json::from_str(json).unwrap();
assert_eq!(input.scope, LockScope::Node);
assert_eq!(input.root_nodes.len(), 1);
assert!(input.radius.is_none());
}
#[test]
fn lock_create_input_deserializes_subgraph_scope() {
let json = r#"{
"agent_id": "jimi",
"scope": "subgraph",
"root_nodes": ["session.rs"],
"radius": 2
}"#;
let input: LockCreateInput = serde_json::from_str(json).unwrap();
assert_eq!(input.scope, LockScope::Subgraph);
assert_eq!(input.radius, Some(2));
}
#[test]
fn lock_watch_input_deserializes() {
let json = r#"{"agent_id": "jimi", "lock_id": "lock_jimi_001", "strategy": "on_ingest"}"#;
let input: LockWatchInput = serde_json::from_str(json).unwrap();
assert_eq!(input.strategy, WatchStrategy::OnIngest);
}
#[test]
fn lock_diff_input_minimal() {
let json = r#"{"agent_id": "jimi", "lock_id": "lock_jimi_001"}"#;
let input: LockDiffInput = serde_json::from_str(json).unwrap();
assert_eq!(input.lock_id, "lock_jimi_001");
}
#[test]
fn lock_release_input_minimal() {
let json = r#"{"agent_id": "jimi", "lock_id": "lock_jimi_001"}"#;
let input: LockReleaseInput = serde_json::from_str(json).unwrap();
assert_eq!(input.lock_id, "lock_jimi_001");
}
}