fix: require package v2 for install readiness

This commit is contained in:
2026-09-27 03:04:28 +08:00
parent 359b93c6bc
commit 486e3f7706
2 changed files with 52 additions and 2 deletions
@@ -35,7 +35,10 @@ RESTORE_TEST_SCHEMA = "yuqei.first-release-restore-test.v1"
PLAN_SCHEMA = "yuqei.first-release-snapshot-restore-plan.v1"
READINESS_RECEIPT_SCHEMA = "yuqei.first-release-recovery-readiness-receipt.v1"
IMAGE_IDENTITY_SCHEMA = "yuqei.release-image-identity.v2"
PACKAGE_SCHEMA = "yuqei.release-package.v1"
PACKAGE_SCHEMA = "yuqei.release-package.v2"
READ_ONLY_PACKAGE_SCHEMAS = frozenset(
{"yuqei.release-package.v1", PACKAGE_SCHEMA}
)
SOURCE_MANIFEST_SCHEMA = "yuqei.release-source-manifest.v1"
CHECKSUM_MANIFEST_SCHEMA = "yuqei.release-package-checksums.v1"
INSTALLABLE_MANIFEST_SCHEMA = "yuqei.installable-release-manifest.v1"
@@ -416,6 +419,7 @@ def _validate_package(
*,
repo_root: Path,
package_validator: Callable[..., Any] | None = None,
require_installable_package_v2: bool = False,
) -> dict[str, Any]:
root, package, actual, bootstrapped_release, release_sha = _bootstrap_release_package(
package_dir,
@@ -438,7 +442,12 @@ def _validate_package(
if getattr(validated, "package_sha256", None) != actual:
raise SnapshotRestorePlanError("official validator package hash drifted")
validated_package = getattr(validated, "package", None)
if validated_package != package or package.get("schema_version") != PACKAGE_SCHEMA:
package_schema = package.get("schema_version")
if (
validated_package != package
or package_schema not in READ_ONLY_PACKAGE_SCHEMAS
or (require_installable_package_v2 and package_schema != PACKAGE_SCHEMA)
):
raise SnapshotRestorePlanError("official validator package payload/schema drifted")
release_manifest = getattr(validated, "release_manifest", None)
if release_manifest != bootstrapped_release:
@@ -581,6 +590,7 @@ def _validate_package(
raise SnapshotRestorePlanError("official validator trusted build receipt drifted")
return {
"release_id": release_id,
"package_schema_version": package_schema,
"package_sha256": actual,
"source_commit": source_commit,
"release_manifest_sha256": release_sha,
@@ -1400,12 +1410,14 @@ def _verify_raw_inputs(
trusted_root_public_key_file: Path,
now: datetime,
package_validator_for_tests: Callable[..., Any] | None = None,
require_installable_package_v2: bool = False,
) -> tuple[dict[str, Any], dict[str, Any], str]:
package_binding = _validate_package(
package_dir,
expected_package_sha256,
repo_root=repo_root,
package_validator=package_validator_for_tests,
require_installable_package_v2=require_installable_package_v2,
)
evidence = load_json(evidence_file, label="first-release recovery evidence")
keyring = load_json(keyring_file, label="recovery keyring")
@@ -1458,6 +1470,7 @@ def verify_and_build_output(
trusted_root_public_key_file=trusted_root_public_key_file,
now=now,
package_validator_for_tests=package_validator_for_tests,
require_installable_package_v2=(output_kind == "install-readiness-receipt"),
)
if output_kind == "plan":
return build_plan(
@@ -1527,6 +1540,7 @@ def validate_install_readiness(
keyring_file=keyring_file,
trusted_root_public_key_file=trusted_root_public_key_file,
now=now,
require_installable_package_v2=True,
)
rebuilt = build_install_readiness_receipt(
package_binding=package_binding,
@@ -594,6 +594,19 @@ def _build_output(case: dict[str, Any], output_kind: str) -> dict[str, Any]:
)
def _rewrite_package_schema(case: dict[str, Any], schema: str) -> None:
package = json.loads(case["package_path"].read_text(encoding="utf-8"))
package["schema_version"] = schema
package = _embedded(
{key: value for key, value in package.items() if key != "package_payload_sha256"},
"package_payload_sha256",
)
_write_json(case["package_path"], package)
case["package_sha256"] = _sha(case["package_path"])
case["validated_package"].package = package
case["validated_package"].package_sha256 = case["package_sha256"]
def test_valid_first_release_evidence_emits_only_a_non_writing_plan(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
@@ -674,6 +687,29 @@ def test_install_readiness_receipt_binds_every_gate_input_without_allowing_resto
)
def test_v1_package_remains_read_only_and_cannot_mint_install_readiness(
tmp_path: Path,
) -> None:
case = _case(tmp_path)
_rewrite_package_schema(case, "yuqei.release-package.v1")
binding = planner._validate_package(
case["package_dir"],
case["package_sha256"],
repo_root=case["repo_root"],
package_validator=case["package_validator"],
)
assert binding["package_schema_version"] == "yuqei.release-package.v1"
with pytest.raises(planner.SnapshotRestorePlanError, match="payload/schema drifted"):
planner._validate_package(
case["package_dir"],
case["package_sha256"],
repo_root=case["repo_root"],
package_validator=case["package_validator"],
require_installable_package_v2=True,
)
@pytest.mark.parametrize("which", ("application", "infrastructure"))
def test_official_package_result_must_cover_all_eleven_images(
tmp_path: Path,