From 77c31862375a2274974b8408299cc582d39a3965 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Fri, 4 Sep 2026 16:42:26 -0700 Subject: [PATCH 1/3] Add tests for provision no longer publishing the tool registry (CL-7393) Provision's tests asserted publishRootToolRegistry calls at sign-in and first-login creation for an unparented root bench. Those calls are the bug: publishCorbitsToolsRegistry runs the tool-package freshness check first, so a dirty packages/*/src under an unchanged version fails root first-login provisioning with tool_registry_publish_failed. Update the tests to assert no publish happens; provision deploys workflows only. --- packages/onboarding/test/provision.test.ts | 43 ++-------------------- 1 file changed, 3 insertions(+), 40 deletions(-) diff --git a/packages/onboarding/test/provision.test.ts b/packages/onboarding/test/provision.test.ts index a742ab3c21..8752742742 100644 --- a/packages/onboarding/test/provision.test.ts +++ b/packages/onboarding/test/provision.test.ts @@ -29,8 +29,6 @@ const noopPush: WorkflowPusher = async () => ({ outcome: "pushed" as const, commitSha: "a".repeat(40), }); -const noopPublishToolRegistry = async () => undefined; - const TOOLS_ASSET_ID = "ast_corbits_tools"; const SEEDED_MEMORY_TARBALL = { filename: "corbits-memory-tools-0.0.4.tgz", @@ -344,7 +342,6 @@ describe("provisionPersonalTenantIfNeeded", () => { userId: "user_1", userEmail: "alice@example.com", userEmailVerified: true, - publishToolRegistry: noopPublishToolRegistry, pushWorkflow: noopPush, log: collector().log, }); @@ -404,7 +401,6 @@ describe("provisionPersonalTenantIfNeeded", () => { userEmail: "alice@example.com", userEmailVerified: true, displayName: "Alice's Lab", - publishToolRegistry: noopPublishToolRegistry, pushWorkflow: noopPush, log: collector().log, }); @@ -440,8 +436,7 @@ describe("provisionPersonalTenantIfNeeded", () => { userEmail: "alice@example.com", userEmailVerified: true, displayName: "Alice's Lab", - publishToolRegistry: noopPublishToolRegistry, - pushWorkflow: noopPush, + pushWorkflow: noopPush, log: collector().log, }), ).rejects.toThrow(/slug conflict/); @@ -450,7 +445,6 @@ describe("provisionPersonalTenantIfNeeded", () => { test("zero principals with no seed model: provisions the bench and reports the seed skip loudly", async () => { let principalsCalls = 0; const { lines, log } = collector(); - const publishedTenants: string[] = []; const api: ApiCall = async (method, path, body) => { if (method === "GET" && path === "/api/me/principals") { principalsCalls += 1; @@ -512,9 +506,6 @@ describe("provisionPersonalTenantIfNeeded", () => { userEmail: "alice@example.com", userEmailVerified: true, displayName: "Alice's Lab", - publishToolRegistry: async ({ tenantId }) => { - publishedTenants.push(tenantId); - }, pushWorkflow: noopPush, log, }); @@ -522,7 +513,6 @@ describe("provisionPersonalTenantIfNeeded", () => { expect(result.kind).toBe("provisioned"); if (result.kind !== "provisioned") throw new Error("unreachable"); expect(result.seeded).toBe(false); - expect(publishedTenants).toEqual([TENANT_ID]); expect(result.seedSkipReason).toContain("ANTHROPIC_API_KEY"); expect(lines.some((line) => line.includes("ANTHROPIC_API_KEY"))).toBe(true); }); @@ -553,7 +543,6 @@ describe("provisionPersonalTenantIfNeeded", () => { userId: "user_1", userEmail: "alice@example.com", userEmailVerified: true, - publishToolRegistry: noopPublishToolRegistry, pushWorkflow: noopPush, log, }); @@ -577,7 +566,6 @@ describe("provisionPersonalTenantIfNeeded", () => { displayName: "Alice's Lab", operatorTenantId: "ten_operator", seedModel: MODEL, - publishToolRegistry: noopPublishToolRegistry, pushWorkflow: noopPush, log, }); @@ -591,10 +579,9 @@ describe("provisionPersonalTenantIfNeeded", () => { expect(tarballPuts).toEqual([]); }); - test("an unparented personal root bench publishes corbits-tools before seeding", async () => { + test("an unparented personal root bench does not publish corbits-tools; provision only deploys workflows", async () => { const { api, tarballPuts } = firstLoginSeedHub({}); const { log } = collector(); - const publishedTenants: string[] = []; const result = await provisionPersonalTenantIfNeeded({ api, cookies: ["session=abc"], @@ -604,9 +591,6 @@ describe("provisionPersonalTenantIfNeeded", () => { userEmailVerified: true, displayName: "Alice's Lab", seedModel: MODEL, - publishToolRegistry: async ({ tenantId }) => { - publishedTenants.push(tenantId); - }, pushWorkflow: noopPush, log, }); @@ -617,7 +601,6 @@ describe("provisionPersonalTenantIfNeeded", () => { tenantSlug: TENANT_SLUG, seeded: true, }); - expect(publishedTenants).toEqual([TENANT_ID]); expect(tarballPuts).toEqual([]); }); @@ -853,7 +836,6 @@ describe("provisionPersonalTenantIfNeeded", () => { userEmailVerified: true, displayName: "Alice's Lab", seedModel: MODEL, - publishToolRegistry: noopPublishToolRegistry, pushWorkflow: noopPush, log: collector().log, }); @@ -869,7 +851,6 @@ describe("provisionPersonalTenantIfNeeded", () => { userEmail: "alice@example.com", userEmailVerified: true, seedModel: MODEL, - publishToolRegistry: noopPublishToolRegistry, pushWorkflow: noopPush, log, }); @@ -905,7 +886,6 @@ describe("provisionPersonalTenantIfNeeded", () => { ), ); const grantsPosted: { resource: string; action: string }[] = []; - const publishedTenants: string[] = []; const api: ApiCall = async (method, path, body) => { const registry = corbitsToolsRegistryResponse(method, path, TENANT_ID, [ SEEDED_MEMORY_TARBALL, @@ -1021,9 +1001,6 @@ describe("provisionPersonalTenantIfNeeded", () => { userEmail: "alice@example.com", userEmailVerified: true, // No seedModel needed: nothing left to seed on the workflow side. - publishToolRegistry: async ({ tenantId }) => { - publishedTenants.push(tenantId); - }, pushWorkflow: noopPush, log: collector().log, }); @@ -1033,7 +1010,6 @@ describe("provisionPersonalTenantIfNeeded", () => { seeded: true, tenantId: "ten_new", }); - expect(publishedTenants).toEqual([TENANT_ID]); // Exactly the one grant this tenant was missing — no more, no less. expect(grantsPosted).toEqual([missingGrant]); }); @@ -1133,7 +1109,6 @@ describe("provisionPersonalTenantIfNeeded", () => { userId: "user_1", userEmail: "alice@example.com", userEmailVerified: true, - publishToolRegistry: noopPublishToolRegistry, pushWorkflow: noopPush, log: collector().log, }); @@ -1151,7 +1126,6 @@ describe("provisionPersonalTenantIfNeeded", () => { // not depend on a seed credential that may never exist. Seeding itself is // skipped (nothing to seed with); the user is not stranded in a loop. let assetListCalls = 0; - const publishedTenants: string[] = []; const api: ApiCall = async (method, path) => { const registry = corbitsToolsRegistryResponse( method, @@ -1235,9 +1209,6 @@ describe("provisionPersonalTenantIfNeeded", () => { userEmail: "alice@example.com", userEmailVerified: true, // No seedModel — hub without ANTHROPIC_API_KEY. - publishToolRegistry: async ({ tenantId }) => { - publishedTenants.push(tenantId); - }, pushWorkflow: noopPush, log: collector().log, }); @@ -1250,7 +1221,6 @@ describe("provisionPersonalTenantIfNeeded", () => { seeded: false, tenantId: "ten_new", }); - expect(publishedTenants).toEqual([TENANT_ID]); // Completeness was checked (tenant-local assets listed) even without a // seed model — membership recovery does not short-circuit before that. expect(assetListCalls).toBe(1); @@ -1479,7 +1449,6 @@ describe("provisionPersonalTenantIfNeeded", () => { userEmail: "alice@example.com", userEmailVerified: true, seedModel: MODEL, - publishToolRegistry: noopPublishToolRegistry, pushWorkflow: noopPush, log: collector().log, }); @@ -1742,7 +1711,6 @@ describe("provisionPersonalTenantIfNeeded", () => { userEmail: "alice@example.com", userEmailVerified: true, seedModel: MODEL, - publishToolRegistry: noopPublishToolRegistry, pushWorkflow: noopPush, log: collector().log, }); @@ -1856,8 +1824,7 @@ describe("provisionPersonalTenantIfNeeded", () => { expect(await isFullySeeded(api, ["session=abc"], TENANT_ID)).toBe(true); }); - test("sign-in republishes an empty inherited corbits-tools registry", async () => { - const publishedTenants: string[] = []; + test("sign-in does not republish an empty inherited corbits-tools registry", async () => { const api: ApiCall = async (method, path) => { const registry = corbitsToolsRegistryResponse( method, @@ -1958,14 +1925,10 @@ describe("provisionPersonalTenantIfNeeded", () => { userId: "user_1", userEmail: "alice@example.com", userEmailVerified: true, - publishToolRegistry: async ({ tenantId }) => { - publishedTenants.push(tenantId); - }, pushWorkflow: noopPush, log: collector().log, }); - expect(publishedTenants).toEqual([TENANT_ID]); expect(result).toEqual({ kind: "existing-member", seeded: false, From 4281f9ceb3067083d19bdae2723633ea6cf58ba5 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Fri, 4 Sep 2026 16:42:33 -0700 Subject: [PATCH 2/3] Provision deploys workflows only; stop publishing the tool registry (CL-7393) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit publishRootToolRegistry ran publishCorbitsToolsRegistry, which checks tool-package freshness before publishing, at both first-login creation and every sign-in for a root (unparented) personal bench. A dirty packages/*/src under an unchanged version made that freshness check fail root first-login provisioning with tool_registry_publish_failed — a check meant to gate CI landed on the sign-in hot path instead. Delete both call sites and the publishToolRegistry hook entirely. Root-tenant tool registry publish now has exactly two callers: the workbench setup CLI and hub boot (apps/hub/src/system-seed.ts), which already publishes unconditionally and retries on failure rather than failing a sign-in. --- packages/onboarding/src/provision.ts | 89 ---------------------------- 1 file changed, 89 deletions(-) diff --git a/packages/onboarding/src/provision.ts b/packages/onboarding/src/provision.ts index 2b74b5e9f4..5df5659a6b 100644 --- a/packages/onboarding/src/provision.ts +++ b/packages/onboarding/src/provision.ts @@ -18,10 +18,8 @@ import { DEFAULT_WORKFLOWS, reconcileSeedGrants, seedTenant, - publishCorbitsToolsRegistry, isCorbitsToolsRegistrySeeded, type ModelSource, - type ToolRegistryPublisher, type WorkflowPusher, isLiveDeploymentStatus, } from "@corbits/seeding"; @@ -107,7 +105,6 @@ export type ProvisionArgs = { displayName?: string; operatorTenantId?: string; seedModel?: ModelSource; - publishToolRegistry?: ToolRegistryPublisher; pushWorkflow: WorkflowPusher; log: (line: string) => void; /** The closed-by-default access-policy gate. Absent means this hub @@ -230,33 +227,6 @@ export async function isFullySeeded( return isCorbitsToolsRegistrySeeded(api, cookies, tenantId); } -async function publishRootToolRegistry(args: { - api: ApiCall; - cookies: string[]; - hubUrl: string; - tenantId: string; - publishToolRegistry?: ToolRegistryPublisher; - log: (line: string) => void; -}): Promise { - const publishToolRegistry = - args.publishToolRegistry ?? publishCorbitsToolsRegistry; - try { - await publishToolRegistry({ - api: args.api, - cookies: args.cookies, - hubUrl: args.hubUrl, - tenantId: args.tenantId, - log: args.log, - }); - } catch (cause) { - throw new ProvisionError( - "tool_registry_publish_failed", - `personal root bench ${args.tenantId} could not publish corbits-tools before seeding: ${cause instanceof Error ? cause.message : String(cause)}`, - "transient", - ); - } -} - /** * The honest partial-seed report `ensureSeeded` reads after catching a * sidecar-unavailable deploy failure (CL-6264): which default workflows @@ -333,39 +303,6 @@ export async function provisionPersonalTenantIfNeeded( tenantResponse.data, "tenant response", ); - if (ownTenant.parentId === undefined || ownTenant.parentId === null) { - await publishRootToolRegistry({ - api: args.api, - cookies: args.cookies, - hubUrl: args.hubUrl, - tenantId: own.tenantId, - ...(args.publishToolRegistry !== undefined - ? { publishToolRegistry: args.publishToolRegistry } - : {}), - log: args.log, - }); - } else if ( - !(await isCorbitsToolsRegistrySeeded( - args.api, - args.cookies, - own.tenantId, - )) - ) { - // A parented bench inherits `corbits-tools`. When that inherited - // registry is missing or dangling-empty, republish locally the - // same way grant reconcile fills missing grants on every sign-in. - await publishRootToolRegistry({ - api: args.api, - cookies: args.cookies, - hubUrl: args.hubUrl, - tenantId: own.tenantId, - ...(args.publishToolRegistry !== undefined - ? { publishToolRegistry: args.publishToolRegistry } - : {}), - log: args.log, - }); - } - const fullySeeded = await isFullySeeded( args.api, args.cookies, @@ -503,32 +440,6 @@ export async function provisionPersonalTenantIfNeeded( ); } - if (tenant.parentId === undefined || tenant.parentId === null) { - await publishRootToolRegistry({ - api: args.api, - cookies: args.cookies, - hubUrl: args.hubUrl, - tenantId: tenant.id, - ...(args.publishToolRegistry !== undefined - ? { publishToolRegistry: args.publishToolRegistry } - : {}), - log: args.log, - }); - } else if ( - !(await isCorbitsToolsRegistrySeeded(args.api, args.cookies, tenant.id)) - ) { - await publishRootToolRegistry({ - api: args.api, - cookies: args.cookies, - hubUrl: args.hubUrl, - tenantId: tenant.id, - ...(args.publishToolRegistry !== undefined - ? { publishToolRegistry: args.publishToolRegistry } - : {}), - log: args.log, - }); - } - if (!args.seedModel) { const seedSkipReason = "no hub-owned seed model credential is configured (ANTHROPIC_API_KEY); the bench was provisioned without the default workflow set"; From 26367c52e0c30882fef00fd84ad3270a24a36f0e Mon Sep 17 00:00:00 2001 From: Sawyer Date: Fri, 4 Sep 2026 16:43:57 -0700 Subject: [PATCH 3/3] Fix formatting in provision tests (CL-7393) --- packages/onboarding/test/provision.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/onboarding/test/provision.test.ts b/packages/onboarding/test/provision.test.ts index 8752742742..4e2fd52f85 100644 --- a/packages/onboarding/test/provision.test.ts +++ b/packages/onboarding/test/provision.test.ts @@ -436,7 +436,7 @@ describe("provisionPersonalTenantIfNeeded", () => { userEmail: "alice@example.com", userEmailVerified: true, displayName: "Alice's Lab", - pushWorkflow: noopPush, + pushWorkflow: noopPush, log: collector().log, }), ).rejects.toThrow(/slug conflict/);