fix(testops): reuse retained-key owner and document gap readback

Use the existing archive-key helpers and state that recovered gap bundles require gap-aware GET reconciliation. Frozen payload bytes and original grades stay unchanged.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-11-02 17:34:29 -06:00
1 parent b4e5e16469
commit d91d18efea
3 files changed
+10 -3

No files matched your search

+4 -1
View File
@@ -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.
@@ -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);
@@ -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)});}