@@ -124,6 +124,8 @@ export interface AdversarialReplay {
124124 schedule : AdversarialScheduleEntry [ ]
125125 provenance ?: Record < string , unknown >
126126 command : string
127+ expectedFingerprint ?: string
128+ expectedStateDigest ?: string
127129}
128130
129131export interface AdversarialScheduleEntry {
@@ -239,8 +241,8 @@ export async function runAdversarialCampaign(campaignInput: AdversarialCampaign,
239241 const failedOracles = oracleResults . filter ( ( result ) => result . failed )
240242 if ( failedOracles . length > 0 || observation . status !== "passed" ) {
241243 if ( options . signal ?. aborted ) { incomplete = true ; diagnostics . push ( { code : "campaign-interrupted" , message : "Campaign stopped before finding minimization after an interruption request." } ) ; break }
242- const minimized = options . minimize === false ? normalizeCorpusEntry ( { id : plan . caseId , actions : plan . actions , input : plan . input } ) : await minimizeAdversarialCase ( campaign , plan , failedOracles , options )
243- const fingerprint = adversarialFindingFingerprint ( { oracleIds : failedOracles . map ( ( result ) => result . oracleId ) , status : observation . status , diagnosticCodes : ( observation . diagnostics ?? [ ] ) . map ( ( item ) => item . code ) . sort ( ) , matrix : plan . matrix } )
244+ const fingerprint = observationFingerprint ( plan , observation , failedOracles )
245+ const minimized = options . minimize === false ? normalizeCorpusEntry ( { id : plan . caseId , actions : plan . actions , input : plan . input } ) : await minimizeAdversarialCase ( campaign , plan , failedOracles , fingerprint , options )
244246 const existing = findings . get ( fingerprint )
245247 if ( existing ) existing . duplicates += 1
246248 else findings . set ( fingerprint , createFinding ( campaign , plan , minimized , observation , failedOracles , schedule , options , fingerprint ) )
@@ -335,14 +337,15 @@ function mutateCorpusEntry(campaign: AdversarialCampaign, source: AdversarialCor
335337 return { id : `${ source . id } -mutation-${ iteration } ` , caseId : `${ campaign . id } -${ iteration } ` , corpusId : source . id , iteration, workerId, matrix, actions, input, mutation, metadata : source . metadata }
336338}
337339
338- async function minimizeAdversarialCase ( campaign : AdversarialCampaign , plan : AdversarialCasePlan , originalOracles : AdversarialOracleResult [ ] , options : AdversarialCampaignRunnerOptions ) : Promise < AdversarialCorpusEntry > {
340+ async function minimizeAdversarialCase ( campaign : AdversarialCampaign , plan : AdversarialCasePlan , originalOracles : AdversarialOracleResult [ ] , expectedFingerprint : string , options : AdversarialCampaignRunnerOptions ) : Promise < AdversarialCorpusEntry > {
339341 const oracleIds = new Set ( originalOracles . filter ( ( item ) => item . failed ) . map ( ( item ) => item . oracleId ) )
340342 const preserves = async ( candidate : AdversarialCorpusEntry ) : Promise < boolean > => {
341343 if ( options . signal ?. aborted ) return false
342344 const candidatePlan = { ...plan , actions : candidate . actions , input : candidate . input }
343345 const observation = await executeBoundedCase ( campaign , candidatePlan , options )
344346 const oracleResults = options . evaluate ? await options . evaluate ( candidatePlan , observation , campaign . oracles ) : defaultOracleResults ( observation )
345- return observation . status !== "passed" || oracleResults . some ( ( item ) => item . failed && ( oracleIds . size === 0 || oracleIds . has ( item . oracleId ) ) )
347+ const preservesOracle = observation . status !== "passed" || oracleResults . some ( ( item ) => item . failed && ( oracleIds . size === 0 || oracleIds . has ( item . oracleId ) ) )
348+ return preservesOracle && observationFingerprint ( candidatePlan , observation , oracleResults . filter ( ( item ) => item . failed ) ) === expectedFingerprint
346349 }
347350 let actions = [ ...plan . actions ]
348351 let chunk = Math . max ( 1 , Math . floor ( actions . length / 2 ) )
@@ -373,7 +376,7 @@ function createFinding(campaign: AdversarialCampaign, plan: AdversarialCasePlan,
373376 status : observation . status ,
374377 minimized,
375378 original : { id : plan . caseId , actions : plan . actions , input : plan . input } ,
376- replay : stripUndefined ( { schema : ADVERSARIAL_REPLAY_SCHEMA , campaignId : campaign . id , seed : campaign . seed , caseId : plan . caseId , corpusId : plan . corpusId , workerId : plan . workerId , iteration : plan . iteration , matrix : plan . matrix , actions : minimized . actions , input : minimized . input , faultSchedule : campaign . faults , schedule : [ ...schedule ] , provenance : campaign . provenance , command } ) ,
379+ replay : stripUndefined ( { schema : ADVERSARIAL_REPLAY_SCHEMA , campaignId : campaign . id , seed : campaign . seed , caseId : plan . caseId , corpusId : plan . corpusId , workerId : plan . workerId , iteration : plan . iteration , matrix : plan . matrix , actions : minimized . actions , input : minimized . input , faultSchedule : campaign . faults , schedule : [ ...schedule ] , provenance : campaign . provenance , command, expectedFingerprint : fingerprint , expectedStateDigest : observation . stateDigest } ) ,
377380 diagnostics : observation . diagnostics ?? [ ] ,
378381 artifactRefs : observation . artifacts ?? [ ] ,
379382 secretScan : { status : "passed" , redactions : 0 } ,
@@ -386,6 +389,16 @@ function defaultOracleResults(observation: AdversarialExecutionObservation): Adv
386389 return observation . status === "passed" ? [ ] : [ { oracleId : "runtime-status" , failed : true , code : observation . status , message : observation . diagnostics ?. [ 0 ] ?. message ?? `Runtime status was ${ observation . status } .` } ]
387390}
388391
392+ function observationFingerprint ( plan : AdversarialCasePlan , observation : AdversarialExecutionObservation , oracles : AdversarialOracleResult [ ] ) : string {
393+ return adversarialFindingFingerprint ( {
394+ oracleIds : oracles . filter ( ( result ) => result . failed ) . map ( ( result ) => result . oracleId ) . sort ( ) ,
395+ status : observation . status ,
396+ diagnosticCodes : ( observation . diagnostics ?? [ ] ) . map ( ( item ) => item . code ) . sort ( ) ,
397+ stateDigest : observation . stateDigest ,
398+ matrix : plan . matrix ,
399+ } )
400+ }
401+
389402function normalizeBudgets ( input : Partial < AdversarialResourceBudget > | undefined ) : AdversarialResourceBudget {
390403 return {
391404 maxCases : boundedInteger ( input ?. maxCases , 100 , 1 , 100_000 ) ,
0 commit comments