fix(#1953): route approval helper-events to the submitter, not the root agent
This commit is contained in:
parent
14539de7d0
commit
3618399d94
7 changed files with 187 additions and 73 deletions
|
|
@ -70,6 +70,21 @@ fn ensure_fetched_sha_column(conn: &Connection) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Same shape as `ensure_fetched_sha_column` but for `submitter` — the
|
||||
/// agent that submitted the approval (the authenticated socket caller).
|
||||
/// Approval-scoped helper events route to this agent. Legacy rows have
|
||||
/// NULL; callers fall back to the root agent for those.
|
||||
fn ensure_submitter_column(conn: &Connection) -> Result<()> {
|
||||
let has: bool = conn
|
||||
.prepare("SELECT 1 FROM pragma_table_info('approvals') WHERE name = 'submitter'")?
|
||||
.exists([])?;
|
||||
if !has {
|
||||
conn.execute_batch("ALTER TABLE approvals ADD COLUMN submitter TEXT;")
|
||||
.context("add approvals.submitter column")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct Approvals {
|
||||
conn: Mutex<Connection>,
|
||||
}
|
||||
|
|
@ -87,6 +102,7 @@ impl Approvals {
|
|||
ensure_kind_column(&conn).context("migrate approvals.kind")?;
|
||||
ensure_fetched_sha_column(&conn).context("migrate approvals.fetched_sha")?;
|
||||
ensure_description_column(&conn).context("migrate approvals.description")?;
|
||||
ensure_submitter_column(&conn).context("migrate approvals.submitter")?;
|
||||
Ok(Self {
|
||||
conn: Mutex::new(conn),
|
||||
})
|
||||
|
|
@ -98,22 +114,41 @@ impl Approvals {
|
|||
kind: ApprovalKind,
|
||||
commit_ref: &str,
|
||||
description: Option<&str>,
|
||||
submitter: &str,
|
||||
) -> Result<i64> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO approvals (agent, kind, commit_ref, requested_at, status, description)
|
||||
VALUES (?1, ?2, ?3, ?4, 'pending', ?5)",
|
||||
"INSERT INTO approvals
|
||||
(agent, kind, commit_ref, requested_at, status, description, submitter)
|
||||
VALUES (?1, ?2, ?3, ?4, 'pending', ?5, ?6)",
|
||||
params![
|
||||
agent,
|
||||
kind_to_str(kind),
|
||||
commit_ref,
|
||||
now_unix(),
|
||||
description
|
||||
description,
|
||||
submitter
|
||||
],
|
||||
)?;
|
||||
Ok(conn.last_insert_rowid())
|
||||
}
|
||||
|
||||
/// The agent that submitted approval `id` (the authenticated socket
|
||||
/// caller at submit time). `None` for legacy rows predating the
|
||||
/// `submitter` column — callers route those to the root agent.
|
||||
pub fn submitter_of(&self, id: i64) -> Result<Option<String>> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let submitter: Option<String> = conn
|
||||
.query_row(
|
||||
"SELECT submitter FROM approvals WHERE id = ?1",
|
||||
params![id],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.optional()?
|
||||
.flatten();
|
||||
Ok(submitter)
|
||||
}
|
||||
|
||||
/// Record the canonical sha hive-c0re fetched from the proposed repo
|
||||
/// into applied at submission time. Idempotent on identical values.
|
||||
pub fn set_fetched_sha(&self, id: i64, sha: &str) -> Result<()> {
|
||||
|
|
@ -428,7 +463,13 @@ mod tests {
|
|||
// approval then vanished from the dashboard.
|
||||
let (_dir, _path, db) = open_temp();
|
||||
let id = db
|
||||
.submit_kind("bitburner", ApprovalKind::InitConfig, "", Some("scaffold"))
|
||||
.submit_kind(
|
||||
"bitburner",
|
||||
ApprovalKind::InitConfig,
|
||||
"",
|
||||
Some("scaffold"),
|
||||
"bitburner",
|
||||
)
|
||||
.expect("submit init_config");
|
||||
let pending = db
|
||||
.pending()
|
||||
|
|
@ -441,10 +482,11 @@ mod tests {
|
|||
#[test]
|
||||
fn mixed_kinds_all_listed() {
|
||||
let (_dir, _path, db) = open_temp();
|
||||
db.submit_kind("a", ApprovalKind::ApplyCommit, "deadbeef", None)
|
||||
db.submit_kind("a", ApprovalKind::ApplyCommit, "deadbeef", None, "a")
|
||||
.unwrap();
|
||||
db.submit_kind("b", ApprovalKind::Spawn, "", None).unwrap();
|
||||
db.submit_kind("c", ApprovalKind::InitConfig, "", None)
|
||||
db.submit_kind("b", ApprovalKind::Spawn, "", None, "b")
|
||||
.unwrap();
|
||||
db.submit_kind("c", ApprovalKind::InitConfig, "", None, "c")
|
||||
.unwrap();
|
||||
let pending = db.pending().expect("pending");
|
||||
assert_eq!(pending.len(), 3, "all three kinds must be visible");
|
||||
|
|
@ -462,6 +504,7 @@ mod tests {
|
|||
ApprovalKind::ApplyCommit,
|
||||
"cafef00d",
|
||||
Some("test"),
|
||||
"bitburner",
|
||||
)
|
||||
.unwrap();
|
||||
let row = db.mark_cancelled(id, "manager").expect("cancel");
|
||||
|
|
@ -481,7 +524,7 @@ mod tests {
|
|||
// final — re-cancelling errors instead of silently overwriting.
|
||||
let (_dir, _path, db) = open_temp();
|
||||
let id = db
|
||||
.submit_kind("a", ApprovalKind::Spawn, "deadbeef", None)
|
||||
.submit_kind("a", ApprovalKind::Spawn, "deadbeef", None, "a")
|
||||
.unwrap();
|
||||
db.mark_cancelled(id, "manager").expect("first cancel");
|
||||
let err = db
|
||||
|
|
@ -496,7 +539,7 @@ mod tests {
|
|||
// whole list — collect_lenient skips it instead of failing.
|
||||
let (_dir, path, db) = open_temp();
|
||||
let good = db
|
||||
.submit_kind("good", ApprovalKind::ApplyCommit, "cafe", None)
|
||||
.submit_kind("good", ApprovalKind::ApplyCommit, "cafe", None, "good")
|
||||
.unwrap();
|
||||
let raw = Connection::open(&path).unwrap();
|
||||
raw.execute(
|
||||
|
|
@ -511,4 +554,26 @@ mod tests {
|
|||
assert_eq!(pending.len(), 1);
|
||||
assert_eq!(pending[0].id, good);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submitter_round_trips_and_legacy_reads_none() {
|
||||
// A submitted approval records its submitter; a legacy row
|
||||
// (inserted without the column) reads back as None so callers
|
||||
// fall back to the root agent.
|
||||
let (_dir, path, db) = open_temp();
|
||||
let id = db
|
||||
.submit_kind("child", ApprovalKind::ApplyCommit, "cafe", None, "parent")
|
||||
.unwrap();
|
||||
assert_eq!(db.submitter_of(id).unwrap().as_deref(), Some("parent"));
|
||||
|
||||
let raw = Connection::open(&path).unwrap();
|
||||
raw.execute(
|
||||
"INSERT INTO approvals (agent, kind, commit_ref, requested_at, status)
|
||||
VALUES ('old', 'apply_commit', '', 0, 'pending')",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
let legacy_id = raw.last_insert_rowid();
|
||||
assert_eq!(db.submitter_of(legacy_id).unwrap(), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue