import { describe, expect, it } from 'vitest'; import { choosePreferredLaunchStateSummary, createPersistedLaunchSummaryProjection, shouldSuppressLegacyLaunchArtifactHeuristic, } from '../../../../src/main/services/team/TeamLaunchSummaryProjection'; describe('TeamLaunchSummaryProjection', () => { it('ignores stale terminal bootstrap-only pending summaries when canonical launch truth is missing', () => { const summary = choosePreferredLaunchStateSummary({ bootstrapSnapshot: { version: 2, teamName: 'atlas-hq-2', updatedAt: '2026-04-09T20:35:57.962Z', launchPhase: 'finished', expectedMembers: ['alice', 'jack'], members: { alice: { name: 'alice', launchState: 'runtime_pending_bootstrap', agentToolAccepted: true, runtimeAlive: false, bootstrapConfirmed: false, hardFailure: false, lastEvaluatedAt: '2026-04-09T20:35:57.962Z', }, jack: { name: 'jack', launchState: 'runtime_pending_bootstrap', agentToolAccepted: true, runtimeAlive: false, bootstrapConfirmed: false, hardFailure: false, lastEvaluatedAt: '2026-04-09T20:35:57.962Z', }, }, summary: { confirmedCount: 0, pendingCount: 2, failedCount: 0, runtimeAlivePendingCount: 0, }, teamLaunchState: 'partial_pending', } as never, launchSummaryProjection: null, }); expect(summary).toBeNull(); }); it('ignores stale terminal launch-state pending summaries before projecting renderer copy', () => { const summary = choosePreferredLaunchStateSummary({ launchSnapshot: { version: 2, teamName: 'atlas-hq-2', updatedAt: '2026-04-09T20:35:57.962Z', launchPhase: 'finished', expectedMembers: ['alice', 'jack'], members: { alice: { name: 'alice', launchState: 'runtime_pending_bootstrap', agentToolAccepted: true, runtimeAlive: false, bootstrapConfirmed: false, hardFailure: false, lastEvaluatedAt: '2026-04-09T20:35:57.962Z', }, jack: { name: 'jack', launchState: 'runtime_pending_bootstrap', agentToolAccepted: true, runtimeAlive: false, bootstrapConfirmed: false, hardFailure: false, lastEvaluatedAt: '2026-04-09T20:35:57.962Z', }, }, summary: { confirmedCount: 0, pendingCount: 2, failedCount: 0, runtimeAlivePendingCount: 0, }, teamLaunchState: 'partial_pending', } as never, bootstrapSnapshot: null, launchSummaryProjection: null, }); expect(summary).toBeNull(); }); it('ignores stale active launch-state pending summaries without outstanding permissions', () => { const summary = choosePreferredLaunchStateSummary({ launchSnapshot: { version: 2, teamName: 'vector-room-13', updatedAt: '2026-04-09T20:35:57.962Z', launchPhase: 'active', expectedMembers: ['alice', 'bob'], members: { alice: { name: 'alice', launchState: 'starting', agentToolAccepted: false, runtimeAlive: false, bootstrapConfirmed: false, hardFailure: false, lastEvaluatedAt: '2026-04-09T20:35:57.962Z', }, bob: { name: 'bob', launchState: 'runtime_pending_bootstrap', agentToolAccepted: true, runtimeAlive: false, bootstrapConfirmed: false, hardFailure: false, lastEvaluatedAt: '2026-04-09T20:35:57.962Z', }, }, summary: { confirmedCount: 0, pendingCount: 2, failedCount: 0, runtimeAlivePendingCount: 0, permissionPendingCount: 0, }, teamLaunchState: 'partial_pending', } as never, launchSummaryProjection: null, }); expect(summary).toBeNull(); }); it('keeps stale launch-state pending summaries when permission approval is outstanding', () => { const summary = choosePreferredLaunchStateSummary({ launchSnapshot: { version: 2, teamName: 'permission-team', updatedAt: '2026-04-09T20:35:57.962Z', launchPhase: 'active', expectedMembers: ['alice'], members: { alice: { name: 'alice', launchState: 'permission_pending', agentToolAccepted: false, runtimeAlive: false, bootstrapConfirmed: false, hardFailure: false, lastEvaluatedAt: '2026-04-09T20:35:57.962Z', }, }, summary: { confirmedCount: 0, pendingCount: 1, failedCount: 0, runtimeAlivePendingCount: 0, permissionPendingCount: 1, }, teamLaunchState: 'partial_pending', } as never, launchSummaryProjection: null, }); expect(summary).toMatchObject({ teamLaunchState: 'partial_pending', permissionPendingCount: 1, }); }); it('ignores stale pending launch-summary projections when canonical launch truth is missing', () => { const summary = choosePreferredLaunchStateSummary({ launchSummaryProjection: { version: 1, teamName: 'atlas-hq-2', updatedAt: '2026-04-09T20:35:57.962Z', launchUpdatedAt: '2026-04-09T20:35:57.962Z', teamLaunchState: 'partial_pending', expectedMemberCount: 2, confirmedCount: 0, pendingCount: 2, failedCount: 0, runtimeProcessPendingCount: 0, permissionPendingCount: 0, }, }); expect(summary).toBeNull(); }); it('ignores stale active pending launch-summary projections without outstanding permissions', () => { const summary = choosePreferredLaunchStateSummary({ launchSummaryProjection: { version: 1, teamName: 'vector-room-13', updatedAt: '2026-04-09T20:35:57.962Z', launchUpdatedAt: '2026-04-09T20:35:57.962Z', launchPhase: 'active', teamLaunchState: 'partial_pending', expectedMemberCount: 2, confirmedCount: 0, pendingCount: 2, failedCount: 0, runtimeProcessPendingCount: 0, permissionPendingCount: 0, }, }); expect(summary).toBeNull(); }); it('keeps stale pending launch-summary projections when permission approval is outstanding', () => { const summary = choosePreferredLaunchStateSummary({ launchSummaryProjection: { version: 1, teamName: 'permission-team', updatedAt: '2026-04-09T20:35:57.962Z', launchUpdatedAt: '2026-04-09T20:35:57.962Z', teamLaunchState: 'partial_pending', expectedMemberCount: 1, confirmedCount: 0, pendingCount: 1, failedCount: 0, permissionPendingCount: 1, }, }); expect(summary).toMatchObject({ teamLaunchState: 'partial_pending', permissionPendingCount: 1, }); }); it('projects provisioned-but-not-alive failures with bootstrap proof as confirmed', () => { const summary = choosePreferredLaunchStateSummary({ launchSnapshot: { version: 2, teamName: 'signal-ops', updatedAt: '2026-05-25T20:14:02.147Z', launchPhase: 'finished', expectedMembers: ['tom'], members: { tom: { name: 'tom', providerId: 'anthropic', launchState: 'failed_to_start', agentToolAccepted: true, runtimeAlive: false, 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', firstSpawnAcceptedAt: '2026-05-25T20:13:46.326Z', lastHeartbeatAt: '2026-05-25T20:13:56.110Z', lastEvaluatedAt: '2026-05-25T20:14:02.147Z', }, }, summary: { confirmedCount: 0, pendingCount: 0, failedCount: 1, runtimeAlivePendingCount: 0, }, teamLaunchState: 'partial_failure', } as never, }); expect(summary).toMatchObject({ teamLaunchState: 'clean_success', confirmedMemberCount: 1, confirmedCount: 1, pendingCount: 0, failedCount: 0, }); expect(summary).not.toMatchObject({ partialLaunchFailure: true, missingMembers: ['tom'], }); }); it('projects Windows process-table-unavailable provisioned-but-not-alive metadata as confirmed', () => { const summary = choosePreferredLaunchStateSummary({ launchSnapshot: { version: 2, teamName: 'signal-ops', updatedAt: '2026-05-25T20:14:02.147Z', launchPhase: 'finished', expectedMembers: ['tom'], members: { tom: { name: 'tom', providerId: 'anthropic', launchState: 'failed_to_start', agentToolAccepted: true, runtimeAlive: false, 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', firstSpawnAcceptedAt: '2026-05-25T20:13:46.326Z', lastHeartbeatAt: '2026-05-25T20:13:56.110Z', lastEvaluatedAt: '2026-05-25T20:14:02.147Z', }, }, summary: { confirmedCount: 0, pendingCount: 0, failedCount: 1, runtimeAlivePendingCount: 0, }, teamLaunchState: 'partial_failure', } as never, }); expect(summary).toMatchObject({ teamLaunchState: 'clean_success', confirmedMemberCount: 1, confirmedCount: 1, failedCount: 0, }); }); it('keeps provisioned-but-not-alive failures with runtime error evidence as failed', () => { const summary = choosePreferredLaunchStateSummary({ launchSnapshot: { version: 2, teamName: 'signal-ops', updatedAt: '2026-05-25T20:14:02.147Z', launchPhase: 'finished', expectedMembers: ['tom'], members: { tom: { name: 'tom', providerId: 'anthropic', launchState: 'failed_to_start', agentToolAccepted: true, runtimeAlive: false, bootstrapConfirmed: true, hardFailure: true, hardFailureReason: 'CLI process exited (code 1) - team provisioned but not alive', livenessKind: 'confirmed_bootstrap', runtimeDiagnostic: 'Runtime process crashed', runtimeDiagnosticSeverity: 'error', lastEvaluatedAt: '2026-05-25T20:14:02.147Z', }, }, summary: { confirmedCount: 0, pendingCount: 0, failedCount: 1, runtimeAlivePendingCount: 0, }, teamLaunchState: 'partial_failure', } as never, }); expect(summary).toMatchObject({ partialLaunchFailure: true, missingMembers: ['tom'], teamLaunchState: 'partial_failure', confirmedCount: 0, failedCount: 1, }); }); it('reconciles unhealed launch-summary projections with bootstrap proof', () => { const summary = choosePreferredLaunchStateSummary({ bootstrapSnapshot: { version: 2, teamName: 'signal-ops', updatedAt: '2026-05-25T20:13:56.110Z', launchPhase: 'finished', expectedMembers: ['tom'], members: { tom: { name: 'tom', providerId: 'anthropic', launchState: 'confirmed_alive', agentToolAccepted: true, runtimeAlive: true, bootstrapConfirmed: true, hardFailure: false, livenessKind: 'confirmed_bootstrap', firstSpawnAcceptedAt: '2026-05-25T20:13:46.326Z', lastHeartbeatAt: '2026-05-25T20:13:56.110Z', lastEvaluatedAt: '2026-05-25T20:13:56.110Z', }, }, summary: { confirmedCount: 1, pendingCount: 0, failedCount: 0, runtimeAlivePendingCount: 0, }, teamLaunchState: 'clean_success', } as never, launchSummaryProjection: { version: 1, teamName: 'signal-ops', updatedAt: '2026-05-25T20:14:02.147Z', launchUpdatedAt: '2026-05-25T20:14:02.147Z', teamLaunchState: 'partial_failure', partialLaunchFailure: true, expectedMemberCount: 1, confirmedMemberCount: 0, missingMembers: ['tom'], confirmedCount: 0, pendingCount: 0, failedCount: 1, runtimeAlivePendingCount: 0, }, }); expect(summary).toMatchObject({ teamLaunchState: 'clean_success', confirmedMemberCount: 1, confirmedCount: 1, failedCount: 0, pendingCount: 0, }); expect(summary).not.toMatchObject({ partialLaunchFailure: true, missingMembers: ['tom'], }); }); it('does not reconcile launch-summary projections from stale bootstrap proof', () => { const summary = choosePreferredLaunchStateSummary({ bootstrapSnapshot: { version: 2, teamName: 'signal-ops', updatedAt: '2026-05-25T20:10:10.000Z', launchPhase: 'finished', expectedMembers: ['tom'], members: { tom: { name: 'tom', providerId: 'anthropic', launchState: 'confirmed_alive', agentToolAccepted: true, runtimeAlive: true, bootstrapConfirmed: true, hardFailure: false, livenessKind: 'confirmed_bootstrap', firstSpawnAcceptedAt: '2026-05-25T20:10:00.000Z', lastHeartbeatAt: '2026-05-25T20:10:05.000Z', lastEvaluatedAt: '2026-05-25T20:10:10.000Z', }, }, summary: { confirmedCount: 1, pendingCount: 0, failedCount: 0, runtimeAlivePendingCount: 0, }, teamLaunchState: 'clean_success', } as never, launchSummaryProjection: { version: 1, teamName: 'signal-ops', updatedAt: '2026-05-25T20:14:02.147Z', launchUpdatedAt: '2026-05-25T20:14:02.147Z', teamLaunchState: 'partial_failure', partialLaunchFailure: true, expectedMemberCount: 1, confirmedMemberCount: 0, missingMembers: ['tom'], confirmedCount: 0, pendingCount: 0, failedCount: 1, runtimeAlivePendingCount: 0, }, }); expect(summary).toMatchObject({ partialLaunchFailure: true, missingMembers: ['tom'], teamLaunchState: 'partial_failure', confirmedCount: 0, failedCount: 1, }); }); it('does not reconcile launch-summary projections from stopped bootstrap proof', () => { const summary = choosePreferredLaunchStateSummary({ bootstrapSnapshot: { version: 2, teamName: 'signal-ops', updatedAt: '2026-05-25T20:13:56.110Z', launchPhase: 'finished', expectedMembers: ['tom'], members: { tom: { name: 'tom', providerId: 'anthropic', launchState: 'confirmed_alive', agentToolAccepted: true, runtimeAlive: false, bootstrapConfirmed: true, hardFailure: false, livenessKind: 'not_found', firstSpawnAcceptedAt: '2026-05-25T20:13:46.326Z', lastEvaluatedAt: '2026-05-25T20:13:56.110Z', }, }, summary: { confirmedCount: 1, pendingCount: 0, failedCount: 0, runtimeAlivePendingCount: 0, }, teamLaunchState: 'clean_success', } as never, launchSummaryProjection: { version: 1, teamName: 'signal-ops', updatedAt: '2026-05-25T20:14:02.147Z', launchUpdatedAt: '2026-05-25T20:14:02.147Z', teamLaunchState: 'partial_failure', partialLaunchFailure: true, expectedMemberCount: 1, confirmedMemberCount: 0, missingMembers: ['tom'], confirmedCount: 0, pendingCount: 0, failedCount: 1, runtimeAlivePendingCount: 0, }, }); expect(summary).toMatchObject({ partialLaunchFailure: true, missingMembers: ['tom'], teamLaunchState: 'partial_failure', confirmedCount: 0, failedCount: 1, }); }); it('keeps provisioned-but-not-alive failures without bootstrap proof as failed', () => { const summary = choosePreferredLaunchStateSummary({ launchSnapshot: { version: 2, teamName: 'signal-ops', updatedAt: '2026-05-25T20:14:02.147Z', launchPhase: 'finished', expectedMembers: ['tom'], members: { tom: { name: 'tom', providerId: 'anthropic', launchState: 'failed_to_start', agentToolAccepted: true, runtimeAlive: false, bootstrapConfirmed: false, hardFailure: true, hardFailureReason: 'CLI process exited (code 1) \u2014 team provisioned but not alive', lastEvaluatedAt: '2026-05-25T20:14:02.147Z', }, }, summary: { confirmedCount: 0, pendingCount: 0, failedCount: 1, runtimeAlivePendingCount: 0, }, teamLaunchState: 'partial_failure', } as never, }); expect(summary).toMatchObject({ partialLaunchFailure: true, missingMembers: ['tom'], teamLaunchState: 'partial_failure', confirmedCount: 0, failedCount: 1, }); }); it('does not project provisioned-but-not-alive from stale bootstrap proof before spawn acceptance', () => { const summary = choosePreferredLaunchStateSummary({ bootstrapSnapshot: { version: 2, teamName: 'signal-ops', updatedAt: '2026-05-25T20:10:10.000Z', launchPhase: 'finished', expectedMembers: ['tom'], members: { tom: { name: 'tom', providerId: 'anthropic', launchState: 'confirmed_alive', agentToolAccepted: true, runtimeAlive: true, bootstrapConfirmed: true, hardFailure: false, firstSpawnAcceptedAt: '2026-05-25T20:10:00.000Z', lastHeartbeatAt: '2026-05-25T20:10:05.000Z', lastEvaluatedAt: '2026-05-25T20:10:10.000Z', }, }, summary: { confirmedCount: 1, pendingCount: 0, failedCount: 0, runtimeAlivePendingCount: 0, }, teamLaunchState: 'clean_success', } as never, launchSnapshot: { version: 2, teamName: 'signal-ops', updatedAt: '2026-05-25T20:14:02.147Z', launchPhase: 'finished', expectedMembers: ['tom'], members: { tom: { name: 'tom', providerId: 'anthropic', launchState: 'failed_to_start', agentToolAccepted: true, runtimeAlive: false, bootstrapConfirmed: false, hardFailure: true, hardFailureReason: 'CLI process exited (code 1) \u2014 team provisioned but not alive', firstSpawnAcceptedAt: '2026-05-25T20:13:46.326Z', lastEvaluatedAt: '2026-05-25T20:14:02.147Z', }, }, summary: { confirmedCount: 0, pendingCount: 0, failedCount: 1, runtimeAlivePendingCount: 0, }, teamLaunchState: 'partial_failure', } as never, }); expect(summary).toMatchObject({ partialLaunchFailure: true, missingMembers: ['tom'], teamLaunchState: 'partial_failure', confirmedCount: 0, failedCount: 1, }); }); it('prefers a mixed-aware persisted summary projection over a newer but poorer bootstrap snapshot', () => { const bootstrapSnapshot = { version: 2, teamName: 'mixed-team', updatedAt: '2026-04-22T12:05:00.000Z', launchPhase: 'active', expectedMembers: ['alice'], members: { alice: { name: 'alice', providerId: 'codex', launchState: 'starting', agentToolAccepted: false, runtimeAlive: false, bootstrapConfirmed: false, hardFailure: false, lastEvaluatedAt: '2026-04-22T12:05:00.000Z', }, }, summary: { confirmedCount: 0, pendingCount: 1, failedCount: 0, runtimeAlivePendingCount: 0, }, teamLaunchState: 'partial_pending', } as const; const mixedSnapshot = { version: 2, teamName: 'mixed-team', updatedAt: '2026-04-22T12:00:00.000Z', launchPhase: 'finished', expectedMembers: ['alice', 'bob'], bootstrapExpectedMembers: ['alice'], members: { alice: { name: 'alice', providerId: 'codex', laneId: 'primary', laneKind: 'primary', laneOwnerProviderId: 'codex', launchState: 'confirmed_alive', agentToolAccepted: true, runtimeAlive: true, bootstrapConfirmed: true, hardFailure: false, lastEvaluatedAt: '2026-04-22T12:00:00.000Z', }, bob: { name: 'bob', providerId: 'opencode', laneId: 'secondary:opencode:bob', laneKind: 'secondary', laneOwnerProviderId: 'opencode', launchState: 'failed_to_start', agentToolAccepted: true, runtimeAlive: false, bootstrapConfirmed: false, hardFailure: true, hardFailureReason: 'Side lane failed', lastEvaluatedAt: '2026-04-22T12:00:00.000Z', }, }, summary: { confirmedCount: 1, pendingCount: 0, failedCount: 1, runtimeAlivePendingCount: 0, }, teamLaunchState: 'partial_failure', } as const; const summary = choosePreferredLaunchStateSummary({ bootstrapSnapshot: bootstrapSnapshot as never, launchSummaryProjection: createPersistedLaunchSummaryProjection(mixedSnapshot as never), }); expect(summary).toMatchObject({ partialLaunchFailure: true, expectedMemberCount: 2, confirmedMemberCount: 1, missingMembers: ['bob'], teamLaunchState: 'partial_failure', confirmedCount: 1, pendingCount: 0, failedCount: 1, }); }); it('suppresses legacy artifact-count launch heuristics for mixed-aware desired rosters', () => { expect( shouldSuppressLegacyLaunchArtifactHeuristic({ leadProviderId: 'codex', members: [ { name: 'alice', providerId: 'codex' }, { name: 'tom', providerId: 'opencode' }, ], }) ).toBe(true); expect( shouldSuppressLegacyLaunchArtifactHeuristic({ leadProviderId: 'opencode', members: [{ name: 'alice', providerId: 'codex' }], }) ).toBe(true); expect( shouldSuppressLegacyLaunchArtifactHeuristic({ leadProviderId: 'codex', members: [ { name: 'alice', providerId: 'opencode' }, { name: 'tom', providerId: 'opencode' }, ], }) ).toBe(true); expect( shouldSuppressLegacyLaunchArtifactHeuristic({ leadProviderId: 'codex', members: [{ name: 'alice', providerId: 'codex' }], }) ).toBe(false); }); it('uses the union of expectedMembers and persisted members for summary projection', () => { const summary = createPersistedLaunchSummaryProjection({ version: 2, teamName: 'mixed-team', updatedAt: '2026-04-22T12:00:00.000Z', launchPhase: 'finished', expectedMembers: ['alice'], members: { alice: { name: 'alice', providerId: 'codex', launchState: 'confirmed_alive', agentToolAccepted: true, runtimeAlive: true, bootstrapConfirmed: true, hardFailure: false, lastEvaluatedAt: '2026-04-22T12:00:00.000Z', }, bob: { name: 'bob', providerId: 'opencode', launchState: 'failed_to_start', agentToolAccepted: true, runtimeAlive: false, bootstrapConfirmed: false, hardFailure: true, hardFailureReason: 'Side lane failed', lastEvaluatedAt: '2026-04-22T12:00:00.000Z', }, }, summary: { confirmedCount: 1, pendingCount: 0, failedCount: 1, runtimeAlivePendingCount: 0, }, teamLaunchState: 'partial_failure', } as never); expect(summary).toMatchObject({ expectedMemberCount: 2, confirmedMemberCount: 1, missingMembers: ['bob'], failedCount: 1, launchPhase: 'finished', teamLaunchState: 'partial_failure', }); }); });