mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-26 14:50:55 +02:00
* fix(settings): preserve symlinked settings targets
Resolve the selected target for locking, mutation, backup, and rollback; refuse target changes and preserve private modes. Addresses #2830.
* fix(redact): bind masking to original detected spans
Inspired by #2929's anchored-span diagnosis; independently implemented using normalization offsets. Addresses #2930 and the relocation portion of #2912 without changing detection sensitivity.
* fix(evals): exclude operator credentials from prefix admission
Adapts the credential-suffix screen proposed in #2636, with real launched-child regression coverage and deliberate provider-auth exceptions.
* fix(artifacts): retain custom allowlist rules on reinitialization
Preserve the exact user-owned suffix and publish only a successfully assembled replacement. Independently implements the repair reported in #2907.
* test(cso): verify exact masked reads and unmaskable payload refusal
* fix(cso): preserve exact filesystem identities through lease recovery
Preserve 64-bit device/inode identity and nanosecond race checks. Add native NTFS lifecycle coverage for #2927; retain ambiguous legacy-state refusal without claiming Windows PID-reuse recovery is resolved.
* fix(redact): bind pre-push scans to destination and preserve seam context
Uses #2935 (bd07318) as source evidence for push-target range and slice-overlap defects. Independently implemented; no cherry-pick or release metadata adoption.
* test(ci): gate native agent ownership and settings links on macOS
* fix(browse): bind agent lifetimes and cleanup to owned generations
Uses #2931 by Chris Hutton / Claude Fable 5.1 as attributed design input; independently implemented without broad sweeps or copied code. Keep uncertain children and locks rather than deleting foreign state.
* test(ci): include concurrent shutdown controls in the native macOS gate
* v1.88.1.0 fix: harden credential boundaries and owned state
* fix(redact): preserve target provenance and scan boundary semantics
* test(artifacts): read managed rules from atomic allowlist assembly
* fix: preserve native exit observations and fixture prerequisites
* fix: preserve UTF-16 offsets through redaction normalization
67 lines
3.6 KiB
TypeScript
67 lines
3.6 KiB
TypeScript
import { describe, expect, spyOn, test } from 'bun:test';
|
|
import * as childProcess from 'node:child_process';
|
|
import { isAgentRecordGone, isOurAgent, stopAgentByRecord } from '../src/terminal-agent-control';
|
|
|
|
describe('terminal-agent native exit observations', () => {
|
|
const pid = 2147483645;
|
|
const startTime = 'Tue Sep 22 23:39:21 2026';
|
|
const gen = 'test-observed-generation';
|
|
const ownedCommand = `bun run terminal-agent.ts --agent-gen=${gen}`;
|
|
const cases = [
|
|
{ name: 'Darwin zombie retains nonempty command text', command: '(bun)', state: 'Z', gone: true },
|
|
{ name: 'zombie retains its generation argument', command: ownedCommand, state: 'Z', gone: true },
|
|
{ name: 'process exits during start-time lookup', command: '', state: '', missingStart: true, reap: true, gone: true },
|
|
{ name: 'process exits during command lookup', command: '', state: '', reap: true, gone: true },
|
|
{ name: 'live process has a failed start-time lookup', command: '', state: 'S', missingStart: true, gone: false },
|
|
{ name: 'live process has a failed command lookup', command: '', state: 'S', gone: false },
|
|
{ name: 'live foreign generation is not ours', command: 'bun unrelated.ts', state: 'S', gone: false },
|
|
{ name: 'live owned generation remains ours', command: ownedCommand, state: 'S', gone: false, owned: true },
|
|
{ name: 'failed state probe cannot certify a zombie', command: '', state: 'Z', stateStatus: 1, gone: false },
|
|
{ name: 'process exits before the owned signal', command: ownedCommand, state: 'S', gone: false, owned: true, reapOnSignal: true },
|
|
];
|
|
|
|
for (const scenario of cases) {
|
|
test(scenario.name, () => {
|
|
const platform = Object.getOwnPropertyDescriptor(process, 'platform')!;
|
|
let live = true;
|
|
const signals: unknown[] = [];
|
|
const kill = spyOn(process, 'kill').mockImplementation(((target: number, signal: unknown) => {
|
|
expect(target).toBe(pid);
|
|
if (signal !== 0) {
|
|
signals.push(signal);
|
|
if (scenario.reapOnSignal) { live = false; throw Object.assign(new Error('gone'), { code: 'ESRCH' }); }
|
|
throw new Error('unexpected signal');
|
|
}
|
|
if (!live) throw Object.assign(new Error('gone'), { code: 'ESRCH' });
|
|
return true;
|
|
}) as typeof process.kill);
|
|
const probe = spyOn(Bun, 'spawnSync').mockImplementation(((command: string[]) => {
|
|
expect(command.slice(0, 3)).toEqual(['ps', '-p', String(pid)]);
|
|
const start = command[4] === 'lstart=';
|
|
if (scenario.reap && (!start || scenario.missingStart)) live = false;
|
|
return {
|
|
exitCode: start && scenario.missingStart ? 1 : 0,
|
|
stdout: Buffer.from(start ? scenario.missingStart ? '' : startTime : scenario.command),
|
|
stderr: Buffer.alloc(0),
|
|
};
|
|
}) as typeof Bun.spawnSync);
|
|
const state = spyOn(childProcess, 'spawnSync').mockReturnValue({
|
|
status: scenario.stateStatus ?? 0, stdout: scenario.state, stderr: '',
|
|
} as any);
|
|
Object.defineProperty(process, 'platform', { ...platform, value: 'darwin' });
|
|
try {
|
|
const record = { pid, gen, startTime, startedAt: 0, ownerPid: pid, ownerStartTime: startTime };
|
|
expect(isAgentRecordGone(record)).toBe(scenario.gone);
|
|
expect(isOurAgent(record)).toBe(scenario.owned ?? false);
|
|
if (scenario.gone || scenario.reapOnSignal) expect(stopAgentByRecord(record, 0)).toBe(true);
|
|
expect(signals).toEqual(scenario.reapOnSignal ? ['SIGTERM'] : []);
|
|
} finally {
|
|
Object.defineProperty(process, 'platform', platform);
|
|
state.mockRestore();
|
|
probe.mockRestore();
|
|
kill.mockRestore();
|
|
}
|
|
});
|
|
}
|
|
});
|