1use rusqlite::{OptionalExtension, params};
2use serde::{Deserialize, Serialize};
3
4use super::{
5 Execution, Receipts, Request, executions,
6 receipts::{Error, update_request},
7};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct HumanClaim {
12 pub request_id: crate::types::RequestId,
13 pub reviewer: crate::types::ReviewerId,
14 pub claimed_at: crate::types::Timestamp,
15}
16
17fn waiting(db: &rusqlite::Connection, id: &str) -> Result<(Request, Execution), Error> {
18 let data: Option<String> = db
19 .query_row(
20 "SELECT e.data FROM requests q JOIN executions e ON e.id=q.execution_id WHERE q.id=? AND q.status IN ('WAITING_HUMAN','DEFERRED') AND e.status='WAITING_HUMAN'",
21 [id],
22 |row| row.get(0),
23 )
24 .optional()?;
25 let data =
26 data.ok_or_else(|| Error::Invalid("Request is not waiting for a Human review.".into()))?;
27 let execution: Execution = super::unreadable::evidence("execution", &data)
28 .ok_or_else(|| Error::Invalid(format!("Request {id} has an unreadable execution.")))?;
29 let data: String = db.query_row(
30 "SELECT data FROM requests WHERE id=? AND status IN ('WAITING_HUMAN','DEFERRED')",
31 [&execution.provenance.request_id],
32 |row| row.get(0),
33 )?;
34 let request: Request =
35 super::unreadable::decode("request", execution.provenance.request_id.as_str(), &data)
36 .map_err(|error| Error::Invalid(error.to_string()))?;
37 if request.profile.kind() != crate::config::ProfileKind::Human
38 || request.human_definition.is_none()
39 {
40 return Err(Error::Invalid(
41 "Request has no recorded Human definition.".into(),
42 ));
43 }
44 Ok((request, execution))
45}
46
47fn claim(db: &rusqlite::Connection, request: &str) -> Result<HumanClaim, Error> {
48 Ok(db.query_row(
49 "SELECT id,claimed_by,claimed_at FROM requests WHERE id=? AND claimed_by IS NOT NULL",
50 [request],
51 |row| {
52 Ok(HumanClaim {
53 request_id: row.get(0)?,
54 reviewer: row.get(1)?,
55 claimed_at: row.get(2)?,
56 })
57 },
58 )?)
59}
60
61fn unclaim(db: &rusqlite::Connection, request: &str) -> Result<(), Error> {
63 db.execute(
64 "UPDATE requests SET claimed_by=NULL,claimed_at=NULL WHERE id=?",
65 [request],
66 )?;
67 Ok(())
68}
69
70fn claimant(
71 db: &rusqlite::Connection,
72 request: &str,
73 reviewer: &crate::types::ReviewerId,
74) -> Result<(), Error> {
75 let owner: Option<crate::types::ReviewerId> = db
76 .query_row(
77 "SELECT claimed_by FROM requests WHERE id=?",
78 [request],
79 |row| row.get(0),
80 )
81 .optional()?
82 .flatten();
83 if owner.as_ref() != Some(reviewer) {
84 return Err(Error::Invalid(
85 "Only the Human claimant may perform this action.".into(),
86 ));
87 }
88 Ok(())
89}
90
91impl Receipts {
92 pub(crate) async fn settled_human_requests(
93 &self,
94 ids: Vec<crate::types::RequestId>,
95 ) -> Result<Vec<Request>, String> {
96 self.connection
97 .call(move |db| -> Result<_, Error> {
98 let mut statement = db.prepare(
99 "SELECT data FROM requests WHERE id IN (SELECT value FROM json_each(?)) AND status NOT IN ('WAITING_HUMAN','DEFERRED')",
100 )?;
101 let rows = statement.query_map([serde_json::to_string(&ids)?], |row| {
102 row.get::<_, String>(0)
103 })?;
104 rows.filter_map(|row| match row {
105 Ok(data) => super::unreadable::evidence("request", &data).map(Ok),
106 Err(error) => Some(Err(Error::Sql(error))),
107 })
108 .collect()
109 })
110 .await
111 .map_err(|e| e.to_string())
112 }
113
114 pub(crate) async fn wait_for_human(
115 &self,
116 execution: &Execution,
117 request: &Request,
118 ) -> Result<(), String> {
119 let execution = execution.clone();
120 let request = request.clone();
121 self.connection
122 .call(move |db| -> Result<(), Error> {
123 let transaction =
124 db.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
125 let data = serde_json::to_string(&execution)?;
126 if execution.key.is_some() && !request.force {
127 if transaction.execute(
128 "UPDATE executions SET status='WAITING_HUMAN',data=? WHERE id=? AND owner_pid=? AND owner_start_time=? AND status='RUNNING'",
129 params![
130 data,
131 execution.id,
132 execution.owner_pid(),
133 execution.owner_start_time() as i64
134 ],
135 )? != 1
136 {
137 return Err(Error::Invalid("Active execution not found.".into()));
138 }
139 } else {
140 transaction.execute(
142 "INSERT INTO executions(id,key,eval_def_hash,status,owner_pid,owner_start_time,data) VALUES (?,NULL,?,'WAITING_HUMAN',?,?,?)",
143 params![
144 execution.id,
145 execution.eval_def_hash,
146 execution.owner_pid(),
147 execution.owner_start_time() as i64,
148 data
149 ],
150 )?;
151 }
152 update_request(&transaction, &request)?;
153 transaction.commit()?;
154 Ok(())
155 })
156 .await
157 .map_err(|e| e.to_string())
158 }
159
160 pub(crate) async fn follow_human(&self, request: &Request) -> Result<Request, String> {
161 let mut request = request.clone();
162 self.connection
163 .call(move |db| -> Result<_, Error> {
164 let transaction =
165 db.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
166 let data: String = transaction.query_row(
167 "SELECT data FROM executions WHERE id=? AND key=?",
168 params![request.execution_id, request.key],
169 |row| row.get(0),
170 )?;
171 let Some(execution) = super::unreadable::evidence::<Execution>("execution", &data)
172 else {
173 request.execution_id = None;
174 request.state =
175 super::RequestState::pending(crate::types::RequestStatus::Queued, None)
176 .expect("queued state");
177 update_request(&transaction, &request)?;
178 transaction.commit()?;
179 return Ok(request);
180 };
181 if execution.status() != crate::types::ExecutionStatus::WaitingHuman {
182 receive(&mut request, &execution);
183 }
184 update_request(&transaction, &request)?;
185 transaction.commit()?;
186 Ok(request)
187 })
188 .await
189 .map_err(|e| e.to_string())
190 }
191
192 pub(crate) async fn claim_human(
193 &self,
194 id: &crate::types::RequestId,
195 reviewer: &crate::types::ReviewerId,
196 ) -> Result<HumanClaim, String> {
197 let id = id.to_owned();
198 let reviewer = reviewer.to_owned();
199 self.connection
200 .call(move |db| -> Result<_, Error> {
201 let transaction =
202 db.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
203 let (request, _) = waiting(&transaction, &id)?;
204 transaction.execute(
205 "UPDATE requests SET claimed_by=?,claimed_at=? WHERE id=? AND claimed_by IS NULL",
206 params![reviewer, crate::broker::now(), request.id],
207 )?;
208 claimant(&transaction, &request.id, &reviewer)?;
209 let claim = claim(&transaction, &request.id)?;
210 transaction.commit()?;
211 Ok(claim)
212 })
213 .await
214 .map_err(|e| e.to_string())
215 }
216
217 pub(crate) async fn release_human(
219 &self,
220 id: &crate::types::RequestId,
221 reviewer: &crate::types::ReviewerId,
222 ) -> Result<HumanClaim, String> {
223 let id = id.to_owned();
224 let reviewer = reviewer.to_owned();
225 self.connection
226 .call(move |db| -> Result<_, Error> {
227 let transaction =
228 db.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
229 let (request, _) = waiting(&transaction, &id)?;
230 claimant(&transaction, &request.id, &reviewer)?;
231 let claim = claim(&transaction, &request.id)?;
232 unclaim(&transaction, &request.id)?;
233 transaction.commit()?;
234 Ok(claim)
235 })
236 .await
237 .map_err(|e| e.to_string())
238 }
239
240 pub(crate) async fn waiting_human(
242 &self,
243 id: &crate::types::RequestId,
244 ) -> Result<Request, String> {
245 let id = id.to_owned();
246 self.connection
247 .call(move |db| -> Result<_, Error> {
248 let transaction = db.transaction()?;
249 let (request, _) = waiting(&transaction, &id)?;
250 transaction.commit()?;
251 Ok(request)
252 })
253 .await
254 .map_err(|e| e.to_string())
255 }
256
257 pub(crate) async fn human_request(
258 &self,
259 id: &crate::types::RequestId,
260 reviewer: &crate::types::ReviewerId,
261 ) -> Result<(Request, Execution), String> {
262 let id = id.to_owned();
263 let reviewer = reviewer.to_owned();
264 self.connection
265 .call(move |db| -> Result<_, Error> {
266 let transaction = db.transaction()?;
267 let (request, execution) = waiting(&transaction, &id)?;
268 claimant(&transaction, &request.id, &reviewer)?;
269 transaction.commit()?;
270 Ok((request, execution))
271 })
272 .await
273 .map_err(|e| e.to_string())
274 }
275
276 pub(crate) async fn settle_human(
277 &self,
278 request: &Request,
279 reviewer: &crate::types::ReviewerId,
280 ) -> Result<Request, String> {
281 let mut request = request.clone();
282 let reviewer = reviewer.to_owned();
283 self.connection
284 .call(move |db| -> Result<_, Error> {
285 let transaction =
286 db.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
287 let (current, mut execution) = waiting(&transaction, &request.id)?;
288 claimant(&transaction, ¤t.id, &reviewer)?;
289 request.state = match request.state {
290 super::RequestState::Completed { result, .. } => {
291 super::RequestState::completed(result, crate::broker::now())
292 }
293 super::RequestState::Failed { error, code, .. } => {
294 super::RequestState::failed(error, code, crate::broker::now())
295 }
296 _ => {
297 return Err(Error::Invalid(
298 "A Human submission needs a result or error.".into(),
299 ));
300 }
301 };
302 request.queue = None;
303 request.blocked_reason = None;
304 execution.state = request.state.clone().try_into().map_err(Error::Invalid)?;
305 execution.reviewer = Some(reviewer.clone());
306 execution.provenance.completed_at = request.completed_at();
307 request.provenance = Some(execution.provenance.clone());
308 let published = executions::settle(&transaction, &execution, &request)?;
309 let followers = {
310 let mut statement = transaction.prepare(
311 "SELECT data FROM requests WHERE execution_id=? AND status IN ('WAITING_HUMAN','DEFERRED')",
312 )?;
313 statement
314 .query_map([&execution.id], |row| row.get::<_, String>(0))?
315 .filter_map(|row| match row {
316 Ok(data) => super::unreadable::evidence("request", &data).map(Ok),
317 Err(error) => Some(Err(Error::Sql(error))),
318 })
319 .collect::<Result<Vec<Request>, Error>>()?
320 };
321 for mut follower in followers {
322 receive(&mut follower, &execution);
323 update_request(&transaction, &follower)?;
324 }
325 unclaim(&transaction, &request.id)?;
326 transaction.commit()?;
327 if published {
328 super::history::collect_after(db);
329 }
330 Ok(request)
331 })
332 .await
333 .map_err(|e| e.to_string())
334 }
335}
336
337pub(super) fn settle_waiting(db: &rusqlite::Connection, entry: &Execution) -> Result<(), Error> {
340 let waiting: Option<String> = db
341 .query_row(
342 "SELECT data FROM executions WHERE key=? AND status='WAITING_HUMAN'",
343 [&entry.key],
344 |row| row.get(0),
345 )
346 .optional()?;
347 let Some(waiting) = waiting else {
348 return Ok(());
349 };
350 let Some(mut execution) = super::unreadable::evidence::<Execution>("execution", &waiting)
351 else {
352 return Ok(());
353 };
354 let now = crate::broker::now();
355 execution.state = super::ExecutionState::Failed {
356 error: format!(
357 "Superseded by the completed result {} for this key.",
358 entry.id
359 ),
360 code: Some(crate::types::FailureCode::Superseded),
361 at: now,
362 };
363 execution.provenance.completed_at = Some(now);
364 db.execute(
365 "UPDATE executions SET status='ERROR',data=? WHERE id=? AND status='WAITING_HUMAN'",
366 params![serde_json::to_string(&execution)?, execution.id],
367 )?;
368 let followers = {
369 let mut statement = db
370 .prepare("SELECT data FROM requests WHERE execution_id=? AND status IN ('WAITING_HUMAN','DEFERRED')")?;
371 statement
372 .query_map([&execution.id], |row| row.get::<_, String>(0))?
373 .filter_map(|row| match row {
374 Ok(data) => super::unreadable::evidence("request", &data).map(Ok),
375 Err(error) => Some(Err(Error::Sql(error))),
376 })
377 .collect::<Result<Vec<Request>, Error>>()?
378 };
379 for mut follower in followers {
380 receive(&mut follower, entry);
381 update_request(db, &follower)?;
382 unclaim(db, &follower.id)?;
383 }
384 Ok(())
385}
386
387fn receive(request: &mut Request, execution: &Execution) {
388 crate::cache::reuse(request, execution, crate::broker::now());
389}