fix(web): retain admitted provenance through incomplete traces

This commit is contained in:
morluto committed 2026-10-27 02:35:54 +08:00
1 parent f6478c056f
commit b4b5c37ab3
7 files changed
+327 -37

No files matched your search

+31 -8
View File
@@ -1,7 +1,10 @@
import { closeOwnedWebResources } from "./closeOwnedWebResources.js";
import { isDeepStrictEqual } from "node:util";
import { z } from "zod";
import type { AnalysisError } from "../domain/analysisErrorBase.js";
import {
primaryAnalysisError,
type AnalysisError,
} from "../domain/analysisErrorBase.js";
import {
AnalysisCancelledError,
AnalysisCapabilityUnavailableError,
@@ -122,10 +125,28 @@ export class WebModuleTraceService {
const bound = validateSelectedWebScript(input, data, OPERATION);
if (!bound.ok) return bound;
const selected = bound.value;
const incomplete = (error: AnalysisError): Result<never, AnalysisError> => {
const resolverObservation =
webModuleResolutionObservationSchema.safeParse(
error.partialObservation,
);
error.partialObservation = {
...(resolverObservation.success
? { resolver_observation: resolverObservation.data }
: {}),
kind: "web-trace-observation",
analysis: "incomplete",
manifest: data.manifestFile,
source: data.sourceFile,
...(data.importMap === null ? {} : { import_map: data.importMap.file }),
};
primaryAnalysisError(error).partialObservation = error.partialObservation;
return err(error);
};
const importerUrl = input.importer_url ?? selected.url;
if (input.importer_url === undefined) {
const importerAdmission = admitContext(importerUrl);
if (!importerAdmission.ok) return importerAdmission;
if (!importerAdmission.ok) return incomplete(importerAdmission.error);
}
const syntax = collectWebModuleImports(data.source, data.sourceFile.path);
const literals = syntax.imports
@@ -135,7 +156,7 @@ export class WebModuleTraceService {
let resolutionFailure: AnalysisError | undefined;
if (literals.length > 0) {
if (options?.signal?.aborted)
return err(
return incomplete(
new AnalysisCancelledError(OPERATION, {
cause: options.signal.reason,
}),
@@ -157,7 +178,7 @@ export class WebModuleTraceService {
execution.value,
);
if (!report.success)
return err(
return incomplete(
new AnalysisOutputError(
OPERATION,
"Resolver returned malformed or incomplete native evidence.",
@@ -173,12 +194,12 @@ export class WebModuleTraceService {
!isDeepStrictEqual(observation.data.request, request) ||
observation.data.batch.resolutions.length !== literals.length
)
return execution;
return incomplete(execution.error);
resolution = observation.data.batch;
resolutionFailure = execution.error;
}
if (resolution.resolutions.length !== literals.length)
return err(
return incomplete(
new AnalysisOutputError(
OPERATION,
"Resolver did not return every selected literal specifier.",
@@ -196,7 +217,7 @@ export class WebModuleTraceService {
else {
const reported = resolution?.resolutions[literalIndex++];
if (reported === undefined)
return err(
return incomplete(
new AnalysisOutputError(
OPERATION,
"Resolver omitted a selected literal specifier.",
@@ -243,7 +264,9 @@ export class WebModuleTraceService {
limitations: LIMITATIONS,
});
if (!normalized.success)
return err(new AnalysisOutputError(OPERATION, normalized.error.message));
return incomplete(
new AnalysisOutputError(OPERATION, normalized.error.message),
);
const evidence = createEvidence(
{
path: data.sourceFile.path,