* feat(web): export captured scripts for JavaScript application analysis Add capture adapters, conservative source path projection, verified exclusive publication and a shared CLI/MCP workflow. Retain source identities, missing content and competing URL versions in a complete manifest for existing static analysis. Refs: #774 Closes: #799 Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * test(web): include script export in independent effect audit Register the new contract family in the independent inventory audit and verify the filesystem, session, network and idempotence metadata of the named export contract. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(web): preserve capture limitation values in export manifests Align the export boundary with passive capture’s string contract instead of rejecting an admitted empty limitation. Cover the publisher boundary and regenerate canonical schemas and identities. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
124 lines
3.9 KiB
TypeScript
124 lines
3.9 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { browserCompletenessSchema } from "./browserCompleteness.js";
|
|
import { browserScenarioCompletenessSchema } from "./browserScenarioCaptureValues.js";
|
|
import { digestSchema, prefixedDigestSchema } from "./digests.js";
|
|
|
|
/** Export already captured scripts into one absent, caller-selected directory. */
|
|
export const exportWebScriptsInputSchema = z.strictObject({
|
|
capture_path: z
|
|
.string()
|
|
.min(1)
|
|
.describe(
|
|
"Absolute filesystem path to saved inspect_web_page or capture_browser_scenario JSON, normalized result or complete Evidence",
|
|
),
|
|
output_directory: z
|
|
.string()
|
|
.min(1)
|
|
.describe(
|
|
"Absolute absent filesystem directory for verified scripts and manifest; parent directory must exist",
|
|
),
|
|
});
|
|
export type ExportWebScriptsInput = z.output<
|
|
typeof exportWebScriptsInputSchema
|
|
>;
|
|
|
|
const sourceSchema = z.discriminatedUnion("kind", [
|
|
z.strictObject({
|
|
kind: z.literal("page-script"),
|
|
script_key: z.string().min(1),
|
|
frame_id: z.string().nullable(),
|
|
is_module: z.boolean(),
|
|
language: z.string().nullable(),
|
|
source_map_url: z.string().nullable(),
|
|
}),
|
|
z.strictObject({
|
|
kind: z.literal("scenario-response"),
|
|
transaction_id: z.string().nullable(),
|
|
request_sequence: z.number().int().min(1),
|
|
response_sequence: z.number().int().min(1).nullable(),
|
|
status: z.number().nullable(),
|
|
}),
|
|
]);
|
|
|
|
/** Exact retained-source identity and publication outcome. */
|
|
export const exportedWebScriptSchema = z.strictObject({
|
|
source: sourceSchema,
|
|
url: z.string(),
|
|
content: z.discriminatedUnion("state", [
|
|
z.strictObject({
|
|
state: z.literal("exported"),
|
|
relative_path: z.string().min(1),
|
|
layout: z.enum(["url-path", "isolated"]),
|
|
layout_reason: z.string().nullable(),
|
|
sha256: digestSchema,
|
|
bytes: z.number().int().min(0),
|
|
media_type: z.string().nullable(),
|
|
redacted: z.boolean().nullable(),
|
|
representation: z.enum([
|
|
"debugger-source-utf8",
|
|
"browser-decoded-response-bytes",
|
|
]),
|
|
}),
|
|
z.strictObject({
|
|
state: z.literal("unavailable"),
|
|
reason: z.string().min(1),
|
|
message: z.string().min(1),
|
|
}),
|
|
]),
|
|
});
|
|
export type ExportedWebScript = z.output<typeof exportedWebScriptSchema>;
|
|
|
|
/** Persisted manifest, also returned inline without duplicating script bytes. */
|
|
export const webScriptExportManifestSchema = z.strictObject({
|
|
capture_path: z.string().min(1),
|
|
capture_sha256: digestSchema,
|
|
capture_kind: z.enum(["page-inspection", "browser-scenario"]),
|
|
source_evidence_id: prefixedDigestSchema("ev").nullable(),
|
|
capture_completeness: z.union([
|
|
browserCompletenessSchema,
|
|
browserScenarioCompletenessSchema,
|
|
]),
|
|
output_directory: z.string().min(1),
|
|
analysis_input: z
|
|
.strictObject({
|
|
input_path: z.string().min(1),
|
|
format: z.literal("directory"),
|
|
})
|
|
.nullable(),
|
|
scripts: z.array(exportedWebScriptSchema),
|
|
limitations: z.array(z.string()),
|
|
});
|
|
|
|
/** Verified publication including the digest of the persisted manifest. */
|
|
export const webScriptExportResultSchema = webScriptExportManifestSchema.extend(
|
|
{
|
|
manifest: z.strictObject({
|
|
path: z.string().min(1),
|
|
sha256: digestSchema,
|
|
bytes: z.number().int().min(0),
|
|
}),
|
|
},
|
|
);
|
|
export type WebScriptExportResult = z.output<
|
|
typeof webScriptExportResultSchema
|
|
>;
|
|
|
|
/** Adapter input to the provider-neutral path planner and publisher. */
|
|
export interface CapturedWebScript {
|
|
readonly source: ExportedWebScript["source"];
|
|
readonly url: string;
|
|
readonly content:
|
|
| {
|
|
readonly state: "captured";
|
|
readonly bytes: Buffer;
|
|
readonly sha256: string;
|
|
readonly media_type: string | null;
|
|
readonly redacted: boolean | null;
|
|
readonly representation:
|
|
| "debugger-source-utf8"
|
|
| "browser-decoded-response-bytes";
|
|
}
|
|
| Extract<ExportedWebScript["content"], { state: "unavailable" }>;
|
|
}
|