Compare commits

...
5 changed files with 65 additions and 9 deletions

No files matched your search

@@ -1066,7 +1066,7 @@ describe("OPR.0.5.6.1 §8 — the production composition is live (R2 B-1/B-2/B-3
try {
wire.startServices?.();
const make = (mod as { makeOperatorDeliveryEngine: (deps: unknown) => { dispatchEscalation: (row: QueueItem, reason: string) => Promise<{ decision: string; resolved: boolean }> } }).makeOperatorDeliveryEngine;
const port = make({ home, queueRepo: repo, dispatch: (op: string, ref: string, payload: unknown) => wire.dispatcher.dispatch(op, ref, payload), registry: { loadHumanRegistry: () => registryWith({ deliveryClass: "B", availability: "available" }) } });
const port = make({ home, readiness: async () => ({ ready: true, reason: "fixture" }), queueRepo: repo, dispatch: (op: string, ref: string, payload: unknown) => wire.dispatcher.dispatch(op, ref, payload), registry: { loadHumanRegistry: () => registryWith({ deliveryClass: "B", availability: "available" }) } });
const src = await repo.create({ sourceSession: "sender@r", destinationSession: "relay@r", body: "obligation" });
const { created } = await repo.handoff({ qitemId: src.qitemId, fromSession: "relay@r", toSession: "worker@r", nudge: false });
@@ -35,7 +35,7 @@ describe("the default person, for escalations and Mission Control's my-queue", (
const escalate = async () => {
const sentTo: string[] = [];
const engine = makeOperatorDeliveryEngine({ home, queueRepo: repo,
const engine = makeOperatorDeliveryEngine({ home, ...{ readiness: async () => ({ ready: true, reason: "fixture" }) }, queueRepo: repo,
dispatch: (_op, ref, _payload, opts) => { sentTo.push(ref); return { ok: true, decisionId: opts?.decisionId ?? "d" }; } });
const row = await repo.create({ sourceSession: "sender@fixture", destinationSession: "worker@fixture", body: "stuck work", summary: "stuck", nudge: false });
const result = await engine.dispatchEscalation(row as never, "blocked");
@@ -53,7 +53,7 @@ describe("prompt-blocked outstanding work reaches the configured route", () => {
});
afterEach(() => { for (const w of wires.splice(0)) w.stop(); db.close(); rmSync(home, { recursive: true, force: true }); });
function engine(posts: unknown[], registered: boolean | (() => LoadResult) = true, to?: string[]) {
function engine(posts: unknown[], registered: boolean | (() => LoadResult) = true, to?: string[], readiness = async () => ({ ready: true, reason: "fixture delivery ready" })) {
const load = typeof registered === "function" ? registered : () => registered ? registry : { ok: true as const, entities: [] };
const secrets = join(home, "slack.env");
writeFileSync(secrets, "SLACK_BOT_TOKEN=fixture-only\n", { mode: 0o600 });
@@ -72,7 +72,7 @@ describe("prompt-blocked outstanding work reaches the configured route", () => {
},
});
wires.push(wire); wire.startServices?.();
return makeOperatorDeliveryEngine({ home, queueRepo: repo,
return makeOperatorDeliveryEngine({ home, queueRepo: repo, ...{ readiness },
registry: { loadHumanRegistry: load },
dispatch: (op, ref, payload, opts) => { to?.push(ref); return wire.dispatcher.dispatch(op, ref, payload, opts); },
});
@@ -144,12 +144,66 @@ describe("prompt-blocked outstanding work reaches the configured route", () => {
expect(wakes).toEqual([]);
expect(alerts()).toHaveLength(1);
expect(alerts()[0]?.destinationSession).toBe(seat); // carrier only, never a pane nudge
expect(alerts()[0]?.sourceSession).toBe(staleSource); // provenance is not rewritten
expect(alerts()[0]?.sourceSession).toBe("daemon@kernel"); // scheduling is OpenRig; member provenance remains in its own row
expect(JSON.stringify(posts)).toContain(row.qitemId);
await tick(port);
expect(posts).toHaveLength(1);
});
it("a human operator setting uses a local carrier and preserves explicit-only for a choice", async () => {
vi.stubEnv("OPENRIG_WORKSPACE_OPERATOR_SEAT_NAME", "human-owner@external");
try {
await refusal(1, `Refused: '${seat}' is at an interactive prompt (selection_prompt). No text was sent.`);
const posts: unknown[] = [];
await tick(engine(posts), { humanRung: "explicit-only", loadHumanRegistry: () => registry });
expect(alerts()).toHaveLength(1);
expect(alerts()[0]!.destinationSession).not.toBe("human-owner@external");
expect(posts).toEqual([]);
expect(repo.getById(episodeRows[0]!)!.state).toBe("in-progress");
} finally { vi.unstubAllEnvs(); }
});
it.each(["not-ready", "indeterminate"])("%s delivery retains one visible alert, then uses the same episode when ready", async status => {
await refusal(); let ready = false; const posts: unknown[] = [];
const port = engine(posts, true, undefined, async () => ({ ready, reason: status }));
await tick(port); await tick(port);
expect(posts).toEqual([]);
expect(alerts()).toHaveLength(1);
const id = alerts()[0]!.qitemId;
expect(notes(id).join("\n")).toContain("unavailable:human-delivery");
expect(notes(id).join("\n")).not.toContain("dispatched-to-engine");
ready = true; await tick(port, { now: new Date(Date.now() + 240_000) });
await vi.waitFor(() => expect(posts).toHaveLength(1));
await tick(port); expect(posts).toHaveLength(1); expect(alerts()[0]!.qitemId).toBe(id);
});
it("the real readiness check keeps a disabled connector out of the dispatch buffer", async () => {
const row = await refusal(); const dispatch = vi.fn(() => ({ ok: true as const, decisionId: "disabled-fixture" }));
const secrets = join(home, "disabled.env"); writeFileSync(secrets, "SLACK_BOT_TOKEN=fixture-only\n");
saveConfig({ ...DEFAULT_CONFIG, enabled: false, channel: "CFIXTURE", secretsEnvFile: secrets }, home);
const port = makeOperatorDeliveryEngine({ home, queueRepo: repo, registry: { loadHumanRegistry: () => registry }, dispatch });
const result = await port.dispatchEscalation(row, "permission prompt");
expect(result).toMatchObject({ dispatched: false, resolved: false });
expect(result.decision).toContain("unavailable:human-delivery");
expect(dispatch).not.toHaveBeenCalled(); expect(new DispatchBuffer(home).pending()).toEqual([]);
});
it.each([true, false])("an undelivered notice about a ladder alert does not recurse (ladder=%s)", async ladderNotice => {
const notice = await repo.create({ sourceSession: "sender@fixture", destinationSession: "human-owner@external",
body: "original undelivered notice", tags: ladderNotice ? ["wake-escalation"] : [], nudge: false });
const finding = await refusal();
db.prepare("UPDATE queue_items SET tags=? WHERE qitem_id=?").run(JSON.stringify([
`stuck-sweep:undelivered-human-notice:${notice.qitemId}`, `recovery-for:${notice.qitemId}`,
]), finding.qitemId);
const posts: unknown[] = []; const port = engine(posts);
await tick(port); await tick(port);
expect(alerts()).toHaveLength(ladderNotice ? 0 : 1);
if (!ladderNotice) await vi.waitFor(() => expect(posts).toHaveLength(1));
else expect(posts).toEqual([]);
expect(repo.getById(finding.qitemId)!.state).toBe("in-progress");
expect(repo.getById(notice.qitemId)!.state).toBe("pending");
});
it("an invalid sender does not turn an explicit-only choice alert into a human post", async () => {
const row = await refusal(1, `Refused: '${seat}' is at an interactive prompt (selection_prompt). No text was sent.`);
const staleSource = "sender@remote@retired-host";
@@ -435,6 +489,8 @@ describe("prompt-blocked outstanding work reaches the configured route", () => {
expect(classifyPromptAfterRefusal({ ...idle, needsInput: { count: 1, reason: "permission prompt" } }, refusedAt)).toBe("blocked");
expect(classifyPromptAfterRefusal({ ...idle, activity: "unknown", changedAt: after, needsInputEvidence: clearAt(after) }, refusedAt)).toBe("unknown");
expect(classifyPromptAfterRefusal(null, refusedAt)).toBe("unknown");
expect(classifyPromptAfterRefusal({ ...idle, activity: "working", needsInputEvidence: clearAt(after) }, refusedAt)).toBe("clear");
expect(classifyPromptAfterRefusal({ ...idle, activity: "working", needsInputEvidence: null }, refusedAt)).toBe("unknown");
});
it("activity-only Codex and generic changes cannot clear a refused prompt", () => {
@@ -656,7 +712,7 @@ describe("prompt-blocked outstanding work reaches the configured route", () => {
const row = await refusal(); const buffer = new DispatchBuffer(home); let sends = 0;
const dispatcher = new GatewayDispatcher({ buffer, send: () => { sends++; throw new Error("connector disconnected after persist"); } });
dispatcher.onCapability({ kind: "capability", connectorId: "fixture", platform: "slack", protocolVersion: 1, ops: ["post_message"] });
const port = makeOperatorDeliveryEngine({ home, queueRepo: repo, registry: { loadHumanRegistry: () => registry },
const port = makeOperatorDeliveryEngine({ home, queueRepo: repo, ...{ readiness: async () => ({ ready: true, reason: "fixture" }) }, registry: { loadHumanRegistry: () => registry },
dispatch: (op, ref, payload, opts) => dispatcher.dispatch(op, ref, payload, opts) });
expect(await port.dispatchEscalation(row, "blocked")).toMatchObject({ decision: "accepted-pending", dispatched: true, resolved: false });
const pending = new DispatchBuffer(home).pending(); expect(pending).toHaveLength(1);
@@ -697,7 +753,7 @@ describe("prompt-blocked outstanding work reaches the configured route", () => {
it("an unadvertised connector is unresolved with no buffer entry or repeated note", async () => {
await refusal(); const sends = vi.fn();
const dispatcher = new GatewayDispatcher({ buffer: new DispatchBuffer(home), send: sends });
const port = makeOperatorDeliveryEngine({ home, queueRepo: repo, registry: { loadHumanRegistry: () => registry },
const port = makeOperatorDeliveryEngine({ home, queueRepo: repo, ...{ readiness: async () => ({ ready: true, reason: "fixture" }) }, registry: { loadHumanRegistry: () => registry },
dispatch: (op, ref, payload, opts) => dispatcher.dispatch(op, ref, payload, opts) });
await tick(port); const id = alerts()[0]!.qitemId; const before = repo.listTransitions(id);
await tick(port); await tick(port);
@@ -316,7 +316,7 @@ describe("holding and releasing through the gateway", () => {
quietNow();
const row = await park();
const dispatched: unknown[] = [];
const engine = makeOperatorDeliveryEngine({ home, queueRepo: repo, registry: { loadHumanRegistry: () => registryWith(prefs) },
const engine = makeOperatorDeliveryEngine({ home, ...{ readiness: async () => ({ ready: true, reason: "fixture" }) }, queueRepo: repo, registry: { loadHumanRegistry: () => registryWith(prefs) },
dispatch: (_op, _ref, payload) => { dispatched.push(payload); return { ok: true, decisionId: "d" } as never; } });
const out = await engine.dispatchEscalation(row, "test");
expect(out).toMatchObject({ dispatched: true, decision: "interrupt" });
@@ -48,7 +48,7 @@ describe("a wake-rung escalation names the seat the work is waiting on", () => {
const home = fs.mkdtempSync(path.join(os.tmpdir(), "sender-first-"));
try {
const sent: Array<Record<string, unknown>> = [];
const engine = makeOperatorDeliveryEngine({ home, registry,
const engine = makeOperatorDeliveryEngine({ home, ...{ readiness: async () => ({ ready: true, reason: "fixture" }) }, registry,
queueRepo: { transitionLog: { latestOwnerNotificationForQitem: () => null }, db: undefined } as never,
dispatch: (_op, _ref, payload) => { sent.push(payload as Record<string, unknown>); return { ok: true, decisionId: "d" } as never; } });
await engine.dispatchEscalation({ qitemId: "q-stuck", body: "Please review.", tags: [], ...row } as QueueItem, "unclaimed");