Files
gstack/.github/workflows/free-tests.yml
T
Garry Tan a84b0b5b6d v1.90.0.0 feat: make browser cookie imports explicit and safe (#2964)
* fix(browse): prepare reliable cookie import wave for validation

* ci: sequence quality and behavior for validation branch

* fix(browse): isolate Windows qualification and preserve native diagnostics

* test(browse): cover cookie workflow quality and isolate Windows user paths

* test(browse): trace native member startup and initialize fresh folders

* fix(browse): keep Windows member stdin alive through EOF

* fix(browse): latch native timeouts and compare contained Edge startup

* test(browse): verify native version metadata and actual Windows argv

* test(browse): qualify Dia import on isolated macOS CI

* fix(browse): require picker origin for session mutations

* fix(browse): bound credential reads through stream completion

* test(browse): inspect owned Windows process arguments natively

* test(evals): preserve passing coverage during cookie repair reruns

* test(browse): isolate Dia qualification in a fresh macOS account

* test(browse): pass bounded integer timeouts to native Mac probes

* test(browse): distinguish Windows profile initialization from containment

* test(browse): await descendant pipe readiness before parent exit

* test(browse): initialize and restore isolated macOS Keychain state

* test(browse): initialize Windows fixture folders before qualification

* test(ci): pin the same Node runtime across Windows checks

* test(browse): distinguish native macOS browser preflight stages

* test(browse): isolate Windows descendant console lifetime

* test(browse): preserve native receipts and identify fixture lock holders

* test(browse): prepare dependency resolution before native Mac worker startup

* test(ci): include lock and close checks in native diagnostics

* test(browse): preserve native owner probe stages and subprocess deadlines

* fix(browse): classify Chromium profile-in-use exit precisely

* test(browse): retain Mac qualification evidence through cleanup failures

* test(browse): bound Mac fixture paths and retire its owned user domain

* test(browse): accept vanished fixture entries without weakening cleanup

* test(browse): identify probe-created macOS user domains safely

* test(browse): observe Mac user domains without targeting them first

* test(browse): use passive fresh-user ownership throughout Mac qualification

* test(browse): distinguish profile and registered-home Keychain lookups

* test(browse): qualify Dia under one registered account home

* test(browse): identify Dia startup and owned process-group failures

* test(browse): classify bounded Dia startup diagnostics without leaking output

* fix(test): preserve native Mac sandboxing and reap owned browser children

* fix(browse): preserve Chromium sandboxing for native profile imports

* test(browse): inspect signed Mach-O architecture without launching Xcode tools

* test(browse): sample pending Dia startup and reap on all cleanup paths

* test(browse): compare protected Dia launches in fresh Bun and Node accounts

* test(browse): inspect isolated Mac GUI readiness without browser access

* v1.90.0.0 fix: bind cookie picker actions to their document

* test: validate cookie guards and fit nested launch fixtures

* ci: configure the bundled Chromium sandbox helper

* fix(browse): classify Playwright authentication timeouts

* test: retain bounded Windows lifecycle diagnostics

* test(cso): reuse bounded NTFS precision candidates

* test(review): handle explicit preservation choices safely

* test(browse): remove owned fixture directories with explicit primitives

* test(review): distinguish descriptive reuse from edit commitments

* test: admit only the approved unscored cookie workflow refusal

* test: keep the Office Hours judge mock export-complete

* fix: keep dependency-free CI planners independent of the model SDK

* test: observe the exact holder after a native fixture unlink failure

* fix: start seeded PTY observations at owned readiness

* test: acquire identity-bound Windows deletion admission before profile resets

* test: preserve qualified Git index bits without authorizing mutations
2026-09-25 12:06:45 -04:00

339 lines
14 KiB
YAML

name: Free Tests
# A single duration-balanced plan covers every free test exactly once. Each
# shard runs serially on its own machine; the aggregate requires every receipt
# and strict outcome. Tree-mutating files, when present, get a separate machine.
#
# Deliberately SECRETLESS: free tests make no API calls, so this lane gets no
# provider keys at all — least privilege, and fork PRs get real test signal
# here (the eval matrix skips fork PRs because repository secrets can't reach
# them). test/free-tests-workflow-wiring.test.ts fails CI if a secret sneaks in.
#
# This is a REQUIRED check from day one (branch protection lists it). If it's
# red, fix or quarantine-with-issue — don't make it advisory; an advisory lane
# is permanent false comfort.
#
# Local `bun run test` still uses the existing bounded process pool.
on:
pull_request:
branches: [main]
# Also on main pushes: two individually-green PRs can merge into a red
# main; without this nothing runs the free suite on main until the next PR.
push:
branches: [main]
workflow_dispatch:
# Keyed on the PR number, not head_ref: a bare branch name carries no fork
# prefix, so same-name branches from two forks would share one group and a
# push to fork B's PR would cancel fork A's in-flight REQUIRED check.
concurrency:
group: free-tests-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
# Least privilege: this job executes PR-controlled code (install lifecycle
# scripts + the test suite), so the GITHUB_TOKEN gets read-only contents and
# the checkout doesn't persist it into .git/config.
permissions:
contents: read
jobs:
free-plan:
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.0
- id: plan
name: Inventory and balance the complete free suite
run: |
matrix=$(bun run scripts/test-free-shards.ts --ci-plan "$RUNNER_TEMP/free-plan.json" --shards 20)
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: free-plan
path: ${{ runner.temp }}/free-plan.json
if-no-files-found: error
cso-macos-launcher:
runs-on: macos-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- name: Install helper dependencies
run: bun install --frozen-lockfile --ignore-scripts
- name: Build and sign the native macOS startup boundary
run: bun run build:cso
- name: Exercise hardened-runtime startup and environment scrubbing
run: bun run test:cso:macos
env:
GSTACK_CSO_MACOS_TESTS: "1"
- name: Build browser lifecycle prerequisites
run: |
bunx playwright install chromium
bun run build
- name: Exercise native agent ownership and linked settings
env:
TMPDIR: /tmp
run: |
files=(browse/test/terminal-agent-lifecycle.test.ts browse/test/terminal-agent-native-observation.test.ts browse/test/terminal-agent-watchdog.test.ts browse/test/server-embedder-terminal-port.test.ts browse/test/server-factory.test.ts test/gstack-settings-hook-symlink.test.ts test/gstack-settings-hook-schema-aware.test.ts)
for file in "${files[@]}"; do test -f "$file"; done
bun test "${files[@]}"
cso-windows-launcher:
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- name: Install helper dependencies
run: bun install --frozen-lockfile --ignore-scripts
- name: Build the native Windows startup boundary from Git Bash
shell: bash
run: bun run build:cso
- name: Exercise native startup, environment, and argument forwarding
run: bun run test:cso:windows
env:
GSTACK_CSO_WINDOWS_TESTS: "1"
cso-docker-integration:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- name: Require local Docker containment prerequisites
# A missing daemon is a failed gate, never a skipped integration test.
run: |
command -v docker
docker --host unix:///var/run/docker.sock info
command -v cc
- name: Install helper dependencies
run: bun install --frozen-lockfile --ignore-scripts
- name: Compile trusted CSO helper and watchdog
run: bun run build:cso
- name: Run CSO Docker integration gate
run: bun run test:cso:docker
env:
GSTACK_CSO_DOCKER_TESTS: "1"
DOCKER_HOST: unix:///var/run/docker.sock
free-suite:
needs: free-plan
runs-on: ubicloud-standard-8
timeout-minutes: 20
strategy:
fail-fast: false
max-parallel: 20
matrix: ${{ fromJSON(needs.free-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.0
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: free-plan
path: ${{ runner.temp }}
- uses: actions/cache@v6
with:
path: ~/.bun/install/cache
key: linux-bun-${{ hashFiles('bun.lock') }}
# A lockfile bump starts from the previous cache instead of cold.
restore-keys: |
linux-bun-
- name: Install dependencies
run: bun install --frozen-lockfile
- uses: actions/cache@v6
with:
path: ~/.cache/ms-playwright
key: linux-playwright-${{ hashFiles('bun.lock') }}
restore-keys: |
linux-playwright-
# Cache restores browser binaries; install is still required for system
# deps and is a fast no-op for already-present browsers.
- name: Install Playwright Chromium
run: npx playwright install --with-deps chromium
- name: Configure the bundled Chromium sandbox helper
run: |
set -euo pipefail
chrome=$(bun -e 'import { chromium } from "playwright"; import { realpathSync } from "node:fs"; console.log(realpathSync(chromium.executablePath()))')
case "$chrome" in
"$HOME"/.cache/ms-playwright/chromium-*/chrome-linux*/chrome) ;;
*) echo "Unexpected Chromium installation path" >&2; exit 1 ;;
esac
helper="${chrome%/*}/chrome_sandbox"
installed="${chrome%/*}/chrome-sandbox"
test -f "$helper" && test ! -L "$helper"
sudo install -T -o root -g root -m 4755 "$helper" "$installed"
test "$(stat -c '%u:%a' "$installed")" = '0:4755'
cmp -s "$helper" "$installed"
# Headed-browser tests (handoff, extension sidepanel DOM) need a real
# DISPLAY — first Linux run failed with Playwright's "launched a headed
# browser without an XServer" banner. xvfb-run below provides it;
# x11-utils ships xdpyinfo for display probing. poppler-utils ships
# pdftotext/pdffonts/pdftoppm for the make-pdf e2e gates;
# fonts-noto-color-emoji is the emoji-gate's render font (playwright
# --with-deps usually installs it, but the gate must not depend on a
# transitive package list). Fonts must land BEFORE the first browse
# daemon launch — Chromium snapshots fontconfig at startup.
- name: Install Xvfb + X11 utilities + gate tools
run: sudo apt-get install -y --no-install-recommends xvfb x11-utils poppler-utils fonts-noto-color-emoji
- name: Configure git identity (tests init temp repos)
run: |
git config --global user.email "free-tests-ci@gstack.test"
git config --global user.name "Free Tests CI"
git config --global init.defaultBranch main
# Some tests run git against the checkout itself; CI checkouts can be
# owned by a different uid than the runner user.
git config --global --add safe.directory '*'
- name: Generate host SKILL.md outputs (.agents, .factory)
# Golden-file tests read generated host outputs that are gitignored.
run: bun run gen:skill-docs --host all
- name: Vendor xterm assets into the extension
# extension/lib/xterm* are gitignored (vendored from npm at build
# time). Without them the sidepanel's terminal script bails and the
# sidepanel DOM tests time out waiting on init that never happens.
run: bun run vendor:xterm
- name: Build server-node bundle (loaded by browse cli imports)
run: bash browse/scripts/build-node-server.sh
# Narrowed gate build: the make-pdf e2e gates probe make-pdf/dist/pdf,
# browse/dist/browse, and the diagram-render bundle, then self-skip when
# absent — which made them silently skip on Linux for their whole life
# (this lane never built binaries). Full `bun run build` compiles five
# binaries and would add ~60-90s to the ONLY required check; the gates
# need exactly these three artifacts.
- name: Build gate binaries (make-pdf e2e gates)
run: bun run build:gates
- name: Build trusted CSO helper used by free contract tests
run: bun run build:cso
# GSTACK_EXPECT_BINARIES=1 arms make-pdf/test/e2e/ci-prereqs.test.ts:
# if a future edit drops the gate build (or poppler), the lane FAILS
# instead of the gates silently self-skipping back to false green.
- name: Run free suite
run: xvfb-run -a bun run test:free --ci-run "$RUNNER_TEMP/free-plan.json" --shard ${{ matrix.shard }} --result "$RUNNER_TEMP/free-results/shard-${{ matrix.shard }}.json"
env:
GSTACK_EXPECT_BINARIES: "1"
# WS1 flake telemetry: a single timing flake must not red the only
# required lane — the runner's attribution-gated retry pass (cap 5,
# truncation veto) re-runs failing files once, serially, and a
# clean retry downgrades to a LOUD flaky-pass. Every flaky-pass is
# appended to the ledger (single writer: the parent runner) and
# uploaded below, so repeat offenders are an enumerable series —
# recorded and ranked, never masked. Pinned by
# test/free-tests-workflow-wiring.test.ts.
GSTACK_FREE_RETRY_FLAKY: "1"
GSTACK_FLAKE_LEDGER: ${{ runner.temp }}/flake-ledger.jsonl
- name: Upload strict shard result
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: free-result-${{ matrix.shard }}
path: ${{ runner.temp }}/free-results/*.json
if-no-files-found: error
# Uploaded unconditionally (not just on failure): a flaky-pass run is
# GREEN — that's the point — so its evidence must survive green runs.
- name: Upload flake ledger
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: flake-ledger-${{ matrix.shard }}
path: ${{ runner.temp }}/flake-ledger.jsonl
if-no-files-found: ignore
retention-days: 90
- name: Detect recovered failures for log retention
id: flake_spool
if: always()
run: |
if [ -s "$RUNNER_TEMP/flake-ledger.jsonl" ]; then
echo 'present=true' >> "$GITHUB_OUTPUT"
fi
# The quiet console omits assertion details. Preserve the original spool
# after a recovered retry too, so a green job retains its first failure.
- name: Upload shard logs on failure or recovered retry
if: failure() || steps.flake_spool.outputs.present == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: free-test-shard-logs-${{ matrix.shard }}
path: /tmp/gstack-free-test-*.log
if-no-files-found: ignore
# Branch protection already requires the `free-tests` context. Keep that
# stable name as an always-running aggregate so every platform-specific CSO
# gate is merge-blocking without a separate branch-protection migration.
free-tests:
if: always()
needs: [free-suite, cso-macos-launcher, cso-windows-launcher, cso-docker-integration]
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Require the free suite and every CSO platform gate
env:
FREE_SUITE_RESULT: ${{ needs.free-suite.result }}
CSO_MACOS_RESULT: ${{ needs.cso-macos-launcher.result }}
CSO_WINDOWS_RESULT: ${{ needs.cso-windows-launcher.result }}
CSO_DOCKER_RESULT: ${{ needs.cso-docker-integration.result }}
run: |
set -eu
test "$FREE_SUITE_RESULT" = success
test "$CSO_MACOS_RESULT" = success
test "$CSO_WINDOWS_RESULT" = success
test "$CSO_DOCKER_RESULT" = success
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: free-plan
path: ${{ runner.temp }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: free-result-*
merge-multiple: true
path: ${{ runner.temp }}/free-results
- name: Require exact coverage and complete strict results
run: bun run scripts/test-free-shards.ts --ci-verify "$RUNNER_TEMP/free-plan.json" --results "$RUNNER_TEMP/free-results"