diff --git a/apps/sidecar/src/workflow-child-boot-graph.test.ts b/apps/sidecar/src/workflow-child-boot-graph.test.ts deleted file mode 100644 index d927b6a9d3..0000000000 --- a/apps/sidecar/src/workflow-child-boot-graph.test.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { describe, test, expect } from "bun:test"; -import { readFileSync } from "node:fs"; -import { dirname, join } from "node:path"; - -// Boot-graph guard for the spawned workflow-child. -// -// The workflow-child evaluates its whole module graph on every cold start. It -// must never pull the control-plane database layer (`@intx/db` / `drizzle`) or -// the sidecar orchestrator (`@intx/hub-agent`) into that graph: the child -// never executes either, the database dependency is a layering violation, and -// importing the orchestrator that spawns the child is a backwards dependency. -// The db-free substrate is reachable via `@intx/hub-sessions/substrate` and -// the path helpers via `@intx/hub-agent/paths`; the package barrels are not. -// -// This walks the child's VALUE-import graph and fails if any forbidden module -// is reachable. `Bun.build` erases `import type` and type-only specifiers -// before resolution (the project's `verbatimModuleSyntax` + `isolatedModules` -// guarantee every value-syntax import that survives is a real runtime import), -// so the captured graph is exactly what the child evaluates at runtime. -// Dynamic `import()` edges are included too, so even a lazy import of a -// forbidden module is caught. npm and `node:` packages are recorded but not -// traversed; workspace (`@intx/*`) packages are resolved to their real source -// (honoring `exports` subpaths) and walked through. - -const repoRoot = process.cwd(); - -// The binary the sidecar spawns as the workflow-child. The graph walk derives -// its entrypoints from this file's actual imports, so anything the binary -// loads -- in any import form -- is part of the checked graph. -const CHILD_BINARY = "apps/sidecar/bin/workflow-child"; - -// The roots the binary is expected to import. The drift guard asserts the -// binary imports exactly these; a new root trips it so a human confirms the -// addition (and the walk covers the new root regardless). -const EXPECTED_CHILD_ROOTS = ["../src/workflow-substrate-factory", "@intx/workflow-host"]; - -// The module specifiers the binary VALUE-imports, in every runtime form -// (static `from`, side-effect `import`, dynamic `import()`, `require`). Bun's -// transpiler is the same machinery the bundler uses, so it ignores comments, -// strings, and member expressions like `Array.from(...)`, and -- crucially -- -// erases `import type` / type-only specifiers, keeping this list aligned with -// the runtime value graph the walk below captures. -function binaryImportSpecifiers(): string[] { - const raw = readFileSync(join(repoRoot, CHILD_BINARY), "utf8"); - // The transpiler does not accept the binary's - // `#!/usr/bin/env -S bun --conditions=intx-src` shebang. - const source = raw.replace(/^#![^\n]*\n/, ""); - const transpiler = new Bun.Transpiler({ loader: "ts" }); - const specs = new Set(transpiler.scanImports(source).map((i) => i.path)); - return [...specs].sort(); -} - -// Resolve the binary's imports to real entrypoint files. A root that cannot -// resolve throws here and fails the test loudly rather than yielding a graph -// built from an incomplete root set. -function binaryEntrypoints(): string[] { - const binDir = dirname(join(repoRoot, CHILD_BINARY)); - return binaryImportSpecifiers().map((spec) => Bun.resolveSync(spec, binDir)); -} - -/** - * Returns the package reason if a value-import specifier is forbidden in the - * child boot graph, or null otherwise. `@intx/db` / `drizzle-orm` are banned - * entirely; the `@intx/hub-sessions` and `@intx/hub-agent` *barrels* are banned - * (their `/substrate` and `/paths` subpaths are the supported db-free doors). - */ -function forbiddenReason(spec: string): string | null { - if (spec === "@intx/db" || spec.startsWith("@intx/db/")) { - return "control-plane database layer"; - } - if (spec === "drizzle-orm" || spec.startsWith("drizzle-orm/")) { - return "database ORM"; - } - if (spec === "@intx/hub-sessions") { - return "control-plane barrel; import @intx/hub-sessions/substrate instead"; - } - if (spec === "@intx/hub-agent") { - return "sidecar orchestrator barrel; import @intx/hub-agent/paths instead"; - } - return null; -} - -async function childValueImportGraph(entrypoints: string[]): Promise<{ - importers: Map>; - resolveFailures: string[]; - success: boolean; -}> { - const importers = new Map>(); - const resolveFailures: string[] = []; - const relativize = (p: string) => p.replace(`${repoRoot}/`, ""); - - const result = await Bun.build({ - entrypoints, - target: "bun", - throw: false, - plugins: [ - { - name: "track-value-imports", - setup(build) { - build.onResolve({ filter: /.*/ }, (args) => { - const spec = args.path; - const from = args.importer ? relativize(args.importer) : "entry"; - const seenFrom = importers.get(spec) ?? new Set(); - seenFrom.add(from); - importers.set(spec, seenFrom); - - // Relative imports: let Bun traverse natively. - if (spec.startsWith(".") || spec.startsWith("/")) return undefined; - // Leaves we record but do not walk into. - if (spec.startsWith("node:")) return { path: spec, external: true }; - if (!spec.startsWith("@intx/")) { - return { path: spec, external: true }; - } - // Workspace packages: resolve to real source so traversal continues - // through them (honoring `exports` subpaths like `/substrate`). A - // failure here would silently leave the subtree unwalked, so record - // it -- the test asserts there are none. - const fromDir = args.importer ? dirname(args.importer) : repoRoot; - try { - return { path: Bun.resolveSync(spec, fromDir) }; - } catch { - resolveFailures.push(`${spec} (from ${from})`); - return { path: spec, external: true }; - } - }); - }, - }, - ], - }); - - return { importers, resolveFailures, success: result.success }; -} - -describe("workflow-child boot graph", () => { - test("the child binary imports only the two known roots", () => { - expect(binaryImportSpecifiers()).toEqual([...EXPECTED_CHILD_ROOTS].sort()); - }); - - test("never value-imports the control-plane database or the orchestrator", async () => { - const { importers, resolveFailures, success } = - await childValueImportGraph(binaryEntrypoints()); - - expect(success).toBe(true); - // A workspace specifier that fails to resolve would drop its subtree from - // the walk while the build still succeeds; surface it rather than silently - // un-guarding that subtree. - expect(resolveFailures).toEqual([]); - - const reached = new Set(importers.keys()); - // Anti-vacuity: a workflow-child necessarily evaluates the workflow - // runtime, so a graph that did not reach it never walked the real child - // (e.g. empty entrypoints) and the forbidden check would pass over nothing. - expect(reached).toContain("@intx/workflow"); - - const violations = [...reached] - .map((spec) => ({ spec, reason: forbiddenReason(spec) })) - .filter((v): v is { spec: string; reason: string } => v.reason !== null); - - if (violations.length > 0) { - const detail = violations - .map((v) => { - const from = [...(importers.get(v.spec) ?? new Set())].join("\n "); - return ` ${v.spec} -- ${v.reason}\n imported by:\n ${from}`; - }) - .join("\n"); - throw new Error( - `The workflow-child boot graph reached forbidden modules:\n${detail}\n\n` + - "The spawned child must not evaluate the control-plane database or the " + - "sidecar orchestrator at boot. Use @intx/hub-sessions/substrate for the " + - "db-free repo/substrate symbols and @intx/hub-agent/paths for the deploy-tree " + - "and address helpers.", - ); - } - }); -}); diff --git a/apps/web/test/static-serving.test.ts b/apps/web/test/static-serving.test.ts deleted file mode 100644 index 5a7e76efa7..0000000000 --- a/apps/web/test/static-serving.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -// The one-origin contract: this app builds to a static directory, and the -// hub's HUB_STATIC_DIR must name that same directory, so the interface and -// the /api routes share an origin with no cross-origin configuration. - -import { expect, test } from "bun:test"; -import path from "node:path"; - -import config from "../vite.config"; - -const appDir = path.resolve(import.meta.dir, ".."); -const repoRoot = path.resolve(appDir, "../.."); - -test("assets are addressed from the origin root", () => { - expect(config.base ?? "/").toBe("/"); -}); - -test("the build lands where .env.example points the hub", async () => { - const outDir = path.resolve(appDir, config.build?.outDir ?? "dist"); - const example = await Bun.file(path.join(repoRoot, ".env.example")).text(); - const staticDir = /^HUB_STATIC_DIR=(.+)$/m.exec(example)?.[1]; - if (staticDir === undefined) { - throw new Error(".env.example does not set HUB_STATIC_DIR"); - } - // The hub resolves a relative HUB_STATIC_DIR against its own working - // directory, which is apps/hub under the dev bootstrap. - const hubWorkingDir = path.join(repoRoot, "apps", "hub"); - expect(path.resolve(hubWorkingDir, staticDir)).toBe(outDir); -});