Files
orca/.github/workflows/pr.yml
T
OrcaWinandOrcaWin 7d6603aa17 [comment audit] Complete repository-wide comment cleanup (#33249)
* [comment audit] Align legacy failure-read counts with cached display

* [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci]

* [comment audit] Condense shared generation, path and preview comments [skip ci]

* [comment audit] Condense host, file and Git contract rationale [skip ci]

* [comment audit] Tighten Git lifecycle and provider contract comments [skip ci]

* [comment audit] Clarify shared host authority and launch isolation [skip ci]

* [comment audit] simplify local access mobile and native chat contracts [skip ci]

* [comment audit] simplify native chat presentation and state contracts [skip ci]

* [comment audit] simplify native runtime and provider contracts [skip ci]

* [comment audit] simplify migration and orchestration contracts [skip ci]

* [comment audit] simplify plugin trust and process capture rationale [skip ci]

* [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci]

* [comment audit] clarify remote recovery and launch wire rationale [skip ci]

* [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci]

* [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci]

* [comment audit] clarify sidebar, skill and SSH invariants [skip ci]

* [comment audit] condense structured chat delivery and timing rationale [skip ci]

* [comment audit] condense terminal replay and telemetry invariants [skip ci]

* [comment audit] clarify launch permission and terminal delivery rationale [skip ci]

* [comment audit] clarify workspace ownership and layout contracts [skip ci]

* [comment audit] wait for fitted and painted SSH terminal links

* [comment audit] clarify layout persistence and session ownership [skip ci]

* [comment audit] clarify host routing and workspace lifecycle contracts [skip ci]

* [comment audit] clarify startup persistence and app lifetime comments [skip ci]

* [comment audit] clarify recovery and activity boundaries [skip ci]

* [comment audit] condense activity and artifact invariants [skip ci]

* [comment audit] condense automation authority contracts [skip ci]

* [comment audit] simplify automation scheduling and history contracts [skip ci]

* [comment audit] clarify browser chrome and annotation lifecycles [skip ci]

* [comment audit] clarify browser routing and retained guest ownership [skip ci]

* [comment audit] clarify browser recovery and document ownership comments [skip ci]

* [comment audit] restore hosted runners required by native IME fixtures

* [comment audit] shorten preview, palette and dashboard invariants [skip ci]

* [comment audit] clarify terminal preview, dashboard and retention comments [skip ci]

* [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci]

* [comment audit] shorten rich-text and combined-diff ownership comments [skip ci]

* [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci]

* [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci]

* [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci]

* [comment audit] clarify rich Markdown source and selection safeguards [skip ci]

* [comment audit] clarify editor loading and emulator stream lifetimes [skip ci]

* [comment audit] trim emulator and feature tour narration [skip ci]

* [comment audit] clarify floating workspace and GitHub cache ownership [skip ci]

* [comment audit] trim provider controls and Linear filter explanations [skip ci]

* [comment audit] clarify pairing and chat resume lifetimes [skip ci]

* [comment audit] clarify chat delivery and transcript lifetimes [skip ci]

* [comment audit] clarify session options and host-owned status [skip ci]

* [comment audit] clarify draft durability and live-status precedence [skip ci]

* [comment audit] Trim chat send and model ownership explanations [skip ci]

* [comment audit] Shorten transcript ownership and send recovery comments [skip ci]

* [comment audit] Simplify native chat composer and transcript hook comments [skip ci]

* [comment audit] Shorten native chat scroll and structured session explanations [skip ci]

* [comment audit] Trim workspace picker and onboarding commentary [skip ci]

* [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci]

* [comment audit] Trim sidebar and session history explanations [skip ci]

* [comment audit] Condense session resume and review checks comments [skip ci]

* [comment audit] Shorten checks state and file explorer refresh comments [skip ci]

* [comment audit] Trim plugin bridge and source-control action rationale [skip ci]

* [comment audit] Trim source-control listing and review rationale [skip ci]

* [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci]

* [comment audit] Condense Settings account and appearance rationale [skip ci]

* [comment audit] Trim pairing and repository Settings explanations [skip ci]

* [comment audit] Condense Settings search SSH and terminal safeguards [skip ci]

* [comment audit] Shorten Settings voice and provider ownership comments [skip ci]

* [comment audit] Condense Settings draft persistence and host-state rationale [skip ci]

* [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci]

* [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci]

* [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci]

* [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci]

* [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci]

* [comment audit] Prune focus-read baseline after comment cleanup

* [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci]

* [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci]

* [comment audit] Clarify viewport geometry and skill update safeguards [skip ci]

* [comment audit] Tighten skill inventory, usage and host status comments [skip ci]

* [comment audit] Clarify usage ownership and resource kill safeguards [skip ci]

* [comment audit] Tighten tab selection, rename and search contracts [skip ci]

* [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci]

* [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci]

* [comment audit] Tighten provider loading and terminal recovery comments [skip ci]

* [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci]

* [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci]

* [comment audit] Tighten PTY startup and replay explanations [skip ci]

* [comment audit] Clarify PTY identity and delivery ownership [skip ci]

* [comment audit] Shorten remote reconnect and input replay rationale [skip ci]

* [comment audit] Condense terminal parking clipboard and file link comments [skip ci]

* [comment audit] Clarify IME ownership and trim trace history [skip ci]

* [comment audit] Shorten pane recovery and parked watcher explanations [skip ci]

* [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci]

* [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci]

* [comment audit] Trim renderer lifecycle and menu rationale [skip ci]

* [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci]

* [comment audit] Clarify renderer hook evidence and source ownership [skip ci]

* [comment audit] Tighten IPC ownership and reconnect explanations [skip ci]

* [comment audit] Trim renderer focus, paste and attention comments [skip ci]

* [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci]

* [comment audit] Trim agent launch, resume and ownership commentary [skip ci]

* [comment audit] Shorten account, browser and editor comments [skip ci]

* [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci]

* [comment audit] Condense launch recovery and editor tokenization comments [skip ci]

* [comment audit] Condense workspace selection and terminal pane rationale [skip ci]

* [comment audit] Condense terminal fitting and rendering recovery comments [skip ci]

* [comment audit] Condense terminal input and host routing explanations [skip ci]

* [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci]

* [comment audit] Condense structured launch and terminal link explanations [skip ci]

* [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci]

* [comment audit] Condense session persistence and workspace activation rationale [skip ci]

* [comment audit] Condense workspace routing and browser ownership rationale [skip ci]

* [comment audit] Condense mirror recovery and layout authority rationale [skip ci]

* [comment audit] Condense runtime transport and file safety rationale [skip ci]

* [comment audit] Condense runtime graph and structured session rationale [skip ci]

* [comment audit] Condense browser creation and remote tab recovery rationale [skip ci]

* [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci]

* [comment audit] Condense frame, startup and provider cache rationale [skip ci]

* [comment audit] Condense host twin and agent lifecycle rationale [skip ci]

* [comment audit] Condense browser authority and editor cleanup rationale [skip ci]

* [comment audit] Condense editor buffer identity and review cache rationale [skip ci]

* [comment audit] Condense provider lifetime and repository ownership rationale [skip ci]

* [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci]

* [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci]

* [comment audit] Condense cleanup ownership and listing admission rationale [skip ci]

* [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci]

* [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci]

* [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci]

* [comment audit] Condense cloud rollout and assignment safety rationale [skip ci]

* [comment audit] Condense relay session and database recovery rationale [skip ci]

* [comment audit] Condense relay placement and protocol compatibility rationale [skip ci]

* [comment audit] Condense build and packaging constraints [skip ci]

* [comment audit] Condense CI gates and validation isolation rationale [skip ci]

* [comment audit] Condense catalog identity and browser validation rationale [skip ci]

* [comment audit] Condense mobile browser and frame-budget rationale [skip ci]

* [comment audit] Shorten mobile route and browser test explanations [skip ci]

* [comment audit] Condense editor and session seam test rationale [skip ci]

* [comment audit] Trim session and terminal closure history [skip ci]

* [comment audit] Shorten terminal browser and census explanations [skip ci]

* [comment audit] Preserve root-only E2E diagnostic baseline

* [comment audit] Condense mobile seam and HTML preview test rationale [skip ci]

* [comment audit] Condense preview instrumentation and render harness rationale [skip ci]

* [comment audit] Condense bundle contracts and native-build constraints [skip ci]

* [comment audit] Shorten native packaging and CI routing rationale [skip ci]

* [comment audit] Condense release tooling and benchmark constraints [skip ci]

* [comment audit] Condense packaging and platform gate rationale [skip ci]

* [comment audit] Shorten mobile route and history rationale [skip ci]

* [comment audit] Condense mobile browser and component rationale [skip ci]

* [comment audit] Shorten drawer and workspace-form rationale [skip ci]

* [comment audit] Trim mobile review and editor-document explanations [skip ci]

* [comment audit] Condense editor lifecycle and Markdown explanations [skip ci]

* [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci]

* [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci]

* [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci]

* [comment audit] Condense bridge stream, native request and audio rationale [skip ci]

* [comment audit] Reduce bridge schema and page-client commentary [skip ci]

* [comment audit] Tighten mobile page bridge contracts [skip ci]

* [comment audit] Clarify mobile cache and session constraints [skip ci]

* [comment audit] Condense mobile route and lifecycle rationale [skip ci]

* [comment audit] Tighten mobile shell hook timing rationale [skip ci]

* [comment audit] Clarify route handoff and notification compatibility [skip ci]

* [comment audit] Clarify push registration and dictation cleanup constraints [skip ci]

* [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci]

* [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci]

* [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci]

* [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci]

* [comment audit] Tighten stream replay and queued session ownership rationale [skip ci]

* [comment audit] Condense mobile composer and reply-schema constraints [skip ci]

* [comment audit] Clarify mobile transcript recovery and session ownership [skip ci]

* [comment audit] Clarify mobile queue and source-control compatibility [skip ci]

* [comment audit] Condense storage and task launch constraints [skip ci]

* [comment audit] Clarify task schemas and terminal document ownership [skip ci]

* [comment audit] Tighten terminal input and viewport rationale [skip ci]

* [comment audit] Shorten terminal and RPC recorder contracts [skip ci]

* [comment audit] Condense recorder engine and connection contracts [skip ci]

* [comment audit] Shorten host credential and relay recovery comments [skip ci]

* [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci]

* [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci]

* [comment audit] Condense catalog and relay entry contracts [skip ci]

* [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci]

* [comment audit] Condense WSL relay and E2E regression rationale [skip ci]

* [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci]

* [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci]

* [comment audit] Shorten layout oracle and regression explanations [skip ci]

* [comment audit] Condense paired recovery and ownership regression contracts [skip ci]

* [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci]

* [comment audit] Condense terminal rendering and IME regression rationale [skip ci]

* [comment audit] Shorten terminal recovery and benchmark contracts [skip ci]

* [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci]

* [comment audit] Condense workflow routing and release rationale [skip ci]

* [comment audit] Condense mobile security and installer contracts [skip ci]

* [comment audit] Shorten native rationale and stylesheet workarounds [skip ci]

* [comment audit] Shorten Windows visibility probe rationale [skip ci]

* [comment audit] Condense embedded release and signing rationale [skip ci]

* [comment audit] Retain current main test behavior in final reconciliation [skip ci]

* [comment audit] Complete final comment review across current source inventory

* [comment audit] Handle consolidated audit reports before final CI

* [comment audit] Preserve credential storage census detail [skip ci]

* [comment audit] Scale broad changed E2E selections with verified timing shards

* [comment audit] Align E2E assertions with browser close and settled session metadata

* [comment audit] Match existing E2Es to current resource and windowing contracts

* [comment audit] Observe the shared server outage notice in reconnect coverage

* [comment audit] Make existing renderer probes measure settled tab and PTY work

* [comment audit] Align palette E2E selector with task search

* [comment audit] Refresh remaining palette search selectors

* [comment audit] Keep selector correction within selected audit specs

* [comment audit] Await emoji fixture settings inside renderer evaluation

* [comment audit] Cancel fixture fit retries between terminal tests

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-25 04:05:46 -07:00

1598 lines
72 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: PR Checks
run-name: "PR ${{ github.event.pull_request.number }} | source ${{ github.sha }} | workflow ${{ github.workflow_sha }} | unit ${{ github.event.pull_request.draft && vars.ORCA_UNIT_SELECTION_MODE == 'selected' && 'selected' || 'full' }}"
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
- converted_to_draft
concurrency:
group: pr-checks-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# Keep required verify present for docs-only PRs while routing expensive jobs by inputs; empty diffs fail closed to full checks.
code_paths:
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
name: detect changes and check repository guards
# Start routing without waiting behind the GitHub-hosted test queue.
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
permissions:
contents: read
actions: read
outputs:
should_run: ${{ steps.filter.outputs.should_run }}
reused_run_id: ${{ steps.readiness.outputs.run_id }}
# A proven success masks required work only; advisory routing still uses the full diff.
mobile_dependencies: ${{ steps.filter.outputs.mobile_dependencies }}
mobile_web_app: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.mobile_web_app }}
static_analysis: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.static_analysis }}
typecheck: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.typecheck }}
mobile_ci_tools: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.mobile_ci_tools }}
git_compatibility: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.git_compatibility }}
codex_index_heal_contract: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.codex_index_heal_contract }}
xterm_patch_sync: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.xterm_patch_sync }}
shell_contracts: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.shell_contracts }}
test: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.test }}
relay_integration: ${{ steps.filter.outputs.relay_integration }}
orcad_browser: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.orcad_browser }}
cross-version-wire: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.cross-version-wire }}
managed_hook_node18: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.managed_hook_node18 }}
package: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.package }}
windows_process_probe: ${{ steps.filter.outputs.windows_process_probe }}
package_windows: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.package_windows }}
layout_oracle: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.layout_oracle }}
dependency_isolation: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.dependency_isolation }}
e2e_should_run: ${{ steps.e2e_filter.outputs.should_run }}
test_files: ${{ steps.e2e_filter.outputs.test_files }}
e2e_run_changed: ${{ steps.e2e_filter.outputs.e2e_run_changed }}
e2e_needs_build: ${{ steps.e2e_filter.outputs.e2e_needs_build }}
e2e_ssh_shards: ${{ steps.e2e_filter.outputs.e2e_ssh_shards }}
native_ime_source_changed: ${{ steps.e2e_filter.outputs.native_ime_source_changed }}
wsl_source_changed: ${{ steps.e2e_filter.outputs.wsl_source_changed }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
# Fetch 50 commits for merge HEAD^1 diffs instead of all 8127 refs (~20s); sparse ~7-file blob:none checkout avoids materialization/refetch (9.5s→1.6s).
fetch-depth: 50
filter: blob:none
sparse-checkout: |
/config/scripts/git-pull-request-diff-base.mjs
/package.json
/README.md
/docs/readme/
/.github/scripts/check-root-directory-entries.mjs
/.github/scripts/check-required-pr-context.mjs
/.github/workflows/
/config/.bun-version
/config/scripts/check-readme-local-links.mjs
/config/scripts/pr-code-change-scope.mjs
/.oxfmtrc.json
/config/scripts/pr-package-test-paths.mjs
/config/scripts/pr-layout-oracle-route.mjs
/config/scripts/pr-e2e-source-routing.mjs
/config/scripts/ci-e2e-job-selection.mjs
/config/scripts/pr-ready-check-reuse.mjs
sparse-checkout-cone-mode: false
persist-credentials: false
- name: Reject new root-level files and folders
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
DIFF_BASE="$(node config/scripts/git-pull-request-diff-base.mjs "$BASE_SHA")"
node .github/scripts/check-root-directory-entries.mjs "$DIFF_BASE" HEAD
# The full Git index retains link targets outside the sparse working tree.
- name: Check README local links
run: node config/scripts/check-readme-local-links.mjs
- name: Set up Bun for required workflow validation
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: config/.bun-version
- name: Check required PR context ownership
run: bun .github/scripts/check-required-pr-context.mjs
# Readiness changes eligibility for advisory tests, not the already-tested source.
- name: Find identical successful required checks
id: readiness
if: github.event.action == 'ready_for_review'
env:
GH_TOKEN: ${{ github.token }}
PR_CHECK_WORKFLOW_SHA: ${{ github.workflow_sha }}
run: node config/scripts/pr-ready-check-reuse.mjs
- name: Classify changed paths
id: filter
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set -euo pipefail
# PR merge HEAD’s first parent is the base; --merge-base would require the payload SHA/full history.
DIFF_BASE="$(node config/scripts/git-pull-request-diff-base.mjs "$BASE_SHA")"
# --no-renames retains code-side deletions when files move into docs.
CHANGED="$(git diff --name-only --no-renames --diff-filter=ACDMR "$DIFF_BASE" HEAD)"
echo "Changed paths:"
printf '%s\n' "$CHANGED"
MOBILE_CI_ARGS=()
if git diff --quiet --no-renames --diff-filter=ACDRTUXB "$DIFF_BASE" HEAD; then
MOBILE_CI_ARGS=(--mobile-ci-tools --daemon-init-test-support)
fi
printf '%s\n' "$CHANGED" | node config/scripts/pr-code-change-scope.mjs "${MOBILE_CI_ARGS[@]}" | tee -a "$GITHUB_OUTPUT"
# Reuse the path-detector checkout instead of queuing another runner.
- name: Filter changed E2E specs
id: e2e_filter
if: github.event.pull_request.draft != true && steps.filter.outputs.should_run == 'true'
run: |
set -euo pipefail
BASE="${{ github.event.pull_request.base.sha }}"
DIFF_BASE="$(node config/scripts/git-pull-request-diff-base.mjs "$BASE")"
CHANGED="$(git diff --name-only --diff-filter=AMCR "$DIFF_BASE" HEAD)"
E2E_SCOPE_ARGS=()
if git diff --quiet --no-renames --diff-filter=ACDRTUXB "$DIFF_BASE" HEAD; then
E2E_SCOPE_ARGS=(--daemon-init-test-support)
fi
# Executable routes let tests prove authorities/exclusions/sentinels without evaluating shell.
TEST_FILES_JSON="$(printf '%s\n' "$CHANGED" | node config/scripts/pr-e2e-source-routing.mjs "${E2E_SCOPE_ARGS[@]}")"
echo "test_files=$TEST_FILES_JSON" >> "$GITHUB_OUTPUT"
printf '%s\n' "$TEST_FILES_JSON" | node config/scripts/ci-e2e-job-selection.mjs --job-outputs >> "$GITHUB_OUTPUT"
# Real IME runs an entire IBus session; route it by IME source, independently of spec lists.
NATIVE_IME_SOURCE_CHANGED="$(printf '%s\n' "$CHANGED" | node config/scripts/pr-e2e-source-routing.mjs --native-ime-source)"
echo "native_ime_source_changed=$NATIVE_IME_SOURCE_CHANGED" >> "$GITHUB_OUTPUT"
WSL_CHANGED="$(git diff --name-only --no-renames --diff-filter=ACDMR "$DIFF_BASE" HEAD)"
WSL_SOURCE_CHANGED="$(printf '%s\n' "$WSL_CHANGED" | node config/scripts/pr-e2e-source-routing.mjs --wsl-source)"
echo "wsl_source_changed=$WSL_SOURCE_CHANGED" >> "$GITHUB_OUTPUT"
echo "Native IME source changed: $NATIVE_IME_SOURCE_CHANGED"
SHOULD_RUN="$(printf '%s\n' "$CHANGED" | node config/scripts/pr-e2e-source-routing.mjs --reusable-workflow "${E2E_SCOPE_ARGS[@]}")"
if [ "$SHOULD_RUN" = true ]; then
echo "should_run=true" >> "$GITHUB_OUTPUT"
echo "Changed E2E specs: $TEST_FILES_JSON"
else
echo "should_run=false" >> "$GITHUB_OUTPUT"
echo "No specs requiring the reusable E2E workflow"
fi
preflight:
name: static analysis and typecheck
needs: [code_paths]
if: needs.code_paths.outputs.static_analysis == 'true' || needs.code_paths.outputs.typecheck == 'true'
# Leave headroom for typechecking and overlapping checks.
runs-on: blacksmith-8vcpu-ubuntu-2404
outputs:
shards: ${{ steps.unit-plan.outputs.shards }}
managed_hook_node18: ${{ steps.managed_hook_node18.outcome }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
# Fetch 50 for merge HEAD^1, no merge-base graph needed. Full-tree checkout omits blob:none to avoid refetching ~30k blobs (23s versus ~11s).
fetch-depth: 50
persist-credentials: false
# The dependency action below restores Node 24 before installing or building packages.
- name: Prepare Node 18 smoke runtime
id: node18_runtime
if: needs.code_paths.outputs.managed_hook_node18 == 'true'
continue-on-error: true
uses: actions/setup-node@v6
with:
node-version: '18'
package-manager-cache: false
- name: Capture Node 18 executable
id: node18_executable
if: needs.code_paths.outputs.managed_hook_node18 == 'true' && steps.node18_runtime.outcome == 'success'
continue-on-error: true
run: node -p '"executable=" + process.execPath' >> "$GITHUB_OUTPUT"
# Choose one root/mobile install: root-only store is 321MB/4.6s versus mixed 537MB/8.6s, saving unused mobile downloads on most PRs.
- uses: ./.github/actions/install-node-dependencies
if: needs.code_paths.outputs.mobile_dependencies != 'true'
with:
native-runtime: node
node-version: '24'
- uses: ./.github/actions/install-node-dependencies
if: needs.code_paths.outputs.mobile_dependencies == 'true'
with:
native-runtime: node
node-version: '24'
cache-dependency-path: |
pnpm-lock.yaml
mobile/pnpm-lock.yaml
# Bun 1.4.3 introduces the compiler used by this job.
- name: Set up Bun for preflight checks
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: config/.bun-version
# Run gate scripts directly to avoid repeated package-manager startup.
- name: Check process-host imports
if: '!cancelled()'
id: process-host-imports
background: true
env:
PREFLIGHT_PHASE_SELECTED: ${{ needs.code_paths.outputs.static_analysis == 'true' }}
PREFLIGHT_PRIOR_SUCCESS: ${{ job.status == 'success' }}
run: |
if [ "$PREFLIGHT_PHASE_SELECTED" != true ] || [ "$PREFLIGHT_PRIOR_SUCCESS" != true ]; then exit 0; fi
bun config/scripts/check-import-boundaries.mjs
- name: Check main-process sync launches
if: '!cancelled()'
id: main-sync-spawn-ratchet
background: true
env:
PREFLIGHT_PHASE_SELECTED: ${{ needs.code_paths.outputs.static_analysis == 'true' }}
PREFLIGHT_PRIOR_SUCCESS: ${{ job.status == 'success' }}
run: |
if [ "$PREFLIGHT_PHASE_SELECTED" != true ] || [ "$PREFLIGHT_PRIOR_SUCCESS" != true ]; then exit 0; fi
bun config/scripts/check-main-sync-spawn-ratchet.mjs
# Keep each check in its own log while sharing this runner.
- name: Lint
if: '!cancelled()'
id: root-lint
background: true
env:
PREFLIGHT_PHASE_SELECTED: ${{ needs.code_paths.outputs.static_analysis == 'true' }}
PREFLIGHT_PRIOR_SUCCESS: ${{ job.status == 'success' }}
run: |
if [ "$PREFLIGHT_PHASE_SELECTED" != true ] || [ "$PREFLIGHT_PRIOR_SUCCESS" != true ]; then exit 0; fi
node node_modules/oxlint/bin/oxlint --format github
# JS plugins make Oxlint hand every AST to Node, so they run apart from the native root lint.
- name: Enforce JS-plugin lint rules
if: '!cancelled()'
id: js-plugin-lint
background: true
env:
PREFLIGHT_PHASE_SELECTED: ${{ needs.code_paths.outputs.static_analysis == 'true' }}
PREFLIGHT_PRIOR_SUCCESS: ${{ job.status == 'success' }}
run: |
if [ "$PREFLIGHT_PHASE_SELECTED" != true ] || [ "$PREFLIGHT_PRIOR_SUCCESS" != true ]; then exit 0; fi
node node_modules/oxlint/bin/oxlint --config config/oxlint-js-plugins.json --format github src config tests mobile native .github
- name: Enforce ast-grep rules
if: needs.code_paths.outputs.static_analysis == 'true'
run: |
node_modules/.bin/ast-grep test --config config/sgconfig.yml --skip-snapshot-tests
node_modules/.bin/ast-grep scan --config config/sgconfig.yml --min-severity warning --format github src config tests mobile
# Why the full tree: it takes ~2s, and the pre-commit hook is skippable.
- name: Check formatting
if: needs.code_paths.outputs.static_analysis == 'true'
run: node node_modules/oxfmt/bin/oxfmt --check .
- name: Reject low-evidence patterns
if: needs.code_paths.outputs.static_analysis == 'true'
run: |
node config/scripts/sync-anti-slop-plugin.mjs
bun --smol node_modules/oxlint/bin/oxlint --config config/oxlint-anti-slop.json src config tests mobile --deny-warnings
- wait: root-lint
- name: Enforce focused code-quality plugins
if: '!cancelled()'
id: native-code-quality
background: true
env:
PREFLIGHT_PHASE_SELECTED: ${{ needs.code_paths.outputs.static_analysis == 'true' }}
PREFLIGHT_PRIOR_SUCCESS: ${{ job.status == 'success' }}
run: |
if [ "$PREFLIGHT_PHASE_SELECTED" != true ] || [ "$PREFLIGHT_PRIOR_SUCCESS" != true ]; then exit 0; fi
node node_modules/oxlint/bin/oxlint --config config/oxlint-code-quality-native-plugins.json src config tests mobile --deny-warnings
- name: Enforce type-aware code-quality baseline
if: needs.code_paths.outputs.static_analysis == 'true'
run: node node_modules/oxlint/bin/oxlint --type-aware --config config/oxlint-code-quality-type-aware.json src config tests --deny-warnings
# Mobile installation changes import resolution for the native cycle check.
- wait: native-code-quality
# Install mobile before type-aware changed-code lint or unresolved mobile types become phantom error-type findings.
- uses: ./.github/actions/install-mobile-dependencies
if: needs.code_paths.outputs.static_analysis == 'true' && needs.code_paths.outputs.mobile_dependencies == 'true'
- name: Enforce changed-code quality
if: '!cancelled()'
id: changed-code-quality
background: true
env:
PREFLIGHT_PHASE_SELECTED: ${{ needs.code_paths.outputs.static_analysis == 'true' }}
PREFLIGHT_PRIOR_SUCCESS: ${{ job.status == 'success' }}
run: |
if [ "$PREFLIGHT_PHASE_SELECTED" != true ] || [ "$PREFLIGHT_PRIOR_SUCCESS" != true ]; then exit 0; fi
node config/scripts/check-changed-code-quality.mjs "${{ github.event.pull_request.base.sha }}"
- name: Verify locked audit tools
if: needs.code_paths.outputs.static_analysis == 'true'
env:
ORCA_BACKGROUND_LAUNCH: '1'
run: node config/scripts/verify-locked-audit-tools.mjs
- name: Enforce React Doctor on changed lines
if: needs.code_paths.outputs.static_analysis == 'true'
run: node config/scripts/check-react-doctor-changed.mjs "${{ github.event.pull_request.base.sha }}"
- wait: changed-code-quality
# The compiler overlaps the remaining checks after the type-aware passes release memory.
- name: Typecheck
if: '!cancelled()'
id: typecheck
background: true
env:
PREFLIGHT_PHASE_SELECTED: ${{ needs.code_paths.outputs.typecheck == 'true' }}
PREFLIGHT_PRIOR_SUCCESS: ${{ job.status == 'success' }}
run: |
if [ "$PREFLIGHT_PHASE_SELECTED" != true ] || [ "$PREFLIGHT_PRIOR_SUCCESS" != true ]; then exit 0; fi
pnpm run typecheck:ci --bun-concurrency=2 --bun-threads=2
- name: Check Zustand selector fan-out budget
if: needs.code_paths.outputs.static_analysis == 'true'
run: node config/scripts/zustand-selector-fanout-benchmark.mjs --check
- name: Check reliability gate manifest
if: needs.code_paths.outputs.static_analysis == 'true'
run: node config/scripts/check-reliability-gates.mjs
- name: Enforce dead design-system classes
if: needs.code_paths.outputs.static_analysis == 'true'
run: node node_modules/oxlint/bin/oxlint --config config/oxlint-dead-classes.json src/renderer
- name: Check VM runtime rollback compatibility
if: needs.code_paths.outputs.static_analysis == 'true'
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
DIFF_BASE="$(node config/scripts/git-pull-request-diff-base.mjs "$BASE_SHA")"
if git diff --quiet "$DIFF_BASE" HEAD -- \
src/shared/ephemeral-vm-runtime-store.ts \
src/shared/ephemeral-vm-runtime-feature-store.ts \
src/shared/ephemeral-vm-runtime-rollback-projection.ts \
src/shared/ephemeral-vm-runtimes.ts \
src/shared/ephemeral-vm-recipes.ts \
src/shared/orca-yaml-hook-types.ts \
config/scripts/run-ephemeral-vm-runtime-store-rollback-repro.mjs \
config/scripts/ephemeral-vm-runtime-store-cross-version.test.ts \
src/main/ephemeral-vm/runtime-service.ts \
src/main/ephemeral-vm/runtime-provisioning-persistence.ts \
src/main/ephemeral-vm/failed-start-cleanup.ts; then
echo "VM runtime persistence is unchanged."
exit 0
fi
node config/scripts/run-ephemeral-vm-runtime-store-rollback-repro.mjs \
config/scripts/ephemeral-vm-runtime-store-cross-version.test.ts
# Runs every config/scripts/check-*-ratchet(s).mjs, so a new ratchet needs no step here.
- name: Enforce ratchets
if: needs.code_paths.outputs.static_analysis == 'true'
run: node config/scripts/run-ratchets.mjs
- name: Check Node runtime pin
if: needs.code_paths.outputs.static_analysis == 'true'
run: node config/scripts/check-node-runtime-pin.mjs
- name: Check every test file has a runner
if: needs.code_paths.outputs.static_analysis == 'true'
run: node config/scripts/check-test-file-runners.mjs
- name: Check root and mobile lock the same shared dependencies
if: needs.code_paths.outputs.static_analysis == 'true'
run: node config/scripts/check-mobile-shared-dependency-versions.mjs
# Extract sorted evidence to isolated temp paths with one normalized platform-independent AST pass; use preflight’s pinned Bun.
- name: Verify localization extraction
if: '!cancelled()'
id: localization-extraction
background: true
env:
PREFLIGHT_PHASE_SELECTED: ${{ needs.code_paths.outputs.static_analysis == 'true' }}
PREFLIGHT_PRIOR_SUCCESS: ${{ job.status == 'success' }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
if [ "$PREFLIGHT_PHASE_SELECTED" != true ] || [ "$PREFLIGHT_PRIOR_SUCCESS" != true ]; then exit 0; fi
# Detection failures run the full check; renames retain the removed input path.
DIFF_BASE="$(node config/scripts/git-pull-request-diff-base.mjs "$BASE_SHA")"
if git diff --name-only --no-renames -z "$DIFF_BASE" HEAD > "$RUNNER_TEMP/localization-changes" &&
scope="$(node config/scripts/localization-extraction-change-scope.mjs "$RUNNER_TEMP/localization-changes")" && [ "$scope" = false ]; then
echo "Localization extraction inputs are unchanged."
else
bun config/scripts/verify-localization-extraction.mjs
fi
- name: Decide whether the orcad terminal smoke runs
id: orcad-smoke-scope
if: needs.code_paths.outputs.static_analysis == 'true'
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
# Detection failures run the smoke; renames retain the removed input path.
DIFF_BASE="$(node config/scripts/git-pull-request-diff-base.mjs "$BASE_SHA")"
if git diff --name-only --no-renames -z "$DIFF_BASE" HEAD > "$RUNNER_TEMP/orcad-smoke-changes" &&
scope="$(node config/scripts/orcad-terminal-smoke-change-scope.mjs "$RUNNER_TEMP/orcad-smoke-changes")" && [ "$scope" = false ]; then
echo "Orcad terminal smoke inputs are unchanged."
echo 'run=false' >> "$GITHUB_OUTPUT"
else
echo 'run=true' >> "$GITHUB_OUTPUT"
fi
# orcad refuses to build without the page phones load.
- uses: ./.github/actions/build-mobile-web-bundle
if: steps.orcad-smoke-scope.outputs.run == 'true'
# Pair import-graph electron exclusion with built-Node boot/pair/worktree/PTY smoke; graph purity alone does not prove runtime operation.
- name: Boot orcad and round-trip a terminal
if: steps.orcad-smoke-scope.outputs.run == 'true'
env:
ORCA_BACKGROUND_LAUNCH: '1'
run: pnpm run smoke:orcad-terminal
- name: Verify the generated RPC params catalog
if: needs.code_paths.outputs.static_analysis == 'true'
run: node config/scripts/generate-rpc-params-catalog.mjs --check
- name: Verify the generated ACP protocol schema
if: needs.code_paths.outputs.static_analysis == 'true'
run: node config/scripts/acp/generate-protocol.mjs --check
- name: Verify bundled skill guides
if: needs.code_paths.outputs.static_analysis == 'true'
run: node config/scripts/generate-bundled-skill-guides.mjs --check
- name: Verify skill freshness manifest
if: needs.code_paths.outputs.static_analysis == 'true'
run: node config/scripts/generate-skill-bundle-manifest.mjs
- name: Verify localization catalogs
if: '!cancelled()'
id: localization-catalogs
background: true
env:
PREFLIGHT_PHASE_SELECTED: ${{ needs.code_paths.outputs.static_analysis == 'true' }}
PREFLIGHT_PRIOR_SUCCESS: ${{ job.status == 'success' }}
run: |
if [ "$PREFLIGHT_PHASE_SELECTED" != true ] || [ "$PREFLIGHT_PRIOR_SUCCESS" != true ]; then exit 0; fi
bun config/scripts/verify-localization-catalogs.mjs
- name: Verify localization coverage
if: needs.code_paths.outputs.static_analysis == 'true'
run: bun config/scripts/audit-localization-coverage.mjs --check
- wait: [localization-catalogs, localization-extraction]
# Keep project declarations in .ts: skipLibCheck silently widened unresolved .d.ts names to any, hiding #1186 IPC breakage. See .github/CONTRIBUTING.md#type-declarations-prefer-ts-over-dts.
- name: Guard against project-owned .d.ts in preload/shared
if: needs.code_paths.outputs.static_analysis == 'true'
run: |
matches=$(find src/preload src/shared -name '*.d.ts' 2>/dev/null || true)
if [ -n "$matches" ]; then
echo "::error::Project-owned .d.ts files are not allowed under src/preload or src/shared."
echo "Move type declarations into a .ts file so skipLibCheck does not hide errors."
echo "See .github/CONTRIBUTING.md#type-declarations-prefer-ts-over-dts."
echo "Found:"
echo "$matches"
exit 1
fi
- name: Check feature wall asset budget
if: needs.code_paths.outputs.static_analysis == 'true'
run: node config/scripts/check-feature-wall-assets.mjs
- name: Verify macOS entitlements
if: needs.code_paths.outputs.static_analysis == 'true'
run: node config/scripts/verify-macos-entitlements.mjs
# Planning shares setup and stays off the compiler's critical path.
- name: Plan unit selection
if: '!cancelled()'
id: unit-plan
background: true
env:
PREFLIGHT_PHASE_SELECTED: ${{ needs.code_paths.outputs.typecheck == 'true' }}
PREFLIGHT_PRIOR_SUCCESS: ${{ job.status == 'success' }}
ORCA_UNIT_SELECTION_MODE: ${{ vars.ORCA_UNIT_SELECTION_MODE || 'shadow' }}
ORCA_PULL_REQUEST_DRAFT: ${{ github.event.pull_request.draft }}
# Five shards use fewer runner minutes; the repository variable can still request ten.
ORCA_UNIT_FULL_SHARD_COUNT: ${{ vars.ORCA_UNIT_FULL_SHARD_COUNT || '5' }}
run: |
if [ "$PREFLIGHT_PHASE_SELECTED" != true ] || [ "$PREFLIGHT_PRIOR_SUCCESS" != true ]; then exit 0; fi
node config/scripts/ci-unit-plan.mjs
- wait: typecheck
- wait: process-host-imports
- wait: main-sync-spawn-ratchet
- wait: js-plugin-lint
- wait: unit-plan
- uses: actions/upload-artifact@v7
if: needs.code_paths.outputs.typecheck == 'true'
with:
name: unit-selection-attempt-${{ github.run_attempt }}
path: ci-shards/unit-selection.json
retention-days: 14
- name: Test Mobile CI tools and runtime routing
if: needs.code_paths.outputs.mobile_ci_tools == 'true'
env:
ORCA_BACKGROUND_LAUNCH: '1'
ORCA_BALANCE_UNIT_SHARDS: '1'
run: >-
node config/scripts/run-vitest.mjs run --config config/vitest.config.ts
--maxWorkers=4 --isolate --retry=0 --bail=0
config/scripts/mobile-release-check-scope.test.mjs
config/scripts/mobile-test-change-scope.test.mjs
config/scripts/pr-ready-check-reuse.test.mjs
config/scripts/ci-dependency-download-cache.test.mjs
config/scripts/win32-test-lane-registration.test.mjs
config/scripts/vitest-node-runtime-boundary.test.ts
config/scripts/vitest-sqlite-runtime-boundary.test.ts
# Report failure through the separate required check without blocking unrelated consumers.
- name: Smoke process-host package on Node 18
id: managed_hook_node18
if: success() && needs.code_paths.outputs.managed_hook_node18 == 'true'
continue-on-error: true
env:
NODE18_EXECUTABLE: ${{ steps.node18_executable.outputs.executable }}
ORCA_BACKGROUND_LAUNCH: '1'
run: |
test -n "$NODE18_EXECUTABLE"
"$NODE18_EXECUTABLE" config/scripts/smoke-process-host-node18.mjs
git_compatibility:
name: Git compatibility
needs: [code_paths, preflight]
if: >-
!cancelled() &&
needs.code_paths.result == 'success' &&
needs.code_paths.outputs.git_compatibility == 'true' &&
needs.preflight.result == 'success'
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
- uses: ./.github/actions/prepare-git-compatibility
- name: Verify Git binary compatibility matrix
env:
ORCA_BACKGROUND_LAUNCH: '1'
run: |
specs=(
"alpine/git:edge-2.38.1|2.38.1"
"alpine/git:v2.49.1|2.49.1"
)
# Pull before tests so a sibling’s timed lane cannot be stalled by the first image download.
for spec in "${specs[@]}"; do
docker pull --quiet "${spec%%|*}"
done
pids=()
(
ORCA_GIT_COMPAT_BINARY="$HOME/.cache/orca-git-compat/git-2.25.5/git" \
GIT_EXEC_PATH="$HOME/.cache/orca-git-compat/git-2.25.5" \
ORCA_GIT_COMPAT_VERSION="2.25.5" \
pnpm exec vitest run --config config/vitest.config.ts \
src/shared/git-binary-compatibility.test.ts \
src/main/git/worktree-safety-real-git.test.ts \
src/main/git/worktree-rebase-update-refs-real-git.test.ts
) &
pids+=("$!")
for spec in "${specs[@]}"; do
(
image="${spec%%|*}"
version="${spec#*|}"
ORCA_GIT_COMPAT_IMAGE="$image" ORCA_GIT_COMPAT_VERSION="$version" \
pnpm exec vitest run --config config/vitest.config.ts \
src/shared/git-binary-compatibility.test.ts \
src/main/git/worktree-safety-real-git.test.ts \
src/main/git/worktree-rebase-update-refs-real-git.test.ts
) &
pids+=("$!")
done
status=0
for pid in "${pids[@]}"; do
wait "$pid" || status=1
done
exit "$status"
# Run pinned real Codex: thread/read must repair an unindexed rollout’s threads row; stub-only units cannot detect upstream removal of this behavior.
codex_index_heal_contract:
name: Codex index-heal contract
needs: [code_paths, preflight]
if: >-
!cancelled() &&
needs.code_paths.result == 'success' &&
needs.code_paths.outputs.codex_index_heal_contract == 'true' &&
needs.preflight.result == 'success'
# Why ARM: @openai/codex ships @openai/codex-linux-arm64.
runs-on: blacksmith-4vcpu-ubuntu-2404-arm
env:
CODEX_CLI_VERSION: '0.150.1'
# Why a second pin: --no-daemon only exists from 0.156, and Orca's codex wrapper relies on it.
CODEX_NO_DAEMON_CLI_VERSION: '0.158.0'
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
- name: Install pinned Codex CLI
run: |
set -euo pipefail
npm install --no-audit --no-fund --prefix "$RUNNER_TEMP/codex-cli" \
"@openai/codex@$CODEX_CLI_VERSION"
- name: Verify Codex index-heal contract
env:
# Require installed binary or skip would report green without exercising the contract.
ORCA_CODEX_CONTRACT_REQUIRED: '1'
ORCA_CODEX_CONTRACT_VERSION: ${{ env.CODEX_CLI_VERSION }}
run: |
set -euo pipefail
ORCA_CODEX_CONTRACT_BINARY="$RUNNER_TEMP/codex-cli/node_modules/.bin/codex" \
pnpm exec vitest run --config config/vitest.config.ts \
src/main/codex/codex-index-heal-binary-contract.test.ts
- name: Install pinned no-daemon Codex CLI
run: |
set -euo pipefail
npm install --no-audit --no-fund --prefix "$RUNNER_TEMP/codex-cli-no-daemon" \
"@openai/codex@$CODEX_NO_DAEMON_CLI_VERSION"
- name: Verify Codex --no-daemon contract
env:
ORCA_CODEX_NO_DAEMON_CONTRACT_REQUIRED: '1'
ORCA_CODEX_NO_DAEMON_CONTRACT_VERSION: ${{ env.CODEX_NO_DAEMON_CLI_VERSION }}
run: |
set -euo pipefail
ORCA_CODEX_NO_DAEMON_CONTRACT_BINARY="$RUNNER_TEMP/codex-cli-no-daemon/node_modules/.bin/codex" \
pnpm exec vitest run --config config/vitest.config.ts \
src/main/pty/codex-no-daemon-binary-contract.test.ts
# Test pins plus latest because Codex’s managed-home approval hash can change before users encounter review prompts.
- name: Verify Codex hook file-entry contract (pinned)
env:
ORCA_CODEX_HOOK_CONTRACT_REQUIRED: '1'
run: |
set -euo pipefail
for install in codex-cli:"$CODEX_CLI_VERSION" codex-cli-no-daemon:"$CODEX_NO_DAEMON_CLI_VERSION"; do
ORCA_CODEX_HOOK_CONTRACT_BINARY="$RUNNER_TEMP/${install%%:*}/node_modules/.bin/codex" \
ORCA_CODEX_HOOK_CONTRACT_VERSION="${install#*:}" \
pnpm exec vitest run --config config/vitest.config.ts \
src/main/codex/codex-hook-file-entry-binary-contract.test.ts
done
- name: Install latest Codex CLI
run: |
set -euo pipefail
npm install --no-audit --no-fund --prefix "$RUNNER_TEMP/codex-cli-latest" "@openai/codex@latest"
- name: Verify Codex hook file-entry contract (latest)
env:
ORCA_CODEX_HOOK_CONTRACT_REQUIRED: '1'
run: |
set -euo pipefail
ORCA_CODEX_HOOK_CONTRACT_BINARY="$RUNNER_TEMP/codex-cli-latest/node_modules/.bin/codex" \
pnpm exec vitest run --config config/vitest.config.ts \
src/main/codex/codex-hook-file-entry-binary-contract.test.ts
# Why the no-daemon install: it is the newest pinned Codex, and its project
# lookup decides whether a worktree launch stops at the trust prompt.
- name: Verify Codex project-trust contract
env:
ORCA_CODEX_TRUST_CONTRACT_REQUIRED: '1'
ORCA_CODEX_TRUST_CONTRACT_VERSION: ${{ env.CODEX_NO_DAEMON_CLI_VERSION }}
run: |
set -euo pipefail
ORCA_CODEX_TRUST_CONTRACT_BINARY="$RUNNER_TEMP/codex-cli-no-daemon/node_modules/.bin/codex" \
pnpm exec vitest run --config config/vitest.config.ts \
src/main/agent-trust-presets.test.ts
xterm_patch_sync:
name: xterm patch sync
needs: [code_paths, preflight]
if: >-
!cancelled() &&
needs.code_paths.result == 'success' &&
needs.code_paths.outputs.xterm_patch_sync == 'true' &&
needs.preflight.result == 'success'
# Use ARM for four-package pristine/patched rebuild byte comparison: output generated on Linux x64 reproduces on darwin-arm64.
runs-on: blacksmith-4vcpu-ubuntu-2404-arm
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
# The generator uses Node built-ins and installs its own upstream toolchain.
- uses: actions/setup-node@v6
with:
node-version-file: package.json
check-latest: true
package-manager-cache: false
# Rebuild both pristine and patched bundles; reuse only the pinned toolchain.
- uses: ./.github/actions/prepare-xterm-dependencies
- name: Verify xterm patches match the pinned upstream build
env:
WORK_DIR: ${{ runner.temp }}/xterm-patch-build
run: node config/scripts/regenerate-xterm-patches.mjs --check --work-dir="$WORK_DIR"
shell_contracts:
name: shell contracts
needs: [code_paths, preflight]
if: >-
!cancelled() &&
needs.code_paths.result == 'success' &&
needs.code_paths.outputs.shell_contracts == 'true' &&
needs.preflight.result == 'success'
# Exercise Linux ARM with the standalone aarch64 release and Ubuntu's zsh.
runs-on: blacksmith-4vcpu-ubuntu-2404-arm
# Bound stalled prerequisite downloads instead of waiting for GitHub's 6-hour default.
timeout-minutes: 15
env:
# Require live fish instead of developer-friendly skips: this is the only full #9993 guard.
ORCA_REQUIRE_FISH: '1'
# Why: the runner image ships pwsh, and the codex wrapper's PowerShell lane must not skip.
ORCA_REQUIRE_PWSH: '1'
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
# fish 4+ provides DECSET 2031 arming required by live shell contracts (#9993).
- name: Install zsh and fish
id: shells
background: true
run: |
# Per-connection limits complement the whole-command apt deadlines below.
sudo tee /etc/apt/apt.conf.d/99-orca-shell-contracts >/dev/null <<'APTCONF'
Acquire::http::Timeout "15";
Acquire::https::Timeout "15";
Acquire::Retries "1";
APTCONF
# Unrelated runner repositories may fail refresh; installing zsh must still succeed.
timeout 120 sudo apt-get update || true
timeout 300 sudo apt-get install -y zsh
# A fixed 4.x release meets the contract without tracking package-repository updates.
case "$(uname -m)" in
aarch64) arch=aarch64 sha=57349f0111728c64b703a84e611aa97ff4d1fa8466385d64e67bd5bc3ffa49fc ;;
*) arch=x86_64 sha=8f643d10ad1abb0ef7072764c01ceb5cf61b2d373fda400cf60f0bad69b7e095 ;;
esac
curl -fsSL --retry 2 --connect-timeout 15 --max-time 120 -o "$RUNNER_TEMP/fish.tar.xz" \
"https://github.com/fish-shell/fish-shell/releases/download/4.9.3/fish-4.9.3-linux-${arch}.tar.xz"
echo "${sha} $RUNNER_TEMP/fish.tar.xz" | sha256sum -c -
tar -xJf "$RUNNER_TEMP/fish.tar.xz" -C "$RUNNER_TEMP" fish
sudo install -m 0755 "$RUNNER_TEMP/fish" /usr/local/bin/fish
for package in fish fish-common; do
if ! dpkg-query -W -f='${binary:Package}: ${Status}\n' "$package" 2>/dev/null; then
echo "$package: absent"
fi
done
# Exclude runner preload while keeping the tests' own XDG vendor snippets reachable.
quarantine="$(mktemp -d "$RUNNER_TEMP/fish-runner-resources.XXXXXX")"
for path in /usr/share/fish /etc/fish; do
if [ -e "$path" ] || [ -L "$path" ]; then
sudo mv -- "$path" "$quarantine/${path//\//_}"
echo "$path: quarantined in $quarantine"
fi
if [ -e "$path" ]; then echo "$path: present"; else echo "$path: absent"; fi
done
printf 'fish selected: %s\n' "$(command -v fish)"
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: node
- wait: shells
# Fail fish prerequisites separately before dependency install; ORCA_REQUIRE_FISH checks again in tests.
- name: Require fish 4+
run: |
version="$(fish --version 2>/dev/null || true)"
major="${version##*version }"
major="${major%%.*}"
case "$major" in '' | *[!0-9]*) major=0 ;; esac
echo "${version:-<fish not installed>}"
if [ "$major" -lt 4 ]; then
echo "::error::shell contracts needs fish 4+ (DECSET 2031 arming, #9993) but got '${version:-none}'. Fix the direct GitHub release install rather than letting the fish lane skip." >&2
exit 1
fi
- name: Test real shell contracts
run: |
pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1 \
src/main/daemon/repro-13767-shell-ready-marker-lost-to-exec.test.ts \
src/main/daemon/shell-ready.test.ts \
src/main/daemon/node-pty-fd-leak.test.ts \
src/main/providers/local-pty-shell-ready-zsh-launch-environment.test.ts \
src/main/providers/__tests__/shell-ready-framework-example.test.ts \
src/main/pty/codex-shell-launch-preflight.test.ts \
src/main/pty/codex-shell-no-daemon.test.ts \
src/main/pty/omp-shell-wrapper-alias-safety.test.ts \
src/main/pty/omp-shell-wrapper.node-pty.test.ts \
src/main/fish-xdg-data-dirs-handoff.test.ts \
src/main/shell-startup-feature-channel.test.ts \
src/main/zsh-deferred-startup-line-init.live-shell.test.ts \
src/main/terminal-history-fish-session.node-pty.test.ts \
src/main/zsh-scoped-histfile.live-shell.test.ts \
src/main/zsh-startup-hook-user-config-equivalence.live-shell.test.ts \
src/main/runtime/structured-session-cli-login-shell.live-shell.test.ts \
src/renderer/src/components/terminal-pane/fish-color-scheme-child-stdin.node-pty.test.ts \
src/shared/fish-query-reply-child-stdin.node-pty.test.ts \
src/shared/pty-reply-echo-shapes.node-pty.test.ts \
src/shared/startup-shell-portability.live-shell.test.ts \
src/shared/posix-command-path-lookup.test.ts
# Preflight saves the Node cache and publishes the plan before tests fan out.
test:
needs: [code_paths, preflight]
# Cancellation and failed prerequisites stop the expensive matrix.
if: >-
!cancelled() &&
needs.code_paths.result == 'success' &&
needs.code_paths.outputs.test == 'true' &&
needs.preflight.result == 'success'
uses: ./.github/workflows/unit-tests.yml
with:
node_versions: '["24"]'
runner: blacksmith-8vcpu-ubuntu-2404
phase_diagnostics: true
shards: ${{ needs.preflight.outputs.shards }}
run_shards: ${{ needs.code_paths.outputs.mobile_ci_tools != 'true' }}
run_relay_integration: ${{ needs.code_paths.outputs.relay_integration != 'false' || needs.code_paths.outputs.orcad_browser == 'true' }}
browser_contract: ${{ needs.code_paths.outputs.orcad_browser == 'true' }}
# Keep advisory evidence sibling to tests and outside verify.needs; reusable-workflow dependency otherwise added ~36s after last shard.
unit_selection_evidence:
needs: [test]
if: ${{ !cancelled() && (needs.test.result == 'success' || needs.test.result == 'failure') }}
uses: ./.github/workflows/unit-selection-evidence.yml
# Isolate two browser/mobile files to avoid every unit shard paying for their installs; test the same shipped bundle plus render/census.
mobile_web_app:
name: mobile web app bundle
needs: [code_paths]
if: needs.code_paths.outputs.mobile_web_app == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
# Why no native-runtime: the builder is esbuild and the render check is a browser. Nothing
# in this job loads node-pty.
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: none
cache-dependency-path: |
pnpm-lock.yaml
mobile/pnpm-lock.yaml
# Test drawer with WebKit as iOS WebView uses it; runner Chrome cannot substitute.
- name: Install WebKit for the drawer check
id: webkit
background: true
run: |
bash .github/scripts/prepare-ubuntu-apt.sh
pnpm exec playwright-core install --with-deps webkit
# The entry lives in mobile/ so one React resolves; without this every RN import is nothing.
- uses: ./.github/actions/install-mobile-dependencies
- name: Prepare mobile route snapshot
id: mobile-routes
background: true
run: node config/scripts/run-mobile-web-app-checks.mjs --prepare-route-snapshot "$RUNNER_TEMP/mobile-routes.json"
# Require runner Google Chrome: Ubuntu’s AppArmor userns profile covers its .deb, unlike downloaded Chromium; missing render checks must fail.
- name: Resolve Chrome for the render check
run: |
set -euo pipefail
chrome="$(command -v google-chrome || command -v google-chrome-stable || true)"
if [ -z "$chrome" ]; then
echo "::error::No Google Chrome on the runner; the render check would silently skip."
exit 1
fi
"$chrome" --version
echo "ORCA_MOBILE_WEB_RENDER_BROWSER=$chrome" >> "$GITHUB_ENV"
- name: Build and verify the app bundle
run: pnpm run build:mobile-web
# Browser checks must observe both a finished install and the built bundle.
- wait: [webkit, mobile-routes]
# Require mobile install and select census family by quoted filename prefix, matching routing; stale file lists/env-gated skips previously hid missing coverage.
- name: Builder, override census and render checks
env:
ORCA_MOBILE_WEB_APP_DEPS_REQUIRED: '1'
ORCA_MOBILE_WEB_PREPARED_ROUTE_SNAPSHOT: ${{ runner.temp }}/mobile-routes.json
run: |
node config/scripts/run-mobile-web-app-checks.mjs
cross-version-wire:
name: cross-version wire compatibility
needs: [code_paths, preflight]
if: >-
!cancelled() &&
needs.code_paths.result == 'success' &&
needs.code_paths.outputs.cross-version-wire == 'true' &&
needs.preflight.result == 'success'
# Why ARM: source-only: tagged checkout plus in-process vitest, no docker or browser.
runs-on: blacksmith-4vcpu-ubuntu-2404-arm
steps:
# Fetch tags/full history for newest-release skew; shallow unit shards cannot supply it.
- name: Checkout
uses: actions/checkout@v6
with:
# Use blob:none for merge-base history because blobs are ~89% of pack; fetch rare historical contents on demand.
fetch-depth: 0
filter: blob:none
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: node
# R1: an upgrade from the newest release must adopt its live terminal daemon.
- name: Daemon protocol crossing against newest release
run: pnpm run check:daemon-protocol-crossing
# R3: a runtime launcher swap must not also bump the daemon protocol (D7.1).
# The label is read from the event payload, so push again after applying it.
- name: Runtime launcher change does not bump daemon protocol
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
ORCA_ALLOW_RUNTIME_LAUNCHER_PROTOCOL_BUMP: ${{ contains(github.event.pull_request.labels.*.name, 'allow-runtime-launcher-protocol-bump') }}
run: |
DIFF_BASE="$(node config/scripts/git-pull-request-diff-base.mjs "$BASE_SHA")"
pnpm run check:runtime-launcher-protocol-ratchet -- --base "$DIFF_BASE"
# Run the entire directory; zero matches fail so new contracts cannot be omitted silently.
- name: Old/new client and server compatibility journeys
run: pnpm exec vitest run --config config/vitest.config.ts tests/e2e/cross-version-wire/
managed_hook_node18:
name: managed hooks on Node 18
needs: [code_paths, preflight]
if: >-
!cancelled() &&
needs.code_paths.result == 'success' &&
needs.code_paths.outputs.managed_hook_node18 == 'true' &&
needs.preflight.result == 'success'
# Retain the required check while sharing preflight's checkout and built packages.
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 5
steps:
- name: Require process-host package smoke on Node 18
env:
MANAGED_HOOK_NODE18: ${{ needs.preflight.outputs.managed_hook_node18 }}
run: |
if [ "$MANAGED_HOOK_NODE18" != success ]; then
echo "::error::Node 18 process-host smoke: expected success, got $MANAGED_HOOK_NODE18"
exit 1
fi
package:
name: package
needs: [code_paths, preflight]
if: needs.code_paths.outputs.package == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
# Let the serial Docker gates reach their own deadlines and report cleanup failures.
timeout-minutes: 90
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Cache electron-builder downloads
uses: actions/cache/restore@v5
with:
path: ~/.cache/electron-builder
key: electron-builder-linux-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
electron-builder-linux-
# Restore in the foreground: background steps may only be single run steps (ci-background-steps.test.mjs).
- uses: ./.github/actions/prepare-linux-package-fixture
id: daemon-fixture-cache-restore
with:
fixture: daemon-shutdown-descendants
load: 'false'
- name: Load daemon shutdown fixture image
id: daemon-fixture-cache
background: true
env:
FIXTURE: daemon-shutdown-descendants
CACHE_HIT: ${{ steps.daemon-fixture-cache-restore.outputs.cache-hit }}
run: bash .github/actions/prepare-linux-package-fixture/load-fixture.sh
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: electron
cache-dependency-path: |
pnpm-lock.yaml
mobile/pnpm-lock.yaml
# Why here: electron-builder's beforePack requires out/mobile-web, and the bundle
# build resolves React Native and Expo from mobile/node_modules.
- uses: ./.github/actions/install-mobile-dependencies
# The checkout must pass without depending on a historical baseline that leaks.
- wait: daemon-fixture-cache
- name: Verify Linux daemon shutdown descendant cleanup
env:
ORCA_BACKGROUND_LAUNCH: '1'
ORCA_DAEMON_SHUTDOWN_FIXTURE_CACHE_IMAGE: ${{ steps.daemon-fixture-cache.outputs.image }}
run: node config/scripts/run-daemon-shutdown-descendants-docker.mjs
# Serialize dual-Electron lifecycle files: four concurrent probes on four vCPUs starve each other past their own deadlines.
- name: Test Linux Electron lifecycle boundary
env:
ORCA_BACKGROUND_LAUNCH: '1'
run: >-
xvfb-run --auto-servernum pnpm exec vitest run --config config/vitest.config.ts
--no-file-parallelism
src/main/browser/browser-client-page-renderer-lifecycle.electron.test.ts
src/main/browser/browser-route-tcp-egress.electron.test.ts
src/main/browser/browser-route-webrtc-egress.electron.test.ts
src/main/browser/browser-route-h3-egress.electron.test.ts
src/main/browser/browser-route-dns-prefetch.electron.test.ts
src/main/persistence/profile-state/profile-state-writer-stall.electron.test.ts
src/main/browser/popup-origin-chrome.electron.test.ts
src/main/browser/browser-link-materialization.electron.test.ts
src/main/browser/browser-popup-compatibility.electron.test.ts
src/main/browser/browser-popup-adapter-compatibility.electron.test.ts
src/main/browser/browser-popup-native-compatibility.electron.test.ts
src/main/browser/browser-popup-background-compatibility.electron.test.ts
- name: Restore native components
id: linux-native-restore
uses: ./.github/actions/prepare-wayland-clipboard
with:
build: 'false'
# The isolated glibc-floor build writes only the Wayland helper, so JavaScript can overlap.
- name: Build native components
id: linux-native
background: true
env:
CACHE_HIT: ${{ steps.linux-native-restore.outputs.cache-hit }}
run: bash .github/actions/prepare-wayland-clipboard/build-or-validate.sh
# Load independent fixtures during builds; keep native lifecycle probes serial.
- uses: ./.github/actions/prepare-linux-package-fixture
id: shutdown-fixture-cache-restore
with:
fixture: headless-serve-shutdown
load: 'false'
- name: Load headless serve shutdown fixture image
id: shutdown-fixture-cache
background: true
env:
FIXTURE: headless-serve-shutdown
CACHE_HIT: ${{ steps.shutdown-fixture-cache-restore.outputs.cache-hit }}
run: bash .github/actions/prepare-linux-package-fixture/load-fixture.sh
- uses: ./.github/actions/prepare-linux-package-fixture
id: cli-fixture-cache-restore
with:
fixture: cli-launch-contract
load: 'false'
- name: Load CLI launch contract fixture image
id: cli-fixture-cache
background: true
env:
FIXTURE: cli-launch-contract
CACHE_HIT: ${{ steps.cli-fixture-cache-restore.outputs.cache-hit }}
run: bash .github/actions/prepare-linux-package-fixture/load-fixture.sh
- name: Install Linux package tooling
id: linux-package-tools
background: true
run: |
# Use the shell lane's bounded refresh; required tooling installation stays fatal.
sudo tee /etc/apt/apt.conf.d/99-orca-package-tools >/dev/null <<'APTCONF'
Acquire::http::Timeout "15";
Acquire::https::Timeout "15";
Acquire::Retries "1";
APTCONF
timeout 120 sudo apt-get update || true
timeout 300 sudo apt-get install -y cpio rpm
- name: Build package inputs
env:
ORCA_CI_MAIN_SOURCEMAPS: '0'
run: |
status=0
pnpm run build:cli || status=1
scripts=(build:wsl-guest build:electron-vite:parallel)
pids=()
for script in "${scripts[@]}"; do
pnpm run "$script" &
pids+=("$!")
done
for pid in "${pids[@]}"; do
wait "$pid" || status=1
done
exit "$status"
- name: Project web client from renderer build
id: web-client
background: true
run: pnpm run build:web-from-renderer
# Build the real ~8MB mobile page because stepwise package assembly bypasses build:release and beforePack refuses its absence.
- name: Build mobile web bundle
run: pnpm run build:mobile-web
- wait: [linux-native, linux-package-tools, web-client]
- name: Package unpacked app
env:
ORCA_BACKGROUND_LAUNCH: '1'
ORCA_REUSE_PREPARED_NATIVE_RUNTIME: '1'
# Prepare once with every hook, then isolate the format-specific mutations.
run: |
pnpm exec electron-builder --config config/electron-builder.config.cjs --linux dir --x64 --publish never
node config/scripts/package-linux-formats.mjs
- name: Verify root-package marker payloads
run: |
set -euo pipefail
version="$(node -p "require('./package.json').version")"
deb="dist/orca-ide_${version}_amd64.deb"
rpm="dist/orca-ide-${version}.x86_64.rpm"
test -s "$deb"
test -s "$rpm"
deb_marker="$(dpkg-deb --fsys-tarfile "$deb" | tar -xOf - ./opt/Orca/resources/package-type)"
rpm_marker="$(rpm2cpio "$rpm" | cpio --quiet --extract --to-stdout ./opt/Orca/resources/package-type)"
[[ "$deb_marker" == deb ]] || { echo "Expected deb marker, got: $deb_marker"; exit 1; }
[[ "$rpm_marker" == rpm ]] || { echo "Expected rpm marker, got: $rpm_marker"; exit 1; }
# Only the AppImage carries the pinned Node (config/scripts/appimage-node-runtime.cjs).
if dpkg-deb --fsys-tarfile "$deb" | tar -t | grep '/resources/node-runtime' >/dev/null; then
echo "The deb must not carry the AppImage's Node"; exit 1
fi
if rpm2cpio "$rpm" | cpio --quiet -t | grep '/resources/node-runtime' >/dev/null; then
echo "The rpm must not carry the AppImage's Node"; exit 1
fi
appimage_root="$(mktemp -d)"
(cd "$appimage_root" && "$GITHUB_WORKSPACE/dist/orca-linux.AppImage" --appimage-extract 'resources/node-runtime/bin/node' >/dev/null)
test -x "$appimage_root/squashfs-root/resources/node-runtime/bin/node"
"$appimage_root/squashfs-root/resources/node-runtime/bin/node" --version
rm -rf "$appimage_root"
ls -l dist/orca-linux.AppImage "$deb" "$rpm"
# Kept briefly so a reviewer can run this exact AppImage on a real Linux desktop.
- uses: actions/upload-artifact@v7
with:
name: orca-linux-appimage-${{ github.event.pull_request.head.sha }}
path: dist/orca-linux.AppImage
retention-days: 3
compression-level: 0
- wait: shutdown-fixture-cache
- name: Verify headless serve signal shutdown
env:
ORCA_SHUTDOWN_FIXTURE_CACHE_IMAGE: ${{ steps.shutdown-fixture-cache.outputs.image }}
run: >-
node config/scripts/run-headless-serve-shutdown-docker.mjs
--appimage dist/orca-linux.AppImage --all-entrypoints
# A default container reproduces the hostile AppImage launch environment.
- wait: cli-fixture-cache
- name: Verify Linux CLI launch contract
env:
ORCA_CLI_FIXTURE_CACHE_IMAGE: ${{ steps.cli-fixture-cache.outputs.image }}
run: node config/scripts/run-linux-cli-launch-contract-docker.mjs --appimage dist/orca-linux.AppImage
- name: Smoke packaged CLI
run: node config/scripts/smoke-packaged-cli.mjs --app-dir=dist/linux-unpacked
- name: Smoke packaged hang watchdog worker
run: xvfb-run --auto-servernum node config/scripts/smoke-packaged-hang-watchdog-worker.mjs --app-dir=dist/linux-unpacked
package_windows:
name: package (windows)
needs: [code_paths, preflight]
if: needs.code_paths.outputs.package_windows == 'true'
runs-on: windows-2022
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Cache electron-builder downloads
uses: actions/cache/restore@v5
with:
path: |
~\AppData\Local\electron\Cache
~\AppData\Local\electron-builder\Cache
# Release builds seed this exact path set; PR-local copies cannot serve other PRs.
key: electron-builder-win-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
electron-builder-win-
# Do not save Node cache post-job: later Electron rebuild mutates the same paths to another ABI.
- uses: ./.github/actions/install-node-dependencies
id: deps
with:
native-runtime: node
persist-native-cache: 'false'
cache-dependency-path: |
pnpm-lock.yaml
mobile/pnpm-lock.yaml
# Why here: electron-builder's beforePack requires out/mobile-web, and the bundle
# build resolves React Native and Expo from mobile/node_modules.
- uses: ./.github/actions/install-mobile-dependencies
- name: Save compiled Node native modules
if: steps.deps.outputs.native-cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: ${{ steps.deps.outputs.native-cache-path }}
key: ${{ steps.deps.outputs.native-cache-key }}
# vitest runs here directly rather than through `pnpm test`, so the addon
# assertions only hold once install-node-dependencies has rebuilt natives.
- name: Test Windows installer process probe
if: needs.code_paths.outputs.windows_process_probe != 'false'
env:
ORCA_BACKGROUND_LAUNCH: '1'
# Serialize the two suites so their cold PowerShell/CIM starts cannot contend.
run: >-
pnpm exec vitest run --config config/vitest.config.ts
--no-file-parallelism
config/scripts/nsis-process-check.test.mjs
config/scripts/idle-cpu-process-sampling.test.mjs
- name: Cache Windows CLI launcher
uses: actions/cache@v5
with:
# Cache cargo registry with .build so misses do not rebuild resource crates cold.
path: |
native/windows-cli-launcher/.build
native/windows-cli-launcher/target
native/windows-agent-hook/.build
native/windows-agent-hook/target
~/.cargo/registry
key: windows-cli-launcher-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('native/windows-cli-launcher/**', 'native/windows-agent-hook/**', 'config/scripts/build-windows-cli-launcher.mjs', 'config/scripts/build-windows-agent-hook.mjs', 'config/scripts/windows-rust-executable-build.mjs', 'resources/build/icon.ico', 'package.json') }}
# Require cargo early; otherwise packaged Rust launcher fails deep inside builder native hooks.
- name: Ensure the Rust toolchain for the Windows CLI launcher
shell: pwsh
run: |
if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
rustup toolchain install stable --profile minimal
rustup default stable
}
cargo --version
# Build agent-hook once before tests so suites share the binary instead of compiling concurrently.
- name: Build the Windows agent hook
run: node config/scripts/build-windows-agent-hook.mjs
- name: Build the Windows CLI launcher
run: node config/scripts/build-windows-cli-launcher.mjs
# Files come from WINDOWS_BOUNDARY_TESTS, the same list that decides whether this job runs.
- name: Test Windows-specific boundaries
env:
ORCA_BACKGROUND_LAUNCH: '1'
ORCA_REAL_WINDOWS_SKILL_TEST: '1'
run: |
$files = node config/scripts/pr-package-test-paths.mjs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts @files
# Isolate slow-taskkill CPU burners so they cannot starve unrelated test files.
- name: Test Windows tree kill under CPU load
env:
ORCA_WINDOWS_TREE_KILL_LOAD: '1'
run: >-
pnpm exec vitest run --config config/vitest.config.ts
src/shared/__tests__/process-host/windows-tree-kill-late-taskkill.win32.test.ts
# Use parallel Vite targets as already packaged/smoked on Linux; release behavior is otherwise identical.
- name: Build package inputs
env:
ORCA_CI_MAIN_SOURCEMAPS: '0'
run: pnpm run build:release:parallel
- name: Prepare Electron native runtime
uses: ./.github/actions/prepare-native-runtime
with:
native-runtime: electron
node-version: ${{ steps.deps.outputs.node-version }}
- name: Package unpacked app
env:
ORCA_REUSE_PREPARED_NATIVE_RUNTIME: '1'
run: pnpm exec electron-builder --config config/electron-builder.config.cjs --dir
- name: Smoke packaged Windows PTY native capability
run: pnpm run smoke:windows-pty-native-capability -- --exe=dist/win-unpacked/Orca.exe
- name: Smoke packaged CLI
run: node config/scripts/smoke-packaged-cli.mjs --app-dir=dist/win-unpacked
e2e:
name: e2e
needs: [code_paths, preflight]
if: >-
!cancelled() &&
needs.code_paths.result == 'success' &&
needs.code_paths.outputs.e2e_should_run == 'true' &&
(
needs.preflight.result == 'success' ||
(
needs.preflight.result == 'skipped' &&
needs.code_paths.outputs.static_analysis == 'false' &&
needs.code_paths.outputs.typecheck == 'false'
)
)
# Why: reusable e2e.yml only checkouts, builds, and uploads artifacts.
permissions:
contents: read
uses: ./.github/workflows/e2e.yml
with:
# The synthetic pull-request merge ref can disappear while this reusable
# workflow is queued. The head SHA is immutable and works for every PR.
ref: ${{ github.event.pull_request.head.sha }}
test_files: ${{ needs.code_paths.outputs.test_files }}
run_changed_e2e: ${{ needs.code_paths.outputs.e2e_run_changed != 'false' }}
needs_build: ${{ needs.code_paths.outputs.e2e_needs_build != 'false' }}
managed_ssh_shards: ${{ needs.code_paths.outputs.e2e_ssh_shards || '["1/2","2/2"]' }}
# Gate the green layout oracles separately from main’s red broader E2E suite: changed-spec window plus both orcad serve hosts.
layout_oracle:
name: layout oracle
needs: [code_paths, preflight]
if: >-
!cancelled() &&
needs.code_paths.result == 'success' &&
needs.code_paths.outputs.layout_oracle == 'true' &&
needs.preflight.result == 'success'
permissions:
contents: read
uses: ./.github/workflows/e2e.yml
with:
# Why the merge commit, unlike `e2e`: a gate must test what lands on main, not a stale head.
ref: ${{ github.sha }}
test_files: '["tests/e2e/workspace-layout-oracle-headless.spec.ts","tests/e2e/workspace-layout-oracle.spec.ts"]'
run_changed_e2e: true
needs_build: true
artifact_suffix: -layout-oracle
# Keep native IME advisory while PR reliability is established (nightly 20/20, 2026-08-09..29, p50 3m25s); do not hide red via continue-on-error. To gate, add verify.needs, TERMINAL_IME_NATIVE env and separate success-or-skipped validation.
terminal_ime_native:
name: real IME
needs: code_paths
if: needs.code_paths.outputs.native_ime_source_changed == 'true'
# Why: the reusable workflow only checks out, builds, and uploads artifacts.
permissions:
contents: read
uses: ./.github/workflows/terminal-ime-e2e.yml
windows_wsl:
name: real WSL terminal
needs: code_paths
if: needs.code_paths.outputs.wsl_source_changed == 'true'
permissions:
contents: read
uses: ./.github/workflows/windows-wsl-e2e.yml
with:
ref: ${{ github.event.pull_request.head.sha }}
# Install like developers’ global virtual store in empty HOME/store; hoisting or stray dependencies can mask undeclared imports.
dependency_isolation:
name: dependency isolation under the global virtual store
needs: [code_paths]
if: >-
!cancelled() &&
needs.code_paths.result == 'success' &&
needs.code_paths.outputs.dependency_isolation == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 30
env:
ORCA_PNPM_FORCE_GLOBAL_VIRTUAL_STORE_IN_CI: '1'
steps:
# Persist layout via GITHUB_ENV (job env cannot read runner.temp) for every pnpm command/install check.
- name: Point HOME and the pnpm store at empty temp dirs
run: |
mkdir -p "$RUNNER_TEMP/empty-home"
{
echo "HOME=$RUNNER_TEMP/empty-home"
echo "pnpm_config_store_dir=$RUNNER_TEMP/isolated-store"
} >> "$GITHUB_ENV"
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: package.json
check-latest: true
package-manager-cache: false
- name: Setup pnpm
uses: ./.github/actions/setup-pnpm
- name: Install with every package isolated in its own store slot
run: |
set -euo pipefail
pnpm install --frozen-lockfile --ignore-scripts
if [ -d node_modules/bun ]; then
pnpm rebuild bun
fi
- name: Typecheck and check undeclared dependency resolution
run: |
set -euo pipefail
pnpm tc
pnpm exec vitest run --config config/vitest.config.ts config/scripts/undeclared-dependency-resolution.test.ts
# Pointless if any pnpm command above fell back to the hoisted layout or another store.
case "$(realpath node_modules/zod)" in
"$(realpath "$pnpm_config_store_dir")"/*) ;;
*) echo "node_modules/zod is not in the isolated store"; exit 1 ;;
esac
verify:
name: PR checks passed
# Publish an explicit result after every required dependency settles.
if: ${{ !cancelled() && github.event.pull_request.draft != true }}
needs:
- code_paths
- preflight
- git_compatibility
- codex_index_heal_contract
- xterm_patch_sync
- shell_contracts
- test
- mobile_web_app
- cross-version-wire
- managed_hook_node18
- package
- package_windows
- layout_oracle
- dependency_isolation
# Report settled results without competing with the GitHub-hosted test queue.
runs-on: blacksmith-2vcpu-ubuntu-2204
timeout-minutes: 5
steps:
# Keep E2E advisory while main’s scheduled suite is red, allowing fixes to merge. To gate, add e2e to needs/E2E env and check success-or-skipped outside the strict required-job loop.
- name: Require successful checks
env:
CODE_PATHS: ${{ needs.code_paths.result }}
SHOULD_RUN: ${{ needs.code_paths.outputs.should_run }}
PREFLIGHT: ${{ needs.preflight.result }}
STATIC_ANALYSIS_SHOULD_RUN: ${{ needs.code_paths.outputs.static_analysis }}
TYPECHECK_SHOULD_RUN: ${{ needs.code_paths.outputs.typecheck }}
PREFLIGHT_SHOULD_RUN: ${{ needs.code_paths.outputs.static_analysis == 'true' || needs.code_paths.outputs.typecheck == 'true' }}
GIT_COMPATIBILITY: ${{ needs.git_compatibility.result }}
GIT_COMPATIBILITY_SHOULD_RUN: ${{ needs.code_paths.outputs.git_compatibility }}
CODEX_INDEX_HEAL_CONTRACT: ${{ needs.codex_index_heal_contract.result }}
CODEX_INDEX_HEAL_CONTRACT_SHOULD_RUN: ${{ needs.code_paths.outputs.codex_index_heal_contract }}
XTERM_PATCH_SYNC: ${{ needs.xterm_patch_sync.result }}
XTERM_PATCH_SYNC_SHOULD_RUN: ${{ needs.code_paths.outputs.xterm_patch_sync }}
SHELL_CONTRACTS: ${{ needs.shell_contracts.result }}
SHELL_CONTRACTS_SHOULD_RUN: ${{ needs.code_paths.outputs.shell_contracts }}
TEST: ${{ needs.test.result }}
TEST_SHOULD_RUN: ${{ needs.code_paths.outputs.test }}
MOBILE_WEB_APP: ${{ needs.mobile_web_app.result }}
MOBILE_WEB_APP_SHOULD_RUN: ${{ needs.code_paths.outputs.mobile_web_app }}
CROSS_VERSION_WIRE: ${{ needs.cross-version-wire.result }}
CROSS_VERSION_WIRE_SHOULD_RUN: ${{ needs.code_paths.outputs.cross-version-wire }}
MANAGED_HOOK_NODE18: ${{ needs.managed_hook_node18.result }}
MANAGED_HOOK_NODE18_SHOULD_RUN: ${{ needs.code_paths.outputs.managed_hook_node18 }}
PACKAGE: ${{ needs.package.result }}
PACKAGE_SHOULD_RUN: ${{ needs.code_paths.outputs.package }}
PACKAGE_WINDOWS: ${{ needs.package_windows.result }}
PACKAGE_WINDOWS_SHOULD_RUN: ${{ needs.code_paths.outputs.package_windows }}
LAYOUT_ORACLE: ${{ needs.layout_oracle.result }}
LAYOUT_ORACLE_SHOULD_RUN: ${{ needs.code_paths.outputs.layout_oracle }}
DEPENDENCY_ISOLATION: ${{ needs.dependency_isolation.result }}
DEPENDENCY_ISOLATION_SHOULD_RUN: ${{ needs.code_paths.outputs.dependency_isolation }}
run: |
if [ "$CODE_PATHS" != "success" ]; then
exit 1
fi
case "$STATIC_ANALYSIS_SHOULD_RUN:$TYPECHECK_SHOULD_RUN" in
true:true|true:false|false:true|false:false) ;;
*) echo "preflight: missing or invalid selection"; exit 1 ;;
esac
if [ "$SHOULD_RUN" != "true" ]; then
echo "Docs-only change; expensive PR checks skipped."
fi
failed=0
check_job() {
local name="$1" result="$2" should="$3"
if [ "$should" = "true" ]; then
if [ "$result" != "success" ]; then
echo "$name: expected success, got $result"
failed=1
fi
elif [ "$should" = "false" ]; then
if [ "$result" != "skipped" ]; then
echo "$name: expected skipped, got $result"
failed=1
fi
else
echo "$name: missing or invalid selection"
failed=1
fi
}
# Require success when the PR has code-relevant changes
check_job preflight "$PREFLIGHT" "$PREFLIGHT_SHOULD_RUN"
check_job git_compatibility "$GIT_COMPATIBILITY" "$GIT_COMPATIBILITY_SHOULD_RUN"
check_job codex_index_heal_contract "$CODEX_INDEX_HEAL_CONTRACT" "$CODEX_INDEX_HEAL_CONTRACT_SHOULD_RUN"
check_job xterm_patch_sync "$XTERM_PATCH_SYNC" "$XTERM_PATCH_SYNC_SHOULD_RUN"
check_job shell_contracts "$SHELL_CONTRACTS" "$SHELL_CONTRACTS_SHOULD_RUN"
check_job test "$TEST" "$TEST_SHOULD_RUN"
check_job mobile_web_app "$MOBILE_WEB_APP" "$MOBILE_WEB_APP_SHOULD_RUN"
check_job cross-version-wire "$CROSS_VERSION_WIRE" "$CROSS_VERSION_WIRE_SHOULD_RUN"
check_job managed_hook_node18 "$MANAGED_HOOK_NODE18" "$MANAGED_HOOK_NODE18_SHOULD_RUN"
check_job package "$PACKAGE" "$PACKAGE_SHOULD_RUN"
check_job package_windows "$PACKAGE_WINDOWS" "$PACKAGE_WINDOWS_SHOULD_RUN"
check_job layout_oracle "$LAYOUT_ORACLE" "$LAYOUT_ORACLE_SHOULD_RUN"
check_job dependency_isolation "$DEPENDENCY_ISOLATION" "$DEPENDENCY_ISOLATION_SHOULD_RUN"
exit "$failed"