mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-07 07:42:19 +01:00
Author genuine uncertain-email recovery and scoped response loss
This commit is contained in:
1 parent
c0dd31d242
commit
12130d11b6
9 files changed
+420
No files matched your search
@@ -0,0 +1,54 @@
|
||||
import { afterEach, expect, it } from "vitest";
|
||||
import { randomUUID, createHash } from "node:crypto";
|
||||
import { mkdtemp, realpath, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { createRequire } from "node:module";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { emailFaultRequestMatches, matchesFaultSend, matchesFaultRead, faultNotification, installOwnedEmailResponseLoss, armOwnedEmailResponseLoss, readOwnedEmailFault, type EmailFaultRequest, type AcceptedEmailFault, type EmailFaultStatus } from "./runtime-email-fault.js";
|
||||
const roots: string[] = [];
|
||||
afterEach(async () => { for (const root of roots.splice(0)) await rm(root, { recursive: true, force: true }); });
|
||||
function fixture() {
|
||||
const nonce = randomUUID(), text = `Self-addressed synthetic fixture ${nonce}`;
|
||||
const request: EmailFaultRequest = { schema: "paperclip.email-response-loss.v1", executionId: "chat-features.runner-codex-subscription.local.recovery.unknown-email-outcome", attemptId: "a".repeat(32), companyId: randomUUID(), endpointId: randomUUID(), agentId: randomUUID(), sourceIssueId: randomUUID(), inboxId: "dedicated-qa@example.test", recipient: "dedicated-qa@example.test", subject: `Owned self test ${nonce}`, textSha256: createHash("sha256").update(text).digest("hex"), nonce, createdAt: Date.now() - 1, expiresAt: Date.now() + 60000, dedicatedDisposableInbox: true };
|
||||
const publicationId = randomUUID(), url = new URL(`https://api.agentmail.to/v0/inboxes/${encodeURIComponent(request.inboxId)}/messages/send`);
|
||||
const init: RequestInit = { method: "POST", headers: { "Idempotency-Key": publicationId }, body: JSON.stringify({ to: [request.recipient], subject: request.subject, text, headers: { "X-Paperclip-Publication-Id": publicationId } }) };
|
||||
return { request, text, publicationId, url, init };
|
||||
}
|
||||
it("accepts only an exact finite owned action and time-bounded self-addressed inbox", () => { const f = fixture(); expect(emailFaultRequestMatches(f.request, f.request)).toBe(true); for (const mutation of [{ executionId: "goals.list" }, { attemptId: "b".repeat(32) }, { dedicatedDisposableInbox: false }, { recipient: "someone-else@example.test" }, { expiresAt: f.request.createdAt + 480001 }, { createdAt: Date.now() + 1000 }, { extra: true }]) expect(emailFaultRequestMatches({ ...f.request, ...mutation } as typeof f.request, f.request)).toBe(false); });
|
||||
it("never intercepts another publication target, body, provider or recipient", () => { const f = fixture(); expect(matchesFaultSend(f.request, f.url, f.init)).toBe(f.publicationId); expect(matchesFaultSend(f.request, new URL(f.url.href.replace("api.agentmail.to", "another.example.test")), f.init)).toBeUndefined(); expect(matchesFaultSend(f.request, f.url, { ...f.init, method: "GET" })).toBeUndefined(); for (const change of [{ to: ["other@example.test"] }, { text: f.text + "changed" }, { subject: "different" }, { attachments: [{ content: "unapproved" }] }, { headers: { "X-Paperclip-Publication-Id": randomUUID() } }]) expect(matchesFaultSend(f.request, f.url, { ...f.init, body: JSON.stringify({ ...JSON.parse(String(f.init.body)), ...change }) })).toBeUndefined(); });
|
||||
it("receipt-read outage stays scoped to the selected inbox", () => { const f = fixture(); expect(matchesFaultRead(f.request, new URL(f.url.href.replace(/\/send$/, "/message-id")), "GET")).toBe(true); expect(matchesFaultRead(f.request, new URL("https://api.agentmail.to/v0/auth/me"), "GET")).toBe(false); expect(matchesFaultRead(f.request, new URL("https://api.agentmail.to/v0/inboxes/other/messages"), "GET")).toBe(false); expect(matchesFaultRead(f.request, f.url, "POST")).toBe(false); });
|
||||
it("uses actual source-qualified message, send and delivery receipt envelopes", () => { for (const value of [{ event_type: "message.sent", send: { inbox_id: "inbox", message_id: "message" } }, { type: "message_received", message: { inbox_id: "inbox", message_id: "message" } }, { event_type: "message.delivered", delivery: { inbox_id: "inbox", message_id: "message" } }]) expect(faultNotification(value)).toEqual({ inboxId: "inbox", messageId: "message" }); expect(faultNotification({ type: "subscribed" })).toBeUndefined(); });
|
||||
it("installs nothing for default source executions", async () => { const original = globalThis.fetch; expect(await installOwnedEmailResponseLoss({ executionIds: ["goals.list"], attemptId: "", temporaryRoot: "/never-read" })).toBeUndefined(); expect(globalThis.fetch).toBe(original); });
|
||||
it("forwards exactly one genuine synthetic upstream response, loses it, blocks only same-key retries and records private actual IDs", async () => {
|
||||
const f = fixture(), root = await realpath(await mkdtemp(path.join(tmpdir(), "email-loss-control-"))); roots.push(root);
|
||||
const previous = globalThis.fetch; let forwarded = 0;
|
||||
globalThis.fetch = (async () => { forwarded++; return new Response(JSON.stringify({ message_id: "actual-upstream-message", thread_id: "actual-upstream-thread" }), { status: 202 }); }) as typeof fetch;
|
||||
const installed = await installOwnedEmailResponseLoss({ executionIds: [f.request.executionId], attemptId: f.request.attemptId, temporaryRoot: root });
|
||||
try {
|
||||
await armOwnedEmailResponseLoss(root, f.request);
|
||||
await expect(fetch(f.url, f.init)).rejects.toThrow("actual accepted send response was lost");
|
||||
const accepted = await readOwnedEmailFault<AcceptedEmailFault>(root, "accepted-private.json"); expect(accepted.publicationId).toBe(f.publicationId); expect(accepted.providerMessageId).toBe("actual-upstream-message");
|
||||
await expect(fetch(f.url, f.init)).rejects.toThrow("original persisted send response remains unavailable");
|
||||
await expect(fetch(new URL(f.url.href.replace(/\/send$/, "")))).rejects.toThrow("receipt transport unavailable");
|
||||
expect(forwarded).toBe(1);
|
||||
const other = randomUUID(), response = await fetch(f.url, { ...f.init, headers: { "Idempotency-Key": other }, body: JSON.stringify({ ...JSON.parse(String(f.init.body)), headers: { "X-Paperclip-Publication-Id": other } }) }); expect(response.status).toBe(202); expect(forwarded).toBe(2);
|
||||
const status = await readOwnedEmailFault<EmailFaultStatus>(root, "status.json"); expect(status.realSendsForwarded).toBe(1);
|
||||
} finally { await installed?.close(); globalThis.fetch = previous; }
|
||||
});
|
||||
it("withholds only the real accepted message's WebSocket receipt and restores the actual emitter at teardown", async () => {
|
||||
const f = fixture(), root = await realpath(await mkdtemp(path.join(tmpdir(), "email-loss-control-"))); roots.push(root);
|
||||
const WS = createRequire(new URL("../../../server/package.json", import.meta.url))("ws") as { prototype: EventEmitter }, originalEmit = WS.prototype.emit, previous = globalThis.fetch;
|
||||
globalThis.fetch = (async () => new Response(JSON.stringify({ message_id: "accepted-id", thread_id: "thread-id" }), { status: 200 })) as typeof fetch;
|
||||
const installed = await installOwnedEmailResponseLoss({ executionIds: [f.request.executionId], attemptId: f.request.attemptId, temporaryRoot: root });
|
||||
try {
|
||||
await armOwnedEmailResponseLoss(root, f.request); await expect(fetch(f.url, f.init)).rejects.toThrow("response was lost");
|
||||
const socket = Object.setPrototypeOf(new EventEmitter(), WS.prototype) as EventEmitter & { _url: string }; socket._url = "wss://ws.agentmail.to/v0"; let observed = 0; socket.on("message", () => { observed++; });
|
||||
socket.emit("message", Buffer.from(JSON.stringify({ event_type: "message.sent", send: { inbox_id: f.request.inboxId, message_id: "accepted-id" } })));
|
||||
expect(observed).toBe(0);
|
||||
socket.emit("message", Buffer.from(JSON.stringify({ event_type: "message.sent", send: { inbox_id: f.request.inboxId, message_id: "unrelated-id" } })));
|
||||
socket.emit("message", Buffer.from(JSON.stringify({ event_type: "message.sent", send: { inbox_id: "other-inbox", message_id: "accepted-id" } })));
|
||||
expect(observed).toBe(2);
|
||||
} finally { await installed?.close(); globalThis.fetch = previous; }
|
||||
expect(WS.prototype.emit).toBe(originalEmit);
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
/** Finite test-server network loss for one actual self-addressed AgentMail send.
|
||||
* No provider success, publication, delivery, or DB status is manufactured. */
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { createRequire } from "node:module";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { constants, openSync, closeSync, fstatSync, lstatSync, readFileSync, realpathSync } from "node:fs";
|
||||
import { mkdir, writeFile, rename, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
export const lostEmailScenario = "recovery.unknown-email-outcome";
|
||||
export interface EmailFaultRequest { schema: "paperclip.email-response-loss.v1"; executionId: string; attemptId: string; companyId: string; endpointId: string; agentId: string; sourceIssueId: string; inboxId: string; recipient: string; subject: string; textSha256: string; nonce: string; createdAt: number; expiresAt: number; dedicatedDisposableInbox: true }
|
||||
export interface AcceptedEmailFault { schema: "paperclip.email-response-loss-accepted.v1"; executionId: string; attemptId: string; nonce: string; publicationId: string; providerMessageId: string; providerThreadId: string; upstreamStatus: number; acceptedAt: number; acceptedResponseSha256: string }
|
||||
export interface EmailFaultStatus { schema: "paperclip.email-response-loss-status.v1"; executionId: string; attemptId: string; nonce: string; realSendsForwarded: number; sameKeyRetriesBlocked: number; receiptReadsBlocked: number; matchingNotificationsWithheld: number; unrelatedNotificationsForwarded: number; released: boolean; expired: boolean }
|
||||
const hash = (bytes: string | Buffer) => createHash("sha256").update(bytes).digest("hex");
|
||||
const uuid = (v: string) => /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(v);
|
||||
const keys = (value: unknown, expected: string[]) => !!value && typeof value === "object" && Object.keys(value).sort().join() === expected.sort().join();
|
||||
export function emailFaultRequestMatches(value: EmailFaultRequest, expected: { executionId: string; attemptId: string }, now = Date.now()) {
|
||||
return keys(value, ["schema", "executionId", "attemptId", "companyId", "endpointId", "agentId", "sourceIssueId", "inboxId", "recipient", "subject", "textSha256", "nonce", "createdAt", "expiresAt", "dedicatedDisposableInbox"]) && value.schema === "paperclip.email-response-loss.v1" && value.executionId === expected.executionId && value.executionId.endsWith(`.local.${lostEmailScenario}`) && value.attemptId === expected.attemptId && /^[a-f0-9]{32}$/.test(value.attemptId)
|
||||
&& [value.companyId, value.endpointId, value.agentId, value.sourceIssueId, value.nonce].every(uuid) && value.dedicatedDisposableInbox === true && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.inboxId) && value.recipient === value.inboxId
|
||||
&& value.subject.includes(value.nonce) && value.subject.length <= 200 && !/[\r\n]/.test(value.subject) && /^[a-f0-9]{64}$/.test(value.textSha256) && Number.isSafeInteger(value.createdAt) && Number.isSafeInteger(value.expiresAt) && value.createdAt <= now && now < value.expiresAt && value.expiresAt - value.createdAt <= 480000;
|
||||
}
|
||||
export function matchesFaultSend(request: EmailFaultRequest, url: URL, init: RequestInit | undefined) {
|
||||
if (url.origin !== "https://api.agentmail.to" || url.pathname !== `/v0/inboxes/${encodeURIComponent(request.inboxId)}/messages/send` || url.search || init?.method !== "POST" || typeof init.body !== "string" || Buffer.byteLength(init.body) > 131072) return undefined;
|
||||
try {
|
||||
const body = JSON.parse(init.body), key = new Headers(init.headers).get("Idempotency-Key"), publication = body.headers?.["X-Paperclip-Publication-Id"];
|
||||
return key && uuid(key) && publication === key && JSON.stringify(body.to) === JSON.stringify([request.recipient]) && !body.cc?.length && !body.bcc?.length && !body.attachments?.length && body.subject === request.subject && typeof body.text === "string" && hash(body.text) === request.textSha256 ? key : undefined;
|
||||
} catch { return undefined; }
|
||||
}
|
||||
export function matchesFaultRead(request: EmailFaultRequest, url: URL, method: string) { return url.origin === "https://api.agentmail.to" && method === "GET" && (url.pathname === `/v0/inboxes/${encodeURIComponent(request.inboxId)}/messages` || url.pathname.startsWith(`/v0/inboxes/${encodeURIComponent(request.inboxId)}/messages/`) || url.pathname.startsWith(`/v0/inboxes/${encodeURIComponent(request.inboxId)}/threads/`)); }
|
||||
export function faultNotification(value: unknown): { inboxId: string; messageId: string } | undefined {
|
||||
const event = value as { event_type?: string; type?: string; message?: unknown; send?: unknown; delivery?: unknown; bounce?: unknown; complaint?: unknown; reject?: unknown };
|
||||
const kind = event?.event_type ?? event?.type?.replace(/^message_/, "message."), item = kind === "message.sent" ? event.send ?? event.message : kind === "message.delivered" ? event.delivery ?? event.message : kind === "message.bounced" ? event.bounce ?? event.message : kind === "message.complained" ? event.complaint ?? event.message : kind === "message.rejected" ? event.reject ?? event.message : kind === "message.received" ? event.message : undefined;
|
||||
const msg = item as { inbox_id?: unknown; message_id?: unknown } | undefined;
|
||||
return typeof msg?.inbox_id === "string" && typeof msg.message_id === "string" ? { inboxId: msg.inbox_id, messageId: msg.message_id } : undefined;
|
||||
}
|
||||
export function emailFaultDirectory(temporaryRoot: string) { return path.join(temporaryRoot, "email-response-loss"); }
|
||||
async function atomic(directory: string, name: string, value: unknown) { const filename = path.join(directory, `${name}.${randomUUID()}.tmp`); await writeFile(filename, JSON.stringify(value), { mode: 0o600, flag: "wx" }); await rename(filename, path.join(directory, name)); }
|
||||
function privateRead<T>(filename: string): T {
|
||||
const folder = path.dirname(filename), dir = lstatSync(folder), file = lstatSync(filename);
|
||||
if (!dir.isDirectory() || dir.mode & 0o077 || dir.uid !== process.getuid?.() || realpathSync(folder) !== folder || !file.isFile() || file.isSymbolicLink() || file.nlink !== 1 || file.uid !== process.getuid?.() || file.mode & 0o077 || file.size > 32768) throw new Error("Email fault private control ownership changed");
|
||||
const fd = openSync(filename, constants.O_RDONLY | constants.O_NOFOLLOW);
|
||||
try { const before = fstatSync(fd); if (before.ino !== file.ino || before.dev !== file.dev) throw new Error("Private email fault control changed before read"); const bytes = readFileSync(fd), after = fstatSync(fd), named = lstatSync(filename); if (before.size !== after.size || before.mtimeMs !== after.mtimeMs || named.ino !== before.ino || named.dev !== before.dev) throw new Error("Private email fault control changed during read"); return JSON.parse(bytes.toString("utf8")) as T; } finally { closeSync(fd); }
|
||||
}
|
||||
/** Called ONLY by server-entry before production service construction. Other
|
||||
* executions install no wrapper, timer, socket hook or private control files. */
|
||||
export async function installOwnedEmailResponseLoss(input: { executionIds: string[]; attemptId: string; temporaryRoot: string }) {
|
||||
if (!input.executionIds.some(id => id.endsWith(`.local.${lostEmailScenario}`))) return undefined;
|
||||
if (input.executionIds.length !== 1 || !/^[a-f0-9]{32}$/.test(input.attemptId) || !path.isAbsolute(input.temporaryRoot) || realpathSync(input.temporaryRoot) !== input.temporaryRoot) throw new Error("Email transport loss requires its exact selected owned attempt");
|
||||
const directory = emailFaultDirectory(input.temporaryRoot); await mkdir(directory, { mode: 0o700 });
|
||||
const WebSocket = createRequire(new URL("../../../server/package.json", import.meta.url))("ws") as { prototype: EventEmitter };
|
||||
const originalFetch = globalThis.fetch, originalEmit = WebSocket.prototype.emit;
|
||||
let request: EmailFaultRequest | undefined, accepted: AcceptedEmailFault | undefined, publicationId: string | undefined, inflight: Promise<void> | undefined, settle: (() => void) | undefined;
|
||||
let status: EmailFaultStatus | undefined, closed = false, pendingNotifications = 0, write = Promise.resolve();
|
||||
const save = () => { if (status) { const snapshot = { ...status }; write = write.then(() => atomic(directory, "status.json", snapshot)); } };
|
||||
const load = () => { if (request || closed) return request; try { const value = privateRead<EmailFaultRequest>(path.join(directory, "request.json")); if (!emailFaultRequestMatches(value, { executionId: input.executionIds[0]!, attemptId: input.attemptId })) throw new Error("Email network fault request binding is stale or undeclared"); request = value; status = { schema: "paperclip.email-response-loss-status.v1", executionId: value.executionId, attemptId: value.attemptId, nonce: value.nonce, realSendsForwarded: 0, sameKeyRetriesBlocked: 0, receiptReadsBlocked: 0, matchingNotificationsWithheld: 0, unrelatedNotificationsForwarded: 0, released: false, expired: false }; save(); return request; } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined; throw error; } };
|
||||
const release = async (expired = false) => { if (closed) return; closed = true; if (status) { status.released = true; status.expired = expired; save(); } if (globalThis.fetch === wrappedFetch) globalThis.fetch = originalFetch; if (WebSocket.prototype.emit === wrappedEmit) WebSocket.prototype.emit = originalEmit; clearInterval(timer); settle?.(); await write; await atomic(directory, "closed.json", { executionId: input.executionIds[0], attemptId: input.attemptId, nonce: request?.nonce ?? null, released: true, expired }); };
|
||||
const wrappedFetch: typeof fetch = async (target, init) => {
|
||||
const req = load(); if (!req || closed) return originalFetch(target, init);
|
||||
if (Date.now() >= req.expiresAt) { await release(true); return originalFetch(target, init); }
|
||||
const url = new URL(typeof target === "string" ? target : target instanceof URL ? target.href : target.url), method = init?.method ?? (target instanceof Request ? target.method : "GET"), candidate = matchesFaultSend(req, url, init);
|
||||
if (matchesFaultRead(req, url, method) && publicationId) { status!.receiptReadsBlocked++; save(); await write; throw new TypeError("Selected inbox receipt transport unavailable during the declared bounded outage"); }
|
||||
if (!candidate || publicationId && candidate !== publicationId) return originalFetch(target, init);
|
||||
if (publicationId) { status!.sameKeyRetriesBlocked++; save(); await write; throw new TypeError("The original persisted send response remains unavailable during the bounded outage"); }
|
||||
publicationId = candidate; inflight = new Promise(resolve => { settle = resolve; }); status!.realSendsForwarded++; save();
|
||||
try {
|
||||
const response = await originalFetch(target, init);
|
||||
if (!response.ok) return response;
|
||||
const reader = response.clone().body?.getReader(); if (!reader) throw new Error("Actual accepted provider response has no receipt body");
|
||||
const chunks: Uint8Array[] = []; let size = 0;
|
||||
try { while (true) { const next = await reader.read(); if (next.done) break; size += next.value.byteLength; if (size > 32768) throw new Error("Accepted AgentMail response exceeded the bounded private receipt"); chunks.push(next.value); } } finally { await reader.cancel(); }
|
||||
const bytes = Buffer.concat(chunks);
|
||||
const body = JSON.parse(Buffer.from(bytes).toString("utf8")) as { message_id?: unknown; thread_id?: unknown };
|
||||
if (typeof body.message_id !== "string" || typeof body.thread_id !== "string" || !body.message_id || !body.thread_id || body.message_id.length > 998 || body.thread_id.length > 998) throw new Error("Real provider accepted send without a valid actual message/thread receipt");
|
||||
accepted = { schema: "paperclip.email-response-loss-accepted.v1", executionId: req.executionId, attemptId: req.attemptId, nonce: req.nonce, publicationId, providerMessageId: body.message_id, providerThreadId: body.thread_id, upstreamStatus: response.status, acceptedAt: Date.now(), acceptedResponseSha256: hash(Buffer.from(bytes)) };
|
||||
await atomic(directory, "accepted-private.json", accepted); await write;
|
||||
throw new TypeError("The actual accepted send response was lost in the declared selected-inbox network outage");
|
||||
} finally { settle?.(); }
|
||||
};
|
||||
const wrappedEmit: EventEmitter["emit"] = function (this: EventEmitter, event, ...args) {
|
||||
const req = request;
|
||||
if (event !== "message" || !req || closed || String((this as EventEmitter & { url?: string }).url) !== "wss://ws.agentmail.to/v0") return originalEmit.call(this, event, ...args);
|
||||
const data = args[0]; if (!Buffer.isBuffer(data) && typeof data !== "string" || Buffer.byteLength(data as string | Buffer) > 131072) return originalEmit.call(this, event, ...args);
|
||||
let notification: ReturnType<typeof faultNotification>; try { notification = faultNotification(JSON.parse(String(data))); } catch { return originalEmit.call(this, event, ...args); }
|
||||
if (notification?.inboxId !== req.inboxId || !publicationId) return originalEmit.call(this, event, ...args);
|
||||
const deliver = () => { if (!closed && accepted?.providerMessageId === notification!.messageId) { status!.matchingNotificationsWithheld++; save(); return; } status!.unrelatedNotificationsForwarded++; save(); originalEmit.call(this, event, ...args); };
|
||||
if (!accepted && inflight) { if (++pendingNotifications > 128) { void release(true); return originalEmit.call(this, event, ...args); } void inflight.then(() => { pendingNotifications--; deliver(); }); return true; }
|
||||
if (accepted?.providerMessageId === notification.messageId) { status!.matchingNotificationsWithheld++; save(); return true; }
|
||||
return originalEmit.call(this, event, ...args);
|
||||
};
|
||||
globalThis.fetch = wrappedFetch; WebSocket.prototype.emit = wrappedEmit;
|
||||
await atomic(directory, "installed.json", { schema: "paperclip.email-response-loss-installed.v1", executionId: input.executionIds[0], attemptId: input.attemptId, pid: process.pid });
|
||||
const timer = setInterval(() => { void (async () => { const req = load(); if (closed || !req) return; let released: { executionId?: string; attemptId?: string; nonce?: string; release?: boolean } | undefined; try { released = privateRead(path.join(directory, "release.json")); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; } if (released) { if (!keys(released, ["executionId", "attemptId", "nonce", "release"]) || released.executionId !== req.executionId || released.attemptId !== req.attemptId || released.nonce !== req.nonce || released.release !== true) throw new Error("Email outage release does not bind this exact action"); await release(); } else if (Date.now() >= req.expiresAt) await release(true); })().catch(() => { void release(true); }); }, 200); timer.unref();
|
||||
return { directory, close: release };
|
||||
}
|
||||
export async function armOwnedEmailResponseLoss(temporaryRoot: string, request: EmailFaultRequest) { if (!emailFaultRequestMatches(request, request)) throw new Error("Cannot arm an undeclared or unsafe email outage"); const directory = emailFaultDirectory(temporaryRoot); const info = lstatSync(directory); if (!info.isDirectory() || info.mode & 0o077 || info.uid !== process.getuid?.() || realpathSync(directory) !== directory) throw new Error("Selected test-server email fault hook is not installed in its owned private directory"); await atomic(directory, "request.json", request); }
|
||||
export async function readOwnedEmailFault<T>(temporaryRoot: string, name: "accepted-private.json" | "status.json" | "closed.json" | "installed.json") { return privateRead<T>(path.join(emailFaultDirectory(temporaryRoot), name)); }
|
||||
export async function releaseOwnedEmailResponseLoss(temporaryRoot: string, request: EmailFaultRequest) { await atomic(emailFaultDirectory(temporaryRoot), "release.json", { executionId: request.executionId, attemptId: request.attemptId, nonce: request.nonce, release: true }); const deadline = Date.now() + 10000; while (Date.now() < deadline) { try { const result = await readOwnedEmailFault<{ executionId: string; attemptId: string; nonce: string; released: boolean; expired: boolean }>(temporaryRoot, "closed.json"); if (result.executionId !== request.executionId || result.attemptId !== request.attemptId || result.nonce !== request.nonce || result.released !== true || result.expired !== false) throw new Error("The exact email fault did not release cleanly within its declared phase"); return; } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; } await new Promise(resolve => setTimeout(resolve, 100)); } throw new Error("The exact owned email outage release was not confirmed"); }
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"syntheticOnly": true,
|
||||
"tests": 2,
|
||||
"childFailureAndExpectedParentFailure": true,
|
||||
"invalidOwnedBindingRejectedBeforeHttp": true,
|
||||
"realProviderCalls": 0,
|
||||
"canonicalAttempts": 0,
|
||||
"browserLaunches": 0,
|
||||
"qualificationClaim": "private child/reporting failure boundary only; actual AgentMail setup, send, and positive provider receipt unqualified",
|
||||
"checkedSinks": [
|
||||
"stdout",
|
||||
"JUnit",
|
||||
"blob ZIP entries",
|
||||
"HTML embedded report ZIP",
|
||||
"error-context"
|
||||
],
|
||||
"scannedItems": 12,
|
||||
"privateValueCount": 4,
|
||||
"findings": [],
|
||||
"sourceSha256": {
|
||||
"runtime-email-private.ts": "38381b54ef3a9903ba26d7c85fa27e327de39ac4055488c02e1106944330801b",
|
||||
"runtime-email-private-worker.ts": "d77652ae26b4a80c4b45fe5c1cf3848b403d9c377c87c6d4ca4d33bcd10e6dc1",
|
||||
"runtime-email-private-privacy.spec.ts": "f7a10150c179ee17862fa819013809df65ce0c4cbe505421203015c52b61dd62"
|
||||
},
|
||||
"configSha256": "dcfb8e7013a1c2ff81277bb44bc5214357dc6fb5eacd2073cfaafca1790b31b1"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/** Explicit opt-in synthetic child/reporting calibration. Invalid owned scope
|
||||
* rejects before HTTP/provider access; no real credential, inbox or DB. */
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { appendFile } from "node:fs/promises";
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { privateEmailOperation } from "./runtime-email-private.js";
|
||||
for (const branch of ["child-failure", "parent-failure"] as const) test(`private email operation reporting ${branch}`, async () => {
|
||||
test.skip(!process.env.PAPERCLIP_EMAIL_PRIVATE_CANARY_FILE, "Explicit synthetic-only private email reporting canary");
|
||||
const credential = `synthetic-email-key-${randomUUID()}`, cookie = `synthetic-email-session-${randomUUID()}`;
|
||||
await appendFile(process.env.PAPERCLIP_EMAIL_PRIVATE_CANARY_FILE!, `${credential}\n${cookie}\n`, { mode: 0o600 });
|
||||
const secrets: string[] = [];
|
||||
await expect(privateEmailOperation({ operation: "setup", scenarioId: "recovery.unknown-email-outcome", companyId: "intentionally-invalid-owned-scope", agentId: "invalid", baseURL: "http://127.0.0.1:1", inboxId: "synthetic@example.test", recipient: "synthetic@example.test", dedicatedDisposableInbox: true, credential, cookies: [{ name: "session", value: cookie, domain: "127.0.0.1", path: "/", expires: -1, httpOnly: true, secure: false, sameSite: "Lax" }] }, secrets)).rejects.toThrow("credential-bearing request and provider diagnostics withheld");
|
||||
expect(secrets.length).toBe(2);
|
||||
if (branch === "parent-failure") { test.fail(); throw new Error("Deliberate safe parent failure after private synthetic email child rejection"); }
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { randomUUID, createHash } from "node:crypto";
|
||||
import { lstat, readFile } from "node:fs/promises";
|
||||
import { test } from "@playwright/test";
|
||||
import { agentmailApi } from "../../../server/src/services/agentmail-api.js";
|
||||
import type { EmailEndpointSummary, EmailPublicationSummary } from "../../../packages/shared/src/types/email.js";
|
||||
import type { EmailPrivateInput, EmailPrivateReceipt } from "./runtime-email-private.js";
|
||||
async function main(): Promise<EmailPrivateReceipt> {
|
||||
let attached = false; try { test.info(); attached = true; } catch { /* standalone only */ } if (attached) throw new Error("Private email credential operations must not execute in Test");
|
||||
const filename = process.argv[2]!, info = await lstat(filename); if (!info.isFile() || info.isSymbolicLink() || info.uid !== process.getuid?.() || info.mode & 0o077 || info.size > 65536) throw new Error("Invalid private email request file");
|
||||
const input = JSON.parse(await readFile(filename, "utf8")) as EmailPrivateInput, base = new URL(input.baseURL);
|
||||
if (!["recovery.unknown-email-outcome", "chat-channels.outbound-email", "chat-channels.reply-to-email"].includes(input.scenarioId) || !["setup", "inspect", "resolve"].includes(input.operation) || base.protocol !== "http:" || base.hostname !== "127.0.0.1" || base.origin !== input.baseURL || !input.dedicatedDisposableInbox || input.inboxId !== input.recipient || ![input.companyId, input.agentId].every(id => /^[a-f0-9-]{36}$/i.test(id))) throw new Error("Email operation requires the exact owned selected self-addressed fixture");
|
||||
let directProviderProbeCount = 0, publicSetupOperationCount = 0;
|
||||
const direct: typeof fetch = async (...args) => { directProviderProbeCount++; return fetch(...args); }, provider = agentmailApi(input.credential, direct);
|
||||
async function local<T>(pathname: string, body?: unknown) { const response = await fetch(base.origin + pathname, { method: body === undefined ? "GET" : "POST", body: body === undefined ? undefined : JSON.stringify(body), headers: { Cookie: input.cookies.map(row => `${row.name}=${row.value}`).join("; "), Origin: base.origin, "Content-Type": "application/json" }, signal: AbortSignal.timeout(120000), redirect: "error" }); if (!response.ok) { await response.body?.cancel(); throw new Error("Actual private email public operation rejected"); } return response.json() as Promise<T>; }
|
||||
const person = await local<{ user: { id: string } }>("/api/auth/get-session"), company = await local<{ id: string }>(`/api/companies/${input.companyId}`), agent = await local<{ id: string; companyId: string }>(`/api/agents/${input.agentId}`);
|
||||
if (!person.user?.id || company.id !== input.companyId || agent.companyId !== company.id) throw new Error("Email setup lacks actual fixture-person/company/agent scope");
|
||||
const [scope, inventory, inbox] = await Promise.all([provider.whoami(), provider.listInboxes(), provider.getInbox(input.inboxId)]);
|
||||
if (inbox.inbox_id !== input.inboxId || inventory.inboxes.length !== 1 || inventory.inboxes[0]?.inbox_id !== input.inboxId || scope.scope_type === "inbox" && scope.inbox_id !== input.inboxId) throw new Error("The selected declared disposable QA inbox is not the actual sole authorized inbox; no account or recipient may be guessed");
|
||||
const common = { processIsolation: true, testRunnerAttached: false, apiStepRecorder: false, operation: input.operation, companyId: input.companyId, agentId: input.agentId, actualSingleDedicatedInbox: true, personId: person.user.id } as const;
|
||||
if (input.operation === "setup") {
|
||||
const connection = await local<{ id: string; companyId: string }>(`/api/companies/${input.companyId}/email/connections`, { apiKey: input.credential, grantKind: "user", allAgents: false, agentIds: [input.agentId], idempotencyKey: randomUUID() }); publicSetupOperationCount++;
|
||||
if (connection.companyId !== input.companyId) throw new Error("Actual encrypted email credential connection scope mismatch");
|
||||
const endpoint = await local<EmailEndpointSummary>(`/api/companies/${input.companyId}/email/inboxes`, { assignedAgentId: input.agentId, credentialConnectionId: connection.id, inboxId: input.inboxId, receiveMode: "websocket", idempotencyKey: input.endpointId ?? randomUUID() }); publicSetupOperationCount++;
|
||||
const saved = (await local<EmailEndpointSummary[]>(`/api/companies/${input.companyId}/email/inboxes`)).find(row => row.id === endpoint.id);
|
||||
if (!saved || saved.companyId !== input.companyId || saved.address !== input.inboxId || saved.assignedAgentId !== input.agentId || saved.status !== "active") throw new Error("Actual email endpoint is not the authorized active self-addressed fixture");
|
||||
return { ...common, connectionId: connection.id, endpointId: saved.id, actualActiveEndpoint: true, publicSetupOperationCount, directProviderProbeCount };
|
||||
}
|
||||
if (!input.endpointId || !input.publicationId || !input.subject || !input.textSha256 || !input.after || !input.accepted || input.accepted.publicationId !== input.publicationId) throw new Error("Email inspection requires the exact actual original accepted send binding");
|
||||
const endpoint = (await local<EmailEndpointSummary[]>(`/api/companies/${input.companyId}/email/inboxes`)).find(row => row.id === input.endpointId);
|
||||
if (!endpoint || endpoint.address !== input.inboxId || endpoint.assignedAgentId !== input.agentId || endpoint.companyId !== input.companyId) throw new Error("Provider reconciliation is not bound to the actual original endpoint");
|
||||
const matchedSent: NonNullable<EmailPrivateReceipt["matchedSent"]> = []; const seen = new Set<string>(); let page: string | undefined, pages = 0;
|
||||
do {
|
||||
if (++pages > 3) throw new Error("Independent fixture duplicate check exceeds its finite dedicated inbox window");
|
||||
const listed = await provider.listMessages(input.inboxId, input.after, page);
|
||||
for (const row of listed.messages) {
|
||||
if (seen.has(row.message_id)) continue; seen.add(row.message_id);
|
||||
const message = await provider.getMessage(input.inboxId, row.message_id), pub = Object.entries(message.headers).find(([key]) => key.toLowerCase() === "x-paperclip-publication-id")?.[1];
|
||||
if (message.inbox_id !== input.inboxId || message.subject !== input.subject || createHash("sha256").update((message.text ?? "").replace(/\r\n/g, "\n").trim()).digest("hex") !== input.textSha256 || !message.labels.includes("sent") || !/^[a-f0-9-]{36}$/i.test(pub ?? "") || message.to.length !== 1 || (message.to[0]?.match(/(?:^|<)([^<>\s]+@[^<>\s]+)>?$/)?.[1]?.toLowerCase() !== input.recipient.toLowerCase())) continue;
|
||||
matchedSent.push({ messageId: message.message_id, threadId: message.thread_id, publicationId: pub!, contentMatches: true, accepted: true, recipientCopyObserved: message.labels.includes("received") });
|
||||
}
|
||||
page = listed.next_page_token;
|
||||
} while (page);
|
||||
const original = matchedSent.filter(row => row.messageId === input.accepted!.providerMessageId && row.threadId === input.accepted!.providerThreadId && row.publicationId === input.publicationId), originalMessageMatched = original.length === 1;
|
||||
if (!originalMessageMatched) throw new Error("The actual provider receipt does not independently match the original persisted send");
|
||||
if (input.operation === "inspect") return { ...common, publicSetupOperationCount, directProviderProbeCount, matchedSent, originalMessageMatched };
|
||||
let current = await local<EmailPublicationSummary>(`/api/companies/${input.companyId}/email/deliveries/${input.publicationId}`), resolution: EmailPrivateReceipt["resolution"] = "provider-reconciled";
|
||||
if (current.outcome === "uncertain") { current = await local<EmailPublicationSummary>(`/api/companies/${input.companyId}/email/deliveries/${input.publicationId}/resolve`, { outcome: "sent", providerMessageId: original[0]!.messageId }); publicSetupOperationCount++; resolution = "human-reconciled"; }
|
||||
if (!["sent", "delivered"].includes(current.outcome) || current.providerMessageId !== original[0]!.messageId) throw new Error("The actual public send has not reconciled to the verified original provider message");
|
||||
return { ...common, publicSetupOperationCount, directProviderProbeCount, matchedSent, originalMessageMatched, resolution, outcome: current.outcome as "sent" | "delivered" };
|
||||
}
|
||||
main().then(receipt => process.stdout.write(JSON.stringify(receipt))).catch(() => { process.stderr.write("Actual private email operation failed; credential/provider diagnostics withheld"); process.exitCode = 1; });
|
||||
@@ -0,0 +1,8 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { validateEmailPrivateReceipt } from "./runtime-email-private.js";
|
||||
const expected = { operation: "setup" as const, companyId: randomUUID(), agentId: randomUUID() };
|
||||
const valid = () => ({ ...expected, processIsolation: true, testRunnerAttached: false, apiStepRecorder: false, actualSingleDedicatedInbox: true, personId: "actual-fixture-person", connectionId: randomUUID(), endpointId: randomUUID(), actualActiveEndpoint: true, publicSetupOperationCount: 2, directProviderProbeCount: 3 });
|
||||
it("accepts the finite actual setup receipt without credential/account contents", () => expect(validateEmailPrivateReceipt(valid(), expected).actualActiveEndpoint).toBe(true));
|
||||
it("rejects another company, Test recorder, missing dedicated proof and secret fields", () => { for (const change of [{ companyId: randomUUID() }, { apiStepRecorder: true }, { actualSingleDedicatedInbox: false }, { token: "synthetic-private-value" }, { publicSetupOperationCount: -1 }]) expect(() => validateEmailPrivateReceipt({ ...valid(), ...change }, expected)).toThrow("closed receipt boundary"); });
|
||||
it("retains only independently verified matched sent-message identities and rejects fabricated free-form payloads", () => { const receipt = { ...valid(), operation: "inspect" as const, matchedSent: [{ messageId: "actual-message", threadId: "actual-thread", publicationId: randomUUID(), contentMatches: true, accepted: true, recipientCopyObserved: false }], originalMessageMatched: true }; expect(validateEmailPrivateReceipt(receipt, { ...expected, operation: "inspect" }).matchedSent).toHaveLength(1); expect(() => validateEmailPrivateReceipt({ ...receipt, matchedSent: [{ ...receipt.matchedSent[0], text: "unapproved raw contents" }] }, { ...expected, operation: "inspect" })).toThrow(); });
|
||||
@@ -0,0 +1,29 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { mkdtemp, chmod, writeFile, rm } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { Cookie } from "@playwright/test";
|
||||
import type { AcceptedEmailFault } from "./runtime-email-fault.js";
|
||||
export interface EmailPrivateInput { operation: "setup" | "inspect" | "resolve"; scenarioId: "recovery.unknown-email-outcome" | "chat-channels.outbound-email" | "chat-channels.reply-to-email"; companyId: string; agentId: string; baseURL: string; inboxId: string; recipient: string; dedicatedDisposableInbox: true; credential: string; cookies: Cookie[]; connectionId?: string; endpointId?: string; subject?: string; textSha256?: string; after?: string; publicationId?: string; accepted?: AcceptedEmailFault }
|
||||
export interface EmailPrivateReceipt { processIsolation: true; testRunnerAttached: false; apiStepRecorder: false; operation: EmailPrivateInput["operation"]; companyId: string; agentId: string; actualSingleDedicatedInbox: true; connectionId?: string; endpointId?: string; actualActiveEndpoint?: true; personId: string; publicSetupOperationCount: number; directProviderProbeCount: number; matchedSent?: Array<{ messageId: string; threadId: string; publicationId: string; contentMatches: true; accepted: true; recipientCopyObserved: boolean }>; originalMessageMatched?: boolean; resolution?: "human-reconciled" | "provider-reconciled"; outcome?: "sent" | "delivered" }
|
||||
export function validateEmailPrivateReceipt(value: unknown, expected: Pick<EmailPrivateInput, "operation" | "companyId" | "agentId">): EmailPrivateReceipt {
|
||||
const r = value as EmailPrivateReceipt, allowed = ["processIsolation", "testRunnerAttached", "apiStepRecorder", "operation", "companyId", "agentId", "actualSingleDedicatedInbox", "connectionId", "endpointId", "actualActiveEndpoint", "personId", "publicSetupOperationCount", "directProviderProbeCount", "matchedSent", "originalMessageMatched", "resolution", "outcome"];
|
||||
const uuid = (v: unknown) => typeof v === "string" && /^[a-f0-9-]{36}$/i.test(v), providerId = (v: unknown) => typeof v === "string" && v.length > 0 && v.length <= 998 && !/[\r\n]/.test(v);
|
||||
if (!r || Object.keys(r).some(key => !allowed.includes(key)) || r.processIsolation !== true || r.testRunnerAttached !== false || r.apiStepRecorder !== false || r.operation !== expected.operation || r.companyId !== expected.companyId || r.agentId !== expected.agentId || r.actualSingleDedicatedInbox !== true || !providerId(r.personId) || !Number.isSafeInteger(r.publicSetupOperationCount) || r.publicSetupOperationCount < 0 || !Number.isSafeInteger(r.directProviderProbeCount) || r.directProviderProbeCount < 0
|
||||
|| r.operation === "setup" && (!uuid(r.connectionId) || !uuid(r.endpointId) || r.actualActiveEndpoint !== true) || r.operation !== "setup" && (!Array.isArray(r.matchedSent) || r.matchedSent.length > 20 || r.matchedSent.some(row => Object.keys(row).sort().join() !== ["messageId", "threadId", "publicationId", "contentMatches", "accepted", "recipientCopyObserved"].sort().join() || !providerId(row.messageId) || !providerId(row.threadId) || !uuid(row.publicationId) || row.contentMatches !== true || row.accepted !== true || typeof row.recipientCopyObserved !== "boolean"))
|
||||
|| r.operation === "resolve" && (!["human-reconciled", "provider-reconciled"].includes(String(r.resolution)) || !["sent", "delivered"].includes(String(r.outcome)))) throw new Error("Private email operation exceeded its exact closed receipt boundary");
|
||||
return r;
|
||||
}
|
||||
export async function privateEmailOperation(input: EmailPrivateInput, privateSecrets: string[]): Promise<EmailPrivateReceipt> {
|
||||
if (!input.credential || input.credential.length < 12) throw new Error("Selected real AgentMail credential is required");
|
||||
privateSecrets.push(input.credential, ...input.cookies.map(row => row.value)); const directory = await mkdtemp(path.join(tmpdir(), "paperclip-email-private-")); await chmod(directory, 0o700);
|
||||
try {
|
||||
const filename = path.join(directory, "request.json"); await writeFile(filename, JSON.stringify(input), { mode: 0o600, flag: "wx" });
|
||||
const loader = fileURLToPath(new URL("../../../server/node_modules/tsx/dist/loader.mjs", import.meta.url)), worker = fileURLToPath(new URL("./runtime-email-private-worker.ts", import.meta.url));
|
||||
let output: string; try { output = (await promisify(execFile)(process.execPath, ["--import", loader, worker, filename], { timeout: 180000, maxBuffer: 32768, env: { PATH: process.env.PATH ?? "", NODE_ENV: "test", HOME: directory, TMPDIR: directory, DO_NOT_TRACK: "1" } })).stdout; } catch { throw new Error("Actual private AgentMail operation failed; credential-bearing request and provider diagnostics withheld"); }
|
||||
if (privateSecrets.some(secret => secret && [secret, encodeURIComponent(secret), Buffer.from(secret).toString("base64")].some(value => output.includes(value)))) throw new Error("Private email child exceeded its secret-free closed output; diagnostics withheld");
|
||||
let value: unknown; try { value = JSON.parse(output); } catch { throw new Error("Private email operation returned no closed receipt"); } return validateEmailPrivateReceipt(value, input);
|
||||
} finally { await rm(directory, { recursive: true, force: true }); }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { actualUncertainEmailReads, gradeUnknownEmail, uncertainEmailReply } from "./runtime-email.js";
|
||||
function valid() { return { companyId: "company", leadId: "ceo", scenarioId: "recovery.unknown-email-outcome", origin: "http://127.0.0.1:42123", reply: "", agents: [], tasks: [{ id: "email-task" }], activity: [], links: [], humanQueries: [], runs: [1, 2, 3].map(id => ({ id: String(id), agentId: "ceo", status: "succeeded" })), state: { actualOriginalUncertain: true, setupSendBound: true, faultOriginalBinding: true, outageActiveDuringCanonical: true, originalUncertaintyRecorded: true, canonicalReadBound: true, canonicalReply: "The original provider receipt is unavailable and the send remains uncertain. I will check the existing publication rather than resend a possibly delivered email.", noNewAgentPublication: true, directMatchedSentCount: 1, originalProviderReceiptMatched: true, outageReleased: true, reconciliationBound: true, resolutionActorBound: true, finalOutcome: "sent", confirmationBound: true, confirmationReply: "The original email was accepted and its saved publication is reconciled with the provider message receipt. There is no proof of recipient delivery, and no second send was made.", finalDirectMatchedSentCount: 1, sameOriginalRequestRetained: true, sameOriginalConversationRetained: true } }; }
|
||||
it("accepts real original uncertainty, independent one-send proof and authorized same-publication reconciliation", () => expect(gradeUnknownEmail(valid()).passed).toBe(true));
|
||||
it("accepts natural uncertainty and accepted-send paraphrases", () => { const o = valid(); o.state.canonicalReply = "It may already have reached the provider. I cannot confirm the outcome, so I checked the original send record and will avoid a duplicate while we reconcile the receipt."; o.state.confirmationReply = "The provider's original message is now verified as sent; the same conversation remains linked. Recipient delivery is still unconfirmed."; expect(gradeUnknownEmail(o).passed).toBe(true); });
|
||||
it("rejects another actual email, a new agent publication, or loss of original uncertainty", () => { const o = valid(); o.state.directMatchedSentCount = 2; expect(gradeUnknownEmail(o).passed).toBe(false); const p = valid(); p.state.noNewAgentPublication = false; expect(gradeUnknownEmail(p).passed).toBe(false); const q = valid(); q.state.originalUncertaintyRecorded = false; expect(gradeUnknownEmail(q).passed).toBe(false); });
|
||||
it("rejects automatic resend or certainty unsupported by the actual uncertain record", () => { expect(uncertainEmailReply("It is uncertain, but I will resend it now while we check the provider receipt.")).toBe(false); expect(uncertainEmailReply("The provider receipt is unavailable, but it definitely failed. I will resend it.")).toBe(false); });
|
||||
it("rejects claims of delivery from accepted-send evidence and wrong final reply phase", () => { const o = valid(); o.state.confirmationReply += " It was delivered."; expect(gradeUnknownEmail(o).passed).toBe(false); const p = valid(); p.state.confirmationBound = false; expect(gradeUnknownEmail(p).passed).toBe(false); });
|
||||
const expected = { companyId: "company", endpointId: "endpoint", publicationId: "publication", issueId: "email-task" };
|
||||
function actualRead(api = false) { const name = api ? "call_api" : "agentmail_delivery", input = api ? { operationId: "GET /api/companies/{companyId}/email/deliveries/{publicationId}", pathParams: { companyId: "company", publicationId: "publication" } } : { publicationId: "publication" }, receipt = { id: "publication", issueId: "email-task", outcome: "uncertain", providerMessageId: null, request: { endpointId: "endpoint" } }; return [{ seq: 1, eventType: "item.started", payload: { prpEvent: { payload: { item: { id: "read", type: "tool_use", name, input } } } } }, { seq: 2, eventType: "item.completed", payload: { prpEvent: { payload: { item: { id: "read", type: "tool_result", result: api ? { ok: true, status: 200, data: receipt } : receipt } } } } }, { seq: 3, eventType: "tool.execution.completed", payload: { prpEvent: { payload: { executionId: "read", name, status: "completed" } } } }]; }
|
||||
it("accepts actual completed connector and equivalent scoped API receipt reads", () => { expect(actualUncertainEmailReads(actualRead(), expected)).toHaveLength(1); expect(actualUncertainEmailReads(actualRead(true), expected)).toHaveLength(1); });
|
||||
it("rejects wrong receipt identity, uncompleted or wrong-tool results, and a write before inspection", () => { expect(actualUncertainEmailReads(actualRead(), { ...expected, publicationId: "other" })).toHaveLength(0); expect(actualUncertainEmailReads(actualRead().slice(0, 2), expected)).toHaveLength(0); const wrong = actualRead(); wrong[2]!.payload.prpEvent.payload = { executionId: "read", name: "different_tool", status: "completed" }; expect(actualUncertainEmailReads(wrong, expected)).toHaveLength(0); const early = { seq: 0, eventType: "item.started", payload: { prpEvent: { payload: { item: { id: "send", type: "tool_use", name: "agentmail_send", input: {} } } } } }; expect(actualUncertainEmailReads([early, ...actualRead()], expected)).toHaveLength(0); });
|
||||
|
||||
it("accepts uncertainty about delivery while rejecting appended affirmative delivery", () => { expect(uncertainEmailReply("I cannot confirm whether it was delivered; I checked the original provider receipt and will avoid another send.")).toBe(true); expect(uncertainEmailReply("The provider outcome is uncertain. It was delivered.")).toBe(false); });
|
||||
@@ -0,0 +1,127 @@
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { expect } from "@playwright/test";
|
||||
import type { EmailEndpointSummary, EmailPublicationSummary, EmailThreadSummary } from "../../../packages/shared/src/types/email.js";
|
||||
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
|
||||
import { collectRunEvents } from "../run-observations.js";
|
||||
import { pollUntil, RunnerApiHttpError } from "../api.js";
|
||||
import { sendChatMessage } from "../chat-flow.js";
|
||||
import { checks, type Entity } from "./internal-support.js";
|
||||
import { openStandingChat } from "./identity-support.js";
|
||||
import { selectedExternalBinding, externalRequirements } from "./external-fixtures.js";
|
||||
import { runtimeDefinition, runtimeObservation, observeRuntime } from "./runtime-support.js";
|
||||
import { privateEmailOperation, type EmailPrivateInput, type EmailPrivateReceipt } from "./runtime-email-private.js";
|
||||
import { armOwnedEmailResponseLoss, readOwnedEmailFault, releaseOwnedEmailResponseLoss, type EmailFaultRequest, type AcceptedEmailFault, type EmailFaultStatus } from "./runtime-email-fault.js";
|
||||
const hash = (value: string) => createHash("sha256").update(value).digest("hex");
|
||||
const record = (value: unknown): Record<string, unknown> => value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
||||
const privateFixtures = new WeakMap<ChatCatalogContext, EmailPrivateInput>();
|
||||
const faultRequests = new WeakMap<ChatCatalogContext, EmailFaultRequest>();
|
||||
export async function prepareOwnedEmailConnection(ctx: ChatCatalogContext) {
|
||||
const binding = selectedExternalBinding(ctx), p = binding.prerequisites;
|
||||
if (!ctx.input.privateSecrets || p.recipientAuthorization !== "self-addressed-only" || p.inboxId !== p.recipient || binding.resourceIds.length !== 1 || binding.resourceIds[0] !== p.inboxId || !/^PAPERCLIP_CHAT_EVAL_[A-Z0-9_]+$/.test(p.apiTokenEnv ?? "")) throw new Error("Setup blocker: require the explicitly authorized dedicated disposable QA inbox, exactly its self-addressed recipient and declared private API-token reference; metadata visibility alone is not send permission");
|
||||
const credential = ctx.input.privateInputs?.read(p.apiTokenEnv!); if (!credential) throw new Error("Selected dedicated AgentMail key reference is unavailable");
|
||||
const input: EmailPrivateInput = { operation: "setup", scenarioId: ctx.scenario.id as EmailPrivateInput["scenarioId"], companyId: ctx.input.fixtures.company.id, agentId: ctx.input.fixtures.agent.id, baseURL: ctx.input.api.baseURL, inboxId: p.inboxId!, recipient: p.recipient!, dedicatedDisposableInbox: true, credential, endpointId: randomUUID(), cookies: await ctx.input.page.context().cookies(ctx.input.api.baseURL) };
|
||||
privateFixtures.set(ctx, input);
|
||||
const receipt = await privateEmailOperation(input, ctx.input.privateSecrets); privateFixtures.set(ctx, { ...input, connectionId: receipt.connectionId, endpointId: receipt.endpointId });
|
||||
const endpoint = (await ctx.input.api.get<EmailEndpointSummary[]>(`/api/companies/${input.companyId}/email/inboxes`)).find(row => row.id === receipt.endpointId), session = await ctx.input.api.get<{ user: { id: string } }>("/api/auth/get-session");
|
||||
if (!endpoint || endpoint.companyId !== input.companyId || endpoint.assignedAgentId !== input.agentId || endpoint.address !== input.inboxId || endpoint.status !== "active" || receipt.personId !== session.user.id) throw new Error("The exact real QA inbox endpoint/fixture person failed independent public ownership checks");
|
||||
return { endpoint, emailConnectionId: receipt.connectionId, personId: receipt.personId, externalIdentity: { email: input.recipient }, providerProbes: [{ phase: "owned-email-setup", operation: "actual-private-account-and-inbox-setup", publicSetupOperationCount: receipt.publicSetupOperationCount, directProviderProbeCount: receipt.directProviderProbeCount, status: "active", usage: "unavailable", cost: "unavailable" }] };
|
||||
}
|
||||
async function providerOperation(ctx: ChatCatalogContext, operation: "inspect" | "resolve", state: Record<string, unknown>) {
|
||||
const input = privateFixtures.get(ctx); if (!input || !ctx.input.privateSecrets) throw new Error("Original private email fixture identity is unavailable");
|
||||
const root = process.env.PAPERCLIP_RUNNER_E2E_TEMP_ROOT; if (!root) throw new Error("Owned email fault root is unavailable");
|
||||
const accepted = await readOwnedEmailFault<AcceptedEmailFault>(root, "accepted-private.json"), request = state.faultRequest as EmailFaultRequest;
|
||||
if (accepted.executionId !== request.executionId || accepted.attemptId !== request.attemptId || accepted.nonce !== request.nonce || accepted.publicationId !== state.publicationId) throw new Error("The actual private accepted provider receipt does not bind this exact original action");
|
||||
return privateEmailOperation({ ...input, operation, subject: request.subject, textSha256: request.textSha256, after: new Date(request.createdAt - 1000).toISOString(), publicationId: accepted.publicationId, accepted }, ctx.input.privateSecrets);
|
||||
}
|
||||
export function assertsEmailDelivery(reply: string) {
|
||||
return reply.split(/[.!?;\n]+/).some(clause => /\b(?:was|has been|is|already|successfully|definitely) delivered\b/i.test(clause) && !/\b(?:whether|if|cannot|can[’']?t|not|no proof|unconfirmed|unknown|uncertain)\b/i.test(clause));
|
||||
}
|
||||
export function uncertainEmailReply(reply: string) {
|
||||
return /uncertain|unknown|unconfirmed|cannot confirm|can['’]?t confirm|not (?:yet )?confirmed|may (?:have|already)|possibly (?:sent|delivered)/i.test(reply) && /receipt|inspect|check|verif|reconcil|provider|original (?:send|message)/i.test(reply)
|
||||
&& !/\bI(?:['’]ll| will| have|['’]ve)\s+(?:now |already |successfully )?(?:resend|send (?:it |the (?:same )?email )?again|resent)\b|\b(?:definitely|certainly) (?:failed|not sent|delivered)\b/i.test(reply) && !assertsEmailDelivery(reply);
|
||||
}
|
||||
/** Trusted native tool correlation permits both the connector facade and its
|
||||
* equivalent company-scoped public API. Assistant prose never supplies receipt proof. */
|
||||
export function actualUncertainEmailReads(events: Array<{ seq?: number; eventType?: string; payload?: unknown }>, expected: { companyId: string; endpointId: string; publicationId: string; issueId: string }) {
|
||||
const calls = new Map<string, { name: string; input: Record<string, unknown>; seq: number }>(), results = new Map<string, unknown>(), completed = new Map<string, string>();
|
||||
for (const event of events) { const payload = record(record(record(event.payload).prpEvent).payload), item = record(payload.item); if (event.eventType === "item.started" && item.type === "tool_use" && typeof item.id === "string" && typeof item.name === "string") calls.set(item.id, { name: item.name, input: record(item.input), seq: event.seq ?? -1 }); if (event.eventType === "item.completed" && item.type === "tool_result" && typeof item.id === "string" && (!item.tool_use_id || item.tool_use_id === item.id)) results.set(item.id, item.result); if (event.eventType === "tool.execution.completed" && payload.status === "completed" && typeof payload.executionId === "string") completed.set(payload.executionId, String(payload.name)); }
|
||||
return [...calls].flatMap(([id, call]) => {
|
||||
if (completed.get(id) !== call.name || call.seq < 0) return [];
|
||||
const result = record(results.get(id)), api = call.name === "call_api", data = api ? record(result.data) : result, params = record(call.input.pathParams);
|
||||
const target = call.name === "agentmail_delivery" && call.input.publicationId === expected.publicationId || call.name === "agentmail_read_thread" || api && params.companyId === expected.companyId && (call.input.operationId === "GET /api/companies/{companyId}/email/deliveries/{publicationId}" && params.publicationId === expected.publicationId || call.input.operationId === "GET /api/companies/{companyId}/email/tasks/{issueId}" && params.issueId === expected.issueId);
|
||||
if (!target || api && (result.ok !== true || result.status !== 200)) return [];
|
||||
const publications = Array.isArray(data.publications) ? data.publications.map(record) : [data], publication = publications.find(row => row.id === expected.publicationId && row.issueId === expected.issueId && row.outcome === "uncertain" && row.providerMessageId === null && record(row.request).endpointId === expected.endpointId);
|
||||
const earlierWrite = [...calls.values()].some(other => other.seq >= 0 && other.seq < call.seq && (other.name === "agentmail_send" || other.name === "call_api" && other.input.operationId === "POST /api/companies/{companyId}/email/send"));
|
||||
return publication && !earlierWrite ? [{ executionId: id, actualPublicationId: expected.publicationId, uncertain: true }] : [];
|
||||
});
|
||||
}
|
||||
export function gradeUnknownEmail(value: unknown) {
|
||||
const c = checks(), o = runtimeObservation(value); c.add("real-authorized-send-with-genuine-lost-response", !!o, "One actual authorized self-addressed send, real upstream acceptance and production uncertainty must be observed."); if (!o) return c.result(); const s = o.state;
|
||||
c.add("original-uncertainty-and-exact-owned-outage", s.actualOriginalUncertain === true && s.setupSendBound === true && s.faultOriginalBinding === true && s.outageActiveDuringCanonical === true && s.originalUncertaintyRecorded === true, "The original genuine native send belongs to the exact task/inbox; production has no reliable provider receipt during the bounded declared outage.");
|
||||
c.add("canonical-inspects-real-receipts-and-does-not-duplicate", s.canonicalReadBound === true && uncertainEmailReply(String(s.canonicalReply)) && s.noNewAgentPublication === true && s.directMatchedSentCount === 1, "The canonical run inspects authoritative original task/publication receipts, truthfully reports uncertainty and produces no new agent publication or physical duplicate; same-key infrastructure retries are distinguished.");
|
||||
c.add("actual-provider-reconciliation-through-supported-path", s.originalProviderReceiptMatched === true && s.outageReleased === true && s.reconciliationBound === true && s.resolutionActorBound === true && ["sent", "delivered"].includes(String(s.finalOutcome)), "After independent real provider inspection, the exact original send reconciles through ordinary provider intake or authorized human resolution without resending.");
|
||||
const report = String(s.confirmationReply ?? "");
|
||||
c.add("phase-bound-real-outcome-with-no-false-delivery", s.confirmationBound === true && /sent|accepted|provider.*(?:receipt|message)|reconcil/i.test(report) && (s.finalOutcome === "delivered" || !assertsEmailDelivery(report)) && s.finalDirectMatchedSentCount === 1, "The subsequent actual reply reports the verified send receipt and distinguishes acceptance from unproven recipient delivery; a final independent duplicate check still finds one sent message.");
|
||||
c.add("finite-real-source-turns-and-original-delivery-retained", o.runs.length === 3 && o.runs.every(row => row.status === "succeeded" && row.agentId === o.leadId) && s.sameOriginalRequestRetained === true && s.sameOriginalConversationRetained === true && o.tasks.length === 1, "One genuine setup human/native send plus canonical and factual follow-up are the only native work; the original request/conversation and saved uncertainty evidence remain inspectable.");
|
||||
return c.result();
|
||||
}
|
||||
const requirement = externalRequirements["recovery.unknown-email-outcome"]!;
|
||||
async function setupUnknownEmail(ctx: ChatCatalogContext) {
|
||||
const root = process.env.PAPERCLIP_RUNNER_E2E_TEMP_ROOT, attemptId = process.env.PAPERCLIP_E2E_ACCOUNTING_ATTEMPT_ID;
|
||||
if (!root || !attemptId) throw new Error("Selected email lost-response fixture requires its actual owned server hook and attempt identity");
|
||||
const installed = await readOwnedEmailFault<{ schema: string; executionId: string; attemptId: string; pid: number }>(root, "installed.json");
|
||||
if (installed.schema !== "paperclip.email-response-loss-installed.v1" || installed.executionId !== ctx.input.execution.id || installed.attemptId !== attemptId || !Number.isSafeInteger(installed.pid)) throw new Error("Exact selected email transport-loss hook is not installed; no provider setup may begin");
|
||||
const source = await openStandingChat(ctx), connection = await prepareOwnedEmailConnection(ctx);
|
||||
const nonce = randomUUID(), subject = `Authorized QA message ${nonce}`, text = `This is one authorized self-addressed delivery check ${nonce}. Please retain this exact notice.`;
|
||||
const request: EmailFaultRequest = { schema: "paperclip.email-response-loss.v1", executionId: ctx.input.execution.id, attemptId, companyId: ctx.input.fixtures.company.id, endpointId: connection.endpoint.id, agentId: ctx.input.fixtures.agent.id, sourceIssueId: source.id, inboxId: connection.externalIdentity.email, recipient: connection.externalIdentity.email, subject, textSha256: hash(text), nonce, createdAt: Date.now(), expiresAt: Date.now() + 480000, dedicatedDisposableInbox: true };
|
||||
await armOwnedEmailResponseLoss(root, request); faultRequests.set(ctx, request);
|
||||
for (const scope of [{ scopeType: "company", scopeId: request.companyId }, { scopeType: "agent", scopeId: request.agentId }]) await ctx.input.api.post(`/api/companies/${request.companyId}/budgets/policies`, { ...scope, amount: 500, hardStopEnabled: true, isActive: true, unpricedUsagePolicy: "allow" });
|
||||
await ctx.input.api.post(`/api/companies/${request.companyId}/tools/policies`, { name: `Exact authorized self-addressed QA send ${nonce}`, policyType: "allow", priority: 10, selectors: { connectionId: connection.endpoint.connectionId, toolNames: ["email.send"] }, conditions: { arguments: { fieldEquals: { to: [request.recipient], subject, text } } }, config: {} });
|
||||
await ctx.input.page.goto(`/${ctx.input.fixtures.company.issuePrefix}/issues/${source.id}`, { waitUntil: "domcontentloaded" });
|
||||
const initial = `Please send one email from my connected inbox to ${request.recipient}, with subject exactly ${JSON.stringify(subject)} and body exactly ${JSON.stringify(text)}. This is my authorized self-addressed QA notice. Use the actual task-bound email connection in this conversation and retain the real send receipt. Do not create another task yourself or send any other message.`;
|
||||
await sendChatMessage(ctx.input.page, initial);
|
||||
const ready = await pollUntil({ label: "Actual initial native email send loses its real accepted provider response", deadlineAt: Date.now() + 240000,
|
||||
load: async () => { await ctx.refreshIssue(); const accepted = await readOwnedEmailFault<AcceptedEmailFault>(root, "accepted-private.json").catch(error => { if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined; throw error; }); return { source: ctx.issue(), runs: await ctx.allRuns(), accepted, delivery: accepted ? await ctx.input.api.get<EmailPublicationSummary>(`/api/companies/${request.companyId}/email/deliveries/${accepted.publicationId}`) : undefined }; },
|
||||
accept: v => v.source?.conversationState === "waiting" && v.runs.length === 1 && v.runs[0]?.status === "succeeded" && v.delivery?.outcome === "uncertain" && v.delivery.providerMessageId === null,
|
||||
reject: v => v.runs.length > 1 || v.runs.some(row => ["failed", "cancelled", "timed_out"].includes(row.status)) ? "Initial authorized setup email admitted extra or failed native work" : undefined });
|
||||
const delivery = ready.delivery!, accepted = ready.accepted!, thread = await ctx.input.api.get<EmailThreadSummary>(`/api/companies/${request.companyId}/email/tasks/${delivery.issueId}`);
|
||||
const comments = await ctx.input.api.get<Entity[]>(`/api/issues/${source.id}/comments?order=asc`), human = comments.filter(row => !row.authorAgentId), run = ready.runs[0]!;
|
||||
const setupSendBound = accepted.nonce === request.nonce && accepted.executionId === request.executionId && accepted.attemptId === attemptId && accepted.upstreamStatus >= 200 && accepted.upstreamStatus < 300 && delivery.request?.endpointId === request.endpointId && delivery.request.subject === subject && hash(delivery.request.text) === request.textSha256 && thread.endpoint.id === request.endpointId && human.length === 1 && human[0]?.body === initial && Array.isArray(run.contextSnapshot?.wakeCommentIds) && run.contextSnapshot.wakeCommentIds.includes(human[0]!.id);
|
||||
await ctx.input.evidence("runtime-original-email-uncertainty.json", { publicationId: delivery.id, issueId: delivery.issueId, conversationId: delivery.conversationId, outcome: delivery.outcome, providerMessageId: delivery.providerMessageId, requestSha256: hash(JSON.stringify(delivery.request)), initialHumanCommentId: human[0]?.id, initialNativeRunId: run.id });
|
||||
return { ...connection, sourceId: source.id, faultRequest: request, setupRunId: run.id, originalDelivery: delivery, publicationId: delivery.id, deliveryIssueId: delivery.issueId, originalRequestSha256: hash(JSON.stringify(delivery.request)), originalConversationId: delivery.conversationId, setupSendBound, actualOriginalUncertain: true, originalUncertaintyRecorded: true, faultOriginalBinding: setupSendBound };
|
||||
}
|
||||
export async function cleanupOwnedEmailConnection(ctx: ChatCatalogContext) {
|
||||
try { const request = faultRequests.get(ctx); if (request) { await releaseOwnedEmailResponseLoss(process.env.PAPERCLIP_RUNNER_E2E_TEMP_ROOT!, request); faultRequests.delete(ctx); } }
|
||||
finally { const input = privateFixtures.get(ctx); try { if (input?.endpointId) { let receipt: EmailEndpointSummary | undefined; try { receipt = await ctx.input.api.post<EmailEndpointSummary>(`/api/email/inboxes/${input.endpointId}/control`, { action: "remove" }); } catch (error) { if (!(error instanceof RunnerApiHttpError) || error.status !== 404) throw error; } if (receipt?.lastError) throw new Error("Owned email provider registration cleanup is unconfirmed; actual operator verification is required"); } } finally { privateFixtures.delete(ctx); } }
|
||||
}
|
||||
export const runtimeEmailRecoveryCases = [runtimeDefinition("recovery.unknown-email-outcome", async ctx => { try { return await setupUnknownEmail(ctx); } catch (error) { await cleanupOwnedEmailConnection(ctx); throw error; } }, gradeUnknownEmail, {
|
||||
authentication: "human-session", setupHumanMessageCount: 1, expectedHumanMessageCount: 3, expectedRunCount: 3, turnTimeoutMs: 120000, attemptTimeoutMs: 900000,
|
||||
externalInput: { provider: requirement.provider, fields: requirement.fields },
|
||||
coverageGaps: ["Finite selected test-server lost-response hook is authored area-locally but not yet wired into server-entry; integration waits for coordinator's immutable initial batch handoff."],
|
||||
setupPolicy: { readiness: "preserve-scenario", invariantCheckIds: ["real-original-email-uncertain-with-no-public-provider-receipt"] },
|
||||
sourceFiles: ["chat-catalog-domains/runtime-email.ts", "chat-catalog-domains/runtime-email-fault.ts", "chat-catalog-domains/runtime-email-private.ts", "chat-catalog-domains/runtime-email-private-worker.ts", "../../server/src/services/email-channels.ts", "../../server/src/services/agentmail-api.ts", "../../server/src/services/connectors/agentmail.ts", "server-entry.ts", "chat-scenarios/recovery.yaml"],
|
||||
async verifySetup(ctx, s) { const delivery = await ctx.input.api.get<EmailPublicationSummary>(`/api/companies/${ctx.input.fixtures.company.id}/email/deliveries/${s.publicationId}`), root = process.env.PAPERCLIP_RUNNER_E2E_TEMP_ROOT!; const status = await readOwnedEmailFault<EmailFaultStatus>(root, "status.json"), request = s.faultRequest as EmailFaultRequest; const c = checks(); c.add("real-original-email-uncertain-with-no-public-provider-receipt", delivery.outcome === "uncertain" && delivery.providerMessageId === null && status.realSendsForwarded === 1 && status.nonce === request.nonce && !status.released && !status.expired && s.setupSendBound === true, "One actual accepted original send persists as uncertain without a reliable public receipt; exact declared outage remains active before unchanged canonical query."); return c.result(); },
|
||||
async act(ctx, s) {
|
||||
const api = ctx.input.api, companyId = ctx.input.fixtures.company.id, publicationId = String(s.publicationId), request = s.faultRequest as EmailFaultRequest, root = process.env.PAPERCLIP_RUNNER_E2E_TEMP_ROOT!;
|
||||
const first = await pollUntil({ label: "Canonical uncertainty inspection without an automatic duplicate email", deadlineAt: Date.now() + 180000, load: async () => { await ctx.refreshIssue(); return { source: ctx.issue(), runs: await ctx.allRuns() }; }, accept: v => v.source?.conversationState === "waiting" && v.runs.length === 2 && v.runs.every(row => row.status === "succeeded") });
|
||||
const canonical = first.runs.find(row => row.id !== s.setupRunId)!, comments = await api.get<Entity[]>(`/api/issues/${ctx.issue()!.id}/comments?order=asc`); s.canonicalReply = comments.filter(row => row.createdByRunId === canonical.id && row.authorAgentId === ctx.input.fixtures.agent.id).map(row => row.body).join("\n");
|
||||
const events = await collectRunEvents<{ seq?: number; eventType?: string; payload?: unknown }>((afterSeq, limit) => api.get(`/api/heartbeat-runs/${canonical.id}/events?afterSeq=${afterSeq}&limit=${limit}`));
|
||||
s.canonicalReadBound = actualUncertainEmailReads(events, { companyId, endpointId: request.endpointId, publicationId, issueId: String(s.deliveryIssueId) }).length > 0;
|
||||
const outage = await readOwnedEmailFault<EmailFaultStatus>(root, "status.json"), original = await api.get<EmailPublicationSummary>(`/api/companies/${companyId}/email/deliveries/${publicationId}`); s.outageActiveDuringCanonical = !outage.released && !outage.expired && original.outcome === "uncertain" && original.providerMessageId === null;
|
||||
const direct = await providerOperation(ctx, "inspect", s); s.directMatchedSentCount = direct.matchedSent?.length; s.originalProviderReceiptMatched = direct.originalMessageMatched === true;
|
||||
const observed = await observeRuntime(ctx, s); s.noNewAgentPublication = observed.tasks.length === 1 && observed.tasks[0]?.id === s.deliveryIssueId && direct.matchedSent?.every(row => row.publicationId === publicationId) === true;
|
||||
await releaseOwnedEmailResponseLoss(root, request); s.outageReleased = true;
|
||||
const resolved = await providerOperation(ctx, "resolve", s); s.reconciliationBound = resolved.originalMessageMatched === true && resolved.matchedSent?.length === 1; s.finalOutcome = resolved.outcome; s.resolutionMode = resolved.resolution;
|
||||
const activities = await api.get<Entity[]>(`/api/companies/${companyId}/activity?limit=1000`); s.resolutionActorBound = resolved.resolution === "provider-reconciled" || activities.some(row => row.action === "email.resolved" && row.actorType === "user" && row.actorId === s.personId && row.details?.publicationId === publicationId);
|
||||
const after = await api.get<EmailPublicationSummary>(`/api/companies/${companyId}/email/deliveries/${publicationId}`); s.sameOriginalRequestRetained = hash(JSON.stringify(after.request)) === s.originalRequestSha256; s.sameOriginalConversationRetained = after.conversationId === s.originalConversationId && after.id === publicationId;
|
||||
const before = (await ctx.allRuns()).map(row => row.id), followup = "The provider's actual receipt for that original email is now available. Please inspect the saved publication and report its real outcome, whether another email was sent, and whether recipient delivery is actually proven. Do not send or retry any email.";
|
||||
await sendChatMessage(ctx.input.page, followup);
|
||||
const final = await pollUntil({ label: "Actual reconciled email outcome reported in its own phase", deadlineAt: Date.now() + 180000, load: async () => { await ctx.refreshIssue(); return { source: ctx.issue(), runs: await ctx.allRuns() }; }, accept: v => v.source?.conversationState === "waiting" && v.runs.length === 3 && v.runs.every(row => row.status === "succeeded") });
|
||||
const replyRun = final.runs.find(row => !before.includes(row.id))!, finalComments = await api.get<Entity[]>(`/api/issues/${ctx.issue()!.id}/comments?order=asc`), human = finalComments.filter(row => !row.authorAgentId && row.body === followup); s.confirmationBound = human.length === 1 && Array.isArray(replyRun.contextSnapshot?.wakeCommentIds) && replyRun.contextSnapshot.wakeCommentIds.length === 1 && replyRun.contextSnapshot.wakeCommentIds[0] === human[0]!.id; s.confirmationReply = finalComments.filter(row => row.createdByRunId === replyRun.id && row.authorAgentId === ctx.input.fixtures.agent.id).map(row => row.body).join("\n");
|
||||
const last = await providerOperation(ctx, "inspect", s); s.finalDirectMatchedSentCount = last.matchedSent?.length;
|
||||
(s.providerProbes as unknown[]).push(...[direct, resolved, last].map(row => ({ phase: row.operation, operation: "independent-real-provider-receipts", directProviderProbeCount: row.directProviderProbeCount, publicSetupOperationCount: row.publicSetupOperationCount, status: "verified", usage: "unavailable", cost: "unavailable" })));
|
||||
},
|
||||
observe: observeRuntime,
|
||||
async verifyResult(ctx, s) { const source = ctx.input.page.url(); await ctx.input.page.goto(`/${ctx.input.fixtures.company.issuePrefix}/issues/${s.deliveryIssueId}`, { waitUntil: "domcontentloaded" }); await expect(ctx.input.page.getByText((s.faultRequest as EmailFaultRequest).subject, { exact: true }).first()).toBeVisible(); await ctx.input.capture("original-email-reconciled", "The original task-bound email thread and actual provider outcome", "runtime-email-reconciled.png"); await ctx.input.page.goto(source, { waitUntil: "domcontentloaded" }); },
|
||||
async cleanup(ctx) { await cleanupOwnedEmailConnection(ctx); },
|
||||
})];
|
||||
Reference in new issue
Block a user