mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-08 04:11:18 +02:00
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:
1 parent
65bfb0ce49
commit
dcaea52800
333 files changed
+41755
-7357
No files matched your search
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user