diff --git a/tests/runner-e2e/chat-catalog-domains/external-personal-secret-receipt.test.ts b/tests/runner-e2e/chat-catalog-domains/external-personal-secret-receipt.test.ts new file mode 100644 index 00000000000..02ba333dad6 --- /dev/null +++ b/tests/runner-e2e/chat-catalog-domains/external-personal-secret-receipt.test.ts @@ -0,0 +1,88 @@ +import { execFile } from "node:child_process"; +import { mkdtemp, realpath, rm } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { promisify } from "node:util"; +import { afterEach, expect, it, vi } from "vitest"; +import type { ChatCatalogContext } from "../chat-catalog-contract.js"; +import { secretTestEndpoint } from "./external-secret-endpoint.js"; +import { personalSecretWorkerReceipt } from "./external-personal-secret.js"; + +const execute = promisify(execFile); +const roots: string[] = []; +afterEach(async () => { vi.unstubAllEnvs(); await Promise.all(roots.splice(0).map(root => rm(root, { recursive: true, force: true }))); }); +async function context() { + const root = await realpath(await mkdtemp(path.join(os.tmpdir(), "personal-receipt-"))); roots.push(root); + vi.stubEnv("PAPERCLIP_RUNNER_E2E_TEMP_ROOT", root); vi.stubEnv("PAPERCLIP_HOME", path.join(root, "home")); vi.stubEnv("PAPERCLIP_INSTANCE_ID", "receipt-control"); + return { scenario: { id: "secrets.personal-secret" }, input: { nonce: "receipt-control", execution: { environment: { id: "local" } }, fixtures: { company: { id: "company" } } } } as unknown as ChatCatalogContext; +} +it("mints a sanitized personal receipt only after actual key-authenticated HTTP success without legacy identity env", async () => { + const ctx = await context(), credential = "synthetic-personal-value"; + const endpoint = await secretTestEndpoint(ctx, "morgan", credential, "PUBLISHING_API_KEY", undefined, { personalWorkerReceipt: true }); + try { + const startedAt = new Date().toISOString(); + const result = await execute(process.execPath, [endpoint.scriptPath], { env: { PATH: process.env.PATH, PUBLISHING_API_KEY: credential } }); + const output = JSON.parse(result.stdout); + expect(output).toMatchObject({ ok: true, status: 200, check: "publishing endpoint", receiptId: expect.stringMatching(/^[a-f0-9]{48}$/) }); + expect(result.stdout).not.toContain(credential); + const f = delivery(); f.run.startedAt = startedAt; f.run.finishedAt = new Date().toISOString(); + Object.assign(f.evidence.calls[0]!, endpoint.calls[0]!); + f.evidence.events[0]!.createdAt = f.run.finishedAt; + f.evidence.events[0]!.payload.prpEvent.payload.output = result.stdout; + expect(personalSecretWorkerReceipt(f.run, f.evidence, f.expected)).toMatchObject({ receiptId: output.receiptId }); + for (const [url, method, key] of [[endpoint.url, "GET", "wrong"], [endpoint.url + "/wrong", "GET", credential], [endpoint.url, "POST", credential]]) { + const response = await fetch(url!, { method, headers: { authorization: `Bearer ${key}` } }); + expect(response.status).toBe(403); expect((await response.json()).receiptId).toBeUndefined(); + } + const repeated = await fetch(endpoint.url, { headers: { authorization: `Bearer ${credential}` } }); + const second = await repeated.json(); expect(second.receiptId).not.toBe(output.receiptId); + expect(endpoint.calls.filter(call => call.receiptId).map(call => call.receiptId)).toEqual([output.receiptId, second.receiptId]); + } finally { await endpoint.close(); } +}); +it("preserves the legacy header contract when the personal receipt mode is absent", async () => { + const endpoint = await secretTestEndpoint(await context(), "morgan", "synthetic-personal-value", "PUBLISHING_API_KEY"); + try { + const response = await fetch(endpoint.url, { headers: { authorization: "Bearer synthetic-personal-value", "x-paperclip-agent": "morgan", "x-paperclip-run": "11111111-1111-4111-8111-111111111111" } }); + expect(response.status).toBe(200); expect(await response.json()).toEqual({ ok: true, check: "publishing endpoint", status: 200 }); + } finally { await endpoint.close(); } +}); + +function delivery() { + const expected = { companyId: "company", agentId: "morgan", taskId: "task", userId: "person" }; + const run = { id: "worker", companyId: "company", agentId: "morgan", responsibleUserId: "person", runtimeMode: "native", status: "succeeded", + contextSnapshot: { issueId: "task" }, startedAt: "2026-11-04T00:00:00.000Z", finishedAt: "2026-11-04T00:00:10.000Z" }; + const output = JSON.stringify({ ok: true, status: 200, check: "publishing endpoint", receiptId: "a".repeat(48) }); + const evidence = { task: { id: "task", companyId: "company", assigneeAgentId: "morgan", responsibleUserId: "person", status: "done" }, + calls: [{ authorized: true, agentId: "", runId: "", method: "GET", pathname: "/publishing-check", status: 200, + createdAt: "2026-11-04T00:00:05.000Z", receiptId: "a".repeat(48) }], + events: [{ runId: "worker", companyId: "company", agentId: "morgan", seq: 1, eventType: "tool.execution.completed", + createdAt: "2026-11-04T00:00:06.000Z", payload: { prpEvent: { runId: "worker", eventType: "tool.execution.completed", + payload: { name: "bash", status: "completed", exitCode: 0, outputTruncated: false, output } } } }] }; + return { expected, run, evidence }; +} +it("binds a receipt to successful native output and allows repeated verification of the same bound receipt", () => { + const f = delivery(); expect(personalSecretWorkerReceipt(f.run, f.evidence, f.expected)).toMatchObject({ runId: "worker", agentId: "morgan", receiptId: "a".repeat(48) }); + f.evidence.events.push({ ...f.evidence.events[0]!, seq: 2 }); + expect(personalSecretWorkerReceipt(f.run, f.evidence, f.expected)).toBeDefined(); +}); +it.each(["wrong key", "wrong path", "wrong status", "missing receipt", "forged receipt", "replayed receipt", "foreign run", "foreign agent", "foreign task", "foreign human", "failed output", "non-tool output", "input-only receipt"])("rejects %s as personal worker proof", fault => { + const f = delivery(), call = f.evidence.calls[0]!, event = f.evidence.events[0]!, tool = event.payload.prpEvent.payload; + if (fault === "wrong key") call.authorized = false; + if (fault === "wrong path") call.pathname = "/wrong"; + if (fault === "wrong status") call.status = 403; + if (fault === "missing receipt") call.receiptId = ""; + if (fault === "forged receipt") call.receiptId = "b".repeat(48); + if (fault === "replayed receipt") call.createdAt = "2026-11-03T23:59:59.000Z"; + if (fault === "foreign run") event.runId = "foreign"; + if (fault === "foreign agent") event.agentId = "foreign"; + if (fault === "foreign task") f.evidence.task.id = "foreign"; + if (fault === "foreign human") f.run.responsibleUserId = "foreign"; + if (fault === "failed output") tool.exitCode = 1; + if (fault === "non-tool output") event.eventType = "item.completed"; + if (fault === "input-only receipt") tool.output = ""; + expect(personalSecretWorkerReceipt(f.run, f.evidence, f.expected)).toBeUndefined(); +}); +it("does not enable the personal receipt option for other credential cases", async () => { + const ctx = await context(); ctx.scenario.id = "secrets.least-scope-delivery"; + await expect(secretTestEndpoint(ctx, "morgan", "synthetic-personal-value", "PUBLISHING_API_KEY", undefined, { personalWorkerReceipt: true })).rejects.toThrow("dedicated personal-secret endpoint"); +}); diff --git a/tests/runner-e2e/chat-catalog-domains/external-personal-secret.test.ts b/tests/runner-e2e/chat-catalog-domains/external-personal-secret.test.ts index 52edcff7630..4c7b6ad7298 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-personal-secret.test.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-personal-secret.test.ts @@ -41,7 +41,8 @@ function valid() { runs: [ { id: "initial", status: "succeeded" }, { - id: "worker", + id: "worker", companyId: "company", responsibleUserId: "person", + startedAt: "2026-11-04T00:00:00.000Z", finishedAt: "2026-11-04T00:00:10.000Z", status: "succeeded", agentId: "morgan", runtimeMode: "native", @@ -76,6 +77,15 @@ function valid() { taskResponsibleUserId: "person", taskAfterStatus: "done", workerReceiptBound: true, + workerReceiptEvidence: { + task: { id: "task", companyId: "company", assigneeAgentId: "morgan", responsibleUserId: "person", status: "done" }, + calls: [{ authorized: true, agentId: "", runId: "", method: "GET", pathname: "/publishing-check", status: 200, + createdAt: "2026-11-04T00:00:05.000Z", receiptId: "a".repeat(48) }], + events: [{ runId: "worker", companyId: "company", agentId: "morgan", seq: 1, eventType: "tool.execution.completed", + createdAt: "2026-11-04T00:00:06.000Z", payload: { prpEvent: { runId: "worker", eventType: "tool.execution.completed", + payload: { name: "bash", status: "completed", exitCode: 0, outputTruncated: false, + output: JSON.stringify({ ok: true, status: 200, check: "publishing endpoint", receiptId: "a".repeat(48) }) } } } }], + }, workerRunId: "worker", morganId: "morgan", taskId: "task", @@ -214,3 +224,10 @@ it("accepts secure-form guidance only when its excluded chat destination stays e expect(personalSecretGuidance(`Enter your personal publishing credential in the secure form, not in chat ${conjunction} here.`)).toBe(false); } }); + +it("rejects missing or mismatched endpoint receipts despite a copied successful-worker flag", () => { + const missing = valid(); missing.state.workerReceiptEvidence.calls = []; + expect(gradePersonalSecret(missing).passed).toBe(false); + const mismatch = valid(); mismatch.state.workerReceiptEvidence.calls[0]!.receiptId = "b".repeat(48); + expect(gradePersonalSecret(mismatch).passed).toBe(false); +}); diff --git a/tests/runner-e2e/chat-catalog-domains/external-personal-secret.ts b/tests/runner-e2e/chat-catalog-domains/external-personal-secret.ts index e8cbd73fc01..cdbfdbdd9a3 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-personal-secret.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-personal-secret.ts @@ -10,6 +10,7 @@ import type { import { pollUntil } from "../api.js"; import { sendChatMessage } from "../chat-flow.js"; import { collectRunEvents } from "../run-observations.js"; +import type { ChatRun } from "../chat-flow.js"; import { identityWorker, openStandingChat, @@ -159,6 +160,44 @@ export function personalSecretConfirmation(reply: string) { ) ); } +export type PersonalSecretWorkerRun = ChatRun & { responsibleUserId?: string | null; finishedAt?: string | null }; +export interface PersonalSecretWorkerEvent { + companyId: string; runId: string; agentId: string; seq: number; eventType: string; createdAt: string; + payload: Record | null; +} +export interface PersonalSecretWorkerEvidence { + task: Entity & { responsibleUserId?: string | null }; + calls: Awaited>["calls"]; + events: PersonalSecretWorkerEvent[]; +} +export function personalSecretWorkerReceipt(run: PersonalSecretWorkerRun | undefined, evidence: PersonalSecretWorkerEvidence | undefined, + expected: { companyId: string; agentId: string; taskId: string; userId: string }) { + const task = evidence?.task; + if (!run || !task || !Array.isArray(evidence?.calls) || !Array.isArray(evidence.events) + || run.companyId !== expected.companyId || run.agentId !== expected.agentId || run.responsibleUserId !== expected.userId + || run.runtimeMode !== "native" || run.status !== "succeeded" || run.contextSnapshot?.issueId !== expected.taskId + || (run.nativeIssueId && run.nativeIssueId !== expected.taskId) || task.id !== expected.taskId || task.companyId !== expected.companyId + || task.assigneeAgentId !== expected.agentId || task.responsibleUserId !== expected.userId || task.status !== "done" || evidence.calls.length !== 1) return; + const call = evidence.calls[0]!, started = Date.parse(String(run.startedAt)), finished = Date.parse(String(run.finishedAt)), requested = Date.parse(String(call.createdAt)); + if (!call.authorized || call.status !== 200 || call.method !== "GET" || call.pathname !== "/publishing-check" + || !/^[a-f0-9]{48}$/.test(call.receiptId ?? "") || ![started, finished, requested].every(Number.isFinite) + || requested < started || requested > finished) return; + const matching = evidence.events.filter(event => { + if (event.companyId !== expected.companyId || event.agentId !== expected.agentId || event.runId !== run.id + || event.eventType !== "tool.execution.completed" || !Number.isSafeInteger(event.seq) || event.seq <= 0) return false; + const prp = event.payload?.prpEvent as { runId?: string; eventType?: string; payload?: { name?: string; status?: string; exitCode?: number; outputTruncated?: boolean; output?: unknown } } | undefined; + const tool = prp?.payload, observed = Date.parse(String(event.createdAt)); + if (prp?.runId !== run.id || prp.eventType !== event.eventType || tool?.name !== "bash" || tool.status !== "completed" + || tool.exitCode !== 0 || tool.outputTruncated !== false || typeof tool.output !== "string" + || !Number.isFinite(observed) || observed < requested || observed > finished) return false; + try { + const output = JSON.parse(tool.output) as { ok?: boolean; status?: number; check?: string; receiptId?: string } | null; + return output?.ok === true && output.status === 200 && output.check === "publishing endpoint" && output.receiptId === call.receiptId; + } catch { return false; } + }); + if (!matching.length) return; + return { receiptId: call.receiptId!, runId: run.id, agentId: run.agentId, event: matching[0]! }; +} export function gradePersonalSecret(value: unknown) { const c = checks(), o = externalObservation(value); @@ -194,6 +233,8 @@ export function gradePersonalSecret(value: unknown) { "Another person's real stored value does not satisfy the current person's required publishing definition, and chat directs secure own-value entry before claiming completion.", ); const secret = o.state.currentSecret as Partial | undefined; + const workerReceipt = personalSecretWorkerReceipt(o.runs.find(run => run.id === o.state.workerRunId), o.state.workerReceiptEvidence as PersonalSecretWorkerEvidence | undefined, + { companyId: o.companyId, agentId: String(o.state.morganId), taskId: String(o.state.taskId), userId: String(o.state.currentPersonId) }); c.add( "actual-own-definition-and-private-ui", o.state.operatorChannel === "private-browser-personal-set-my-value" && @@ -221,7 +262,7 @@ export function gradePersonalSecret(value: unknown) { o.state.requiredReferenceAfter === true && o.state.taskResponsibleUserId === o.state.currentPersonId && o.state.taskAfterStatus === "done" && - o.state.workerReceiptBound === true && + o.state.workerReceiptBound === true && workerReceipt?.runId === o.state.workerRunId && o.runs.some( (run) => run.id === o.state.workerRunId && @@ -342,7 +383,7 @@ export const personalSecretExternalCases = [ ctx, morgan.id, value, - "PUBLISHING_API_KEY", + "PUBLISHING_API_KEY", undefined, { personalWorkerReceipt: true }, ); fixtures.set(ctx, { value, otherValue, other, endpoint }); const task = await ctx.input.api.post< @@ -618,24 +659,24 @@ export const personalSecretExternalCases = [ "current-person publishing credential actually delivered to Morgan", deadlineAt: Date.now() + 150000, intervalMs: 1000, - load: async () => ({ - task: await ctx.input.api.get( - `/api/issues/${state.taskId}`, - ), - runs: (await ctx.allRuns()).filter( - (run) => run.contextSnapshot?.issueId === state.taskId, - ), - }), + load: async () => { + const task = await ctx.input.api.get(`/api/issues/${state.taskId}`), + runs = (await ctx.allRuns()).filter(run => run.contextSnapshot?.issueId === state.taskId); + let evidence: PersonalSecretWorkerEvidence | undefined; + let receipt: ReturnType; + if (runs.length === 1 && runs[0]?.status === "succeeded" && task.status === "done") { + const worker = await ctx.input.api.get(`/api/heartbeat-runs/${runs[0].id}`); + evidence = { task, calls: fixture.endpoint.calls, events: await collectRunEvents(afterSeq => + privateScanJson(ctx, `/api/heartbeat-runs/${worker.id}/events?afterSeq=${afterSeq}&limit=1000`)) }; + receipt = personalSecretWorkerReceipt(worker, evidence, { companyId: source.companyId, agentId: String(state.morganId), taskId: String(state.taskId), userId: String(state.currentPersonId) }); + } + return { task, runs, evidence, receipt }; + }, accept: (v) => v.runs.length === 1 && v.runs[0]?.status === "succeeded" && v.task.status === "done" && - fixture.endpoint.calls.some( - (call) => - call.authorized && - call.runId === v.runs[0]?.id && - call.agentId === state.morganId, - ), + v.receipt?.runId === v.runs[0]?.id, reject: (v) => v.runs.length > 1 || v.runs.some((run) => @@ -644,7 +685,9 @@ export const personalSecretExternalCases = [ ? "Personal publishing worker did not complete one actual credential check" : undefined, }); - state.workerRunId = result.runs[0]!.id; + if (!result.receipt || !result.evidence) throw new Error("Actual native worker output did not bind the authenticated publishing receipt"); + state.workerRunId = result.receipt.runId; + state.workerReceiptEvidence = { ...result.evidence, events: [result.receipt.event] }; if (question) { const [current] = (await ctx.input.api.get(`${root(ctx)}/me/user-secrets`)).filter(row => row.definition.id === state.definitionId); if (!current?.secret || current.secret.id !== state.secretId || current.secret.ownerUserId !== state.currentPersonId @@ -717,7 +760,10 @@ export const personalSecretExternalCases = [ const comments = await ctx.input.api.get( `/api/issues/${state.taskId}/comments?order=asc`, ); - state.workerReceiptBound = comments.some( + const delivery = state.workerRunId ? personalSecretWorkerReceipt(await ctx.input.api.get(`/api/heartbeat-runs/${state.workerRunId}`), + { ...(state.workerReceiptEvidence as PersonalSecretWorkerEvidence), task }, + { companyId: ctx.input.fixtures.company.id, agentId: String(state.morganId), taskId: String(state.taskId), userId: String(state.currentPersonId) }) : undefined; + state.workerReceiptBound = !!delivery && comments.some( (row) => row.createdByRunId === state.workerRunId && row.authorAgentId === state.morganId && @@ -727,8 +773,8 @@ export const personalSecretExternalCases = [ state.authorizedChecks = checks.length; state.unauthorizedChecks = fixture.endpoint.calls.length - checks.length; - state.checkRunId = checks[0]?.runId; - state.checkAgentId = checks[0]?.agentId; + state.checkRunId = delivery?.runId; + state.checkAgentId = delivery?.agentId; const o = await observeExternal(ctx, state), ids = new Set([ctx.issue()!.id, String(state.taskId)]); for (;;) { diff --git a/tests/runner-e2e/chat-catalog-domains/external-secret-endpoint.ts b/tests/runner-e2e/chat-catalog-domains/external-secret-endpoint.ts index 88a1bbe293a..7df37413c31 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-secret-endpoint.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-secret-endpoint.ts @@ -1,4 +1,5 @@ import { createServer } from "node:http"; +import { randomBytes } from "node:crypto"; import { mkdir, realpath, writeFile, rm } from "node:fs/promises"; import path from "node:path"; import { resolveDefaultAgentWorkspaceDir } from "../../../server/src/home-paths.js"; @@ -90,8 +91,9 @@ export async function secretTestEndpoint( value: string, envKey: "PUBLISHING_API_KEY" | "DEPLOYMENT_API_KEY", scopeProbe?: SecretScopeProbe, - options?: { forbidAwsRuntimeAuthority?: true; taskBound?: true }, + options?: { forbidAwsRuntimeAuthority?: true; taskBound?: true; personalWorkerReceipt?: true }, ) { + if (options?.personalWorkerReceipt && (ctx.scenario.id !== "secrets.personal-secret" || envKey !== "PUBLISHING_API_KEY" || scopeProbe || options.taskBound || options.forbidAwsRuntimeAuthority)) throw new Error("Personal worker receipts require the dedicated personal-secret endpoint"); if (ctx.input.execution.environment.id !== "local") throw new Error( "Setup blocker: controlled secret endpoint needs the actual local worker environment", @@ -117,12 +119,15 @@ export async function secretTestEndpoint( method: string; pathname: string; awsAuthorityAbsent?: boolean; + receiptId?: string; + createdAt?: string; + status?: number; }> = []; const scopeReceipts: SecretScopeReceipt[] = [], privateRuntimeValues = new Set(); const server = createServer(async (request, response) => { const authorized = request.headers.authorization === `Bearer ${value}`; - const record = { + const record: (typeof calls)[number] = { authorized, agentId: String(request.headers["x-paperclip-agent"] ?? ""), runId: String(request.headers["x-paperclip-run"] ?? ""), @@ -143,8 +148,7 @@ export async function secretTestEndpoint( calls.push(record); let ok = authorized && - record.agentId === agentId && - /^[a-f0-9-]{36}$/i.test(record.runId) && + (options?.personalWorkerReceipt || (record.agentId === agentId && /^[a-f0-9-]{36}$/i.test(record.runId))) && record.method === "GET" && record.pathname === "/publishing-check"; if (options?.forbidAwsRuntimeAuthority) @@ -241,19 +245,24 @@ export async function secretTestEndpoint( ok = false; } } + if (options?.personalWorkerReceipt) { + record.createdAt = new Date().toISOString(); record.status = ok ? 200 : 403; + if (ok) record.receiptId = randomBytes(24).toString("hex"); + } response.writeHead(ok ? 200 : 403, { "content-type": "application/json" }); response.end( JSON.stringify({ ok, check: "publishing endpoint", status: ok ? 200 : 403, + ...(record.receiptId ? { receiptId: record.receiptId } : {}), }), ); }); const port = await listenOnFetchAllowedPort(server), scriptPath = home ? path.join(home, `secret-endpoint-check-${ctx.input.nonce}.mjs`) : "", url = `http://127.0.0.1:${port}/publishing-check`; - const script = `const credential=process.env[${JSON.stringify(envKey)}];if(!credential){process.stdout.write(JSON.stringify({ok:false,reason:'credential unavailable'}));process.exitCode=1;}else{try{const response=await fetch(${JSON.stringify(url)},{method:'GET',redirect:'error',signal:AbortSignal.timeout(20000),headers:{authorization:'Bearer '+credential,'x-paperclip-agent':process.env.PAPERCLIP_AGENT_ID??'','x-paperclip-run':process.env.PAPERCLIP_RUN_ID??'',...${options?.taskBound ? "{'x-paperclip-task':process.env.PAPERCLIP_TASK_ID??'','x-paperclip-cwd':process.cwd()}" : "{}"},...${scopeProbe ? "{'x-paperclip-runtime':process.env.PAPERCLIP_API_KEY??''}" : "{}"},...${options?.forbidAwsRuntimeAuthority ? "{'x-paperclip-no-aws':String(!Object.keys(process.env).some(key=>key.startsWith('AWS_')||key.startsWith('PAPERCLIP_SECRETS_AWS_')))}" : "{}"}}});const result=await response.json();process.stdout.write(JSON.stringify({ok:result.ok===true,status:response.status,check:'publishing endpoint'}));if(!response.ok)process.exitCode=1;}catch{process.stdout.write(JSON.stringify({ok:false,reason:'endpoint check failed'}));process.exitCode=1;}}\n`; + const script = `const credential=process.env[${JSON.stringify(envKey)}];if(!credential){process.stdout.write(JSON.stringify({ok:false,reason:'credential unavailable'}));process.exitCode=1;}else{try{const response=await fetch(${JSON.stringify(url)},{method:'GET',redirect:'error',signal:AbortSignal.timeout(20000),headers:{authorization:'Bearer '+credential,...${options?.personalWorkerReceipt ? "{}" : "{'x-paperclip-agent':process.env.PAPERCLIP_AGENT_ID??'','x-paperclip-run':process.env.PAPERCLIP_RUN_ID??''}"},...${options?.taskBound ? "{'x-paperclip-task':process.env.PAPERCLIP_TASK_ID??'','x-paperclip-cwd':process.cwd()}" : "{}"},...${scopeProbe ? "{'x-paperclip-runtime':process.env.PAPERCLIP_API_KEY??''}" : "{}"},...${options?.forbidAwsRuntimeAuthority ? "{'x-paperclip-no-aws':String(!Object.keys(process.env).some(key=>key.startsWith('AWS_')||key.startsWith('PAPERCLIP_SECRETS_AWS_')))}" : "{}"}}});const result=await response.json();process.stdout.write(JSON.stringify({ok:result.ok===true,status:response.status,check:'publishing endpoint',...${options?.personalWorkerReceipt ? "(/^[a-f0-9]{48}$/.test(result.receiptId??''))?{receiptId:result.receiptId}:{}" : "{}"}}));if(!response.ok)process.exitCode=1;}catch{process.stdout.write(JSON.stringify({ok:false,reason:'endpoint check failed'}));process.exitCode=1;}}\n`; if (scriptPath) await writeFile(scriptPath, script, { mode: 0o600 }); const command = scriptPath ? `node ${JSON.stringify(scriptPath)}` : `node --input-type=module -e '${script.replace(/'/g, "'\\''")}'`; return {