Files
gstack/.github/workflows/windows-free-tests.yml
T
Garry Tanandcapy-ai[bot] c5c2f28994 v1.91.128.0 ci(seed): weekly standing recorder for the Linux and Windows duration seeds (#3253)
* ci(seed): weekly standing recorder for the Linux and Windows duration seeds (W4)

free-durations-record.yml runs Mondays 04:00 UTC (before test-health) and on
dispatch. Linux records on ubicloud-standard-8, the free-suite shard runner,
with scripts/ubicloud/setup-free-suite.sh; Windows records through
windows-free-tests.yml's record_durations path (now also workflow_call).
When either seed's file set drifts from main, the propose job force-updates
bot/free-duration-seeds, opens or refreshes one seed PR stating the seed
ratchet result and the files that failed while recording, and dispatches the
free lanes on it. A red file keeps its duration; an interrupted recording
uploads no seed (this also stops the Windows dispatch from uploading a stale
copy), and more than 5 red files on either OS proposes nothing. The bot never
edits the allowlist. open_pr=false on dispatch records and reports only.

The workflow is classified free-lane only in scripts/test-pr-profile.ts, with
a non-behavioral HARNESS_VERSION decision.

Intentional pin change: test/windows-native-workflows.test.ts now expects
workflow_call and the complete-recording guard on the Windows record job.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(seed): keep maintainer commits on the open seed PR, publish only from main, survive long ratchet reports

/review on #3253 (gpt-6.1-sol): a refresh force-pushed over maintainer repairs
on the seed PR, a dispatch from a feature branch would publish that branch's
commits, and grep | head under pipefail could abort the step on a long ratchet
report. The refresh now leaves an open seed PR alone when it holds any commit
besides the bot's one refresh commit and pushes with an exact lease; only main
publishes; the report is cut with sed. Each has a functional test that fails on
the old workflow.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): unreleased entry for the weekly duration-seed recorder

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: stamp v1.91.128.0

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-18 01:07:35 +00:00

274 lines
11 KiB
YAML

name: Windows Free Tests
# The Windows-safe subset of the free suite on GitHub's free `windows-latest`
# runners. scripts/test-free-shards.ts --windows-only is the single source of
# truth for which files run: POSIX-bound tests (bin/ shebang spawns, raw /tmp
# paths, /bin/sh, mode bitmasks, the Bun-driven browse server) are excluded by
# pattern there, with explicit force-include and known-incompatible lists.
# Every run prints the curated count and top exclusion reasons in the job
# summary so a shrinking subset is visible.
#
# Same plan/run/verify model as free-tests.yml: one duration-balanced plan of
# the curated files, one shard per Windows job, and an aggregate that requires
# every strict result and exact coverage. The plan and the verify step are
# static (file contents and the git revision), so they run on Linux. Packing
# reads Windows-measured durations (scripts/free-test-durations-windows.json):
# Windows-only files such as the native cookie job cost minutes there and
# nothing on Linux. Refresh it with a `record_durations` dispatch and commit
# the artifact.
#
# Native browser qualification campaigns (Windows cookie extraction, launch
# diagnostics, Dia on macOS) are dispatch-only in native-qualification.yml.
on:
pull_request:
branches: [main]
workflow_dispatch:
inputs:
record_durations:
description: Record per-file Windows durations (artifact) instead of running the lane
type: boolean
default: false
# free-durations-record.yml calls the recording path weekly.
workflow_call:
inputs:
record_durations:
type: boolean
default: false
concurrency:
group: windows-free-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
# Test-only lane — no token writes.
permissions:
contents: read
jobs:
windows-plan:
if: ${{ !inputs.record_durations }}
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
matrix: ${{ steps.plan.outputs.matrix }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
- id: plan
name: Curate and balance the Windows-safe free suite
# Six shards: ~85 s of Linux-measured work each, ~2.5 min on a
# 4-vCPU Windows runner, so the lane lands near 4 min with setup.
env:
GSTACK_FREE_TEST_DURATIONS: scripts/free-test-durations-windows.json
run: |
matrix=$(bun run scripts/test-free-shards.ts --windows-only --ci-plan "$RUNNER_TEMP/windows-plan.json" --shards 6)
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: windows-plan
path: ${{ runner.temp }}/windows-plan.json
if-no-files-found: error
windows-free-shard:
needs: windows-plan
runs-on: windows-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.windows-plan.outputs.matrix) }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24.18.0
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
name: windows-plan
path: ${{ runner.temp }}
# bun's install cache lives under ~/.bun/install/cache on every platform.
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: ~/.bun/install/cache
key: windows-bun-${{ hashFiles('bun.lock') }}
# A lockfile bump starts from the previous cache instead of cold.
restore-keys: |
windows-bun-
- name: Configure git identity (required by tests that init temp repos)
run: |
git config --global user.email "windows-ci@gstack.test"
git config --global user.name "Windows CI"
git config --global init.defaultBranch main
shell: bash
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build server-node.mjs (required by Windows browse path)
# browse/src/cli.ts throws at module load on Windows if server-node.mjs
# is missing — Bun can't drive Playwright's Chromium on Windows
# (oven-sh/bun#4253), so any test that transitively loads cli.ts needs
# the Node-compatible server bundle. The full build is unnecessary.
run: bash browse/scripts/build-node-server.sh
shell: bash
- name: Generate host SKILL.md outputs (.agents, .factory)
# Golden-file tests read gitignored generated host outputs.
run: bun run gen:skill-docs --host all
shell: bash
- name: Install Chromium for the Node worker smoke
run: bunx playwright install chromium
- name: Run Windows-safe shard
env:
# Same policy as the required Linux lane: an attributed failure gets
# one serial retry (cap 5 files, truncation veto) and a clean retry
# is a loud flaky pass, appended to the ledger uploaded below and
# listed in the aggregate job summary.
GSTACK_FREE_RETRY_FLAKY: '1'
GSTACK_FLAKE_LEDGER: ${{ runner.temp }}/flake-ledger.jsonl
TEMP: ${{ runner.temp }}
TMP: ${{ runner.temp }}
run: bun run test:windows --ci-run "$RUNNER_TEMP/windows-plan.json" --shard ${{ matrix.shard }} --result "$RUNNER_TEMP/windows-results/shard-${{ matrix.shard }}.json"
shell: bash
- name: Upload strict shard result
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: windows-result-${{ matrix.shard }}
path: ${{ runner.temp }}/windows-results/*.json
if-no-files-found: error
# A red lane must carry the WHY: the runner's quiet console names files,
# not causes, so complete shard logs are kept on every run.
- name: Upload full shard logs
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: windows-free-test-shard-logs-${{ matrix.shard }}
path: |
.context/free-test-logs/gstack-free-test-*.log
${{ runner.temp }}/gstack-free-test-*.log
include-hidden-files: true
if-no-files-found: ignore
- name: Upload flake ledger
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: flake-ledger-windows-${{ matrix.shard }}
path: ${{ runner.temp }}/flake-ledger.jsonl
if-no-files-found: ignore
retention-days: 90
windows-free-tests:
if: ${{ always() && !inputs.record_durations }}
needs: [windows-plan, windows-free-shard]
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Require the plan and every Windows shard
env:
PLAN_RESULT: ${{ needs.windows-plan.result }}
SHARD_RESULT: ${{ needs.windows-free-shard.result }}
run: |
set -eu
test "$PLAN_RESULT" = success
test "$SHARD_RESULT" = success
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
name: windows-plan
path: ${{ runner.temp }}
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
pattern: windows-result-*
merge-multiple: true
path: ${{ runner.temp }}/windows-results
- name: Require exact coverage and complete strict results
run: bun run scripts/test-free-shards.ts --windows-only --ci-verify "$RUNNER_TEMP/windows-plan.json" --results "$RUNNER_TEMP/windows-results"
windows-record-durations:
if: ${{ inputs.record_durations }}
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24.18.0
- name: Prepare the same inputs as a Windows shard
shell: bash
run: |
git config --global user.email "windows-ci@gstack.test"
git config --global user.name "Windows CI"
git config --global init.defaultBranch main
bun install --frozen-lockfile
bash browse/scripts/build-node-server.sh
bun run gen:skill-docs --host all
bunx playwright install chromium
# Records into this checkout's default seed path: GSTACK_FREE_TEST_DURATIONS
# would leak into every timed test, and seed-reading tests would then read
# the half-written recording instead of their fixtures.
- name: Time every Windows-safe file alone
id: record
shell: bash
env:
TEMP: ${{ runner.temp }}
TMP: ${{ runner.temp }}
run: |
set -o pipefail
bun run test:windows --record-durations --wall-timeout 1200 2>&1 | tee "$RUNNER_TEMP/record.log"
- name: Name the recording as the Windows seed
id: seed
if: always() && steps.record.outcome != 'skipped'
shell: bash
run: |
grep -q 'wrote [0-9]* durations to' "$RUNNER_TEMP/record.log" || { echo "::error::The recording was interrupted; no Windows seed to upload."; exit 1; }
cp scripts/free-test-durations.json "$RUNNER_TEMP/free-test-durations-windows.json"
sed -n '/failed while recording:/,$p' "$RUNNER_TEMP/record.log" | sed 's/\r$//' | { grep -E '^ ✗ [^ ]+$' || true; } | sed 's/^ ✗ //' > "$RUNNER_TEMP/record-failures.txt"
- name: Upload full recording logs
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: windows-record-durations-logs
path: |
.context/free-test-logs/gstack-free-test-*.log
${{ runner.temp }}/gstack-free-test-*.log
include-hidden-files: true
if-no-files-found: ignore
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
if: always() && steps.seed.outcome == 'success'
with:
name: free-test-durations-windows
path: |
${{ runner.temp }}/free-test-durations-windows.json
${{ runner.temp }}/record-failures.txt
if-no-files-found: error