v1.91.7.0 feat: add functional QA and pre-publication docs checks (#2983)

* 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
This commit is contained in:
Garry Tan authored and GitHub committed 2026-09-29 06:07:35 -07:00
1 parent 65bfb0ce49
commit dcaea52800
333 files changed
+41755 -7357

No files matched your search

+74 -4
View File
@@ -54,6 +54,7 @@ import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { spawnSync } from 'node:child_process';
import { createBootstrapRetentionScope } from '../test/helpers/bootstrap-retention';
import {
BunTestOutputClassifier,
exactTestFileSelectors,
@@ -62,6 +63,7 @@ import {
normalizeRelativePath,
runShardChild,
strictTestExitCode,
type ShardChildResult,
} from './test-strict-output';
import { PAID_TEST_GLOBS, isPaidTestFile } from '../test/helpers/paid-test-set';
import { PERIODIC_CI_EXCLUDE } from '../test/helpers/periodic-exclude-data';
@@ -707,6 +709,14 @@ export async function runPaidShard(
env.TEMP = childTmp;
env.TMP = childTmp;
env.CHROMIUM_PROFILE = path.join(stateDir, 'chromium-profile');
const bootstrapFile = files.some(file => normalizeRelativePath(file) === 'test/skill-e2e-qa-workflow.test.ts');
delete env.GSTACK_BOOTSTRAP_RETENTION;
if (bootstrapFile && process.platform !== 'linux') log(`${label} bootstrap dependency retention unavailable on ${process.platform}; native behavior still runs without retained-dependency qualification`);
const bootstrapRetention = bootstrapFile && process.platform === 'linux'
? createBootstrapRetentionScope(childTmp, path.join(env.GSTACK_EVAL_DIR || getProjectEvalDir(), 'bootstrap-retention'), env.EVALS_RUN_ID ||= `bootstrap-${Date.now()}-${process.pid}`)
: undefined;
if (bootstrapRetention) Object.assign(env, bootstrapRetention.env);
let retentionFailed = false;
const startedAt = Date.now();
log(`${label} START ${files.join(' ')} (timeout ${Math.round(timeoutMs / 1000)}s, ${budget.source}${budget.policyId ? `: ${budget.policyId}` : ''})`);
@@ -740,6 +750,8 @@ export async function runPaidShard(
let exitCode: number | null = null;
let timedOut = false;
let groupPid: number | null = null;
let incompleteCapture: ShardChildResult['incompleteCapture'];
const shardDeadline = Date.now() + timeoutMs;
try {
// Shared spawn/detached/group-kill/wall-timer/reap lifecycle.
const result = await runShardChild({
@@ -748,6 +760,7 @@ export async function runPaidShard(
cwd: rootDir,
env,
timeoutMs,
deadlineMs: shardDeadline,
hookStreams: (child) => {
const streams: Array<Promise<void>> = [];
if (child.stdout) streams.push(forwardAndClassify(child.stdout, sink(process.stdout), classifier, 'stdout'));
@@ -758,15 +771,64 @@ export async function runPaidShard(
exitCode = result.exitCode;
timedOut = result.timedOut;
groupPid = result.groupPid;
incompleteCapture = result.incompleteCapture;
} catch (error) {
const result = (error as { shardResult?: ShardChildResult } | null)?.shardResult;
if (result) {
exitCode = result.exitCode;
timedOut = result.timedOut;
groupPid = result.groupPid;
incompleteCapture = result.incompleteCapture;
}
throw error;
} finally {
// Close the spool even when the spawn itself failed.
await new Promise<void>((resolve) => logStream.end(() => resolve()));
if (incompleteCapture) log(`${label} incomplete child capture: ${JSON.stringify(incompleteCapture)}; retained log prefix: ${logPath}`);
await new Promise<void>((resolve) => {
let settled = false;
let timer: ReturnType<typeof setTimeout> | undefined;
const finish = (complete: boolean) => {
if (settled) return;
settled = true;
clearTimeout(timer);
logStream.off('error', onError);
logStream.off('close', onClose);
if (!complete) {
logWriteFailed = true;
logStream.destroy();
log(`${label} incomplete log capture; retained prefix: ${logPath}`);
}
resolve();
};
const onError = () => finish(false);
const onClose = () => finish(logStream.writableFinished && !logWriteFailed);
const expire = () => { timedOut = true; finish(false); };
logStream.once('error', onError);
logStream.once('close', onClose);
if (Date.now() >= shardDeadline) { expire(); return; }
if (logWriteFailed || logStream.destroyed) { finish(false); return; }
timer = setTimeout(expire, shardDeadline - Date.now());
try { logStream.end(() => finish(logStream.writableFinished && !logWriteFailed)); }
catch { finish(false); }
});
let retentionRemovable = true;
if (bootstrapRetention) {
try {
const retained = await bootstrapRetention.cleanup(shardDeadline);
retentionFailed = !retained.complete;
retentionRemovable = retained.removable;
if (retentionFailed) log(`${label} bootstrap retention incomplete; qualification failed`);
} catch {
retentionFailed = true;
retentionRemovable = false;
log(`${label} bootstrap retention acknowledgment failed; preserving shard state`);
}
}
try {
// async rm: a SIGKILLed shard can leave a full git workspace + Chromium
// profile here; a synchronous recursive delete on the parent's event
// loop would stall every sibling shard's stream classification and
// wall timers for seconds (review finding).
await fs.promises.rm(stateDir, { recursive: true, force: true });
if (retentionRemovable) await fs.promises.rm(stateDir, { recursive: true, force: true });
} catch {
// Best-effort: a locked file must not turn a real verdict into an
// exception (same posture as the free runner's cleanup).
@@ -786,7 +848,7 @@ export async function runPaidShard(
const expectedFiles = files.length;
let status: ShardStatus = timedOut
? 'timed-out'
: strictTestExitCode(exitCode ?? 1, summary, expectedFiles) === 0 ? 'passed' : 'failed';
: !retentionFailed && !logWriteFailed && !incompleteCapture && strictTestExitCode(exitCode ?? 1, summary, expectedFiles) === 0 ? 'passed' : 'failed';
if (status === 'passed' && options.expectedCases) {
const expected = files.reduce((count, file) => count + (options.expectedCases![file] ?? 0), 0);
const actual = summary.terminalTestCounts.reduce((count, value) => count + value, 0) - summary.skippedTests;
@@ -1714,6 +1776,14 @@ async function main(): Promise<number> {
for (const files of shards) resolvePaidShardTimeoutMs(files, timeoutOverride);
console.log(`[test:paid] slice ${options.sliceIndex}/${manifest.sliceCount}: ${shards.length} shard(s), tier=${manifest.tier}, evalsAll=${manifest.evalsAll}`);
if (options.listOnly) {
for (const [index, files] of shards.entries()) {
const budget = resolvePaidShardBudget(files, timeoutOverride);
console.log(` shard ${index + 1}/${shards.length}: ${files.join(' ')} wall=${budget.timeoutMs}ms source=${budget.source} policy=${budget.policyId ?? 'none'}`);
}
return 0;
}
const evalDirBase = process.env.GSTACK_EVAL_DIR || getProjectEvalDir();
let summary: RunSummary;
if (shards.length === 0) {