From 58fb8241055b326d9dc40652e6859893587c7a86 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sun, 25 Oct 2026 05:01:36 +0800 Subject: [PATCH] refactor(evidence): centralize JSON payload admission Give ordinary Evidence creation an explicit unknown-payload input contract while retaining the strong JSON contract for producer helpers and cooperative immutable creation. Keep schema admission, input snapshotting and identity hashing with the Evidence owner. Remove 32 generic JSON preparses that only fed ordinary Evidence construction. Preserve semantic format validation, selected-source bindings, standalone JSON boundaries, typed executions and scenario digest contracts. Invalid JSON now reports its Evidence parameters/normalized_result/raw_result prefix. Validation: fresh runtime/catalog build, 560 focused and README tests passed, 4 platform-specific cases skipped, full static/docs checks, test discovery, duplication and diff checks. New regressions cover omitted semantic optionals, raw-result defaults and nested undefined refusal without input mutation. Installed providers, real browsers/Frida/JADX and Linux-only recovery remain unverified; boundary providers are authored fixtures. --- docs/tool-design.md | 11 +++ src/application/BrowserEvidence.ts | 3 +- src/application/BrowserScenarioEvidence.ts | 4 +- .../ReconstructionObligationLedger.fixture.ts | 15 +-- src/application/WebModuleTraceService.ts | 2 +- src/application/WebRuntimeService.ts | 3 +- src/application/WebScriptExportService.ts | 3 +- src/application/WebSourceLocationService.ts | 3 +- .../analysisView/AnalysisViewService.ts | 5 +- .../android/AndroidAnalysisService.ts | 3 +- .../frida/FridaInstrumentationService.ts | 5 +- .../javascript/ElectronActiveEvidence.ts | 4 +- .../javascript/JavaScriptRecoveryService.ts | 5 +- .../JavaScriptRuntimeObservationEvidence.ts | 3 +- src/application/process/ProcessEvidence.ts | 3 +- .../sqlite/SqliteDatabaseService.ts | 3 +- src/contracts/investigationExamples.ts | 5 +- src/domain/analysisObservation.ts | 4 +- src/domain/evidence.test.ts | 96 +++++++++++++++++++ src/domain/evidence.ts | 24 +++-- src/domain/pcap/pcapEvidence.ts | 4 +- .../process/processComparisonEvidence.ts | 5 +- src/server/registerArtifactComparisonTool.ts | 3 +- src/server/registerBundleComparisonTool.ts | 3 +- src/server/registerFunctionComparisonTool.ts | 3 +- src/server/registerInvestigationTools.ts | 9 +- 26 files changed, 162 insertions(+), 69 deletions(-) diff --git a/docs/tool-design.md b/docs/tool-design.md index 5c2d3b7391..1fb7e2d901 100644 --- a/docs/tool-design.md +++ b/docs/tool-design.md @@ -241,6 +241,17 @@ Put provider protocols/parsing in adapters and shared CLI/MCP workflows in the application layer. Share Evidence provenance, unknown projection, and session retention semantics; matching payloads alone do not establish parity. +Let `createEvidence` own JSON validation and snapshotting of ordinary observation +payloads. `EvidenceObservationInput` accepts semantically parsed parameters, +results, and raw results whose JSON validity still needs admission. Keep format, +selection, and identity validation at their producing boundary. Invalid JSON +issues are rooted at `parameters`, `normalized_result`, or `raw_result` in the +Evidence envelope. + +`EvidenceObservation` retains the stronger JSON contract used by producer +helpers and `createImmutableEvidenceSteps`. The cooperative constructor also +requires an authenticated immutable result; preserve that ownership proof. + Measure the resource before choosing a budget and enforce it before the costly step. Account for value alternatives, trace-frame products, retained records, serialization expansion, and temporary-file growth separately. Derive facet diff --git a/src/application/BrowserEvidence.ts b/src/application/BrowserEvidence.ts index 311d8ac549..a704a6b0e0 100644 --- a/src/application/BrowserEvidence.ts +++ b/src/application/BrowserEvidence.ts @@ -4,7 +4,6 @@ import { type Evidence, type EvidenceObservation, } from "../domain/evidence.js"; -import { jsonValueSchema } from "../domain/jsonValue.js"; import { sanitizeBrowserUrl } from "../domain/browserObservation.js"; import type { InspectWebPageInput, @@ -77,7 +76,7 @@ export const createBrowserEvidence = ( predicateType: browserPredicate(operation), operation, parameters: browserParameters(input, result), - result: jsonValueSchema.parse(result), + result, confidence: "observed", authority: "external-service", environment: diff --git a/src/application/BrowserScenarioEvidence.ts b/src/application/BrowserScenarioEvidence.ts index 5dae07c218..7fd9c2bd2e 100644 --- a/src/application/BrowserScenarioEvidence.ts +++ b/src/application/BrowserScenarioEvidence.ts @@ -7,7 +7,7 @@ import { type Evidence, type EvidenceObservation, } from "../domain/evidence.js"; -import { jsonObjectSchema, jsonValueSchema } from "../domain/jsonValue.js"; +import { jsonObjectSchema } from "../domain/jsonValue.js"; const browserScenarioParameters = ( scenario: BrowserScenario, @@ -26,7 +26,7 @@ export const createBrowserScenarioEvidence = ( predicateType: "rea.browser-scenario-capture", operation: "capture_browser_scenario", parameters: browserScenarioParameters(scenario), - result: jsonValueSchema.parse(capture), + result: capture, confidence: "observed", authority: "controlled-replay", environment: { diff --git a/src/application/ReconstructionObligationLedger.fixture.ts b/src/application/ReconstructionObligationLedger.fixture.ts index ca1da93d09..5d2e422592 100644 --- a/src/application/ReconstructionObligationLedger.fixture.ts +++ b/src/application/ReconstructionObligationLedger.fixture.ts @@ -12,7 +12,6 @@ import { createEvidenceBundle, type EvidenceBundle, } from "../domain/evidenceBundle.js"; -import { jsonValueSchema } from "../domain/jsonValue.js"; import { reconstructionObligationLedgerSchema, type ReconstructionObligationLedgerInput, @@ -47,12 +46,10 @@ export const processEvidence = ( predicateType: "rea.process-capture", operation: "capture_process_scenario", parameters: {}, - result: jsonValueSchema.parse( - processCaptureSchema.parse({ - ...EMPTY_PROCESS_CAPTURE_EXAMPLE, - ...overrides, - }), - ), + result: processCaptureSchema.parse({ + ...EMPTY_PROCESS_CAPTURE_EXAMPLE, + ...overrides, + }), confidence: "observed", authority: "controlled-replay", }); @@ -70,9 +67,7 @@ export const boundProofEvidence = ( predicateType: "rea.reconstruction-proof", operation: "verify_reconstruction_obligations", parameters: {}, - result: jsonValueSchema.parse( - reconstructionObligationProofSchema.parse(result), - ), + result: reconstructionObligationProofSchema.parse(result), confidence: "observed", authority, }, diff --git a/src/application/WebModuleTraceService.ts b/src/application/WebModuleTraceService.ts index b5f5878a67..ee57889e7a 100644 --- a/src/application/WebModuleTraceService.ts +++ b/src/application/WebModuleTraceService.ts @@ -261,7 +261,7 @@ export class WebModuleTraceService { }, { operation: OPERATION, - parameters: jsonObjectSchema.parse(input), + parameters: input, result: normalized.data, rawResult: resolution?.rawResult ?? null, confidence: "derived", diff --git a/src/application/WebRuntimeService.ts b/src/application/WebRuntimeService.ts index acdacb0f32..64dd8d4d3b 100644 --- a/src/application/WebRuntimeService.ts +++ b/src/application/WebRuntimeService.ts @@ -7,7 +7,6 @@ import { AnalysisOutputError, } from "../domain/analysisErrorCore.js"; import { createEvidence, type Evidence } from "../domain/evidence.js"; -import { jsonObjectSchema } from "../domain/jsonValue.js"; import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { err, ok, type Result } from "../domain/result.js"; import { @@ -162,7 +161,7 @@ export class WebRuntimeService { createEvidence(undefined, this.provider.identity(), { predicateType: `rea.${operation.replaceAll("_", "-")}`, operation, - parameters: jsonObjectSchema.parse(input), + parameters: input, result, confidence: "observed", authority: "external-service", diff --git a/src/application/WebScriptExportService.ts b/src/application/WebScriptExportService.ts index c81617304f..c0a52b351a 100644 --- a/src/application/WebScriptExportService.ts +++ b/src/application/WebScriptExportService.ts @@ -21,7 +21,6 @@ import { AnalysisError } from "../domain/analysisErrorBase.js"; import { createEvidence, type Evidence } from "../domain/evidence.js"; import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js"; import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; -import { jsonValueSchema } from "../domain/jsonValue.js"; import { ProviderAdapterError } from "../domain/providerAdapterError.js"; import { ProviderCleanupError } from "../domain/providerCleanupError.js"; import { err, ok, type Result } from "../domain/result.js"; @@ -136,7 +135,7 @@ export const exportWebScriptsValidated = async ( capture_path: input.capture_path, output_directory: input.output_directory, }, - result: jsonValueSchema.parse(result), + result, rawResult: null, confidence: "derived", authority: "historical-reference", diff --git a/src/application/WebSourceLocationService.ts b/src/application/WebSourceLocationService.ts index b40ad21870..1a5ad8325b 100644 --- a/src/application/WebSourceLocationService.ts +++ b/src/application/WebSourceLocationService.ts @@ -9,7 +9,6 @@ import { } from "../domain/analysisErrorCore.js"; import { createEvidence, type Evidence } from "../domain/evidence.js"; import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; -import { jsonObjectSchema } from "../domain/jsonValue.js"; import { err, ok, type Result } from "../domain/result.js"; import { selectedWebScriptArtifactsSchema, @@ -171,7 +170,7 @@ export class WebSourceLocationService { }, { operation: OPERATION, - parameters: jsonObjectSchema.parse(input), + parameters: input, result: normalized.data, rawResult: { source_map_text: map.text }, confidence: "derived", diff --git a/src/application/analysisView/AnalysisViewService.ts b/src/application/analysisView/AnalysisViewService.ts index 2d83a52a0b..55e614be31 100644 --- a/src/application/analysisView/AnalysisViewService.ts +++ b/src/application/analysisView/AnalysisViewService.ts @@ -2,7 +2,6 @@ import { setImmediate } from "node:timers/promises"; import { dispatchQueuedInterrupts } from "../../process/queuedInterrupts.js"; import { readImmutableEvidenceBundle } from "../EvidenceBundleFiles.js"; import { INSPECT_ANALYSIS_VIEW_OPERATION } from "../../domain/analysisView/analysisViewErrors.js"; -import { jsonValueSchema } from "../../domain/jsonValue.js"; import { ANALYSIS_VIEW_PROVIDER } from "../../domain/investigationProviders.js"; import type { EvidenceLookup } from "../EvidenceInputResolver.js"; @@ -346,9 +345,9 @@ const createAnalysisViewEvidence = ( operation: INSPECT_ANALYSIS_VIEW_OPERATION, parameters: { parent_evidence_id: parent.evidence_id, - view: jsonValueSchema.parse(input.view), + view: input.view, }, - result: jsonValueSchema.parse(result), + result, rawResult: null, confidence: // Grouping is whole-capture, so any templated group or parameter makes the result inferred; diff --git a/src/application/android/AndroidAnalysisService.ts b/src/application/android/AndroidAnalysisService.ts index f31dff0dc7..12433b3311 100644 --- a/src/application/android/AndroidAnalysisService.ts +++ b/src/application/android/AndroidAnalysisService.ts @@ -11,7 +11,6 @@ import { import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js"; import type { AnalysisError } from "../../domain/analysisErrorBase.js"; import { createEvidence, type Evidence } from "../../domain/evidence.js"; -import { jsonObjectSchema } from "../../domain/jsonValue.js"; import { err, type Result } from "../../domain/result.js"; import { completeProviderEvidence } from "../ProviderOperationExecution.js"; @@ -62,7 +61,7 @@ export class AndroidAnalysisService { execution.provider, { operation, - parameters: jsonObjectSchema.parse(request.data.input), + parameters: request.data.input, result: execution.result, rawResult: execution.rawResult, limitations: execution.limitations, diff --git a/src/application/frida/FridaInstrumentationService.ts b/src/application/frida/FridaInstrumentationService.ts index 685347bd30..b0f52f7c67 100644 --- a/src/application/frida/FridaInstrumentationService.ts +++ b/src/application/frida/FridaInstrumentationService.ts @@ -3,7 +3,6 @@ import { access, stat } from "node:fs/promises"; import { setTimeout as delay } from "node:timers/promises"; import { createEvidence, type Evidence } from "../../domain/evidence.js"; -import { jsonObjectSchema, jsonValueSchema } from "../../domain/jsonValue.js"; import { resolvedAnalysisError, type AnalysisError, @@ -458,8 +457,8 @@ const fridaEvidence = ( { predicateType: "rea.frida.instrumentation-observation", operation, - parameters: jsonObjectSchema.parse(parameters), - result: jsonValueSchema.parse(result), + parameters, + result, ...(script?.sourceRepresentation === undefined ? {} : { diff --git a/src/application/javascript/ElectronActiveEvidence.ts b/src/application/javascript/ElectronActiveEvidence.ts index cbd844e263..ebf1569cda 100644 --- a/src/application/javascript/ElectronActiveEvidence.ts +++ b/src/application/javascript/ElectronActiveEvidence.ts @@ -4,7 +4,7 @@ import type { CanonicalElectronActiveObservationInput } from "./ElectronActiveOb import type { Evidence, EvidenceObservation } from "../../domain/evidence.js"; import { createEvidence } from "../../domain/evidence.js"; import type { ElectronActiveObservationResult } from "../../domain/javascript/electronActiveObservation.js"; -import { jsonObjectSchema, jsonValueSchema } from "../../domain/jsonValue.js"; +import { jsonObjectSchema } from "../../domain/jsonValue.js"; /** Commit the selected Electron experiment and its observed result. */ export const createElectronActiveEvidence = ( @@ -16,7 +16,7 @@ export const createElectronActiveEvidence = ( predicateType: "rea.electron-active-scenario", operation: "capture_electron_scenario", parameters: parameters(input), - result: jsonValueSchema.parse(result), + result, confidence: "observed", authority: "controlled-replay", environment: { diff --git a/src/application/javascript/JavaScriptRecoveryService.ts b/src/application/javascript/JavaScriptRecoveryService.ts index 5fa5624a9c..c5291f148d 100644 --- a/src/application/javascript/JavaScriptRecoveryService.ts +++ b/src/application/javascript/JavaScriptRecoveryService.ts @@ -11,7 +11,6 @@ import { javascriptRecoveryInputSchema, javascriptRecoveryResultSchema, } from "../../domain/javascript/javascriptRecovery.js"; -import { jsonObjectSchema, jsonValueSchema } from "../../domain/jsonValue.js"; import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js"; import { err, type Result } from "../../domain/result.js"; import type { ExecutionOptions } from "../AnalysisProvider.js"; @@ -115,8 +114,8 @@ export class JavaScriptRecoveryService { execution.value.provider, { operation: OPERATION, - parameters: jsonObjectSchema.parse(parsed.data), - result: jsonValueSchema.parse(result.data), + parameters: parsed.data, + result: result.data, rawResult: execution.value.rawResult, locations: execution.value.locations, limitations: execution.value.limitations, diff --git a/src/application/javascript/JavaScriptRuntimeObservationEvidence.ts b/src/application/javascript/JavaScriptRuntimeObservationEvidence.ts index a5f0d9a2a8..399f20b3f1 100644 --- a/src/application/javascript/JavaScriptRuntimeObservationEvidence.ts +++ b/src/application/javascript/JavaScriptRuntimeObservationEvidence.ts @@ -10,7 +10,6 @@ import type { ListJavaScriptRuntimeTargetsInput, ObserveJavaScriptRuntimeInput, } from "../../domain/javascript/javascriptRuntimeObservation.js"; -import { jsonValueSchema } from "../../domain/jsonValue.js"; type RuntimeObservationOperation = | "list_javascript_runtime_targets" @@ -30,7 +29,7 @@ export const createJavaScriptRuntimeObservationEvidence = ( : "rea.javascript-runtime-observation", operation, parameters: parameters(input), - result: jsonValueSchema.parse(result), + result, rawResult: null, confidence: "observed", authority: "external-service", diff --git a/src/application/process/ProcessEvidence.ts b/src/application/process/ProcessEvidence.ts index cee402e545..4fd233088a 100644 --- a/src/application/process/ProcessEvidence.ts +++ b/src/application/process/ProcessEvidence.ts @@ -10,7 +10,6 @@ import { PROCESS_PROVIDER, isProcessEvidenceProvider, } from "../../domain/process/processEvidenceProvider.js"; -import { jsonValueSchema } from "../../domain/jsonValue.js"; import { safeParseProcessCapture, type ProcessCapture, @@ -91,7 +90,7 @@ export const createProcessCaptureEvidence = ( predicateType: PROCESS_CAPTURE_EVIDENCE.predicate, operation: PROCESS_CAPTURE_EVIDENCE.operation, parameters: processEvidenceParameters(scenario, capture.manifest.platform), - result: jsonValueSchema.parse(capture), + result: capture, confidence: "observed", authority: "controlled-replay", environment: { diff --git a/src/application/sqlite/SqliteDatabaseService.ts b/src/application/sqlite/SqliteDatabaseService.ts index 187f008d1a..f1cc74eecd 100644 --- a/src/application/sqlite/SqliteDatabaseService.ts +++ b/src/application/sqlite/SqliteDatabaseService.ts @@ -23,7 +23,6 @@ import { } from "../../domain/sqlite/sqliteDatabase.js"; import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js"; import { err, ok, type Result } from "../../domain/result.js"; -import { jsonValueSchema } from "../../domain/jsonValue.js"; const OPERATION = "inspect_sqlite_database"; @@ -115,7 +114,7 @@ export class SqliteDatabaseService { createEvidence(execution.subject, execution.provider, { operation: OPERATION, parameters: sqliteInspectionParameters(input.data), - result: jsonValueSchema.parse(report.data), + result: report.data, ...(execution.rawResult === null ? {} : { rawResult: execution.rawResult }), diff --git a/src/contracts/investigationExamples.ts b/src/contracts/investigationExamples.ts index 293bf8d654..3fa2b1a826 100644 --- a/src/contracts/investigationExamples.ts +++ b/src/contracts/investigationExamples.ts @@ -1,5 +1,4 @@ import { createEvidence } from "../domain/evidence.js"; -import { jsonValueSchema } from "../domain/jsonValue.js"; import { FUNCTION_COMPARISON_PROVIDER } from "../domain/comparisonEvidenceIdentity.js"; import { compareProcessCaptureEvidence } from "../domain/process/processComparisonEvidence.js"; import { PROCESS_PROVIDER } from "../domain/process/processEvidenceProvider.js"; @@ -31,7 +30,7 @@ export const FUNCTION_COMPARISON_EVIDENCE = createEvidence( left_evidence_id: left.value.evidence.evidence_id, right_evidence_id: right.value.evidence.evidence_id, }, - result: jsonValueSchema.parse(comparison), + result: comparison, confidence: "derived", authority: "analyst-inference", limitations: comparison.limitations, @@ -48,7 +47,7 @@ const captureEvidence = (scenario: string) => predicateType: "rea.process-capture", operation: "capture_process_scenario", parameters: { scenario }, - result: jsonValueSchema.parse(capture), + result: capture, confidence: "observed", authority: "controlled-replay", environment: { diff --git a/src/domain/analysisObservation.ts b/src/domain/analysisObservation.ts index 06ed145a01..f43e8972a8 100644 --- a/src/domain/analysisObservation.ts +++ b/src/domain/analysisObservation.ts @@ -7,7 +7,7 @@ import { type EvidenceSubjectTarget, } from "./evidence.js"; import type { AnalysisProfileCommitment } from "./analysisProfile.js"; -import { jsonObjectSchema, type JsonValue } from "./jsonValue.js"; +import type { JsonValue } from "./jsonValue.js"; import type { RelationshipCoverage } from "./relationshipCoverage.js"; /** Provider observations that can be committed to Evidence. */ @@ -44,7 +44,7 @@ export const createAnalysisExecutionEvidence = ( execution.provider, { operation, - parameters: jsonObjectSchema.parse(input), + parameters: input, result: execution.result, rawResult: execution.rawResult, limitations: execution.limitations, diff --git a/src/domain/evidence.test.ts b/src/domain/evidence.test.ts index 2230190472..6d08843610 100644 --- a/src/domain/evidence.test.ts +++ b/src/domain/evidence.test.ts @@ -138,6 +138,102 @@ it("projects known provider fields and preserves open JSON payloads", () => { expect(parseEvidence(evidence)).toEqual(evidence); }); +it("admits typed semantic payloads with omitted optional facts without changing inputs or identity", () => { + interface Selection { + readonly procedure: string; + readonly address?: string; + } + interface SymbolRow { + readonly name: string; + readonly size?: number; + } + interface NativeRecord { + readonly producer: string; + readonly diagnostic?: string; + } + const parameters: Selection = { procedure: "main" }; + const row: SymbolRow = { name: "main" }; + const result = { symbols: [row] }; + const rawResult: NativeRecord = { producer: "authored fixture" }; + const evidence = createEvidence(TARGET, PROVIDER, { + operation: "inspect", + parameters, + result, + rawResult, + }); + const equivalent = createEvidence(TARGET, PROVIDER, { + operation: "inspect", + parameters: { procedure: "main" }, + result: { symbols: [{ name: "main" }] }, + rawResult: { producer: "authored fixture" }, + }); + + expect(evidence).toEqual(equivalent); + expect(evidence.evidence_id).toBe(equivalent.evidence_id); + expect(parameters).toEqual({ procedure: "main" }); + expect(result).toEqual({ symbols: [{ name: "main" }] }); + expect(result.symbols[0]).toBe(row); + expect(rawResult).toEqual({ producer: "authored fixture" }); + expect(Object.hasOwn(parameters, "address")).toBe(false); + expect(Object.hasOwn(row, "size")).toBe(false); + expect(Object.hasOwn(rawResult, "diagnostic")).toBe(false); + expect(Object.isFrozen(parameters)).toBe(false); + expect(Object.isFrozen(result)).toBe(false); + expect(Object.isFrozen(row)).toBe(false); + expect(Object.isFrozen(rawResult)).toBe(false); +}); + +it("keeps an omitted, undefined, or null raw payload equivalent", () => { + const observation = { operation: "inspect", parameters: {}, result: true }; + const omitted = createEvidence(TARGET, PROVIDER, observation); + const explicitUndefined = createEvidence(TARGET, PROVIDER, { + ...observation, + rawResult: undefined, + }); + const explicitNull = createEvidence(TARGET, PROVIDER, { + ...observation, + rawResult: null, + }); + + expect(omitted.raw_result).toBeNull(); + expect(explicitUndefined).toEqual(omitted); + expect(explicitNull).toEqual(omitted); +}); + +it.each([ + { field: "parameters", prefix: "parameters" }, + { field: "result", prefix: "normalized_result" }, + { field: "rawResult", prefix: "raw_result" }, +])( + "rejects explicit nested undefined in $field at its Evidence prefix", + ({ field, prefix }) => { + const invalid = { nested: { value: undefined } }; + let failure: unknown; + try { + createEvidence(TARGET, PROVIDER, { + operation: "inspect", + parameters: {}, + result: true, + [field]: invalid, + }); + } catch (cause: unknown) { + failure = cause; + } + + expect(failure).toMatchObject({ + name: "ZodError", + issues: [ + { + path: [prefix], + message: expect.stringContaining("JSON"), + }, + ], + }); + expect(Object.hasOwn(invalid.nested, "value")).toBe(true); + expect(invalid.nested.value).toBeUndefined(); + }, +); + it("preserves identity and bytes when reusing an owned immutable result", () => { const value = { nested: { values: ["observed", 1] } }; const mutable = createEvidence(TARGET, PROVIDER, { diff --git a/src/domain/evidence.ts b/src/domain/evidence.ts index bcbc959386..789537f564 100644 --- a/src/domain/evidence.ts +++ b/src/domain/evidence.ts @@ -242,13 +242,14 @@ type ExecutionEnvironment = z.infer; export type EvidenceProvider = Readonly>; -export interface EvidenceObservation { +/** Payloads admitted and snapshotted by the ordinary Evidence constructor. */ +export interface EvidenceObservationInput { readonly predicateType?: string; readonly operation: string; - readonly parameters: Readonly>; - readonly result: JsonValue; + readonly parameters: unknown; + readonly result: unknown; readonly analysisProfile?: AnalysisProfileCommitment; - readonly rawResult?: JsonValue; + readonly rawResult?: unknown; readonly confidence?: "observed" | "derived" | "inferred"; readonly authority?: EvidenceAuthority; readonly environment?: ExecutionEnvironment | null; @@ -260,6 +261,13 @@ export interface EvidenceObservation { readonly evidenceLinks?: readonly string[]; } +/** Producer facts that already promise JSON, including cooperative Evidence input. */ +export interface EvidenceObservation extends EvidenceObservationInput { + readonly parameters: Readonly>; + readonly result: JsonValue; + readonly rawResult?: JsonValue; +} + type EvidenceWithoutId = Omit; /** Content committed by an Evidence ID, excluding local request/display metadata. */ @@ -403,11 +411,11 @@ export interface EvidenceCreationOptions { readonly immutable?: boolean; } -/** Build deterministic Evidence from an immutable artifact subject. */ +/** Admit payloads into deterministic Evidence bound to an immutable artifact subject. */ export const createEvidence = ( target: EvidenceSubjectTarget | BinaryTarget | undefined, provider: EvidenceProvider, - observation: EvidenceObservation, + observation: EvidenceObservationInput, options?: EvidenceCreationOptions, ): Evidence => { const { normalized, sharedResult } = normalizeEvidenceObservation( @@ -460,7 +468,7 @@ export function* createImmutableEvidenceSteps( const normalizeEvidenceObservation = ( target: EvidenceSubjectTarget | BinaryTarget | undefined, provider: EvidenceProvider, - observation: EvidenceObservation, + observation: EvidenceObservationInput, ) => { const subject = target === undefined @@ -509,7 +517,7 @@ const normalizeEvidenceObservation = ( : { relationship_coverage: observation.relationshipCoverage }), locations: [...(observation.locations ?? [])], evidence_links: [...(observation.evidenceLinks ?? [])], - } satisfies JsonValue; + }; const sharedResult = typeof observation.result === "object" && observation.result !== null && diff --git a/src/domain/pcap/pcapEvidence.ts b/src/domain/pcap/pcapEvidence.ts index d2379ac75f..14892f533b 100644 --- a/src/domain/pcap/pcapEvidence.ts +++ b/src/domain/pcap/pcapEvidence.ts @@ -1,5 +1,5 @@ import { createEvidence } from "../evidence.js"; -import { jsonObjectSchema, type JsonValue } from "../jsonValue.js"; +import type { JsonValue } from "../jsonValue.js"; import type { PcapRequest } from "./pcapAnalysis.js"; /** Domain facts needed by both successful and retained partial observations. */ @@ -21,7 +21,7 @@ export const createPcapEvidence = ( ) => createEvidence(observation.subject ?? undefined, observation.provider, { operation: request.operation, - parameters: jsonObjectSchema.parse(request.input), + parameters: request.input, result: observation.result, rawResult: observation.rawResult, limitations: observation.limitations, diff --git a/src/domain/process/processComparisonEvidence.ts b/src/domain/process/processComparisonEvidence.ts index 4a27f80f51..2aa42ba9ee 100644 --- a/src/domain/process/processComparisonEvidence.ts +++ b/src/domain/process/processComparisonEvidence.ts @@ -1,5 +1,4 @@ import { createEvidence, type Evidence } from "../evidence.js"; -import { jsonValueSchema } from "../jsonValue.js"; import type { ProcessCapture } from "./processCaptureParsing.js"; import { compareProcessCaptures, @@ -37,9 +36,9 @@ export const compareProcessCaptureEvidence = ( : { max_capture_age_ms: options.maxCaptureAgeMs }), ...(options.traceSpecification === undefined ? {} - : { trace_spec: jsonValueSchema.parse(options.traceSpecification) }), + : { trace_spec: options.traceSpecification }), }, - result: jsonValueSchema.parse(comparison), + result: comparison, confidence: "derived", authority: "analyst-inference", limitations: comparison.limitations, diff --git a/src/server/registerArtifactComparisonTool.ts b/src/server/registerArtifactComparisonTool.ts index 3e9fe67a40..f21ddb8c7c 100644 --- a/src/server/registerArtifactComparisonTool.ts +++ b/src/server/registerArtifactComparisonTool.ts @@ -6,7 +6,6 @@ import { compareArtifacts } from "../domain/artifactComparison.js"; import { ARTIFACT_COMPARISON_PROVIDER } from "../domain/comparisonEvidenceIdentity.js"; import { createEvidence } from "../domain/evidence.js"; import { readEvidence } from "../domain/evidenceValidation.js"; -import { jsonValueSchema } from "../domain/jsonValue.js"; import { resolveArtifactComparisonEvidence } from "../application/artifacts/ArtifactComparisonEvidence.js"; import type { RecordUnknownInput } from "../domain/residualUnknown.js"; import { recordDerivedEvidence } from "./recordDerivedEvidence.js"; @@ -78,7 +77,7 @@ export const registerArtifactComparisonTool = ( left_evidence_ids: leftEvidenceIds, right_evidence_ids: rightEvidenceIds, }, - result: jsonValueSchema.parse(comparison), + result: comparison, confidence: "derived", authority: "analyst-inference", limitations: comparison.limitations, diff --git a/src/server/registerBundleComparisonTool.ts b/src/server/registerBundleComparisonTool.ts index 1a90b3e839..033f98a938 100644 --- a/src/server/registerBundleComparisonTool.ts +++ b/src/server/registerBundleComparisonTool.ts @@ -5,7 +5,6 @@ import { readImmutableEvidenceBundle } from "../application/EvidenceBundleFiles. import { toolContract } from "../contracts/toolContracts.js"; import { compareBundles } from "../domain/bundleComparison.js"; import { createEvidence } from "../domain/evidence.js"; -import { jsonValueSchema } from "../domain/jsonValue.js"; import { runDerivedOperation } from "./runDerivedOperation.js"; import { recordEvidenceSources } from "../application/investigation/recordEvidenceSources.js"; import { BUNDLE_COMPARISON_PROVIDER } from "../domain/investigationProviders.js"; @@ -82,7 +81,7 @@ export const registerBundleComparisonTool = ( right_bundle_sha256: comparison.right_bundle_sha256, record_pairs: input.record_pairs, }, - result: jsonValueSchema.parse(comparison), + result: comparison, confidence: "derived", authority: "analyst-inference", limitations: comparison.limitations, diff --git a/src/server/registerFunctionComparisonTool.ts b/src/server/registerFunctionComparisonTool.ts index 4817c183b5..5c036c4f9a 100644 --- a/src/server/registerFunctionComparisonTool.ts +++ b/src/server/registerFunctionComparisonTool.ts @@ -6,7 +6,6 @@ import { toolContract } from "../contracts/toolContracts.js"; import { createEvidence } from "../domain/evidence.js"; import { parseFunctionEvidence } from "../domain/functionDossierEvidence.js"; import { FUNCTION_COMPARISON_PROVIDER } from "../domain/comparisonEvidenceIdentity.js"; -import { jsonValueSchema } from "../domain/jsonValue.js"; import type { RecordUnknownInput } from "../domain/residualUnknown.js"; import { recordDerivedEvidence } from "./recordDerivedEvidence.js"; import { recordEvidenceSources } from "../application/investigation/recordEvidenceSources.js"; @@ -84,7 +83,7 @@ export const registerFunctionComparisonTool = ( left_evidence_id: leftEvidence.evidence_id, right_evidence_id: rightEvidence.evidence_id, }, - result: jsonValueSchema.parse(comparison), + result: comparison, confidence: "derived", authority: "analyst-inference", limitations: comparison.limitations, diff --git a/src/server/registerInvestigationTools.ts b/src/server/registerInvestigationTools.ts index 689b812e5c..dd211dcbe7 100644 --- a/src/server/registerInvestigationTools.ts +++ b/src/server/registerInvestigationTools.ts @@ -6,7 +6,6 @@ import { toolContract } from "../contracts/toolContracts.js"; import { buildCallPath } from "../domain/callPath.js"; import { findChangedBehavior } from "../domain/changedBehavior.js"; import { createEvidence } from "../domain/evidence.js"; -import { jsonValueSchema } from "../domain/jsonValue.js"; import { correlateStaticAndRuntime } from "../domain/staticRuntimeCorrelation.js"; import { comparisonClosure, @@ -88,7 +87,7 @@ const registerChangedBehavior = ( parameters: { comparison_evidence_ids: [...comparisonEvidenceIds], }, - result: jsonValueSchema.parse(result), + result, confidence: "derived", authority: "analyst-inference", limitations: result.limitations, @@ -161,7 +160,7 @@ const registerCallPath = ( start: input.start.address, goal: input.goal.address, }, - result: jsonValueSchema.parse(result), + result, confidence: "derived", authority: "analyst-inference", limitations: result.limitations, @@ -236,7 +235,7 @@ const registerStaticRuntime = ( parameters: { mapping_count: input.mappings.length, }, - result: jsonValueSchema.parse(result), + result, confidence: "inferred", authority: "analyst-inference", limitations: result.limitations, @@ -311,7 +310,7 @@ const registerReconstruction = ( ({ claim_id: id }) => id, ), }, - result: jsonValueSchema.parse(result), + result, confidence: "derived", authority: "analyst-inference", limitations: result.limitations,