From cee6e96eaeb7d85b84b4dcdeaa7be877acc867a4 Mon Sep 17 00:00:00 2001 From: 0cwa Date: Fri, 25 Sep 2026 11:05:15 +0000 Subject: [PATCH 1/3] fix: make runtime boot animation ZIP deterministic --- src/boot_animation.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/boot_animation.py b/src/boot_animation.py index b216fc90..0a3a4cb6 100644 --- a/src/boot_animation.py +++ b/src/boot_animation.py @@ -225,8 +225,17 @@ def build_runtime_payload(path: str | os.PathLike[str]) -> bytes: for info in source.infolist(): if info.is_dir(): continue - runtime.writestr( + runtime_info = zipfile.ZipInfo( info.filename, + date_time=(1980, 1, 1, 0, 0, 0), + ) + runtime_info.compress_type = zipfile.ZIP_STORED + runtime_info.create_system = 3 + runtime_info.external_attr = (stat.S_IFREG | 0o644) << 16 + runtime_info.extra = b"" + runtime_info.comment = b"" + runtime.writestr( + runtime_info, _read_member(source, info), compress_type=zipfile.ZIP_STORED, ) From dbcbef83a476c3f70c321404d25744b6255e3e77 Mon Sep 17 00:00:00 2001 From: 0cwa Date: Fri, 25 Sep 2026 11:05:29 +0000 Subject: [PATCH 2/3] build: version deterministic boot animation contract --- src/ci/selection_variant.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ci/selection_variant.sh b/src/ci/selection_variant.sh index 3038b5bb..7047a923 100755 --- a/src/ci/selection_variant.sh +++ b/src/ci/selection_variant.sh @@ -7,7 +7,7 @@ # this helper owns the byte-for-byte representation that is hashed by both the # build and release preflight paths. -BOOT_ANIMATION_SELECTION_CONTRACT="product-image-root-theme-context-stored-v6" +BOOT_ANIMATION_SELECTION_CONTRACT="product-image-root-theme-context-deterministic-stored-v7" function selection_variant_manifest() { local field value From 84b5d7b3a92a453c35f0c445cc043e4a38441544 Mon Sep 17 00:00:00 2001 From: 0cwa Date: Fri, 25 Sep 2026 11:05:31 +0000 Subject: [PATCH 3/3] test: require deterministic runtime boot animation ZIPs --- tests/boot_animation_test.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/boot_animation_test.py b/tests/boot_animation_test.py index 509d0b0c..1721c5e0 100644 --- a/tests/boot_animation_test.py +++ b/tests/boot_animation_test.py @@ -31,6 +31,20 @@ def write_valid(path: Path, frame: bytes = b"frame") -> None: archive.writestr("part0/frame.png", frame) +def write_valid_with_timestamp( + path: Path, + timestamp: tuple[int, int, int, int, int, int], +) -> None: + with zipfile.ZipFile(path, "w") as archive: + for name, data in ( + ("desc.txt", b"1 1 1\np 1 0 part0\n"), + ("part0/frame.png", b"frame"), + ): + info = zipfile.ZipInfo(name, date_time=timestamp) + info.compress_type = zipfile.ZIP_DEFLATED + archive.writestr(info, data) + + def assert_rejected(path: Path, context: str) -> None: try: validate_payload(path) @@ -84,6 +98,13 @@ def test_runtime_payload_installation() -> None: files = [info for info in archive.infolist() if not info.is_dir()] assert files assert all(info.compress_type == zipfile.ZIP_STORED for info in files) + assert all(info.date_time == (1980, 1, 1, 0, 0, 0) for info in files) + + timestamp_a = root / "timestamp-a.zip" + timestamp_b = root / "timestamp-b.zip" + write_valid_with_timestamp(timestamp_a, (2020, 1, 2, 3, 4, 6)) + write_valid_with_timestamp(timestamp_b, (2026, 9, 25, 10, 58, 20)) + assert build_runtime_payload(timestamp_a) == build_runtime_payload(timestamp_b) product = FakeExtFs(root / "product-fs") install_runtime_payload({"product": product}, runtime)