135 lines
4.8 KiB
TypeScript
135 lines
4.8 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
buildProcessBootstrapPendingDiagnostic,
|
|
buildProcessBootstrapTimeoutDiagnostic,
|
|
deriveProcessTransportProjectionPhase,
|
|
sanitizeProcessRuntimeEventFilePrefix,
|
|
summarizeProcessBootstrapTransportEvents,
|
|
} from '@main/services/team/ProcessBootstrapTransportEvidence';
|
|
|
|
describe('ProcessBootstrapTransportEvidence', () => {
|
|
it('keeps retryable submit rejection non-terminal when a later submit succeeds', () => {
|
|
const summary = summarizeProcessBootstrapTransportEvents([
|
|
{
|
|
type: 'runtime_ready',
|
|
timestamp: '2026-05-07T10:00:00.000Z',
|
|
detail: 'ready',
|
|
},
|
|
{
|
|
type: 'bootstrap_submit_rejected',
|
|
timestamp: '2026-05-07T10:00:01.000Z',
|
|
detail: 'temporary backoff',
|
|
retryable: true,
|
|
},
|
|
{
|
|
type: 'bootstrap_submitted',
|
|
timestamp: '2026-05-07T10:00:02.000Z',
|
|
detail: 'messageId=abc',
|
|
},
|
|
]);
|
|
|
|
expect(summary).toMatchObject({
|
|
submitted: true,
|
|
hasProgress: true,
|
|
});
|
|
expect(summary?.terminalFailure).toBeUndefined();
|
|
expect(summary?.lastStage).toContain('bootstrap submitted');
|
|
});
|
|
|
|
it('treats non-retryable submit rejection as terminal', () => {
|
|
const summary = summarizeProcessBootstrapTransportEvents([
|
|
{
|
|
type: 'bootstrap_submit_rejected',
|
|
timestamp: '2026-05-07T10:00:01.000Z',
|
|
detail: 'fatal submit rejection',
|
|
retryable: false,
|
|
},
|
|
]);
|
|
|
|
expect(summary?.terminalFailure).toMatchObject({
|
|
kind: 'non_retryable_submit_rejection',
|
|
reason: 'bootstrap submit rejected: fatal submit rejection',
|
|
});
|
|
});
|
|
|
|
it('treats accepted submit without a message id as terminal', () => {
|
|
const summary = summarizeProcessBootstrapTransportEvents([
|
|
{
|
|
type: 'bootstrap_submit_accepted_without_uuid',
|
|
timestamp: '2026-05-07T10:00:01.000Z',
|
|
detail: 'accepted but missing message id',
|
|
},
|
|
]);
|
|
|
|
expect(summary?.terminalFailure).toMatchObject({
|
|
kind: 'accepted_without_message_id',
|
|
reason: 'bootstrap submit accepted without message id: accepted but missing message id',
|
|
});
|
|
});
|
|
|
|
it('redacts secrets and paths from transport diagnostics', () => {
|
|
const summary = summarizeProcessBootstrapTransportEvents([
|
|
{
|
|
type: 'bootstrap_submit_rejected',
|
|
timestamp: '2026-05-07T10:00:01.000Z',
|
|
detail:
|
|
'failed in /Users/belief/dev/project with token sk-ant-api03-abcdefghijklmnopqrstuvwxyz',
|
|
retryable: false,
|
|
},
|
|
]);
|
|
|
|
expect(summary?.terminalFailure?.reason).toContain('[path]');
|
|
expect(summary?.terminalFailure?.reason).toContain('[redacted]');
|
|
expect(summary?.terminalFailure?.reason).not.toContain('/Users/belief');
|
|
expect(summary?.terminalFailure?.reason).not.toContain('sk-ant-api03');
|
|
});
|
|
|
|
it('does not surface raw command or cwd details for parent-owned process stages', () => {
|
|
const summary = summarizeProcessBootstrapTransportEvents([
|
|
{
|
|
type: 'process_spawned',
|
|
timestamp: '2026-05-07T10:00:01.000Z',
|
|
detail: 'spawned /Users/belief/project with command secret',
|
|
},
|
|
]);
|
|
|
|
expect(summary?.lastStage).toBe('process spawned');
|
|
});
|
|
|
|
it('builds stable pending and timeout diagnostics from the last transport stage', () => {
|
|
const summary = summarizeProcessBootstrapTransportEvents([
|
|
{
|
|
type: 'bootstrap_prompt_observed',
|
|
timestamp: '2026-05-07T10:00:01.000Z',
|
|
detail: 'prompt seen',
|
|
},
|
|
]);
|
|
|
|
expect(summary).not.toBeNull();
|
|
expect(buildProcessBootstrapPendingDiagnostic(summary!)).toBe(
|
|
'Bootstrap transport reached bootstrap prompt observed: prompt seen; waiting for bootstrap confirmation.'
|
|
);
|
|
expect(buildProcessBootstrapTimeoutDiagnostic(summary!)).toBe(
|
|
'Teammate was registered but did not bootstrap-confirm before timeout. Last transport stage: bootstrap prompt observed: prompt seen'
|
|
);
|
|
});
|
|
|
|
it('keeps active phase pending and turns final timeout into final projection', () => {
|
|
expect(deriveProcessTransportProjectionPhase({ launchPhase: 'active' })).toBe('active');
|
|
expect(
|
|
deriveProcessTransportProjectionPhase({
|
|
launchPhase: 'active',
|
|
finalTimeoutReached: true,
|
|
})
|
|
).toBe('final');
|
|
expect(deriveProcessTransportProjectionPhase({ launchPhase: 'finished' })).toBe('final');
|
|
});
|
|
|
|
it('matches orchestrator runtime-event filename sanitization for important names', () => {
|
|
expect(sanitizeProcessRuntimeEventFilePrefix('jack')).toBe('jack');
|
|
expect(sanitizeProcessRuntimeEventFilePrefix('con.txt')).toBe('con-txt');
|
|
expect(sanitizeProcessRuntimeEventFilePrefix('CON')).toBe('_con');
|
|
expect(sanitizeProcessRuntimeEventFilePrefix('alice/bob')).toBe('alice-bob');
|
|
});
|
|
});
|