From effb7960275717fd5dc2c616d9f9ca23f101a678 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:14:34 +0800 Subject: [PATCH] fix(evidence): validate retained views by semantic identity --- .../analysisView/AnalysisViewService.test.ts | 22 +++++++++++++++++++ .../analysisView/AnalysisViewService.ts | 1 - .../NativeObservationViews.test.ts | 19 ++++++++++++++++ .../WebNetworkProjectionAccumulator.ts | 16 +++++--------- src/domain/analysisView/analysisView.ts | 2 -- .../analysisView/analysisViewParent.test.ts | 13 +---------- src/domain/analysisView/analysisViewParent.ts | 6 ----- .../analysisView/desktopApplicationView.ts | 16 +++++--------- .../environmentManifestView.test.ts | 11 +--------- src/domain/analysisView/evmInterfaceView.ts | 16 +++++--------- src/domain/analysisView/goBinaryView.test.ts | 5 ++--- src/domain/analysisView/goBinaryView.ts | 15 +++++-------- .../analysisView/gpuCodeObjectsView.test.ts | 2 +- src/domain/analysisView/gpuCodeObjectsView.ts | 12 ++++------ src/domain/analysisView/il2cppMetadataView.ts | 14 +++++------- .../analysisView/javascriptRoutesView.test.ts | 2 +- .../analysisView/javascriptRoutesView.ts | 9 ++------ src/domain/analysisView/modelArtifactView.ts | 13 +++-------- src/domain/analysisView/pcapCaptureView.ts | 15 ++++--------- .../analysisView/protobufCaptureView.test.ts | 5 ++++- .../analysisView/rustBinaryView.test.ts | 1 - src/domain/analysisView/rustBinaryView.ts | 8 ++----- .../webNetworkCaptureView.test.ts | 6 ++--- .../analysisView/webNetworkCaptureView.ts | 14 ++++-------- 24 files changed, 101 insertions(+), 142 deletions(-) diff --git a/src/application/analysisView/AnalysisViewService.test.ts b/src/application/analysisView/AnalysisViewService.test.ts index fc0519fd31..d7b3a422da 100644 --- a/src/application/analysisView/AnalysisViewService.test.ts +++ b/src/application/analysisView/AnalysisViewService.test.ts @@ -111,6 +111,28 @@ it("projects retained GPU source facts while delivering the caller's rebound sub }); } expect(ledger.forAnalysis(original.evidence_id)).toBe(retained); + // Local path metadata may be rebound before first retention or bundle import. + const aliasLedger = new EvidenceLedger(); + expect(aliasLedger.record(selected).ok).toBe(true); + const imported = new EvidenceLedger(); + expect(imported.import(aliasLedger.export()).ok).toBe(true); + // Importing metadata into an existing ledger must preserve view validity too. + expect(ledger.import(aliasLedger.export()).ok).toBe(true); + for (const sourceLedger of [aliasLedger, imported, ledger]) { + const result = projectRetainedAnalysis( + selected, + { + recordEvidence: (evidence) => sourceLedger.record(evidence), + evidenceById: (id) => sourceLedger.forAnalysis(id), + }, + { kind: "summary" }, + ); + if (!result.ok) throw result.error; + expect(result.value.subject?.local_path).toBe("/selected/gpu"); + expect(result.value.normalized_result).toMatchObject({ + artifact: { path: "/original/gpu", sha256 }, + }); + } expect(retained.normalized_result).toBe(payload); expect(retained.subject?.local_path).toBe("/original/gpu"); const mismatched = createEvidence( diff --git a/src/application/analysisView/AnalysisViewService.ts b/src/application/analysisView/AnalysisViewService.ts index 676fff685e..e24921b7ee 100644 --- a/src/application/analysisView/AnalysisViewService.ts +++ b/src/application/analysisView/AnalysisViewService.ts @@ -224,7 +224,6 @@ const projectResolvedAnalysisView = ( // Sealing an Evidence root deep-freezes it; only the root is registered. sealedResult: isImmutableJsonSnapshot(parent), subject: presentation.subject, - sourceSubject: parent.subject, limitations: parent.limitations, parameters: parent.parameters, artifact: diff --git a/src/application/analysisView/NativeObservationViews.test.ts b/src/application/analysisView/NativeObservationViews.test.ts index 6c65ab3d78..c3e2de8d8d 100644 --- a/src/application/analysisView/NativeObservationViews.test.ts +++ b/src/application/analysisView/NativeObservationViews.test.ts @@ -519,6 +519,25 @@ it("keeps context identity across authenticated target path aliases while preser }); expect(ledger.forAnalysis(original.evidence_id)).toBe(retained); expect(retained.subject?.local_path).toBe("/selected/fixture.elf"); + const imported = new EvidenceLedger(); + const exporting = new EvidenceLedger(); + expect(exporting.record(rebound).ok).toBe(true); + expect(imported.import(exporting.export()).ok).toBe(true); + const replay = await inspectAnalysisView( + { + source: { kind: "retained-evidence", evidence_id: rebound.evidence_id }, + view, + }, + (id) => imported.forAnalysis(id), + ); + if (!replay.ok) throw replay.error; + expect(replay.value.subject?.local_path).toBe("/other/alias.elf"); + expect(replay.value.normalized_result).toMatchObject({ + artifact: { path: "/other/alias.elf" }, + items: { + rows: [{ record: { subject: { local_path: "/other/alias.elf" } } }], + }, + }); }); it("pages recorded empty graph levels without inventing nodes or dropping their identity", async () => { diff --git a/src/domain/analysisView/WebNetworkProjectionAccumulator.ts b/src/domain/analysisView/WebNetworkProjectionAccumulator.ts index d703cab40a..0c9d7128e3 100644 --- a/src/domain/analysisView/WebNetworkProjectionAccumulator.ts +++ b/src/domain/analysisView/WebNetworkProjectionAccumulator.ts @@ -13,10 +13,7 @@ import { } from "../webNetworkCapture.js"; import { admitAnalysisViewData } from "./analysisViewDataAdmission.js"; import { analysisViewInputError } from "./analysisViewErrors.js"; -import { - analysisViewSourceSubject, - analysisViewResultInputError, -} from "./analysisViewParent.js"; +import { analysisViewResultInputError } from "./analysisViewParent.js"; import type { AnalysisViewParent } from "./analysisView.js"; import { ENTRY_BYTES, @@ -150,24 +147,21 @@ export const admitHarCaptureParent = ( return unsupported( `${viewKind} does not support capture format ${parsed.data.format}.`, ); - // Environment views cite the report path and digest, so their parent's subject must be exactly them; + // Environment views preserve the recorded report path and authenticate its digest; // the api-surface view keeps the admission its existing callers rely on. const { artifact } = parsed.data; - const sourceSubject = analysisViewSourceSubject(parent); + const subject = parent.subject; if ( viewKind !== "api-surface" && artifact.sha256 !== null && - (sourceSubject == null || - sourceSubject.local_path !== artifact.path || - sourceSubject.digest.sha256 !== artifact.sha256) + (subject == null || subject.digest.sha256 !== artifact.sha256) ) return err( analysisViewInputError([ { path: ["source", "subject"], reason: "invalid_value", - message: - "Capture parent subject must identify the report's exact path and SHA-256.", + message: "Capture parent subject must identify the report's SHA-256.", }, ]), ); diff --git a/src/domain/analysisView/analysisView.ts b/src/domain/analysisView/analysisView.ts index c3858ae2d3..75c144cf38 100644 --- a/src/domain/analysisView/analysisView.ts +++ b/src/domain/analysisView/analysisView.ts @@ -1445,8 +1445,6 @@ export interface AnalysisViewParent { readonly sealedResult?: boolean; /** Subject presentation for this delivery, including explicit absence. */ readonly subject?: Evidence["subject"]; - /** Original retained identity for report validation; omitted for direct domain callers. */ - readonly sourceSubject?: Evidence["subject"]; readonly limitations: readonly string[]; readonly artifact?: { readonly path: string; readonly sha256: string } | null; /** Parent Evidence parameters, when the parent operation records them. */ diff --git a/src/domain/analysisView/analysisViewParent.test.ts b/src/domain/analysisView/analysisViewParent.test.ts index a9966c7171..93f56b57db 100644 --- a/src/domain/analysisView/analysisViewParent.test.ts +++ b/src/domain/analysisView/analysisViewParent.test.ts @@ -5,10 +5,7 @@ import { isImmutableJsonSnapshot, } from "../immutableJson.js"; import type { AnalysisViewParent } from "./analysisView.js"; -import { - analysisViewSourceSubject, - createAnalysisViewResultReader, -} from "./analysisViewParent.js"; +import { createAnalysisViewResultReader } from "./analysisViewParent.js"; const parent = (normalizedResult: unknown): AnalysisViewParent => ({ evidenceId: `ev_${"a".repeat(64)}`, @@ -18,14 +15,6 @@ const parent = (normalizedResult: unknown): AnalysisViewParent => ({ limitations: [], }); -it("keeps explicit source absence separate from a delivery subject", () => { - const source = parent({}); - expect(analysisViewSourceSubject(source)).toBeUndefined(); - expect( - analysisViewSourceSubject({ ...source, sourceSubject: null }), - ).toBeNull(); -}); - it("does not trust a caller's sealed flag or shallow freeze for mutable facts", () => { const item = { value: "valid" }; const source = Object.freeze({ items: [item] }); diff --git a/src/domain/analysisView/analysisViewParent.ts b/src/domain/analysisView/analysisViewParent.ts index 02ba6adf64..9e2d9e2311 100644 --- a/src/domain/analysisView/analysisViewParent.ts +++ b/src/domain/analysisView/analysisViewParent.ts @@ -67,9 +67,3 @@ export const analysisViewResultInputError = ( { source: { normalized_result: input } }, { cause }, ); - -/** Resolve report-validation identity once, preserving explicit source absence. */ -export const analysisViewSourceSubject = ( - parent: AnalysisViewParent, -): AnalysisViewParent["subject"] => - parent.sourceSubject === undefined ? parent.subject : parent.sourceSubject; diff --git a/src/domain/analysisView/desktopApplicationView.ts b/src/domain/analysisView/desktopApplicationView.ts index c07babf563..a179baf873 100644 --- a/src/domain/analysisView/desktopApplicationView.ts +++ b/src/domain/analysisView/desktopApplicationView.ts @@ -11,10 +11,7 @@ import { type DesktopApplication, } from "../desktopApplication.js"; import { analysisViewInputError } from "./analysisViewErrors.js"; -import { - analysisViewSourceSubject, - parseAnalysisViewResult, -} from "./analysisViewParent.js"; +import { parseAnalysisViewResult } from "./analysisViewParent.js"; import { completeWithinViewCoverage, pageViewCoverage, @@ -184,13 +181,12 @@ export const projectDesktopApplicationView = ( validatedParents.add(key); } } - const sourceSubject = analysisViewSourceSubject(parent); + const subject = parent.subject; if ( - sourceSubject === undefined || - sourceSubject === null || - sourceSubject.format !== "directory" || - sourceSubject.local_path !== report.root.path || - sourceSubject.digest.sha256 !== report.root.snapshot_sha256 + subject === undefined || + subject === null || + subject.format !== "directory" || + subject.digest.sha256 !== report.root.snapshot_sha256 ) return err( analysisViewInputError([ diff --git a/src/domain/analysisView/environmentManifestView.test.ts b/src/domain/analysisView/environmentManifestView.test.ts index 6a4c5915c3..995b737667 100644 --- a/src/domain/analysisView/environmentManifestView.test.ts +++ b/src/domain/analysisView/environmentManifestView.test.ts @@ -1152,7 +1152,7 @@ it("keeps opaque or redacted empty response text unknown when selecting bodiless ]); }); -it("refuses a parent whose subject is absent or names another path or digest", () => { +it("refuses a parent whose subject is absent or names another digest", () => { const parent = parentOf(); const refusal = { ok: false, @@ -1167,15 +1167,6 @@ it("refuses a parent whose subject is absent or names another path or digest", ( projectAnalysisView({ ...withoutSubject, subject: null }, view()), ).toMatchObject(refusal); if (subject == null) throw new Error("Fixture evidence carries a subject"); - expect( - projectAnalysisView( - { - ...parent, - subject: { ...subject, local_path: "/elsewhere/capture.har" }, - }, - view(), - ), - ).toMatchObject(refusal); expect( projectAnalysisView( { diff --git a/src/domain/analysisView/evmInterfaceView.ts b/src/domain/analysisView/evmInterfaceView.ts index d9e1185ca2..246e525a0f 100644 --- a/src/domain/analysisView/evmInterfaceView.ts +++ b/src/domain/analysisView/evmInterfaceView.ts @@ -4,10 +4,7 @@ import type { AnalysisError } from "../analysisErrorBase.js"; import { uniqueSorted } from "../canonicalOrdering.js"; import { evmInterfaceSchema } from "../evm/evmInterface.js"; import { err, ok, type Result } from "../result.js"; -import { - analysisViewSourceSubject, - parseAnalysisViewResult, -} from "./analysisViewParent.js"; +import { parseAnalysisViewResult } from "./analysisViewParent.js"; import type { AnalysisViewParent, AnalysisViewRequest, @@ -69,12 +66,11 @@ export const projectEvmInterfaceView = ( const parsed = parseAnalysisViewResult(evmInterfaceSchema, parent); if (!parsed.ok) return parsed; const report = parsed.value; - const sourceSubject = analysisViewSourceSubject(parent); + const subject = parent.subject; if ( - sourceSubject == null || - sourceSubject.format !== "file" || - sourceSubject.local_path !== report.artifact.path || - sourceSubject.digest.sha256 !== report.artifact.sha256 + subject == null || + subject.format !== "file" || + subject.digest.sha256 !== report.artifact.sha256 ) return err( analysisViewInputError([ @@ -82,7 +78,7 @@ export const projectEvmInterfaceView = ( path: ["source", "subject"], reason: "invalid_value", message: - "EVM parent subject must identify the report's exact carrier path and SHA-256.", + "EVM parent subject must identify the report's carrier format and SHA-256.", }, ]), ); diff --git a/src/domain/analysisView/goBinaryView.test.ts b/src/domain/analysisView/goBinaryView.test.ts index f8ff4aecdc..807b10f14d 100644 --- a/src/domain/analysisView/goBinaryView.test.ts +++ b/src/domain/analysisView/goBinaryView.test.ts @@ -72,14 +72,13 @@ it("bounds inherited messages while keeping their complete parent reference", () expect(input.limitations[0]).toBe(note); }); -it.each(["path", "digest", "format"] as const)( +it.each(["digest", "format"] as const)( "rejects a Go parent whose authenticated %s conflicts with its report", (field) => { const input = parent(); if (input.subject == null) throw new Error("Expected artifact subject"); const subject = { ...input.subject, - ...(field === "path" ? { local_path: "/artifacts/other" } : {}), ...(field === "digest" ? { digest: { sha256: "b".repeat(64) } } : {}), }; const result = projectAnalysisView( @@ -144,7 +143,7 @@ it("revalidates the parent subject after immutable Go report facts have been cac projectAnalysisView( { ...input, - subject: { ...input.subject, local_path: "/artifacts/other" }, + subject: { ...input.subject, digest: { sha256: "b".repeat(64) } }, }, { kind: "summary" }, ).ok, diff --git a/src/domain/analysisView/goBinaryView.ts b/src/domain/analysisView/goBinaryView.ts index b8b8fb767b..83e2510a07 100644 --- a/src/domain/analysisView/goBinaryView.ts +++ b/src/domain/analysisView/goBinaryView.ts @@ -20,10 +20,7 @@ import { pageViewCoverage, } from "./analysisViewCoverage.js"; import { analysisViewInputError } from "./analysisViewErrors.js"; -import { - analysisViewSourceSubject, - createAnalysisViewResultReader, -} from "./analysisViewParent.js"; +import { createAnalysisViewResultReader } from "./analysisViewParent.js"; import { selectViewRows } from "./selectViewRows.js"; const indexSchema = z.number().int().nonnegative(); @@ -125,12 +122,12 @@ export const projectGoBinaryView = ( const parsed = parseReport(parent); if (!parsed.ok) return parsed; const report = parsed.value; - const sourceSubject = analysisViewSourceSubject(parent); + const subject = parent.subject; if ( - sourceSubject?.local_path !== report.artifact.path || - sourceSubject.digest.sha256 !== report.artifact.sha256 || - sourceSubject.format !== metadataReportSubjectFormat(report) || - sourceSubject.architecture !== + subject == null || + subject.digest.sha256 !== report.artifact.sha256 || + subject.format !== metadataReportSubjectFormat(report) || + subject.architecture !== (binaryMetadataArchitecture(report.architecture) ?? null) ) return err( diff --git a/src/domain/analysisView/gpuCodeObjectsView.test.ts b/src/domain/analysisView/gpuCodeObjectsView.test.ts index 147a1bbd6e..bf4f74a2e0 100644 --- a/src/domain/analysisView/gpuCodeObjectsView.test.ts +++ b/src/domain/analysisView/gpuCodeObjectsView.test.ts @@ -41,7 +41,7 @@ it("checks each subject despite reusable GPU summary indexes", () => { const input = parent(source); expect(projectGpuCodeObjectsView(input, { kind: "summary" }).ok).toBe(true); const subject = createEvidence( - { path: "/other", sha256: source.artifact.sha256, format: "file" }, + { path: "/other", sha256: "b".repeat(64), format: "file" }, { id: "fixture", name: "Fixture", version: "1" }, { operation: input.operation, diff --git a/src/domain/analysisView/gpuCodeObjectsView.ts b/src/domain/analysisView/gpuCodeObjectsView.ts index 4fd7c25c37..2447d0f268 100644 --- a/src/domain/analysisView/gpuCodeObjectsView.ts +++ b/src/domain/analysisView/gpuCodeObjectsView.ts @@ -18,10 +18,7 @@ import type { UnsignedAnalysisView, } from "./analysisView.js"; import { analysisViewInputError } from "./analysisViewErrors.js"; -import { - analysisViewSourceSubject, - createAnalysisViewResultReader, -} from "./analysisViewParent.js"; +import { createAnalysisViewResultReader } from "./analysisViewParent.js"; import { boundedViewMessages, completeWithinViewCoverage, @@ -196,11 +193,10 @@ const readGpuFacts = ( }; if (reusable) checkedGpu.set(report, checked); } - const sourceSubject = analysisViewSourceSubject(parent); + const subject = parent.subject; if ( - sourceSubject != null && - (sourceSubject.local_path !== checked.report.artifact.path || - sourceSubject.digest.sha256 !== checked.report.artifact.sha256) + subject != null && + subject.digest.sha256 !== checked.report.artifact.sha256 ) return err( analysisViewInputError([ diff --git a/src/domain/analysisView/il2cppMetadataView.ts b/src/domain/analysisView/il2cppMetadataView.ts index 39eec554a1..e62d2a4fd2 100644 --- a/src/domain/analysisView/il2cppMetadataView.ts +++ b/src/domain/analysisView/il2cppMetadataView.ts @@ -24,10 +24,7 @@ import { il2cppMetadataTypeSchema, } from "../il2cpp/il2cppMetadata.js"; import { err, ok, type Result } from "../result.js"; -import { - analysisViewSourceSubject, - createAnalysisViewResultReader, -} from "./analysisViewParent.js"; +import { createAnalysisViewResultReader } from "./analysisViewParent.js"; import type { AnalysisViewParent, AnalysisViewRequest, @@ -538,12 +535,11 @@ export const projectIl2cppMetadataView = ( const parsed = parseMetadataReport(parent); if (!parsed.ok) return parsed; const { report, counts } = parsed.value; - const sourceSubject = analysisViewSourceSubject(parent); + const subject = parent.subject; if ( - sourceSubject != null && - (sourceSubject.format !== "file" || - sourceSubject.local_path !== report.artifact.path || - sourceSubject.digest.sha256 !== report.artifact.sha256) + subject != null && + (subject.format !== "file" || + subject.digest.sha256 !== report.artifact.sha256) ) return err( analysisViewInputError([ diff --git a/src/domain/analysisView/javascriptRoutesView.test.ts b/src/domain/analysisView/javascriptRoutesView.test.ts index 20cbde9e36..c58af1afa7 100644 --- a/src/domain/analysisView/javascriptRoutesView.test.ts +++ b/src/domain/analysisView/javascriptRoutesView.test.ts @@ -179,7 +179,7 @@ it("checks subject identity again after warming an immutable parent", () => { subject: { name: "app", local_path: "/different", - digest: { sha256: "c".repeat(64) }, + digest: { sha256: "d".repeat(64) }, format: "javascript-bundle", architecture: null, }, diff --git a/src/domain/analysisView/javascriptRoutesView.ts b/src/domain/analysisView/javascriptRoutesView.ts index 38277e7756..1b2974ff5c 100644 --- a/src/domain/analysisView/javascriptRoutesView.ts +++ b/src/domain/analysisView/javascriptRoutesView.ts @@ -1,4 +1,3 @@ -import { analysisViewSourceSubject } from "./analysisViewParent.js"; import { javascriptSourceRangeSchema } from "../javascript/javascriptSourceCoordinates.js"; import { z } from "zod"; import { webBundleViewRequestSchema } from "./webBundleView.js"; @@ -345,12 +344,8 @@ export const projectJavaScriptRoutesView = ( path: identity.data.input_path, sha256: identity.data.root_artifact_sha256, }; - const sourceSubject = analysisViewSourceSubject(parent); - if ( - sourceSubject != null && - (sourceSubject.local_path !== artifact.path || - sourceSubject.digest.sha256 !== artifact.sha256) - ) + const subject = parent.subject; + if (subject != null && subject.digest.sha256 !== artifact.sha256) return err( inputError( "Parent subject does not match its application artifact identity.", diff --git a/src/domain/analysisView/modelArtifactView.ts b/src/domain/analysisView/modelArtifactView.ts index 39609edb4e..3b96c9b7ee 100644 --- a/src/domain/analysisView/modelArtifactView.ts +++ b/src/domain/analysisView/modelArtifactView.ts @@ -10,10 +10,7 @@ import { } from "../model/modelArtifact.js"; import type { AnalysisError } from "../analysisErrorBase.js"; import { analysisViewInputError } from "./analysisViewErrors.js"; -import { - analysisViewSourceSubject, - createAnalysisViewResultReader, -} from "./analysisViewParent.js"; +import { createAnalysisViewResultReader } from "./analysisViewParent.js"; import { isImmutableJsonSnapshot } from "../immutableJson.js"; import type { JsonValue } from "../jsonValue.js"; import { err, ok, type Result } from "../result.js"; @@ -145,12 +142,8 @@ const read = (parent: AnalysisViewParent): Result => { const report = parsed.value; const reusable = isImmutableJsonSnapshot(report); const cached = reusable ? checked.get(report) : undefined; - const sourceSubject = analysisViewSourceSubject(parent); - if ( - sourceSubject != null && - (sourceSubject.local_path !== report.artifact.path || - sourceSubject.digest.sha256 !== report.artifact.sha256) - ) + const subject = parent.subject; + if (subject != null && subject.digest.sha256 !== report.artifact.sha256) return err( analysisViewInputError([ { diff --git a/src/domain/analysisView/pcapCaptureView.ts b/src/domain/analysisView/pcapCaptureView.ts index bd6bbbe006..5ae4095d88 100644 --- a/src/domain/analysisView/pcapCaptureView.ts +++ b/src/domain/analysisView/pcapCaptureView.ts @@ -1,10 +1,7 @@ import { analysisViewInputError } from "./analysisViewErrors.js"; import { z } from "zod"; import { pcapEvidenceSchemas } from "../pcap/pcapAnalysis.js"; -import { - analysisViewSourceSubject, - parseAnalysisViewResult, -} from "./analysisViewParent.js"; +import { parseAnalysisViewResult } from "./analysisViewParent.js"; import type { AnalysisError } from "../analysisErrorBase.js"; import { err, ok, type Result } from "../result.js"; import type { @@ -116,18 +113,14 @@ const parseParent = ( ): Result => { const parsed = parseAnalysisViewResult(captureSchema, parent); if (!parsed.ok) return parsed; - const sourceSubject = analysisViewSourceSubject(parent); - if ( - sourceSubject?.local_path !== parsed.value.target.path || - sourceSubject.digest.sha256 !== parsed.value.target.sha256 - ) + const subject = parent.subject; + if (subject == null || subject.digest.sha256 !== parsed.value.target.sha256) return err( analysisViewInputError([ { path: ["source", "subject"], reason: "invalid_value", - message: - "Packet parent subject must identify the report's exact path and SHA-256.", + message: "Packet parent subject must identify the report's SHA-256.", }, ]), ); diff --git a/src/domain/analysisView/protobufCaptureView.test.ts b/src/domain/analysisView/protobufCaptureView.test.ts index 04d613a5a5..d0619f31b7 100644 --- a/src/domain/analysisView/protobufCaptureView.test.ts +++ b/src/domain/analysisView/protobufCaptureView.test.ts @@ -207,7 +207,10 @@ it("omits large values by default, returns requested text inline and preserves p items: [{ text: "hello", body_status: "partial" }], }); const changedSubject = projectAnalysisView( - { ...parent, subject: { ...parent.subject!, local_path: "/other.har" } }, + { + ...parent, + subject: { ...parent.subject!, digest: { sha256: "e".repeat(64) } }, + }, { kind: "protobuf", collection: "bodies", offset: 0, limit: 1 }, ); expect(changedSubject.ok).toBe(false); diff --git a/src/domain/analysisView/rustBinaryView.test.ts b/src/domain/analysisView/rustBinaryView.test.ts index 15aafbedf7..065b86a6fd 100644 --- a/src/domain/analysisView/rustBinaryView.test.ts +++ b/src/domain/analysisView/rustBinaryView.test.ts @@ -111,7 +111,6 @@ it.each([false, true])( it("checks exact artifact identity and subject architecture for every Rust view", () => { for (const changed of [ - { local_path: "/different" }, { format: "pe" as const }, { architecture: "arm64" as const }, { digest: { sha256: "b".repeat(64) } }, diff --git a/src/domain/analysisView/rustBinaryView.ts b/src/domain/analysisView/rustBinaryView.ts index 2560bca419..5354e2441e 100644 --- a/src/domain/analysisView/rustBinaryView.ts +++ b/src/domain/analysisView/rustBinaryView.ts @@ -20,10 +20,7 @@ import { pageViewCoverage, } from "./analysisViewCoverage.js"; import { analysisViewInputError } from "./analysisViewErrors.js"; -import { - analysisViewSourceSubject, - createAnalysisViewResultReader, -} from "./analysisViewParent.js"; +import { createAnalysisViewResultReader } from "./analysisViewParent.js"; import { selectViewRows } from "./selectViewRows.js"; const count = z.number().int().nonnegative(); @@ -126,10 +123,9 @@ export const projectRustBinaryView = ( const parsed = parseReport(parent); if (!parsed.ok) return parsed; const report = parsed.value; - const subject = analysisViewSourceSubject(parent); + const subject = parent.subject; if ( subject == null || - subject.local_path !== report.artifact.path || subject.digest.sha256 !== report.artifact.sha256 || subject.format !== metadataReportSubjectFormat(report) || subject.architecture !== diff --git a/src/domain/analysisView/webNetworkCaptureView.test.ts b/src/domain/analysisView/webNetworkCaptureView.test.ts index ea87b86863..f55e907a94 100644 --- a/src/domain/analysisView/webNetworkCaptureView.test.ts +++ b/src/domain/analysisView/webNetworkCaptureView.test.ts @@ -93,7 +93,7 @@ it("checks each parent's subject even after its immutable capture has been valid projectAnalysisView( { ...source, - subject: { ...source.subject, local_path: "/different.saz" }, + subject: { ...source.subject, digest: { sha256: "e".repeat(64) } }, }, { kind: "summary" }, ).ok, @@ -154,7 +154,7 @@ it("keeps selected-record page coverage distinct from capture coverage and unsaf }); }); -it("retains explicitly unknown digests and rejects a different subject path", () => { +it("retains explicitly unknown digests and rejects a different subject digest", () => { const { parent, report } = capture(); const unknown = projectAnalysisView( { @@ -177,7 +177,7 @@ it("retains explicitly unknown digests and rejects a different subject path", () const mismatched = projectAnalysisView( { ...parent, - subject: { ...parent.subject, local_path: "/different/source.saz" }, + subject: { ...parent.subject, digest: { sha256: "e".repeat(64) } }, }, { kind: "summary" }, ); diff --git a/src/domain/analysisView/webNetworkCaptureView.ts b/src/domain/analysisView/webNetworkCaptureView.ts index 565aac344b..eabee83134 100644 --- a/src/domain/analysisView/webNetworkCaptureView.ts +++ b/src/domain/analysisView/webNetworkCaptureView.ts @@ -3,10 +3,7 @@ import { analysisViewInputError } from "./analysisViewErrors.js"; import { createHash } from "node:crypto"; import { z } from "zod"; import type { AnalysisError } from "../analysisErrorBase.js"; -import { - analysisViewSourceSubject, - parseAnalysisViewResult, -} from "./analysisViewParent.js"; +import { parseAnalysisViewResult } from "./analysisViewParent.js"; import type { JsonValue } from "../jsonValue.js"; import { err, ok, type Result } from "../result.js"; import { @@ -425,20 +422,17 @@ const parseCaptureParent = ( const parsed = parseAnalysisViewResult(captureParser, parent); if (!parsed.ok) return parsed; const report = parsed.value; - const sourceSubject = analysisViewSourceSubject(parent); + const subject = parent.subject; if ( report.artifact.sha256 !== null && - (sourceSubject == null || - sourceSubject.local_path !== report.artifact.path || - sourceSubject.digest.sha256 !== report.artifact.sha256) + (subject == null || subject.digest.sha256 !== report.artifact.sha256) ) return err( analysisViewInputError([ { path: ["source", "subject"], reason: "invalid_value", - message: - "Capture parent subject must identify the report's exact path and SHA-256.", + message: "Capture parent subject must identify the report's SHA-256.", }, ]), );