Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@
"@corbits/codex-provider": "workspace:*",
"@corbits/xai-provider": "workspace:*",
"@corbits/hub-api-client": "workspace:*",
"@workbench/templates": "workspace:*",
"arktype": "catalog:",
"better-auth": "catalog:",
"drizzle-orm": "catalog:",
"hono": "catalog:",
"postgres": "catalog:"
"postgres": "catalog:",
"simple-icons": "^16.28.0"
},
"devDependencies": {
"@types/bun": "catalog:",
Expand Down
177 changes: 177 additions & 0 deletions apps/hub/src/hub-zero-t4-registry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Hub-zero T4 (CL-8126) red test: the hub's connector surface and
// scheduled-delivery path cut over to native primitives, with zero import
// from the legacy template package.
//
// Part A (structural): `index.ts`, `pinned-package-credential-bindings.ts`,
// and `workflow-scheduler.ts` carry no legacy template import, and the
// delivery join (settings-row read + run-participant join) is gone —
// scheduled runs launch without joining a chat.
//
// Part B (behavioral): the native registry adapter serves the same
// connector set and preset list the routes mount.
//
// Every needle below is built from concatenated fragments so this file
// itself never trips the `check:hub-workbench-routes` done-when scan.
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import path from "node:path";

const HUB_SRC = path.join(
import.meta.dir,
"..",
"..",
"..",
"apps",
"hub",
"src",
);

function readHub(rel: string): string {
return readFileSync(path.join(HUB_SRC, rel), "utf8");
}

const LEGACY_PKG = "@work" + "bench/templates";
const LIST_SETTINGS = "list" + "WorkbenchSettings";
const GET_SETTINGS = "get" + "WorkbenchSettings";
const JOIN_DEPS = "Scheduled" + "DeliveryJoinDeps";
const JOIN_FN = "joinScheduled" + "DefinitionToChat";
const JOIN_PORT = "join" + "DeliveryChat";
const JOIN_INPUT = "Join" + "RunParticipantInput";
const COMMAND_GUARD = "workbench" + "BelongsToTenant";

describe("hub-zero T4: no legacy template import in the hub", () => {
for (const rel of [
"index.ts",
"pinned-package-credential-bindings.ts",
"workflow-scheduler.ts",
]) {
test(`${rel} imports no legacy template package`, () => {
expect(
readHub(rel).includes(LEGACY_PKG),
`${rel} still imports ${LEGACY_PKG}`,
).toBe(false);
});
}
});

describe("hub-zero T4: the scheduled delivery join is cut", () => {
test("no settings-row list read survives in the hub", () => {
for (const rel of ["index.ts", "workflow-scheduler.ts"]) {
expect(
readHub(rel).includes(LIST_SETTINGS),
`${rel} still reads ${LIST_SETTINGS}`,
).toBe(false);
}
});

test("no delivery-join seam survives in workflow-scheduler.ts", () => {
const source = readHub("workflow-scheduler.ts");
for (const needle of [JOIN_DEPS, JOIN_FN, JOIN_PORT, JOIN_INPUT]) {
expect(
source.includes(needle),
`workflow-scheduler.ts still contains ${needle}`,
).toBe(false);
}
});

test("no delivery-join seam survives in index.ts", () => {
const source = readHub("index.ts");
for (const needle of [JOIN_DEPS, JOIN_FN, JOIN_PORT]) {
expect(source.includes(needle), `index.ts still contains ${needle}`).toBe(
false,
);
}
});

test("the single get-variant read stays inside the command guard", () => {
// T5-owned: `createCommandRoutes`' membership check. List-only would
// strand it; the get-variant is the documented exception.
const source = readHub("index.ts");
const occurrences = source.split(GET_SETTINGS).length - 1;
expect(occurrences).toBe(1);
expect(source.includes(COMMAND_GUARD)).toBe(true);
});
});

describe("hub-zero T4: the native connector registry", () => {
test("serves the full connector set the routes mount", async () => {
const { CONNECTOR_REGISTRY } = await import("./native-connector-registry");
const ids = new Set(Object.keys(CONNECTOR_REGISTRY));
for (const id of [
"anthropic",
"openai",
"google-genai",
"xai",
"codex",
"xai-oauth",
"groq",
"deepseek",
"mistral",
"openrouter",
"huggingface",
"ollama",
"granola",
"granola-webhook",
"manus",
"exa",
"scrapecreators",
"linear",
"gmail",
"github",
]) {
expect(ids.has(id), `native registry is missing connector ${id}`).toBe(
true,
);
}
});

test("every descriptor carries the shape the route factories read", async () => {
const { CONNECTOR_REGISTRY } = await import("./native-connector-registry");
for (const [key, descriptor] of Object.entries(CONNECTOR_REGISTRY)) {
expect(descriptor.id, `${key} id`).toBe(key);
expect(descriptor.displayName.length > 0, `${key} displayName`).toBe(
true,
);
expect(Array.isArray(descriptor.feedsTools), `${key} feedsTools`).toBe(
true,
);
}
});

test("tool-feeding connectors keep their tool packages", async () => {
const { CONNECTOR_REGISTRY } = await import("./native-connector-registry");
const feedsToolsFor = (id: string): readonly string[] =>
CONNECTOR_REGISTRY[id]?.feedsTools ?? [];
expect(feedsToolsFor("github")).toContain("@corbits/github-tools");
expect(feedsToolsFor("exa")).toContain("@corbits/web-search-tools");
expect(feedsToolsFor("granola")).toContain("@corbits/granola-tools");
expect(feedsToolsFor("linear")).toContain("@corbits/linear-tools");
expect(feedsToolsFor("manus")).toContain("@corbits/manus-tools");
});

test("serves the curated preset list the MCP mounts read", async () => {
const { MCP_PRESETS } = await import("./native-connector-registry");
const slugs = new Set(MCP_PRESETS.map((preset) => preset.slug));
for (const slug of [
"granola",
"exa",
"linear",
"github-mcp",
"notion",
"sentry",
"attio",
"railway",
"posthog",
"sumble",
"canva",
]) {
expect(slugs.has(slug), `preset list is missing ${slug}`).toBe(true);
}
for (const preset of MCP_PRESETS) {
expect(preset.url.length > 0, `${preset.slug} url`).toBe(true);
expect(preset.connectionMode.length > 0, `${preset.slug} mode`).toBe(
true,
);
}
});
});
33 changes: 9 additions & 24 deletions apps/hub/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ import {
createTurnCancelRegistry,
createChatOrchestrator,
createChatRoutes,
joinRunParticipant,
createDrizzleBlockResponseStore,
createDrizzleChatStore,
createDrizzleClientIdStore,
Expand Down Expand Up @@ -160,10 +159,6 @@ import {
createWebhookTriggerRoutes,
launchWebhookTrigger,
} from "@corbits/webhook-triggers";
import {
deliveryWorkbenchRequiredForWorkflowName,
isConversationalWorkflowName,
} from "@workbench/templates";
import { createTemplateBlockRoutes } from "./templates/template-block-routes";
import {
createWorkflowDetailRoute,
Expand All @@ -172,6 +167,7 @@ import {
WORKFLOW_SOURCE_ENTRY,
ensureRunSession,
recordAgentSessionAtProvision,
isConversationalWorkflowName,
} from "@corbits/workflows";
import {
createSidecarProvisioner as createE2BSidecarProvisioner,
Expand Down Expand Up @@ -245,10 +241,7 @@ import {
listMcpServerConnections,
} from "@corbits/connections";
import type { ServiceConnectedHook } from "@corbits/connections";
import {
CONNECTOR_REGISTRY,
MCP_PRESETS,
} from "@workbench/templates/connectors";
import { CONNECTOR_REGISTRY, MCP_PRESETS } from "./native-connector-registry";
import {
createProviderHealthPort,
createProviderHealthStore,
Expand Down Expand Up @@ -299,7 +292,6 @@ import {
launchScheduledDefinitionFromDb,
listScheduledDefinitionsFromDb,
runNowScheduledDefinition,
type ScheduledDeliveryJoinDeps,
} from "./workflow-scheduler";
import { createToolGrantsForPins } from "./tool-grants";
import { drainHubServer, shutdownHub } from "./shutdown";
Expand Down Expand Up @@ -1766,18 +1758,12 @@ export async function createHub(config: HubConfig) {
}),
}),
);
const scheduledDeliveryJoinDeps: ScheduledDeliveryJoinDeps = {
deliveryChatRequired: deliveryWorkbenchRequiredForWorkflowName,
resolveDeliveryChat: async (tenantId) => {
const rows = await chatStore.listWorkbenchSettings(tenantId);
const first = [...rows].sort((a, b) =>
a.workbenchId.localeCompare(b.workbenchId),
)[0];
return first?.workbenchId;
},
joinDeliveryChat: (input) =>
joinRunParticipant({ store: chatStore }, input),
};
// Hub-zero T4 (CL-8126): the scheduled delivery join is cut — a
// triggered run launches straight into the runner with no
// settings-row lookup and no chat join. Pre-cutover rows carry no
// migration burden: the join never persisted anything (it read the
// settings registry and joined the run to a chat in memory per
// trigger), so there is no backfill and no default to apply.
app.route(
`${TENANT_PREFIX}/workflows`,
createScheduledWorkflowRoutes({
Expand All @@ -1788,7 +1774,7 @@ export async function createHub(config: HubConfig) {
}),
runNow: async (args) =>
runNowScheduledDefinition(
{ db, sidecarRouter, ...scheduledDeliveryJoinDeps },
{ db, sidecarRouter },
{
tenantId: args.tenantId,
definitionId: args.definitionId,
Expand Down Expand Up @@ -2526,7 +2512,6 @@ export async function createHub(config: HubConfig) {
launch: launchScheduledDefinitionFromDb({
db,
sidecarRouter,
...scheduledDeliveryJoinDeps,
}),
...(config.routineSchedulerPollIntervalMs !== undefined
? { pollIntervalMs: config.routineSchedulerPollIntervalMs }
Expand Down
Loading
Loading