Files
reverse-engineering-anything/src/wasm/WabtRuntime.ts
T
morluto 22a0746f90 fix(wasm): retain cleanup ownership through WABT recovery
Bind runtime and descriptor cleanup to exact retry leases and retain command supervisors until stop is confirmed. Preserve primary errors, partial Evidence and finalization cancellation reasons.

Validate with 88 focused tests, official WABT 1.0.42 CLI/MCP verifier (51 cases), build, static and documentation checks. Command stop failures use controlled source seams; real permission failures and target execution are unverified.

Fixes #4787

Fixes #4798
2026-10-17 21:02:50 +08:00

85 lines
2.8 KiB
TypeScript

import {
AnalysisCleanupLease,
AnalysisCleanupLeaseGroup,
} from "../artifacts/AnalysisCleanupLease.js";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import type { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import type { AnalysisError } from "../domain/analysisErrorBase.js";
import type { PrivateRuntimeRoot } from "../process/PrivateRuntimeRoot.js";
import type { ProviderProcessSupervisor } from "../process/ProviderProcess.js";
import { OwnedCommandFailure } from "../process/OwnedCommand.js";
/** A private runtime cannot be removed until every retained command has stopped. */
export class WabtRuntime {
readonly lease: AnalysisCleanupLease;
readonly path: string;
readonly #commands = new Map<
ProviderProcessSupervisor,
AnalysisCleanupLease
>();
#unknownCommand: string | undefined;
constructor(root: Pick<PrivateRuntimeRoot, "path" | "close">) {
this.path = root.path;
this.lease = new AnalysisCleanupLease(async () => {
const pending = [...this.#commands.values()].filter(
(lease) => !lease.released,
);
if (pending.length > 0 || this.#unknownCommand !== undefined)
throw new ArtifactReaderFailure(
"unavailable",
"WABT commands must stop before runtime removal",
{
cleanup: {
reason:
this.#unknownCommand ??
"WABT command cleanup remains incomplete",
resources: [
...new Set([
root.path,
...pending.flatMap((lease) => lease.cleanupResources ?? []),
]),
],
},
},
);
await root.close();
}, [root.path]);
}
/** Adopt only the supervisor supplied by the command boundary, never a PID or label. */
retainCommandFailure(
cause: unknown,
failure: AnalysisError,
resources: ArtifactResourceScope,
): void {
if (
!(cause instanceof OwnedCommandFailure) ||
cause.cleanupFailure === null
)
return;
const owner = cause.cleanupOwner;
if (owner === undefined) {
this.#unknownCommand = cause.cleanupFailure;
return;
}
let lease = this.#commands.get(owner);
if (lease === undefined) {
lease = new AnalysisCleanupLease(async () => {
const stopped = await owner.stop();
if (stopped.status === "incomplete")
throw new ArtifactReaderFailure("unavailable", stopped.reason, {
cleanup: { reason: stopped.reason, resources: cause.resources },
});
}, cause.resources);
this.#commands.set(owner, lease);
resources.retain({
kind: "lease",
resource: cause.resources[0] ?? this.path,
lease,
});
}
new AnalysisCleanupLeaseGroup(failure, [lease]);
}
}