mirror of
https://github.com/morluto/rea.git
synced 2026-10-28 18:35:53 +00:00
* feat(javascript): recover readable modules with Wakaru Integrate pinned upstream source behind a typed optional recovery port, preserve source identity and producer provenance, and publish verified artifacts for existing JavaScript analysis through CLI and MCP. Add focused real-tool CI with genuine bundler fixtures and bounded resource, cancellation, cleanup, schema and packaged-artifact checks. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(javascript): align recovery catalog and browser verification Share canonical Wakaru provider identity, describe CLI mode options, and format the new workflow test. Disconnect the real page transport after its observation readiness progress instead of a fixed delay. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * refactor(javascript): consume the investigation evidence writer Verify that the SDK recovery handler records complete Evidence through the newly shared investigation owner. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) * chore(test): account for isolated recovery fixture compilers * fix(javascript): validate recovery paths before acquiring the engine Return field-specific invalid_format diagnostics for relative paths and verify compiled CLI/MCP parity even when the optional engine is unavailable. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
162 lines
5.3 KiB
TypeScript
162 lines
5.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { AnalysisCapabilityUnavailableError } from "../domain/analysisErrorCore.js";
|
|
import { err, ok } from "../domain/result.js";
|
|
import type {
|
|
JavaScriptRecoveryInput,
|
|
JavaScriptRecoveryResult,
|
|
} from "../domain/javascriptRecovery.js";
|
|
import { createAnalysisExecution } from "./AnalysisProvider.js";
|
|
import { JavaScriptRecoveryService } from "./JavaScriptRecoveryService.js";
|
|
|
|
const artifact = { path: "/tmp/bundle.js", sha256: "a".repeat(64), bytes: 20 };
|
|
const recoveryResult = (): JavaScriptRecoveryResult => ({
|
|
source: {
|
|
...artifact,
|
|
snapshot_path: "/tmp/work/inputs/bundle.js",
|
|
published_copy: artifact,
|
|
},
|
|
engine: {
|
|
id: "fixture-recovery",
|
|
version: "1",
|
|
executable: artifact,
|
|
audited_source_revision: "pinned",
|
|
executed_source_revision: null,
|
|
},
|
|
options: { extraction_mode: "structural", rewrite_level: "standard" },
|
|
status: "complete",
|
|
detected_formats: [],
|
|
reported_safety: "normal",
|
|
reported_strategy: "structural",
|
|
reported_format: "unknown",
|
|
total: 0,
|
|
failed: 0,
|
|
modules: [],
|
|
warnings: [],
|
|
report: artifact,
|
|
provenance: artifact,
|
|
manifest: artifact,
|
|
analysis_input: null,
|
|
runtime_equivalence: "unknown",
|
|
limitations: ["Not executed"],
|
|
});
|
|
const providerIdentity = {
|
|
id: "fixture-recovery",
|
|
name: "Fixture recovery",
|
|
version: "1",
|
|
};
|
|
const input = { path: artifact.path, output_directory: "/tmp/output" };
|
|
|
|
describe("JavaScript recovery workflow", () => {
|
|
it("passes normalized requests to a typed port and returns derived Evidence", async () => {
|
|
const received: JavaScriptRecoveryInput[] = [];
|
|
const service = new JavaScriptRecoveryService({
|
|
recover: async (request) => {
|
|
received.push(request);
|
|
return ok(
|
|
createAnalysisExecution(recoveryResult(), providerIdentity, {
|
|
subject: { ...artifact, format: "javascript" },
|
|
limitations: ["Not executed"],
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
const result = await service.recover(input);
|
|
if (!result.ok) throw result.error;
|
|
expect(received).toEqual([
|
|
{ ...input, extraction_mode: "structural", rewrite_level: "standard" },
|
|
]);
|
|
expect(result.value).toMatchObject({
|
|
operation: "recover_javascript_sources",
|
|
confidence: "derived",
|
|
authority: "shipped-artifact",
|
|
provider: providerIdentity,
|
|
});
|
|
});
|
|
it("preserves a missing provider reason", async () => {
|
|
const failure = new AnalysisCapabilityUnavailableError(
|
|
"fixture-recovery",
|
|
"recover_javascript_sources",
|
|
"fixture missing engine",
|
|
);
|
|
const result = await new JavaScriptRecoveryService({
|
|
recover: async () => err(failure),
|
|
}).recover(input);
|
|
expect(result).toEqual(err(failure));
|
|
});
|
|
it.each([
|
|
"invalid-result",
|
|
"missing-subject",
|
|
"subject-mismatch",
|
|
"engine-mismatch",
|
|
])("rejects %s returned across the port", async (mode) => {
|
|
const service = new JavaScriptRecoveryService({
|
|
recover: async () =>
|
|
ok(
|
|
createAnalysisExecution(
|
|
mode === "invalid-result" ? {} : recoveryResult(),
|
|
mode === "engine-mismatch"
|
|
? { ...providerIdentity, id: "another-engine" }
|
|
: providerIdentity,
|
|
mode === "missing-subject"
|
|
? {}
|
|
: {
|
|
subject: {
|
|
path:
|
|
mode === "subject-mismatch" ? "/wrong.js" : artifact.path,
|
|
sha256: artifact.sha256,
|
|
format: "javascript" as const,
|
|
},
|
|
},
|
|
),
|
|
),
|
|
});
|
|
const result = await service.recover(input);
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) throw new Error("expected invalid port output");
|
|
expect(result.error._tag).toBe("AnalysisOutputError");
|
|
});
|
|
it.each(["path", "output_directory"])(
|
|
"rejects relative %s before acquiring the recovery engine",
|
|
async (field) => {
|
|
let invoked = false;
|
|
const service = new JavaScriptRecoveryService({
|
|
recover: async () => {
|
|
invoked = true;
|
|
return err(
|
|
new AnalysisCapabilityUnavailableError(
|
|
"fixture",
|
|
"recover_javascript_sources",
|
|
"engine missing",
|
|
),
|
|
);
|
|
},
|
|
});
|
|
const result = await service.recover({
|
|
...input,
|
|
[field]: "relative.js",
|
|
});
|
|
expect(invoked).toBe(false);
|
|
if (result.ok) throw new Error("expected invalid path");
|
|
expect(result.error._tag).toBe("AnalysisInputError");
|
|
expect(result.error).toMatchObject({
|
|
issues: [{ path: [field], reason: "invalid_format" }],
|
|
});
|
|
},
|
|
);
|
|
it("rejects malformed and pre-cancelled input before acquiring the port", async () => {
|
|
const service = new JavaScriptRecoveryService({
|
|
recover: async () => {
|
|
throw new Error("port must not execute");
|
|
},
|
|
});
|
|
const invalid = await service.recover({});
|
|
if (invalid.ok) throw new Error("expected malformed input");
|
|
expect(invalid.error._tag).toBe("AnalysisInputError");
|
|
const cancelled = await service.recover(input, {
|
|
signal: AbortSignal.abort(),
|
|
});
|
|
if (cancelled.ok) throw new Error("expected cancellation");
|
|
expect(cancelled.error._tag).toBe("AnalysisCancelledError");
|
|
});
|
|
});
|