mirror of
https://github.com/andrewyng/openworker.git
synced 2026-11-05 05:47:02 +00:00
431 lines
16 KiB
Rust
431 lines
16 KiB
Rust
use openworker_runtime::{
|
|
control::{Action, Binding, Command, Status},
|
|
store::{Session, StateStore},
|
|
};
|
|
use openworker_store_postgres::{Config, PostgresStore, Tls, TurnSubmission};
|
|
use serde_json::{json, Value};
|
|
use std::sync::{Arc, Barrier};
|
|
|
|
fn fixture() -> (Config, PostgresStore, Command) {
|
|
let mut c = Config::new(
|
|
std::env::var("OW_PG_TEST_URL").expect("disposable DB required"),
|
|
format!("ow_ctl_{}", uuid::Uuid::new_v4().simple()),
|
|
);
|
|
c.tls = Tls::LoopbackPlaintext;
|
|
PostgresStore::migrate(&c).unwrap();
|
|
let mut owner = PostgresStore::open(c.clone()).unwrap();
|
|
let mut s:Session=serde_json::from_value(json!({"id":"s_ctl","name":"Control","workspace":"/work","coworker":"test","bundle_digest":"bundle","schema_digest":"schema","interactions":[],"turn":null,"sandbox":null,"closed":false,"resource_policy":openworker_runtime::resource_policy::Resolved::default()})).unwrap();
|
|
s.tool_policy.digest = "policy".into();
|
|
s.resource_policy.digest = "resource".into();
|
|
owner.save(&s, "session_created", json!({})).unwrap();
|
|
let (mut s, _, _) = owner.accept("s_ctl", "turn", "Task").unwrap();
|
|
let tid = s.turn.as_ref().unwrap().id.clone();
|
|
let call =
|
|
json!({"type":"function_call","call_id":"call_one","name":"test__probe","arguments":"{}"});
|
|
s.history.push(call.clone());
|
|
s.turn.as_mut().unwrap().queue = vec![call.clone()];
|
|
s.turn.as_mut().unwrap().status = "waiting".into();
|
|
s.interactions.push(serde_json::from_value(json!({"id":"i_one","turn_id":tid,"kind":"approval","call":call,"bundle_digest":"bundle","schema_digest":"schema","response":null,"resource_policy_digest":"resource","policy_digest":"policy"})).unwrap());
|
|
owner
|
|
.save(
|
|
&s,
|
|
"interaction_required",
|
|
json!({"interaction_id":"i_one"}),
|
|
)
|
|
.unwrap();
|
|
let command = Command {
|
|
session_id: "s_ctl".into(),
|
|
request_id: "control-one".into(),
|
|
turn_id: tid,
|
|
actor: "principal-1".into(),
|
|
action: Action::Response {
|
|
interaction_id: "i_one".into(),
|
|
response: "once".into(),
|
|
binding: Binding {
|
|
bundle_digest: "bundle".into(),
|
|
schema_digest: "schema".into(),
|
|
policy_digest: "policy".into(),
|
|
resource_policy_digest: "resource".into(),
|
|
},
|
|
},
|
|
};
|
|
(c, owner, command)
|
|
}
|
|
fn admin(c: &Config) -> postgres::Client {
|
|
let mut db = postgres::Client::connect(&c.connection, postgres::NoTls).unwrap();
|
|
db.batch_execute(&format!("SET search_path={},pg_catalog", c.schema))
|
|
.unwrap();
|
|
db
|
|
}
|
|
fn applied(owner: &mut PostgresStore, c: &Command) -> anyhow::Result<()> {
|
|
let mut s = owner.get(&c.session_id)?;
|
|
if let Action::Response { response, .. } = &c.action {
|
|
s.interactions[0].response = Some(response.clone());
|
|
s.interactions[0].origin = Some("human".into());
|
|
s.turn.as_mut().unwrap().status = "accepted".into();
|
|
owner.save(
|
|
&s,
|
|
"interaction_resolved",
|
|
json!({"interaction_id":"i_one","control_request_id":c.request_id,"actor":c.actor}),
|
|
)?;
|
|
} else {
|
|
s.turn.as_mut().unwrap().status = "cancelling".into();
|
|
owner.save(
|
|
&s,
|
|
"turn_cancelling",
|
|
json!({"control_request_id":c.request_id,"actor":c.actor}),
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn closed_session_rejects_new_cancel_but_preserves_prior_receipts() {
|
|
let (config, mut owner, command) = fixture();
|
|
let api = PostgresStore::open(config).unwrap();
|
|
api.submit_control(command.clone()).unwrap();
|
|
applied(&mut owner, &command).unwrap();
|
|
let mut session = owner.get("s_ctl").unwrap();
|
|
session.turn.as_mut().unwrap().status = "completed".into();
|
|
session.turn.as_mut().unwrap().queue.clear();
|
|
session.closed = true;
|
|
owner.save(&session, "session_closed", json!({})).unwrap();
|
|
assert_eq!(
|
|
api.submit_control(command.clone()).unwrap().status,
|
|
Status::Applied
|
|
);
|
|
let cancel = Command {
|
|
request_id: "closed-cancel".into(),
|
|
action: Action::Cancel,
|
|
..command
|
|
};
|
|
assert_eq!(
|
|
api.submit_control(cancel)
|
|
.unwrap_err()
|
|
.downcast_ref::<openworker_store_postgres::SubmissionError>(),
|
|
Some(&openworker_store_postgres::SubmissionError::Closed)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn late_control_defers_turn_admission_without_consuming_or_replacing_it() {
|
|
use openworker_store_postgres::{SubmissionError, SubmissionStatus};
|
|
let (c, mut owner, command) = fixture();
|
|
let api = PostgresStore::open(c).unwrap();
|
|
let mut session = owner.get("s_ctl").unwrap();
|
|
session.turn.as_mut().unwrap().status = "completed".into();
|
|
session.turn.as_mut().unwrap().queue.clear();
|
|
owner
|
|
.save(&session, "turn_completed", json!({"status":"completed"}))
|
|
.unwrap();
|
|
let receipt = api
|
|
.submit_turn(TurnSubmission {
|
|
session_id: "s_ctl".into(),
|
|
submission_id: "next".into(),
|
|
text: "Next".into(),
|
|
})
|
|
.unwrap();
|
|
let peek = owner.next_turn("s_ctl").unwrap().unwrap();
|
|
// Deterministic interleaving: the worker has peeked, but not admitted work.
|
|
let cancel = Command {
|
|
request_id: "late-cancel".into(),
|
|
action: Action::Cancel,
|
|
..command
|
|
};
|
|
api.submit_control(cancel.clone()).unwrap();
|
|
let error = owner
|
|
.accept(&peek.session_id, &peek.submission_id, &peek.text)
|
|
.unwrap_err();
|
|
assert_eq!(
|
|
error.downcast_ref::<SubmissionError>(),
|
|
Some(&SubmissionError::ControlsPending)
|
|
);
|
|
assert_eq!(
|
|
api.submission("s_ctl", "next").unwrap().status,
|
|
SubmissionStatus::Queued
|
|
);
|
|
owner
|
|
.settle_control("s_ctl", &cancel.request_id, false, "already_terminal")
|
|
.unwrap();
|
|
let (started, fresh, _) = owner
|
|
.accept(&peek.session_id, &peek.submission_id, &peek.text)
|
|
.unwrap();
|
|
assert!(fresh);
|
|
assert_eq!(started.turn.unwrap().id, receipt.turn_id);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn control_domain_errors_are_typed_and_do_not_poison_the_connection() {
|
|
use openworker_store_postgres::SubmissionError as E;
|
|
let (c, _owner, command) = fixture();
|
|
let api = PostgresStore::open(c).unwrap();
|
|
let mut bad = command.clone();
|
|
bad.request_id.clear();
|
|
assert_eq!(
|
|
api.submit_control(bad).unwrap_err().downcast_ref::<E>(),
|
|
Some(&E::InvalidRequest)
|
|
);
|
|
assert_eq!(
|
|
api.control_receipt("s_ctl", "missing")
|
|
.unwrap_err()
|
|
.downcast_ref::<E>(),
|
|
Some(&E::NotFound)
|
|
);
|
|
api.submit_control(command.clone()).unwrap();
|
|
let mut bad = command.clone();
|
|
bad.actor = "different".into();
|
|
assert_eq!(
|
|
api.submit_control(bad).unwrap_err().downcast_ref::<E>(),
|
|
Some(&E::Conflict)
|
|
);
|
|
let mut bad = command.clone();
|
|
bad.request_id = "another-id".into();
|
|
assert_eq!(
|
|
api.submit_control(bad).unwrap_err().downcast_ref::<E>(),
|
|
Some(&E::ControlConflict)
|
|
);
|
|
assert_eq!(api.submit_control(command).unwrap().status, Status::Pending);
|
|
api.ready().unwrap();
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn closing_cannot_strand_a_control_admitted_before_the_close() {
|
|
use openworker_store_postgres::SubmissionError;
|
|
let (c, mut owner, command) = fixture();
|
|
let api = PostgresStore::open(c).unwrap();
|
|
let mut s = owner.get("s_ctl").unwrap();
|
|
s.turn.as_mut().unwrap().status = "completed".into();
|
|
s.turn.as_mut().unwrap().queue.clear();
|
|
owner
|
|
.save(&s, "turn_completed", json!({"status":"completed"}))
|
|
.unwrap();
|
|
let cancel = Command {
|
|
request_id: "before-close".into(),
|
|
action: Action::Cancel,
|
|
..command
|
|
};
|
|
api.submit_control(cancel.clone()).unwrap();
|
|
let mut s = owner.get("s_ctl").unwrap();
|
|
s.closed = true;
|
|
assert_eq!(
|
|
owner
|
|
.save(&s, "session_closed", json!({}))
|
|
.unwrap_err()
|
|
.downcast_ref::<SubmissionError>(),
|
|
Some(&SubmissionError::ControlsPending)
|
|
);
|
|
assert!(!owner.get("s_ctl").unwrap().closed);
|
|
owner
|
|
.settle_control("s_ctl", &cancel.request_id, false, "already_terminal")
|
|
.unwrap();
|
|
let mut s = owner.get("s_ctl").unwrap();
|
|
s.closed = true;
|
|
owner.save(&s, "session_closed", json!({})).unwrap();
|
|
assert_eq!(api.submit_control(cancel).unwrap().status, Status::Applied);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn concurrent_decisions_are_durable_idempotent_and_attributed() {
|
|
let (c, mut owner, command) = fixture();
|
|
let a = PostgresStore::open(c.clone()).unwrap();
|
|
let b = PostgresStore::open(c.clone()).unwrap();
|
|
let gate = Arc::new(Barrier::new(3));
|
|
let ga = gate.clone();
|
|
let gb = gate.clone();
|
|
let ca = command.clone();
|
|
let cb = command.clone();
|
|
let first = std::thread::spawn(move || {
|
|
ga.wait();
|
|
a.submit_control(ca).unwrap()
|
|
});
|
|
let second = std::thread::spawn(move || {
|
|
gb.wait();
|
|
b.submit_control(cb).unwrap()
|
|
});
|
|
gate.wait();
|
|
assert_eq!(first.join().unwrap(), second.join().unwrap());
|
|
let api = PostgresStore::open(c).unwrap();
|
|
assert!(owner.get("s_ctl").unwrap().interactions[0]
|
|
.response
|
|
.is_none());
|
|
let mut conflict = command.clone();
|
|
conflict.actor = "other".into();
|
|
assert!(api.submit_control(conflict).is_err());
|
|
let mut conflict = command.clone();
|
|
if let Action::Response { response, .. } = &mut conflict.action {
|
|
*response = "deny".into();
|
|
}
|
|
assert!(api.submit_control(conflict).is_err());
|
|
assert!(api.next_control("s_ctl").is_err());
|
|
applied(&mut owner, &command).unwrap();
|
|
assert_eq!(
|
|
api.control_receipt("s_ctl", &command.request_id)
|
|
.unwrap()
|
|
.status,
|
|
Status::Applied
|
|
);
|
|
assert_eq!(api.submit_control(command).unwrap().status, Status::Applied);
|
|
assert!(owner.next_control("s_ctl").unwrap().is_none());
|
|
let events = owner.audit("s_ctl", 0).unwrap();
|
|
assert_eq!(
|
|
events
|
|
.iter()
|
|
.filter(|e| e["actor"] == "principal-1")
|
|
.count(),
|
|
2
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn cancellation_overtakes_response_and_fences_new_tool_intents() {
|
|
let (c, mut owner, response) = fixture();
|
|
let api = PostgresStore::open(c).unwrap();
|
|
api.submit_turn(TurnSubmission {
|
|
session_id: "s_ctl".into(),
|
|
submission_id: "next".into(),
|
|
text: "Next".into(),
|
|
})
|
|
.unwrap();
|
|
api.submit_control(response.clone()).unwrap();
|
|
let cancel = Command {
|
|
request_id: "cancel-one".into(),
|
|
action: Action::Cancel,
|
|
..response.clone()
|
|
};
|
|
api.submit_control(cancel.clone()).unwrap();
|
|
assert_eq!(
|
|
owner
|
|
.next_control("s_ctl")
|
|
.unwrap()
|
|
.unwrap()
|
|
.command
|
|
.request_id,
|
|
"cancel-one"
|
|
);
|
|
assert!(owner.next_turn("s_ctl").unwrap().is_none());
|
|
assert!(owner
|
|
.append_event("s_ctl", "tool_started", json!({"call_id":"call_one"}))
|
|
.is_err());
|
|
assert!(applied(&mut owner, &response).is_err());
|
|
assert!(owner.get("s_ctl").unwrap().interactions[0]
|
|
.response
|
|
.is_none());
|
|
applied(&mut owner, &cancel).unwrap();
|
|
assert_eq!(
|
|
api.control_receipt("s_ctl", "cancel-one").unwrap().status,
|
|
Status::Applied
|
|
);
|
|
assert!(owner.uncertain_calls("s_ctl").unwrap().is_empty());
|
|
owner
|
|
.settle_control("s_ctl", "control-one", true, "stale_identity_or_target")
|
|
.unwrap();
|
|
assert_eq!(
|
|
api.control_receipt("s_ctl", "control-one").unwrap().status,
|
|
Status::Rejected
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn saved_identity_questions_and_stale_turns_are_validated() {
|
|
let (c, mut owner, command) = fixture();
|
|
let api = PostgresStore::open(c.clone()).unwrap();
|
|
let mut bad = command.clone();
|
|
bad.turn_id = "not_current".into();
|
|
assert!(api.submit_control(bad).is_err());
|
|
let mut bad = command.clone();
|
|
if let Action::Response { binding, .. } = &mut bad.action {
|
|
binding.resource_policy_digest = "changed".into();
|
|
}
|
|
assert!(api.submit_control(bad).is_err());
|
|
let mut bad = command.clone();
|
|
if let Action::Response { response, .. } = &mut bad.action {
|
|
*response = "always_forever".into();
|
|
}
|
|
assert!(api.submit_control(bad).is_err());
|
|
let mut s = owner.get("s_ctl").unwrap();
|
|
s.interactions[0].kind = "question".into();
|
|
s.interactions[0].call["arguments"] = json!(json!({"options":["Yes","No"]}).to_string());
|
|
owner.save(&s, "question", json!({})).unwrap();
|
|
assert!(api.submit_control(command.clone()).is_err());
|
|
let mut question = command.clone();
|
|
if let Action::Response { response, .. } = &mut question.action {
|
|
*response = "Yes".into();
|
|
}
|
|
api.submit_control(question.clone()).unwrap();
|
|
applied(&mut owner, &question).unwrap();
|
|
assert_eq!(
|
|
owner.get("s_ctl").unwrap().interactions[0]
|
|
.response
|
|
.as_deref(),
|
|
Some("Yes")
|
|
);
|
|
let mut bad = question;
|
|
bad.request_id = "another".into();
|
|
assert!(api.submit_control(bad).is_err());
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn receipt_application_and_audit_roll_back_together() {
|
|
let (c, mut owner, command) = fixture();
|
|
let api = PostgresStore::open(c.clone()).unwrap();
|
|
admin(&c).batch_execute("CREATE FUNCTION fail_control() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'SECRET_CONTROL_SQL'; END $$; CREATE TRIGGER fail_control BEFORE INSERT ON audit FOR EACH ROW WHEN (NEW.kind='control_received') EXECUTE FUNCTION fail_control()").unwrap();
|
|
assert!(!api
|
|
.submit_control(command.clone())
|
|
.unwrap_err()
|
|
.to_string()
|
|
.contains("SECRET_CONTROL_SQL"));
|
|
admin(&c)
|
|
.batch_execute("DROP TRIGGER fail_control ON audit")
|
|
.unwrap();
|
|
let api = PostgresStore::open(c.clone()).unwrap();
|
|
assert!(api.control_receipt("s_ctl", "control-one").is_err());
|
|
api.submit_control(command.clone()).unwrap();
|
|
admin(&c).batch_execute("CREATE TRIGGER fail_control BEFORE INSERT ON audit FOR EACH ROW WHEN (NEW.kind='interaction_resolved') EXECUTE FUNCTION fail_control()").unwrap();
|
|
assert!(applied(&mut owner, &command).is_err());
|
|
assert_eq!(
|
|
api.control_receipt("s_ctl", "control-one").unwrap().status,
|
|
Status::Pending
|
|
);
|
|
assert!(api.inspect("s_ctl").unwrap().interactions[0]
|
|
.response
|
|
.is_none());
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn takeover_preserves_pending_decision_and_rejects_old_owner() {
|
|
let (c, mut old, command) = fixture();
|
|
let api = PostgresStore::open(c.clone()).unwrap();
|
|
api.submit_control(command.clone()).unwrap();
|
|
admin(&c)
|
|
.execute(
|
|
"UPDATE sessions SET expires=clock_timestamp()-interval '1 second'",
|
|
&[],
|
|
)
|
|
.unwrap();
|
|
let mut new = PostgresStore::open(c.clone()).unwrap();
|
|
new.claim("s_ctl").unwrap();
|
|
new.recover().unwrap();
|
|
assert_eq!(new.next_control("s_ctl").unwrap().unwrap().command, command);
|
|
assert!(applied(&mut old, &command).is_err());
|
|
applied(&mut new, &command).unwrap();
|
|
assert_eq!(
|
|
api.control_receipt("s_ctl", "control-one").unwrap().status,
|
|
Status::Applied
|
|
);
|
|
admin(&c).execute("DELETE FROM controls", &[]).unwrap();
|
|
assert!(api.control_receipt("s_ctl", "control-one").is_err());
|
|
assert!(api.submit_control(command).is_err());
|
|
assert!(new
|
|
.append_event("s_ctl", "tool_started", Value::Null)
|
|
.is_err());
|
|
}
|