Files
rea/tests/acceptance/godotPck.test.ts
5fa4defcbc feat(artifacts): inspect and extract Godot PCK with retained facts (#3634)
* feat(artifacts): read Godot PCK packs

Add a Godot Engine PCK artifact reader for standalone packs and packs
embedded behind an executable trailer (formats 2 and 3). Members are
uncompressed byte ranges verified against their declared MD5 on read.

Encrypted directories, sparse bundles, patch removal entries, unknown
flags and format 1 fail closed as unavailable; encrypted members are
reported without being read. inspect-artifact and extract-artifact
recognize standalone packs by their GDPC magic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(artifacts): bound Godot PCK metadata and own member streams

Cap member paths at 4096 bytes and the retained directory at 64 MiB
before allocation. Read members with positional chunk reads through
pipeline so cancellation is typed, abandoned consumers release their
source, and the shared descriptor stays reader-owned. Retain the root
descriptor for godot-pck inventory, as for ZIP and universal Mach-O.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(artifacts): complete Godot PCK facts, integrity and selected views

* fix(artifacts): preserve atomic-root inventory Evidence

* fix(artifacts): bound aggregate PCK member expansion

---------

Co-authored-by: Samucahub <151155051+Samucahub@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-10-20 09:53:53 +08:00

340 lines
11 KiB
TypeScript

import { createHash } from "node:crypto";
import { readFile, readdir, unlink, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { cliTest } from "../support/cli/cliFixture.js";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
import {
buildAliasedGodotPck,
buildGodotPck,
pckU64,
} from "../fixtures/godotPck.js";
import { elf } from "../../src/domain/binaryTarget.fixture.js";
import { readEvidenceBundle } from "../../src/application/EvidenceBundleFiles.js";
import { analysisViewResultSchema } from "../../src/domain/analysisView/analysisView.js";
import { parseEvidence } from "../../src/domain/evidence.js";
import { artifactInspectionResultSchema } from "../../src/domain/artifactInspection.js";
import {
artifactExtractionResultSchema,
artifactInventoryResultSchema,
} from "../../src/domain/artifactGraph.js";
const members = [
{ path: "res://scripts/main.gd", data: "extends Node\n" },
{ path: "res://data/玩家.txt", data: "玩家数据\n" },
];
const graphFrom = (reply: unknown) => {
const evidence = parseEvidence(reply);
const inspection = artifactInspectionResultSchema.parse(
evidence.normalized_result,
);
return artifactInventoryResultSchema.parse(
inspection.substeps[0]?.evidence.normalized_result,
);
};
cliTest.for([2, 3, 4] as const)(
"inspects and safely extracts format %s PCK through the compiled CLI",
async (version, { cli }) => {
const root = await createTestTempDirectory("rea-pck-cli-");
const source = join(root, "game.pck");
await writeFile(source, buildGodotPck(version, members));
const inspected = await cli.run({
arguments: ["inspect-artifact", source, "--json"],
});
expect(inspected.exitCode, inspected.stdout + inspected.stderr).toBe(0);
const graph = graphFrom(inspected.json);
expect(graph.coverage).toEqual({
scope: "container-entries",
status: "complete",
});
const rootRow = graph.occurrences.find((row) => row.logical_path === ".");
expect(rootRow?.format_metadata).toMatchObject({
kind: "godot-pck",
format_version: version,
declared_file_count: 2,
});
for (const [index, member] of members.entries()) {
const row = graph.occurrences.find(
(row) => row.logical_path === member.path.slice(6),
);
expect(row?.declared_integrity).toEqual({
algorithm: "md5",
digest: createHash("md5").update(member.data).digest("hex"),
});
expect(row?.format_metadata).toMatchObject({
index,
source_path: member.path,
flags: 0,
});
expect(row?.hash_status).toBe("verified");
}
const extracted = await cli.run({
arguments: ["extract-artifact", source, "--json"],
environment: { TMPDIR: root, TMP: root, TEMP: root },
});
expect(extracted.exitCode, extracted.stdout + extracted.stderr).toBe(0);
const result = artifactExtractionResultSchema.parse(
parseEvidence(extracted.json).normalized_result,
);
expect(result.containment_verified).toBe(true);
for (const member of members)
expect(
await readFile(join(result.output_root, member.path.slice(6)), "utf8"),
).toBe(member.data);
},
);
cliTest(
"reads an embedded pack while preserving the native carrier's content classification",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-pck-embedded-cli-");
const source = join(root, "game.x86_64");
const pack = buildGodotPck(4, members);
const carrier = elf(2, 1, 62);
const bytes = Buffer.concat([
carrier,
pack,
pckU64(pack.length),
Buffer.from("GDPC"),
]);
await writeFile(source, bytes);
const reply = await cli.run({
arguments: ["inspect-artifact", source, "--json"],
});
expect(reply.exitCode, reply.stdout + reply.stderr).toBe(0);
const graph = graphFrom(reply.json);
expect(graph.manifest.root_sha256).toBe(
createHash("sha256").update(bytes).digest("hex"),
);
expect(
graph.nodes.find(
(row) => row.artifact_id === graph.manifest.root_artifact_id,
)?.format,
).toBe("elf");
expect(
graph.occurrences.find((row) => row.logical_path === ".")
?.format_metadata,
).toMatchObject({
embedded: true,
pack_start: carrier.length,
pack_end: carrier.length + pack.length,
});
},
);
cliTest(
"applies strict and record-and-continue MD5 integrity to inspection and extraction",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-pck-integrity-cli-");
const source = join(root, "contradiction.pck");
await writeFile(
source,
buildGodotPck(4, [
{ path: "res://main.gd", data: "real", md5: Buffer.alloc(16, 1) },
]),
);
const rejected = await cli.run({
arguments: ["inspect-artifact", source, "--json"],
});
expect(rejected.exitCode).toBe(1);
expect(rejected.json).toMatchObject({
code: "artifact_integrity_mismatch",
details: {
algorithm: "md5",
logical_path: "main.gd",
declared_md5: "01".repeat(16),
calculated_md5: createHash("md5").update("real").digest("hex"),
},
});
const inspected = await cli.run({
arguments: [
"inspect-artifact",
source,
"--integrity-policy",
"record-and-continue",
"--json",
],
});
expect(inspected.exitCode, inspected.stdout + inspected.stderr).toBe(0);
const graph = graphFrom(inspected.json);
expect(graph.integrity_contradictions).toEqual([
expect.objectContaining({
algorithm: "md5",
logical_path: "main.gd",
declared_md5: "01".repeat(16),
observed_md5: createHash("md5").update("real").digest("hex"),
trust: "observed-untrusted",
}),
]);
expect(
graph.occurrences.find((row) => row.logical_path === "main.gd")
?.hash_status,
).toBe("mismatched");
const extracted = await cli.run({
arguments: [
"extract-artifact",
source,
"--integrity-policy",
"record-and-continue",
"--json",
],
environment: { TMPDIR: root, TMP: root, TEMP: root },
});
expect(extracted.exitCode, extracted.stdout + extracted.stderr).toBe(0);
const result = artifactExtractionResultSchema.parse(
parseEvidence(extracted.json).normalized_result,
);
expect(result.integrity_contradictions).toHaveLength(1);
expect(await readFile(join(result.output_root, "main.gd"), "utf8")).toBe(
"real",
);
},
);
cliTest(
"retains encrypted-directory declarations without claiming an empty package",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-pck-encrypted-cli-");
const source = join(root, "encrypted.pck");
const bytes = buildGodotPck(4, members, { packFlags: 3 });
const directory = Number(bytes.readBigUInt64LE(32));
bytes.fill(0xff, directory + 4);
await writeFile(source, bytes);
const inspected = await cli.run({
arguments: ["inspect-artifact", source, "--json"],
});
expect(inspected.exitCode, inspected.stdout + inspected.stderr).toBe(0);
const graph = graphFrom(inspected.json);
expect(graph.coverage).toEqual({
scope: "container-entries",
status: "unknown",
});
expect(graph.occurrences).toHaveLength(1);
expect(graph.occurrences[0]).toMatchObject({
encrypted: true,
format_metadata: {
directory_status: "encrypted",
declared_file_count: 2,
},
});
const extracted = await cli.run({
arguments: ["extract-artifact", source, "--json"],
environment: { TMPDIR: root, TMP: root, TEMP: root },
});
expect(extracted.exitCode).toBe(1);
},
);
cliTest(
"exports compact CLI delivery and queries its retained complete Evidence after unlink",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-pck-export-");
const source = join(root, "game.pck");
const bundle = join(root, "evidence.json");
const refused = await cli.run({
arguments: ["inspect-artifact", source, "--detail", "summary", "--json"],
});
expect(refused.exitCode).toBe(1);
expect(refused.json).toMatchObject({
code: "invalid_request",
details: { issues: [{ path: ["evidence_output"] }] },
});
await writeFile(source, buildGodotPck(4, members));
const args = [
"inspect-artifact",
source,
"--detail",
"summary",
"--evidence_output",
bundle,
"--json",
];
const reply = await cli.run({ arguments: args });
expect(reply.exitCode, reply.stdout + reply.stderr).toBe(0);
expect(reply.json).toMatchObject({
evidence_bundle: { path: bundle, bytes: expect.any(Number) },
summary: {
normalized_result: {
kind: "summary",
summary: { root_format_metadata: { format_version: 4 } },
},
},
});
const saved = await readEvidenceBundle(bundle);
if (!saved.ok) throw saved.error;
const evidence = saved.value.records[0];
if (evidence === undefined) throw new Error("Missing complete inspection");
expect(graphFrom(evidence).occurrences).toHaveLength(3);
const before = await readFile(bundle);
expect((await cli.run({ arguments: args })).exitCode).toBe(1);
expect(await readFile(bundle)).toEqual(before);
await unlink(source);
const query = join(root, "query.json");
await writeFile(
query,
JSON.stringify({
source: { kind: "inline", evidence },
view: {
kind: "inventory",
collection: "occurrences",
logical_paths: ["scripts/main.gd"],
fields: ["logical_path"],
},
}),
);
const selected = await cli.run({
arguments: ["inspect-analysis-view", query, "--json"],
});
expect(selected.exitCode, selected.stdout + selected.stderr).toBe(0);
const view = analysisViewResultSchema.parse(
parseEvidence(selected.json).normalized_result,
);
if (view.kind !== "inventory") throw new Error("Expected inventory view");
expect(view.parent_evidence_id).toBe(evidence.evidence_id);
expect(view.items).toEqual([
expect.objectContaining({
logical_path: "scripts/main.gd",
occurrence_id: expect.any(String),
artifact_id: expect.any(String),
parent_occurrence_id: expect.any(String),
}),
]);
},
30000,
);
cliTest(
"rejects repeated-range expansion before hashing members or creating extraction output",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-pck-expansion-");
const source = join(root, "aliased.pck");
await writeFile(source, buildAliasedGodotPck(4, 1100));
for (const operation of ["inspect-artifact", "extract-artifact"]) {
for (const policy of ["fail", "record-and-continue"]) {
const reply = await cli.run({
arguments: [
operation,
source,
"--integrity-policy",
policy,
"--json",
],
environment: { TMPDIR: root, TMP: root, TEMP: root },
});
expect(reply.exitCode, reply.stdout + reply.stderr).toBe(1);
expect(reply.json).toMatchObject({
code: "truncated",
details: {
reason: "limit",
truncated: true,
detail: expect.stringContaining("expansion budget"),
},
});
expect(await readdir(root)).toEqual(["aliased.pck"]);
}
}
},
30000,
);