agent-ecosystem/test/main/services/team/TeamProvisioningLaunchDiagnostics.test.ts

457 lines
14 KiB
TypeScript

import {
buildLaunchDiagnosticsFromRun,
buildWorkspaceTrustPreflightLaunchDiagnostic,
mentionsProcessTableUnavailable,
mergeLaunchDiagnosticItem,
} from '@main/services/team/provisioning/TeamProvisioningLaunchDiagnostics';
import { describe, expect, it } from 'vitest';
import type { WorkspaceTrustExecutionResult } from '@features/workspace-trust/main';
import type { MemberSpawnStatusEntry, TeamLaunchDiagnosticItem } from '@shared/types';
const NOW = '2026-05-24T00:00:00.000Z';
const nowIso = () => NOW;
function spawnEntry(overrides: Partial<MemberSpawnStatusEntry>): MemberSpawnStatusEntry {
return {
status: 'waiting',
launchState: 'starting',
updatedAt: NOW,
...overrides,
};
}
function buildRun(entries: [string, Partial<MemberSpawnStatusEntry>][], isLaunch = true) {
return {
isLaunch,
memberSpawnStatuses: new Map(
entries.map(([memberName, entry]) => [memberName, spawnEntry(entry)])
),
};
}
function workspaceTrustExecution(
overrides: Partial<WorkspaceTrustExecutionResult>
): WorkspaceTrustExecutionResult {
return {
id: 'claude-pty-workspace-trust',
provider: 'claude',
status: 'ok',
workspaceIds: ['workspace-1'],
...overrides,
};
}
describe('TeamProvisioningLaunchDiagnostics', () => {
it('skips non-launch and empty launch runs', () => {
expect(buildLaunchDiagnosticsFromRun(buildRun([], false), { nowIso })).toBeUndefined();
expect(buildLaunchDiagnosticsFromRun(buildRun([]), { nowIso })).toBeUndefined();
expect(
buildLaunchDiagnosticsFromRun({ isLaunch: true, memberSpawnStatuses: undefined }, { nowIso })
).toBeUndefined();
});
it('projects member spawn status entries into launch diagnostics without changing priority order', () => {
const diagnostics = buildLaunchDiagnosticsFromRun(
buildRun([
['Lead', { launchState: 'confirmed_alive' }],
[
'WorkerA',
{
launchState: 'failed_to_start',
error: 'fallback error',
hardFailureReason: 'hard failure reason',
agentToolAccepted: true,
},
],
[
'WorkerB',
{
launchState: 'runtime_pending_permission',
runtimeDiagnostic: 'waiting for user approval',
},
],
[
'WorkerC',
{
bootstrapStalled: true,
runtimeDiagnostic: 'bootstrap deadline exceeded',
livenessKind: 'runtime_process',
},
],
[
'WorkerD',
{
runtimeDiagnostic: 'process table temporarily unavailable',
livenessKind: 'shell_only',
},
],
['WorkerE', { livenessKind: 'shell_only', runtimeDiagnostic: 'tmux pane only' }],
[
'WorkerF',
{
livenessKind: 'runtime_process_candidate',
runtimeDiagnostic: 'process found but bootstrap missing',
},
],
['WorkerG', { livenessKind: 'runtime_process', runtimeDiagnostic: 'process found' }],
['WorkerH', { livenessKind: 'registered_only', runtimeDiagnostic: 'registered only' }],
['WorkerI', { livenessKind: 'stale_metadata', runtimeDiagnostic: 'stale metadata' }],
['WorkerJ', { livenessKind: 'not_found', runtimeDiagnostic: 'no runtime' }],
['WorkerK', { agentToolAccepted: true, runtimeDiagnostic: 'spawn accepted' }],
['WorkerL', {}],
]),
{ nowIso }
);
expect(diagnostics).toEqual([
{
id: 'Lead:bootstrap_confirmed',
memberName: 'Lead',
severity: 'info',
code: 'bootstrap_confirmed',
label: 'Lead - bootstrap confirmed',
observedAt: NOW,
},
{
id: 'WorkerA:bootstrap_stalled',
memberName: 'WorkerA',
severity: 'error',
code: 'bootstrap_stalled',
label: 'WorkerA - failed to start',
detail: 'hard failure reason',
observedAt: NOW,
},
{
id: 'WorkerB:permission_pending',
memberName: 'WorkerB',
severity: 'warning',
code: 'permission_pending',
label: 'WorkerB - awaiting permission',
detail: 'waiting for user approval',
observedAt: NOW,
},
{
id: 'WorkerC:bootstrap_stalled',
memberName: 'WorkerC',
severity: 'warning',
code: 'bootstrap_stalled',
label: 'WorkerC - bootstrap stalled',
detail: 'bootstrap deadline exceeded',
observedAt: NOW,
},
{
id: 'WorkerD:process_table_unavailable',
memberName: 'WorkerD',
severity: 'warning',
code: 'process_table_unavailable',
label: 'WorkerD - process table unavailable',
detail: 'process table temporarily unavailable',
observedAt: NOW,
},
{
id: 'WorkerE:tmux_shell_only',
memberName: 'WorkerE',
severity: 'warning',
code: 'tmux_shell_only',
label: 'WorkerE - shell only',
detail: 'tmux pane only',
observedAt: NOW,
},
{
id: 'WorkerF:runtime_process_candidate',
memberName: 'WorkerF',
severity: 'warning',
code: 'runtime_process_candidate',
label: 'WorkerF - bootstrap unconfirmed',
detail: 'process found but bootstrap missing',
observedAt: NOW,
},
{
id: 'WorkerG:runtime_process_detected',
memberName: 'WorkerG',
severity: 'info',
code: 'runtime_process_detected',
label: 'WorkerG - waiting for bootstrap',
detail: 'process found',
observedAt: NOW,
},
{
id: 'WorkerH:runtime_not_found',
memberName: 'WorkerH',
severity: 'warning',
code: 'runtime_not_found',
label: 'WorkerH - waiting for runtime',
detail: 'registered only',
observedAt: NOW,
},
{
id: 'WorkerI:runtime_not_found',
memberName: 'WorkerI',
severity: 'warning',
code: 'runtime_not_found',
label: 'WorkerI - waiting for runtime',
detail: 'stale metadata',
observedAt: NOW,
},
{
id: 'WorkerJ:runtime_not_found',
memberName: 'WorkerJ',
severity: 'warning',
code: 'runtime_not_found',
label: 'WorkerJ - waiting for runtime',
detail: 'no runtime',
observedAt: NOW,
},
{
id: 'WorkerK:spawn_accepted',
memberName: 'WorkerK',
severity: 'info',
code: 'spawn_accepted',
label: 'WorkerK - spawn accepted',
detail: 'spawn accepted',
observedAt: NOW,
},
]);
});
it('classifies bootstrap-confirmed provisioned-but-not-alive entries as confirmed', () => {
const diagnostics = buildLaunchDiagnosticsFromRun(
buildRun([
[
'tom',
{
status: 'error',
launchState: 'failed_to_start',
bootstrapConfirmed: true,
hardFailure: true,
hardFailureReason: 'CLI process exited (code 1) \u2014 team provisioned but not alive',
livenessKind: 'confirmed_bootstrap',
runtimeDiagnostic:
'runtime pid could not be verified because process table is unavailable',
runtimeDiagnosticSeverity: 'warning',
},
],
]),
{ nowIso }
);
expect(diagnostics).toEqual([
{
id: 'tom:bootstrap_confirmed',
memberName: 'tom',
severity: 'info',
code: 'bootstrap_confirmed',
label: 'tom - bootstrap confirmed',
observedAt: NOW,
},
]);
});
it('classifies process-table-unavailable registered metadata as confirmed', () => {
const diagnostics = buildLaunchDiagnosticsFromRun(
buildRun([
[
'tom',
{
status: 'error',
launchState: 'failed_to_start',
bootstrapConfirmed: true,
hardFailure: true,
hardFailureReason:
'CLI process exited (code 1) - team provisioned but not alive; process table unavailable',
livenessKind: 'registered_only',
runtimeDiagnostic:
'runtime pid could not be verified because process table is unavailable',
runtimeDiagnosticSeverity: 'warning',
},
],
]),
{ nowIso }
);
expect(diagnostics).toEqual([
{
id: 'tom:bootstrap_confirmed',
memberName: 'tom',
severity: 'info',
code: 'bootstrap_confirmed',
label: 'tom - bootstrap confirmed',
observedAt: NOW,
},
]);
});
it('keeps error diagnostics for bootstrap-confirmed provisioned-but-not-alive entries', () => {
const diagnostics = buildLaunchDiagnosticsFromRun(
buildRun([
[
'tom',
{
status: 'error',
launchState: 'failed_to_start',
bootstrapConfirmed: true,
hardFailure: true,
hardFailureReason: 'CLI process exited (code 1) - team provisioned but not alive',
livenessKind: 'confirmed_bootstrap',
runtimeDiagnostic: 'Runtime process crashed',
runtimeDiagnosticSeverity: 'error',
},
],
]),
{ nowIso }
);
expect(diagnostics).toEqual([
{
id: 'tom:bootstrap_stalled',
memberName: 'tom',
severity: 'error',
code: 'bootstrap_stalled',
label: 'tom - launch diagnostic error',
detail: 'Runtime process crashed',
observedAt: NOW,
},
]);
});
it('keeps stopped liveness diagnostics for bootstrap-confirmed provisioned-but-not-alive entries', () => {
const diagnostics = buildLaunchDiagnosticsFromRun(
buildRun([
[
'tom',
{
status: 'error',
launchState: 'failed_to_start',
bootstrapConfirmed: true,
hardFailure: true,
hardFailureReason: 'CLI process exited (code 1) - team provisioned but not alive',
livenessKind: 'not_found',
runtimeDiagnostic: 'Runtime is no longer registered',
runtimeDiagnosticSeverity: 'warning',
},
],
]),
{ nowIso }
);
expect(diagnostics).toEqual([
{
id: 'tom:bootstrap_stalled',
memberName: 'tom',
severity: 'error',
code: 'bootstrap_stalled',
label: 'tom - launch diagnostic error',
detail: 'Runtime is no longer registered',
observedAt: NOW,
},
]);
});
it('uses failed launch error when hard failure reason is absent', () => {
expect(
buildLaunchDiagnosticsFromRun(
buildRun([['Worker', { launchState: 'failed_to_start', error: 'spawn failed' }]]),
{ nowIso }
)?.[0]
).toMatchObject({
id: 'Worker:bootstrap_stalled',
detail: 'spawn failed',
});
});
it('recognizes process table unavailable diagnostics case-insensitively', () => {
expect(mentionsProcessTableUnavailable('Process table is currently unavailable')).toBe(true);
expect(mentionsProcessTableUnavailable('process runtime unavailable')).toBe(false);
expect(mentionsProcessTableUnavailable(undefined)).toBe(false);
});
it('builds workspace trust launch diagnostics for blocked, soft-failed, and completed preflight results', () => {
expect(
buildWorkspaceTrustPreflightLaunchDiagnostic(
workspaceTrustExecution({
status: 'blocked',
errorCode: 'workspace_trust_required',
errorMessage: ' trust must be accepted ',
evidence: ['fallback evidence'],
}),
{ nowIso }
)
).toEqual({
id: 'workspace-trust:preflight',
severity: 'error',
code: 'workspace_trust_preflight',
label: 'Workspace trust preflight blocked launch',
detail: 'trust must be accepted',
observedAt: NOW,
});
expect(
buildWorkspaceTrustPreflightLaunchDiagnostic(
workspaceTrustExecution({
status: 'soft_failed',
errorCode: 'workspace_trust_probe_failed',
evidence: ['fallback evidence'],
}),
{ nowIso }
)
).toMatchObject({
severity: 'warning',
label: 'Workspace trust preflight could not verify trust',
detail: 'workspace_trust_probe_failed',
});
expect(
buildWorkspaceTrustPreflightLaunchDiagnostic(
workspaceTrustExecution({
status: 'ok',
evidence: [' ', ' trusted from state probe '],
}),
{ nowIso }
)
).toMatchObject({
severity: 'info',
label: 'Workspace trust preflight completed',
detail: 'trusted from state probe',
});
});
it('omits cancelled workspace trust launch diagnostics', () => {
expect(
buildWorkspaceTrustPreflightLaunchDiagnostic(
workspaceTrustExecution({ status: 'cancelled' }),
{ nowIso }
)
).toBeNull();
});
it('merges launch diagnostic items by id without mutating existing diagnostics', () => {
const existing: TeamLaunchDiagnosticItem[] = [
{
id: 'old',
severity: 'info',
code: 'spawn_accepted',
label: 'Old',
observedAt: NOW,
},
{
id: 'same',
severity: 'warning',
code: 'runtime_not_found',
label: 'Previous',
observedAt: NOW,
},
];
const replacement: TeamLaunchDiagnosticItem = {
id: 'same',
severity: 'error',
code: 'bootstrap_stalled',
label: 'Replacement',
observedAt: NOW,
};
expect(mergeLaunchDiagnosticItem(existing, replacement)).toEqual([existing[0], replacement]);
expect(existing[1].label).toBe('Previous');
expect(mergeLaunchDiagnosticItem(undefined, replacement)).toEqual([replacement]);
});
});