mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-07 17:04:42 +00:00
feat(workspaces): publish external runtime contract
Publish the rootless workspace runtime protocol, enforce module and host-execution boundaries, and prove an independently authored command runtime across lifecycle, PTY, files, Git, providers, and CI.
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import ts from "typescript";
|
||||
|
||||
const SERVER_ROOT = "packages/server/src/server";
|
||||
const FIXTURE_ROOT = "packages/fixture-workspace-runtime";
|
||||
const migratedOwners = [
|
||||
"/session/files/",
|
||||
"/session/git-mutation/",
|
||||
"/session/provider/",
|
||||
"/session/workspace-git-observer/",
|
||||
"/session/workspace-provisioning/",
|
||||
"/session/workspace-scripts/",
|
||||
];
|
||||
const hostModules = new Set(["child_process", "fs", "fs/promises", "module", "which"]);
|
||||
|
||||
export async function findWorkspaceBoundaryViolations(repoRoot) {
|
||||
const files = [
|
||||
...(await collectSources(path.join(repoRoot, SERVER_ROOT))),
|
||||
...(await collectSources(path.join(repoRoot, FIXTURE_ROOT))),
|
||||
];
|
||||
const modules = await Promise.all(
|
||||
files.map(async (filename) => {
|
||||
const source = await readFile(filename, "utf8");
|
||||
return { filename, sourceFile: parseSource(filename, source) };
|
||||
}),
|
||||
);
|
||||
const publicOwners = new Set(
|
||||
modules
|
||||
.filter(({ sourceFile }) => staticSpecifiers(sourceFile).some(isPublicEntryPoint))
|
||||
.map(({ filename }) => toRepoPath(repoRoot, filename)),
|
||||
);
|
||||
const violations = [];
|
||||
for (const { filename, sourceFile } of modules) {
|
||||
const importer = toRepoPath(repoRoot, filename);
|
||||
const external = importer.startsWith(`${FIXTURE_ROOT}/`);
|
||||
const governed =
|
||||
external ||
|
||||
(!isTestFile(importer) && (ownsMigratedSurface(importer) || publicOwners.has(importer)));
|
||||
for (const load of moduleLoads(sourceFile)) {
|
||||
if (load.specifier === null) {
|
||||
if (governed) violations.push(violation(importer, "<computed>", "nonliteral-module-load"));
|
||||
continue;
|
||||
}
|
||||
const specifier = load.specifier;
|
||||
if (isForbiddenInternal(importer, specifier)) {
|
||||
violations.push(violation(importer, specifier, "module-entrypoint"));
|
||||
}
|
||||
if (governed && !external && isHostCapability(specifier)) {
|
||||
violations.push(violation(importer, specifier, "workspace-host-access"));
|
||||
}
|
||||
if (external && isExternalEscape(importer, specifier)) {
|
||||
violations.push(violation(importer, specifier, "external-runtime-contract"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return violations.sort((left, right) =>
|
||||
`${left.file}:${left.import}:${left.rule}`.localeCompare(
|
||||
`${right.file}:${right.import}:${right.rule}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function parseSource(filename, source) {
|
||||
return ts.createSourceFile(
|
||||
filename,
|
||||
source,
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
filename.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
|
||||
);
|
||||
}
|
||||
|
||||
function staticSpecifiers(sourceFile) {
|
||||
const specifiers = [];
|
||||
for (const statement of sourceFile.statements) {
|
||||
if (
|
||||
(ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement)) &&
|
||||
statement.moduleSpecifier &&
|
||||
ts.isStringLiteral(statement.moduleSpecifier)
|
||||
) {
|
||||
specifiers.push(statement.moduleSpecifier.text);
|
||||
}
|
||||
}
|
||||
return specifiers;
|
||||
}
|
||||
|
||||
function moduleLoads(sourceFile) {
|
||||
const loads = [];
|
||||
visit(sourceFile);
|
||||
return loads;
|
||||
|
||||
function visit(node) {
|
||||
if (
|
||||
ts.isCallExpression(node) &&
|
||||
(node.expression.kind === ts.SyntaxKind.ImportKeyword ||
|
||||
(ts.isIdentifier(node.expression) && node.expression.text === "require"))
|
||||
) {
|
||||
const argument = node.arguments.length === 1 ? node.arguments[0] : undefined;
|
||||
loads.push({ specifier: argument && ts.isStringLiteral(argument) ? argument.text : null });
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
}
|
||||
}
|
||||
|
||||
function isPublicEntryPoint(specifier) {
|
||||
return /workspace-(?:runtime|helper)\/index\.(?:js|ts)$/.test(specifier);
|
||||
}
|
||||
|
||||
function ownsMigratedSurface(importer) {
|
||||
return !isTestFile(importer) && migratedOwners.some((owner) => importer.includes(owner));
|
||||
}
|
||||
|
||||
function isHostCapability(specifier) {
|
||||
const normalized = specifier.startsWith("node:") ? specifier.slice(5) : specifier;
|
||||
return (
|
||||
hostModules.has(normalized) || /(?:^|\/)utils\/run-git-command\.(?:js|ts)$/.test(normalized)
|
||||
);
|
||||
}
|
||||
|
||||
function isForbiddenInternal(importer, specifier) {
|
||||
const target = resolveSpecifier(importer, specifier);
|
||||
if (!target || !target.includes("/internal/")) return false;
|
||||
if (target.includes("/workspace-runtime/command/internal/")) {
|
||||
return !(
|
||||
importer.endsWith("/workspace-runtime/command/index.ts") ||
|
||||
importer.includes("/workspace-runtime/command/internal/")
|
||||
);
|
||||
}
|
||||
if (target.includes("/workspace-runtime/git-observation/internal/")) {
|
||||
return !(
|
||||
importer.endsWith("/workspace-runtime/git-observation/index.ts") ||
|
||||
importer.includes("/workspace-runtime/git-observation/internal/") ||
|
||||
importer.endsWith("/workspace-runtime/internal/service.ts")
|
||||
);
|
||||
}
|
||||
if (target.includes("/workspace-runtime/internal/")) {
|
||||
return !(
|
||||
importer.endsWith("/workspace-runtime/index.ts") ||
|
||||
importer.includes("/workspace-runtime/internal/")
|
||||
);
|
||||
}
|
||||
if (target.includes("/workspace-helper/internal/")) {
|
||||
return !(
|
||||
importer.endsWith("/workspace-helper/index.ts") ||
|
||||
importer.includes("/workspace-helper/internal/") ||
|
||||
importer.endsWith("/workspace-runtime/internal/service.ts")
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isExternalEscape(importer, specifier) {
|
||||
const normalized = specifier.startsWith("node:") ? specifier.slice(5) : specifier;
|
||||
if (normalized === "module") return true;
|
||||
if (specifier.startsWith("node:") || hostModules.has(specifier) || specifier === "node-pty") {
|
||||
return false;
|
||||
}
|
||||
if (specifier === "@getpaseo/workspace-runtime-contract") return false;
|
||||
if (path.posix.isAbsolute(specifier) || specifier.startsWith("file:")) return true;
|
||||
if (specifier.startsWith(".")) {
|
||||
const target = path.posix.normalize(path.posix.join(path.posix.dirname(importer), specifier));
|
||||
return !target.startsWith(`${FIXTURE_ROOT}/`);
|
||||
}
|
||||
return (
|
||||
specifier === "@getpaseo/server" ||
|
||||
specifier.startsWith("@getpaseo/server/") ||
|
||||
specifier.includes("packages/server/") ||
|
||||
specifier.includes("/internal/")
|
||||
);
|
||||
}
|
||||
|
||||
function resolveSpecifier(importer, specifier) {
|
||||
if (specifier.startsWith(".")) {
|
||||
return path.posix.normalize(path.posix.join(path.posix.dirname(importer), specifier));
|
||||
}
|
||||
return specifier.replace(/^@getpaseo\/server\/?/, `${SERVER_ROOT}/`);
|
||||
}
|
||||
|
||||
function isTestFile(filename) {
|
||||
return /\.(?:test|spec)\.[^.]+$/.test(filename);
|
||||
}
|
||||
|
||||
function violation(file, imported, rule) {
|
||||
return {
|
||||
file,
|
||||
import: imported,
|
||||
rule,
|
||||
message: `${file}: ${rule} forbids ${imported}`,
|
||||
};
|
||||
}
|
||||
|
||||
async function collectSources(directory) {
|
||||
let entries;
|
||||
try {
|
||||
entries = await readdir(directory, { withFileTypes: true });
|
||||
} catch (error) {
|
||||
if (error.code === "ENOENT") return [];
|
||||
throw error;
|
||||
}
|
||||
const nested = await Promise.all(
|
||||
entries.map((entry) => {
|
||||
const target = path.join(directory, entry.name);
|
||||
if (entry.isDirectory()) return collectSources(target);
|
||||
return [".js", ".mjs", ".ts", ".tsx"].includes(path.extname(entry.name)) ? [target] : [];
|
||||
}),
|
||||
);
|
||||
return nested.flat();
|
||||
}
|
||||
|
||||
function toRepoPath(repoRoot, filename) {
|
||||
return path.relative(repoRoot, filename).split(path.sep).join("/");
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const violations = await findWorkspaceBoundaryViolations(repoRoot);
|
||||
for (const item of violations) process.stderr.write(`${item.message}\n`);
|
||||
if (violations.length > 0) process.exitCode = 1;
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
await main();
|
||||
}
|
||||
@@ -119,6 +119,7 @@ test("focused contracts stay inside existing required checks", () => {
|
||||
assert.doesNotMatch(changes, /Install dependencies|npm run build/);
|
||||
|
||||
assert.match(server, /test:hub-cli-contract/);
|
||||
assert.match(server, /npm test --workspace=@getpaseo\/workspace-runtime-contract/);
|
||||
assert.match(server, /npm run test --workspace=@getpaseo\/server/);
|
||||
assert.ok(!jobs.has("hub-cli-contract"));
|
||||
|
||||
@@ -159,7 +160,12 @@ test("PR routing declares stable behavior ownership", () => {
|
||||
],
|
||||
quality: ["**/*.{cjs,js,json,jsx,mjs,ts,tsx}", "packages/expo-two-way-audio/**"],
|
||||
hub: ["packages/cli/src/commands/hub/**", "packages/server/src/server/hub/**"],
|
||||
server: ["packages/server/**", "packages/app/e2e/support/fixtures/recording.*"],
|
||||
server: [
|
||||
"packages/server/**",
|
||||
"packages/fixture-workspace-runtime/**",
|
||||
"packages/workspace-runtime-contract/**",
|
||||
"packages/app/e2e/support/fixtures/recording.*",
|
||||
],
|
||||
desktop: [
|
||||
"packages/desktop/**",
|
||||
"packages/app/src/desktop/**",
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { copyFile, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
import { findWorkspaceBoundaryViolations } from "./check-workspace-runtime-boundaries.mjs";
|
||||
|
||||
const run = promisify(execFile);
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const oxlint = path.join(repoRoot, "node_modules/.bin/oxlint");
|
||||
|
||||
test("Oxlint rejects static host imports, re-exports, aliases, and internal package paths", async (t) => {
|
||||
const root = await fixtureRoot(t);
|
||||
await copyFile(path.join(repoRoot, ".oxlintrc.json"), path.join(root, ".oxlintrc.json"));
|
||||
await source(
|
||||
root,
|
||||
"packages/server/src/server/session/workspace-scripts/forbidden.ts",
|
||||
'import { spawn } from "child_process";\n' +
|
||||
'import { readFile } from "node:fs/promises";\n' +
|
||||
'import { createRequire as makeRequire } from "node:module";\n' +
|
||||
"const renamedLoader = makeRequire(import.meta.url);\n" +
|
||||
'renamedLoader("child_process");\n' +
|
||||
'renamedLoader("fs");\n' +
|
||||
'renamedLoader("@getpaseo/server/workspace-runtime/internal/service.js");\n' +
|
||||
'export * from "@getpaseo/server/workspace-runtime/internal/service.js";\n' +
|
||||
'export * from "@getpaseo/server/workspace-runtime/command/internal/command-runtime.js";\n' +
|
||||
'export * from "@getpaseo/server/workspace-runtime/git-observation/internal/integration.js";\n' +
|
||||
'export { default as helper } from "../../workspace-helper/internal/client.js";\n' +
|
||||
"void spawn; void readFile;\n",
|
||||
);
|
||||
await source(
|
||||
root,
|
||||
"packages/fixture-workspace-runtime/src/forbidden.mjs",
|
||||
'import "@getpaseo/server/workspace-runtime/command/internal/command-runtime.js";\n' +
|
||||
'import { createRequire as externalRequire } from "module";\n' +
|
||||
"const fixtureLoader = externalRequire(import.meta.url);\n" +
|
||||
'fixtureLoader("node:fs");\n' +
|
||||
'export * from "../../../packages/server/src/server/workspace-helper/internal/client.js";\n',
|
||||
);
|
||||
|
||||
const error = await captureFailure(run(oxlint, ["packages"], { cwd: root }));
|
||||
const output = `${error.stdout ?? ""}${error.stderr ?? ""}`;
|
||||
assert.match(output, /child_process/);
|
||||
assert.match(output, /node:fs\/promises/);
|
||||
assert.match(output, /node:module/);
|
||||
assert.match(output, /'module'/);
|
||||
assert.match(output, /workspace-runtime\/internal/);
|
||||
assert.match(output, /workspace-runtime\/command\/internal/);
|
||||
assert.match(output, /git-observation\/internal/);
|
||||
assert.match(output, /workspace-helper\/internal/);
|
||||
});
|
||||
|
||||
test("the AST guard rejects computed loading and dynamic host/internal bypasses", async (t) => {
|
||||
const root = await fixtureRoot(t);
|
||||
await source(
|
||||
root,
|
||||
"packages/server/src/server/session/workspace-scripts/forbidden.ts",
|
||||
'import type { WorkspaceRuntimeService } from "../../workspace-runtime/index.js";\n' +
|
||||
'require("fs");\n' +
|
||||
'import("node:child_process");\n' +
|
||||
'const { createRequire: makeLoader } = require("module");\n' +
|
||||
'const renamedLoader = makeLoader(import.meta.url); renamedLoader("fs");\n' +
|
||||
'require("node:" + "fs/promises");\n' +
|
||||
"import(`@getpaseo/server/${segment}`);\n",
|
||||
);
|
||||
await source(
|
||||
root,
|
||||
"packages/server/src/server/session/business.ts",
|
||||
'require("@getpaseo/server/workspace-runtime/command/internal/command-runtime.js");\n',
|
||||
);
|
||||
await source(
|
||||
root,
|
||||
"packages/fixture-workspace-runtime/src/forbidden.mjs",
|
||||
'require("../../../packages/server/src/server/workspace-runtime/internal/service.js");\n' +
|
||||
'import("@getpaseo/" + packageName);\n',
|
||||
);
|
||||
|
||||
const violations = await findWorkspaceBoundaryViolations(root);
|
||||
assert.deepEqual(
|
||||
violations.map(({ rule }) => rule).sort(),
|
||||
[
|
||||
"external-runtime-contract",
|
||||
"module-entrypoint",
|
||||
"module-entrypoint",
|
||||
"nonliteral-module-load",
|
||||
"nonliteral-module-load",
|
||||
"nonliteral-module-load",
|
||||
"workspace-host-access",
|
||||
"workspace-host-access",
|
||||
"workspace-host-access",
|
||||
].sort(),
|
||||
);
|
||||
});
|
||||
|
||||
test("owned integrations, explicit legacy code, and fixture contract imports pass", async (t) => {
|
||||
const root = await fixtureRoot(t);
|
||||
await copyFile(path.join(repoRoot, ".oxlintrc.json"), path.join(root, ".oxlintrc.json"));
|
||||
await source(
|
||||
root,
|
||||
"packages/server/src/server/workspace-runtime/index.ts",
|
||||
'import { service } from "./internal/service.js"; void service;\n',
|
||||
);
|
||||
await source(
|
||||
root,
|
||||
"packages/server/src/server/workspace-runtime/command/index.ts",
|
||||
'export * from "./internal/command-runtime.js";\n',
|
||||
);
|
||||
await source(
|
||||
root,
|
||||
"packages/server/src/server/workspace-runtime/internal/service.ts",
|
||||
'import { git } from "../git-observation/internal/integration.js";\n' +
|
||||
'import { helper } from "../../workspace-helper/internal/integration/index.js";\n' +
|
||||
"export const service = [git, helper];\n",
|
||||
);
|
||||
await source(
|
||||
root,
|
||||
"packages/server/src/server/workspace-git-service.ts",
|
||||
'import { readFile } from "fs/promises"; void readFile;\n',
|
||||
);
|
||||
await source(
|
||||
root,
|
||||
"packages/fixture-workspace-runtime/src/index.mjs",
|
||||
'import { CommandRuntimeControlSchema } from "@getpaseo/workspace-runtime-contract";\n' +
|
||||
'import { spawn } from "node:child_process";\n' +
|
||||
'import("./nested.mjs"); void CommandRuntimeControlSchema; void spawn;\n',
|
||||
);
|
||||
await source(
|
||||
root,
|
||||
"packages/server/src/server/workspace-runtime/git-observation/internal/integration.ts",
|
||||
"export const git = 1;\n",
|
||||
);
|
||||
await source(
|
||||
root,
|
||||
"packages/server/src/server/workspace-helper/internal/integration/index.ts",
|
||||
"export const helper = 1;\n",
|
||||
);
|
||||
await source(
|
||||
root,
|
||||
"packages/fixture-workspace-runtime/src/nested.mjs",
|
||||
"export const nested = 1;\n",
|
||||
);
|
||||
|
||||
assert.deepEqual(await findWorkspaceBoundaryViolations(root), []);
|
||||
try {
|
||||
await run(oxlint, ["packages"], { cwd: root });
|
||||
} catch (error) {
|
||||
assert.fail(`${error.stdout ?? ""}${error.stderr ?? ""}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("strict public schemas and helper argv reject root authority independent of syntax", async (t) => {
|
||||
await run("npm", ["run", "build", "--workspace=@getpaseo/workspace-runtime-contract"], {
|
||||
cwd: repoRoot,
|
||||
});
|
||||
const contract = await import(
|
||||
pathToFileURL(path.join(repoRoot, "packages/workspace-runtime-contract/dist/index.js"))
|
||||
);
|
||||
const key = ["ro", "ot"].join("");
|
||||
const state = { workspaceId: "workspace-01", lifecycle: "ready", [key]: "/private" };
|
||||
const schemaAlias = contract.CommandRuntimeStateSchema;
|
||||
assert.throws(() => schemaAlias.parse(state), /unrecognized key/i);
|
||||
assert.throws(
|
||||
() =>
|
||||
contract.CommandRuntimeLifecycleResponseSchema.parse({
|
||||
type: "state",
|
||||
protocolVersion: 1,
|
||||
state: { ...state },
|
||||
placement: { cwd: "/workspace" },
|
||||
}),
|
||||
/unrecognized key/i,
|
||||
);
|
||||
|
||||
const root = await fixtureRoot(t);
|
||||
const helperError = await captureFailure(
|
||||
run(
|
||||
process.execPath,
|
||||
[
|
||||
path.join(repoRoot, "packages/server/src/server/workspace-helper/executable.mjs"),
|
||||
"fs-stat",
|
||||
`--${key}`,
|
||||
"/private",
|
||||
"--path",
|
||||
".",
|
||||
],
|
||||
{ cwd: root },
|
||||
),
|
||||
);
|
||||
assert.match(helperError.stderr, /Unknown workspace-helper argument: --root/);
|
||||
});
|
||||
|
||||
async function captureFailure(promise) {
|
||||
try {
|
||||
await promise;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
assert.fail("Expected command to fail");
|
||||
}
|
||||
|
||||
async function fixtureRoot(t) {
|
||||
const root = await mkdtemp(path.join(tmpdir(), "workspace-boundaries-"));
|
||||
t.after(() => rm(root, { recursive: true, force: true }));
|
||||
return root;
|
||||
}
|
||||
|
||||
async function source(root, relativePath, contents) {
|
||||
const filename = path.join(root, relativePath);
|
||||
await mkdir(path.dirname(filename), { recursive: true });
|
||||
await writeFile(filename, contents);
|
||||
}
|
||||
Reference in New Issue
Block a user