834 lines
25 KiB
TypeScript
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',
|
|
});
|
|
});
|
|
});
|