mirror of
https://github.com/morluto/rea.git
synced 2026-10-11 21:39:55 +00:00
fix(javascript): accept the Wakaru 1.x release line and verify 1.14.0 (#1540)
* fix(javascript): accept the Wakaru 1.x release line and verify 1.14.0 Exact --version equality rejected every Wakaru build except 1.13.0, and strict report schemas rejected any added field, so each upstream release broke recovery until REA re-pinned. Wakaru now documents that --version, --json and provenance.json change only by addition within a major version. Admit ^1.13.0 with the verified/compatible split used for Binwalk and Unblob, report the observed version instead of the pinned constant, and add a limitation for unverified releases. Parse reports with loose objects and keep the reported module status as a string; path, identity, range and file-set checks stay exact. Move the audited pin, submodule, CI binary and verifier to 1.14.0, and document where to get the binary from the refusal message. * fix(javascript): project known Wakaru warning fields --------- Co-authored-by: morluto <[email protected]>
This commit is contained in:
@@ -56,9 +56,9 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p "$RUNNER_TEMP/rea-wakaru"
|
||||
curl --fail --location --retry 3 https://github.com/pionxzh/wakaru/releases/download/v1.13.0/wakaru-linux-x64.tar.gz -o "$RUNNER_TEMP/rea-wakaru/release.tar.gz"
|
||||
curl --fail --location --retry 3 https://github.com/pionxzh/wakaru/releases/download/v1.14.0/wakaru-linux-x64.tar.gz -o "$RUNNER_TEMP/rea-wakaru/release.tar.gz"
|
||||
tar -xzf "$RUNNER_TEMP/rea-wakaru/release.tar.gz" -C "$RUNNER_TEMP/rea-wakaru" wakaru
|
||||
printf '%s %s\n' e8c7ca052974604197389f6ace7464e4dd24dce64e2730971521bd6606ec9c9e "$RUNNER_TEMP/rea-wakaru/wakaru" | sha256sum --check
|
||||
printf '%s %s\n' 664fa06782a61eda7ed15885134b52525fd565dd77607e642062ceb6cd927c38 "$RUNNER_TEMP/rea-wakaru/wakaru" | sha256sum --check
|
||||
- name: Install isolated fixture compilers
|
||||
run: npm install --prefix "$RUNNER_TEMP/rea-fixture-compilers" --ignore-scripts --no-audit --no-fund [email protected] [email protected]
|
||||
- run: npm run build:cached
|
||||
|
||||
@@ -7,19 +7,46 @@ never executes the selected or recovered application code.
|
||||
|
||||
## Caller-supplied engine
|
||||
|
||||
The initial adapter is verified on Linux x64 with Wakaru **1.13.0**. Set
|
||||
`REA_WAKARU_COMMAND` to the absolute path of an executable supplied by the caller.
|
||||
The adapter uses util-linux `prlimit` (default `/usr/bin/prlimit`; override with
|
||||
an absolute `REA_JAVASCRIPT_PRLIMIT_COMMAND`). REA does not install either tool.
|
||||
Engine configuration is checked only when recovery is requested; other REA
|
||||
capabilities remain available without it.
|
||||
REA accepts Wakaru **^1.13.0** on Linux x64. The report parser is verified with
|
||||
Wakaru **1.14.0**. Another release in that range is accepted, the observed
|
||||
version is reported, and a limitation records that the parser was not verified
|
||||
against that release. Wakaru changes `--version`, `--json` and `provenance.json`
|
||||
only by addition within a major version
|
||||
([compatibility policy](https://github.com/pionxzh/wakaru/blob/v1.14.0/docs/cli.md#machine-readable-output-compatibility)),
|
||||
so the parser keeps unknown report fields and reported module statuses.
|
||||
|
||||
Set `REA_WAKARU_COMMAND` to the absolute path of an executable supplied by the
|
||||
caller. The adapter uses util-linux `prlimit` (default `/usr/bin/prlimit`;
|
||||
override with an absolute `REA_JAVASCRIPT_PRLIMIT_COMMAND`). REA does not install
|
||||
either tool. Engine configuration is checked only when recovery is requested;
|
||||
other REA capabilities remain available without it.
|
||||
|
||||
Unmodified upstream source is retained in `third_party/wakaru`, pinned to
|
||||
`6070266d24b32951fa2fe1d2dad5b4313bd5c81b` (v1.13.0, Apache-2.0).
|
||||
`8219bf5016a063d7fb215101a8c45d5959ec733f` (v1.14.0, Apache-2.0).
|
||||
The result reports the executed binary's exact path, SHA-256 and observed version.
|
||||
The audited source revision is distinct from `executed_source_revision: null`:
|
||||
a version string cannot prove which source produced a caller-supplied binary.
|
||||
|
||||
### Install Wakaru
|
||||
|
||||
Use the official release binary, the same one CI verifies:
|
||||
|
||||
```sh
|
||||
curl -fL https://github.com/pionxzh/wakaru/releases/download/v1.14.0/wakaru-linux-x64.tar.gz | tar -xz wakaru
|
||||
export REA_WAKARU_COMMAND="$PWD/wakaru"
|
||||
```
|
||||
|
||||
Or install it from npm. Point at the platform binary, not the Node.js shim in
|
||||
`node_modules/.bin`:
|
||||
|
||||
```sh
|
||||
npm install [email protected]
|
||||
export REA_WAKARU_COMMAND="$(realpath node_modules/@wakaru/cli-linux-x64/wakaru)"
|
||||
```
|
||||
|
||||
Both give the same binary (SHA-256
|
||||
`664fa06782a61eda7ed15885134b52525fd565dd77607e642062ceb6cd927c38`).
|
||||
|
||||
## Recover and analyze
|
||||
|
||||
```sh
|
||||
|
||||
+4
-2
@@ -876,12 +876,14 @@ Neither optional toolchain is a base-lane prerequisite. See
|
||||
|
||||
Build once with `npm run build:cached`, then run `npm run verify:javascript:recovery`.
|
||||
This focused lane requires Linux x64, util-linux `prlimit`,
|
||||
`REA_WAKARU_COMMAND` pointing to the official Wakaru 1.13.0 Linux x64 binary,
|
||||
`REA_WAKARU_COMMAND` pointing to the official Wakaru 1.14.0 Linux x64 binary,
|
||||
and `REA_JAVASCRIPT_FIXTURE_TOOLS` pointing to an isolated npm prefix containing
|
||||
esbuild 0.25.10 and webpack 5.101.3. No global installation is required.
|
||||
The lane compiles source-owned fixtures, exercises CLI and stdio MCP, verifies
|
||||
published bytes and provenance, feeds recovered modules into existing analysis,
|
||||
and compares a finite set of known fixture results. It does not establish
|
||||
and compares a finite set of known fixture results. The provider also accepts
|
||||
other ^1.13.0 releases and reports them as unverified; this lane proves the
|
||||
audited release. It does not establish
|
||||
arbitrary recovered-application equivalence. CI installs these prerequisites only
|
||||
in `.github/workflows/real-javascript-recovery.yml`; the existing `real-browser`
|
||||
lane uses real Chrome for browser capture and website workflows.
|
||||
|
||||
@@ -15,10 +15,10 @@ import { buildRecoveryFixtures } from "../../fixtures/javascript-recovery/build.
|
||||
import { mcpTextValue } from "../../lib/mcp-verifier-results.mjs";
|
||||
|
||||
const OFFICIAL_BINARY_SHA256 =
|
||||
"e8c7ca052974604197389f6ace7464e4dd24dce64e2730971521bd6606ec9c9e";
|
||||
"664fa06782a61eda7ed15885134b52525fd565dd77607e642062ceb6cd927c38";
|
||||
if (!process.env.REA_WAKARU_COMMAND)
|
||||
throw new Error(
|
||||
"Provide REA_WAKARU_COMMAND for the official Wakaru 1.13.0 Linux x64 binary",
|
||||
"Provide REA_WAKARU_COMMAND for the official Wakaru 1.14.0 Linux x64 binary",
|
||||
);
|
||||
const entrypoint =
|
||||
process.argv[2] ?? fileURLToPath(new URL("../../rea.mjs", import.meta.url));
|
||||
@@ -130,7 +130,7 @@ try {
|
||||
}
|
||||
}
|
||||
finalReport = {
|
||||
engine: "wakaru 1.13.0",
|
||||
engine: "wakaru 1.14.0",
|
||||
toolchains: fixtures.toolchains,
|
||||
results: counts,
|
||||
verified: true,
|
||||
|
||||
@@ -303,7 +303,7 @@ const workflowAvailabilityFor = ({
|
||||
: {
|
||||
reason: "provider_missing",
|
||||
remediation:
|
||||
"On Linux x64, provide an absolute REA_WAKARU_COMMAND for Wakaru 1.13.0 and util-linux prlimit. No binary target is required.",
|
||||
"On Linux x64, provide an absolute REA_WAKARU_COMMAND for Wakaru ^1.13.0 (verified with 1.14.0) and util-linux prlimit; see docs/javascript-recovery.md#install-wakaru. No binary target is required.",
|
||||
};
|
||||
if (name === "inspect_binary_layout")
|
||||
return policy.binaryLayoutEnabled === true
|
||||
|
||||
@@ -13,7 +13,7 @@ export const JAVASCRIPT_RECOVERY_TOOL_CONTRACTS = [
|
||||
...toolContractMetadata("recover_javascript_sources"),
|
||||
kind: "application",
|
||||
description:
|
||||
"Recover readable modules from one local UTF-8 JavaScript script/bundle into an absent directory, using caller-supplied Wakaru 1.13.0 on Linux x64. Returns original/derived digests, extraction byte ranges, emitted source maps, warnings and analysis_input inline. Structural splitting may fall back to one module; original names and runtime equivalence remain unknown. No application code execution. Requires util-linux prlimit; one worker, 1 GiB address-space ceiling, 120-second deadline, 64 MiB input, 128 MiB output, 10000 entries and 8 MiB per diagnostic/report stream.",
|
||||
"Recover readable modules from one local UTF-8 JavaScript script/bundle into an absent directory, using caller-supplied Wakaru ^1.13.0 (verified with 1.14.0) on Linux x64. Returns original/derived digests, extraction byte ranges, emitted source maps, warnings and analysis_input inline. Structural splitting may fall back to one module; original names and runtime equivalence remain unknown. No application code execution. Requires util-linux prlimit; one worker, 1 GiB address-space ceiling, 120-second deadline, 64 MiB input, 128 MiB output, 10000 entries and 8 MiB per diagnostic/report stream.",
|
||||
inputSchema: javascriptRecoveryInputSchema,
|
||||
outputSchema: evidenceResultOf(javascriptRecoveryResultSchema),
|
||||
examples: [
|
||||
|
||||
@@ -26,7 +26,7 @@ import { RECOVERY_LIMITS } from "./WakaruRelease.js";
|
||||
|
||||
const OPERATION = "recover_javascript_sources";
|
||||
const WAKARU_REQUIREMENT =
|
||||
"Provide an absolute REA_WAKARU_COMMAND pointing to Wakaru 1.13.0 on Linux x64; no tool is installed by REA";
|
||||
"Provide an absolute REA_WAKARU_COMMAND pointing to a Wakaru ^1.13.0 Linux x64 binary (verified with 1.14.0); no tool is installed by REA. See docs/javascript-recovery.md#install-wakaru.";
|
||||
|
||||
/** Inject the owned process launcher while retaining production protocol parsing. */
|
||||
export type WakaruLauncher = (
|
||||
|
||||
@@ -24,11 +24,8 @@ import {
|
||||
} from "./WakaruCommand.js";
|
||||
import { parseWakaruReports } from "./WakaruReport.js";
|
||||
import { publishWakaruArtifacts } from "./WakaruPublication.js";
|
||||
import {
|
||||
RECOVERY_LIMITS,
|
||||
WAKARU_RELEASE,
|
||||
WAKARU_PROVIDER_IDENTITY,
|
||||
} from "./WakaruRelease.js";
|
||||
import { RECOVERY_LIMITS, WAKARU_PROVIDER_IDENTITY } from "./WakaruRelease.js";
|
||||
import { admitWakaruVersion } from "./WakaruVersion.js";
|
||||
|
||||
const OPERATION = "recover_javascript_sources";
|
||||
const MODES = {
|
||||
@@ -66,12 +63,13 @@ const executeWakaruRecovery = async (context: RecoveryWorkspace) => {
|
||||
...commandContext,
|
||||
arguments: ["--version"],
|
||||
});
|
||||
if (
|
||||
version.exit_code !== 0 ||
|
||||
version.stdout.trim() !== `wakaru ${WAKARU_RELEASE.version}`
|
||||
) {
|
||||
const admitted =
|
||||
version.exit_code === 0
|
||||
? admitWakaruVersion(version.stdout.trim())
|
||||
: undefined;
|
||||
if (admitted === undefined || admitted.status === "unsupported") {
|
||||
// The message names the mismatch; the reason also keeps the tool's stderr.
|
||||
const mismatch = `This adapter requires Wakaru ${WAKARU_RELEASE.version}; reported version: ${version.stdout.trim()}; exit: ${String(version.exit_code)}`;
|
||||
const mismatch = `${admitted?.message ?? `Wakaru --version failed; reported version: ${version.stdout.trim()}`}; exit: ${String(version.exit_code)}`;
|
||||
throw new AnalysisCapabilityUnavailableError(
|
||||
"wakaru",
|
||||
OPERATION,
|
||||
@@ -106,20 +104,22 @@ const executeWakaruRecovery = async (context: RecoveryWorkspace) => {
|
||||
sourceBytes: snapshot,
|
||||
files,
|
||||
});
|
||||
return { version, run, parsed };
|
||||
return { version, admitted, run, parsed };
|
||||
};
|
||||
|
||||
/** Publish validated recovery and retain exact source and engine bindings. */
|
||||
export const prepareWakaruExecution = async (context: RecoveryWorkspace) => {
|
||||
const { root, tree, input, source, engine, options } = context;
|
||||
const staging = join(root, "modules");
|
||||
const { version, run, parsed } = await executeWakaruRecovery(context);
|
||||
const { version, admitted, run, parsed } =
|
||||
await executeWakaruRecovery(context);
|
||||
const result = await publishWakaruArtifacts({
|
||||
tree,
|
||||
staging,
|
||||
input,
|
||||
source,
|
||||
engine,
|
||||
admitted,
|
||||
parsed,
|
||||
execution: run,
|
||||
...(options?.signal === undefined ? {} : { signal: options.signal }),
|
||||
@@ -150,25 +150,29 @@ export const prepareWakaruExecution = async (context: RecoveryWorkspace) => {
|
||||
OPERATION,
|
||||
"Configured engine bytes changed during recovery",
|
||||
);
|
||||
return createAnalysisExecution(result, WAKARU_PROVIDER_IDENTITY, {
|
||||
rawResult: jsonValueSchema.parse({
|
||||
version,
|
||||
execution: run,
|
||||
report: parsed.report,
|
||||
provenance: parsed.provenance,
|
||||
}),
|
||||
subject: {
|
||||
path: source.path,
|
||||
sha256: source.sha256,
|
||||
format: "javascript",
|
||||
return createAnalysisExecution(
|
||||
result,
|
||||
{ ...WAKARU_PROVIDER_IDENTITY, version: admitted.version },
|
||||
{
|
||||
rawResult: jsonValueSchema.parse({
|
||||
version,
|
||||
execution: run,
|
||||
report: parsed.report,
|
||||
provenance: parsed.provenance,
|
||||
}),
|
||||
subject: {
|
||||
path: source.path,
|
||||
sha256: source.sha256,
|
||||
format: "javascript",
|
||||
},
|
||||
locations: [
|
||||
{ kind: "artifact-path", path: source.path },
|
||||
...result.modules.map((module) => ({
|
||||
kind: "artifact-path" as const,
|
||||
path: module.artifact.path,
|
||||
})),
|
||||
],
|
||||
limitations: result.limitations,
|
||||
},
|
||||
locations: [
|
||||
{ kind: "artifact-path", path: source.path },
|
||||
...result.modules.map((module) => ({
|
||||
kind: "artifact-path" as const,
|
||||
path: module.artifact.path,
|
||||
})),
|
||||
],
|
||||
limitations: result.limitations,
|
||||
});
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ import type {
|
||||
resolveWakaruCommand,
|
||||
runWakaruCommand,
|
||||
} from "./WakaruCommand.js";
|
||||
import type { WakaruVersionAdmission } from "./WakaruVersion.js";
|
||||
|
||||
type PublicationContext = {
|
||||
tree: SafeOutputTree;
|
||||
@@ -34,6 +35,7 @@ type PublicationContext = {
|
||||
bytes: number;
|
||||
};
|
||||
engine: Awaited<ReturnType<typeof resolveWakaruCommand>>;
|
||||
admitted: Exclude<WakaruVersionAdmission, { status: "unsupported" }>;
|
||||
parsed: Awaited<ReturnType<typeof parseWakaruReports>>;
|
||||
execution: Awaited<ReturnType<typeof runWakaruCommand>>;
|
||||
signal?: AbortSignal;
|
||||
@@ -86,7 +88,7 @@ export const publishWakaruArtifacts = async (
|
||||
source: { ...source, published_copy: publishedCopy },
|
||||
engine: {
|
||||
id: "wakaru",
|
||||
version: WAKARU_RELEASE.version,
|
||||
version: context.admitted.version,
|
||||
executable: {
|
||||
path: context.engine.command,
|
||||
sha256: context.engine.sha256,
|
||||
@@ -107,7 +109,14 @@ export const publishWakaruArtifacts = async (
|
||||
total: parsed.report.total,
|
||||
failed: parsed.report.failed,
|
||||
modules,
|
||||
warnings: parsed.report.warnings,
|
||||
warnings: parsed.report.warnings.map(
|
||||
({ filename, kind, is_error, message }) => ({
|
||||
filename,
|
||||
kind,
|
||||
is_error,
|
||||
message,
|
||||
}),
|
||||
),
|
||||
report,
|
||||
provenance,
|
||||
analysis_input:
|
||||
@@ -118,7 +127,12 @@ export const publishWakaruArtifacts = async (
|
||||
format: "directory" as const,
|
||||
},
|
||||
runtime_equivalence: "unknown" as const,
|
||||
limitations: [...RECOVERY_LIMITATIONS],
|
||||
limitations: [
|
||||
...RECOVERY_LIMITATIONS,
|
||||
...(context.admitted.status === "compatible"
|
||||
? [context.admitted.limitation]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
const manifest = await write(
|
||||
"manifest.json",
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
/** Unchanged upstream source pin; executed bytes are identified separately. */
|
||||
export const WAKARU_RELEASE = {
|
||||
version: "1.13.0",
|
||||
revision: "6070266d24b32951fa2fe1d2dad5b4313bd5c81b",
|
||||
version: "1.14.0",
|
||||
revision: "8219bf5016a063d7fb215101a8c45d5959ec733f",
|
||||
repository: "https://github.com/pionxzh/wakaru",
|
||||
} as const;
|
||||
|
||||
/** Exact provider identity shared by executions and the generated catalog. */
|
||||
/**
|
||||
* Releases the report parser accepts. Wakaru changes `--version`, `--json`
|
||||
* and `provenance.json` only by addition within a major version
|
||||
* (https://github.com/pionxzh/wakaru/blob/v1.14.0/docs/cli.md#machine-readable-output-compatibility);
|
||||
* 1.13.0 is the first release this adapter verified.
|
||||
*/
|
||||
export const WAKARU_ACCEPTED_RANGE = "^1.13.0";
|
||||
|
||||
/** Catalog identity for the verified Wakaru release. Evidence replaces version with the observed banner. */
|
||||
export const WAKARU_PROVIDER_IDENTITY = {
|
||||
id: "wakaru",
|
||||
name: "Wakaru",
|
||||
|
||||
@@ -10,20 +10,20 @@ import { readRecoveryFile, recoveryFailureMessage } from "./RecoveryFiles.js";
|
||||
import { RECOVERY_LIMITS } from "./WakaruRelease.js";
|
||||
import type { runWakaruCommand } from "./WakaruCommand.js";
|
||||
|
||||
const warningSchema = z.strictObject({
|
||||
const warningSchema = z.looseObject({
|
||||
filename: z.string(),
|
||||
kind: z.string(),
|
||||
is_error: z.boolean(),
|
||||
message: z.string(),
|
||||
});
|
||||
const reportSchema = z.strictObject({
|
||||
const reportSchema = z.looseObject({
|
||||
detected_formats: z.array(z.string()),
|
||||
safety: z.string(),
|
||||
modules: z.array(
|
||||
z.strictObject({
|
||||
z.looseObject({
|
||||
filename: z.string().min(1),
|
||||
kind: z.literal("javascript"),
|
||||
status: z.literal("decompiled"),
|
||||
status: z.string().min(1),
|
||||
}),
|
||||
),
|
||||
warnings: z.array(warningSchema),
|
||||
@@ -35,12 +35,12 @@ const rangeSchema = z.tuple([
|
||||
z.number().int().nonnegative(),
|
||||
z.number().int().nonnegative(),
|
||||
]);
|
||||
const provenanceSchema = z.strictObject({
|
||||
const provenanceSchema = z.looseObject({
|
||||
format: z.string(),
|
||||
strategy: z.string(),
|
||||
modules: z.record(
|
||||
z.string(),
|
||||
z.strictObject({
|
||||
z.looseObject({
|
||||
input: z.string(),
|
||||
ranges: z.array(rangeSchema),
|
||||
extraction: z.string(),
|
||||
@@ -59,7 +59,12 @@ type ReportContext = {
|
||||
files: readonly { relativePath: string }[];
|
||||
};
|
||||
|
||||
/** Parse exact pinned producer JSON before projecting provider-neutral artifacts. */
|
||||
/**
|
||||
* Parse producer JSON before projecting provider-neutral artifacts. Wakaru adds
|
||||
* fields and category values within a major version, so unknown keys are kept
|
||||
* and `status` stays the reported string; identity, path, range and file-set
|
||||
* checks below remain exact.
|
||||
*/
|
||||
export const parseWakaruReports = async (context: ReportContext) => {
|
||||
const { execution, provenancePath } = context;
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { admitWakaruVersion } from "./WakaruVersion.js";
|
||||
import { WAKARU_RELEASE } from "./WakaruRelease.js";
|
||||
|
||||
describe("Wakaru release range", () => {
|
||||
it("accepts the verified banner without an extra limitation", () => {
|
||||
expect(admitWakaruVersion(`wakaru ${WAKARU_RELEASE.version}`)).toEqual({
|
||||
status: "verified",
|
||||
version: WAKARU_RELEASE.version,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["wakaru 1.13.0", "1.13.0"],
|
||||
["wakaru 1.15.2", "1.15.2"],
|
||||
])("accepts %s as compatible", (banner, version) => {
|
||||
const admitted = admitWakaruVersion(banner);
|
||||
expect(admitted).toMatchObject({ status: "compatible", version });
|
||||
if (admitted.status !== "compatible")
|
||||
throw new Error("expected a compatible release");
|
||||
expect(admitted.limitation).toContain(version);
|
||||
expect(admitted.limitation).toContain(WAKARU_RELEASE.version);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["wakaru 1.12.0", "wakaru 1.12.0"],
|
||||
["wakaru 2.0.0", "wakaru 2.0.0"],
|
||||
["wakaru 1.15.0-rc.1", "wakaru 1.15.0-rc.1"],
|
||||
["wakaru v1.14.0", "wakaru v1.14.0"],
|
||||
["1.14.0", "1.14.0"],
|
||||
["", "missing"],
|
||||
])("rejects banner %j", (banner, detail) => {
|
||||
const admitted = admitWakaruVersion(banner);
|
||||
if (admitted.status !== "unsupported")
|
||||
throw new Error("expected a rejected banner");
|
||||
expect(admitted.message).toContain(detail);
|
||||
expect(admitted.message).toContain("^1.13.0");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { satisfies, valid } from "semver";
|
||||
import { WAKARU_ACCEPTED_RANGE, WAKARU_RELEASE } from "./WakaruRelease.js";
|
||||
|
||||
/** Admission of one observed `wakaru --version` banner. */
|
||||
export type WakaruVersionAdmission =
|
||||
| { readonly status: "verified"; readonly version: string }
|
||||
| {
|
||||
readonly status: "compatible";
|
||||
readonly version: string;
|
||||
readonly limitation: string;
|
||||
}
|
||||
| { readonly status: "unsupported"; readonly message: string };
|
||||
|
||||
/** Caller-facing accepted releases, naming the verified build. */
|
||||
export const wakaruReleaseLine = (): string =>
|
||||
`Wakaru ${WAKARU_ACCEPTED_RANGE} (verified with ${WAKARU_RELEASE.version})`;
|
||||
|
||||
/** Classify a `wakaru <version>` banner against the accepted release range. */
|
||||
export const admitWakaruVersion = (banner: string): WakaruVersionAdmission => {
|
||||
const token = banner.startsWith("wakaru ")
|
||||
? banner.slice("wakaru ".length)
|
||||
: undefined;
|
||||
if (
|
||||
token === undefined ||
|
||||
valid(token) !== token ||
|
||||
!satisfies(token, WAKARU_ACCEPTED_RANGE)
|
||||
)
|
||||
return {
|
||||
status: "unsupported",
|
||||
message: `Unsupported Wakaru version ${banner.length === 0 ? "missing" : banner}; accepted releases are ${wakaruReleaseLine()}`,
|
||||
};
|
||||
if (token === WAKARU_RELEASE.version)
|
||||
return { status: "verified", version: token };
|
||||
return {
|
||||
status: "compatible",
|
||||
version: token,
|
||||
limitation: `This session uses Wakaru ${token}. The report parser is verified against ${WAKARU_RELEASE.version}; other ${WAKARU_ACCEPTED_RANGE} releases are accepted and the observed version is reported.`,
|
||||
};
|
||||
};
|
||||
@@ -97,6 +97,44 @@ describe.skipIf(process.platform !== "linux" || process.arch !== "x64")(
|
||||
await assertRecoveryCleanup(fixture.launches);
|
||||
});
|
||||
|
||||
it("accepts a later release on the verified major line with additive report fields", async () => {
|
||||
const fixture = await recoveryFixture("additive");
|
||||
const result = await fixture.service.recover({
|
||||
path: fixture.path,
|
||||
output_directory: fixture.output,
|
||||
});
|
||||
if (!result.ok) throw result.error;
|
||||
const value = javascriptRecoveryResultSchema.parse(
|
||||
result.value.normalized_result,
|
||||
);
|
||||
expect(value).toMatchObject({
|
||||
status: "complete",
|
||||
engine: { version: "1.99.0" },
|
||||
modules: [{ reported_status: "future_status" }],
|
||||
});
|
||||
expect(result.value.provider.version).toBe("1.99.0");
|
||||
expect(
|
||||
value.limitations.some((limitation) =>
|
||||
limitation.includes("This session uses Wakaru 1.99.0"),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
JSON.parse(await readFile(value.report.path, "utf8")),
|
||||
).toMatchObject({
|
||||
future_report_field: { nested: true },
|
||||
warnings: [{ code: "W001" }],
|
||||
});
|
||||
expect(value.warnings).toEqual([
|
||||
{
|
||||
filename: "module.js",
|
||||
kind: "notice",
|
||||
is_error: false,
|
||||
message: "future warning",
|
||||
},
|
||||
]);
|
||||
await assertRecoveryCleanup(fixture.launches);
|
||||
});
|
||||
|
||||
it("retains valid partial output and exact failure diagnostics", async () => {
|
||||
const fixture = await recoveryFixture("partial");
|
||||
const result = await fixture.service.recover({
|
||||
|
||||
+33
-3
@@ -4,7 +4,13 @@ import { dirname, join } from "node:path";
|
||||
const args = process.argv.slice(2);
|
||||
const mode = process.env.REA_RECOVERY_FIXTURE_MODE ?? "normal";
|
||||
if (args.includes("--version")) {
|
||||
console.log(mode === "wrong-version" ? "wakaru 0.0.0" : "wakaru 1.13.0");
|
||||
console.log(
|
||||
mode === "wrong-version"
|
||||
? "wakaru 0.0.0"
|
||||
: mode === "additive"
|
||||
? "wakaru 1.99.0"
|
||||
: "wakaru 1.14.0",
|
||||
);
|
||||
} else {
|
||||
const input = args[0];
|
||||
const output = args[args.indexOf("-o") + 1];
|
||||
@@ -25,7 +31,16 @@ if (args.includes("--version")) {
|
||||
if (mode === "source-change")
|
||||
await writeFile(process.env.REA_RECOVERY_ORIGINAL_PATH, "changed original");
|
||||
const filename = mode === "escaping" ? "../escaped.js" : "module.js";
|
||||
const modules = [{ filename, kind: "javascript", status: "decompiled" }];
|
||||
const modules = [
|
||||
mode === "additive"
|
||||
? {
|
||||
filename,
|
||||
kind: "javascript",
|
||||
status: "future_status",
|
||||
future_module_field: true,
|
||||
}
|
||||
: { filename, kind: "javascript", status: "decompiled" },
|
||||
];
|
||||
if (mode === "duplicate") modules.push(modules[0]);
|
||||
if (mode !== "missing-file" && mode !== "escaping") {
|
||||
if (mode === "symlink") await symlink(input, join(output, filename));
|
||||
@@ -65,6 +80,10 @@ if (args.includes("--version")) {
|
||||
},
|
||||
},
|
||||
};
|
||||
if (mode === "additive") {
|
||||
provenance.future_top_field = "added";
|
||||
provenance.modules[filename].future_entry_field = [1];
|
||||
}
|
||||
if (mode === "extra-provenance")
|
||||
provenance.modules["extra.js"] = provenance.modules[filename];
|
||||
if (mode !== "missing-provenance")
|
||||
@@ -86,7 +105,18 @@ if (args.includes("--version")) {
|
||||
message: "fixture transform failed; retained source",
|
||||
},
|
||||
]
|
||||
: [],
|
||||
: mode === "additive"
|
||||
? [
|
||||
{
|
||||
filename,
|
||||
kind: "notice",
|
||||
is_error: false,
|
||||
message: "future warning",
|
||||
code: "W001",
|
||||
},
|
||||
]
|
||||
: [],
|
||||
...(mode === "additive" ? { future_report_field: { nested: true } } : {}),
|
||||
total: modules.length,
|
||||
failed: mode === "partial" ? 1 : 0,
|
||||
elapsed_ms: 1,
|
||||
|
||||
Vendored
+1
-1
Submodule third_party/wakaru updated: 6070266d24...8219bf5016
Reference in New Issue
Block a user