mirror of
https://github.com/morluto/rea
synced 2026-10-28 06:46:47 -04:00
221 lines
6.7 KiB
TypeScript
221 lines
6.7 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { emptyArraySchema } from "./emptyArraySchema.js";
|
|
import { inspectWebPageWithSourceInputSchema } from "./browserObservation.js";
|
|
import { webTextArtifactSchema } from "./webContentArtifact.js";
|
|
|
|
/** Capture-and-analyze input with optional source-map fetching. */
|
|
export const analyzeWebBundleInputSchema =
|
|
inspectWebPageWithSourceInputSchema.safeExtend({
|
|
fetch_source_maps: z.boolean().default(false),
|
|
});
|
|
export type AnalyzeWebBundleInput = z.infer<typeof analyzeWebBundleInputSchema>;
|
|
|
|
const sourceLocationSchema = z.object({
|
|
script_key: z.string(),
|
|
line: z.number().int().min(1).nullable(),
|
|
column: z.number().int().min(0).nullable(),
|
|
});
|
|
|
|
const basisSchema = z.object({
|
|
script_key: z.string(),
|
|
artifact_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
line: z.number().int().min(1).nullable(),
|
|
column: z.number().int().min(0).nullable(),
|
|
detector: z.string(),
|
|
});
|
|
|
|
const findingSchema = z.object({
|
|
value: z.string(),
|
|
mechanism: z.string(),
|
|
location: sourceLocationSchema,
|
|
});
|
|
|
|
const originalSourceSchema = z.object({
|
|
source: z.string(),
|
|
artifact: webTextArtifactSchema.nullable(),
|
|
});
|
|
const originalModuleEdgeSchema = z.object({
|
|
from_source: z.string(),
|
|
kind: z.enum(["static_import", "dynamic_import", "require"]),
|
|
specifier: z.string(),
|
|
resolved_source: z.string().nullable(),
|
|
});
|
|
const sourceMapMappingSchema = z.object({
|
|
generated_line: z.number().int().min(1),
|
|
generated_column: z.number().int().min(0),
|
|
source: z.string(),
|
|
original_line: z.number().int().min(1),
|
|
original_column: z.number().int().min(0),
|
|
name: z.string().nullable(),
|
|
});
|
|
const sourceMapContextShape = {
|
|
script_key: z.string(),
|
|
declared_url: z.string(),
|
|
};
|
|
const parsedSourceMapShape = {
|
|
artifact: webTextArtifactSchema,
|
|
original_sources: z.array(originalSourceSchema),
|
|
original_module_edges: z.array(originalModuleEdgeSchema),
|
|
mappings: z.array(sourceMapMappingSchema),
|
|
};
|
|
|
|
const sourceMapSchema = z.union([
|
|
z.object({
|
|
...sourceMapContextShape,
|
|
...parsedSourceMapShape,
|
|
status: z.literal("included"),
|
|
limitation: z.null(),
|
|
}),
|
|
z.object({
|
|
...sourceMapContextShape,
|
|
...parsedSourceMapShape,
|
|
status: z.literal("partial"),
|
|
limitation: z.string(),
|
|
}),
|
|
z.object({
|
|
...sourceMapContextShape,
|
|
...parsedSourceMapShape,
|
|
status: z.enum(["fetch_failed", "invalid", "policy_filtered"]),
|
|
artifact: z.null(),
|
|
original_sources: emptyArraySchema,
|
|
original_module_edges: emptyArraySchema,
|
|
mappings: emptyArraySchema,
|
|
limitation: z.string(),
|
|
}),
|
|
]);
|
|
|
|
const webTextArtifactSummarySchema = z.object({
|
|
sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
bytes: z.number().int().min(0),
|
|
media_type: z.string().min(1),
|
|
charset: z.literal("utf-8"),
|
|
text_available: z.literal(true),
|
|
});
|
|
|
|
export const webSourceMapsSchema = z
|
|
.object({
|
|
status: z.enum(["not_requested", "included", "partial", "unavailable"]),
|
|
requested: z.number().int().min(0),
|
|
processed: z.number().int().min(0),
|
|
items: z.array(sourceMapSchema),
|
|
limitation: z.string().min(1).optional(),
|
|
})
|
|
.superRefine((sourceMaps, context) => {
|
|
if (
|
|
sourceMaps.processed > sourceMaps.requested ||
|
|
sourceMaps.processed !== sourceMaps.items.length
|
|
)
|
|
context.addIssue({
|
|
code: "custom",
|
|
message: "Source-map coverage counts are inconsistent",
|
|
});
|
|
if (
|
|
sourceMaps.processed < sourceMaps.requested &&
|
|
sourceMaps.limitation === undefined
|
|
)
|
|
context.addIssue({
|
|
code: "custom",
|
|
message:
|
|
"Unprocessed source maps require an explicit coverage limitation",
|
|
path: ["limitation"],
|
|
});
|
|
const statuses = sourceMaps.items.map(({ status }) => status);
|
|
const retained = statuses.filter(
|
|
(status) => status === "included" || status === "partial",
|
|
).length;
|
|
const allowedStatuses =
|
|
sourceMaps.processed < sourceMaps.requested
|
|
? retained === 0
|
|
? ["unavailable"]
|
|
: ["partial"]
|
|
: sourceMaps.items.length === 0
|
|
? ["not_requested", "unavailable"]
|
|
: retained === 0
|
|
? ["unavailable"]
|
|
: retained === sourceMaps.items.length &&
|
|
!statuses.includes("partial")
|
|
? ["included"]
|
|
: ["partial"];
|
|
if (!allowedStatuses.includes(sourceMaps.status))
|
|
context.addIssue({
|
|
code: "custom",
|
|
message: "Source-map status contradicts retained coverage",
|
|
path: ["status"],
|
|
});
|
|
});
|
|
|
|
export type WebSourceMapItem = z.infer<typeof sourceMapSchema>;
|
|
export type WebSourceMaps = z.infer<typeof webSourceMapsSchema>;
|
|
|
|
/** Provider-neutral result of JavaScript bundle reverse engineering. */
|
|
export const webBundleAnalysisSchema = z.object({
|
|
capture: z.object({
|
|
target_url: z.string(),
|
|
scripts_observed: z.number().int().min(0),
|
|
scripts_analyzed: z.number().int().min(0),
|
|
source_artifacts: z.array(webTextArtifactSummarySchema),
|
|
}),
|
|
observations: z.object({
|
|
chunks: z.object({
|
|
nodes: z.array(
|
|
z.object({
|
|
script_key: z.string(),
|
|
url: z.string(),
|
|
artifact_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
bytes: z.number().int().min(0),
|
|
}),
|
|
),
|
|
edges: z.array(
|
|
z.object({
|
|
from_script_key: z.string(),
|
|
kind: z.enum([
|
|
"static_import",
|
|
"dynamic_import",
|
|
"require",
|
|
"worker_import",
|
|
]),
|
|
specifier: z.string(),
|
|
resolved_url: z.string().nullable(),
|
|
location: sourceLocationSchema,
|
|
}),
|
|
),
|
|
}),
|
|
routes: z.array(findingSchema),
|
|
endpoints: z.array(findingSchema),
|
|
webmcp_declarations: z.array(
|
|
z.object({
|
|
name: z.string().nullable(),
|
|
description: z.string().nullable(),
|
|
schema_property_names: z.array(z.string()),
|
|
trust: z.literal("page-declared-untrusted"),
|
|
location: sourceLocationSchema,
|
|
}),
|
|
),
|
|
source_maps: webSourceMapsSchema,
|
|
}),
|
|
inferences: z.array(
|
|
z.object({
|
|
kind: z.enum(["vendor_fingerprint", "route_framework", "bundle_runtime"]),
|
|
value: z.string(),
|
|
confidence: z.enum(["low", "medium", "high"]),
|
|
basis: z.array(basisSchema).min(1),
|
|
}),
|
|
),
|
|
unknowns: z.array(
|
|
z.object({
|
|
dimension: z.string(),
|
|
reason: z.string(),
|
|
affected_script_keys: z.array(z.string()),
|
|
}),
|
|
),
|
|
completeness: z.object({
|
|
status: z.enum(["complete", "partial"]),
|
|
parsed_scripts: z.number().int().min(0),
|
|
parse_failures: z.number().int().min(0),
|
|
visited_ast_nodes: z.number().int().min(0),
|
|
}),
|
|
limitations: z.array(z.string()),
|
|
});
|
|
export type WebBundleAnalysis = z.infer<typeof webBundleAnalysisSchema>;
|