ci: narrow packaged leaf merge validation (#6104)

This commit is contained in:
PerishFire
2026-07-27 09:12:28 +00:00
committed by GitHub
parent c3de4d5b95
commit 783391e9fa
6 changed files with 446 additions and 13 deletions
+268
View File
@@ -0,0 +1,268 @@
import { readFile, readdir } from "node:fs/promises";
import path from "node:path";
import ts from "typescript";
import {
CERTAIN_PACKAGED_LEAF_PREFIXES,
evaluateScopeOutputs,
SCOPE_EFFECTS,
} from "./scopes.ts";
const repoRoot = path.resolve(import.meta.dirname, "..");
const checkedRoots = ["apps", "packages", "tools", "e2e"] as const;
const checkedExtensions = new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
const skippedDirectories = new Set([
".astro",
".next",
".od-data",
"dist",
"node_modules",
"out",
"reports",
"test-results",
"vendor",
]);
const leafPackages = ["@open-design/desktop", "@open-design/packaged", "@open-design/tools-pack"] as const;
const allowedConsumerPrefixes = new Map([
[
"tools/dev/",
"tools-dev launches the desktop entry; the certain rule keeps @open-design/tools-dev tests armed",
],
]);
const allowedConsumers = new Map([
[
"apps/packaged/esbuild.config.mjs",
"the packaged build owns these entrypoints; the config itself remains medium-tier",
],
[
"tools/pack/esbuild.config.mjs",
"the tools-pack build owns this entrypoint; the config itself remains medium-tier",
],
[
"e2e/tests/packaged-launcher-update-loop.test.ts",
"the focused cross-boundary fallback runs whenever broad E2E Vitest is skipped",
],
[
"e2e/tests/packaged-smoke-workflow.test.ts",
"workflow and scope topology fixtures; packaged paths are data rather than runtime imports",
],
[
"e2e/tests/scripts/approve-fork-pr-workflows.test.ts",
"fork-approval path classification fixtures; packaged paths are data",
],
[
"e2e/tests/scripts/check-cross-app-imports.test.ts",
"cross-app import parser fixtures; the packaged import is passed as source text",
],
[
"e2e/tests/scripts/scopes.test.ts",
"scope planner fixtures; packaged paths are classification inputs",
],
[
"packages/platform/tests/index.test.ts",
"package-manager invocation fixtures serialize the desktop build command without loading desktop code",
],
]);
const requiredWorkspaceUnitBlock = `if [ "\${{ needs.scopes.outputs.tools_dev_tests_required }}" = "true" ]; then
pnpm --filter @open-design/tools-dev test
fi
if [ "\${{ needs.scopes.outputs.tools_pack_tests_required }}" = "true" ]; then
pnpm --filter @open-design/desktop build
pnpm --filter @open-design/desktop test
pnpm --filter @open-design/packaged test
pnpm --filter @open-design/tools-pack test
if [ "\${{ needs.scopes.outputs.run_e2e_vitest }}" != "true" ]; then
pnpm --filter @open-design/e2e test tests/packaged-launcher-update-loop.test.ts
fi
fi`;
export type PackagedLeafConsumptionViolation = {
filePath: string;
lineNumber: number;
literal: string;
};
function repositoryPath(filePath: string): string {
return path.relative(repoRoot, filePath).split(path.sep).join("/");
}
function landsInPackagedLeaf(value: string): boolean {
return CERTAIN_PACKAGED_LEAF_PREFIXES.some(
(prefix) => value === prefix.slice(0, -1) || value.startsWith(prefix),
);
}
function literalTargetsPackagedLeaf(fromRepositoryPath: string, literal: string): boolean {
if (leafPackages.some((packageName) => literal === packageName || literal.startsWith(`${packageName}/`))) {
return true;
}
const resolved =
literal.startsWith("./") || literal.startsWith("../")
? path.posix.normalize(path.posix.join(path.posix.dirname(fromRepositoryPath), literal))
: literal;
return landsInPackagedLeaf(resolved);
}
type StaticPath = { value: string; display: string };
function staticPath(node: ts.Expression, sourceFile: ts.SourceFile): StaticPath | undefined {
if (ts.isStringLiteralLike(node)) return { value: node.text, display: node.getText(sourceFile) };
if (ts.isTemplateExpression(node)) {
return { value: node.head.text, display: node.getText(sourceFile) };
}
if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression)) return undefined;
if (
!ts.isIdentifier(node.expression.expression) ||
node.expression.expression.text !== "path" ||
(node.expression.name.text !== "join" && node.expression.name.text !== "resolve")
) {
return undefined;
}
const segments: string[] = [];
for (const [index, argument] of node.arguments.entries()) {
if (index === 0 && ts.isIdentifier(argument) && /^(?:repoRoot|workspaceRoot|WORKSPACE_ROOT)$/.test(argument.text)) {
continue;
}
if (!ts.isExpression(argument)) return undefined;
const segment = staticPath(argument, sourceFile);
if (segment == null || segment.value.length === 0) return undefined;
segments.push(segment.value);
}
return segments.length > 0
? { value: path.posix.join(...segments), display: node.getText(sourceFile) }
: undefined;
}
export function collectPackagedLeafConsumptionFromSource(
filePath: string,
source: string,
): PackagedLeafConsumptionViolation[] {
const sourceFile = ts.createSourceFile(
filePath,
source,
ts.ScriptTarget.Latest,
true,
filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
);
const violations: PackagedLeafConsumptionViolation[] = [];
const seen = new Set<number>();
const visit = (node: ts.Node): void => {
if (ts.isExpression(node)) {
const candidate = staticPath(node, sourceFile);
if (candidate != null && literalTargetsPackagedLeaf(filePath, candidate.value)) {
const start = node.getStart(sourceFile);
if (!seen.has(start)) {
seen.add(start);
violations.push({
filePath,
lineNumber: sourceFile.getLineAndCharacterOfPosition(start).line + 1,
literal: candidate.display,
});
}
return;
}
}
ts.forEachChild(node, visit);
};
visit(sourceFile);
return violations;
}
function isAllowedConsumer(filePath: string): boolean {
return (
allowedConsumers.has(filePath) ||
[...allowedConsumerPrefixes.keys()].some((prefix) => filePath.startsWith(prefix))
);
}
async function collectCheckedFiles(directory: string): Promise<string[]> {
const files: string[] = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
const fullPath = path.join(directory, entry.name);
const repoPath = repositoryPath(fullPath);
if (entry.isDirectory()) {
if (
skippedDirectories.has(entry.name) ||
entry.name.startsWith(".next-") ||
landsInPackagedLeaf(`${repoPath}/`)
) {
continue;
}
files.push(...(await collectCheckedFiles(fullPath)));
} else if (entry.isFile() && checkedExtensions.has(path.extname(entry.name))) {
files.push(repoPath);
}
}
return files;
}
function scopeBoundaryErrors(): string[] {
const expectedEffects = new Set([
"tools_dev_tests_required",
"tools_pack_tests_required",
"workspace_validation_required",
]);
const errors: string[] = [];
for (const filePath of [
"apps/desktop/src/main/index.ts",
"apps/desktop/tests/main/runtime.test.ts",
"apps/packaged/src/index.ts",
"apps/packaged/tests/index.test.ts",
"tools/pack/src/index.ts",
"tools/pack/tests/index.test.ts",
"tools/pack/resources/linux/open-design.desktop.template",
]) {
const evaluation = evaluateScopeOutputs([filePath], "certain", {
deriveWorkspaceValidationFromTestScopes: true,
});
const decision = evaluation.decisions[0];
const actualEffects = SCOPE_EFFECTS.filter((effect) => evaluation.outputs[effect]);
if (
decision?.escalated !== false ||
decision.matchedRules.length !== 1 ||
decision.matchedRules[0] !== "certain-packaged-leaf-sources" ||
actualEffects.length !== expectedEffects.size ||
actualEffects.some((effect) => !expectedEffects.has(effect))
) {
errors.push(`${filePath} does not resolve to the guarded packaged-leaf effects at the certain threshold`);
}
}
return errors;
}
export async function checkPackagedLeafBoundary(): Promise<boolean> {
const violations: PackagedLeafConsumptionViolation[] = [];
for (const root of checkedRoots) {
for (const filePath of await collectCheckedFiles(path.join(repoRoot, root))) {
if (isAllowedConsumer(filePath)) continue;
const source = await readFile(path.join(repoRoot, filePath), "utf8");
violations.push(...collectPackagedLeafConsumptionFromSource(filePath, source));
}
}
const workflow = await readFile(path.join(repoRoot, ".github/workflows/ci.yml"), "utf8");
const errors = scopeBoundaryErrors();
if (!workflow.includes(requiredWorkspaceUnitBlock)) {
errors.push("ci.yml no longer contains the guarded tools-dev, packaged unit, and focused E2E command block");
}
if (violations.length > 0 || errors.length > 0) {
console.error("Packaged-leaf boundary violations found:");
for (const violation of violations) {
console.error(`- ${violation.filePath}:${violation.lineNumber} ${violation.literal}`);
}
for (const error of errors) console.error(`- ${error}`);
console.error("Keep new consumers inside the guarded validation plan or leave the changed surface at medium confidence.");
return false;
}
console.log("Packaged-leaf boundary check passed: certain-tier consumers, effects, and CI commands stay aligned.");
return true;
}
+2
View File
@@ -5,6 +5,7 @@ import ts from "typescript";
import { checkCertainExemptConsumption } from "./check-certain-exempt-consumption.ts";
import { checkCrossAppImports } from "./check-cross-app-imports.ts";
import { checkPackagedLeafBoundary } from "./check-packaged-leaf-boundary.ts";
import { checkTsNocheckImports } from "./check-ts-nocheck-imports.ts";
import { checkDesignSystemManifests } from "./check-design-system-manifests.ts";
import { checkDesignSystemPackageQuality } from "./check-design-system-package-quality.ts";
@@ -1326,6 +1327,7 @@ async function checkCiTopology(): Promise<boolean> {
const checks: GuardCheck[] = [
{ name: "residual JavaScript", run: checkResidualJavaScript },
{ name: "certain-exempt surface consumption", run: checkCertainExemptConsumption },
{ name: "packaged leaf boundary", run: checkPackagedLeafBoundary },
{ name: "package dependency specs", run: checkPackageDependencySpecs },
{ name: "product neutrality", run: checkProductNeutrality },
{ name: "cross-app imports", run: checkCrossAppImports },
+31 -5
View File
@@ -122,6 +122,25 @@ const CERTAIN_EXEMPT_SURFACE: RuleMatch = {
exact: CERTAIN_EXEMPT_EXACT,
};
export const CERTAIN_PACKAGED_LEAF_PREFIXES = [
"apps/desktop/src/",
"apps/desktop/tests/",
"apps/packaged/src/",
"apps/packaged/tests/",
"tools/pack/src/",
"tools/pack/tests/",
"tools/pack/resources/",
] as const;
const CERTAIN_PACKAGED_LEAF_SURFACE: RuleMatch = {
prefixes: CERTAIN_PACKAGED_LEAF_PREFIXES,
};
const CERTAIN_SURFACE: RuleMatch = {
prefixes: [...CERTAIN_EXEMPT_PREFIXES, ...CERTAIN_PACKAGED_LEAF_PREFIXES],
exact: CERTAIN_EXEMPT_EXACT,
};
// Medium-tier exempt residue. The global markdown regex stays medium on
// purpose: its safety at certain would depend on every other rule continuing
// to cover each directory where markdown is runtime content (skills/, craft/,
@@ -146,9 +165,8 @@ const MEDIUM_EXEMPT_EXACT = [
const EXEMPT_REGEXES = [/\.(?:md|mdx|txt)$/] as const;
// Union of both exempt tiers; this is what the fail-closed fallbacks exclude.
const EXEMPT_SURFACE: RuleMatch = {
prefixes: [...CERTAIN_EXEMPT_PREFIXES, ...MEDIUM_EXEMPT_PREFIXES],
const WORKSPACE_FALLBACK_EXCLUDED_SURFACE: RuleMatch = {
prefixes: [...CERTAIN_EXEMPT_PREFIXES, ...MEDIUM_EXEMPT_PREFIXES, ...CERTAIN_PACKAGED_LEAF_PREFIXES],
exact: [...CERTAIN_EXEMPT_EXACT, ...MEDIUM_EXEMPT_EXACT],
regexes: EXEMPT_REGEXES,
};
@@ -170,7 +188,7 @@ export const scopeRules: readonly ScopeRule[] = [
prefixes: MEDIUM_EXEMPT_PREFIXES,
exact: MEDIUM_EXEMPT_EXACT,
regexes: EXEMPT_REGEXES,
excludeWhen: CERTAIN_EXEMPT_SURFACE,
excludeWhen: CERTAIN_SURFACE,
},
effects: [],
confidence: "medium",
@@ -231,6 +249,13 @@ export const scopeRules: readonly ScopeRule[] = [
effects: ["tools_dev_tests_required"],
confidence: "medium",
},
{
id: "certain-packaged-leaf-sources",
match: CERTAIN_PACKAGED_LEAF_SURFACE,
effects: ["tools_dev_tests_required", "tools_pack_tests_required", "workspace_validation_required"],
confidence: "certain",
guard: "packaged leaf boundary",
},
{
id: "tools-pack-sources",
match: {
@@ -245,6 +270,7 @@ export const scopeRules: readonly ScopeRule[] = [
"packages/sidecar/",
"packages/sidecar-proto/",
],
excludeWhen: CERTAIN_PACKAGED_LEAF_SURFACE,
},
effects: ["tools_pack_tests_required"],
confidence: "medium",
@@ -314,7 +340,7 @@ export const scopeRules: readonly ScopeRule[] = [
},
{
id: "workspace-fallback",
match: { excludeWhen: EXEMPT_SURFACE },
match: { excludeWhen: WORKSPACE_FALLBACK_EXCLUDED_SURFACE },
effects: ["workspace_validation_required"],
confidence: "medium",
},