Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ jobs:
# of silently making every expensive job skip.
ci: ${{ steps.scope.outputs.ci }}
desktop: ${{ steps.scope.outputs.desktop }}
fast: ${{ steps.lane.outputs.fast }} # true: `test changed` replaces the four shards
fast: ${{ steps.lane.outputs.fast }} # true: `test changed` replaces the five shards
native: ${{ steps.matrices.outputs.native }}
# Matrix include lists for keyring-smoke and npm-global-smoke, built and
# shape-checked by the same validation step as `native`.
Expand Down Expand Up @@ -633,7 +633,7 @@ jobs:
# Keep role validation after the historical isolate preload and spawnSync failures
# (#6713, #4956). Promotion requires a green full cross-platform lane=all run on
# the exact candidate head; focused local results do not replace that proof.
# The suite, split by file across four Linux runners.
# The suite, split by file across five Linux runners.
#
# `scripts/ci/run-bun-test-batches.sh` assigns files to shards by the per-file
# durations recorded in `scripts/ci/test-durations.tsv` (sorted round-robin when
Expand All @@ -649,21 +649,21 @@ jobs:
# the same coverage in fresh Bun processes.
#
# Only the suite lives here. Typecheck, lint, build, and the scans run once in
# `gates` rather than four times — they are fixed cost, and paying it per shard
# `gates` rather than five times — they are fixed cost, and paying it per shard
# would eat what the sharding saves.
test:
name: test ${{ matrix.shard }}/4
name: test ${{ matrix.shard }}/5
needs: changes
if: github.event_name != 'pull_request' || (needs.changes.outputs.ci == 'true' && needs.changes.outputs.fast != 'true')
runs-on: ubuntu-latest
# A quarter of the suite. A shard that needs longer than this is wedged, not
# A fifth of the suite. A shard that needs longer than this is wedged, not
# slow — the old 30-minute ceiling was margin for the Windows leg, which no
# longer runs here (see platform-windows).
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
shard: [1, 2, 3, 4, 5]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
Expand Down Expand Up @@ -711,7 +711,7 @@ jobs:

- name: Test in fresh-process batches
env:
TEST_SHARD: ${{ matrix.shard }}/4
TEST_SHARD: ${{ matrix.shard }}/5
run: bash scripts/ci/run-bun-test-batches.sh "$TEST_SHARD"

# Fast lane: import-graph tests for the diff; the batched dev dispatch is the backstop.
Expand Down
5,735 changes: 2,902 additions & 2,833 deletions scripts/ci/test-durations.tsv

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions structure/ops/cross-platform-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
The [desktop membership contract](../runtime.md#codex-desktop-process-membership) has adapter regression coverage on every host and real PowerShell prefilter regression coverage with synthetic CIM rows on Windows in `tests/clients/desktop-app-restart.test.ts`. A skipped Windows lane does not exercise that native filter; uid-dependent POSIX cases in `tests/clients/desktop-app-restart-posix.test.ts` are skipped on Windows.

`.github/workflows/ci.yml` is the ordinary quality gate for runtime/package changes. A pull
request verifies Linux and TypeScript: Linux runs the suite in four shards with a separate
`gates` job alongside the scoped docs, structure, packaging, keyring, and npm-global jobs. The
request verifies Linux and TypeScript: the fast lane runs `test changed`, and the full lane runs
the Linux suite in five shards. A separate `gates` job runs alongside the scoped docs, structure,
packaging, keyring, and npm-global jobs. The
`platform-macos` macOS suite, the `widget` macOS widget + Tauri app-bundle build, and the
`desktop-shell` Rust toolchain build are native-gated: they run on the promotion pushes to
`main` and `preview` and on explicit `workflow_dispatch`, and on a pull request only when the
Expand Down
2 changes: 1 addition & 1 deletion structure/ops/docs-and-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ cannot supply a write-job PR number. If no unique local current-head candidate r

| Workflow | Trigger | Purpose |
| --- | --- | --- |
| `.github/workflows/ci.yml` | Any `pull_request`; runtime/package `push` to `main`/`preview`; manual dispatch | A pull request verifies Linux and TypeScript: Linux runs four suite shards plus `gates` alongside the scoped docs, structure, packaging, and npm-global jobs; `docker-smoke` and the ubuntu `keyring-smoke` leg run on a pull request only when the `changes` job's `docker` / `keyring` filters select it, and on every push, dispatch, and nightly run. The `platform-macos` macOS suite, the `widget` macOS widget + Tauri app-bundle build, and the `desktop-shell` Rust toolchain build are native-gated: they run on `main`/`preview` pushes and manual dispatch, and on a pull request only when the `changes` job's native path filter selects the change. `dev` pushes start nothing; dev integration is covered by the pull-request run, while `main` and `preview` must stay push triggers because `release.yml` requires a push-event run for the exact release SHA. Windows runs nine shards only on manual dispatch with `lane=all` (or empty), not on push events. Linux runs at-most-12-file processes with a 120-second process bound; Windows uses measured six-file/480-second processes and all-file scope so its full-suite contract is unchanged. The dedicated Windows batch step sets `OCX_TEST_NO_QUEUE=1` because its sequential processes are one logical runner; each process still creates an isolated home and arms the test guards before the lock boundary. No lane retries: a test failure, a process timeout and a Bun runtime crash each fail their job on the first occurrence. Aggregate `ci` is event-aware — it derives which jobs this event requested and requires `success` from each of them and `skipped` from the rest, and on a `lane=all` dispatch it reads the run's own job list and requires nine concrete successful `windows N/9` results. `npm-global-smoke` remains GitHub-hosted because it mutates the global package prefix. Manual `lane=release-gates` keeps the ordinary native-gated jobs and selected dynamic keyring/packaging matrix legs, but skips the Windows suite and unsharded macOS control. Default `all` (or empty) still requests both diagnostics; `macos-control` requests the control without Windows suite shards. Release eligibility requires successful push-event CI on the exact release SHA; a manual lane does not authorize publishing. Changes that touch only `.github/actions/` or `native/remote-workspace-helper/` run the narrow `setup-action` and `remote-helper` jobs instead of the full matrix, and Linux shard membership follows the per-file durations in `scripts/ci/test-durations.tsv`. |
| `.github/workflows/ci.yml` | Any `pull_request`; runtime/package `push` to `main`/`preview`; manual dispatch | A pull request verifies Linux and TypeScript: Linux runs `test changed` in the fast lane or five suite shards in the full lane, plus `gates` alongside the scoped docs, structure, packaging, and npm-global jobs; `docker-smoke` and the ubuntu `keyring-smoke` leg run on a pull request only when the `changes` job's `docker` / `keyring` filters select it, and on every push, dispatch, and nightly run. The `platform-macos` macOS suite, the `widget` macOS widget + Tauri app-bundle build, and the `desktop-shell` Rust toolchain build are native-gated: they run on `main`/`preview` pushes and manual dispatch, and on a pull request only when the `changes` job's native path filter selects the change. `dev` pushes start nothing; dev integration is covered by the pull-request run, while `main` and `preview` must stay push triggers because `release.yml` requires a push-event run for the exact release SHA. Windows runs nine shards only on manual dispatch with `lane=all` (or empty), not on push events. Linux runs at-most-12-file processes with a 120-second process bound; Windows uses measured six-file/480-second processes and all-file scope so its full-suite contract is unchanged. The dedicated Windows batch step sets `OCX_TEST_NO_QUEUE=1` because its sequential processes are one logical runner; each process still creates an isolated home and arms the test guards before the lock boundary. No lane retries: a test failure, a process timeout and a Bun runtime crash each fail their job on the first occurrence. Aggregate `ci` is event-aware — it derives which jobs this event requested and requires `success` from each of them and `skipped` from the rest, and on a `lane=all` dispatch it reads the run's own job list and requires nine concrete successful `windows N/9` results. `npm-global-smoke` remains GitHub-hosted because it mutates the global package prefix. Manual `lane=release-gates` keeps the ordinary native-gated jobs and selected dynamic keyring/packaging matrix legs, but skips the Windows suite and unsharded macOS control. Default `all` (or empty) still requests both diagnostics; `macos-control` requests the control without Windows suite shards. Release eligibility requires successful push-event CI on the exact release SHA; a manual lane does not authorize publishing. Changes that touch only `.github/actions/` or `native/remote-workspace-helper/` run the narrow `setup-action` and `remote-helper` jobs instead of the full matrix, and Linux shard membership follows the per-file durations in `scripts/ci/test-durations.tsv`. |
| `.github/workflows/dev-version-bump.yml` | Manual dispatch with an intended version and `pre-move` or `repair` mode | Opens the reviewed pull request that moves `dev` past a release target. The default `pre-move` mode runs before promotion and publication; explicit `repair` mode retains the post-publish catch-up path. It is neither called by `release.yml` nor triggered by publication. |
| `.github/workflows/release.yml` | Manual dispatch only | npm publish/dry-run workflow. The `preflight` job checks channel, version sources, tag, GitHub release, npm, global tag ordering and the `dev` pre-move before any packaging job starts. The publish job repeats those checks, requires a successful push-event Cross-platform CI run for the exact `GITHUB_SHA` (a pull-request run does not qualify), requires `dev` to outrank the target, then checks the target against the freshly fetched global tag set before publish or dry-run. After a real publish, `release-outcomes` reports the public GitHub release, the npm version read-back and the npm dist-tag as separate rows. |
| `.github/workflows/deploy-docs.yml` | `push` to `main` touching `docs-site/**` or the workflow, or manual dispatch | Build and publish the Astro/Starlight docs site to GitHub Pages. This is the deploy path; the pull-request build gate is the `docs-site-build` job in `ci.yml`. |
Expand Down
2 changes: 1 addition & 1 deletion tests/ci-workflows/ci-review-lanes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ describe("CI review lanes", () => {
expect(`${jobName}:${job?.needs}`).toBe(`${jobName}:changes`);
expect(`${jobName}:${job?.if}`).toBe(`${jobName}:${scopedCondition}`);
}
// The pull-request fast lane swaps the four full shards for `test changed`; every
// The pull-request fast lane swaps the five full shards for `test changed`; every
// other event keeps the shards, and `fast` is only ever true on a pull request.
const shards = ci.jobs?.test as { needs?: string; if?: string } | undefined;
expect(shards?.needs).toBe("changes");
Expand Down
7 changes: 5 additions & 2 deletions tests/ci-workflows/ci-shard-balance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,11 @@ describe.skipIf(process.platform === "win32")("duration-weighted shards, execute

test("the shards still tile the suite exactly under a skewed table", () => {
const durations = { "alpha.test.ts": 9_000, "delta.test.ts": 4_000, "foxtrot.test.ts": 3_500 };
const seen = [1, 2, 3, 4].flatMap(index => filesOf(runShard(`${index}/4`, { durations })));
expect([...seen].sort()).toEqual(tests(FILES));
for (const count of [4, 5]) {
const shards = Array.from({ length: count }, (_, index) => filesOf(runShard(`${index + 1}/${count}`, { durations })));
for (const shard of shards) expect(shard.length).toBeGreaterThan(0);
expect(shards.flat().sort()).toEqual(tests(FILES));
}
}, SPAWN_BUDGET_MS);

test("each shard runs its files in sorted order", () => {
Expand Down
11 changes: 9 additions & 2 deletions tests/ci-workflows/ci-workflows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,18 @@ describe("GitHub Actions hardening", () => {
// green — the worst failure available here. Pin them to each other.
const linuxShards = (ci.jobs?.test as { strategy?: { matrix?: { shard?: number[] } } })
?.strategy?.matrix?.shard ?? [];
expect(linuxShards).toEqual([1, 2, 3, 4]);
expect(linuxShards).toEqual([1, 2, 3, 4, 5]);
Comment thread
lidge-jun marked this conversation as resolved.
Resolved
// The Linux shards pass their divisor through TEST_SHARD to the batch runner. Before
// Windows sharded differently, this assertion matched the Windows step's --shard
// literal by coincidence; pin the Linux env line so it observes the Linux job.
expect(workflow).toContain(`TEST_SHARD: \${{ matrix.shard }}/${linuxShards.length}`);
const linuxJob = ci.jobs?.test as {
name?: string;
steps?: Array<{ name?: string; env?: Record<string, string>; run?: string }>;
};
const linuxTest = linuxJob.steps?.find(step => step.name === "Test in fresh-process batches");
expect(linuxJob.name).toBe(`test \${{ matrix.shard }}/${linuxShards.length}`);
expect(linuxTest?.env?.TEST_SHARD).toBe(`\${{ matrix.shard }}/${linuxShards.length}`);
expect(linuxTest?.run).toBe('bash scripts/ci/run-bun-test-batches.sh "$TEST_SHARD"');

// Every job that runs tests/ must fetch tags, because one of those tests reads
// them. tests/ci-workflows/release-version-line.test.ts compares package.json against the
Expand Down
Loading