From 2ac52e43f6aafcc9e0317789cccab07f7a14bca5 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Tue, 20 Oct 2026 15:11:55 -0700 Subject: [PATCH 1/3] refactor(hub): remove duplicate library-kind-segments and empty workflows workspace --- apps/hub/src/library-kind-segments.ts | 40 ------------------------- apps/hub/src/server.ts | 7 ----- apps/web/src/api.ts | 12 -------- apps/web/src/shell/library-artifacts.ts | 5 ---- package.json | 9 +++--- scripts/run-all.ts | 1 - scripts/typecheck.ts | 1 - workflows/README.md | 8 ----- 8 files changed, 4 insertions(+), 79 deletions(-) delete mode 100644 apps/hub/src/library-kind-segments.ts delete mode 100644 workflows/README.md diff --git a/apps/hub/src/library-kind-segments.ts b/apps/hub/src/library-kind-segments.ts deleted file mode 100644 index 45f06ac915..0000000000 --- a/apps/hub/src/library-kind-segments.ts +++ /dev/null @@ -1,40 +0,0 @@ -// The hub's own copy of the Files kind-nav mapping, kept in step with -// `apps/web/src/library/kind-filter.ts` by hand: it buckets the same -// `ArtifactListRow`s `@corbits/artifacts`' `countArtifactsBySegments` -// walks, so the per-kind counts the web app's kind nav shows match what -// the web app itself filters client-side. - -import type { ArtifactListRow } from "@corbits/artifacts"; - -export const LIBRARY_KIND_SEGMENTS = ["document", "sheet", "pdf", "routine"] as const; - -function titleExtension(title: string): string { - const lower = title.trim().toLowerCase(); - const dot = lower.lastIndexOf("."); - if (dot <= 0 || dot === lower.length - 1) return ""; - return lower.slice(dot + 1); -} - -export function artifactMatchesLibraryKindSegment( - artifact: Pick, - segment: string, -): boolean { - const kind = artifact.kind.trim().toLowerCase(); - const ext = titleExtension(artifact.title); - - switch (segment) { - case "document": - if (kind === "document") return true; - return kind === "file" && (ext === "doc" || ext === "txt" || ext === "md"); - case "sheet": - if (kind === "csv-export" || kind === "sheet") return true; - return kind === "file" && (ext === "xls" || ext === "csv"); - case "pdf": - if (kind === "pdf") return true; - return kind === "file" && ext === "pdf"; - case "routine": - return kind === "routine"; - default: - return false; - } -} diff --git a/apps/hub/src/server.ts b/apps/hub/src/server.ts index 9ca0f1c675..1d49b2a8ba 100644 --- a/apps/hub/src/server.ts +++ b/apps/hub/src/server.ts @@ -132,7 +132,6 @@ import { mountWorkflowArtifacts, type WorkflowArtifactEnv, } from "@corbits/artifacts"; -import { artifactMatchesLibraryKindSegment, LIBRARY_KIND_SEGMENTS } from "./library-kind-segments"; import { workflowRun } from "@intx/db/schema"; import path from "node:path"; import { migrateHub } from "./migrate"; @@ -652,12 +651,6 @@ export async function createHubServer({ db, contentStore: artifactContentStore, requireGrant: createRequireGrant({ grantStore, conditionRegistry: grantConditionRegistry }), - countSegments: Object.fromEntries( - LIBRARY_KIND_SEGMENTS.map((segment) => [ - segment, - (row: { kind: string; title: string }) => artifactMatchesLibraryKindSegment(row, segment), - ]), - ), }); app.route(TENANT_PREFIX, artifactsApi); } diff --git a/apps/web/src/api.ts b/apps/web/src/api.ts index a67d24f08b..7ab80f8de8 100644 --- a/apps/web/src/api.ts +++ b/apps/web/src/api.ts @@ -57,17 +57,6 @@ export const ArtifactUploadResponseSchema = type({ artifacts: ArtifactRowSchema.array(), }); -// GET /api/tenants/:id/artifacts/counts — honest per-kind-segment counts -// over the tenant's full artifact list, computed by the hub so the Library -// nav never shows a number the page itself couldn't otherwise prove. -export const ArtifactCountsSchema = type({ - all: "number", - document: "number", - sheet: "number", - pdf: "number", - routine: "number", -}); - export type Profile = typeof UserProfile.infer; export type Principal = typeof PrincipalSummary.infer; export type TenantDetail = typeof TenantResponse.infer; @@ -77,7 +66,6 @@ export type AssetRow = typeof AssetWithOriginResponse.infer; export type ArtifactListItem = typeof ArtifactListItemSchema.infer; export type ArtifactListPage = typeof ArtifactListPageSchema.infer; export type ArtifactDetail = typeof ArtifactRowSchema.infer; -export type ArtifactCounts = typeof ArtifactCountsSchema.infer; // Stated structurally because the generic schema's inferred type rejects // plain literals. type Paginated = { data: T[]; nextCursor: string | null }; diff --git a/apps/web/src/shell/library-artifacts.ts b/apps/web/src/shell/library-artifacts.ts index 482d3c5c74..577e72b434 100644 --- a/apps/web/src/shell/library-artifacts.ts +++ b/apps/web/src/shell/library-artifacts.ts @@ -37,11 +37,6 @@ export function mapArtifactListToSummaries(rows: readonly ArtifactListRow[]): Ar return rows.map(artifactListRowToSummary); } -/** `GET /api/tenants/:id/artifacts/counts` — the Library kind nav's counts. */ -export function artifactCountsPath(tenantId: string): string { - return `/api/tenants/${tenantId}/artifacts/counts`; -} - // Throws with status on non-2xx so the page can surface an honest failure. export async function uploadArtifactFiles( tenantId: string, diff --git a/package.json b/package.json index 05a1359fcc..afb3ada383 100644 --- a/package.json +++ b/package.json @@ -6,15 +6,14 @@ "workspaces": [ "apps/*", "packages/*", - "vendor/intx/*", - "workflows/*" + "vendor/intx/*" ], "scripts": { "check": "bun run typecheck && bun run lint && bun run fmt:check && bun run test", "typecheck": "bun run scripts/typecheck.ts", - "lint": "oxlint apps packages workflows scripts e2e", - "fmt": "oxfmt apps packages workflows scripts e2e", - "fmt:check": "oxfmt --check apps packages workflows scripts e2e", + "lint": "oxlint apps packages scripts e2e", + "fmt": "oxfmt apps packages scripts e2e", + "fmt:check": "oxfmt --check apps packages scripts e2e", "build": "bun run scripts/run-all.ts build", "test": "bun run scripts/run-all.ts test", "test:e2e": "bun test e2e", diff --git a/scripts/run-all.ts b/scripts/run-all.ts index 38a92833dd..ed9f003bce 100644 --- a/scripts/run-all.ts +++ b/scripts/run-all.ts @@ -14,7 +14,6 @@ const WORKSPACE_ROOTS = [ "apps/*/package.json", "packages/*/package.json", "skills/*/package.json", - "workflows/*/package.json", "vendor/intx/*/package.json", ]; diff --git a/scripts/typecheck.ts b/scripts/typecheck.ts index 92888014a2..2d2fa5d70d 100644 --- a/scripts/typecheck.ts +++ b/scripts/typecheck.ts @@ -32,7 +32,6 @@ import { resolveConcurrency } from "./concurrency.ts"; const PROJECT_GLOBS = [ "apps/*/tsconfig.json", "packages/*/tsconfig.json", - "workflows/*/tsconfig.json", "vendor/intx/*/tsconfig.json", ]; diff --git a/workflows/README.md b/workflows/README.md deleted file mode 100644 index 3117a3ca6c..0000000000 --- a/workflows/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Workflow packages - -Owner ruling (2026-09-17, LESS IS MORE): the shipped workflow catalog is -deleted. The bench worker (`packages/worker`) creates workflows dynamically for a -tenant's own needs; there is no seeded catalog of ready-made workflows -any more. The `echo` fixture workflow is gone too — the e2e smoke suites -now deploy the `packages/worker` definition to prove the -mail-triggered contract works. From 47ac865e116a848f0888115f52b620b0b1b597dc Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Tue, 20 Oct 2026 15:11:55 -0700 Subject: [PATCH 2/3] docs: remove stale workflows/* workspace references --- AGENTS.md | 4 ++-- CONTRIBUTING.md | 2 +- VENDORED.md | 5 ++--- docs/GLOSSARY.md | 6 +++--- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bfc943f1d4..e6683953a0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,7 +13,7 @@ Repository-wide rules. `apps/*` have their own AGENTS.md with the rules for that ## Boundaries -- Interchange is stock. Code lives in `apps/*`, `packages/*`, and `workflows/*`. +- Interchange is stock. Code lives in `apps/*` and `packages/*`. - `vendor/intx` holds hand-copied fallbacks for capabilities not yet published to npm, tracked with kill dates in [VENDORED.md](VENDORED.md); the upstream repository is never modified. ## Architecture @@ -21,7 +21,7 @@ Repository-wide rules. `apps/*` have their own AGENTS.md with the rules for that - **Stock first.** Before writing logic, look for an Interchange route or primitive that already does it (see the reference below). Never reimplement credential resolution, agent launch, session orchestration, inference, ID generation, or the workflow runtime — consume `@intx/*` as published packages. - **Hub mounts only stock + Corbits libraries.** The hub mounts stock `@intx/hub-api` routes plus one Corbits mount block (`@corbits/mailbox`, `@corbits/memory`, `@corbits/artifacts`, `@corbits/webhooks`, `@corbits/cron`, `@corbits/mcp`, `@corbits/agent-token`, and OAuth login/refresh on `@corbits/oauth-core`); any other hub mount is cutover debt with a Linear issue, never a pattern to extend. - **A workbench is a plain Interchange tenant, nothing more.** The web client converges its own tenant, workflow deployments, credentials, and grants over stock routes on start; the hub never seeds data on a client's behalf. -- **Apps stay generic; packages own the domain.** A product rule inside `apps/*` belongs in a package. `workflows/*` are plain npm-shaped packages, deployable to any stock Interchange hub through the stock deploy route — this repo does not publish them to npm. +- **Apps stay generic; packages own the domain.** A product rule inside `apps/*` belongs in a package. - **`packages/worker` is the default harness** — the one definition package a bench's worker, and any agent Workbench creates, deploys on; it is trim (`index.ts` + `system-prompt.ts`) with no hub-calling code. - **Ancestor-walkable except grants.** Credentials, tools, and definitions inherit down the tenant tree; grants never do. - **Custom DB tables live on their own Postgres schema**, with foreign keys back to Interchange's tenant/principal tables. Each package ships one idempotent migration, which the hub applies itself at boot. A custom table that gets deleted is hard-removed: delete the schema code, no drop migration. Cutovers are breaking; reset the database. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e0a735879c..1fe886f91e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ Thanks for your interest in Corbits Workbench. - Contributions are accepted under the terms of the [Contributor License Agreement](CLA.md) and the project license: the application ([GPLv2 with AI Exception](LICENSE.md)), or LGPL-2.1 for - contributions to a library under `packages/` or `workflows/`. + contributions to a library under `packages/`. - [docs/local-dev.md](docs/local-dev.md) covers running the stack locally. - [AGENTS.md](AGENTS.md), and the `AGENTS.md` files under `apps/`, hold the architecture and code rules every change follows, whether a person or a diff --git a/VENDORED.md b/VENDORED.md index ccb78ab08a..d3bec015f9 100644 --- a/VENDORED.md +++ b/VENDORED.md @@ -56,9 +56,8 @@ Local deltas are restated in each package's own `VENDORED-FROM` file. - [ ] Delete `vendor/intx/` and remove it from the root `package.json` workspaces. -- [ ] Restore the `@intx/*` dependencies in `apps/*`, `packages/*`, and - `workflows/*` to the published npm version, and drop the root - `overrides` pins. +- [ ] Restore the `@intx/*` dependencies in `apps/*` and `packages/*` to + the published npm version, and drop the root `overrides` pins. - [ ] Delete the `vendor/intx/*` ledger rows above. - [ ] `bun install` - [ ] `bun run check` diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md index 6d82cd8967..c390378ef4 100644 --- a/docs/GLOSSARY.md +++ b/docs/GLOSSARY.md @@ -20,6 +20,6 @@ paths keep the platform's own names. ## Package naming -`@corbits/*` is the default scope for every package under `packages/`, -`tools/`, `agents/`, `skills/`, and `workflows/` — with a kebab-case kind -suffix where a package is one of a family (`-agent`, `-tools`). +`@corbits/*` is the default scope for every package under `packages/` — +with a kebab-case kind suffix where a package is one of a family +(`-agent`, `-tools`). From a5173a9c84f6968de2c8d348cb856c95b4bec9f0 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Tue, 20 Oct 2026 15:33:07 -0700 Subject: [PATCH 3/3] refactor(web): remove unused artifactCounts query key --- apps/web/src/query-client.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apps/web/src/query-client.ts b/apps/web/src/query-client.ts index 260a4355fb..668c3ec24e 100644 --- a/apps/web/src/query-client.ts +++ b/apps/web/src/query-client.ts @@ -81,10 +81,6 @@ export const tenantKeys = { visibleAgents: (tenantId: string) => ["tenant", tenantId, "agents", "visible"] as const, assets: (tenantId: string) => ["tenant", tenantId, "assets"] as const, artifacts: (tenantId: string) => ["tenant", tenantId, "artifacts"] as const, - // Nested under `artifacts` (not a sibling key) so one - // `invalidateQueries({ queryKey: tenantKeys.artifacts(tenantId) })` after - // an upload covers both the list and the kind-nav counts. - artifactCounts: (tenantId: string) => ["tenant", tenantId, "artifacts", "counts"] as const, credentials: (tenantId: string) => ["tenant", tenantId, "credentials"] as const, principals: (tenantId: string) => ["tenant", tenantId, "principals"] as const, roles: (tenantId: string) => ["tenant", tenantId, "roles"] as const, @@ -129,10 +125,6 @@ export function pathToQueryKey(path: string): readonly unknown[] { } const assets = /^\/api\/tenants\/([^/]+)\/assets$/.exec(path); if (assets?.[1] !== undefined) return tenantKeys.assets(assets[1]); - const artifactCounts = /^\/api\/tenants\/([^/]+)\/artifacts\/counts$/.exec(path); - if (artifactCounts?.[1] !== undefined) { - return tenantKeys.artifactCounts(artifactCounts[1]); - } const artifacts = /^\/api\/tenants\/([^/]+)\/artifacts(?:\?(.*))?$/.exec(path); if (artifacts?.[1] !== undefined) { return [...tenantKeys.artifacts(artifacts[1]), artifacts[2] ?? ""] as const;