fix(apps): preserve grant and approval defaults

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
DottaandCodex committed 2026-08-20 22:11:52 +00:00
1 parent fda25be6b1
commit 13dd4c1eae
12 files changed
+27 -15

No files matched your search

@@ -1,4 +1,4 @@
ALTER TABLE "connection_grant_members" DROP CONSTRAINT "connection_grant_members_grant_id_connection_grants_id_fk";
ALTER TABLE "connection_grant_members" DROP CONSTRAINT IF EXISTS "connection_grant_members_grant_id_connection_grants_id_fk";
--> statement-breakpoint
ALTER TABLE "connection_grants" ADD CONSTRAINT "connection_grants_company_id_uq" UNIQUE("company_id","id");--> statement-breakpoint
ALTER TABLE "connection_grant_members" ADD CONSTRAINT "connection_grant_members_company_grant_fk" FOREIGN KEY ("company_id","grant_id") REFERENCES "public"."connection_grants"("company_id","id") ON DELETE cascade ON UPDATE no action;
+2 -3
View File
@@ -68,10 +68,9 @@ export function credentialConfigPath(field: FieldDef): string {
}
export function recommendedDefaultsForApp(app: AppDefinition, methodKey?: string | null): Record<string, unknown> {
void app;
void methodKey;
const method = getAvailableConnectionMethod(app, methodKey);
return {
access: "all_agents",
askFirstRiskLevels: [],
askFirstRiskLevels: method?.riskTier === "S1" ? [] : ["write", "destructive"],
};
}
@@ -366,6 +366,9 @@ describeEmbeddedPostgres("generic remote MCP connections", () => {
// this connection cannot be depending on gallery metadata for anything.
expect(connection!.config).not.toHaveProperty("sourceTemplateKey");
expect(connection!.config).not.toHaveProperty("connectionMethodKey");
await expect(service.listConnectionGrants(result.connectionId, company.id)).resolves.toMatchObject({
grants: [expect.objectContaining({ kind: "organization", isDefault: true, credentialSecretRefs: [] })],
});
const [profile] = await db.select().from(toolProfiles).where(eq(
toolProfiles.profileKey,
`app:${result.connectionId}`,
+3
View File
@@ -4585,6 +4585,7 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {}
})
.where(eq(toolConnections.id, existing.id))
.returning();
await ensureDefaultOrganizationGrant(updated);
await syncCredentialBindings(updated);
await ensureRuntimeSlot(updated);
return { row: updated, created: false };
@@ -4605,6 +4606,7 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {}
credentialRefs: [],
credentialSecretRefs: [],
}).returning();
await ensureDefaultOrganizationGrant(created);
await syncCredentialBindings(created);
await ensureRuntimeSlot(created);
return { row: created, created: true };
@@ -6568,6 +6570,7 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {}
createdByUserId: actor?.actorType === "user" ? actor.actorId ?? null : null,
}).returning();
}
await ensureDefaultOrganizationGrant(connectionRow);
await syncCredentialBindings(connectionRow);
await ensureRuntimeSlot(connectionRow);
+1 -1
View File
@@ -118,7 +118,7 @@ test.describe.serial("not-connected app page", () => {
await page.goto(`/${seed.prefix}/apps/app/${applicationId}`);
await page.getByRole("button", { name: "Reconnect", exact: true }).click();
await expect(page).toHaveURL(/\/apps\/connect\?/, { timeout: 20_000 });
await expect(page.getByText("Connect with a link")).toBeVisible({ timeout: 20_000 });
await expect(page.getByText("Connect your own MCP server")).toBeVisible({ timeout: 20_000 });
await expect(page.getByText(mock.url)).toBeVisible();
await page.screenshot({ path: `${SCREENSHOT_DIR}/apps-nav-w6-02-reconnect-prefilled.png`, fullPage: true });
@@ -33,7 +33,7 @@ test("captures the current app removal confirmations", async ({ page }) => {
applicationName: "Guarded MCP",
name: "Primary connection",
transport: "mcp_remote",
config: { url: "https://fixture.example/mcp" },
config: { url: "http://localhost:65535/mcp" },
},
});
expect(conn.ok(), `connection create failed ${conn.status()}: ${await conn.text()}`).toBe(true);
+1 -1
View File
@@ -46,7 +46,7 @@ async function createConnection(
const res = await request.post(`/api/companies/${companyId}/tools/connections`, {
data: {
transport: "mcp_remote",
config: { url: "https://fixture.example/mcp" },
config: { url: "http://localhost:65535/mcp" },
enabled: true,
status: "active",
...data,
+1 -1
View File
@@ -137,7 +137,7 @@ test.describe.serial("dark-mode Apps surfaces", () => {
await forceDark(page);
await page.goto(`/${seed.prefix}/apps/connections`);
await expect(page.getByRole("heading", { name: "Connections" })).toBeVisible({ timeout: 30_000 });
await expect(page.getByText(/app needs attention/i).first()).toBeVisible({ timeout: 30_000 });
await expect(page.getByText(/needs attention/i).first()).toBeVisible({ timeout: 30_000 });
await page.screenshot({ path: `${SCREENSHOT_DIR}/apps-nav-02-attention-dark.png`, fullPage: true });
});
+11 -5
View File
@@ -95,6 +95,7 @@ async function setScoutScript(
) {
await json(await request.patch(`/api/agents/${scout.id}`, {
data: {
adapterType: "process",
adapterConfig: {
command: process.execPath,
args: ["--input-type=module", "-e", script],
@@ -287,17 +288,22 @@ test.describe.serial("MCP prod Phase 5a user-story harness", () => {
test(`${storyById("US-1").id} ${storyById("US-1").title} @mcp-runnable @mcp-us1`, async ({ page, request }) => {
const { seed, scout, mock, connectionId } = await seedConnectedFixture(request, "us1");
try {
await page.goto(`/${seed.prefix}/apps/${connectionId}`);
await expect(page.getByRole("heading", { name: /Sheets Fixture us1/i })).toBeVisible({ timeout: 30_000 });
await screenshot(page, "US-1", "01-connected-app");
await setScoutScript(request, scout, buildGatewayCallScript(connectionId, "sheets:list_rows"));
const invoked = await invokeHeartbeat(request, scout.id);
const run = await waitForRun(request, invoked.id);
expect(run.status, run.error ?? `heartbeat run ${run.id} did not succeed`).toBe("succeeded");
const failedLog = run.status === "succeeded"
? null
: await json<{ content?: string }>(await request.get(`/api/heartbeat-runs/${run.id}/log?offset=0&limitBytes=65536`));
expect(
run.status,
[run.error ?? `heartbeat run ${run.id} did not succeed`, failedLog?.content].filter(Boolean).join("\n"),
).toBe("succeeded");
expect(mock.captures.some((capture) => capture.method === "tools/call" && capture.toolName === "sheets:list_rows")).toBe(true);
await expectAuditEvent(request, seed.companyId, { connectionId, agentId: scout.id, search: "sheets:list_rows" });
await page.goto(`/${seed.prefix}/apps/${connectionId}`);
await expect(page.getByRole("heading", { name: /Sheets Fixture us1/i })).toBeVisible({ timeout: 30_000 });
await screenshot(page, "US-1", "01-connected-app");
await page.goto(`/${seed.prefix}/apps/${connectionId}/activity`);
await screenshot(page, "US-1", "02-activity");
} finally {
+1
View File
@@ -70,6 +70,7 @@ export default defineConfig({
...process.env,
NODE_ENV: "test",
PORT: String(PORT),
PAPERCLIP_API_URL: BASE_URL,
PAPERCLIP_HOME,
PAPERCLIP_INSTANCE_ID,
PAPERCLIP_CONFIG,
+1 -1
View File
@@ -178,7 +178,7 @@ describe("Browse store door (PAP-13254 door 1)", () => {
await act(async () => {
byoCard?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
expect(navigateMock).toHaveBeenCalledWith("/apps/byo");
expect(navigateMock).toHaveBeenCalledWith("/apps/connect?byo=1");
});
it("filters the gallery by the search query", async () => {
+1 -1
View File
@@ -7,7 +7,7 @@ import { appSourceConnectHref } from "./app-connect-policy";
export const POPULAR_KEYS = ["zapier", "github", "slack", "notion", "posthog", "linear"];
/** Deep-link into the Connect wizard's bring-your-own-tool URL flow. */
export const BYO_CONNECT_HREF = "/apps/byo";
export const BYO_CONNECT_HREF = "/apps/connect?byo=1";
/** Zapier connects with the complete MCP URL issued by Zapier. */
export const ZAPIER_CONNECT_HREF = "/apps/connect?byo=1&source=zapier";