diff --git a/tests/runner-e2e/README.md b/tests/runner-e2e/README.md index 377190c0cf9..9ec8f7f1485 100644 --- a/tests/runner-e2e/README.md +++ b/tests/runner-e2e/README.md @@ -3407,7 +3407,10 @@ any upload archive. Private originals, screenshots and traces stay private. Review the immutable payload and its file hashes before an authorized upload to the original jobrun. Persist a once-intent before transmission. After an ambiguous -response, reconcile with GET requests; do not resend or regenerate UUIDs. Final +response, reconcile with manual GET requests or an independently reviewed, +gap-aware read-only check; do not resend or regenerate UUIDs. The existing +`verify-upload` CLI accepts strict original outcomes only. It cannot verify the +separate BROKEN gap shape and must not be used for this recovery bundle. Final coverage must match all selected case IDs and preserve the separate gap count. This mode prepares data only. It does not upload, start jobs, reopen launches, change credentials, or change evaluation and evidence limits. diff --git a/tests/runner-e2e/chat-full-catalog-testops-merge.ts b/tests/runner-e2e/chat-full-catalog-testops-merge.ts index 398856f3f50..740bfdf711f 100644 --- a/tests/runner-e2e/chat-full-catalog-testops-merge.ts +++ b/tests/runner-e2e/chat-full-catalog-testops-merge.ts @@ -8,6 +8,8 @@ import { hash, validateSelection, catalogDefinition, model } from "./chat-comple import type { PilotContext, PilotSelection } from "./chat-completion-testops-contract.mjs"; import type { RunnerE2EResult } from "./types.js"; import { findSecretLeakInJsonValues } from "./redaction.js"; +import { privateArtifactKey } from "./private-history.js"; +import { manualCellKind } from "./manual-private-retention.js"; export async function mergeCatalogProjections(root: string, output: string, selection: PilotSelection, context: PilotContext) { if (context.catalog !== "full") throw new Error("Full catalog context required"); @@ -85,7 +87,7 @@ export async function prepareRetainedCatalogReport(root: string, output: string, for (const gap of disposition.gaps) { if (!keys(gap, ["executionId", "archiveKey", "archiveStatus"]) || gaps.has(gap.executionId) || !selected.some(row => row.executionId === gap.executionId) || gap.archiveStatus !== "not_found" - || gap.archiveKey !== `runner-e2e-private/bounded-v1/campaigns/gha-${context.runId}-1/manual-cell-${hash(gap.executionId)}.zip`) throw new Error("Invalid reporting gap"); + || gap.archiveKey !== privateArtifactKey(manualCellKind(gap.executionId), context.runId)) throw new Error("Invalid reporting gap"); gaps.add(gap.executionId); } const stat = await lstat(root); diff --git a/tests/runner-e2e/chat-full-catalog-testops-retained.test.ts b/tests/runner-e2e/chat-full-catalog-testops-retained.test.ts index b8aa5f84988..720ca985c0f 100644 --- a/tests/runner-e2e/chat-full-catalog-testops-retained.test.ts +++ b/tests/runner-e2e/chat-full-catalog-testops-retained.test.ts @@ -6,6 +6,8 @@ import os from 'node:os'; import * as aggregate from './chat-full-catalog-testops-merge.js'; import {hash,model} from './chat-completion-testops-contract.mjs'; import {fullCatalogCases,catalogTargetSha} from './chat-full-catalog-testops-manifest.mjs'; +import {privateArtifactKey} from './private-history.js'; +import {manualCellKind} from './manual-private-retention.js'; const roots:string[]=[]; afterEach(async()=>{await Promise.all(roots.splice(0).map(root=>rm(root,{recursive:true,force:true})));}); const context={catalog:'full' as const,runId:'123',attempt:'1',workflowSha:'a'.repeat(40),targetSha:catalogTargetSha}; @@ -22,7 +24,7 @@ async function fixture(t:any){ for(const [name,data] of files)await writeFile(path.join(dir,name),data); await writeFile(path.join(dir,'binding.json'),JSON.stringify({schema:'paperclip.chat-completion-testops-export/v1',...context,campaignId:'gha-123-1',executionIds:[row.executionId],files:[...files].map(([name,data])=>({path:name,bytes:data.length,sha256:hash(data)})),privateEvidence:[{executionId:row.executionId,requiredEvidenceComplete:passed,finalScreenshot:passed?{sha256:'e'.repeat(64),bytes:24}:null}]})); } - const disposition={schema:'paperclip.chat-catalog-retained-disposition/v1',origin:{...context,projectId:1,planId:102,jobId:100,launchId:12,allureJobRunId:'77'},selectionSha256:hash(JSON.stringify(selection,null,2)),upstream:{status:'completed',jobsSha256:'b'.repeat(64)},custodySha256:'c'.repeat(64),gaps:[{executionId:cases[2].executionId,archiveKey:`runner-e2e-private/bounded-v1/campaigns/gha-123-1/manual-cell-${hash(cases[2].executionId)}.zip`,archiveStatus:'not_found'}]}; + const disposition={schema:'paperclip.chat-catalog-retained-disposition/v1',origin:{...context,projectId:1,planId:102,jobId:100,launchId:12,allureJobRunId:'77'},selectionSha256:hash(JSON.stringify(selection,null,2)),upstream:{status:'completed',jobsSha256:'b'.repeat(64)},custodySha256:'c'.repeat(64),gaps:[{executionId:cases[2].executionId,archiveKey:privateArtifactKey(manualCellKind(cases[2].executionId),context.runId),archiveStatus:'not_found'}]}; return {root,input,selection,disposition,context,output:path.join(root,'report')}; } async function prepare(f:any){return (aggregate.prepareRetainedCatalogReport ?? aggregate.mergeCatalogProjections)(f.input,f.output,f.selection,f.context,f.disposition,{kind:'operator',revisionSha:'d'.repeat(40)});}