mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-28 07:32:14 +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
126 lines
6.9 KiB
TypeScript
126 lines
6.9 KiB
TypeScript
import { lstatSync, unlinkSync } from 'node:fs';
|
|
import { toNamespacedPath } from 'node:path';
|
|
import { dlopen, FFIType, ptr } from 'bun:ffi';
|
|
|
|
type Identity = { dev: bigint; ino: bigint; mode: bigint };
|
|
type Handle = number | bigint;
|
|
type Stage = 'admission' | 'identity' | 'disposition' | 'close' | 'absence';
|
|
|
|
export class FixtureDeleteError extends Error {
|
|
readonly code: string;
|
|
readonly syscall: string;
|
|
constructor(public stage: Stage, public path: string, public win32Error?: number, public deadlineExceeded = false) {
|
|
super(`Owned fixture deletion failed at ${stage}${deadlineExceeded ? ' (deadline)' : win32Error === undefined ? '' : ` (Windows ${win32Error})`}`);
|
|
this.name = 'FixtureDeleteError';
|
|
this.code = deadlineExceeded ? 'ETIMEDOUT' : win32Error === 32 ? 'EBUSY' : win32Error === 2 || win32Error === 3 ? 'ENOENT' : win32Error === 5 ? 'EACCES' : 'EIO';
|
|
this.syscall = { admission: 'open', identity: 'fstat', disposition: 'unlink', close: 'close', absence: 'lstat' }[stage];
|
|
}
|
|
}
|
|
|
|
export interface FixtureDeleteBackend {
|
|
open(file: string): Handle;
|
|
identity(handle: Handle, file: string): { dev: bigint; ino: bigint; attributes: number; filesystem: string };
|
|
dispose(handle: Handle, file: string): void;
|
|
close(handle: Handle, file: string): void;
|
|
absent(file: string): boolean;
|
|
}
|
|
|
|
type LeaseClock = { now(): number; wait(ms: number): void; onSharing?(): void };
|
|
const leaseClock: LeaseClock = { now: () => performance.now(), wait: ms => { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); } };
|
|
|
|
export function deleteWithFixtureLease(file: string, expected: Identity, deadline: number, verify: () => void,
|
|
backend: FixtureDeleteBackend, clock: LeaseClock = leaseClock): { admissionProbes: number; waitedMs: number } {
|
|
if (!Number.isFinite(deadline)) throw new Error('Invalid fixture deletion deadline');
|
|
let handle: Handle | undefined;
|
|
let firstSharing: FixtureDeleteError | undefined;
|
|
let admissionProbes = 0;
|
|
let waitedMs = 0;
|
|
while (handle === undefined) {
|
|
if (clock.now() >= deadline) throw firstSharing ?? new FixtureDeleteError('admission', file, undefined, true);
|
|
verify();
|
|
if (clock.now() >= deadline) throw firstSharing ?? new FixtureDeleteError('admission', file, undefined, true);
|
|
try { admissionProbes++; handle = backend.open(file); }
|
|
catch (error) {
|
|
if (!(error instanceof FixtureDeleteError) || error.stage !== 'admission' || error.win32Error !== 32) throw error;
|
|
if (!firstSharing) { firstSharing = error; clock.onSharing?.(); }
|
|
const remaining = deadline - clock.now();
|
|
if (remaining <= 0) throw firstSharing;
|
|
const before = clock.now();
|
|
clock.wait(Math.min(20, remaining));
|
|
waitedMs += Math.max(0, clock.now() - before);
|
|
}
|
|
}
|
|
let failed = false;
|
|
let failure: unknown;
|
|
try {
|
|
const current = backend.identity(handle, file);
|
|
if (current.filesystem !== 'NTFS' || current.dev !== expected.dev || current.ino !== expected.ino
|
|
|| (current.attributes & (0x1 | 0x10 | 0x400)) !== 0) throw new FixtureDeleteError('identity', file);
|
|
verify();
|
|
if (clock.now() >= deadline) throw firstSharing ?? new FixtureDeleteError('admission', file, undefined, true);
|
|
backend.dispose(handle, file);
|
|
} catch (error) { failed = true; failure = error; }
|
|
try { backend.close(handle, file); }
|
|
catch (error) {
|
|
if (failed) console.error(JSON.stringify({ nativeFixtureDeleteCloseFailure: {
|
|
stage: error instanceof FixtureDeleteError ? error.stage : 'close',
|
|
win32Error: error instanceof FixtureDeleteError ? error.win32Error : undefined,
|
|
} }));
|
|
else { failed = true; failure = error; }
|
|
}
|
|
if (failed) throw failure;
|
|
if (!backend.absent(file)) throw new FixtureDeleteError('absence', file);
|
|
return { admissionProbes, waitedMs };
|
|
}
|
|
|
|
export function createFixtureDeleteLease(deadline: number, onSharing?: () => void) {
|
|
if (process.platform !== 'win32') return { unlink: (file: string, _identity: Identity, _verify: () => void) => unlinkSync(file), close: () => {} };
|
|
const kernel = dlopen('kernel32.dll', {
|
|
CreateFileW: { args: [FFIType.ptr, FFIType.u32, FFIType.u32, FFIType.ptr, FFIType.u32, FFIType.u32, FFIType.u64], returns: FFIType.u64 },
|
|
GetFileInformationByHandle: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.i32 },
|
|
GetVolumeInformationByHandleW: { args: [FFIType.u64, FFIType.ptr, FFIType.u32, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.u32], returns: FFIType.i32 },
|
|
SetFileInformationByHandle: { args: [FFIType.u64, FFIType.i32, FFIType.ptr, FFIType.u32], returns: FFIType.i32 },
|
|
CloseHandle: { args: [FFIType.u64], returns: FFIType.i32 },
|
|
GetLastError: { args: [], returns: FFIType.u32 },
|
|
});
|
|
const backend: FixtureDeleteBackend = {
|
|
open(file) {
|
|
const name = Buffer.from(toNamespacedPath(file) + '\0', 'utf16le');
|
|
const handle = kernel.symbols.CreateFileW(ptr(name), 0x10080, 3, null, 3, 0x00200000, 0);
|
|
const error = kernel.symbols.GetLastError();
|
|
if (BigInt(handle) === 0xffffffffffffffffn || BigInt(handle) === 0n) throw new FixtureDeleteError('admission', file, error);
|
|
return handle;
|
|
},
|
|
identity(handle, file) {
|
|
const info = Buffer.alloc(52);
|
|
if (!kernel.symbols.GetFileInformationByHandle(handle, ptr(info))) throw new FixtureDeleteError('identity', file, kernel.symbols.GetLastError());
|
|
const filesystem = Buffer.alloc(128);
|
|
if (!kernel.symbols.GetVolumeInformationByHandleW(handle, null, 0, null, null, null, ptr(filesystem), 64)) throw new FixtureDeleteError('identity', file, kernel.symbols.GetLastError());
|
|
return { dev: BigInt(info.readUInt32LE(28)), ino: (BigInt(info.readUInt32LE(44)) << 32n) | BigInt(info.readUInt32LE(48)),
|
|
attributes: info.readUInt32LE(0), filesystem: filesystem.toString('utf16le').split('\0')[0] };
|
|
},
|
|
dispose(handle, file) {
|
|
const flags = Buffer.alloc(4);
|
|
flags.writeUInt32LE(3);
|
|
if (!kernel.symbols.SetFileInformationByHandle(handle, 21, ptr(flags), 4)) throw new FixtureDeleteError('disposition', file, kernel.symbols.GetLastError());
|
|
},
|
|
close(handle, file) {
|
|
if (!kernel.symbols.CloseHandle(handle)) throw new FixtureDeleteError('close', file, kernel.symbols.GetLastError());
|
|
},
|
|
absent(file) {
|
|
try { lstatSync(file); return false; }
|
|
catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return true; throw error; }
|
|
},
|
|
};
|
|
return {
|
|
unlink(file: string, identity: Identity, verify: () => void) {
|
|
if ((identity.mode & 0o170000n) !== 0o100000n) { verify(); unlinkSync(file); return; }
|
|
const receipt = deleteWithFixtureLease(file, identity, deadline, verify, backend, { ...leaseClock, onSharing });
|
|
if (receipt.admissionProbes > 1) console.log(JSON.stringify({ nativeFixtureSharingAdmission: {
|
|
objectDev: identity.dev.toString(), objectIno: identity.ino.toString(), ...receipt, dispositionCalls: 1,
|
|
} }));
|
|
},
|
|
close() { kernel.close(); },
|
|
};
|
|
}
|