diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8895d684f..154fc62da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -560,10 +560,16 @@ jobs: ".github/workflows/build.yml", "CHANGELOG.md", "README.md", + "ROADMAP.md", "app/src/main/java/com/papi/nova/ui/NovaGameDetailActivity.kt", + "clients/deck/README.md", + "clients/deck/docs/steam-frame.md", + "clients/deck/packaging/flatpak/README.md", + "docs/linux-updates.md", "docs/screenshots/beta2/nova-control-detail-aurora.webp", "docs/screenshots/beta2/nova-control-detail-director.webp", "docs/screenshots/beta2/nova-play-setup-aurora.webp", + "docs/screenshots/beta2/polaris-mission-control-aurora.webp", "docs/screenshots/beta2/provenance.json", "app/src/test/java/com/papi/nova/NovaReleaseMetadataTest.kt", "app/src/test/java/com/papi/nova/ui/NovaComposeBuildConfigurationTest.kt", @@ -596,7 +602,19 @@ jobs: subprocess.run(["git", "merge-base", "--is-ancestor", FRAME["source"], source], check=True) changed = subprocess.check_output(["git", "diff", "--no-renames", "--name-only", "-z", FRAME["source"], source]).decode().split("\0") - require(set(changed) - {""} <= RELEASE_ONLY, "Runtime/source inputs changed after Frame qualification") + changed = set(changed) - {""} + metainfo = "clients/deck/packaging/flatpak/com.papi_ux.Nova.metainfo.xml" + if metainfo in changed: + # This approved maturity label does not change the qualified Frame runtime. + # Keep built metadata outside the path allowlist: no other byte or mode may change. + before = subprocess.check_output(["git", "show", FRAME["source"] + ":" + metainfo]) + after = subprocess.check_output(["git", "show", source + ":" + metainfo]) + require(before.count(b"This Alpha targets") == 1 and + after == before.replace(b"This Alpha targets", b"This Beta targets", 1) and + output("git", "ls-tree", source, "--", metainfo).startswith("100644 blob "), + "Unqualified Frame metadata change") + changed.remove(metainfo) + require(changed <= RELEASE_ONLY, "Runtime/source inputs changed after Frame qualification") def digest(path): require(path.is_file() and not path.is_symlink(), "Not an ordinary asset: " + str(path)) diff --git a/app/src/test/java/com/papi/nova/NovaReleaseMetadataTest.kt b/app/src/test/java/com/papi/nova/NovaReleaseMetadataTest.kt index bb3e95fba..de6cb1326 100644 --- a/app/src/test/java/com/papi/nova/NovaReleaseMetadataTest.kt +++ b/app/src/test/java/com/papi/nova/NovaReleaseMetadataTest.kt @@ -590,6 +590,12 @@ class ReleaseReadback(unittest.TestCase): (root / "clients/deck/tests/deck_library_navigation_test.py").write_text("qualified test fixture\n") (root / "CHANGELOG.md").write_text("qualified notes\n") (root / "README.md").write_text("qualified README\n") + metainfo = "clients/deck/packaging/flatpak/com.papi_ux.Nova.metainfo.xml" + (root / metainfo).parent.mkdir(parents=True, exist_ok=True) + description = "This Alpha targets Steam Frame." + if case == "beta-metadata-ambiguous": description += " This Alpha targets another device." + (root / metainfo).write_text('' + description + + 'nova-deck\n') (root / ".github/workflows/build.yml").write_text("old release-only metadata\n") git("add", "."); git("commit", "-qm", "qualified source") source, tree = git("rev-parse", "HEAD"), git("rev-parse", "HEAD^{tree}") @@ -623,6 +629,28 @@ class ReleaseReadback(unittest.TestCase): if case == "unsafe-checksum": (root / "remote" / (name + ".sha256")).write_text("a" * 64 + " ../../escape\n") if case == "held": pins["source"] = "PENDING" if case == "tree": pins["tree"] = "0" * 40 + successor_paths = [] + if case == "native-beta-docs": + for path in ("ROADMAP.md", "clients/deck/README.md", "clients/deck/docs/steam-frame.md", + "clients/deck/packaging/flatpak/README.md", "docs/linux-updates.md", + "docs/screenshots/beta2/polaris-mission-control-aurora.webp"): + (root / path).parent.mkdir(parents=True, exist_ok=True) + (root / path).write_bytes(b"approved documentation fixture\n") + successor_paths.append(path) + if case == "native-beta-doc-near-name": + (root / "docs").mkdir(exist_ok=True) + (root / "docs/linux-updates.md.extra").write_text("unqualified near-name\n") + successor_paths.append("docs/linux-updates.md.extra") + if case == "native-beta-docs" or case.startswith("beta-metadata"): + content = (root / metainfo).read_bytes().replace(b"This Alpha targets", b"This Beta targets", 1) + if case == "beta-metadata-command": content = content.replace(b"nova-deck", b"other-command") + if case == "beta-metadata-extra": content += b"\n" + (root / metainfo).write_bytes(content) + successor_paths.append(metainfo) + if case == "beta-metadata-mode": (root / metainfo).chmod(0o755) + if case.startswith("native-beta-doc") or case.startswith("beta-metadata"): + git("add", "--", *successor_paths); git("commit", "-qm", "native Beta documentation successor") + release = git("rev-parse", "HEAD"); git("tag", "-f", tag) if case == "runtime-qml": (root / "clients/deck/qml/NovaHud.qml").write_text("unqualified QML\n") git("add", "clients/deck/qml/NovaHud.qml"); git("commit", "-qm", "runtime drift") @@ -685,11 +713,17 @@ class ReleaseReadback(unittest.TestCase): calls = [json.loads(line) for line in (root / "calls").read_text().splitlines()] if (root / "calls").exists() else [] self.assertTrue(all(a[:2] in (["release", "view"], ["release", "download"]) for a in calls)) if case in ("held", "tree", "runtime-qml", "readme-near-name", "readme-image-near-name", "readme-other-image", - "android-activity-near-name", "android-other-source"): self.assertEqual([], calls) + "android-activity-near-name", "android-other-source", "native-beta-doc-near-name", + "beta-metadata-command", "beta-metadata-extra", "beta-metadata-ambiguous", + "beta-metadata-mode"): self.assertEqual([], calls) print(case + ": expected " + ("PASS" if succeeds else "REFUSE"), flush=True) def test_changed_release_boundaries(self): for case, phase, succeeds in ( + ("native-beta-docs", "preflight", True), ("beta-metadata", "final", True), + ("beta-metadata-command", "preflight", False), ("beta-metadata-extra", "preflight", False), + ("beta-metadata-ambiguous", "preflight", False), ("beta-metadata-mode", "preflight", False), + ("native-beta-doc-near-name", "preflight", False), ("ordinary-eight", "final", True), ("draft-pair", "preflight", True), ("final-ten", "final", True), ("rerun-ten", "preflight", True), ("missing", "preflight", False), ("half", "preflight", False),