diff --git a/1020098/META.json b/1020098/META.json new file mode 100644 index 0000000..caeea87 --- /dev/null +++ b/1020098/META.json @@ -0,0 +1,53 @@ +{ + "spotify_version": "1.2.98.301", + "classmap_key": "1020098", + "status": "verified", + "generated": "2026-09-05", + "inherited_from": "1020097", + "pipeline": "inherit(1020097 -> 1020098) + static target-CSS verification + CDP e2e (deep)", + "stats": { + "leaves": 36, + "inherited": 36, + "static_present": 32, + "verified_cdp": 21, + "live_only": 0, + "unresolved_missing": 4, + "stale": 2, + "cdp_hit_rate": 0.5833, + "overlay_entries": 3 + }, + "required_paths": { + "main.playbar.buttons.button.wrapper": "verified_cdp", + "main.topbar.wrapper": "verified_cdp", + "main.navbar.link.wrapper": "verified_cdp", + "tracklist.column_header": "verified_cdp", + "context_menu.menu_item": "verified_cdp", + "search_box.container": "verified_cdp", + "search_chips.wrapper": "verified_cdp", + "sort_box.list.button": "verified_cdp", + "settings.header.container": "verified_cdp", + "settings.section.container": "verified_cdp", + "settings.button.wrapper": "verified_cdp", + "modal.track_credits.container": "verified (inherited; present in target CSS)", + "modal.widget_generator.container": "verified (inherited; present in target CSS)" + }, + "stale_leaves": [ + "main.topbar.left.button.icon.wrapper", + "main.topbar.right.upgrade_button.wrapper" + ], + "unverified_leaves": [ + "main.playbar.buttons.button.wrapper__indicator", + "settings.text_input" + ], + "notes": [ + "Classmap inherited byte-for-byte from 1020097; no migration guesses were accepted.", + "Static verification found 32/36 paths in the target CSS.", + "0 CSS-only misses were observed live and remain verified.", + "Deep CDP verification observed 21/36 paths on the routes and transient surfaces exercised by the verifier.", + "Unresolved new misses remain usable but are marked unverified; inherited stale paths stay blocked." + ], + "verification_summary": { + "needs_manual_check": 32, + "missing_in_css": 4 + } +} diff --git a/1020098/classmap-19f856aefd5.json b/1020098/classmap-19f856aefd5.json new file mode 100644 index 0000000..dd8e490 --- /dev/null +++ b/1020098/classmap-19f856aefd5.json @@ -0,0 +1,109 @@ +{ + "main": { + "topbar": { + "wrapper": "NXttmW3KPwE1r4Am", + "left": { + "button": { + "wrapper": "kUHE42xvQVzWqabl", + "icon": { + "wrapper": "IYDlXmBmmUKHveMzIPCF" + } + }, + "button_t": { + "wrapper": "_Bg_zSvFrEutyacG" + } + }, + "right": { + "button": { + "wrapper": "kUHE42xvQVzWqabl" + }, + "button_t": { + "wrapper": "sXjzYBob5Y4psogB" + }, + "upgrade_button": { + "wrapper": "Upqw01TOXETOmR5Td7Dj" + } + } + }, + "playbar": { + "widget": {}, + "controls": {}, + "buttons": { + "button": { + "wrapper": "_EuXSrqTA94N1Evn", + "wrapper__active": "v1kNYVvW5P8Wa4jS", + "wrapper__indicator": "EHxL6K_6WWDlTCZP6x5w" + } + } + }, + "navbar": { + "link": { + "wrapper": "kUHE42xvQVzWqabl", + "container": "kUHE42xvQVzWqabl", + "container__active": "kUHE42xvQVzWqabl" + } + }, + "global_navbar": {}, + "sidebar": {}, + "panel": {} + }, + "tracklist": { + "column_header": "F_VvNCRKZ2cKj1a9" + }, + "search_chips": { + "wrapper_wrapper": "qZHfdckfFVY5AURF", + "wrapper": "x_HLN829yDsvJDgl", + "container": "dxlv5wfAu8zgm7HY", + "chip": "U4idT8HUFwfj7PWP" + }, + "modal": { + "widget_generator": { + "container": "gx9qsprOAO4ubEFc", + "header": { + "container": "g9sFYudWkUldbuNa", + "close": "Pk4GI8WfBMjS5R2J" + }, + "content": { + "container": "ywtSYUL2xRwVlvSo" + } + }, + "track_credits": { + "container": "p5LJBmlswjcRT458", + "header": { + "container": "idsrX6VAUY49Eczw", + "close": "_18qFNPzv7Eyyck8" + }, + "content": { + "container": "yKG9lWaUWIBUyVCu" + } + } + }, + "settings": { + "header": { + "container": "fEEfljGMIEhhmWVi" + }, + "section": { + "container": "nWR7PbHjFXQJiYNL" + }, + "button": { + "wrapper": "nHgEqWYM1q0omZ6y" + }, + "text_input": "e-10451-form-input e-10451-form-control encore-text-body-medium" + }, + "scrollable_text": { + "container": "MyaStf5D5Im3gt9M", + "wrapper": "dxlv5wfAu8zgm7HY" + }, + "sort_box": { + "list": { + "button": "LQ6y2jhZZtMS7xCr" + } + }, + "search_box": { + "container": "S0nU7XTXYMOBcASz", + "expand_button": "eu6ObgEHE5owtzi_" + }, + "context_menu": { + "menu_item": "yqG4N5YYcY4uEujt" + } +} diff --git a/1020098/css-map.json b/1020098/css-map.json new file mode 100644 index 0000000..e19fdc7 --- /dev/null +++ b/1020098/css-map.json @@ -0,0 +1,5 @@ +{ + "U4idT8HUFwfj7PWP": "search-searchCategory-categoryGridItem", + "fEEfljGMIEhhmWVi": "x-settings-outputSectionHeader", + "nWR7PbHjFXQJiYNL": "x-settings-section" +} diff --git a/index.json b/index.json index 5f06ca2..8bd086a 100644 --- a/index.json +++ b/index.json @@ -97,6 +97,22 @@ }, "spotifyVersion": "1.2.97.270", "status": "verified" + }, + "1020098": { + "classmap": { + "file": "classmap-19f856aefd5.json", + "sha256": "d69d189567b5ddb7fcafffa9f4e7d84ee8b2ed27160ae168ae7bd8c8d03d7956" + }, + "cssMapOverlay": { + "file": "css-map.json", + "sha256": "8c05d9ca347e4e13fb31013df4c42bdbf16046408b989afe4d0ea6a25037800e" + }, + "meta": { + "file": "META.json", + "sha256": "fa0334b0b336907d6a796e259c2607907bf4e1a028545c1a4e105621130bab87" + }, + "spotifyVersion": "1.2.98.301", + "status": "verified" } }, "version": 1 diff --git a/scripts/build_index.py b/scripts/build_index.py index 01d7f75..12f013a 100644 --- a/scripts/build_index.py +++ b/scripts/build_index.py @@ -94,7 +94,7 @@ def main() -> int: print("index.json is up to date") return 0 - INDEX.write_text(rendered) + INDEX.write_text(rendered, encoding="utf-8", newline="\n") print(f"wrote {INDEX.relative_to(ROOT)} ({len(build()['keys'])} keys)") return 0 diff --git a/scripts/promote_inherited.py b/scripts/promote_inherited.py index 16709ac..5b21b20 100644 --- a/scripts/promote_inherited.py +++ b/scripts/promote_inherited.py @@ -319,7 +319,9 @@ def promote_inherited_release( shutil.copy2(source_classmap, staging_dir / source_classmap.name) if overlay.is_file(): shutil.copy2(overlay, staging_dir / overlay.name) - (staging_dir / "META.json").write_text(json.dumps(meta, indent=2, allow_nan=False) + "\n") + (staging_dir / "META.json").write_text( + json.dumps(meta, indent=2, allow_nan=False) + "\n", encoding="utf-8", newline="\n" + ) staging_dir.rename(target_dir) except Exception: shutil.rmtree(staging_dir, ignore_errors=True) diff --git a/scripts/test_promote_inherited.py b/scripts/test_promote_inherited.py index e2a3b6a..74dd9a6 100644 --- a/scripts/test_promote_inherited.py +++ b/scripts/test_promote_inherited.py @@ -12,6 +12,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent)) import promote_inherited as promote # noqa: E402 +import build_index # noqa: E402 class PromoteInheritedTests(unittest.TestCase): @@ -114,6 +115,7 @@ def test_promotes_an_unchanged_map_and_records_new_stale_paths(self): {"customHash": "semantic-name"}, ) meta = json.loads((target / "META.json").read_text()) + self.assertNotIn(b"\r", (target / "META.json").read_bytes()) self.assertEqual(meta["status"], "verified") self.assertEqual(meta["inherited_from"], "1020094") self.assertEqual( @@ -126,6 +128,23 @@ def test_promotes_an_unchanged_map_and_records_new_stale_paths(self): self.assertEqual(meta["required_paths"]["main.topbar.wrapper"], "verified_cdp") self.assertEqual(meta["required_paths"]["main.playbar.indicator"], "unverified") + def test_generated_index_uses_lf_and_hashes_published_bytes(self): + index_path = self.root / "index.json" + with ( + mock.patch.object(build_index, "ROOT", self.root), + mock.patch.object(build_index, "INDEX", index_path), + mock.patch.object(sys, "argv", ["build_index.py"]), + ): + self.assertEqual(build_index.main(), 0) + index_bytes = index_path.read_bytes() + self.assertNotIn(b"\r", index_bytes) + entry = json.loads(index_bytes)["keys"]["1020094"] + for field in ("classmap", "cssMapOverlay", "meta"): + self.assertEqual( + entry[field]["sha256"], + hashlib.sha256((self.source / entry[field]["file"]).read_bytes()).hexdigest(), + ) + def test_refuses_a_report_from_another_spotify_version(self): self.cdp_report["cdp"]["browser"]["User-Agent"] = "Spotify/1.2.95.453 Chrome/146"