fix(publication)!: retain cleanup owners across export workflows

Require an ArtifactResourceScope for web script exports and share its lifetime with CLI or MCP shutdown. Retain setup-failed output trees in firmware and Wakaru providers. Preserve completed publications when descriptor cleanup fails, returning the full result after a successful retry or as a partial observation while cleanup remains unresolved.
This commit is contained in:
morluto
2026-10-11 05:16:42 +08:00
parent 3f8c51e9b5
commit be75ed5e4b
15 changed files with 801 additions and 166 deletions
+8 -7
View File
@@ -4,6 +4,7 @@ import { isAbsolute } from "node:path";
import { z } from "zod";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import type { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import { publishWebScripts } from "../browser/assets/PublishWebScripts.js";
import { selectScriptCapture } from "../browser/assets/ScriptCaptureAdapters.js";
import {
@@ -37,11 +38,12 @@ const OPERATION = "export_web_scripts";
/** Export one local capture through the shared CLI/MCP application workflow. */
export const exportWebScripts = async (
rawInput: unknown,
resources: ArtifactResourceScope,
options: ExecutionOptions = {},
): Promise<Result<Evidence, AnalysisError>> => {
const input = exportWebScriptsInputSchema.safeParse(rawInput);
return input.success
? exportWebScriptsValidated(input.data, options)
? exportWebScriptsValidated(input.data, resources, options)
: err(
analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput),
);
@@ -50,6 +52,7 @@ export const exportWebScripts = async (
/** Publish input already parsed by a named adapter contract. */
export const exportWebScriptsValidated = async (
input: ExportWebScriptsInput,
resources: ArtifactResourceScope,
options: ExecutionOptions = {},
): Promise<Result<Evidence, AnalysisError>> => {
if (!isAbsolute(input.capture_path) || !isAbsolute(input.output_directory))
@@ -72,12 +75,10 @@ export const exportWebScriptsValidated = async (
if (!loaded.ok) return loaded;
options.signal?.throwIfAborted();
const sha256 = createHash("sha256").update(bytes).digest("hex");
const result = await publishWebScripts(
input,
loaded.value,
sha256,
options.signal,
);
const result = await publishWebScripts(input, loaded.value, sha256, {
resources,
...(options.signal === undefined ? {} : { signal: options.signal }),
});
return ok(
createEvidence(
{ path: input.capture_path, sha256, format: "file" },