mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
test: cover quota-full and terminal reminder recovery
This commit is contained in:
1 parent
9cba975cd0
commit
2b0fa7000a
2 files changed
+106
-1
No files matched your search
@@ -736,3 +736,75 @@ describe("#1933: the first reminder waits a grace after the seat starts waiting"
|
||||
expect((await run(new Date(NOW - GRACE_MS - 1_000).toISOString())).action).toBe("send");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("parked reminders after a positive no-input quota rejection", () => {
|
||||
const quotaState = "guard-revision-a:100:8192";
|
||||
const failedQuota = (result: Awaited<ReturnType<ReturnType<typeof makeParkedOwnerConsumerPolicy>["evaluate"]>>, phase: string | undefined = "not_reached") => {
|
||||
const entry = sentHistory({ episodeKey: String(result.notes?.["episodeKey"]), primaryRow: String(result.notes?.["primaryRow"]), deliveryStatus: "failed", deliveryReason: "Held-message quota is full; no input was written." });
|
||||
entry.evaluationNotes = { ...entry.evaluationNotes, deliveryCode: "retained_quota_full", ...(phase ? { deliveryInputPhase: phase } : {}) };
|
||||
return entry;
|
||||
};
|
||||
const policy = (store: RowStore, history: WatchdogHistoryEntry[], state = quotaState, terminal = false) =>
|
||||
makeParkedOwnerConsumerPolicy({ ...makeDeps([parkedSeat()], store, history), retentionState: () => state, isTerminalSeat: () => terminal } as ParkedOwnerConsumerDeps);
|
||||
|
||||
it("bounds unchanged attempts AND row bookkeeping across restart and history pruning", async () => {
|
||||
const store = new RowStore();
|
||||
const first = await policy(store, []).evaluate(makeJob());
|
||||
expect(first.action).toBe("send");
|
||||
const history = [failedQuota(first)];
|
||||
const next = await policy(store, history).evaluate(makeJob());
|
||||
expect(next).toMatchObject({ action: "skip", notes: { skippedSeats: [{ seat: SEAT, why: "retention-quota-unchanged" }] } });
|
||||
const writes = store.appended.length;
|
||||
const nudges = store.nudges.length;
|
||||
expect(store.nudges[0]?.result).toContain("retained_quota_full");
|
||||
expect(store.nudges[0]?.result).toContain("not_reached");
|
||||
for (let i = 0; i < 100; i++) {
|
||||
expect((await policy(store, i < 50 ? history : []).evaluate(makeJob())).action).toBe("skip");
|
||||
}
|
||||
expect(store.appended).toHaveLength(writes);
|
||||
expect(store.nudges).toHaveLength(nudges);
|
||||
expect(nudges).toBe(1);
|
||||
});
|
||||
|
||||
it.each(["quota-released", "guard-revision-b:off"])("rechecks admission when %s changes", async state => {
|
||||
const store = new RowStore();
|
||||
const first = await policy(store, []).evaluate(makeJob());
|
||||
const history = [failedQuota(first)];
|
||||
expect((await policy(store, history).evaluate(makeJob())).action).toBe("skip");
|
||||
const retry = await policy(store, history, state).evaluate(makeJob());
|
||||
expect(retry.action).toBe("send");
|
||||
const delivered = sentHistory({ episodeKey: String(retry.notes?.["episodeKey"]), primaryRow: String(retry.notes?.["primaryRow"]) });
|
||||
expect((await policy(store, [delivered, ...history], state).evaluate(makeJob())).action).toBe("skip");
|
||||
});
|
||||
|
||||
it.each([undefined, "attempted", "written"])("does not turn phase %s into positive no-input admission", async phase => {
|
||||
const store = new RowStore();
|
||||
const first = await policy(store, []).evaluate(makeJob());
|
||||
const entry = failedQuota(first, phase);
|
||||
if (phase === undefined) delete entry.evaluationNotes!["deliveryInputPhase"];
|
||||
expect((await policy(store, [entry]).evaluate(makeJob())).action).toBe("send");
|
||||
});
|
||||
|
||||
it("leaves an ordinary transient failure retryable", async () => {
|
||||
const store = new RowStore();
|
||||
const first = await policy(store, []).evaluate(makeJob());
|
||||
const entry = failedQuota(first);
|
||||
delete entry.evaluationNotes!["deliveryCode"];
|
||||
expect((await policy(store, [entry]).evaluate(makeJob())).action).toBe("send");
|
||||
});
|
||||
|
||||
it("records terminal-only custody once without reserving recurring input attempts", async () => {
|
||||
const store = new RowStore();
|
||||
const first = await policy(store, [], quotaState, true).evaluate(makeJob());
|
||||
expect(first).toMatchObject({ action: "skip", notes: { skippedSeats: [{ seat: SEAT, why: "terminal-seat" }] } });
|
||||
expect(store.nudges).toHaveLength(1);
|
||||
expect(store.nudges[0]?.result).toContain("failed:terminal-seat:");
|
||||
expect(store.nudges[0]?.result).toContain("route it to an agent seat");
|
||||
const writes = store.appended.length;
|
||||
for (let i = 0; i < 100; i++) expect((await policy(store, [], quotaState, true).evaluate(makeJob())).action).toBe("skip");
|
||||
expect(store.appended).toHaveLength(writes);
|
||||
expect(store.nudges).toHaveLength(1);
|
||||
expect((await policy(store, [], quotaState, false).evaluate(makeJob())).action).toBe("send");
|
||||
});
|
||||
});
|
||||
@@ -12,7 +12,7 @@ import { TmuxAdapter } from "../src/adapters/tmux.js";
|
||||
import { ALL_MIGRATIONS } from "../src/db/all-migrations.js";
|
||||
import { migrate } from "../src/db/migrate.js";
|
||||
import type { ComposerSnapshot } from "../src/domain/composer-prompts.js";
|
||||
import { OutboxHandler } from "../src/domain/outbox-handler.js";
|
||||
import { OutboxHandler, OutboxHandlerError } from "../src/domain/outbox-handler.js";
|
||||
import { RigRepository } from "../src/domain/rig-repository.js";
|
||||
import { SeatDeliveryGuard, resolveGuardTarget } from "../src/domain/seat-delivery-guard.js";
|
||||
import { SessionRegistry } from "../src/domain/session-registry.js";
|
||||
@@ -368,3 +368,36 @@ describe("current parked-owner reminders", () => {
|
||||
expect(f.submissions).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("watchdog positive no-input outcomes", () => {
|
||||
it("preserves the typed quota rejection through the real engine history", async () => {
|
||||
const f = fixture();
|
||||
const jobs = new WatchdogJobsRepository(f.db);
|
||||
const history = new WatchdogHistoryLog(f.db);
|
||||
const deliver = makeWatchdogDeliver({ sessionTransport: { send: async () => { throw new OutboxHandlerError("retained_quota_full", "Held-message quota is full; no input was written."); } } });
|
||||
const engine = new WatchdogPolicyEngine({ jobsRepo: jobs, historyLog: history, eventBus: new EventBus(f.db), deliver });
|
||||
const job = jobs.register({ policy: "periodic-reminder", targetSession: seat, intervalSeconds: 60, registeredBySession: seat, specYaml: "message: Still owed\n" });
|
||||
await engine.evaluate(job);
|
||||
expect(history.listForJob(job.jobId, 1)[0]).toMatchObject({ deliveryStatus: "failed", evaluationNotes: { deliveryCode: "retained_quota_full", deliveryInputPhase: "not_reached" } });
|
||||
expect(f.pastes).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("does not infer no input from an untyped error with the same text", async () => {
|
||||
const deliver = makeWatchdogDeliver({ sessionTransport: { send: async () => { throw new Error("Held-message quota is full; no input was written."); } } });
|
||||
const result = await deliver({ targetSession: seat, message: "Still owed" }, { jobId: "quota-control", policy: PARKED_OWNER_POLICY_NAME, occurrenceId: "one" });
|
||||
expect(result).not.toHaveProperty("code");
|
||||
expect(result).not.toHaveProperty("inputPhase");
|
||||
});
|
||||
|
||||
it("excludes bound terminal-only parked reminders while preserving other deliberate deliveries", async () => {
|
||||
const sends = vi.fn().mockResolvedValue({ ok: true });
|
||||
const deliver = makeWatchdogDeliver({ sessionTransport: { send: sends }, isTerminalSeat: () => true } as Parameters<typeof makeWatchdogDeliver>[0]);
|
||||
const refused = await deliver({ targetSession: seat, message: "Parked wake" }, { jobId: "terminal-policy", policy: PARKED_OWNER_POLICY_NAME, occurrenceId: "one" });
|
||||
expect(refused).toMatchObject({ status: "failed", code: "terminal_seat", inputPhase: "not_reached" });
|
||||
expect(sends).not.toHaveBeenCalled();
|
||||
const deliberate = await deliver({ targetSession: seat, message: "Explicitly requested terminal text" }, { jobId: "terminal-explicit", policy: "periodic-reminder", occurrenceId: "two" });
|
||||
expect(deliberate.status).toBe("ok");
|
||||
expect(sends).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user