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
65 changes: 61 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ jobs:
# skipping the check it selects.
setup_action: ${{ steps.narrow.outputs.setup_action }}
remote_helper: ${{ steps.narrow.outputs.remote_helper }}
# Pull-request scope for the two smoke jobs that used to ride the whole ci filter.
# Push, dispatch and schedule run both regardless; see the docker/keyring filters.
docker: ${{ steps.narrow.outputs.docker }}
keyring: ${{ steps.narrow.outputs.keyring }}
# Whether this pull request needs the nine Windows shards: the windows path
# filter below OR an added src/tests line naming a Windows marker. Computed
# by scripts/ci/windows-sensitive-diff.sh, which validates its input and
Expand Down Expand Up @@ -388,6 +392,43 @@ jobs:
# real only on macOS and Windows, so its job lints and tests the crate on all three.
remote_helper:
- 'native/remote-workspace-helper/**'
# docker-smoke and keyring-smoke on a pull request. Both used to run on every PR the
# ci filter matched, which is nearly all of them, while each proves something narrow:
# the image builds, starts and survives recreation with its volume; the OS keyring
# binding creates, reads and deletes a secret. A pull request now runs each only when
# its own inputs move. Push to main/preview, workflow_dispatch and the nightly still
# request both unconditionally, so the release gate's push run always carries them.
# ci.yml is listed so an edit to either job verifies itself. The setup action is left to
# its own setup-action job, as the docs and structure filters leave it.
docker:
Comment thread
lidge-jun marked this conversation as resolved.
Resolved
- 'Dockerfile'
- 'compose.yaml'
- '.dockerignore'
- 'docker/**'
- 'scripts/ci/docker-smoke.ts'
- 'scripts/generate-compatibility-version.ts'
- 'scripts/model-metadata.source.json'
# The image's final RUN imports version.ts to prove the generated compatibility
# manifest; these four files are its whole module closure (Bun.build metafile).
- 'src/routing/compatibility/version.ts'
- 'src/lab/digest.ts'
- 'src/lab/conformance/digest.ts'
- 'src/lab/conformance/jcs.ts'
- 'src/cli/index.ts'
- 'package.json'
- 'bun.lock'
- 'tsconfig.json'
- 'gui/package.json'
- 'gui/bun.lock'
- '.github/workflows/ci.yml'
# scripts/keyring-smoke.ts imports only src/lib/keyring-native.ts; the native binding
# itself arrives through package.json and bun.lock.
keyring:
- 'src/lib/keyring-native.ts'
- 'scripts/keyring-smoke.ts'
- 'package.json'
- 'bun.lock'
- '.github/workflows/ci.yml'
# Paths whose change selects the nine Windows suite shards on a pull
# request. These carry the Windows service, update, shim, intercept,
# ACL and process code, and the harness every Windows shard runs.
Expand Down Expand Up @@ -532,9 +573,11 @@ jobs:
env:
SETUP_ACTION: ${{ github.event_name == 'schedule' && 'true' || steps.filter.outputs.setup_action }}
REMOTE_HELPER: ${{ github.event_name == 'schedule' && 'true' || steps.filter.outputs.remote_helper }}
DOCKER_SCOPE: ${{ github.event_name == 'schedule' && 'true' || steps.filter.outputs.docker }}
KEYRING_SCOPE: ${{ github.event_name == 'schedule' && 'true' || steps.filter.outputs.keyring }}
run: |
set -euo pipefail
for pair in "setup_action=$SETUP_ACTION" "remote_helper=$REMOTE_HELPER"; do
for pair in "setup_action=$SETUP_ACTION" "remote_helper=$REMOTE_HELPER" "docker=$DOCKER_SCOPE" "keyring=$KEYRING_SCOPE"; do
case "${pair#*=}" in
true|false)
printf '%s\n' "$pair" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -1148,7 +1191,7 @@ jobs:
keyring-smoke:
name: keyring ${{ matrix.name }}
needs: changes
if: github.event_name != 'pull_request' || needs.changes.outputs.ci == 'true'
if: github.event_name != 'pull_request' || (needs.changes.outputs.ci == 'true' && needs.changes.outputs.keyring == 'true')
runs-on: ${{ matrix.runner }}
timeout-minutes: 8
strategy:
Expand Down Expand Up @@ -1211,7 +1254,7 @@ jobs:
docker-smoke:
name: docker smoke
needs: changes
if: github.event_name != 'pull_request' || needs.changes.outputs.ci == 'true'
if: github.event_name != 'pull_request' || (needs.changes.outputs.ci == 'true' && needs.changes.outputs.docker == 'true')
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
Expand Down Expand Up @@ -1729,6 +1772,8 @@ jobs:
CHANGES_STRUCTURE: ${{ needs.changes.outputs.structure }}
CHANGES_SETUP_ACTION: ${{ needs.changes.outputs.setup_action }}
CHANGES_REMOTE_HELPER: ${{ needs.changes.outputs.remote_helper }}
CHANGES_DOCKER: ${{ needs.changes.outputs.docker }}
CHANGES_KEYRING: ${{ needs.changes.outputs.keyring }}
CHANGES_NATIVE: ${{ needs.changes.outputs.native }}
CHANGES_DESKTOP: ${{ needs.changes.outputs.desktop }}
CHANGES_WINDOWS: ${{ needs.changes.outputs.windows }}
Expand Down Expand Up @@ -1785,6 +1830,16 @@ jobs:
if [ "$CHANGES_REMOTE_HELPER" = "true" ]; then
remote_helper=requested
fi
# docker-smoke and keyring-smoke follow the ci scope, narrowed on a pull request to
# their own filters. Mirrors their `if:` exactly, like desktop_shell above.
docker=$scoped
if [ "$EVENT_NAME" = "pull_request" ] && [ "$CHANGES_DOCKER" != "true" ]; then
docker=not-requested
fi
keyring=$scoped
if [ "$EVENT_NAME" = "pull_request" ] && [ "$CHANGES_KEYRING" != "true" ]; then
keyring=not-requested
fi
# privacy-gate runs exactly where `gates` (scoped) does not, on every
# pull request the ci filter declines. Deriving it from `scoped` keeps
# the two scans complementary here as they are in the jobs' own
Expand Down Expand Up @@ -1823,8 +1878,10 @@ jobs:
expected_for() {
case "$1" in
changes|select-windows-runner) echo requested ;;
test|storage-policy|api-usage|gates|keyring-smoke|docker-smoke)
test|storage-policy|api-usage|gates)
echo "$scoped" ;;
docker-smoke) echo "$docker" ;;
keyring-smoke) echo "$keyring" ;;
platform-macos|widget)
echo "$native" ;;
desktop-shell) echo "$desktop_shell" ;;
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/react-doctor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@ name: React Doctor

on:
# Scans the PR's changed files relative to the merge base of the target branch.
# The scan reads only gui/ (`directory: gui` below), so a pull request that
# changes nothing there has nothing to scan: it used to start a full-history
# checkout and an npm install on every PR for an empty changed-file list. No
# branch rule requires this check, so a PR it skips is not left waiting on it.
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- "gui/**"
- ".github/workflows/react-doctor.yml"
# Scans `main` on every push to track the health-score trend.
push:
branches: ["main"]
Expand Down
4 changes: 2 additions & 2 deletions 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, 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 `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 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/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 All @@ -226,7 +226,7 @@ cannot supply a write-job PR number. If no unique local current-head candidate r
| `.github/workflows/issue-quality-tests.yml` | `pull_request` and `push` to `main`/`preview` filtered on the issue/PR automation scripts, templates, and their workflows | Tests the issue and PR automation scripts themselves, so the gates cannot rot silently. |
| `.github/workflows/issue-triage.yml` | `issues` (opened) | Duplicate detection and triage labeling for new issues. |
| `.github/workflows/pr-labeler.yml` | `pull_request_target` (opened, edited, synchronize, labeled, unlabeled) | Type and path labeling plus title sync; `labeled`/`unlabeled` let a human override enqueue a fresher run in the per-PR concurrency group. |
| `.github/workflows/react-doctor.yml` | `pull_request` (opened, synchronize, reopened, ready_for_review) and `push` to `main`; no path filter | React-focused static review. Findings fail the job; write-scoped outputs stay disabled, a contract pinned by `tests/ci-workflows/ci-workflows.test.ts`. |
| `.github/workflows/react-doctor.yml` | `pull_request` (opened, synchronize, reopened, ready_for_review) touching `gui/**` or the workflow, and every `push` to `main` | React-focused static review. Findings fail the job; write-scoped outputs stay disabled, a contract pinned by `tests/ci-workflows/ci-workflows.test.ts`. |
| `.github/workflows/stale-needs-info.yml` | `schedule` only (daily 06:15 UTC); deliberately no manual dispatch | Closes issues left in needs-info past the grace period. Manual dispatch is omitted so a branch-selected run cannot execute that branch's body with issue write scope. |

`pull_request_target`, `issues`, and `schedule` workflows always load from the repository default branch, not from `dev`. Landing a change to one of them on `dev` does not change live behavior until it is promoted, so those files follow the promotion model rather than ordinary integration.
Expand Down
2 changes: 2 additions & 0 deletions tests/ci-workflows/ci-privacy-gate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ describe.skipIf(cannotRunAggregate)("the aggregate ci gate, executed", () => {
CHANGES_STRUCTURE: "false",
CHANGES_SETUP_ACTION: "false",
CHANGES_REMOTE_HELPER: "false",
CHANGES_DOCKER: "false",
CHANGES_KEYRING: "false",
...scope,
// needs serializes as an object per job; the gate reads .value.result.
RESULTS: JSON.stringify(Object.fromEntries(Object.entries(results).map(([job, result]) => [job, { result }]))),
Expand Down
Loading
Loading