Merge upstream and regenerate Boat migration as 0333

This commit is contained in:
Dotta committed 2026-10-10 23:57:20 -05:00
commit d7d7808302
32 files changed
+4114 -845

No files matched your search

+148 -1
View File
@@ -5,7 +5,8 @@ import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest
import {
agentInstructionWorkingCopies, agents, agentApiKeys, agentWakeupRequests, companies, companySecretProposals, createDb,
adapterAuthSessions, environments, environmentLeases, executionWorkspaces,
heartbeatRuns, issues, issueWatchdogs, projects, routines, type Db,
heartbeatRuns, issues, issueWatchdogs, issueThreadInteractions, issueQuestionResponseDeliveries,
projects, routines, type Db,
} from "@paperclipai/db";
import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js";
import { readIdleSleepSafety, type IdleSleepDrainStatus } from "../services/idle-sleep-safety.js";
@@ -184,6 +185,152 @@ if (!support.supported) console.warn(`Skipping idle sleep Postgres tests: ${supp
return { companyId, agentId, runId };
}
async function seedQuestionDelivery(change: Partial<typeof issueQuestionResponseDeliveries.$inferInsert> = {}) {
const seeded = await seed();
await db.update(heartbeatRuns).set({ finishedAt: new Date(now) }).where(eq(heartbeatRuns.id, seeded.runId));
const issueId = randomUUID();
const interactionId = randomUUID();
await db.insert(issues).values({ id: issueId, companyId: seeded.companyId, title: "Finished question", status: "done" });
await db.insert(issueThreadInteractions).values({
id: interactionId, companyId: seeded.companyId, issueId,
kind: "ask_user_questions", status: "answered", payload: {},
});
await db.insert(issueQuestionResponseDeliveries).values({
companyId: seeded.companyId, issueId, interactionId, sourceRunId: seeded.runId,
targetRunId: seeded.runId, correlationId: randomUUID(), payloadSha256: "fixture",
status: "fallback_queued", deliveryMode: "wake_fallback", acknowledgedAt: new Date(now), ...change,
});
return { ...seeded, issueId, interactionId };
}
async function seedQuestionWake(
delivery: Awaited<ReturnType<typeof seedQuestionDelivery>>,
change: Partial<typeof agentWakeupRequests.$inferInsert> = {},
) {
await db.insert(agentWakeupRequests).values({
companyId: delivery.companyId, agentId: delivery.agentId, source: "automation",
idempotencyKey: `question-response:${delivery.interactionId}`,
status: "completed", finishedAt: new Date(now), ...change,
});
}
it.each(["pending", "delivering"])("keeps a %s question delivery awake", async status => {
await seedQuestionDelivery({ status, acknowledgedAt: null });
expect(await read()).toEqual(present);
});
it.each(["delivered", "fallback_queued"])("permits acknowledged %s history after its target run finishes", async status => {
await seedQuestionDelivery({ status });
expect(await read()).toEqual(none);
});
it.each(["delivered", "fallback_queued"])("keeps an unacknowledged %s question delivery awake", async status => {
await seedQuestionDelivery({ status, acknowledgedAt: null });
expect(await read()).toEqual(present);
});
it.each([
["queued", { status: "queued", finishedAt: null }],
["running", { status: "running", finishedAt: null }],
["unfinished", { finishedAt: null }],
["retry", { scheduledRetryAt: new Date(now + 1000) }],
["accounting", { costAccountingPending: true }],
] as const)("keeps an acknowledged question delivery awake with %s target evidence", async (_name, change) => {
const { runId } = await seedQuestionDelivery();
await db.update(heartbeatRuns).set(change).where(eq(heartbeatRuns.id, runId));
expect(await read()).toEqual(present);
});
it("does not accept a foreign company's finished question target", async () => {
const foreign = await seedQuestionDelivery({ status: "failed", targetRunId: null });
await seedQuestionDelivery({ targetRunId: foreign.runId });
expect(await read()).toEqual(present);
});
it("keeps delivered history with a missing target awake", async () => {
await seedQuestionDelivery({ status: "delivered", targetRunId: null });
expect(await read()).toEqual(present);
});
it("keeps fallback history without a durable wake receipt awake", async () => {
await seedQuestionDelivery({ targetRunId: null });
expect(await read()).toEqual(present);
});
it.each(["completed", "failed", "cancelled", "skipped", "timed_out"])(
"permits fallback history after its exact durable wake is %s and finished", async status => {
const delivery = await seedQuestionDelivery({ targetRunId: null });
await seedQuestionWake(delivery, { status });
expect(await read()).toEqual(none);
},
);
it.each(["queued", "deferred_issue_execution", "coalesced"])("keeps an unresolved %s question wake awake", async status => {
const delivery = await seedQuestionDelivery({ targetRunId: null });
await seedQuestionWake(delivery, { status, finishedAt: null });
expect(await read()).toEqual(present);
});
it("keeps a terminal question wake without completion evidence awake", async () => {
const delivery = await seedQuestionDelivery({ targetRunId: null });
await seedQuestionWake(delivery, { finishedAt: null });
expect(await read()).toEqual(present);
});
it("permits a deferred question fallback after its coalesced continuation finishes", async () => {
const delivery = await seedQuestionDelivery({ targetRunId: null });
await seedQuestionWake(delivery, { status: "deferred_issue_execution", finishedAt: null });
expect(await read()).toEqual(present);
await db.update(agentWakeupRequests).set({
status: "coalesced", runId: delivery.runId, finishedAt: new Date(now),
}).where(eq(agentWakeupRequests.companyId, delivery.companyId));
expect(await read()).toEqual(none);
});
it.each(["missing_run", "wrong_company", "wrong_agent", "unfinished", "retry", "accounting"])(
"keeps a coalesced question fallback awake with %s linked-run evidence", async kind => {
const delivery = await seedQuestionDelivery({ targetRunId: null });
let runId = delivery.runId;
if (kind === "missing_run") runId = randomUUID();
if (kind === "wrong_company") runId = (await seedQuestionDelivery({ status: "failed", targetRunId: null })).runId;
if (kind === "wrong_agent") {
const otherAgent = randomUUID();
await db.insert(agents).values({ id: otherAgent, companyId: delivery.companyId, name: "Other", role: "engineer", status: "idle", adapterType: "process" });
await db.update(heartbeatRuns).set({ agentId: otherAgent }).where(eq(heartbeatRuns.id, runId));
}
if (kind === "unfinished") await db.update(heartbeatRuns).set({ finishedAt: null }).where(eq(heartbeatRuns.id, runId));
if (kind === "retry") await db.update(heartbeatRuns).set({ scheduledRetryAt: new Date(now + 1000) }).where(eq(heartbeatRuns.id, runId));
if (kind === "accounting") await db.update(heartbeatRuns).set({ costAccountingPending: true }).where(eq(heartbeatRuns.id, runId));
await seedQuestionWake(delivery, { status: "coalesced", runId });
expect(await read()).toEqual(present);
},
);
it("does not accept another interaction's or company's question wake receipt", async () => {
const delivery = await seedQuestionDelivery({ targetRunId: null });
await seedQuestionWake(delivery, { idempotencyKey: `question-response:${randomUUID()}` });
const foreign = await seedQuestionDelivery({ status: "failed", targetRunId: null });
await seedQuestionWake(delivery, { companyId: foreign.companyId, agentId: foreign.agentId });
expect(await read()).toEqual(present);
});
it("permits failed question-delivery history without a pending target", async () => {
await seedQuestionDelivery({ status: "failed", targetRunId: null, acknowledgedAt: null });
expect(await read()).toEqual(none);
});
it("still blocks other work after a question handoff finishes", async () => {
const { issueId } = await seedQuestionDelivery();
await db.update(issues).set({ status: "todo" }).where(eq(issues.id, issueId));
expect(await read()).toEqual(present);
});
it("requires a current quiet owner even with completed question history", async () => {
await seedQuestionDelivery();
expect(await readIdleSleepSafety(db, owned, () => now, "stale-owner", emptyLocal)).toEqual(unknown);
expect(await readIdleSleepSafety(db, owned, () => now + 60_001, ownerId, emptyLocal)).toEqual(unknown);
});
it("keeps saved agent files awake until deferred cleanup completes", async () => {
const { companyId, agentId, runId } = await seed();
await db.insert(agentInstructionWorkingCopies).values({
@@ -12952,6 +12952,91 @@ describeEmbeddedPostgres("tool access service", () => {
);
});
it.each(["new", "saved"])("starts Sentry MCP sign-in for a %s connection without a manual OAuth app", async (state) => {
const company = await createCompany(db);
const service = createTestToolAccessService(db);
const connected = await service.connectGalleryApp(company.id, {
galleryKey: "sentry", connectionMethodKey: "mcp-oauth", name: "Sentry",
});
const redirectUri = "http://localhost:3200/api/tools/oauth/callback";
const issuer = "https://mcp.sentry.dev";
const resource = `${issuer}/mcp`;
const scopes = ["org:read", "project:write", "team:write", "event:write", "alerts:write"];
if (state === "saved") {
const [connection] = await db.select().from(toolConnections)
.where(eq(toolConnections.id, connected.connectionId));
// An existing MCP registration must survive catalog changes. This is the
// state before a reconnect, with endpoints cached by successful discovery.
await db.update(toolConnections).set({ config: {
...connection.config,
oauth: {
...connection.config.oauth as Record<string, unknown>,
provider: "sentry", issuer, resource,
authorizationUrl: `${issuer}/oauth/authorize`, tokenUrl: `${issuer}/oauth/token`,
metadataUrl: `${issuer}/.well-known/oauth-authorization-server`,
clientId: "saved-sentry-client", clientRegistrationSource: "dcr",
clientIssuer: issuer, clientResource: resource,
clientCompanyId: company.id, clientRedirectUri: redirectUri,
clientTokenEndpointAuthMethod: "none",
},
} }).where(eq(toolConnections.id, connected.connectionId));
}
const calls: string[] = [];
vi.spyOn(globalThis, "fetch").mockImplementation(async (url, init) => {
const href = String(url);
calls.push(href);
// Both hosts publish valid OAuth metadata. The web/API issuer cannot
// register an MCP client; discovery must select the resource's issuer.
if (href === "https://sentry.io/.well-known/oauth-authorization-server") {
return mcpHttpResponse({
issuer: "https://sentry.io", authorization_endpoint: "https://sentry.io/oauth/authorize/",
token_endpoint: "https://sentry.io/oauth/token/", code_challenge_methods_supported: ["S256"],
});
}
if (href === `${issuer}/.well-known/oauth-protected-resource/mcp`) {
return mcpHttpResponse({ resource, authorization_servers: [issuer], scopes_supported: scopes });
}
if (href === `${issuer}/.well-known/oauth-authorization-server`) {
return mcpHttpResponse({
issuer, authorization_endpoint: `${issuer}/oauth/authorize`, token_endpoint: `${issuer}/oauth/token`,
registration_endpoint: `${issuer}/oauth/register`, scopes_supported: scopes,
grant_types_supported: ["authorization_code", "refresh_token"],
code_challenge_methods_supported: ["S256"],
token_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post", "none"],
client_id_metadata_document_supported: true,
});
}
if (href === `${issuer}/oauth/register`) {
expect(JSON.parse(String(init?.body))).toMatchObject({
redirect_uris: [redirectUri], token_endpoint_auth_method: "none",
});
return mcpHttpResponse({
client_id: "new-sentry-client", redirect_uris: [redirectUri],
grant_types: ["authorization_code", "refresh_token"], response_types: ["code"],
token_endpoint_auth_method: "none",
});
}
throw new Error(`unexpected fetch ${href}`);
});
const started = await service.startOAuth(company.id, connected.connectionId, {
redirectUri, actor: { actorType: "user", actorId: "board" },
});
const authorization = new URL(started.authorizationUrl);
expect(authorization.origin + authorization.pathname).toBe(`${issuer}/oauth/authorize`);
expect(authorization.searchParams.get("resource")).toBe(resource);
expect(authorization.searchParams.get("scope")?.split(" ")).toEqual(scopes);
expect(authorization.searchParams.get("code_challenge_method")).toBe("S256");
expect(authorization.searchParams.get("client_id")).toBe(`${state}-sentry-client`);
expect(calls).not.toContain("https://sentry.io/.well-known/oauth-authorization-server");
expect(calls.filter((url) => url === `${issuer}/oauth/register`)).toHaveLength(state === "new" ? 1 : 0);
const [saved] = await db.select().from(toolConnections)
.where(eq(toolConnections.id, connected.connectionId));
expect(saved.config.oauth).toMatchObject({
clientId: `${state}-sentry-client`, clientIssuer: issuer, clientResource: resource,
clientCompanyId: company.id, clientRedirectUri: redirectUri,
});
});
it("uses Asana v2 metadata instead of stale v1 endpoints for a saved custom app", async () => {
const company = await createCompany(db);
const service = createTestToolAccessService(db);
+26 -1
View File
@@ -93,13 +93,38 @@ const WORK_CHECKS = [
THEN s.provider_lease_id::uuid END)
AND (l.status IN ('released', 'expired') AND l.cleanup_status = 'success'
AND l.released_at IS NOT NULL) IS NOT TRUE))`,
// Answer delivery history is not work once its acknowledged handoff has
// finished. Keep pending deliveries, unfinished target runs, and missing or
// unfinished fallback wake receipts awake. Other work checks still apply.
`SELECT 1 FROM issue_question_response_deliveries d WHERE
d.status NOT IN ('delivered', 'fallback_queued', 'failed')
OR (d.status IN ('delivered', 'fallback_queued') AND d.acknowledged_at IS NULL)
OR (d.status = 'delivered' AND d.target_run_id IS NULL)
OR (d.target_run_id IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM heartbeat_runs r WHERE r.id = d.target_run_id
AND r.company_id = d.company_id
AND r.status IN ('succeeded', 'failed', 'cancelled', 'timed_out', 'interrupted')
AND r.finished_at IS NOT NULL AND r.scheduled_retry_at IS NULL
AND r.cost_accounting_pending = false))
OR (d.status = 'fallback_queued' AND d.target_run_id IS NULL AND NOT EXISTS (
SELECT 1 FROM agent_wakeup_requests w WHERE w.company_id = d.company_id
AND w.idempotency_key = 'question-response:' || d.interaction_id::text
AND w.idempotency_key LIKE 'question-response:%'
AND w.finished_at IS NOT NULL
AND (w.status IN ('completed', 'failed', 'cancelled', 'skipped', 'timed_out')
OR (w.status = 'coalesced' AND EXISTS (
SELECT 1 FROM heartbeat_runs r WHERE r.id = w.run_id
AND r.company_id = w.company_id AND r.agent_id = w.agent_id
AND r.status IN ('succeeded', 'failed', 'cancelled', 'timed_out', 'interrupted')
AND r.finished_at IS NOT NULL AND r.scheduled_retry_at IS NULL
AND r.cost_accounting_pending = false)))))`,
// These less common work sources fail closed on any retained state. Their
// terminal-state exceptions can be added with tests for the owning service.
...[
"chat_endpoints", "chat_deliveries", "chat_publications", "chat_actions",
"chat_completion_deliveries", "connection_event_deliveries",
"connection_intent_deliveries", "tool_action_requests", "tool_action_deliveries",
"issue_question_response_deliveries", "workspace_runtime_services",
"workspace_runtime_services",
"execution_workspace_runtime_leases", "pipeline_automation_executions",
"native_run_finalizations", "company_transfer_runs", "decisions", "decision_effect_executions",
"decision_archive_notification_outbox", "browser_use_sessions", "browser_use_runs",