fix(apple): preserve own artifact metadata through schema projection

This commit is contained in:
morluto committed 2026-10-26 23:06:36 +08:00
1 parent b7e76a57cd
commit fef76a02fb
3 files changed
+43 -6

No files matched your search

@@ -659,3 +659,34 @@ it.each(["Toolbar", "MissingToolbar"])(
expect(result.resource_key_coverage.status).toBe("complete");
},
);
it("preserves every own JSON metadata field through catalog inspection and page projection", async () => {
const bundle = await createTestTempDirectory("rea-asset-metadata-members-");
const catalog = Buffer.from("inert catalog for the output-decoding boundary");
await writeFile(join(bundle, "Assets.car"), catalog);
// Authored producer text exercises the accepted metadata contract; it is not
// evidence that native assetutil emits these additional field names.
const output =
'[{"Name":"Toolbar","RenditionName":"Toolbar@2x","__proto__":{"Name":"metadata, not an inherited asset name"},"constructor":"retained constructor metadata","toString":false,"Unknown":{"__proto__":{"value":0}},"Empty":"","Zero":0,"Absent":null}]';
const result = await analyzeAppleAssetCatalogs({
bundlePath: bundle,
targetSha256: "a".repeat(64),
environment: {},
runAssetUtil: async (path) => {
expect(path).toBe(join(bundle, "Assets.car"));
return output;
},
});
const expected: unknown = JSON.parse(output);
expect(result.records.map(({ metadata }) => metadata)).toEqual(expected);
expect(result.records[0]).toMatchObject({
asset_name: "Toolbar",
rendition_name: "Toolbar@2x",
catalog_path: "Assets.car",
catalog_sha256: createHash("sha256").update(catalog).digest("hex"),
});
const metadata = result.records[0]?.metadata;
if (metadata === undefined) throw new Error("missing catalog metadata");
expect(Object.hasOwn(metadata, "__proto__")).toBe(true);
expect(Object.getPrototypeOf(metadata)).toBe(Object.prototype);
});
+3 -3
View File
@@ -1,7 +1,7 @@
import { z } from "zod";
import { bundleResourceBoundResultSchema } from "./bundleResourceRoot.js";
import { jsonValueSchema } from "../jsonValue.js";
import { jsonObjectSchema, jsonValueSchema } from "../jsonValue.js";
import { AnalysisOutputError } from "../analysisErrorCore.js";
import {
artifactInventoryCoverageSchema,
@@ -51,7 +51,7 @@ const recordSchema = z.strictObject({
kind: z.enum(["catalog", "rendition"]),
asset_name: z.string().nullable(),
rendition_name: z.string().nullable(),
metadata: z.record(z.string(), jsonValueSchema),
metadata: jsonObjectSchema,
});
const resourceKeyMatchSchema = z.strictObject({
@@ -116,7 +116,7 @@ export const parseAppleAssetCatalogRecords = (
"assetutil output must be a JSON array",
);
return value.map((record, index) => {
const parsed = z.record(z.string(), jsonValueSchema).safeParse(record);
const parsed = jsonObjectSchema.safeParse(record);
if (!parsed.success)
throw new AnalysisOutputError(
"inspect_asset_catalog",
@@ -43,7 +43,7 @@ it("keeps literal wrapper-shaped dictionaries as real root maps", () => {
}
});
it("retains own binary dictionary facts when a prototype key changes the decoder prototype", () => {
it("retains an own binary dictionary prototype key without changing the decoded prototype", () => {
const object = Object.fromEntries([
["__proto__", { inherited: "not an own fact" }],
["payload", "retained"],
@@ -63,8 +63,14 @@ it("retains own binary dictionary facts when a prototype key changes the decoder
kind: "dictionary",
value: { payload: "retained" },
});
expect(graph.objects[1]?.value).toEqual({ payload: "retained" });
expect(graph.limitations).toContainEqual(
const value = graph.objects[1]?.value;
expect(value).toEqual(object);
if (value === null || typeof value !== "object")
throw new Error("Expected a decoded dictionary");
expect(Object.hasOwn(value, "__proto__")).toBe(true);
expect(Object.hasOwn(value, "inherited")).toBe(false);
expect(Object.getPrototypeOf(value)).toBe(Object.prototype);
expect(graph.limitations).not.toContainEqual(
expect.stringContaining("__proto__"),
);
});