Files
PaperClipAI/tests/runner-e2e/history.ts
DottaandPaperclip b62924a737 Preserve incomplete outcomes and unknown costs in E2E reports
Reject incidental and negated task mentions as proposals. Carry incomplete journeys through history and retain unknown judge totals instead of reporting them as zero.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-09-15 22:51:20 -05:00

283 lines
9.1 KiB
TypeScript

import { isIncompleteFirstTaskResult } from "./case-outcome.js";
import { validateRetainedRunnerResult } from "./result-validation.js";
import {
discoverReportCatalog,
parseReportExecutionId,
type ReportExecution,
} from "./report-catalog.js";
import { runnerMatrix, runnerSuites } from "./catalog.js";
import {
aggregateCampaignBilling,
summarizeExecutionBilling,
} from "./billing.js";
import { resolveRunnerE2ESource } from "./source.js";
import type {
RunnerE2ECampaign,
RunnerE2EHistoryCampaign,
RunnerE2EHistoryIndex,
RunnerE2EResult,
} from "./types.js";
export function canonicalExecutionId(id: string) {
if (runnerMatrix.some((execution) => execution.id === id)) return id;
const coreId = `core-compatibility.${id}`;
return runnerMatrix.some((execution) => execution.id === coreId)
? coreId
: id;
}
export function upgradeRunnerResult(result: RunnerE2EResult): RunnerE2EResult {
validateRetainedRunnerResult(result);
const executionId = canonicalExecutionId(result.executionId);
const execution = runnerMatrix.find(
(candidate) => candidate.id === executionId,
);
if (!execution)
return {
...result,
executionId,
suiteId: result.suiteId ?? parseReportExecutionId(executionId).suiteId,
};
return {
...result,
executionId,
suiteId: result.suiteId ?? execution.suite.id,
suiteDefinitionHash:
result.suiteDefinitionHash ?? execution.suiteDefinitionHash,
...(result.schema === "paperclip.runner-e2e.result/v1"
? {
source: result.source ?? {
sha: null,
ref: null,
workflowRunUrl: null,
},
}
: {}),
};
}
export function buildRunnerCampaign(input: {
campaignId: string;
generatedAt: string;
expected: readonly string[];
results: readonly RunnerE2EResult[];
eventName?: string | null;
catalog?: readonly ReportExecution[];
}): RunnerE2ECampaign {
const expected = input.expected.map(canonicalExecutionId);
const results = input.results.map((result) => ({
...upgradeRunnerResult(result),
billing: result.billing ?? summarizeExecutionBilling(result),
}));
const knownCatalog = input.catalog ?? runnerMatrix;
const catalog = discoverReportCatalog({
catalog: knownCatalog,
expected,
results,
});
const discoveredSuites = [
...new Map(
catalog.map((execution) => [execution.suite.id, execution.suite]),
).values(),
];
const resultSource = results.find((result) => result.source)?.source;
const source = {
...resolveRunnerE2ESource(resultSource),
eventName: input.eventName ?? process.env.GITHUB_EVENT_NAME ?? null,
};
const suites = discoveredSuites
.map((suite) => {
const suiteExpected = expected.filter((id) =>
id.startsWith(`${suite.id}.`),
);
if (suiteExpected.length === 0) return null;
const suiteResults = results.filter(
(result) =>
expected.includes(result.executionId) &&
result.executionId.startsWith(`${suite.id}.`),
);
const passed = suiteResults.filter(
(result) => result.status === "passed" && result.cleanup === "passed",
).length;
const executed = suiteResults.filter(
(result) => result.attempt > 0,
).length;
return {
suiteId: suite.id,
suiteDefinitionHash:
suiteResults[0]?.suiteDefinitionHash ??
catalog.find((execution) => execution.suite.id === suite.id)!
.suiteDefinitionHash,
expected: suite.expectedMatrixSize,
selected: suiteExpected.length,
executed,
passed,
failed: suiteExpected.length - passed - suiteResults.filter(r => isIncompleteFirstTaskResult(r)).length,
incomplete: suiteResults.filter(r => isIncompleteFirstTaskResult(r)).length,
retries: suiteResults.reduce(
(total, result) => total + Math.max(0, result.attempt - 1),
0,
),
cleanupPassed: suiteResults.every(
(result) => result.cleanup === "passed",
),
complete:
suiteExpected.length === suite.expectedMatrixSize &&
knownCatalog.filter((execution) => execution.suite.id === suite.id)
.length === suiteExpected.length &&
knownCatalog
.filter((execution) => execution.suite.id === suite.id)
.every(
(execution) =>
suiteExpected.includes(execution.id) &&
suiteResults.every(
(result) =>
result.suiteDefinitionHash ===
execution.suiteDefinitionHash,
),
),
durationMs: suiteResults.reduce(
(total, result) => total + result.durationMs,
0,
),
billing: aggregateCampaignBilling(suiteResults),
};
})
.filter((suite): suite is NonNullable<typeof suite> => Boolean(suite));
const passed = results.filter(
(result) => result.status === "passed" && result.cleanup === "passed",
).length;
const rankingSnapshots = [
...new Map(
results.flatMap((result) =>
result.rankingSnapshot
? [
[
result.rankingSnapshot.snapshotId,
{
snapshotId: result.rankingSnapshot.snapshotId,
capturedAt: result.rankingSnapshot.capturedAt,
sourceUrl: result.rankingSnapshot.sourceUrl,
},
] as const,
]
: [],
),
).values(),
];
return {
schema: "paperclip.runner-e2e.campaign/v2",
campaignId: input.campaignId,
generatedAt: input.generatedAt,
source,
expected,
complete:
suites.length === runnerSuites.length &&
suites.every((suite) => suite.complete),
selected: expected.length,
executed: results.filter((result) => result.attempt > 0).length,
passed,
failed: expected.length - passed - results.filter(r => isIncompleteFirstTaskResult(r)).length,
incomplete: results.filter(r => isIncompleteFirstTaskResult(r)).length,
retries: results.reduce(
(total, result) => total + Math.max(0, result.attempt - 1),
0,
),
cleanupPassed: results.every((result) => result.cleanup === "passed"),
rankingSnapshots,
billing: aggregateCampaignBilling(results),
suites,
results,
};
}
export function campaignHistoryRecord(
campaign: RunnerE2ECampaign,
publicBaseUrl: string,
): RunnerE2EHistoryCampaign {
const publicUrl = `${publicBaseUrl.replace(/\/$/, "")}/campaigns/${encodeURIComponent(campaign.campaignId)}/`;
return {
campaignId: campaign.campaignId,
generatedAt: campaign.generatedAt,
source: campaign.source,
complete: campaign.complete,
selected: campaign.selected,
executed: campaign.executed,
passed: campaign.passed,
failed: campaign.failed,
incomplete: campaign.incomplete ?? 0,
retries: campaign.retries,
cleanupPassed: campaign.cleanupPassed,
publicUrl,
billing: campaign.billing,
suites: campaign.suites,
executions: campaign.results.map((result) => ({
executionId: result.executionId,
suiteId: result.suiteId ?? "core-compatibility",
profileId: result.profileId,
environmentId: result.environmentId,
caseId: result.caseId,
provider: result.provider,
model: result.model,
status: isIncompleteFirstTaskResult(result) ? "incomplete" : result.status,
durationMs: result.durationMs,
attempt: result.attempt,
cleanup: result.cleanup,
billing: result.billing ?? summarizeExecutionBilling(result),
})),
};
}
export function emptyRunnerHistory(): RunnerE2EHistoryIndex {
return {
schema: "paperclip.runner-e2e.history/v1",
updatedAt: new Date(0).toISOString(),
latestCampaignId: null,
latestGreenCampaignId: null,
latestBySuite: {},
latestGreenBySuite: {},
campaigns: [],
};
}
export function mergeRunnerHistory(
current: RunnerE2EHistoryIndex | null | undefined,
campaign: RunnerE2EHistoryCampaign,
): RunnerE2EHistoryIndex {
const base =
current?.schema === "paperclip.runner-e2e.history/v1"
? current
: emptyRunnerHistory();
const campaigns = [
campaign,
...base.campaigns.filter(
(candidate) => candidate.campaignId !== campaign.campaignId,
),
].sort(
(left, right) =>
Date.parse(right.generatedAt) - Date.parse(left.generatedAt),
);
const latestBySuite: Record<string, string> = {};
const latestGreenBySuite: Record<string, string> = {};
for (const candidate of campaigns) {
for (const suite of candidate.suites) {
latestBySuite[suite.suiteId] ??= candidate.campaignId;
if (suite.complete && suite.failed === 0 && (suite.incomplete ?? 0) === 0) {
latestGreenBySuite[suite.suiteId] ??= candidate.campaignId;
}
}
}
return {
schema: "paperclip.runner-e2e.history/v1",
updatedAt: new Date().toISOString(),
latestCampaignId: campaigns[0]?.campaignId ?? null,
latestGreenCampaignId:
campaigns.find(
(candidate) => candidate.complete && candidate.failed === 0 && (candidate.incomplete ?? 0) === 0,
)?.campaignId ?? null,
latestBySuite,
latestGreenBySuite,
campaigns,
};
}