mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-02 17:40:02 +02:00
* feat: add surface-aware exploratory QA and ship documentation gates * test: preserve delegated QA setup authority after main integration * fix(qa): clarify exploration order and preserve report artifacts * test(qa): follow the shared setup reference directly * refactor(ship): make verification and recovery routes explicit * test(ship): align evidence and review guards with explicit routes * fix(workflows): clarify ship recovery and functional QA evidence * fix(workflows): clarify approval recovery and full QA coverage * refactor(workflows): order review transactions and clarify ship state * fix(ship): clarify final verification and fail closed at publication * fix(evals): attribute native atomic documentation writes * fix(ship): clarify recovery and documentation lifecycle guidance * fix(test): preserve observed native placeholder styling in CI * fix(codex): report watchdog timeouts without a process-exit race * Checkpoint functional QA implementation and workflow validation repairs * Fix documentation and shared-review fixture contracts * docs: clarify judge reuse and evaluation supervision * test: align review evidence and selected case contracts * test: verify append-only documentation checkpoints and recovery * fix: qualify QA workflows and CI validation repairs * fix: launch shared-libs fixture scripts on Windows * fix: qualify QA deadlines, fixture isolation, and shard cleanup * fix: preserve qualified QA and cancellation repairs * fix: enforce functional fixture authority and share strict event decoding * fix: retain free-test evidence and explain recovery * fix: reject malformed native evidence after decoder consolidation * test: use reliable capture for telemetry privacy filters * test: refresh measured quick coverage and document validation costs * Fix native fixture receipts and preserve VM validation evidence * Align negative judge controls with upstream clarity policy * Fix report-only QA preparation and public evidence handling * Clarify QA-only preparation and current-report preservation * Stream Ship quality judgments with an explicit 64k response contract * Validate compact judge reasoning locally with supported wire schema * Align functional QA fixture instructions with evidence acceptance * Bind native browser diagnostics to execution evidence and align review verdicts * Preserve native diagnostic line boundaries * Serialize functional QA evidence from native captures * Keep large QA evidence fixture payload out of Windows argv
108 lines
5.4 KiB
TypeScript
108 lines
5.4 KiB
TypeScript
/**
|
|
* Static tripwire for .github/workflows/free-tests.yml — the Linux free-suite
|
|
* lane. Pins the three properties that made the lane worth having:
|
|
*
|
|
* 1. It invokes the CANONICAL runner (bun run test:free), not a raw
|
|
* `bun test <dirs>` glob — the runner owns TEST_ROOTS and strict-output
|
|
* classification, so a truncated run can't report green.
|
|
* 2. It is SECRETLESS: free tests make no API calls, and keeping keys out
|
|
* means fork PRs get real signal here. Any `secrets.` reference is a
|
|
* regression.
|
|
* 3. It triggers on `pull_request` (never `pull_request_target`, which
|
|
* would hand a fork PR the base repo's context).
|
|
*
|
|
* Same wiring-tripwire class as test/hermetic-wiring.test.ts.
|
|
*/
|
|
|
|
import { describe, test, expect } from 'bun:test';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import * as os from 'os';
|
|
import { spawnSync } from 'node:child_process';
|
|
|
|
const WORKFLOW = path.resolve(import.meta.dir, '..', '.github', 'workflows', 'free-tests.yml');
|
|
|
|
describe('free-tests workflow wiring', () => {
|
|
const source = fs.readFileSync(WORKFLOW, 'utf-8');
|
|
|
|
test('workflow exists and invokes the canonical runner', () => {
|
|
expect(source).toContain('bun run test:free');
|
|
expect(source).not.toMatch(/run:\s*bun test\s/);
|
|
});
|
|
|
|
test('secretless: no secrets reach the free lane', () => {
|
|
expect(source).not.toContain('secrets.');
|
|
expect(source).not.toContain('ANTHROPIC_API_KEY');
|
|
expect(source).not.toContain('OPENAI_API_KEY');
|
|
});
|
|
|
|
test('pull_request trigger, never pull_request_target', () => {
|
|
expect(source).toContain('pull_request:');
|
|
expect(source).not.toContain('pull_request_target');
|
|
});
|
|
|
|
test('the isolated matrix consumes one plan and the required aggregate verifies all receipts', () => {
|
|
const workflow = Bun.YAML.parse(source) as any;
|
|
const planner = workflow.jobs['free-plan'];
|
|
const suite = workflow.jobs['free-suite'];
|
|
const aggregate = workflow.jobs['free-tests'];
|
|
expect(planner.steps.find((step: any) => step.id === 'plan').run).toContain('--ci-plan');
|
|
expect(suite.needs).toBe('free-plan');
|
|
expect(suite.strategy.matrix).toBe('${{ fromJSON(needs.free-plan.outputs.matrix) }}');
|
|
expect(suite.strategy['fail-fast']).toBe(false);
|
|
expect(suite.strategy['max-parallel']).toBe(20);
|
|
expect(suite.steps.find((step: any) => step.name === 'Run free suite').run).toContain('--ci-run');
|
|
expect(suite.steps.find((step: any) => step.name === 'Upload strict shard result').if).toBe('always()');
|
|
expect(aggregate.if).toBe('always()');
|
|
expect(aggregate.needs).toContain('free-suite');
|
|
expect(aggregate.steps.some((step: any) => step.run?.includes('--ci-verify'))).toBe(true);
|
|
expect(source).not.toContain('--quick');
|
|
});
|
|
|
|
test('flake telemetry stays wired: retry flag, single-writer ledger, unconditional artifact', () => {
|
|
// WS1: a timing flake must not red the required lane, but every
|
|
// flaky-pass must be recorded and uploaded — a green run is exactly when
|
|
// the evidence matters. Removing any of these silently returns flakes to
|
|
// either merge-blocking (flag off) or invisibility (ledger/artifact off).
|
|
expect(source).toMatch(/GSTACK_FREE_RETRY_FLAKY:\s*"1"/);
|
|
expect(source).toMatch(/GSTACK_FLAKE_LEDGER:\s*\$\{\{ runner\.temp \}\}\/flake-ledger\.jsonl/);
|
|
expect(source).toContain('name: flake-ledger');
|
|
expect(source).toMatch(/name: Upload flake ledger\s*\n\s*if: always\(\)/);
|
|
});
|
|
|
|
test.skipIf(!Bun.which('bash'))('a recovered retry retains its original detailed spool', () => {
|
|
const steps = (Bun.YAML.parse(source) as any).jobs['free-suite'].steps;
|
|
const probe = steps.find((step: any) => step.id === 'flake_spool');
|
|
const upload = steps.find((step: any) => step.with?.name === 'free-test-shard-logs-${{ matrix.shard }}');
|
|
expect(probe.if).toBe('always()');
|
|
expect(upload.if).toBe("failure() || steps.flake_spool.outputs.present == 'true'");
|
|
expect(upload.with.path).toBe('.context/free-test-logs/gstack-free-test-*.log');
|
|
expect(upload.with['include-hidden-files']).toBe(true);
|
|
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'free spool '));
|
|
const output = path.join(directory, 'step-output');
|
|
const ledger = path.join(directory, 'flake-ledger.jsonl');
|
|
try {
|
|
for (const contents of [null, '', '{"kind":"flaky-pass","file":"test/example.test.ts"}\n']) {
|
|
if (contents !== null) fs.writeFileSync(ledger, contents);
|
|
fs.writeFileSync(output, '');
|
|
const result = spawnSync('bash', ['-e', '-c', probe.run], {
|
|
// Git Bash accepts C:/... paths; native backslashes are not shell paths.
|
|
env: { ...process.env, RUNNER_TEMP: directory.split(path.sep).join('/'),
|
|
GITHUB_OUTPUT: output.split(path.sep).join('/') },
|
|
encoding: 'utf8', timeout: 5000,
|
|
});
|
|
expect(result.status, result.stderr).toBe(0);
|
|
expect(fs.readFileSync(output, 'utf8')).toBe(contents ? 'present=true\n' : '');
|
|
}
|
|
} finally { fs.rmSync(directory, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('least-privilege token: contents read-only, credentials not persisted', () => {
|
|
// The job executes PR-controlled code (install lifecycle scripts + the
|
|
// suite itself). A default-grant GITHUB_TOKEN persisted into .git/config
|
|
// by checkout would hand that code whatever the repo default allows.
|
|
expect(source).toMatch(/permissions:\s*\n\s*contents:\s*read/);
|
|
expect(source).toMatch(/persist-credentials:\s*false/);
|
|
});
|
|
});
|