diff --git a/.github/workflows/node-server-tests.yml b/.github/workflows/node-server-tests.yml index 457382b1b91..41a987b92ae 100644 --- a/.github/workflows/node-server-tests.yml +++ b/.github/workflows/node-server-tests.yml @@ -60,6 +60,7 @@ jobs: outputs: should_run: ${{ steps.scope.outputs.should_run || steps.graph.outputs.should_run }} qualification: ${{ steps.scope.outputs.qualification || steps.graph.outputs.qualification }} + native_compatibility: ${{ steps.scope.outputs.native_compatibility || steps.graph.outputs.native_compatibility }} runners: ${{ steps.scope.outputs.runners || steps.graph.outputs.runners }} steps: - uses: actions/checkout@v6 @@ -76,6 +77,7 @@ jobs: env: PUSH_BASE: ${{ github.event.before }} EVENT_NAME: ${{ github.event_name }} + ALLOW_TEST_ONLY_NATIVE_SKIP: ${{ github.event_name == 'pull_request' && !inputs.build_template && inputs.ref == '' }} run: | if [ "$EVENT_NAME" = push ]; then # Compare the entire push, including multi-commit pushes and removed files. @@ -88,10 +90,15 @@ jobs: fi exit 0 fi + NATIVE_SCOPE_ARGS=() + if [ "$ALLOW_TEST_ONLY_NATIVE_SKIP" = true ]; then + NATIVE_SCOPE_ARGS=(--pull-request --diff-paths "$RUNNER_TEMP/node-server-native-diff") + fi # Compare the tested merge with its base, retaining both sides of renames. if git diff --name-only --no-renames -z HEAD^1 HEAD > "$RUNNER_TEMP/node-server-changes" && - git diff --name-only --no-renames --diff-filter=M -z HEAD^1 HEAD > "$RUNNER_TEMP/node-server-modified"; then - node config/scripts/node-server-change-scope.mjs "$RUNNER_TEMP/node-server-changes" --defer-graph --modified-paths "$RUNNER_TEMP/node-server-modified" + git diff --name-only --no-renames --diff-filter=M -z HEAD^1 HEAD > "$RUNNER_TEMP/node-server-modified" && + git diff --raw --no-renames --no-abbrev -z HEAD^1 HEAD > "$RUNNER_TEMP/node-server-native-diff"; then + node config/scripts/node-server-change-scope.mjs "$RUNNER_TEMP/node-server-changes" --defer-graph --modified-paths "$RUNNER_TEMP/node-server-modified" "${NATIVE_SCOPE_ARGS[@]}" else echo 'should_run=true' >> "$GITHUB_OUTPUT" fi @@ -108,11 +115,16 @@ jobs: shell: bash env: EVENT_NAME: ${{ github.event_name }} + ALLOW_TEST_ONLY_NATIVE_SKIP: ${{ github.event_name == 'pull_request' && !inputs.build_template && inputs.ref == '' }} run: | if [ "$EVENT_NAME" = push ]; then node config/scripts/node-server-change-scope.mjs "$RUNNER_TEMP/node-server-changes" --full-qualification --modified-paths "$RUNNER_TEMP/node-server-modified" else - node config/scripts/node-server-change-scope.mjs "$RUNNER_TEMP/node-server-changes" --modified-paths "$RUNNER_TEMP/node-server-modified" + NATIVE_SCOPE_ARGS=() + if [ "$ALLOW_TEST_ONLY_NATIVE_SKIP" = true ]; then + NATIVE_SCOPE_ARGS=(--pull-request --diff-paths "$RUNNER_TEMP/node-server-native-diff") + fi + node config/scripts/node-server-change-scope.mjs "$RUNNER_TEMP/node-server-changes" --modified-paths "$RUNNER_TEMP/node-server-modified" "${NATIVE_SCOPE_ARGS[@]}" fi persistence: @@ -359,7 +371,9 @@ jobs: if: >- ${{ !cancelled() && needs.persistence.result == 'success' && needs.changes.outputs.should_run != 'false' && - needs.changes.outputs.qualification != 'false' }} + needs.changes.outputs.qualification != 'false' && + (github.event_name != 'pull_request' || inputs.build_template || inputs.ref != '' || + needs.changes.outputs.native_compatibility != 'false') }} runs-on: ubuntu-22.04 timeout-minutes: 20 env: diff --git a/config/scripts/headless-detector-compiler-cache.test.mjs b/config/scripts/headless-detector-compiler-cache.test.mjs index 55bf9f03a92..0f8ad3c4e3b 100644 --- a/config/scripts/headless-detector-compiler-cache.test.mjs +++ b/config/scripts/headless-detector-compiler-cache.test.mjs @@ -74,6 +74,7 @@ it('activates the compiler and workspace-policy parser and preserves import grap 'node-server-change-scope', 'node-server-test-paths', 'node-server-qualification', + 'node-server-native-compat-scope', 'orcad-entry-build', 'workspace-source-exports' ]) { diff --git a/config/scripts/node-server-change-scope.mjs b/config/scripts/node-server-change-scope.mjs index cd06384f996..28292fc29c5 100644 --- a/config/scripts/node-server-change-scope.mjs +++ b/config/scripts/node-server-change-scope.mjs @@ -3,6 +3,7 @@ import { resolve } from 'node:path' import { pathToFileURL } from 'node:url' import { nodeServerTestPaths } from './node-server-test-paths.mjs' import { nodeServerQualification } from './node-server-qualification.mjs' +import { classifyNativeCompatChanges } from './node-server-native-compat-scope.mjs' const ROOT = resolve(import.meta.dirname, '../..') const BUILD_SCRIPTS = [ @@ -37,6 +38,7 @@ const ALWAYS_FILES = new Set([ 'tsconfig.json', '.github/workflows/node-server-tests.yml', 'config/scripts/node-server-change-scope.mjs', + 'config/scripts/node-server-native-compat-scope.mjs', ...NODE_SERVER_POLICY_TESTS, 'config/scripts/workspace-source-exports.mjs', 'config/scripts/workspace-source-exports.test.mjs', @@ -216,9 +218,24 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) const policy = nodeServerQualification(changedFiles, result, { fullQualification: process.argv.includes('--full-qualification') }) - const output = result.graphRequired - ? 'graph_required=true\n' - : `should_run=${result.shouldRun}\nqualification=${policy.qualification}\nrunners=${JSON.stringify(policy.runners)}\n` + const diffArgument = process.argv.indexOf('--diff-paths') + const native = result.graphUnavailable + ? { nativeCompatibility: true, reason: 'Server dependency graph unavailable' } + : await classifyNativeCompatChanges(changedFiles, collectNodeServerInputs, { + pullRequest: process.argv.includes('--pull-request'), + fullQualification: process.argv.includes('--full-qualification'), + rawDiff: + diffArgument === -1 ? undefined : readFileSync(process.argv[diffArgument + 1], 'utf8'), + deferGraph: process.argv.includes('--defer-graph') + }) + console.log(native.reason) + let output = result.graphRequired || native.graphRequired ? 'graph_required=true\n' : '' + if (!result.graphRequired) { + output += `should_run=${result.shouldRun}\nqualification=${policy.qualification}\nrunners=${JSON.stringify(policy.runners)}\n` + } + if (!native.graphRequired) { + output += `native_compatibility=${native.nativeCompatibility}\n` + } if (process.env.GITHUB_OUTPUT) { appendFileSync(process.env.GITHUB_OUTPUT, output) } else { diff --git a/config/scripts/node-server-change-scope.test.mjs b/config/scripts/node-server-change-scope.test.mjs index dfaf3f2cb59..73f0c488fb4 100644 --- a/config/scripts/node-server-change-scope.test.mjs +++ b/config/scripts/node-server-change-scope.test.mjs @@ -158,6 +158,7 @@ it.each([ 'tsconfig.json', 'config/tsconfig.node.json', 'config/scripts/node-server-qualification.mjs', + 'config/scripts/node-server-native-compat-scope.mjs', 'config/patches/node-pty@1.1.0.patch', 'native/windows-registry/src/addon.cc', '.github/actions/install-node-dependencies/action.yml', @@ -297,29 +298,48 @@ it.each([ fullQualification: false, expected: 'should_run=true' }, + { + files: ['src/main/persistence/profile-state/profile-state-recovery-crash-boundaries.test.ts'], + deferred: true, + fullQualification: false, + pullRequest: true, + nativeGraph: true, + expected: 'should_run=true' + }, { files: ['src/renderer/src/example.ts'], deferred: true, expected: 'graph_required=true' }, { files: ['src/renderer/src/example.ts'], deferred: false, expected: 'should_run=true' } ])('fails closed or requests dependencies in an uninstalled checkout: %j', (scenario) => { const root = moduleTree( Object.fromEntries( - ['node-server-change-scope', 'node-server-test-paths', 'node-server-qualification'].map( - (name) => [ - `config/scripts/${name}.mjs`, - readFileSync(new URL(`./${name}.mjs`, import.meta.url), 'utf8') - ] - ) + [ + 'node-server-change-scope', + 'node-server-test-paths', + 'node-server-qualification', + 'node-server-native-compat-scope' + ].map((name) => [ + `config/scripts/${name}.mjs`, + readFileSync(new URL(`./${name}.mjs`, import.meta.url), 'utf8') + ]) ) ) const changes = join(root, 'changes') const stepOutput = join(root, 'step-output') + const statuses = join(root, 'native-diff') writeFileSync(changes, scenario.files.map((file) => `${file}\0`).join('')) + writeFileSync( + statuses, + scenario.files + .map((file) => `:100644 100644 ${'1'.repeat(40)} ${'2'.repeat(40)} M\0${file}\0`) + .join('') + ) const result = runProcessSync({ program: process.execPath, args: [ realpathSync(join(root, 'config/scripts/node-server-change-scope.mjs')), changes, ...(scenario.fullQualification === false ? [] : ['--full-qualification']), - ...(scenario.deferred ? ['--defer-graph'] : []) + ...(scenario.deferred ? ['--defer-graph'] : []), + ...(scenario.pullRequest ? ['--pull-request', '--diff-paths', statuses] : []) ], cwd: root, env: { ...process.env, GITHUB_OUTPUT: stepOutput }, @@ -328,6 +348,10 @@ it.each([ expect(result.code).toBe(0) const output = readFileSync(stepOutput, 'utf8') expect(output).toContain(scenario.expected) + if (scenario.nativeGraph) { + expect(output).toContain('graph_required=true') + expect(output).not.toContain('native_compatibility=') + } if (scenario.expected === 'graph_required=true') { expect(output).not.toContain('should_run=') expect(output).not.toContain('runners=') @@ -555,38 +579,6 @@ it('keeps every platform job and runs them when detection is skipped or fails', } }) -it('builds server glibc slots on glibc 2.28 and the compat slot on glibc 2.17 (design D6)', () => { - const workflow = parse( - readFileSync(new URL('../../.github/workflows/node-server-tests.yml', import.meta.url), 'utf8') - ) - const floor = workflow.jobs.linux_glibc_floor - expect(floor.container).toBe('${{ matrix.image }}') - expect(floor.strategy.matrix.include).toEqual([ - { - os: 'ubuntu-22.04', - image: expect.stringMatching(/^quay\.io\/pypa\/manylinux_2_28_x86_64@sha256:[0-9a-f]{64}$/) - }, - { - os: 'ubuntu-24.04-arm', - image: expect.stringMatching(/^quay\.io\/pypa\/manylinux_2_28_aarch64@sha256:[0-9a-f]{64}$/) - } - ]) - expect(floor.env).toMatchObject({ CC: 'gcc', CXX: 'g++' }) - - const compat = workflow.jobs.linux_glibc217_compat - expect(compat.needs).toEqual(['changes', 'persistence']) - expect(compat.if).toBe(floor.if) - expect(compat['runs-on']).toBe('ubuntu-22.04') - const run = compat.steps.map((step) => step.run ?? '').join('\n') - expect(run).toMatch(/quay\.io\/pypa\/manylinux2014_x86_64@sha256:[0-9a-f]{64} /) - expect(run).toContain('--slot=linux-x64-glibc217 --print-runtime') - expect(run).toContain('build-orcad-prebuilds.mjs --slot=linux-x64-glibc217\n') - expect(run).toContain('--require-slots linux-x64-glibc217') - expect(run).toContain( - 'env -u LD_LIBRARY_PATH node config/scripts/build-orcad-prebuilds.mjs --slot=linux-x64-glibc217 --smoke' - ) -}) - it('runs the Bun and Node cross-runtime tests on Linux against pinned inputs', () => { const workflow = parse( readFileSync(new URL('../../.github/workflows/node-server-tests.yml', import.meta.url), 'utf8') diff --git a/config/scripts/node-server-native-compat-cleanup.test-fixture.json b/config/scripts/node-server-native-compat-cleanup.test-fixture.json new file mode 100644 index 00000000000..9e880dbc86e --- /dev/null +++ b/config/scripts/node-server-native-compat-cleanup.test-fixture.json @@ -0,0 +1,132 @@ +{ + "base": "e8c7b32a9037da11d1c194020c01355a6c2fcc89", + "head": "5560ab04ea93dd28a46af421dda37446bcf7df87", + "paths": [ + ["M", "config/reliability-gates.jsonc"], + ["M", "config/scripts/mobile-web-app-module-closure-entrypoints.test.mjs"], + ["D", "config/scripts/mobile-web-app-module-entry-resolution.test.mjs"], + ["M", "config/scripts/oxc-cli-invocation.test.mjs"], + ["D", "config/scripts/oxlint-cli-invocation.test.mjs"], + ["D", "config/scripts/renderer-boot-graph.test.mjs"], + ["M", "config/scripts/vitest-sqlite-database-runtime-files.mjs"], + ["M", "config/scripts/win32-test-lane-registration.test.mjs"], + ["D", "src/main/ipc/pty-controller-owner-recovery.test.ts"], + ["M", "src/main/ipc/pty-controller-ownership-routing.test.ts"], + ["D", "src/main/ipc/pty-cumulative-ack-accounting.test.ts"], + ["M", "src/main/ipc/pty-daemon-controller-teardown.test.ts"], + ["D", "src/main/ipc/pty-daemon-ssh-lease-lifecycle.test.ts"], + ["D", "src/main/ipc/pty-encoding.test.ts"], + ["D", "src/main/ipc/pty-ipc-producer-flow-control.test.ts"], + ["M", "src/main/ipc/pty-output-batching-drain.test.ts"], + ["D", "src/main/ipc/pty-output-drain-rounds.test.ts"], + ["M", "src/main/ipc/pty-pending-output-cap.test.ts"], + ["M", "src/main/ipc/pty-renderer-inflight-credit.test.ts"], + ["M", "src/main/ipc/pty-renderer-lifecycle-delivery-reset.test.ts"], + ["D", "src/main/ipc/pty-renderer-send-failure-recovery.test.ts"], + ["M", "src/main/ipc/pty-spawn-cwd-fallback.test.ts"], + ["D", "src/main/ipc/pty-wsl-cwd-validation.test.ts"], + ["M", "src/main/native-chat/agent-session-journal/journal-host-database-test-support.ts"], + ["D", "src/main/persistence/applying-settings/settings-update-machine-name.test.ts"], + ["A", "src/main/persistence/applying-settings/settings-update-normalization.test.ts"], + ["D", "src/main/persistence/applying-settings/settings-update-terminal-contrast.test.ts"], + ["D", "src/main/persistence/applying-settings/settings-update-terminal-default-shell.test.ts"], + ["M", "src/main/persistence/profile-state/profile-state-recovery-crash-boundaries.test.ts"], + ["M", "src/main/runtime/orca-runtime-structured-status-sink-wiring.test.ts"], + ["M", "src/main/runtime/orca-runtime-terminal-close-records.test.ts"], + ["M", "src/main/runtime/orchestration/coordinator-stale-base-flag.test.ts"], + ["M", "src/main/runtime/orchestration/db.test.ts"], + ["D", "src/main/runtime/orchestration/db/decision-gate-lifecycle.test.ts"], + ["D", "src/main/runtime/orchestration/db/run-list-cursor.test.ts"], + ["M", "src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts"], + ["M", "src/main/runtime/orchestration/preamble.test.ts"], + ["D", "src/main/runtime/push/push-agent-state.test.ts"], + ["M", "src/main/runtime/push/push-dispatcher.test.ts"], + ["M", "src/main/runtime/relay/relay-renewal-jitter.test.ts"], + ["D", "src/main/runtime/rpc/methods/diagnostics.test.ts"], + ["M", "src/main/runtime/rpc/methods/structured-agent-session-visual.test.ts"], + ["M", "src/main/runtime/rpc/methods/terminal/terminal-inspect-process-params.test.ts"], + ["M", "src/main/runtime/rpc/methods/worktree-create-args.test.ts"], + ["D", "src/main/runtime/rpc/methods/worktree-create-setup-choice.test.ts"], + ["M", "src/main/runtime/runtime-reference-integration.test.ts"], + ["M", "src/main/runtime/runtime-rpc-browser-host-admission.test.ts"], + ["M", "src/main/runtime/runtime-rpc-metadata-lifecycle.test.ts"], + ["M", "src/main/runtime/runtime-rpc-pairing-offer.test.ts"], + ["M", "src/main/runtime/runtime-rpc-relay-pairing.test.ts"], + ["M", "src/main/runtime/runtime-rpc-relay-provider-install.test.ts"], + ["M", "src/main/runtime/runtime-rpc-websocket-fallback-port.test.ts"], + ["M", "src/main/runtime/session-tabs-inventory-publication.test.ts"], + ["M", "src/main/runtime/workspaces/workspace-query-prototype.test.ts"], + ["M", "src/renderer/src/components/contextual-tours/ContextualTourOverlay.test.tsx"], + [ + "D", + "src/renderer/src/components/contextual-tours/ContextualTourOverlaySurface.localization.test.tsx" + ], + ["M", "src/renderer/src/components/native-chat/NativeChatCodeBlock.test.tsx"], + [ + "M", + "src/renderer/src/components/native-chat/NativeChatMessageList.tool-stream-cost.test.tsx" + ], + ["M", "src/renderer/src/components/native-chat/NativeChatMessageList.turn-status.test.tsx"], + ["D", "src/renderer/src/components/native-chat/NativeChatTurnActivityLine.test.tsx"], + ["M", "src/renderer/src/components/right-sidebar/FileExplorerFilesTreePane.errors.test.tsx"], + ["D", "src/renderer/src/components/right-sidebar/FileExplorerRow.load-error.test.tsx"], + ["M", "src/renderer/src/components/settings/AccountsPane.test.tsx"], + ["M", "src/renderer/src/components/settings/GitPane.test.ts"], + ["D", "src/renderer/src/components/settings/GitPane.test.tsx"], + ["D", "src/renderer/src/components/settings/accounts-pane-claude-section.test.tsx"], + ["D", "src/renderer/src/components/sidebar/CommentMarkdown.github-callout.test.tsx"], + ["D", "src/renderer/src/components/sidebar/CommentMarkdown.jira-links.test.tsx"], + ["M", "src/renderer/src/components/sidebar/CommentMarkdown.test.tsx"], + ["D", "src/renderer/src/store/slices/agent-status-cursor-checkpoint.test.ts"], + ["M", "src/renderer/src/store/slices/agent-status-main-agent-fact.test.ts"], + ["M", "src/renderer/src/store/slices/agent-status-provider-session.test.ts"], + ["D", "src/renderer/src/store/slices/agent-status-quit-capture-resumable-agents.test.ts"], + ["D", "src/renderer/src/store/slices/agent-status-remember-sleeping-session.test.ts"], + ["D", "src/renderer/src/store/slices/agent-status-reminted-pane-key.test.ts"], + ["D", "src/renderer/src/store/slices/agent-status-session-boundary-done.test.ts"], + ["D", "src/renderer/src/store/slices/agent-status-turn-started-at.test.ts"], + ["D", "src/renderer/src/store/slices/terminal-startup-command-retention.test.ts"], + ["M", "src/renderer/src/store/slices/terminal-tab-recovery-remount.test.ts"], + ["A", "src/shared/agent-resume-argv-drop.spec.ts"], + ["D", "src/shared/agent-resume-argv-drop.test.ts"], + ["A", "src/shared/agent-resume-launch-command.spec.ts"], + ["D", "src/shared/agent-resume-launch-command.test.ts"], + ["A", "src/shared/agent-startup-plan-inputs-extra-agent-args.spec.ts"], + ["D", "src/shared/agent-startup-plan-inputs-extra-agent-args.test.ts"], + ["A", "src/shared/agent-startup-plan-inputs.spec.ts"], + ["D", "src/shared/agent-startup-plan-inputs.test.ts"], + ["A", "src/shared/automation-extra-agent-args.spec.ts"], + ["D", "src/shared/automation-extra-agent-args.test.ts"], + ["A", "src/shared/dsh-startup.spec.ts"], + ["D", "src/shared/dsh-startup.test.ts"], + ["A", "src/shared/freebuff-launch.spec.ts"], + ["D", "src/shared/freebuff-launch.test.ts"], + ["A", "src/shared/freebuff-startup.spec.ts"], + ["D", "src/shared/freebuff-startup.test.ts"], + ["A", "src/shared/omp-fresh-launch.spec.ts"], + ["D", "src/shared/omp-fresh-launch.test.ts"], + ["A", "src/shared/opencode-startup-prompt.spec.ts"], + ["D", "src/shared/opencode-startup-prompt.test.ts"], + ["A", "src/shared/opencode2-resume-launch-mode.spec.ts"], + ["D", "src/shared/opencode2-resume-launch-mode.test.ts"], + ["A", "src/shared/qoder-cn-launch-recipe.spec.ts"], + ["D", "src/shared/qoder-cn-launch-recipe.test.ts"], + ["A", "src/shared/tui-agent-fork-startup.spec.ts"], + ["D", "src/shared/tui-agent-fork-startup.test.ts"], + ["A", "src/shared/tui-agent-launch-defaults.spec.ts"], + ["D", "src/shared/tui-agent-launch-defaults.test.ts"], + ["A", "src/shared/tui-agent-prompt-transport.spec.ts"], + ["D", "src/shared/tui-agent-prompt-transport.test.ts"], + ["A", "src/shared/tui-agent-startup-copilot-resume.spec.ts"], + ["D", "src/shared/tui-agent-startup-copilot-resume.test.ts"], + ["A", "src/shared/tui-agent-startup-hermes.spec.ts"], + ["D", "src/shared/tui-agent-startup-hermes.test.ts"], + ["A", "src/shared/tui-agent-startup-session-options.spec.ts"], + ["D", "src/shared/tui-agent-startup-session-options.test.ts"], + ["A", "src/shared/tui-agent-startup-shell.spec.ts"], + ["D", "src/shared/tui-agent-startup-shell.test.ts"], + ["A", "src/shared/tui-agent-startup.spec.ts"], + ["M", "src/shared/tui-agent-startup.test.ts"] + ], + "rawDiff": ":100644 100644 ed468cf4d79556d176ceb0aabc312e1d6e872d88 94c99746545ef6ddd4dc085b775d9eba0d768458 M\u0000config/reliability-gates.jsonc\u0000:100644 100644 4ca3e52463a721f91ea1662f5e17a6200e664f75 c2af5741cda25ed24f4c9e985d7b2c791d1ad635 M\u0000config/scripts/mobile-web-app-module-closure-entrypoints.test.mjs\u0000:100644 000000 ccdf6936927eed0246a4a329708b00db5cba1ac1 0000000000000000000000000000000000000000 D\u0000config/scripts/mobile-web-app-module-entry-resolution.test.mjs\u0000:100644 100644 5776580dbfeab42b28b7d1c77b478116e96e7f3e a5dae584f5f92312bddb60135f6a9d710b52e2da M\u0000config/scripts/oxc-cli-invocation.test.mjs\u0000:100644 000000 7a681a825d9f1aefdb44340f1ebb5ec789da92b7 0000000000000000000000000000000000000000 D\u0000config/scripts/oxlint-cli-invocation.test.mjs\u0000:100644 000000 bfc3b3518c8c1ffe8c3709059a6bbee45b6149a7 0000000000000000000000000000000000000000 D\u0000config/scripts/renderer-boot-graph.test.mjs\u0000:100644 100644 f9b56d5a8baab226647d04348036561efb438c98 6b303421afb3092deb592781551c9fd69cd38fa2 M\u0000config/scripts/vitest-sqlite-database-runtime-files.mjs\u0000:100644 100644 dcff04783d8b346a003ef7e5a6d403c6601cb9b9 4cc207f1edff18efe49ea57edcfd55cfaa939414 M\u0000config/scripts/win32-test-lane-registration.test.mjs\u0000:100644 000000 dfc334afca6e4ca2f50f921a126d185a7209ba26 0000000000000000000000000000000000000000 D\u0000src/main/ipc/pty-controller-owner-recovery.test.ts\u0000:100644 100644 4c6f3c2401ac29b525fd33e5e671bc43a2294d9f 7abc9a60a28d8677dd917471a4526c472b5669b9 M\u0000src/main/ipc/pty-controller-ownership-routing.test.ts\u0000:100644 000000 688081e63699b0c08eeea6bc4ea2eb5e81ea6553 0000000000000000000000000000000000000000 D\u0000src/main/ipc/pty-cumulative-ack-accounting.test.ts\u0000:100644 100644 3a964e61a745a8b18d91bb3163a590a30f6e70fe dd2c4b5bf345b26d2d402fcbffedf7fc51e13dba M\u0000src/main/ipc/pty-daemon-controller-teardown.test.ts\u0000:100644 000000 9d93418448d6a2b4612dcd7d5116d66c3184db7f 0000000000000000000000000000000000000000 D\u0000src/main/ipc/pty-daemon-ssh-lease-lifecycle.test.ts\u0000:100644 000000 c6d362150149a25de993354b5205d56e1e6b90c4 0000000000000000000000000000000000000000 D\u0000src/main/ipc/pty-encoding.test.ts\u0000:100644 000000 1c31d36167a515dea8b09caee0a96efa25b3c21c 0000000000000000000000000000000000000000 D\u0000src/main/ipc/pty-ipc-producer-flow-control.test.ts\u0000:100644 100644 013e6d492a5aee6f08cacf03b88d50a10ddf3f68 867047f9495a6f44a1e624b803a22eca61485f14 M\u0000src/main/ipc/pty-output-batching-drain.test.ts\u0000:100644 000000 94944b9011beebd491f54c835cef30657d673a80 0000000000000000000000000000000000000000 D\u0000src/main/ipc/pty-output-drain-rounds.test.ts\u0000:100644 100644 0416719e8e8bef0ef379e25b972427d2cc6b5c92 e161b74cc3d0d53f940799a842496b3cc6513e39 M\u0000src/main/ipc/pty-pending-output-cap.test.ts\u0000:100644 100644 5024f408d61d7a1d364d15bbd0dfde88a81d8314 79107ad739248365cf3a4f19c01f673993b1a299 M\u0000src/main/ipc/pty-renderer-inflight-credit.test.ts\u0000:100644 100644 16070629c203a19ad8ff7cb3f22d54d944f20e05 b189dd64f3ab5d42196a423a2bdaf066dcda594c M\u0000src/main/ipc/pty-renderer-lifecycle-delivery-reset.test.ts\u0000:100644 000000 00870825a0735f4799074c10266f0f7ea0d042f8 0000000000000000000000000000000000000000 D\u0000src/main/ipc/pty-renderer-send-failure-recovery.test.ts\u0000:100644 100644 ddf1d55fde61d0d15e5ddb686a341da53d1703f7 3d8935552de8a78d7beb50b33d6af4b0652dec2e M\u0000src/main/ipc/pty-spawn-cwd-fallback.test.ts\u0000:100644 000000 8f6f0e4d709bdfa25fbb65e22467b82332126e08 0000000000000000000000000000000000000000 D\u0000src/main/ipc/pty-wsl-cwd-validation.test.ts\u0000:100644 100644 32b0c846571f0d3b3b8165d1d261dec8cfb20324 aba1aaba39e277b5fde6e8cab403eb0d712707aa M\u0000src/main/native-chat/agent-session-journal/journal-host-database-test-support.ts\u0000:100644 000000 5a849acc9259109b638838f82a4d27b68f3b2d75 0000000000000000000000000000000000000000 D\u0000src/main/persistence/applying-settings/settings-update-machine-name.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 bf4e2dc88b1b05a8688f7a43c049a0471b2ff33b A\u0000src/main/persistence/applying-settings/settings-update-normalization.test.ts\u0000:100644 000000 7de32d7b36d7c626f69693231808671045b593a9 0000000000000000000000000000000000000000 D\u0000src/main/persistence/applying-settings/settings-update-terminal-contrast.test.ts\u0000:100644 000000 4c407e8943f681072e43cc95fe6da526e386dafa 0000000000000000000000000000000000000000 D\u0000src/main/persistence/applying-settings/settings-update-terminal-default-shell.test.ts\u0000:100644 100644 2e89c0279d2c151f70c985573d93c660029c67f5 65fd09bcbb589fe7cdb48503dbb1aa26daf066b3 M\u0000src/main/persistence/profile-state/profile-state-recovery-crash-boundaries.test.ts\u0000:100644 100644 d4b2f77324afaf80dc9e66ba2a3534a9513e7568 75833fe9e5f012abc7568bb1a60bf6dcb163fafd M\u0000src/main/runtime/orca-runtime-structured-status-sink-wiring.test.ts\u0000:100644 100644 db20105d10c6afe4f3a5da7ad7e4f1e7be54512a 15ab7ee194142d8b39e851e2f4118fb1dfd0ee0d M\u0000src/main/runtime/orca-runtime-terminal-close-records.test.ts\u0000:100644 100644 818d3f848dc29ed702270d5782586d4cffdc015a ac7e9a5f788fef4b766ba43dafce31b37cdd7443 M\u0000src/main/runtime/orchestration/coordinator-stale-base-flag.test.ts\u0000:100644 100644 a6aa94a6b2f5461890464ff5bfa897af2e70dcdd 724d33f1be6efd62b7e17c810d9ed209c602466b M\u0000src/main/runtime/orchestration/db.test.ts\u0000:100644 000000 9fdc9d9b5fb74b4c6b7010fffbb00371fb76aaf2 0000000000000000000000000000000000000000 D\u0000src/main/runtime/orchestration/db/decision-gate-lifecycle.test.ts\u0000:100644 000000 d60906b05314248d5f466488afd29e1e7258ed01 0000000000000000000000000000000000000000 D\u0000src/main/runtime/orchestration/db/run-list-cursor.test.ts\u0000:100644 100644 c980b8fe02aad031ef3e9f83efbe7f3a0eedaa35 f9a985512d2a71893c6ea42dcd627fadf69a89cb M\u0000src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts\u0000:100644 100644 61d8c2731cad196e484fada7b54435f22aae65cb e6238e20578dc38f24cf582578f998b6f0ca44b8 M\u0000src/main/runtime/orchestration/preamble.test.ts\u0000:100644 000000 e56d39ffb014c5d8b8d96eb2bb64e09cb1d3cf63 0000000000000000000000000000000000000000 D\u0000src/main/runtime/push/push-agent-state.test.ts\u0000:100644 100644 47373045f28709dde8b0fd798c6929e399009021 d9ba6186d81aad92447f9350875c233906ef4040 M\u0000src/main/runtime/push/push-dispatcher.test.ts\u0000:100644 100644 5bbc09948364c99840dc5f856209aad22136dd15 ac05668a52e9661008d83b146ce128c609f544d2 M\u0000src/main/runtime/relay/relay-renewal-jitter.test.ts\u0000:100644 000000 dab6b8fe85dc824c32d165f763da77f4cef01a95 0000000000000000000000000000000000000000 D\u0000src/main/runtime/rpc/methods/diagnostics.test.ts\u0000:100644 100644 9b7dd8e5ba79be818bbc88edbfca63c25f0b1111 e1c950fa873ababb57e5efe26ea47ff6d5810a08 M\u0000src/main/runtime/rpc/methods/structured-agent-session-visual.test.ts\u0000:100644 100644 de6e2e0222bf808b7cfa5037e71c392b2f567c18 277dba4c66375dde0ec565747cf38788aabfa03c M\u0000src/main/runtime/rpc/methods/terminal/terminal-inspect-process-params.test.ts\u0000:100644 100644 19747628dae9c38a811683b3e3c46afbcb1f8475 b4eb657dffa70f3ec6882d6c8416d84b80d12420 M\u0000src/main/runtime/rpc/methods/worktree-create-args.test.ts\u0000:100644 000000 153c4560d5d4d9e944d91e779c80a09a14e179f8 0000000000000000000000000000000000000000 D\u0000src/main/runtime/rpc/methods/worktree-create-setup-choice.test.ts\u0000:100644 100644 b457caa3704ff01c2126a666ecfdf20003e88f07 6fa5b8ddaf25a35b8a2671e79204b03cbf94f4d8 M\u0000src/main/runtime/runtime-reference-integration.test.ts\u0000:100644 100644 24b94ee4fe47b0b6d568f1fc5b45d550620ee80a 93f2b63fe63c03d0fe633f69affc167e175e7a62 M\u0000src/main/runtime/runtime-rpc-browser-host-admission.test.ts\u0000:100644 100644 4735601c6a95b1b2a0c21c040893aab6955008a8 ac8ceb112fe434cca9a476b21d053b291ade140f M\u0000src/main/runtime/runtime-rpc-metadata-lifecycle.test.ts\u0000:100644 100644 1dabeea095d8d8c3a6efab8f683c4c6d20476821 18368eb052264e302a2c3d3e0d0443fc2e5fe067 M\u0000src/main/runtime/runtime-rpc-pairing-offer.test.ts\u0000:100644 100644 5083f0e78519eb98b25ceece5cc8ff27455e079b 019302512e00e5111db6e5143edb89ea5a57e028 M\u0000src/main/runtime/runtime-rpc-relay-pairing.test.ts\u0000:100644 100644 5f591b52f2be1b876c5d8d513debeb3a46825a63 3f0543bdc5ab568c7a42b66204af23089c834818 M\u0000src/main/runtime/runtime-rpc-relay-provider-install.test.ts\u0000:100644 100644 8f50289484568ff9ba94eb6066d54baf49b59e5c 2ac6987cd9e3076bb9cc16815596f3126d32983f M\u0000src/main/runtime/runtime-rpc-websocket-fallback-port.test.ts\u0000:100644 100644 2cfdc6a3b59e5fe65f16472ab0911d8335588324 bd50245aa5efb6f676035206069cf2ae64776f51 M\u0000src/main/runtime/session-tabs-inventory-publication.test.ts\u0000:100644 100644 3faa076d0a66c70a0534097b0bd3e27b01a63261 140c9d93fad39bc0b9c08a17eb614043075508e2 M\u0000src/main/runtime/workspaces/workspace-query-prototype.test.ts\u0000:100644 100644 e1d881503c067c724df332a9566ad94587c7d9dd a44104f0b9ef1d6a38623ba238cb6e9f99ca1808 M\u0000src/renderer/src/components/contextual-tours/ContextualTourOverlay.test.tsx\u0000:100644 000000 b9923d0fc112cc278cbed5eb6f38631feda77193 0000000000000000000000000000000000000000 D\u0000src/renderer/src/components/contextual-tours/ContextualTourOverlaySurface.localization.test.tsx\u0000:100644 100644 dafa78bc8d99c2390e8aa29fdc759fcbd80853b4 67dcdfe1d8236c1bba7ef4634634856acb9390f4 M\u0000src/renderer/src/components/native-chat/NativeChatCodeBlock.test.tsx\u0000:100644 100644 ed2c0328befa9f155698bdb4526f070ea7591e1f a155b323cf2fc0d40b118ef00cb28dd4584cce83 M\u0000src/renderer/src/components/native-chat/NativeChatMessageList.tool-stream-cost.test.tsx\u0000:100644 100644 cc7c6c86f39913f9c2e68a9b0f79c42dccbcf6e5 8f84c3db5c9cf019180510bf3554eecd88c90e37 M\u0000src/renderer/src/components/native-chat/NativeChatMessageList.turn-status.test.tsx\u0000:100644 000000 9c0b2657cf8ebc7e7df676300ca82ec03a3b1130 0000000000000000000000000000000000000000 D\u0000src/renderer/src/components/native-chat/NativeChatTurnActivityLine.test.tsx\u0000:100644 100644 4d4d041e16b50d40f8839c0e019020db17336b7b 1aaa7e5d23b8b395056f0868f9f47258f1d4b231 M\u0000src/renderer/src/components/right-sidebar/FileExplorerFilesTreePane.errors.test.tsx\u0000:100644 000000 3fc3115a5ea1b060018af681ffc5c1664cbd955f 0000000000000000000000000000000000000000 D\u0000src/renderer/src/components/right-sidebar/FileExplorerRow.load-error.test.tsx\u0000:100644 100644 4def6f914166467a2d7f5a1be7e30eaf2572c0cc a76535faa8171f915eb98513ce0f3f1a8caab1b4 M\u0000src/renderer/src/components/settings/AccountsPane.test.tsx\u0000:100644 100644 3275eb9a1830c990d8f1a1d0db6d92c29a74cb1c df0158497f0ebeb8252d74194328efd723897477 M\u0000src/renderer/src/components/settings/GitPane.test.ts\u0000:100644 000000 70cc28c328908e586b00fc94c1e19163219cfd7d 0000000000000000000000000000000000000000 D\u0000src/renderer/src/components/settings/GitPane.test.tsx\u0000:100644 000000 a3c8f97bf45a73f918e19baa7576d5e070fa994e 0000000000000000000000000000000000000000 D\u0000src/renderer/src/components/settings/accounts-pane-claude-section.test.tsx\u0000:100644 000000 e844ceb3d0f7be481855dae4fc59f65167f177b1 0000000000000000000000000000000000000000 D\u0000src/renderer/src/components/sidebar/CommentMarkdown.github-callout.test.tsx\u0000:100644 000000 b2545935b00920503c471f6a0f535053abaddc94 0000000000000000000000000000000000000000 D\u0000src/renderer/src/components/sidebar/CommentMarkdown.jira-links.test.tsx\u0000:100644 100644 0f3974c55783c94164ed931777e5daa024faa793 66d18daec74cbe5c2c8051886ab2e20c11ad0962 M\u0000src/renderer/src/components/sidebar/CommentMarkdown.test.tsx\u0000:100644 000000 7ab9effad71b05171ea193f77cdb228d99170e10 0000000000000000000000000000000000000000 D\u0000src/renderer/src/store/slices/agent-status-cursor-checkpoint.test.ts\u0000:100644 100644 8ceb45488e8d0e81ee9020c3efd1591b5c513d0c d629cdc0ad181cc5891a2ba0fdd6345e1bc94639 M\u0000src/renderer/src/store/slices/agent-status-main-agent-fact.test.ts\u0000:100644 100644 30793ffce2908e86f068b552dafc78be17c6f1cd ece019e9710743cdbd620b4ef30a6451748f5340 M\u0000src/renderer/src/store/slices/agent-status-provider-session.test.ts\u0000:100644 000000 7915f8a9b5ddb4b62ef4d8c5dd37fd3058213038 0000000000000000000000000000000000000000 D\u0000src/renderer/src/store/slices/agent-status-quit-capture-resumable-agents.test.ts\u0000:100644 000000 632b55b52dde06e280e931542f390d1f0660f92d 0000000000000000000000000000000000000000 D\u0000src/renderer/src/store/slices/agent-status-remember-sleeping-session.test.ts\u0000:100644 000000 fd8f38c8b191c4ddabfe2b976592ccc1b0134457 0000000000000000000000000000000000000000 D\u0000src/renderer/src/store/slices/agent-status-reminted-pane-key.test.ts\u0000:100644 000000 da11a3077c1e7e03959185895cceb60d937949ab 0000000000000000000000000000000000000000 D\u0000src/renderer/src/store/slices/agent-status-session-boundary-done.test.ts\u0000:100644 000000 324b70e075f1ec0a9e1691186aa3924f82fbe862 0000000000000000000000000000000000000000 D\u0000src/renderer/src/store/slices/agent-status-turn-started-at.test.ts\u0000:100644 000000 ea369e54f6a1003092a0f99bb4535415c98dd4ad 0000000000000000000000000000000000000000 D\u0000src/renderer/src/store/slices/terminal-startup-command-retention.test.ts\u0000:100644 100644 b1db16ae413c4c7b56bf5e542ac9267dec0f1413 adf36cdb92d5659692681d71e5b7bc3ccadd2212 M\u0000src/renderer/src/store/slices/terminal-tab-recovery-remount.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 7006497ec43e4f0b63f2838678d6bfc8914c7654 A\u0000src/shared/agent-resume-argv-drop.spec.ts\u0000:100644 000000 7006497ec43e4f0b63f2838678d6bfc8914c7654 0000000000000000000000000000000000000000 D\u0000src/shared/agent-resume-argv-drop.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 544cd92cd20641ca12ba022829a4259fedfb9794 A\u0000src/shared/agent-resume-launch-command.spec.ts\u0000:100644 000000 544cd92cd20641ca12ba022829a4259fedfb9794 0000000000000000000000000000000000000000 D\u0000src/shared/agent-resume-launch-command.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 75375e3500fb2404e5f78fcb5c190ecee338e927 A\u0000src/shared/agent-startup-plan-inputs-extra-agent-args.spec.ts\u0000:100644 000000 75375e3500fb2404e5f78fcb5c190ecee338e927 0000000000000000000000000000000000000000 D\u0000src/shared/agent-startup-plan-inputs-extra-agent-args.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 bc645aba5ea1339069954f3d05e10c8a036cb821 A\u0000src/shared/agent-startup-plan-inputs.spec.ts\u0000:100644 000000 bc645aba5ea1339069954f3d05e10c8a036cb821 0000000000000000000000000000000000000000 D\u0000src/shared/agent-startup-plan-inputs.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 07cc16fbc836954571d6f7d2ca26327fa03a4f5d A\u0000src/shared/automation-extra-agent-args.spec.ts\u0000:100644 000000 07cc16fbc836954571d6f7d2ca26327fa03a4f5d 0000000000000000000000000000000000000000 D\u0000src/shared/automation-extra-agent-args.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 0a99d2cc67b3f214c84ef6078b21546fa2c21913 A\u0000src/shared/dsh-startup.spec.ts\u0000:100644 000000 0a99d2cc67b3f214c84ef6078b21546fa2c21913 0000000000000000000000000000000000000000 D\u0000src/shared/dsh-startup.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 b58dc4bd0917b091471f88986b5c41f45ba3a70b A\u0000src/shared/freebuff-launch.spec.ts\u0000:100644 000000 b58dc4bd0917b091471f88986b5c41f45ba3a70b 0000000000000000000000000000000000000000 D\u0000src/shared/freebuff-launch.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 65806790af4a05442a2376cea1ec875dd0f10d4f A\u0000src/shared/freebuff-startup.spec.ts\u0000:100644 000000 65806790af4a05442a2376cea1ec875dd0f10d4f 0000000000000000000000000000000000000000 D\u0000src/shared/freebuff-startup.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 eb8c94e2e5c863f8313a68da202f84ba330c8043 A\u0000src/shared/omp-fresh-launch.spec.ts\u0000:100644 000000 eb8c94e2e5c863f8313a68da202f84ba330c8043 0000000000000000000000000000000000000000 D\u0000src/shared/omp-fresh-launch.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 286f7e3ad2f55dac366907098d04365de1ece2a6 A\u0000src/shared/opencode-startup-prompt.spec.ts\u0000:100644 000000 286f7e3ad2f55dac366907098d04365de1ece2a6 0000000000000000000000000000000000000000 D\u0000src/shared/opencode-startup-prompt.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 513bd76eb515160ce29b287afde3f0ac48bfa007 A\u0000src/shared/opencode2-resume-launch-mode.spec.ts\u0000:100644 000000 513bd76eb515160ce29b287afde3f0ac48bfa007 0000000000000000000000000000000000000000 D\u0000src/shared/opencode2-resume-launch-mode.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 510d2d9407783f3fcbfc2fc00bc148a1f0029c8e A\u0000src/shared/qoder-cn-launch-recipe.spec.ts\u0000:100644 000000 510d2d9407783f3fcbfc2fc00bc148a1f0029c8e 0000000000000000000000000000000000000000 D\u0000src/shared/qoder-cn-launch-recipe.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 e56b5b0892b4cf84d1ffaed7103b895f7cf07ac1 A\u0000src/shared/tui-agent-fork-startup.spec.ts\u0000:100644 000000 e56b5b0892b4cf84d1ffaed7103b895f7cf07ac1 0000000000000000000000000000000000000000 D\u0000src/shared/tui-agent-fork-startup.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 78d8e8f2c1c16f1a92ff262ab44f3ecc002549cf A\u0000src/shared/tui-agent-launch-defaults.spec.ts\u0000:100644 000000 78d8e8f2c1c16f1a92ff262ab44f3ecc002549cf 0000000000000000000000000000000000000000 D\u0000src/shared/tui-agent-launch-defaults.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 f117493dc7114818d4eabbd27560599d4ef38720 A\u0000src/shared/tui-agent-prompt-transport.spec.ts\u0000:100644 000000 f117493dc7114818d4eabbd27560599d4ef38720 0000000000000000000000000000000000000000 D\u0000src/shared/tui-agent-prompt-transport.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 d62bdb9293c504ecc518dd95efccd63cd1092eca A\u0000src/shared/tui-agent-startup-copilot-resume.spec.ts\u0000:100644 000000 d62bdb9293c504ecc518dd95efccd63cd1092eca 0000000000000000000000000000000000000000 D\u0000src/shared/tui-agent-startup-copilot-resume.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 bcf5f43750c1829e23709d118ab20458687d32db A\u0000src/shared/tui-agent-startup-hermes.spec.ts\u0000:100644 000000 bcf5f43750c1829e23709d118ab20458687d32db 0000000000000000000000000000000000000000 D\u0000src/shared/tui-agent-startup-hermes.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 63d7e9b70059e105212f843b624d80265de4c106 A\u0000src/shared/tui-agent-startup-session-options.spec.ts\u0000:100644 000000 63d7e9b70059e105212f843b624d80265de4c106 0000000000000000000000000000000000000000 D\u0000src/shared/tui-agent-startup-session-options.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 1698e86b08b1db657ae42e448bef03f63430d172 A\u0000src/shared/tui-agent-startup-shell.spec.ts\u0000:100644 000000 1698e86b08b1db657ae42e448bef03f63430d172 0000000000000000000000000000000000000000 D\u0000src/shared/tui-agent-startup-shell.test.ts\u0000:000000 100644 0000000000000000000000000000000000000000 6da47b35f1a6faab407f2ff09c9d078f91ede40d A\u0000src/shared/tui-agent-startup.spec.ts\u0000:100644 100644 6da47b35f1a6faab407f2ff09c9d078f91ede40d 299464c765eb822a62b8cfb9cbac4bdd3bc18e1e M\u0000src/shared/tui-agent-startup.test.ts\u0000" +} diff --git a/config/scripts/node-server-native-compat-scope.mjs b/config/scripts/node-server-native-compat-scope.mjs new file mode 100644 index 00000000000..7ce843068ec --- /dev/null +++ b/config/scripts/node-server-native-compat-scope.mjs @@ -0,0 +1,133 @@ +// The glibc 2.17 lane builds and smokes native slots; it does not run the server tests. +export const NATIVE_COMPAT_ENTRY_POINTS = [ + 'config/scripts/build-orcad-prebuilds.mjs', + 'config/scripts/orcad-prebuild-smoke.mjs', + 'config/scripts/orcad-prebuild-smoke-child.cjs', + // createRequire and the smoke subprocess do not expose these edges to esbuild. + 'config/scripts/node-pty-job-ownership.cjs', + 'config/scripts/verify-linux-glibc-floor.cjs' +] + +// These files configure or support tests; additions/deletions still require qualification. +const MODIFIED_TEST_INPUTS = new Set([ + 'config/reliability-gates.jsonc', + 'src/main/daemon/serialize-grid-variant-scope.ts', + 'config/scripts/vitest-sqlite-database-runtime-files.mjs', + 'src/main/native-chat/agent-session-journal/journal-host-database-test-support.ts' +]) + +function isRepositoryPath(file) { + return ( + typeof file === 'string' && + file.length > 0 && + !file.startsWith('/') && + !file.includes(':') && + !/[\\\0\r\n]/.test(file) && + file.split('/').every((part) => part !== '' && part !== '.' && part !== '..') + ) +} + +function isOrdinaryChange(header) { + const match = + /^:(\d{6}) (\d{6}) ([a-f0-9]{40}|[a-f0-9]{64}) ([a-f0-9]{40}|[a-f0-9]{64}) ([ADM])$/.exec( + header + ) + if (!match) { + return false + } + const [, beforeMode, afterMode, before, after, status] = match + if (before.length !== after.length) { + return false + } + const ordinary = (mode) => mode === '100644' || mode === '100755' + const absent = (mode, hash) => mode === '000000' && /^0+$/.test(hash) + const present = (mode, hash) => ordinary(mode) && !/^0+$/.test(hash) + if (status === 'A') { + return absent(beforeMode, before) && present(afterMode, after) + } + if (status === 'D') { + return present(beforeMode, before) && absent(afterMode, after) + } + return present(beforeMode, before) && present(afterMode, after) +} + +export function readNativeCompatDiff(changedFiles, rawDiff) { + if ( + changedFiles.length === 0 || + new Set(changedFiles).size !== changedFiles.length || + !changedFiles.every(isRepositoryPath) || + typeof rawDiff !== 'string' || + !rawDiff.endsWith('\0') + ) { + return null + } + const fields = rawDiff.slice(0, -1).split('\0') + if (fields.length !== changedFiles.length * 2) { + return null + } + const entries = [] + for (let index = 0; index < fields.length; index += 2) { + const header = fields[index] + const file = fields[index + 1] + if (!isOrdinaryChange(header) || !isRepositoryPath(file)) { + return null + } + entries.push({ status: header.slice(-1), file }) + } + const files = new Set(entries.map(({ file }) => file)) + return files.size === changedFiles.length && changedFiles.every((file) => files.has(file)) + ? entries + : null +} + +function isTestInput({ status, file }) { + // Build policy and process-host fixtures can be read without a static import. + if ( + file.startsWith('src/packages/') || + /^config\/scripts\/[^/]*(?:orcad|node-server|node-pty|native|glibc|pinned-node|node-runtime|workspace-source-exports|script-module-dependencies)[^/]*$/.test( + file + ) + ) { + return false + } + return ( + (status === 'M' && MODIFIED_TEST_INPUTS.has(file)) || + /^src\/(?:[^/]+\/)*[^/]+\.(?:test|spec)\.(?:ts|tsx)$/.test(file) || + /^config\/scripts\/(?:[^/]+\/)*[^/]+\.test\.(?:ts|mjs)$/.test(file) || + /^tests\/e2e\/(?:[^/]+\/)*[^/]+\.unit\.test\.ts$/.test(file) + ) +} + +export async function classifyNativeCompatChanges( + changedFiles, + collect, + { pullRequest = false, fullQualification = false, rawDiff, deferGraph = false } = {} +) { + const required = (reason) => ({ nativeCompatibility: true, reason }) + if (!pullRequest || fullQualification) { + return required('Native qualification is unconditional outside an ordinary PR') + } + const entries = readNativeCompatDiff(changedFiles, rawDiff) + if (!entries || !entries.every(isTestInput)) { + return required('The complete diff is not limited to recognized test inputs') + } + if (deferGraph) { + return { graphRequired: true, reason: 'Native build and smoke imports must be checked' } + } + try { + const inputs = await collect({ entryPoints: NATIVE_COMPAT_ENTRY_POINTS }) + if ( + !(inputs instanceof Set) || + ![...inputs].every(isRepositoryPath) || + !NATIVE_COMPAT_ENTRY_POINTS.every((file) => inputs.has(file)) + ) { + return required('The native build and smoke input graph is incomplete') + } + const matched = changedFiles.find((file) => inputs.has(file)) + return matched + ? required(`Native build or smoke input changed: ${matched}`) + : { nativeCompatibility: false, reason: 'Only unconsumed test inputs changed' } + } catch (error) { + return required(`Native dependency graph unavailable: ${String(error)}`) + } +} diff --git a/config/scripts/node-server-native-compat-scope.test.mjs b/config/scripts/node-server-native-compat-scope.test.mjs new file mode 100644 index 00000000000..f338f53d030 --- /dev/null +++ b/config/scripts/node-server-native-compat-scope.test.mjs @@ -0,0 +1,404 @@ +import { readFileSync } from 'node:fs' +import { expect, it, vi } from 'vitest' +import { parse } from 'yaml' +import { compileAggregateExpression } from './pr-aggregate-gate-context.mjs' +import { + classifyNativeCompatChanges, + NATIVE_COMPAT_ENTRY_POINTS, + readNativeCompatDiff +} from './node-server-native-compat-scope.mjs' +import { classifyNodeServerChanges, collectNodeServerInputs } from './node-server-change-scope.mjs' +import { NODE_SERVER_RUNNERS, nodeServerQualification } from './node-server-qualification.mjs' + +const root = 'src/main/persistence/profile-state/profile-state-recovery-crash-boundaries.test.ts' +const support = 'src/main/native-chat/agent-session-journal/journal-host-database-test-support.ts' +const cleanup = JSON.parse( + readFileSync( + new URL('./node-server-native-compat-cleanup.test-fixture.json', import.meta.url), + 'utf8' + ) +) +const rawHeader = (status, beforeMode, afterMode) => { + const before = (status === 'A' ? '0' : '1').repeat(40) + const after = (status === 'D' ? '0' : '2').repeat(40) + return `:${beforeMode} ${afterMode} ${before} ${after} ${status}` +} +const rawDiff = (entries) => + entries + .map(([status, file]) => { + const beforeMode = status === 'A' ? '000000' : '100644' + const afterMode = status === 'D' ? '000000' : '100644' + return `${rawHeader(status, beforeMode, afterMode)}\0${file}\0` + }) + .join('') +const filesOf = (entries) => entries.map(([, file]) => file) +const nativeInputs = () => new Set(NATIVE_COMPAT_ENTRY_POINTS) +const optionsFor = (entries) => ({ pullRequest: true, rawDiff: rawDiff(entries) }) + +it('reconciles complete no-rename status evidence including additions and deletions', () => { + const entries = [ + ['M', root], + ['A', 'src/shared/new.spec.ts'], + ['D', 'src/shared/old.test.ts'] + ] + expect(readNativeCompatDiff(filesOf(entries), rawDiff(entries))).toEqual( + entries.map(([status, file]) => ({ status, file })) + ) +}) + +it.each([ + [[], ''], + [[root], undefined], + [[root], rawDiff([['M', root]]).slice(0, -1)], + [[root], `${rawDiff([['M', root]])}trailing\0`], + [ + [root], + rawDiff([ + ['M', root], + ['M', root] + ]) + ], + [ + [root, root], + rawDiff([ + ['M', root], + ['M', root] + ]) + ], + [ + [root, 'src/shared/other.test.ts'], + rawDiff([ + ['M', root], + ['M', root] + ]) + ], + [[root], rawDiff([['M', 'src/shared/foreign.test.ts']])], + [[root], rawDiff([['R100', root]])], + [[root], rawDiff([['T', root]])], + ...[ + 'src/../outside.test.ts', + 'src/./outside.test.ts', + 'src//outside.test.ts', + '/src/outside.test.ts', + 'src\\outside.test.ts', + 'src/C:/outside.test.ts' + ].map((file) => [[file], rawDiff([['M', file]])]) +])( + 'requires native qualification for incomplete or malformed diff evidence: %j', + async (files, raw) => { + const collect = vi.fn(nativeInputs) + const result = await classifyNativeCompatChanges(files, collect, { + pullRequest: true, + rawDiff: raw + }) + expect(result.nativeCompatibility).toBe(true) + expect(collect).not.toHaveBeenCalled() + } +) + +it.each([ + ['120000', '120000', 'M'], + ['000000', '120000', 'A'], + ['120000', '000000', 'D'], + ['160000', '160000', 'M'], + ['100644', '120000', 'T'], + ['100644', '100644', 'A'], + ['000000', '000000', 'D'], + ['100644', '000000', 'M'] +])('rejects nonordinary or inconsistent modes: %s %s %s', async (beforeMode, afterMode, status) => { + const raw = `${rawHeader(status, beforeMode, afterMode)}\0${root}\0` + const collect = vi.fn(nativeInputs) + const result = await classifyNativeCompatChanges([root], collect, { + pullRequest: true, + rawDiff: raw + }) + expect(result.nativeCompatibility).toBe(true) + expect(collect).not.toHaveBeenCalled() +}) + +it('checks the native closure before skipping a selected server test', async () => { + const entries = [['M', root]] + const collect = vi.fn(nativeInputs) + const options = optionsFor(entries) + const deferred = await classifyNativeCompatChanges([root], collect, { + ...options, + deferGraph: true + }) + expect(deferred.graphRequired).toBe(true) + expect(deferred.nativeCompatibility).toBeUndefined() + expect(collect).not.toHaveBeenCalled() + expect(await classifyNativeCompatChanges([root], collect, options)).toMatchObject({ + nativeCompatibility: false + }) + expect(collect).toHaveBeenCalledWith({ entryPoints: NATIVE_COMPAT_ENTRY_POINTS }) +}) + +it('retains native qualification when a recognized test is consumed by its builder or smoke', async () => { + const result = await classifyNativeCompatChanges( + [root], + async () => new Set([...NATIVE_COMPAT_ENTRY_POINTS, root]), + optionsFor([['M', root]]) + ) + expect(result.nativeCompatibility).toBe(true) +}) + +it.each(['missing-root', 'malformed-input', 'unavailable'])( + 'retains native qualification when closure evidence is %s', + async (evidence) => { + const collect = async () => { + if (evidence === 'unavailable') { + throw new Error('missing imported native input') + } + return evidence === 'missing-root' + ? new Set() + : new Set([...NATIVE_COMPAT_ENTRY_POINTS, '../bad']) + } + const result = await classifyNativeCompatChanges([root], collect, optionsFor([['M', root]])) + expect(result.nativeCompatibility).toBe(true) + } +) + +it.each([ + 'package.json', + 'pnpm-lock.yaml', + '.npmrc', + 'src/shared/node-runtime-pin.ts', + 'src/shared/product-source.ts', + 'src/packages/process-host/src/run-process.test.ts', + 'native/example/fixture.test.ts', + 'config/patches/node-pty@1.1.0.patch', + 'config/scripts/build-orcad-prebuilds.mjs', + 'config/scripts/orcad-prebuild-smoke.test.mjs', + 'config/scripts/node-server-native-compat-scope.mjs', + 'config/scripts/node-server-native-compat-scope.test.mjs', + 'config/scripts/node-server-native-compat-cleanup.test-fixture.json', + 'config/scripts/node-server-change-scope.mjs', + 'config/scripts/verify-linux-glibc-floor.cjs', + '.github/actions/prepare-native-runtime/action.yml', + '.github/actions/prepare-headless-compiler/action.yml', + '.github/workflows/node-server-tests.yml', + 'src/main/persistence/worker.test-fixture.ts', + 'src/main/native-chat/new-test-support.ts', + 'docs/ci.md' +])('keeps an unknown or protected input even when a test changes too: %s', async (file) => { + const entries = [ + ['M', root], + ['M', file] + ] + const collect = vi.fn(nativeInputs) + const result = await classifyNativeCompatChanges(filesOf(entries), collect, optionsFor(entries)) + expect(result.nativeCompatibility).toBe(true) + expect(collect).not.toHaveBeenCalled() +}) + +it.each(['A', 'D'])( + 'retains qualification for %s of a named test-support input', + async (status) => { + const entries = [[status, support]] + const result = await classifyNativeCompatChanges([support], nativeInputs, optionsFor(entries)) + expect(result.nativeCompatibility).toBe(true) + } +) + +it.each([ + [ + ['D', 'src/shared/source.ts'], + ['A', 'src/shared/source.test.ts'] + ], + [ + ['D', 'src/shared/source.test.ts'], + ['A', 'src/shared/source.ts'] + ] +])( + 'retains qualification for a rename crossing production and test paths: %j', + async (...entries) => { + const result = await classifyNativeCompatChanges( + filesOf(entries), + nativeInputs, + optionsFor(entries) + ) + expect(result.nativeCompatibility).toBe(true) + } +) + +it.each([{}, { pullRequest: false }, { pullRequest: true, fullQualification: true }])( + 'keeps native qualification without ordinary PR authorization: %j', + async (options) => { + const collect = vi.fn(nativeInputs) + const result = await classifyNativeCompatChanges([root], collect, { + rawDiff: rawDiff([['M', root]]), + ...options + }) + expect(result.nativeCompatibility).toBe(true) + expect(collect).not.toHaveBeenCalled() + } +) + +it('qualifies a standalone change to the executable native routing policy', async () => { + const file = 'config/scripts/node-server-native-compat-scope.mjs' + const collect = vi.fn(nativeInputs) + const scope = await classifyNodeServerChanges([file], collect, { modifiedFiles: [file] }) + expect(scope.shouldRun).toBe(true) + expect(collect).not.toHaveBeenCalled() + expect(nodeServerQualification([file], scope)).toEqual({ + qualification: true, + runners: NODE_SERVER_RUNNERS + }) + const native = await classifyNativeCompatChanges([file], collect, optionsFor([['M', file]])) + expect(native.nativeCompatibility).toBe(true) + expect(collect).not.toHaveBeenCalled() +}) + +it('recognizes the complete 119-path cleanup while retaining all runtime platform qualification', async () => { + const entries = cleanup.paths + expect(entries).toHaveLength(119) + const files = filesOf(entries) + const native = await classifyNativeCompatChanges(files, nativeInputs, { + pullRequest: true, + rawDiff: cleanup.rawDiff + }) + expect(native.nativeCompatibility).toBe(false) + const scope = await classifyNodeServerChanges(files, async () => { + throw new Error('the selected profile-state test forces runtime qualification') + }) + expect(scope.shouldRun).toBe(true) + expect(scope.graphUnavailable).toBeUndefined() + expect(nodeServerQualification(files, scope)).toEqual({ + qualification: true, + runners: NODE_SERVER_RUNNERS + }) +}) + +it('checks the cleanup against the actual native build and smoke closure', async () => { + const files = filesOf(cleanup.paths) + const result = await classifyNativeCompatChanges(files, collectNodeServerInputs, { + pullRequest: true, + rawDiff: cleanup.rawDiff + }) + expect(result.nativeCompatibility).toBe(false) +}, 60_000) + +it('limits the extra job guard to glibc 2.17 and preserves unconditional callers', () => { + const workflow = parse( + readFileSync(new URL('../../.github/workflows/node-server-tests.yml', import.meta.url), 'utf8') + ) + const compat = workflow.jobs.linux_glibc217_compat + expect(compat.if).toContain("needs.changes.outputs.native_compatibility != 'false'") + expect(compat.if).toContain( + "github.event_name != 'pull_request' || inputs.build_template || inputs.ref != ''" + ) + for (const job of [ + workflow.jobs.persistence, + workflow.jobs.linux_glibc_floor, + workflow.jobs.linux_musl + ]) { + expect(job.if).not.toContain('native_compatibility') + } + const scope = workflow.jobs.changes.steps.find((step) => step.id === 'scope') + const graph = workflow.jobs.changes.steps.find((step) => step.id === 'graph') + expect(scope.run).toContain('git diff --raw --no-renames --no-abbrev -z HEAD^1 HEAD') + for (const step of [scope, graph]) { + expect(step.env.ALLOW_TEST_ONLY_NATIVE_SKIP).toBe( + "${{ github.event_name == 'pull_request' && !inputs.build_template && inputs.ref == '' }}" + ) + expect(step.run).toContain('--pull-request --diff-paths "$RUNNER_TEMP/node-server-native-diff"') + } + expect(workflow.on).toHaveProperty('workflow_dispatch') + expect(workflow.on).toHaveProperty('workflow_call') + expect(workflow.on.schedule).toHaveLength(1) +}) + +it('builds server glibc slots on glibc 2.28 and the compat slot on glibc 2.17 (design D6)', () => { + const workflow = parse( + readFileSync(new URL('../../.github/workflows/node-server-tests.yml', import.meta.url), 'utf8') + ) + const floor = workflow.jobs.linux_glibc_floor + expect(floor.container).toBe('${{ matrix.image }}') + expect(floor.strategy.matrix.include).toEqual([ + { + os: 'ubuntu-22.04', + image: expect.stringMatching(/^quay\.io\/pypa\/manylinux_2_28_x86_64@sha256:[0-9a-f]{64}$/) + }, + { + os: 'ubuntu-24.04-arm', + image: expect.stringMatching(/^quay\.io\/pypa\/manylinux_2_28_aarch64@sha256:[0-9a-f]{64}$/) + } + ]) + expect(floor.env).toMatchObject({ CC: 'gcc', CXX: 'g++' }) + + const compat = workflow.jobs.linux_glibc217_compat + expect(compat.needs).toEqual(['changes', 'persistence']) + expect(compat.if).toContain(floor.if.slice(0, -3)) + expect(compat.if).toContain("needs.changes.outputs.native_compatibility != 'false'") + expect(compat.if).toContain( + "github.event_name != 'pull_request' || inputs.build_template || inputs.ref != ''" + ) + expect(compat['runs-on']).toBe('ubuntu-22.04') + const run = compat.steps.map((step) => step.run ?? '').join('\n') + expect(run).toMatch(/quay\.io\/pypa\/manylinux2014_x86_64@sha256:[0-9a-f]{64} /) + expect(run).toContain('--slot=linux-x64-glibc217 --print-runtime') + expect(run).toContain('build-orcad-prebuilds.mjs --slot=linux-x64-glibc217\n') + expect(run).toContain('--require-slots linux-x64-glibc217') + expect(run).toContain( + 'env -u LD_LIBRARY_PATH node config/scripts/build-orcad-prebuilds.mjs --slot=linux-x64-glibc217 --smoke' + ) +}) + +const nativeWorkflow = parse( + readFileSync(new URL('../../.github/workflows/node-server-tests.yml', import.meta.url), 'utf8') +) +const nativeJob = compileAggregateExpression(nativeWorkflow.jobs.linux_glibc217_compat.if, [ + 'needs', + 'github', + 'inputs' +]) + +it.each([ + { expected: false, native: 'false' }, + { expected: true, native: 'true' }, + { expected: true, native: '' }, + { expected: true }, + { expected: true, native: 'false', event: 'push' }, + { expected: true, native: 'false', event: 'schedule' }, + { expected: true, native: 'false', event: 'workflow_dispatch' }, + { expected: true, native: 'false', template: true }, + { expected: true, native: 'false', ref: 'other-commit' }, + { expected: false, native: 'true', persistence: 'failure' }, + { expected: false, native: 'true', persistence: 'cancelled' }, + { expected: false, native: 'true', persistence: 'skipped' }, + { expected: false, native: 'true', qualification: 'false' }, + { expected: false, native: 'true', shouldRun: 'false' }, + { expected: false, native: 'true', cancelled: true } +])('evaluates the hosted native job guard with %j', (scenario) => { + const context = { + github: { event_name: scenario.event ?? 'pull_request' }, + inputs: { build_template: scenario.template ?? false, ref: scenario.ref ?? '' }, + needs: { + persistence: { result: scenario.persistence ?? 'success' }, + changes: { + outputs: { + should_run: scenario.shouldRun ?? 'true', + qualification: scenario.qualification ?? 'true', + ...(scenario.native === undefined ? {} : { native_compatibility: scenario.native }) + } + } + } + } + expect(nativeJob(context, scenario.cancelled ?? false).coerceString()).toBe( + String(scenario.expected) + ) +}) + +it.each(['M', 'A', 'D'])( + 'checks %s of the existing serializer test diagnostics against the native closure', + async (status) => { + const file = 'src/main/daemon/serialize-grid-variant-scope.ts' + const result = await classifyNativeCompatChanges( + [file], + collectNodeServerInputs, + optionsFor([[status, file]]) + ) + expect(result.nativeCompatibility).toBe(status !== 'M') + }, + 60_000 +)