Hand over the M3a plan: 22 tasks, their files, and the check record
Task files, the files they copy in (byte-identical to the reference on m3a-ref), each area's check record, and a README with the per-task table of what each check exposed. The handoff note is done with. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
//! Scripted stand-ins for `brokerd`'s admin socket and for `loopd`, for tests. Do not edit.
|
||||
|
||||
#![allow(dead_code)] // each test file uses a different part of this module
|
||||
|
||||
use proto::{
|
||||
ApprovalList, CallId, DataClass, Envelope, ErrorCode, Message, PROTOCOL_VERSION,
|
||||
PendingApproval, SessionId, Timestamp, TurnDone, TurnEvent, Usage, WireError, read_frame,
|
||||
write_frame,
|
||||
};
|
||||
use std::os::unix::net::UnixListener;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
static NEXT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
/// A socket path in a fresh temporary directory.
|
||||
pub fn temp_socket(name: &str) -> PathBuf {
|
||||
let n = NEXT.fetch_add(1, Ordering::SeqCst);
|
||||
let dir = std::env::temp_dir().join(format!("bxctl-m3a-{}-{n}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
dir.join(name)
|
||||
}
|
||||
|
||||
pub fn ts(text: &str) -> Timestamp {
|
||||
Timestamp::parse(text).unwrap()
|
||||
}
|
||||
|
||||
pub struct FakeBrokerd {
|
||||
pub socket: PathBuf,
|
||||
/// Every request message received, in order.
|
||||
pub requests: Arc<Mutex<Vec<Message>>>,
|
||||
}
|
||||
|
||||
impl FakeBrokerd {
|
||||
pub fn requests(&self) -> Vec<Message> {
|
||||
self.requests.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Serves `admin.sock`: one request per connection, answered with the frames `answer` returns,
|
||||
/// written exactly as given (so a test can send a wrong id or a frame that is not final). An
|
||||
/// empty list closes the connection without an answer.
|
||||
pub fn fake_brokerd_frames(
|
||||
answer: impl Fn(&Envelope) -> Vec<Envelope> + Send + 'static,
|
||||
) -> FakeBrokerd {
|
||||
let socket = temp_socket("admin.sock");
|
||||
let listener = UnixListener::bind(&socket).unwrap();
|
||||
let requests = Arc::new(Mutex::new(Vec::new()));
|
||||
let seen = Arc::clone(&requests);
|
||||
thread::spawn(move || {
|
||||
for stream in listener.incoming() {
|
||||
let Ok(mut stream) = stream else { continue };
|
||||
let Ok(request) = read_frame(&mut stream) else {
|
||||
continue;
|
||||
};
|
||||
seen.lock().unwrap().push(request.msg.clone());
|
||||
for frame in answer(&request) {
|
||||
if write_frame(&mut stream, &frame).is_err() {
|
||||
break; // the client went away; the next connection is still served
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
FakeBrokerd { socket, requests }
|
||||
}
|
||||
|
||||
/// The usual case: one final frame with the request's id.
|
||||
pub fn fake_brokerd(answer: impl Fn(&Message) -> Message + Send + 'static) -> FakeBrokerd {
|
||||
fake_brokerd_frames(move |request| {
|
||||
vec![Envelope {
|
||||
v: PROTOCOL_VERSION,
|
||||
id: request.id,
|
||||
r#final: true,
|
||||
msg: answer(&request.msg),
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
pub fn wire_error(code: ErrorCode, detail: &str) -> Message {
|
||||
Message::Error(WireError {
|
||||
code,
|
||||
detail: detail.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// A pending approval created at 12:00:00 that expires at 12:15:00 on 2026-09-18.
|
||||
pub fn pending(approval: u64, tool: &str, arguments: &str) -> PendingApproval {
|
||||
PendingApproval {
|
||||
approval,
|
||||
session: SessionId::new("chat-1758196800-123456789").unwrap(),
|
||||
call: CallId(7),
|
||||
tool: tool.to_string(),
|
||||
arguments: arguments.to_string(),
|
||||
grant: "shell-scratch".to_string(),
|
||||
taint: DataClass::Private,
|
||||
created: ts("2026-09-18T12:00:00.000Z"),
|
||||
expires: ts("2026-09-18T12:15:00.000Z"),
|
||||
}
|
||||
}
|
||||
|
||||
/// A `brokerd` with these approvals pending. `approve` is answered with `outcome`, `refuse` with
|
||||
/// `ok`; both answer `no_such_approval` for an id that is not in the list.
|
||||
pub fn brokerd_with(items: Vec<PendingApproval>, outcome: proto::DecisionRecord) -> FakeBrokerd {
|
||||
fake_brokerd(move |msg| {
|
||||
let known = |id: u64| items.iter().any(|item| item.approval == id);
|
||||
match msg {
|
||||
Message::Approvals(_) => Message::ApprovalList(ApprovalList {
|
||||
items: items.clone(),
|
||||
}),
|
||||
Message::Approve(a) if known(a.approval) => {
|
||||
Message::ApproveResult(proto::ApproveResult {
|
||||
outcome: outcome.clone(),
|
||||
})
|
||||
}
|
||||
Message::Refuse(r) if known(r.approval) => Message::Ok(proto::Empty {}),
|
||||
Message::Approve(_) | Message::Refuse(_) => {
|
||||
wire_error(ErrorCode::NoSuchApproval, "no such approval")
|
||||
}
|
||||
_ => wire_error(ErrorCode::BadMessage, "not an admin request"),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub struct FakeLoopd {
|
||||
pub socket: PathBuf,
|
||||
/// The content of every turn received, in order.
|
||||
pub turns: Arc<Mutex<Vec<String>>>,
|
||||
}
|
||||
|
||||
pub fn usage() -> Usage {
|
||||
Usage {
|
||||
cache_n: 10,
|
||||
prompt_n: 5,
|
||||
predicted_n: 7,
|
||||
reasoning_tokens: 3,
|
||||
thinking_capped: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Serves `loop.sock`: every turn gets the same events, then `turn_done` with `answer`. It does
|
||||
/// not wait for an approval to be answered: `bxctl` deals with each event before it reads the
|
||||
/// next frame, so the order of what it does is fixed all the same.
|
||||
pub fn fake_loopd(events: Vec<TurnEvent>, answer: &str) -> FakeLoopd {
|
||||
let socket = temp_socket("loop.sock");
|
||||
let listener = UnixListener::bind(&socket).unwrap();
|
||||
let turns = Arc::new(Mutex::new(Vec::new()));
|
||||
let seen = Arc::clone(&turns);
|
||||
let answer = answer.to_string();
|
||||
thread::spawn(move || {
|
||||
for stream in listener.incoming() {
|
||||
let Ok(mut stream) = stream else { continue };
|
||||
let Ok(request) = read_frame(&mut stream) else {
|
||||
continue;
|
||||
};
|
||||
let Message::Turn(turn) = request.msg else {
|
||||
continue;
|
||||
};
|
||||
seen.lock().unwrap().push(turn.content);
|
||||
let mut frames: Vec<(bool, Message)> = events
|
||||
.iter()
|
||||
.map(|e| (false, Message::TurnEvent(e.clone())))
|
||||
.collect();
|
||||
frames.push((
|
||||
true,
|
||||
Message::TurnDone(TurnDone {
|
||||
content: answer.clone(),
|
||||
usage: usage(),
|
||||
}),
|
||||
));
|
||||
for (last, msg) in frames {
|
||||
let frame = Envelope {
|
||||
v: PROTOCOL_VERSION,
|
||||
id: request.id,
|
||||
r#final: last,
|
||||
msg,
|
||||
};
|
||||
if write_frame(&mut stream, &frame).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
FakeLoopd { socket, turns }
|
||||
}
|
||||
Reference in New Issue
Block a user