mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-24 10:12:44 +00:00
feat(workspaces): define project runtime source authority
Route probe and user materialization through one projectRuntimeSource seam, where persisted Git source overrides the host-only convenience root and must belong to Git project data. Lock default revision, explicit revision, and subdirectory semantics with real Docker acceptance. Enforce provider-probe internals structurally and document that the current Docker POC needs a locally built image because its default reference is unpublished.
This commit is contained in:
@@ -41,6 +41,11 @@ export async function findWorkspaceBoundaryViolations(repoRoot) {
|
||||
const governed =
|
||||
external ||
|
||||
(!isTestFile(importer) && (ownsMigratedSurface(importer) || publicOwners.has(importer)));
|
||||
for (const specifier of staticSpecifiers(sourceFile)) {
|
||||
if (!isTestFile(importer) && isForbiddenInternal(importer, specifier)) {
|
||||
violations.push(violation(importer, specifier, "module-entrypoint"));
|
||||
}
|
||||
}
|
||||
for (const load of moduleLoads(sourceFile)) {
|
||||
if (load.specifier === null) {
|
||||
if (governed) violations.push(violation(importer, "<computed>", "nonliteral-module-load"));
|
||||
@@ -91,6 +96,8 @@ function staticSpecifiers(sourceFile) {
|
||||
|
||||
function moduleLoads(sourceFile) {
|
||||
const loads = [];
|
||||
const constants = constantBindings(sourceFile);
|
||||
const loaderAliases = moduleLoaderAliases(sourceFile);
|
||||
visit(sourceFile);
|
||||
return loads;
|
||||
|
||||
@@ -98,15 +105,88 @@ function moduleLoads(sourceFile) {
|
||||
if (
|
||||
ts.isCallExpression(node) &&
|
||||
(node.expression.kind === ts.SyntaxKind.ImportKeyword ||
|
||||
(ts.isIdentifier(node.expression) && node.expression.text === "require"))
|
||||
(ts.isIdentifier(node.expression) && loaderAliases.has(node.expression.text)))
|
||||
) {
|
||||
const argument = node.arguments.length === 1 ? node.arguments[0] : undefined;
|
||||
loads.push({ specifier: argument && ts.isStringLiteral(argument) ? argument.text : null });
|
||||
loads.push({ specifier: argument ? constantString(argument, constants) : null });
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
}
|
||||
}
|
||||
|
||||
function constantBindings(sourceFile) {
|
||||
const constants = new Map();
|
||||
let changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (const statement of sourceFile.statements) {
|
||||
if (!ts.isVariableStatement(statement)) continue;
|
||||
if (!(statement.declarationList.flags & ts.NodeFlags.Const)) continue;
|
||||
for (const declaration of statement.declarationList.declarations) {
|
||||
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
||||
const value = constantString(declaration.initializer, constants);
|
||||
if (value === null || constants.get(declaration.name.text) === value) continue;
|
||||
constants.set(declaration.name.text, value);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return constants;
|
||||
}
|
||||
|
||||
function moduleLoaderAliases(sourceFile) {
|
||||
const aliases = new Set(["require"]);
|
||||
let changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (const statement of sourceFile.statements) {
|
||||
if (!ts.isVariableStatement(statement)) continue;
|
||||
for (const declaration of statement.declarationList.declarations) {
|
||||
if (
|
||||
!ts.isIdentifier(declaration.name) ||
|
||||
!declaration.initializer ||
|
||||
!ts.isIdentifier(declaration.initializer) ||
|
||||
!aliases.has(declaration.initializer.text) ||
|
||||
aliases.has(declaration.name.text)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
aliases.add(declaration.name.text);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return aliases;
|
||||
}
|
||||
|
||||
function constantString(expression, constants) {
|
||||
if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) {
|
||||
return expression.text;
|
||||
}
|
||||
if (ts.isIdentifier(expression)) return constants.get(expression.text) ?? null;
|
||||
if (ts.isParenthesizedExpression(expression)) {
|
||||
return constantString(expression.expression, constants);
|
||||
}
|
||||
if (
|
||||
ts.isBinaryExpression(expression) &&
|
||||
expression.operatorToken.kind === ts.SyntaxKind.PlusToken
|
||||
) {
|
||||
const left = constantString(expression.left, constants);
|
||||
const right = constantString(expression.right, constants);
|
||||
return left === null || right === null ? null : left + right;
|
||||
}
|
||||
if (ts.isTemplateExpression(expression)) {
|
||||
let value = expression.head.text;
|
||||
for (const span of expression.templateSpans) {
|
||||
const substitution = constantString(span.expression, constants);
|
||||
if (substitution === null) return null;
|
||||
value += substitution + span.literal.text;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isPublicEntryPoint(specifier) {
|
||||
return /workspace-(?:runtime|helper)\/index\.(?:js|ts)$/.test(specifier);
|
||||
}
|
||||
@@ -125,6 +205,12 @@ function isHostCapability(specifier) {
|
||||
function isForbiddenInternal(importer, specifier) {
|
||||
const target = resolveSpecifier(importer, specifier);
|
||||
if (!target || !target.includes("/internal/")) return false;
|
||||
if (target.includes("/provider-probe/internal/")) {
|
||||
return !(
|
||||
importer.endsWith("/provider-probe/index.ts") ||
|
||||
importer.includes("/provider-probe/internal/")
|
||||
);
|
||||
}
|
||||
if (target.includes("/workspace-runtime/command/internal/")) {
|
||||
return !(
|
||||
importer.endsWith("/workspace-runtime/command/index.ts") ||
|
||||
|
||||
@@ -88,7 +88,7 @@ test("the AST guard rejects computed loading and dynamic host/internal bypasses"
|
||||
"module-entrypoint",
|
||||
"nonliteral-module-load",
|
||||
"nonliteral-module-load",
|
||||
"nonliteral-module-load",
|
||||
"workspace-host-access",
|
||||
"workspace-host-access",
|
||||
"workspace-host-access",
|
||||
"workspace-host-access",
|
||||
@@ -96,6 +96,36 @@ test("the AST guard rejects computed loading and dynamic host/internal bypasses"
|
||||
);
|
||||
});
|
||||
|
||||
test("the AST guard rejects every statically resolvable provider probe internal import", async (t) => {
|
||||
const root = await fixtureRoot(t);
|
||||
await source(
|
||||
root,
|
||||
"packages/server/src/server/session/computed.ts",
|
||||
'import { createService as direct } from "../provider-probe/internal/service.js";\n' +
|
||||
'import { createService as alias } from "@getpaseo/server/provider-probe/internal/service.js";\n' +
|
||||
'export { createService } from "../provider-probe/internal/service.js";\n' +
|
||||
'const internal = "../provider-probe/" + "internal/service.js";\n' +
|
||||
"const load = require;\n" +
|
||||
'void require("../provider-probe/internal/service.js");\n' +
|
||||
'void import("../provider-probe/" + "internal/service.js");\n' +
|
||||
'void import(`../provider-probe/${"internal"}/service.js`);\n' +
|
||||
"void import(internal);\n" +
|
||||
"void load(internal);\n" +
|
||||
"void direct; void alias;\n",
|
||||
);
|
||||
|
||||
const violations = await findWorkspaceBoundaryViolations(root);
|
||||
assert.equal(violations.length, 8);
|
||||
assert.deepEqual(new Set(violations.map(({ rule }) => rule)), new Set(["module-entrypoint"]));
|
||||
assert.deepEqual(
|
||||
new Set(violations.map((violation) => violation.import)),
|
||||
new Set([
|
||||
"../provider-probe/internal/service.js",
|
||||
"@getpaseo/server/provider-probe/internal/service.js",
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
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"));
|
||||
@@ -104,6 +134,13 @@ test("owned integrations, explicit legacy code, and fixture contract imports pas
|
||||
"packages/server/src/server/workspace-runtime/index.ts",
|
||||
'import { service } from "./internal/service.js"; void service;\n',
|
||||
);
|
||||
await source(
|
||||
root,
|
||||
"packages/server/src/server/provider-probe/index.ts",
|
||||
'import { createService } from "./internal/service.js";\n' +
|
||||
'export { createProbeStore } from "./internal/probe-store.js";\n' +
|
||||
"void createService;\n",
|
||||
);
|
||||
await source(
|
||||
root,
|
||||
"packages/server/src/server/workspace-runtime/command/index.ts",
|
||||
|
||||
Reference in New Issue
Block a user