KeiSeiKit-1.0/_primitives/_rust/kei-scheduler/src/run.rs
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

86 lines
2.8 KiB
Rust

//! `mark_run` — record completion of a triggered execution.
//!
//! Caller supplies `now` explicitly so tests are deterministic. The new
//! `next_run_at` is re-computed from `now` using the task's stored
//! trigger_kind / trigger_spec:
//! - `interval` → `now + secs` (never terminal).
//! - `cron` → next cron occurrence after `now` (falls back to
//! terminal `done` if no future occurrence exists).
//! - `at` → one-shot; status → `done`, next_run_at → NULL.
//!
//! Status transitions: cancelled rows are immutable (function returns
//! `Error::NotFound` to keep the surface minimal — the caller should
//! not be marking runs on cancelled tasks).
use crate::error::Error;
use crate::query::get_task;
use crate::task::status;
use crate::trigger::{compute_next, AT};
use rusqlite::{params, Connection};
/// Record a run outcome and advance the schedule.
///
/// Returns `Ok(())` on success, `Error::NotFound` if `id` doesn't
/// exist or refers to a cancelled task, `Error::Parse` if the stored
/// trigger spec is no longer parseable (should not happen if the row
/// was created via `schedule`).
pub fn mark_run(
conn: &Connection,
id: i64,
exit_code: i64,
now: i64,
) -> Result<(), Error> {
let task = match get_task(conn, id)? {
Some(t) if t.status == status::CANCELLED => return Err(Error::NotFound(id)),
Some(t) => t,
None => return Err(Error::NotFound(id)),
};
let (next, next_status) = advance(&task.trigger_kind, &task.trigger_spec, exit_code, now)?;
write_run(conn, id, exit_code, now, next, next_status)
}
/// Compute the next `(next_run_at, status)` pair given the trigger +
/// run outcome. Exit-code 0 on `at` → `done`; non-zero → `failed`.
/// `cron`/`interval` ignore exit code when scheduling next fire.
fn advance(
kind: &str,
spec: &str,
exit_code: i64,
now: i64,
) -> Result<(Option<i64>, &'static str), Error> {
if kind == AT {
let s = if exit_code == 0 { status::DONE } else { status::FAILED };
return Ok((None, s));
}
let next = compute_next(kind, spec, now)?;
let status_next = match next {
Some(_) => status::SCHEDULED,
// Cron schedule with no future occurrence — treat as terminal.
None => status::DONE,
};
Ok((next, status_next))
}
fn write_run(
conn: &Connection,
id: i64,
exit_code: i64,
now: i64,
next_run_at: Option<i64>,
next_status: &str,
) -> Result<(), Error> {
let rows = conn.execute(
"UPDATE scheduler_tasks SET \
last_run_at = ?1, \
last_exit_code = ?2, \
next_run_at = ?3, \
status = ?4, \
updated_at = ?5 \
WHERE id = ?6",
params![now, exit_code, next_run_at, next_status, now, id],
)?;
if rows == 0 {
return Err(Error::NotFound(id));
}
Ok(())
}