Skip to main content

ai_crew_sync/tools/
locks.rs

1use rmcp::{
2    ErrorData, Json, handler::server::wrapper::Parameters, service::RequestContext, tool,
3    tool_router,
4};
5use schemars::JsonSchema;
6use serde::Deserialize;
7
8use super::{Bus, auth_of};
9use crate::{
10    model::{Ack, LockList, LockResult},
11    store::locks,
12};
13
14#[derive(Debug, Deserialize, JsonSchema)]
15pub struct AcquireLockArgs {
16    /// Resource to lock, e.g. "deploy:staging" or "schema:users". Free-form,
17    /// lowercased; pick names the whole team will recognise.
18    pub name: String,
19    /// Seconds until the lock lapses on its own (5-86400, default 300).
20    /// Re-acquire before it expires to extend it.
21    #[serde(default)]
22    pub ttl_seconds: Option<i64>,
23    /// One line shown to whoever finds the resource locked.
24    #[serde(default)]
25    pub purpose: Option<String>,
26}
27
28#[derive(Debug, Deserialize, JsonSchema)]
29pub struct LockNameArgs {
30    /// The lock name.
31    pub name: String,
32}
33
34#[tool_router(router = locks_router, vis = "pub")]
35impl Bus {
36    #[tool(
37        description = "Take an advisory lock on a shared resource (a deploy target, a schema, \
38                       a file) before touching it. Returns acquired=false with the holder's name \
39                       if someone else has it — in that case wait_for_updates will wake you when \
40                       it frees. Locks expire on their own, so a crashed agent cannot jam the \
41                       team. Re-acquiring your own lock extends it."
42    )]
43    async fn acquire_lock(
44        &self,
45        ctx: RequestContext<rmcp::RoleServer>,
46        Parameters(args): Parameters<AcquireLockArgs>,
47    ) -> Result<Json<LockResult>, ErrorData> {
48        let auth = auth_of(&ctx)?;
49        Ok(Json(
50            locks::acquire_lock(&self.db, &auth, &args.name, args.ttl_seconds, args.purpose)
51                .await?,
52        ))
53    }
54
55    #[tool(
56        description = "Release a lock you hold, waking anyone waiting on it. Releasing a lock \
57                       you do not hold is an error."
58    )]
59    async fn release_lock(
60        &self,
61        ctx: RequestContext<rmcp::RoleServer>,
62        Parameters(args): Parameters<LockNameArgs>,
63    ) -> Result<Json<Ack>, ErrorData> {
64        let auth = auth_of(&ctx)?;
65        Ok(Json(
66            locks::release_lock(&self.db, &auth, &args.name).await?,
67        ))
68    }
69
70    #[tool(description = "List the team's currently held locks: who holds what, why, until when.")]
71    async fn list_locks(
72        &self,
73        ctx: RequestContext<rmcp::RoleServer>,
74    ) -> Result<Json<LockList>, ErrorData> {
75        let auth = auth_of(&ctx)?;
76        Ok(Json(locks::list_locks(&self.db, &auth).await?))
77    }
78}