Skip to content

Commit abe5aa8

Browse files
committed
test(tui): pin that an open approval gate holds off the resume bound
A watchdog resume waits for the operator to answer the approval it re-presents, so the one-stall-budget bound on an unanswered attempt could in principle abort an operator mid-decision. The open gate exempts the turn from the stall clock, which prevents that. These tests pin it: a gate held open well past the bound is never interrupted, and a refusal that lands after the operator answered does not interrupt the now-live turn.
1 parent e408348 commit abe5aa8

1 file changed

Lines changed: 46 additions & 0 deletions

File tree

‎src/tui/turn-monitor.test.ts‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,52 @@ describe("stall recovery of a suspended approval", () => {
670670
});
671671
});
672672

673+
test("an approval gate open past the resume budget is never aborted", async () => {
674+
await withHarness(async (t) => {
675+
const d = deferredRecovery();
676+
t.bridge.setSuspendedApprovalRecovery(d.recovery);
677+
reachStallBudget(t);
678+
t.tick();
679+
expect(d.calls()).toBe(1);
680+
681+
// The re-presented approval is on screen and the operator takes their
682+
// time, well past the one-budget bound on an unanswered attempt.
683+
t.bridge.gateOpened();
684+
for (let i = 0; i < 5; i++) {
685+
t.advance(1_000);
686+
t.tick();
687+
}
688+
expect(t.port.calls).toEqual([]);
689+
expect(d.calls()).toBe(1);
690+
691+
// They answer: the gate closes and the decision lands.
692+
t.bridge.gateClosed();
693+
d.resolve(true);
694+
await flush();
695+
t.tick();
696+
expect(t.port.calls).toEqual([]);
697+
});
698+
});
699+
700+
test("a late refusal after the operator answered does not interrupt", async () => {
701+
await withHarness(async (t) => {
702+
const d = deferredRecovery();
703+
t.bridge.setSuspendedApprovalRecovery(d.recovery);
704+
reachStallBudget(t);
705+
t.tick();
706+
707+
t.bridge.gateOpened();
708+
t.advance(2_000);
709+
t.tick();
710+
t.bridge.gateClosed();
711+
// The answer restarted the stall clock, so the turn is live again.
712+
d.resolve(false);
713+
await flush();
714+
t.tick();
715+
expect(t.port.calls).toEqual([]);
716+
});
717+
});
718+
673719
test("a refusal before presenting never flashes the resume status", async () => {
674720
await withHarness(async (t) => {
675721
const d = deferredRecovery({ presents: false });

0 commit comments

Comments
 (0)