fix(team): keep launch summary projection aligned

This commit is contained in:
777genius 2026-04-23 03:57:05 +03:00
parent f036cf0386
commit 33f51b68a7
2 changed files with 56 additions and 3 deletions

View file

@ -30,6 +30,10 @@ export interface PersistedTeamLaunchSummaryProjection extends LaunchStateSummary
mixedAware?: true;
}
function getPersistedLaunchMemberNames(snapshot: PersistedTeamLaunchSnapshot): string[] {
return Array.from(new Set([...snapshot.expectedMembers, ...Object.keys(snapshot.members)]));
}
function normalizeIsoDate(value: unknown): string | null {
if (typeof value !== 'string') {
return null;
@ -48,7 +52,8 @@ function toMillis(value: string | undefined | null): number {
export function createLaunchStateSummary(
snapshot: PersistedTeamLaunchSnapshot
): LaunchStateSummary {
const missingMembers = snapshot.expectedMembers.filter((name) => {
const persistedMemberNames = getPersistedLaunchMemberNames(snapshot);
const missingMembers = persistedMemberNames.filter((name) => {
const member = snapshot.members[name];
return member?.launchState === 'failed_to_start';
});
@ -57,8 +62,8 @@ export function createLaunchStateSummary(
...(snapshot.teamLaunchState === 'partial_failure'
? { partialLaunchFailure: true as const }
: {}),
...(snapshot.expectedMembers.length > 0
? { expectedMemberCount: snapshot.expectedMembers.length }
...(persistedMemberNames.length > 0
? { expectedMemberCount: persistedMemberNames.length }
: {}),
...(snapshot.summary.confirmedCount > 0
? { confirmedMemberCount: snapshot.summary.confirmedCount }

View file

@ -174,4 +174,52 @@ describe('TeamLaunchSummaryProjection', () => {
})
).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,
teamLaunchState: 'partial_failure',
});
});
});