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

834 lines
25 KiB
TypeScript

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',
});
});
});