Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 93 additions & 1 deletion src/server/handler_disconnect.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -740,13 +740,14 @@ fn ServerState::leaf_merge_gate_ready_for_agent(
self.config.pr_policy.reviewer,
self.config.pr_policy.skip_copilot_on_feature_branch,
tracked.parent_branch,
"",
self.cached_default_branch,
)
if @poller.tl_tracked_pr_merge_gate_snapshot_ready(
tracked,
unresolved_threads,
review_mode=tracked.review_mode,
review_required~,
audit_receipt_state=tracked.audit_receipt_state,
) {
return true
}
Expand All @@ -755,6 +756,97 @@ fn ServerState::leaf_merge_gate_ready_for_agent(
false
}

///|
test "leaf merge gate threads audit state and cached default branch" {
let make_state = fn(
label : String,
audit_receipt_state : @types.AuditReceiptState,
review : @poller.ReviewState,
) -> ServerState {
let project_dir = "/tmp/choir-leaf-merge-gate-" +
label +
"-" +
@sys.get_pid().to_string() +
"-" +
@env.now().reinterpret_as_int64().to_string()
ignore(@sys.create_dir_all(project_dir + "/.choir/state/kv"))
let state = ServerState::new(
{
..@types.Config::default(),
project_dir,
pr_policy: {
..@types.PrPolicy::default(),
reviewer: @types.Reviewer::Copilot,
},
},
make_hermetic_recovery_provider(),
)
let agent_id = "main.leaf-merge-gate"
state.poller.track(
901, agent_id, "feature/leaf", "feature/base", "https://example.test/pr/901",
0L,
)
match state.poller.get(901) {
Some(tracked) =>
state.poller.restore({
..tracked,
last_review_state: review,
last_ci_rollup: @poller.CiRollupStatus::Success,
audit_receipt_state,
unresolved_thread_ids: [],
unresolved_thread_ids_fetched: true,
})
None => abort("expected tracked leaf PR")
}
state
}

let pending = make_state(
"pending",
@types.AuditReceiptState::Pending(head_sha="head"),
@poller.ReviewState::Approved,
)
assert_false(pending.leaf_merge_gate_ready_for_agent("main.leaf-merge-gate"))
ignore(@sys.rm_rf(pending.config.project_dir))

let findings = make_state(
"findings",
@types.AuditReceiptState::FindingsRemain(head_sha="head", count=2),
@poller.ReviewState::Approved,
)
assert_false(findings.leaf_merge_gate_ready_for_agent("main.leaf-merge-gate"))
ignore(@sys.rm_rf(findings.config.project_dir))

let valid = make_state(
"valid",
@types.AuditReceiptState::Valid,
@poller.ReviewState::Approved,
)
assert_true(valid.leaf_merge_gate_ready_for_agent("main.leaf-merge-gate"))
ignore(@sys.rm_rf(valid.config.project_dir))

let not_required = make_state(
"not-required",
@types.AuditReceiptState::NotRequired,
@poller.ReviewState::Approved,
)
assert_true(
not_required.leaf_merge_gate_ready_for_agent("main.leaf-merge-gate"),
)
ignore(@sys.rm_rf(not_required.config.project_dir))

let default_branch = make_state(
"default-branch",
@types.AuditReceiptState::NotRequired,
@poller.ReviewState::None_,
)
assert_eq(default_branch.cached_default_branch, "main")
assert_true(
default_branch.leaf_merge_gate_ready_for_agent("main.leaf-merge-gate"),
)
ignore(@sys.rm_rf(default_branch.config.project_dir))
}

///|
fn worker_no_handoff_terminal_process(
lifecycle : @phase.AgentLifecycle,
Expand Down
28 changes: 28 additions & 0 deletions src/server/handler_poll_delivery.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -2333,6 +2333,7 @@ async fn ServerState::refresh_tracked_pr_audit_receipt_states(
self.config.project_dir,
capture,
)
self.cached_default_branch = default_branch
for tracked in tracked_prs {
match self.poller.get(tracked.pr_number) {
Some(current) => {
Expand All @@ -2353,6 +2354,33 @@ async fn ServerState::refresh_tracked_pr_audit_receipt_states(
}
}

///|
async test "poller audit refresh caches detected default branch" {
let project_dir = "/tmp/choir-tick-default-branch-" +
@sys.get_pid().to_string() +
"-" +
@env.now().reinterpret_as_int64().to_string()
ignore(@sys.create_dir_all(project_dir + "/.choir/state/kv"))
let state = ServerState::new(
{ ..@types.Config::default(), project_dir, },
make_hermetic_recovery_provider(),
)
state.poller.track(
902, "main.leaf-tick-default", "feature/leaf", "feature/base", "https://example.test/pr/902",
0L,
)
state.refresh_tracked_pr_audit_receipt_states(
async fn(_cmd) {
@async.sleep(0)
(0, "origin/develop\n")
},
fn(_) { false },
fn(_) { "" },
)
assert_eq(state.cached_default_branch, "develop")
ignore(@sys.rm_rf(project_dir))
}

///|
async fn deliver_pending_poll_events(
state : ServerState,
Expand Down
3 changes: 3 additions & 0 deletions src/server/state.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ pub struct ServerState {
poller : @poller.PollerState
kv_store : @kv.KvStore
run_id : String
// Synchronous watchdog gate cache; populated by the poller tick, main before its first tick.
mut cached_default_branch : String
pending_disconnects : Map[String, Int64]
agent_pids : Map[String, Int]
disconnect_contexts : Map[String, DisconnectContext]
Expand Down Expand Up @@ -446,6 +448,7 @@ pub fn ServerState::new(
poller,
kv_store: @kv.KvStore::new(config.project_dir),
run_id,
cached_default_branch: "main",
pending_disconnects: {},
agent_pids: {},
disconnect_contexts: {},
Expand Down
15 changes: 15 additions & 0 deletions src/server/state_test.mbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
///|
test "ServerState::new defaults cached default branch to main" {
let project_dir = "/tmp/choir-cached-default-branch-init-" +
@sys.get_pid().to_string() +
"-" +
@env.now().reinterpret_as_int64().to_string()
ignore(@sys.create_dir_all(project_dir + "/.choir/state/kv"))
let state = ServerState::new(
{ ..@types.Config::default(), project_dir, },
make_hermetic_recovery_provider(),
)
assert_eq(state.cached_default_branch, "main")
ignore(@sys.rm_rf(project_dir))
}

///|
test "register_agent_pid + forget_agent_pid" {
let project_dir = "/tmp/choir-agent-pid-state-" +
Expand Down
Loading