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 signoff_attended(
120 &self,
121 ids: Vec<crate::types::RequestId>,
122 ) -> Result<bool, String> {
123 if ids.is_empty() {
124 return Ok(false);
125 }
126 self.connection
127 .call(move |db| -> Result<_, Error> {
128 Ok(db.query_row(
129 "SELECT EXISTS(SELECT 1 FROM requests q JOIN requests c ON c.execution_id=q.execution_id WHERE q.id IN (SELECT value FROM json_each(?1)) AND q.status IN ('WAITING_HUMAN','DEFERRED') AND c.claimed_by IS NOT NULL) OR EXISTS(SELECT 1 FROM requests WHERE id IN (SELECT value FROM json_each(?1)) AND status NOT IN ('WAITING_HUMAN','DEFERRED'))",
130 [serde_json::to_string(&ids)?],
131 |row| row.get(0),
132 )?)
133 })
134 .await
135 .map_err(|e| e.to_string())
136 }
137
138 pub(crate) async fn wait_for_human(
139 &self,
140 execution: &Execution,
141 request: &Request,
142 ) -> Result<(), String> {
143 let execution = execution.clone();
144 let request = request.clone();
145 self.connection
146 .call(move |db| -> Result<(), Error> {
147 let transaction =
148 db.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
149 let data = serde_json::to_string(&execution)?;
150 if execution.key.is_some() && !request.force {
151 if transaction.execute(
152 "UPDATE executions SET status='WAITING_HUMAN',data=? WHERE id=? AND owner_pid=? AND owner_start_time=? AND status='RUNNING'",
153 params![
154 data,
155 execution.id,
156 execution.owner_pid(),
157 execution.owner_start_time() as i64
158 ],
159 )? != 1
160 {
161 return Err(Error::Invalid("Active execution not found.".into()));
162 }
163 } else {
164 transaction.execute(
166 "INSERT INTO executions(id,key,eval_def_hash,status,owner_pid,owner_start_time,data) VALUES (?,NULL,?,'WAITING_HUMAN',?,?,?)",
167 params![
168 execution.id,
169 execution.eval_def_hash,
170 execution.owner_pid(),
171 execution.owner_start_time() as i64,
172 data
173 ],
174 )?;
175 }
176 update_request(&transaction, &request)?;
177 supersede_deferred(&transaction, &request)?;
178 transaction.commit()?;
179 Ok(())
180 })
181 .await
182 .map_err(|e| e.to_string())
183 }
184
185 pub(crate) async fn follow_human(&self, request: &Request) -> Result<Request, String> {
186 let mut request = request.clone();
187 self.connection
188 .call(move |db| -> Result<_, Error> {
189 let transaction =
190 db.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
191 let data: String = transaction.query_row(
192 "SELECT data FROM executions WHERE id=? AND key=?",
193 params![request.execution_id, request.key],
194 |row| row.get(0),
195 )?;
196 let Some(execution) = super::unreadable::evidence::<Execution>("execution", &data)
197 else {
198 request.execution_id = None;
199 request.state =
200 super::RequestState::pending(crate::types::RequestStatus::Queued, None)
201 .expect("queued state");
202 update_request(&transaction, &request)?;
203 transaction.commit()?;
204 return Ok(request);
205 };
206 if execution.status() != crate::types::ExecutionStatus::WaitingHuman {
207 receive(&mut request, &execution);
208 }
209 update_request(&transaction, &request)?;
210 if request.status().is_human_wait() {
211 supersede_deferred(&transaction, &request)?;
212 }
213 transaction.commit()?;
214 Ok(request)
215 })
216 .await
217 .map_err(|e| e.to_string())
218 }
219
220 pub(crate) async fn claim_human(
221 &self,
222 id: &crate::types::RequestId,
223 reviewer: &crate::types::ReviewerId,
224 ) -> Result<HumanClaim, String> {
225 let id = id.to_owned();
226 let reviewer = reviewer.to_owned();
227 self.connection
228 .call(move |db| -> Result<_, Error> {
229 let transaction =
230 db.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
231 let (request, _) = waiting(&transaction, &id)?;
232 transaction.execute(
233 "UPDATE requests SET claimed_by=?,claimed_at=? WHERE id=? AND claimed_by IS NULL",
234 params![reviewer, crate::broker::now(), request.id],
235 )?;
236 claimant(&transaction, &request.id, &reviewer)?;
237 let claim = claim(&transaction, &request.id)?;
238 transaction.commit()?;
239 Ok(claim)
240 })
241 .await
242 .map_err(|e| e.to_string())
243 }
244
245 pub(crate) async fn release_human(
247 &self,
248 id: &crate::types::RequestId,
249 reviewer: &crate::types::ReviewerId,
250 ) -> Result<HumanClaim, String> {
251 let id = id.to_owned();
252 let reviewer = reviewer.to_owned();
253 self.connection
254 .call(move |db| -> Result<_, Error> {
255 let transaction =
256 db.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
257 let (request, _) = waiting(&transaction, &id)?;
258 claimant(&transaction, &request.id, &reviewer)?;
259 let claim = claim(&transaction, &request.id)?;
260 unclaim(&transaction, &request.id)?;
261 transaction.commit()?;
262 Ok(claim)
263 })
264 .await
265 .map_err(|e| e.to_string())
266 }
267
268 pub(crate) async fn waiting_human(
270 &self,
271 id: &crate::types::RequestId,
272 ) -> Result<Request, String> {
273 let id = id.to_owned();
274 self.connection
275 .call(move |db| -> Result<_, Error> {
276 let transaction = db.transaction()?;
277 let (request, _) = waiting(&transaction, &id)?;
278 transaction.commit()?;
279 Ok(request)
280 })
281 .await
282 .map_err(|e| e.to_string())
283 }
284
285 pub(crate) async fn human_request(
286 &self,
287 id: &crate::types::RequestId,
288 reviewer: &crate::types::ReviewerId,
289 ) -> Result<(Request, Execution), String> {
290 let id = id.to_owned();
291 let reviewer = reviewer.to_owned();
292 self.connection
293 .call(move |db| -> Result<_, Error> {
294 let transaction = db.transaction()?;
295 let (request, execution) = waiting(&transaction, &id)?;
296 claimant(&transaction, &request.id, &reviewer)?;
297 transaction.commit()?;
298 Ok((request, execution))
299 })
300 .await
301 .map_err(|e| e.to_string())
302 }
303
304 pub(crate) async fn settle_human(
305 &self,
306 request: &Request,
307 reviewer: &crate::types::ReviewerId,
308 ) -> Result<Request, String> {
309 let mut request = request.clone();
310 let reviewer = reviewer.to_owned();
311 self.connection
312 .call(move |db| -> Result<_, Error> {
313 let transaction =
314 db.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
315 let (current, mut execution) = waiting(&transaction, &request.id)?;
316 claimant(&transaction, ¤t.id, &reviewer)?;
317 request.state = match request.state {
318 super::RequestState::Completed { result, .. } => {
319 super::RequestState::completed(result, crate::broker::now())
320 }
321 super::RequestState::Failed { error, code, .. } => {
322 super::RequestState::failed(error, code, crate::broker::now())
323 }
324 _ => {
325 return Err(Error::Invalid(
326 "A Human submission needs a result or error.".into(),
327 ));
328 }
329 };
330 request.queue = None;
331 request.blocked_reason = None;
332 execution.state = request.state.clone().try_into().map_err(Error::Invalid)?;
333 execution.reviewer = Some(reviewer.clone());
334 execution.provenance.completed_at = request.completed_at();
335 request.provenance = Some(execution.provenance.clone());
336 executions::settle(&transaction, &execution, &request)?;
337 let followers = {
338 let mut statement = transaction.prepare(
339 "SELECT data FROM requests WHERE execution_id=? AND status IN ('WAITING_HUMAN','DEFERRED')",
340 )?;
341 statement
342 .query_map([&execution.id], |row| row.get::<_, String>(0))?
343 .filter_map(|row| match row {
344 Ok(data) => super::unreadable::evidence("request", &data).map(Ok),
345 Err(error) => Some(Err(Error::Sql(error))),
346 })
347 .collect::<Result<Vec<Request>, Error>>()?
348 };
349 for mut follower in followers {
350 receive(&mut follower, &execution);
351 update_request(&transaction, &follower)?;
352 }
353 unclaim(&transaction, &request.id)?;
354 transaction.commit()?;
355 Ok(request)
356 })
357 .await
358 .map_err(|e| e.to_string())
359 }
360}
361
362fn supersede_deferred(db: &rusqlite::Connection, request: &Request) -> Result<(), Error> {
364 let deferred = {
365 let mut statement = db.prepare("SELECT q.data,json_extract(e.data,'$.provenance.requestId'),e.id FROM requests q JOIN executions e ON e.id=q.execution_id JOIN runs r ON r.id=q.run_id WHERE e.status='WAITING_HUMAN' AND q.status='DEFERRED' AND q.eval_id=?1 AND r.repo=(SELECT repo FROM runs WHERE id=?2) AND json_extract(q.data,'$.key') IS NOT ?3 AND q.id<>?4")?;
366 statement
367 .query_map(
368 params![request.eval_id, request.run_id, request.key, request.id],
369 |row| {
370 Ok((
371 row.get::<_, String>(0)?,
372 row.get::<_, crate::types::RequestId>(1)?,
373 row.get::<_, crate::types::ExecutionId>(2)?,
374 ))
375 },
376 )?
377 .collect::<Result<Vec<_>, _>>()?
378 };
379 for (data, original, execution) in deferred {
380 let request: Request = serde_json::from_str(&data)?;
381 let code = crate::types::FailureCode::Superseded;
382 let error = "Superseded by a newer Human request for this eval in this working tree.";
383 if request.id == original {
384 super::run_control::retire_execution(db, &execution, code, error)?;
385 } else {
386 super::run_control::retire_request(db, request, code, error, crate::broker::now())?;
388 }
389 }
390 Ok(())
391}
392
393pub(super) fn settle_waiting(db: &rusqlite::Connection, entry: &Execution) -> Result<(), Error> {
396 let waiting: Option<String> = db
397 .query_row(
398 "SELECT data FROM executions WHERE key=? AND status='WAITING_HUMAN'",
399 [&entry.key],
400 |row| row.get(0),
401 )
402 .optional()?;
403 let Some(waiting) = waiting else {
404 return Ok(());
405 };
406 let Some(mut execution) = super::unreadable::evidence::<Execution>("execution", &waiting)
407 else {
408 return Ok(());
409 };
410 let now = crate::broker::now();
411 execution.state = super::ExecutionState::Failed {
412 error: format!(
413 "Superseded by the completed result {} for this key.",
414 entry.id
415 ),
416 code: Some(crate::types::FailureCode::Superseded),
417 at: now,
418 };
419 execution.provenance.completed_at = Some(now);
420 db.execute(
421 "UPDATE executions SET status='ERROR',data=? WHERE id=? AND status='WAITING_HUMAN'",
422 params![serde_json::to_string(&execution)?, execution.id],
423 )?;
424 let followers = {
425 let mut statement = db
426 .prepare("SELECT data FROM requests WHERE execution_id=? AND status IN ('WAITING_HUMAN','DEFERRED')")?;
427 statement
428 .query_map([&execution.id], |row| row.get::<_, String>(0))?
429 .filter_map(|row| match row {
430 Ok(data) => super::unreadable::evidence("request", &data).map(Ok),
431 Err(error) => Some(Err(Error::Sql(error))),
432 })
433 .collect::<Result<Vec<Request>, Error>>()?
434 };
435 for mut follower in followers {
436 receive(&mut follower, entry);
437 update_request(db, &follower)?;
438 unclaim(db, &follower.id)?;
439 }
440 Ok(())
441}
442
443fn receive(request: &mut Request, execution: &Execution) {
444 crate::cache::reuse(request, execution, crate::broker::now());
445}