From e15b7895da526337f44624f8ac38700c0d3ced87 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Wed, 30 Sep 2026 01:22:46 +0200 Subject: [PATCH] feat(scripts): bind a published overlay to the CDP run that applied it A CDP report that records its overlay digest must match the overlay being published, and a run without one is noted in VERIFICATION.md. Overlays must be in canonical form so the digests compare. --- README.md | 11 +++++++---- scripts/publish-key.test.mts | 24 +++++++++++++++++++++++- scripts/publish-key.ts | 21 ++++++++++++++++----- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0272272..f764bc0 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,10 @@ You never write `META.json`, `VERIFICATION.md`, or `index.json` by hand. validates the new key and every key inheriting from it, and rebuilds the index. It refuses mismatched versions, maps, leaf values, shallow CDP runs, inconsistent summaries, low hit rates, and existing keys. If any step fails, it -rolls back both the key and `index.json`. Run `pnpm publish-key --help` for -every flag. +rolls back both the key and `index.json`. When the CDP report records the +overlay it applied, that overlay must be the one being published; otherwise +`VERIFICATION.md` notes that the overlay's names were not checked live. Run +`pnpm publish-key --help` for every flag. The CLI commands below run from a spicetify/cli checkout next to this repository. They need the stock `xpui.spa` of each Spotify build involved, taken @@ -101,7 +103,7 @@ against the new build and publish it as inherited: node scripts/classmap-cdp-verify.mjs --port 9229 --mode both --deep \ --classmap ../classmaps/1020094/classmap.json --css-map css-map.json \ - --out /tmp/1020096-cdp.json + --overlay ../classmaps/1020094/css-map.json --out /tmp/1020096-cdp.json ``` 2. From this repository, publish the inherited key: @@ -146,7 +148,8 @@ and publish the result as a derived key: 4. Verify the candidate with the same two commands as an unchanged release, passing `--classmap /tmp/1030002.json`, `--target-version 1.3.2.100`, and - `--report /tmp/1030002-migrate.json` to `verify`. Alternatively, run the whole + `--report /tmp/1030002-migrate.json` to `verify`, and + `--overlay /tmp/1030002-overlay.json` to the CDP verifier. Alternatively, run the whole CLI side in one command with `SPOTIFY_VERSION=1.3.2.100 BASE_CLASSMAP=... BASE_CSS_DIR=... OUT_DIR=... scripts/classmap-e2e.sh --deep`. diff --git a/scripts/publish-key.test.mts b/scripts/publish-key.test.mts index c449a40..ee6a4cf 100644 --- a/scripts/publish-key.test.mts +++ b/scripts/publish-key.test.mts @@ -344,7 +344,7 @@ test("--replace refuses a map that would break the keys inheriting from it", () test("publishes a derived key from a candidate map", () => { const overlay = path.join(root, "overlay.json"); - writeFileSync(overlay, '{"topbarHashAA":"Root__globalNav"}'); + writeFileSync(overlay, renderJson({ topbarHashAA: "Root__globalNav" })); const target = publishRelease( options(derived(classmap, { overlayPath: overlay, notes: ["The retired leaf has no rendered instance."] })), ); @@ -362,6 +362,28 @@ test("publishes a derived key from a candidate map", () => { assert.ok(readVerification(target).includes("- The retired leaf has no rendered instance.")); }); +test("binds the published overlay to the one the CDP run applied", () => { + const overlay = path.join(root, "overlay.json"); + writeFileSync(overlay, renderJson({ topbarHashAA: "Root__globalNav" })); + cdpReport.cssMap = { sha256: "c".repeat(64), overlaySha256: sha256(readFileSync(overlay)) }; + const target = publishRelease(options(derived(classmap, { overlayPath: overlay }))); + assert.ok(!readVerification(target).includes("did not apply this key's overlay")); + rmSync(target, { recursive: true }); + + cdpReport.cssMap.overlaySha256 = "d".repeat(64); + assert.throws(() => publishRelease(options(derived(classmap, { overlayPath: overlay }))), /different overlay/); + + delete cdpReport.cssMap; + const unchecked = publishRelease(options(derived(classmap, { overlayPath: overlay }))); + assert.ok(readVerification(unchecked).includes("The CDP run did not apply this key's overlay")); +}); + +test("refuses an overlay that is not in canonical form", () => { + const overlay = path.join(root, "overlay.json"); + writeFileSync(overlay, '{"topbarHashAA":"Root__globalNav"}'); + assert.throws(() => publishRelease(options(derived(classmap, { overlayPath: overlay }))), /overlay is not in canonical form/); +}); + test("a derived key keeps the leaves the migrate report kept as stale", () => { writeSourceMeta({ stale_leaves: [], required_paths: { "main.topbar.wrapper": "verified" } }); const migrateReport = { unmatched: [{ path: "main.playbar.indicator", old: "indicatorHashCC", kept: "indicatorHashCC", stale: true }] }; diff --git a/scripts/publish-key.ts b/scripts/publish-key.ts index e6a78d7..26a57f9 100644 --- a/scripts/publish-key.ts +++ b/scripts/publish-key.ts @@ -337,14 +337,18 @@ export function publishRelease(o: PublishOptions, index = false): string { if (sourceKey) checkSource(sourceKey, targetKey, sourceMeta, inherited); let classmapBytes: Buffer; - let overlay: Record | undefined; + let overlayText: string | null = null; if (inherited) { classmapBytes = readFileSync(path.join(o.root, o.inheritFrom as string, "classmap.json")); const sourceOverlay = path.join(o.root, o.inheritFrom as string, "css-map.json"); - if (isFile(sourceOverlay)) overlay = JSON.parse(readUtf8(sourceOverlay)); + if (isFile(sourceOverlay)) overlayText = readUtf8(sourceOverlay); } else { classmapBytes = readFileSync(o.classmapPath as string); - if (o.overlayPath) overlay = JSON.parse(readUtf8(o.overlayPath)); + if (o.overlayPath) overlayText = readUtf8(o.overlayPath); + } + const overlay: Record | undefined = overlayText === null ? undefined : JSON.parse(overlayText); + if (overlayText !== null && overlayText !== renderJson(overlay)) { + throw new Error("the overlay is not in canonical form; run pnpm fix on it before the CDP run"); } const classmap: Classmap = JSON.parse(classmapBytes.toString("utf8")); if (classmapBytes.toString("utf8") !== renderJson(classmap)) { @@ -357,6 +361,8 @@ export function publishRelease(o: PublishOptions, index = false): string { const evidence = checkReports(o.spotifyVersion, classmap, sha256(classmapBytes), o.staticReport, o.cdpReport, o.minHitRate); const { missing, live, deadHashes } = evidence; + const overlaySha = overlayText === null ? null : sha256(overlayText); + const liveOverlaySha: unknown = o.cdpReport.cssMap?.overlaySha256; const notes = [...(o.notes ?? [])]; let stale: Set; @@ -393,10 +399,15 @@ export function publishRelease(o: PublishOptions, index = false): string { const observedStale = [...stale].filter((p) => live.has(p)).sort(); if (observedStale.length) notes.push(`Marked stale although observed live: ${observedStale.join(", ")}.`); if (exists) notes.push(`Replaces an earlier verification of this key; its history is in git.`); + if (liveOverlaySha !== undefined && liveOverlaySha !== overlaySha) { + throw new Error("the CDP report was run with a different overlay than the one being published"); + } + if (overlaySha && liveOverlaySha === undefined) { + notes.push("The CDP run did not apply this key's overlay, so its names were not checked live."); + } const unverified = new Set([...doubted].filter((p) => !live.has(p) && !stale.has(p))); const method = inherited ? "inherited" : "derived"; - const overlayText = overlay ? renderJson(overlay) : null; const meta = { schema_version: 2, classmap_key: targetKey, @@ -420,7 +431,7 @@ export function publishRelease(o: PublishOptions, index = false): string { cdpReport: o.cdpReport, staticReport: o.staticReport, classmapSha: sha256(classmapBytes), - overlaySha: overlayText ? sha256(overlayText) : null, + overlaySha, overlayEntries: overlay ? Object.keys(overlay).length : 0, notes, });