mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-27 23:21:53 +02:00
* 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
129 lines
8.0 KiB
TypeScript
129 lines
8.0 KiB
TypeScript
import { spawnSync } from 'node:child_process';
|
|
import { existsSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
import { createRequire } from 'node:module';
|
|
import { release, tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { nativeBrowserPaths } from '../src/cookie-import-native';
|
|
import { nativeCookieEnvironment, probeNativeCookieMember } from '../src/cookie-import-native-worker';
|
|
import { hashNativeFile, nativeCodeHashes, nativeCodeMatches, NATIVE_BROWSER_VERSION_COMMAND, NATIVE_QUALIFICATION_DATA } from '../src/cookie-import-native-integrity';
|
|
import { createNativeCookieJob, NativeCookieJobError, nativeCookieDiagnostic, type NativeCookieDiagnostic, type NativeCookieJob } from '../src/cookie-import-native-job';
|
|
|
|
if (process.platform !== 'win32') {
|
|
console.error('Native cookie qualification requires Windows; no cases ran and no qualification was issued.');
|
|
process.exit(1);
|
|
}
|
|
if (process.env.GITHUB_ACTIONS !== 'true' || process.env.CI !== 'true') {
|
|
console.error('Native cookie qualification requires a disposable GitHub Actions runner and never uses an existing browser profile.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const output = mkdtempSync(path.join(process.argv[2] || tmpdir(), 'cookie-native-qualification-'));
|
|
function incomplete(reason: string, diagnostic?: NativeCookieDiagnostic): never {
|
|
const receipt = { status: 'incomplete', reason, ...(diagnostic ? { diagnostic } : {}), counts: { pass: 0, fail: 0, skip: 0 }, activation: 'No build was qualified; production extraction remains disabled.' };
|
|
writeFileSync(path.join(output, 'qualification.json'), JSON.stringify(receipt, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
|
|
console.log(JSON.stringify({ ...receipt, artifactDirectory: output }));
|
|
process.exit(2);
|
|
}
|
|
if (Bun.version !== '1.4.0') incomplete('bun_1_4_0_required');
|
|
let emptyJob: NativeCookieJob | undefined;
|
|
try {
|
|
emptyJob = await createNativeCookieJob();
|
|
if (emptyJob.activeProcesses() !== 0) throw new NativeCookieJobError('job_query');
|
|
emptyJob.terminate();
|
|
if (emptyJob.activeProcesses() !== 0) throw new NativeCookieJobError('job_query');
|
|
emptyJob.close();
|
|
const preflight = { status: 'passed', activeProcesses: 0 };
|
|
writeFileSync(path.join(output, 'job-preflight.json'), JSON.stringify(preflight, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
|
|
console.log(JSON.stringify({ nativeJobPreflight: preflight }));
|
|
} catch (error) {
|
|
const diagnostic = nativeCookieDiagnostic(error, 'job_create');
|
|
try { emptyJob?.close(); } catch {}
|
|
writeFileSync(path.join(output, 'job-preflight.json'), JSON.stringify({ status: 'failed', diagnostic }, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
|
|
incomplete('native_job_preflight_failed', diagnostic);
|
|
}
|
|
const memberProbe = await probeNativeCookieMember();
|
|
const memberPreflight = 'cookies' in memberProbe ? { status: 'passed', activeProcesses: 0 } : { status: 'failed', error: memberProbe.error, diagnostic: memberProbe.diagnostic };
|
|
writeFileSync(path.join(output, 'member-preflight.json'), JSON.stringify(memberPreflight, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
|
|
console.log(JSON.stringify({ nativeMemberPreflight: memberPreflight }));
|
|
if ('error' in memberProbe) incomplete('native_member_preflight_failed', memberProbe.diagnostic);
|
|
const root = path.resolve(import.meta.dir, '../..');
|
|
let sourceHashes: Record<string, string>;
|
|
try {
|
|
sourceHashes = await nativeCodeHashes(root, Date.now() + 25_000);
|
|
} catch {
|
|
incomplete('source_inputs_unavailable_or_timed_out');
|
|
}
|
|
const environment = nativeCookieEnvironment(process.env);
|
|
const browser = nativeBrowserPaths('Edge', environment);
|
|
const executable = browser.executables.find(existsSync);
|
|
const node = Bun.which('node');
|
|
if (!executable || !node) incomplete('node_or_edge_not_installed');
|
|
if (existsSync(browser.userDataDir)) incomplete('existing_default_profile_refused');
|
|
const executableSha256 = await hashNativeFile(executable, Date.now() + 25_000);
|
|
const nodeProbe = spawnSync(node, ['-p', 'JSON.stringify({ version: process.version, architecture: process.arch })'], { env: environment, encoding: 'utf8', timeout: 10_000, windowsHide: true });
|
|
if (nodeProbe.status !== 0) incomplete('node_runtime_preflight_failed');
|
|
const nodeInfo = JSON.parse(nodeProbe.stdout);
|
|
if (nodeInfo.architecture !== process.arch || !['x64', 'arm64'].includes(process.arch)) incomplete('runtime_architecture_mismatch');
|
|
const require = createRequire(import.meta.url);
|
|
const playwrightVersion = require('playwright/package.json').version;
|
|
if (playwrightVersion !== '1.62.1') incomplete('playwright_1_62_1_required');
|
|
const versionProbe = spawnSync(path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'), [
|
|
'-NoProfile', '-NonInteractive', '-Command', NATIVE_BROWSER_VERSION_COMMAND,
|
|
], { env: { ...environment, GSTACK_QUALIFY_BROWSER_EXE: executable }, encoding: 'utf8', timeout: 10_000, windowsHide: true });
|
|
const versionErrorCode = (versionProbe.error as NodeJS.ErrnoException | undefined)?.code;
|
|
const versionMetadata = {
|
|
exitCode: versionProbe.status,
|
|
signal: versionProbe.signal,
|
|
spawnError: versionProbe.error ? (['ENOENT', 'EACCES', 'EPERM', 'ETIMEDOUT'].includes(versionErrorCode || '') ? versionErrorCode : 'spawn_failed') : undefined,
|
|
stdoutBytes: Buffer.byteLength(versionProbe.stdout || ''),
|
|
stderrBytes: Buffer.byteLength(versionProbe.stderr || ''),
|
|
versionValid: /^\d+(?:\.\d+){2,3}$/.test((versionProbe.stdout || '').trim()),
|
|
};
|
|
writeFileSync(path.join(output, 'browser-version-preflight.json'), JSON.stringify(versionMetadata, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
|
|
console.log(JSON.stringify({ nativeBrowserVersionPreflight: versionMetadata }));
|
|
if (versionProbe.status !== 0 || !versionMetadata.versionValid) incomplete('browser_version_preflight_failed');
|
|
const result = spawnSync(process.execPath, ['--no-env-file', '--no-install', '--no-macros', '--config=NUL', 'test', 'browse/test/cookie-import-native-job.test.ts', '--test-name-pattern', '^native Windows process qualification'], {
|
|
cwd: root,
|
|
env: { ...environment, CI: 'true', GITHUB_ACTIONS: 'true', GSTACK_COOKIE_NATIVE_DEFAULT_FIXTURE: '1', NO_COLOR: '1' },
|
|
encoding: 'utf8',
|
|
timeout: 300_000,
|
|
maxBuffer: 16 * 1024 * 1024,
|
|
windowsHide: true,
|
|
});
|
|
const log = `${result.stdout || ''}\n${result.stderr || ''}`;
|
|
writeFileSync(path.join(output, 'qualification.log'), log, { mode: 0o600, flag: 'wx' });
|
|
const count = (kind: string) => Number([...log.matchAll(new RegExp(`(?:^|\\n)\\s*(\\d+) ${kind}\\b`, 'g'))].at(-1)?.[1] ?? 0);
|
|
const counts = { pass: count('pass'), fail: count('fail'), skip: count('skip') };
|
|
let inputsUnchanged = false;
|
|
try {
|
|
const deadline = Date.now() + 25_000;
|
|
inputsUnchanged = await hashNativeFile(executable, deadline) === executableSha256 && nativeCodeMatches(sourceHashes, await nativeCodeHashes(root, deadline));
|
|
} catch {}
|
|
const passed = result.status === 0 && !result.error && counts.pass === 7 && counts.fail === 0 && counts.skip === 0 && inputsUnchanged && !existsSync(browser.userDataDir);
|
|
const receipt = {
|
|
status: passed ? 'passed' : 'failed',
|
|
scope: 'Edge default-profile v20 extraction, pipe transport, profile identity, and owned-process cleanup',
|
|
qualifiedBuild: {
|
|
browserName: 'Edge',
|
|
architecture: nodeInfo.architecture,
|
|
windowsRelease: release(),
|
|
executableSha256,
|
|
nodeVersion: nodeInfo.version,
|
|
bunVersion: Bun.version,
|
|
playwrightVersion,
|
|
sourceHashes,
|
|
},
|
|
browserVersion: versionProbe.stdout.trim(),
|
|
supervisorArchitecture: process.arch,
|
|
jobPreflight: { status: 'passed', activeProcesses: 0 },
|
|
memberPreflight,
|
|
counts,
|
|
inputsUnchanged,
|
|
sourceHashes,
|
|
activationData: NATIVE_QUALIFICATION_DATA,
|
|
activation: 'Review this receipt and add only this exact qualifiedBuild to the separate activation data; the runner never enables extraction and activation does not modify the recorded code hashes.',
|
|
};
|
|
writeFileSync(path.join(output, 'qualification.json'), JSON.stringify(receipt, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
|
|
console.log(JSON.stringify({ status: receipt.status, counts, artifactDirectory: output }));
|
|
process.exitCode = passed ? 0 : 1;
|