ci: narrow native-host lane selection and reuse build caches (#2131)
* ci: narrow native-host lane selection and reuse build caches Unowned src/ and bridge/ files no longer fall back to the full baseline. They run the Linux suite plus the Linux x64 installed-package/Inspector baseline. New apple and package scopes own the Apple artifact lane, the package matrix and the Windows curated lane, including the test files those lanes invoke. Manual-only verifier entrypoints run static checks and the Linux suite. CHANGELOG, SECURITY, GitHub templates and the native Windows README are documentation-only. Pull requests omit the macOS x64 package host unless labelled ci:full. The Windows and Apple lanes restore the shared Turbo build cache, the Windows lane drops its duplicate typecheck, and the Linux suite uses six shards. Main pushes keep the full baseline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci: match package script entrypoints against scope globs Script-only package.json edits resolved their entrypoint with literal equality, so glob-owned verifiers such as scripts/verify/apple/** and scripts/verify/inspector/** fell back to the full baseline instead of selecting their owning lane. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5190843f8f
commit
4f46eac453
|
|
@ -105,7 +105,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1/4, 2/4, 3/4, 4/4]
|
||||
shard: [1/6, 2/6, 3/6, 4/6, 5/6, 6/6]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
|
|
@ -372,7 +372,7 @@ jobs:
|
|||
|
||||
apple-artifacts:
|
||||
name: Real Apple artifact CLI/MCP E2E
|
||||
needs: changes
|
||||
needs: [changes, build]
|
||||
if: needs.changes.outputs.apple == 'true'
|
||||
runs-on: macos-15
|
||||
timeout-minutes: 15
|
||||
|
|
@ -383,6 +383,10 @@ jobs:
|
|||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: rea-build-cache
|
||||
path: .turbo/cache/
|
||||
- name: Verify Apple artifact formats and metadata
|
||||
env:
|
||||
REA_ARTIFACT_NODE_COMPILE_CACHE: ${{ runner.temp }}/rea-artifact-node-cache
|
||||
|
|
@ -445,7 +449,7 @@ jobs:
|
|||
|
||||
windows-curated:
|
||||
name: Windows capability and package lane
|
||||
needs: [changes, windows-native-build]
|
||||
needs: [changes, build, windows-native-build]
|
||||
if: needs.changes.outputs.package == 'true'
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 25
|
||||
|
|
@ -456,8 +460,11 @@ jobs:
|
|||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: rea-build-cache
|
||||
path: .turbo/cache/
|
||||
- run: npm run build:cached
|
||||
- run: npm run typecheck
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: windows-native-controls
|
||||
|
|
|
|||
|
|
@ -837,25 +837,42 @@ not make native host acceptance unavailable.
|
|||
## Native platform baseline in CI
|
||||
|
||||
Pull requests that change only root `README*.md` files, `docs/`, `AGENTS.md`,
|
||||
or `CONTRIBUTING.md` run formatting and generated-document validation without
|
||||
the source-test shards or native package lanes. Classification compares the PR
|
||||
head with its merge base, so later base-branch changes do not expand that scope.
|
||||
Static and coverage aggregate jobs remain present and fail if classification
|
||||
fails. Every code change retains the complete four-shard Linux suite; individual
|
||||
test files are not selected by imports or filenames.
|
||||
`CONTRIBUTING.md`, `CHANGELOG.md`, `SECURITY.md`, `native/windows/README.md`,
|
||||
or the GitHub issue and pull-request templates run formatting and
|
||||
generated-document validation without the source-test shards or native package
|
||||
lanes. Classification compares the PR head with its merge base, so later
|
||||
base-branch changes do not expand that scope. Static and coverage aggregate jobs
|
||||
remain present and fail if classification fails. Every code change retains the
|
||||
complete six-shard Linux suite; individual test files are not selected by
|
||||
imports or filenames.
|
||||
|
||||
`scripts/ci/plan.mjs` selects lanes using the ownership table in
|
||||
`scripts/ci/scopes.mjs`. Provider implementation changes run their real-provider
|
||||
verifiers and the Linux x64 installed-package/Inspector baseline. Test-only
|
||||
changes run the Linux suite without native package verification. Website changes
|
||||
run website checks. Provider workflow changes validate the workflow and run that provider's verifier.
|
||||
Manual-provider/Android workflow changes run workflow validation only.
|
||||
Shared process/contract/CLI boundaries, dependencies, packaging inputs, unknown
|
||||
paths, and changes to the planner select the full baseline. Package script-only
|
||||
edits select their owning verifier when its entrypoint is identifiable; unknown
|
||||
or shared script changes select everything. Android build-script edits retain
|
||||
the Linux suite and workflow validation; real Android verification stays manual.
|
||||
Renames account for both paths.
|
||||
verifiers and the Linux x64 installed-package/Inspector baseline. The `apple`
|
||||
scope owns the Apple artifact lane; the `package` scope owns the multi-host
|
||||
package matrix and the Windows curated lane, including Windows native controls,
|
||||
Inspector, setup, configuration and packaging entrypoints. Runtime sources under
|
||||
`src/` or `bridge/` without an owner, and the authored skill, run the Linux suite
|
||||
and the Linux x64 installed-package/Inspector baseline only. Test-only changes
|
||||
run the Linux suite without native package verification, except test files that
|
||||
the Apple or Windows curated lanes invoke explicitly, which also select that
|
||||
lane. Edits to verifier entrypoints run only by manual, licensed or self-hosted
|
||||
workflows run static checks and the Linux suite. Website changes run website
|
||||
checks. Provider workflow changes validate the workflow and run that provider's
|
||||
verifier. Manual-provider/Android workflow changes run workflow validation only.
|
||||
Shared process/contract/CLI boundaries, dependencies, other packaging inputs,
|
||||
unknown paths, and changes to the planner select the full baseline. Package
|
||||
script-only edits select their owning verifier when its entrypoint is
|
||||
identifiable; unknown or shared script changes select everything. Android
|
||||
build-script edits retain the Linux suite and workflow validation; real Android
|
||||
verification stays manual. Renames account for both paths.
|
||||
|
||||
This narrower selection trades PR-time coverage for runner capacity. A change to
|
||||
unowned shared runtime code that fails only on arm64, macOS, Windows, the Apple
|
||||
artifact lane, or a provider lane that does not own the file is not caught on
|
||||
the PR; the main-push full baseline reports it after merge. Ownership globs do
|
||||
not follow imports. Use the `ci:full` label when a change may have native-host
|
||||
or provider effects outside its owners.
|
||||
|
||||
Main pushes and manual dispatch retain the full baseline, including all portable
|
||||
provider lanes and the native matrix below. A `ci:full` PR label selects the full
|
||||
|
|
@ -879,12 +896,16 @@ CI exercises the pinned Node.js runtime on native hosted runners:
|
|||
| Linux x64 | `ubuntu-latest` | Installed package and real Node Inspector CLI/MCP |
|
||||
| Linux arm64 (aarch64) | `ubuntu-24.04-arm` | Installed package and real Node Inspector CLI/MCP |
|
||||
| macOS 15 arm64 | `macos-15` | Installed package and real Node Inspector CLI/MCP |
|
||||
| macOS 15 x64 | `macos-15-intel` | Installed package and real Node Inspector CLI/MCP |
|
||||
| macOS 15 x64 | `macos-15-intel` | Installed package and real Node Inspector CLI/MCP (main, dispatch and `ci:full` only) |
|
||||
| Windows x64 | `windows-latest` | Curated capabilities, native controls, installed package and real Node Inspector CLI/MCP |
|
||||
|
||||
macOS x64 is the slowest package host. Pull requests without the `ci:full` label
|
||||
omit it, so darwin x64 installed-package and Inspector regressions surface on
|
||||
the main-push full baseline rather than on the PR.
|
||||
|
||||
Package lanes assert the actual Node platform/architecture before verification
|
||||
and record those values with the Node version. The four-host package matrix
|
||||
runs up to four jobs concurrently on independent hosted runners with explicit
|
||||
and record those values with the Node version. The package matrix runs up to
|
||||
four jobs concurrently on independent hosted runners with explicit
|
||||
timeouts and per-runner Node heap/thread limits; Windows retains its curated
|
||||
capability lane. Package checks cover installation, CLI/MCP discovery,
|
||||
target-free analysis, configuration backups/recovery, Evidence and owned
|
||||
|
|
@ -1077,7 +1098,7 @@ CI owns coverage; aggregate and domain/contract thresholds are maintained in
|
|||
`vitest.config.ts`. They are glob-specific and never updated automatically.
|
||||
Coverage does not replace named boundary or real-provider scenarios.
|
||||
|
||||
CI runs four native Vitest shards without retries. Each shard emits a blob
|
||||
CI runs six native Vitest shards without retries. Each shard emits a blob
|
||||
report; the merge job produces aggregate coverage plus JUnit and JSON timing
|
||||
reports, uploads them together, and writes the slowest files to the workflow
|
||||
summary. Static checks, documentation, build, package verification, and
|
||||
|
|
@ -1091,8 +1112,9 @@ architecture, and `.npmrc`; test-only changes can reuse compiled outputs. Every
|
|||
build and verification command still runs, so Turbo checks task inputs and
|
||||
rebuilds misses. There is no prefix fallback to accumulate older build outputs.
|
||||
|
||||
Native package jobs reuse the same workflow's Turbo cache for portable compiled
|
||||
JavaScript, skills, and catalogs. They retain the normal build and prepack
|
||||
Native package jobs, the Windows curated lane, and the Apple artifact lane reuse
|
||||
the same workflow's Turbo cache for portable compiled JavaScript, skills, and
|
||||
catalogs. The Windows lane relies on the Linux static job for typechecking. They retain the normal build and prepack
|
||||
commands, which restore matching outputs or rebuild on a cache miss. Native
|
||||
artifacts, dependency installs, package archives, and runtime verification remain
|
||||
host-local. Keep platform-dependent outputs out of this shared cache.
|
||||
|
|
|
|||
|
|
@ -1,19 +1,24 @@
|
|||
import { execFileSync } from "node:child_process";
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { matchesGlob } from "node:path";
|
||||
import { SCOPES } from "./scopes.mjs";
|
||||
import { MANUAL_VERIFIERS, SCOPES } from "./scopes.mjs";
|
||||
|
||||
const flags = [
|
||||
"code",
|
||||
"runtime",
|
||||
"package",
|
||||
"apple",
|
||||
"fixtures",
|
||||
"readiness",
|
||||
"docs",
|
||||
"workflows",
|
||||
...Object.keys(SCOPES),
|
||||
...new Set([
|
||||
"code",
|
||||
"runtime",
|
||||
"package",
|
||||
"apple",
|
||||
"fixtures",
|
||||
"readiness",
|
||||
"docs",
|
||||
"workflows",
|
||||
...Object.keys(SCOPES),
|
||||
]),
|
||||
];
|
||||
// Test files select the native-host lanes that run them explicitly. Provider
|
||||
// scopes remain implementation-owned, so test-only edits stay off those lanes.
|
||||
const NATIVE_TEST_OWNERS = ["apple", "package"];
|
||||
// Actionlint validates workflow syntax only. The semantic suite asserts these
|
||||
// workflows' release, publication and self-hosted runner safeguards.
|
||||
const GUARDED_WORKFLOWS = new Set([
|
||||
|
|
@ -87,11 +92,11 @@ function classifyPackage(base, head) {
|
|||
const scriptOwners =
|
||||
simple &&
|
||||
entries.every((entry) =>
|
||||
Object.values(SCOPES).some((patterns) => patterns.includes(entry)),
|
||||
Object.values(SCOPES).some((patterns) => matchesAny(entry, patterns)),
|
||||
)
|
||||
? Object.entries(SCOPES)
|
||||
.filter(([, patterns]) =>
|
||||
entries.some((entry) => patterns.includes(entry)),
|
||||
entries.some((entry) => matchesAny(entry, patterns)),
|
||||
)
|
||||
.map(([owner]) => owner)
|
||||
: [];
|
||||
|
|
@ -119,14 +124,23 @@ if (
|
|||
.filter(Boolean);
|
||||
for (const file of files) {
|
||||
if (
|
||||
/^(README(?:_[^/]+)?\.md|AGENTS\.md|CONTRIBUTING\.md)$/.test(file) ||
|
||||
file.startsWith("docs/")
|
||||
/^(README(?:_[^/]+)?\.md|AGENTS\.md|CONTRIBUTING\.md|CHANGELOG\.md|SECURITY\.md|native\/windows\/README\.md|\.github\/pull_request_template\.md)$/.test(
|
||||
file,
|
||||
) ||
|
||||
file.startsWith("docs/") ||
|
||||
file.startsWith(".github/ISSUE_TEMPLATE/")
|
||||
) {
|
||||
selected.docs = true;
|
||||
selected.website ||= matchesAny(file, SCOPES.website);
|
||||
continue;
|
||||
}
|
||||
if (/^(src|tests)\/.*\.(?:test|spec)\.[cm]?[jt]sx?$/.test(file)) {
|
||||
selected.code = true;
|
||||
for (const owner of NATIVE_TEST_OWNERS)
|
||||
selected[owner] ||= matchesAny(file, SCOPES[owner]);
|
||||
continue;
|
||||
}
|
||||
if (matchesAny(file, MANUAL_VERIFIERS)) {
|
||||
selected.code = true;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -166,6 +180,14 @@ if (
|
|||
} else if (owners.length) {
|
||||
if (file.startsWith("src/") || file.startsWith("bridge/"))
|
||||
selected.runtime = true;
|
||||
} else if (/^(src|bridge)\//.test(file)) {
|
||||
// Unowned runtime sources keep the Linux x64 installed-package baseline;
|
||||
// ci:full, main pushes and manual dispatch retain every native host.
|
||||
selected.runtime = true;
|
||||
reasons.push(`Unowned runtime source: ${file}`);
|
||||
} else if (file.startsWith(".agents/skills/")) {
|
||||
selected.runtime = true;
|
||||
selected.docs = true;
|
||||
} else {
|
||||
full(`Unclassified input: ${file}`);
|
||||
}
|
||||
|
|
@ -173,13 +195,19 @@ if (
|
|||
} else throw new Error(`Unsupported CI event: ${event}`);
|
||||
selected.runtime ||= selected.package;
|
||||
const linux = { os: "ubuntu-latest", platform: "linux", architecture: "x64" };
|
||||
// macOS x64 is the slowest package host. Pull requests run it only with ci:full;
|
||||
// main pushes, schedules and manual dispatch always include it.
|
||||
const intel =
|
||||
event !== "pull_request" || process.env.CI_FULL === "true"
|
||||
? [{ os: "macos-15-intel", platform: "darwin", architecture: "x64" }]
|
||||
: [];
|
||||
const matrix = {
|
||||
include: selected.package
|
||||
? [
|
||||
linux,
|
||||
{ os: "ubuntu-24.04-arm", platform: "linux", architecture: "arm64" },
|
||||
{ os: "macos-15", platform: "darwin", architecture: "arm64" },
|
||||
{ os: "macos-15-intel", platform: "darwin", architecture: "x64" },
|
||||
...intel,
|
||||
]
|
||||
: [linux],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,88 @@
|
|||
// Ownership of the real-provider and website verification lanes. Shared build
|
||||
// inputs are classified once in plan.mjs rather than repeated in every workflow.
|
||||
// Ownership of the native-host, real-provider and website verification lanes.
|
||||
// Shared build inputs are classified once in plan.mjs rather than repeated in
|
||||
// every workflow.
|
||||
export const SCOPES = {
|
||||
// Apple artifact CLI/MCP lane on macOS, including its focused regressions.
|
||||
apple: [
|
||||
"src/native/Apple*",
|
||||
"src/native/NativeMacOS*",
|
||||
"src/native/NativeUi*",
|
||||
"src/native/Lldb*",
|
||||
"bridge/native/**",
|
||||
"bridge/process/**",
|
||||
"scripts/verify/apple/**",
|
||||
"tests/boundary/filesystem/installScript.test.ts",
|
||||
"tests/boundary/filesystem/nibNativeHierarchy.test.ts",
|
||||
"tests/boundary/filesystem/interfaceBuilderBundle.test.ts",
|
||||
"tests/boundary/cli/nativePlistCli.test.ts",
|
||||
"tests/boundary/cli/nativePlistPrototypeKeyCli.test.ts",
|
||||
"tests/boundary/cli/nativePlistSelectionFailureCli.test.ts",
|
||||
"tests/boundary/cli/nativeDemangleCli.test.ts",
|
||||
"tests/boundary/mcp/nativeDemangleMcp.test.ts",
|
||||
"tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts",
|
||||
"tests/boundary/cli/nativeSignatureCli.test.ts",
|
||||
"tests/boundary/mcp/nativeSignatureTargetBindingMcp.test.ts",
|
||||
"src/process/capture/FilesystemSnapshot.test.ts",
|
||||
"tests/boundary/process/darwinProcessRunTokenReader.test.ts",
|
||||
"tests/boundary/process/directAnalysisSignals.test.ts",
|
||||
"tests/boundary/process/processCaptureLifecycle.test.ts",
|
||||
"tests/boundary/process/processCli.test.ts",
|
||||
"tests/boundary/mcp/sessionMcp.test.ts",
|
||||
],
|
||||
// Four-host installed-package matrix and the Windows curated lane.
|
||||
package: [
|
||||
"src/windows/**",
|
||||
"native/windows/**",
|
||||
"src/inspector/**",
|
||||
"scripts/verify/inspector/**",
|
||||
"scripts/verify-package.mjs",
|
||||
"scripts/verify-package-*",
|
||||
"scripts/verify-windows-*.mjs",
|
||||
"scripts/build-windows-native.mjs",
|
||||
"scripts/windows-capabilities.mjs",
|
||||
"scripts/prepack.mjs",
|
||||
"scripts/prepare.mjs",
|
||||
"scripts/rea.mjs",
|
||||
"install.sh",
|
||||
"src/config/**",
|
||||
"src/application/Setup*.ts",
|
||||
"src/application/Uninstall*.ts",
|
||||
"src/cli/interactiveSetup.ts",
|
||||
"src/cli/setupCommands.ts",
|
||||
"tests/boundary/filesystem/binaryTarget.test.ts",
|
||||
"tests/boundary/filesystem/safeOutputTree.test.ts",
|
||||
"tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts",
|
||||
"tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts",
|
||||
"tests/boundary/filesystem/setupUnavailableProvider.test.ts",
|
||||
"tests/boundary/filesystem/windowsNpxRegistration.test.ts",
|
||||
"tests/boundary/filesystem/webScriptManifestPaths.test.ts",
|
||||
"tests/boundary/process/ProcessEvidence.host.test.ts",
|
||||
"tests/acceptance/webModuleCli.test.ts",
|
||||
"tests/acceptance/webSourceLocationCli.test.ts",
|
||||
"tests/boundary/cli/referenceSourceImportStatus.test.ts",
|
||||
"tests/boundary/filesystem/asarInventoryFreshness.test.ts",
|
||||
"tests/boundary/filesystem/javascript-applications/javascriptArtifactReconstruction.test.ts",
|
||||
"tests/conformance/providers/ghidra/windowsCapabilities.test.ts",
|
||||
"src/ghidra/GhidraInstallation.test.ts",
|
||||
"tests/boundary/providers/ghidra/ghidraTransport.test.ts",
|
||||
"tests/boundary/providers/ghidra/ghidraTargetSnapshot.test.ts",
|
||||
"tests/boundary/providers/ghidra/ghidraLauncher.test.ts",
|
||||
"tests/boundary/providers/hopper/hopperClient.cleanupRetry.test.ts",
|
||||
"src/windows/WindowsNativeLoader.test.ts",
|
||||
"src/windows/WindowsOwnedProcess.test.ts",
|
||||
"src/ghidra/GhidraLauncherEnvironment.test.ts",
|
||||
"src/process/ProcessOwnership.test.ts",
|
||||
"src/process/ProcessOwnership.part2.test.ts",
|
||||
"src/process/ProcessOwnership.part3.test.ts",
|
||||
"src/process/ProcessOwnership.validation.test.ts",
|
||||
"src/application/Update.test.ts",
|
||||
"tests/process-global/updateMaintenance.test.ts",
|
||||
"tests/conformance/providers/ghidra/realGhidraWindowsWorkflow.test.ts",
|
||||
"tests/boundary/filesystem/directoryArtifactReaderOpen.test.ts",
|
||||
"tests/boundary/filesystem/javascriptPackagePrecedence.test.ts",
|
||||
"tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts",
|
||||
"tests/boundary/process/sourceMapUncHosts.test.ts",
|
||||
],
|
||||
browser: [
|
||||
".github/workflows/real-browser.yml",
|
||||
"scripts/verify-real-browser.mjs",
|
||||
|
|
@ -227,3 +309,23 @@ export const SCOPES = {
|
|||
".github/workflows/website-pages.yml",
|
||||
],
|
||||
};
|
||||
|
||||
// Verifier entrypoints that only manual, licensed, or self-hosted workflows
|
||||
// run. No selected CI lane executes them, so their edits need only the static
|
||||
// checks and the source-test suite. Shared helpers under scripts/lib remain
|
||||
// broad inputs.
|
||||
export const MANUAL_VERIFIERS = [
|
||||
"scripts/verify-real-electron.mjs",
|
||||
"scripts/verify-real-ghidra*.mjs",
|
||||
"scripts/verify-real-hopper*.mjs",
|
||||
"scripts/verify-real-ida.mjs",
|
||||
"scripts/verify-*-client.mjs",
|
||||
"scripts/verify-native-agent-client.mjs",
|
||||
"scripts/verify-jeb.mjs",
|
||||
"scripts/verify-apktool.mjs",
|
||||
"scripts/verify-adb.mjs",
|
||||
"scripts/verify-agent-experience.mjs",
|
||||
"scripts/verify-packaged-ghidra-windows.mjs",
|
||||
"scripts/verify-wasm-artifact.mjs",
|
||||
"scripts/verify-packaged-wasm-artifact.mjs",
|
||||
];
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
rename,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
import { dirname, join, matchesGlob } from "node:path";
|
||||
|
||||
import { afterAll, beforeAll, expect, it } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
|
@ -157,7 +157,7 @@ it("retains complete checks when implementation is renamed into documentation",
|
|||
const head = await commit(directory);
|
||||
expect(await classify(directory, base, head)).toMatchObject({
|
||||
code: true,
|
||||
package: true,
|
||||
runtime: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -268,6 +268,54 @@ it.each([
|
|||
{ code: false, evm: true, browser: false, package: false },
|
||||
],
|
||||
["unknown-input", { code: true, package: true, evm: true, browser: true }],
|
||||
[
|
||||
"src/application/Example.ts",
|
||||
{
|
||||
code: true,
|
||||
runtime: true,
|
||||
package: false,
|
||||
apple: false,
|
||||
evm: false,
|
||||
browser: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
"bridge/ghidra/Example.java",
|
||||
{ code: true, runtime: true, package: false, apple: false },
|
||||
],
|
||||
[
|
||||
"src/native/AppleExample.ts",
|
||||
{ code: true, runtime: true, apple: true, package: false, evm: false },
|
||||
],
|
||||
[
|
||||
"scripts/verify/apple/example.mjs",
|
||||
{ code: true, apple: true, package: false, runtime: false },
|
||||
],
|
||||
[
|
||||
"src/windows/Example.ts",
|
||||
{ code: true, runtime: true, package: true, apple: false, evm: false },
|
||||
],
|
||||
["install.sh", { code: true, runtime: true, package: true, evm: false }],
|
||||
[
|
||||
".agents/skills/reverse-engineer-anything/SKILL.md",
|
||||
{ code: true, runtime: true, docs: true, package: false, evm: false },
|
||||
],
|
||||
[
|
||||
"scripts/verify-real-ghidra.mjs",
|
||||
{ code: true, runtime: false, package: false, apple: false, evm: false },
|
||||
],
|
||||
[
|
||||
"scripts/verify-claude-client.mjs",
|
||||
{ code: true, runtime: false, package: false, browser: false },
|
||||
],
|
||||
[
|
||||
"tests/boundary/process/darwinProcessRunTokenReader.test.ts",
|
||||
{ code: true, apple: true, package: false, runtime: false },
|
||||
],
|
||||
[
|
||||
"src/process/ProcessOwnership.test.ts",
|
||||
{ code: true, package: true, apple: false, evm: false, browser: false },
|
||||
],
|
||||
])("selects the appropriate lanes for %s", async (path, expected) => {
|
||||
const { directory, base } = await fixture();
|
||||
const head = await change(directory, [path]);
|
||||
|
|
@ -404,6 +452,14 @@ it.each([
|
|||
{ code: true, package: true, browser: true, evm: true },
|
||||
],
|
||||
["docs/.vitepress/config.ts", { code: false, docs: true, package: false }],
|
||||
["CHANGELOG.md", { code: false, docs: true, package: false }],
|
||||
["SECURITY.md", { code: false, docs: true, package: false }],
|
||||
[".github/pull_request_template.md", { code: false, docs: true }],
|
||||
[
|
||||
".github/ISSUE_TEMPLATE/bug.yml",
|
||||
{ code: false, docs: true, workflows: false },
|
||||
],
|
||||
["native/windows/README.md", { code: false, docs: true, package: false }],
|
||||
[
|
||||
"src/contracts/evm/tools.ts",
|
||||
{ code: true, docs: true, evm: true, package: false },
|
||||
|
|
@ -462,6 +518,43 @@ it("fails without emitting a plan when package metadata is malformed", async ()
|
|||
expect(await readFile(join(directory, "scope-output"), "utf8")).toBe("");
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
"verify:keyed-archive",
|
||||
"npm run build:cached && node scripts/verify/apple/keyed-archive.mjs",
|
||||
{ apple: true, package: false, evm: false, browser: false },
|
||||
],
|
||||
[
|
||||
"verify:inspector",
|
||||
"npm run build:cached && node scripts/verify/inspector/runtime-observation.mjs",
|
||||
{ package: true, apple: false, evm: false, browser: false },
|
||||
],
|
||||
[
|
||||
"verify:windows-native",
|
||||
"node scripts/verify-windows-native.mjs",
|
||||
{ package: true, apple: false, evm: false, browser: false },
|
||||
],
|
||||
])(
|
||||
"routes the %s script to its glob-owned lane",
|
||||
async (script, command, expected) => {
|
||||
const { directory } = await fixture();
|
||||
const base = await change(
|
||||
directory,
|
||||
["package.json"],
|
||||
JSON.stringify({ scripts: { [script]: command } }),
|
||||
);
|
||||
const head = await change(
|
||||
directory,
|
||||
["package.json"],
|
||||
JSON.stringify({ scripts: { [script]: `${command} --verbose` } }),
|
||||
);
|
||||
expect(await classify(directory, base, head)).toMatchObject({
|
||||
code: true,
|
||||
...expected,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("retains a broad fallback when a provider script adds shared shell work", async () => {
|
||||
const { directory } = await fixture();
|
||||
const before = {
|
||||
|
|
@ -538,3 +631,119 @@ it.each(["compile:termux", "build:termux", "build:termux:run"])(
|
|||
});
|
||||
},
|
||||
);
|
||||
|
||||
const INTEL_HOST = {
|
||||
os: "macos-15-intel",
|
||||
platform: "darwin",
|
||||
architecture: "x64",
|
||||
};
|
||||
|
||||
it("keeps macOS x64 package verification for ci:full and non-PR events", async () => {
|
||||
const { directory, base } = await fixture();
|
||||
const head = await change(directory, ["src/windows/Example.ts"]);
|
||||
const matrix = z.object({ include: z.array(z.unknown()) });
|
||||
const hosts = async (event: string, full: boolean) =>
|
||||
matrix.parse((await classify(directory, base, head, event, full)).matrix)
|
||||
.include;
|
||||
const pullRequest = await hosts("pull_request", false);
|
||||
expect(pullRequest).toHaveLength(3);
|
||||
expect(pullRequest).not.toContainEqual(INTEL_HOST);
|
||||
expect(await hosts("pull_request", true)).toContainEqual(INTEL_HOST);
|
||||
for (const event of ["push", "schedule", "workflow_dispatch"])
|
||||
expect(await hosts(event, false)).toContainEqual(INTEL_HOST);
|
||||
});
|
||||
|
||||
const workflowFile = async (name: string): Promise<unknown> =>
|
||||
parse(
|
||||
await readFile(
|
||||
new URL(`../../../.github/workflows/${name}`, import.meta.url),
|
||||
"utf8",
|
||||
),
|
||||
);
|
||||
|
||||
const scopesSchema = z.object({
|
||||
SCOPES: z.record(z.string(), z.array(z.string())),
|
||||
MANUAL_VERIFIERS: z.array(z.string()),
|
||||
});
|
||||
|
||||
const loadScopes = async () =>
|
||||
scopesSchema.parse(
|
||||
await import(
|
||||
new URL("../../../scripts/ci/scopes.mjs", import.meta.url).href
|
||||
),
|
||||
);
|
||||
|
||||
const stepRuns = z.object({
|
||||
on: z.unknown(),
|
||||
jobs: z.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
steps: z.array(z.object({ run: z.string().optional() })).optional(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
it("owns every explicit native-host test path with the lane that runs it", async () => {
|
||||
const { SCOPES } = await loadScopes();
|
||||
const ci = stepRuns.parse(await workflowFile("ci.yml"));
|
||||
const listed = (job: string) =>
|
||||
(ci.jobs[job]?.steps ?? [])
|
||||
.flatMap((step) => step.run?.split(/\s+/) ?? [])
|
||||
.filter((token) => token.endsWith(".test.ts"));
|
||||
const unowned = (job: string, scope: string) =>
|
||||
listed(job).filter(
|
||||
(path) =>
|
||||
!(SCOPES[scope] ?? []).some((pattern) => matchesGlob(path, pattern)),
|
||||
);
|
||||
expect(listed("apple-artifacts").length).toBeGreaterThan(0);
|
||||
expect(listed("windows-curated").length).toBeGreaterThan(0);
|
||||
expect(unowned("apple-artifacts", "apple")).toEqual([]);
|
||||
expect(unowned("windows-curated", "package")).toEqual([]);
|
||||
});
|
||||
|
||||
it("never runs a manual-only verifier from an automatic workflow", async () => {
|
||||
const { MANUAL_VERIFIERS } = await loadScopes();
|
||||
const scripts = z
|
||||
.object({ scripts: z.record(z.string(), z.string()) })
|
||||
.parse(
|
||||
JSON.parse(
|
||||
await readFile(
|
||||
new URL("../../../package.json", import.meta.url),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
).scripts;
|
||||
const directory = new URL("../../../.github/workflows/", import.meta.url);
|
||||
const commands: string[] = [];
|
||||
for (const name of await readdir(directory)) {
|
||||
const workflow = stepRuns.parse(await workflowFile(name));
|
||||
const triggers = JSON.stringify(workflow.on);
|
||||
if (
|
||||
name !== "ci.yml" &&
|
||||
!triggers.includes("workflow_call") &&
|
||||
!triggers.includes("pull_request")
|
||||
)
|
||||
continue;
|
||||
for (const job of Object.values(workflow.jobs))
|
||||
for (const step of job.steps ?? []) if (step.run) commands.push(step.run);
|
||||
}
|
||||
const files = new Set<string>();
|
||||
const seen = new Set<string>();
|
||||
while (commands.length) {
|
||||
const command = commands.pop() ?? "";
|
||||
for (const [, file] of command.matchAll(/\b(scripts\/[\w./-]+\.mjs)\b/g))
|
||||
if (file) files.add(file);
|
||||
for (const [, script] of command.matchAll(/npm run ([\w:.-]+)/g)) {
|
||||
if (!script || seen.has(script)) continue;
|
||||
seen.add(script);
|
||||
const body = scripts[script];
|
||||
if (body) commands.push(body);
|
||||
}
|
||||
}
|
||||
expect(files.size).toBeGreaterThan(0);
|
||||
expect(
|
||||
[...files].filter((file) =>
|
||||
MANUAL_VERIFIERS.some((pattern) => matchesGlob(file, pattern)),
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue