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
14 changes: 13 additions & 1 deletion apps/hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,38 @@
"@corbits/approvals": "workspace:*",
"@corbits/artifacts": "github:corbitsdev/corbits-artifacts#dc435ba749b4378f19e5de772159c2a57e932938",
"@corbits/artifacts-hub": "workspace:*",
"@corbits/attio-task-agent-workflow": "workspace:*",
"@corbits/bench": "workspace:*",
"@corbits/catalog-tools": "workspace:*",
"@corbits/chat": "workspace:*",
"@corbits/code-review-workflow": "workspace:*",
"@corbits/collateral-generation-workflow": "workspace:*",
"@corbits/commands": "workspace:*",
"@corbits/error-sink": "workspace:*",
"@corbits/credential-providers": "workspace:*",
"@corbits/diligence-brief-workflow": "workspace:*",
"@corbits/docker-provisioner": "workspace:*",
"@corbits/e2b-sandbox-sidecar": "workspace:*",
"@corbits/echo-workflow": "workspace:*",
"@corbits/evals": "workspace:*",
"@corbits/exa-topic-watch-workflow": "workspace:*",
"@corbits/granola-call-workflow": "workspace:*",
"@corbits/inbox": "workspace:*",
"@corbits/inference-catalog": "workspace:*",
"@corbits/last-30-days-research-workflow": "workspace:*",
"@corbits/insights": "workspace:*",
"@corbits/mailbox": "github:corbitsdev/corbits-mailbox#118a2cd3b0b2d83abcd57b4bed1698e95ee41b6d",
"@corbits/mcp-tools": "workspace:*",
"@corbits/memory": "github:corbitsdev/corbits-memory#9e6f213fa2c002b531d3f6af1aa0abd737b8afe3",
"@corbits/memory-hub": "workspace:*",
"@corbits/morning-brief-workflow": "workspace:*",
"@corbits/notify": "workspace:*",
"@corbits/pain-point-collateral-workflow": "workspace:*",
"@corbits/preferences": "workspace:*",
"@corbits/presence": "workspace:*",
"@corbits/process-granola-call-workflow": "workspace:*",
"@corbits/process-provisioner": "workspace:*",
"@corbits/reddit-opportunity-scanner-workflow": "workspace:*",
"@corbits/run-key-history": "workspace:*",
"@corbits/run-scope": "workspace:*",
"@corbits/skills": "workspace:*",
Expand All @@ -47,6 +59,7 @@
"@corbits/turn-artifacts": "workspace:*",
"@corbits/url-path": "workspace:*",
"@corbits/webhook-triggers": "workspace:*",
"@corbits/workbench-digest-workflow": "workspace:*",
"@corbits/workflows": "workspace:*",
"@intx/authz": "0.3.0",
"@intx/crypto": "0.3.0",
Expand All @@ -64,7 +77,6 @@
"@corbits/codex-provider": "workspace:*",
"@corbits/xai-provider": "workspace:*",
"@corbits/hub-api-client": "workspace:*",
"@workbench/onboarding": "workspace:*",
"@workbench/templates": "workspace:*",
"arktype": "catalog:",
"better-auth": "catalog:",
Expand Down
21 changes: 9 additions & 12 deletions apps/hub/src/templates/block-workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
// (CL-6405) generalized to any catalog entry with a source package
// under `workflows/<name>` (CL-7073), not just `code-review`.
//
// Runtime wiring (CL-7585): the per-workflow `buildJson` closures (each
// one closing over its workflow package's trigger address and turn
// timeouts) live on `@workbench/onboarding`'s tenant-seed
// `CATALOG_WORKFLOWS` entries, reached here through
// `deployableCatalogWorkflow` — the hub names no builder of its own, so
// Runtime wiring (CL-8113, hub-zero T2): the per-workflow `buildJson`
// closures (each one closing over its workflow package's trigger
// address and turn timeouts) live on this hub's own native
// `./catalog-blocks` `CATALOG_BLOCKS` entries, reached here through
// `deployableCatalogBlock` — the hub names no builder of its own, so
// the catalog's deployable set and the block sources served here can
// never drift apart silently. `assistant` (seeded, never redeployed
// here) and `heartbeat` (test-only, never deployed onto a real bench)
// are outside `CATALOG_WORKFLOWS`, so they answer `undefined` here, same
// are outside `CATALOG_BLOCKS`, so they answer `undefined` here, same
// as any name outside the catalog entirely — a route answering
// `undefined` as a 404 is the honest statement of that.
//
Expand All @@ -20,10 +20,7 @@
// `@intx/agent`/`@intx/workflow`. Only `./template-block-routes.ts`
// (mounted in `apps/hub`) imports this; it is deliberately not
// re-exported from the package root.
import { deployableCatalogWorkflow } from "@workbench/onboarding/tenant-seed";
// TODO(CL-8113, hub-zero T2): this tenant-seed import is the hub's last
// `@workbench/onboarding` edge — cut it over to the native catalog when
// T2 lands, then drop the dependency from `apps/hub/package.json`.
import { deployableCatalogBlock } from "./catalog-blocks";

export interface BlockWorkflowBuildInput {
readonly tenantDomain: string;
Expand All @@ -42,15 +39,15 @@ export interface BlockWorkflowSource {
/**
* The serialized source-form definition for one catalog workflow — or
* `undefined` for `assistant`, `heartbeat`, and any name outside the
* catalog. `buildJson` is a required field on every `CATALOG_WORKFLOWS`
* catalog. `buildJson` is a required field on every `CATALOG_BLOCKS`
* entry, so a catalog name always answers here: the deployable set and
* the served sources cannot drift apart silently.
*/
export function buildBlockWorkflowSource(
assetName: string,
input: BlockWorkflowBuildInput,
): BlockWorkflowSource | undefined {
const workflow = deployableCatalogWorkflow(assetName);
const workflow = deployableCatalogBlock(assetName);
if (workflow === undefined) {
return undefined;
}
Expand Down
61 changes: 61 additions & 0 deletions apps/hub/src/templates/catalog-blocks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Pins the hub's native catalog-block adapter (`./catalog-blocks`): the
// deployable set served by `POST /template-blocks/:assetName/deploy`,
// rendered from the `workflows/*` builder packages against
// `@corbits/workflows` catalog metadata — with zero import from
// `@workbench/onboarding`.
//
// ANTI-DRIFT PIN: the adapter's asset names are exactly the native
// `WORKFLOW_CATALOG` names minus `assistant` (seeded already, never
// redeployed here) and `heartbeat` (test-only, never deployed onto a
// real bench). A workflow added to the catalog without a builder entry
// here — or a builder entry with no catalog backing — fails this test,
// so the deployable set and the served sources cannot drift apart
// silently.
import { describe, expect, test } from "bun:test";
import { WORKFLOW_CATALOG } from "@corbits/workflows";

import {
CATALOG_BLOCK_ASSET_NAMES,
deployableCatalogBlock,
} from "./catalog-blocks";

const FAKE_PREFERENCES = [{ provider: "anthropic", model: "claude-sonnet-5" }];
const TENANT_DOMAIN = "acme.example";

describe("native catalog-block adapter", () => {
test("the deployable set is the native catalog minus assistant and heartbeat", () => {
const expected = WORKFLOW_CATALOG.map((entry) => entry.assetName)
.filter((name) => name !== "assistant" && name !== "heartbeat")
.sort();
expect([...CATALOG_BLOCK_ASSET_NAMES].sort()).toEqual(expected);
});

test("every deployable block renders its real definition for the tenant", () => {
for (const assetName of CATALOG_BLOCK_ASSET_NAMES) {
const block = deployableCatalogBlock(assetName);
expect(block).toBeDefined();
expect(block?.assetName).toBe(assetName);
expect(block?.displayName.length).toBeGreaterThan(0);
const parsed = JSON.parse(
block?.buildJson(TENANT_DOMAIN, FAKE_PREFERENCES) ?? "",
) as { stepOrder?: readonly string[] };
expect(parsed.stepOrder?.length).toBeGreaterThan(0);
expect(block?.buildJson(TENANT_DOMAIN, FAKE_PREFERENCES)).toContain(
assetName,
);
}
});

test("code-review stamps the requesting tenant's trigger address", () => {
const block = deployableCatalogBlock("code-review");
expect(block?.buildJson(TENANT_DOMAIN, FAKE_PREFERENCES)).toContain(
"code-review@acme.example",
);
});

test("assistant, heartbeat, and unknown names answer undefined", () => {
expect(deployableCatalogBlock("assistant")).toBeUndefined();
expect(deployableCatalogBlock("heartbeat")).toBeUndefined();
expect(deployableCatalogBlock("does-not-exist")).toBeUndefined();
});
});
Loading
Loading