From 51cfd08d45dec8fbca61633ce34aab799d69590f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 4 Nov 2026 17:50:36 +0000 Subject: [PATCH] fix(cli): report result sources when Evidence publication fails A trace keeps linked sources on the result. Use those records for the undelivered source closure as well as the bundle, so a refused output does not report them as not captured. --- src/cli/analysisEvidenceDelivery.ts | 24 +++++++++++++------ .../undeliveredSourceClosure.test.ts | 20 ++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/cli/analysisEvidenceDelivery.ts b/src/cli/analysisEvidenceDelivery.ts index 947c634f63..0517c8acbc 100644 --- a/src/cli/analysisEvidenceDelivery.ts +++ b/src/cli/analysisEvidenceDelivery.ts @@ -250,13 +250,24 @@ export const publishCliAnalysisEvidence = async ( input: CliEvidenceDelivery, ): Promise => { const { operation, signal, detail, evidenceOutput } = input; + // Failure reporting uses the same captured sources as publication. A trace + // carries them on the result, while other commands pass them on the input. + const capturedSources = [ + ...(input.sourceEvidence ?? []), + ...(result.sourceEvidence ?? []), + ]; + const closureInput = + capturedSources.length === 0 + ? input + : { ...input, sourceEvidence: capturedSources }; const projectFailure = (error: AnalysisError) => projectAnalysisError( redactExplicitFailure(error, input.sensitiveValues ?? []), ); const undelivered = (error: AnalysisError) => - withUndeliveredSourceClosure(projectFailure(error), input); - if (!result.ok) return withUndeliveredSourceClosure(result.error, input); + withUndeliveredSourceClosure(projectFailure(error), closureInput); + if (!result.ok) + return withUndeliveredSourceClosure(result.error, closureInput); const complete = parseEvidence(result.value); const inputError = cliEvidenceDeliveryInputError(input); if (inputError !== undefined) { @@ -285,11 +296,10 @@ export const publishCliAnalysisEvidence = async ( if (!inspectionSources.ok) return undelivered(inspectionSources.error); const records = [ ...new Map( - [ - ...(input.sourceEvidence ?? []), - ...(result.sourceEvidence ?? []), - ...inspectionSources.value, - ].map((record) => [record.evidence_id, record]), + [...capturedSources, ...inspectionSources.value].map((record) => [ + record.evidence_id, + record, + ]), ).values(), ]; // Only skip the writer's heap check when every record is already sealed. diff --git a/tests/boundary/filesystem/undeliveredSourceClosure.test.ts b/tests/boundary/filesystem/undeliveredSourceClosure.test.ts index 1ce25fdbf2..48dd7928a0 100644 --- a/tests/boundary/filesystem/undeliveredSourceClosure.test.ts +++ b/tests/boundary/filesystem/undeliveredSourceClosure.test.ts @@ -435,6 +435,26 @@ it("reports the closure when an existing output refuses publication", async () = expect(await readFile(requested.evidenceOutput, "utf8")).toBe("original"); }); +it("reports result sources when an existing output refuses publication", async () => { + const root = await createTestTempDirectory("rea-closure-result-sources-"); + const held = source("held"); + const requested = delivery(root); + await writeFile(requested.evidenceOutput, "original"); + const output = await publishCliAnalysisEvidence( + { ...ok(observed([held.evidence_id])), sourceEvidence: [held] }, + requested, + ); + expect(output).toMatchObject({ details: { reason: "exists" } }); + expect(closureOf(output)).toMatchObject({ + publication: "not_published", + complete: true, + omitted_count: 0, + omitted: [], + }); + expect(closureOf(output).records).toEqual([held]); + expect(await readFile(requested.evidenceOutput, "utf8")).toBe("original"); +}); + it("defers to the committed bundle receipt after cancellation wins late", async () => { const root = await createTestTempDirectory("rea-closure-committed-"); const held = source("held");