diff --git a/.github/scripts/test_verify_relink_recovery_snapshot.py b/.github/scripts/test_verify_relink_recovery_snapshot.py index 50113aef..0b55bbe1 100644 --- a/.github/scripts/test_verify_relink_recovery_snapshot.py +++ b/.github/scripts/test_verify_relink_recovery_snapshot.py @@ -43,7 +43,8 @@ def make_snapshot(path: Path, content: str, context: str = "ספר א") -> None: class RecoverySnapshotVerifierTest(unittest.TestCase): - def run_case(self, original_content, rebuilt_content, artifact_record=None, rebuilt_context="ספר א"): + def run_case(self, original_content, rebuilt_content, artifact_record=None, rebuilt_context="ספר א", + meta_schema=2, baseline_schema=2): temporary = tempfile.TemporaryDirectory() self.addCleanup(temporary.cleanup) root = Path(temporary.name) @@ -53,12 +54,12 @@ def run_case(self, original_content, rebuilt_content, artifact_record=None, rebu digest = hashlib.sha256(original.read_bytes()).hexdigest() payload_meta = root / "meta.json" payload_meta.write_text( - json.dumps({"schema_version": 2, "snapshot": {"sha256": digest}}), + json.dumps({"schema_version": meta_schema, "snapshot": {"sha256": digest}}), encoding="utf-8", ) baseline = root / "baseline.json" baseline.write_text( - json.dumps({"schema_version": 2, "snapshot_sha256": digest}), + json.dumps({"schema_version": baseline_schema, "snapshot_sha256": digest}), encoding="utf-8", ) artifacts = root / "artifacts" / "Sefaria" @@ -84,6 +85,23 @@ def test_identical_snapshots_pass(self): self.assertEqual(result.returncode, 0, result.stderr) self.assertIn("unlinked_image_src_differences=0", result.stdout) + def test_linker_meta_schema_3_passes(self): + # The Linker writes meta.json schema 3 since a9ae2d4; the snapshot sha + # field it binds is unchanged, so a recovery must still verify. + result = self.run_case("טקסט", "טקסט", meta_schema=3) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("RECOVERY_SNAPSHOT_SEMANTIC_OK", result.stdout) + + def test_unknown_linker_meta_schema_fails(self): + result = self.run_case("טקסט", "טקסט", meta_schema=4) + self.assertNotEqual(result.returncode, 0) + self.assertIn("Linker meta schema", result.stderr) + + def test_unknown_baseline_schema_fails(self): + result = self.run_case("טקסט", "טקסט", baseline_schema=3) + self.assertNotEqual(result.returncode, 0) + self.assertIn("line baseline schema 2", result.stderr) + def test_unlinked_remote_to_inline_image_passes(self): inline = base64.b64encode(b"png").decode() result = self.run_case( diff --git a/.github/scripts/verify_relink_recovery_snapshot.py b/.github/scripts/verify_relink_recovery_snapshot.py index cabd647b..42462c63 100644 --- a/.github/scripts/verify_relink_recovery_snapshot.py +++ b/.github/scripts/verify_relink_recovery_snapshot.py @@ -31,6 +31,8 @@ re.IGNORECASE, ) MAX_IMAGE_BYTES = 5 * 1024 * 1024 +PAYLOAD_META_SCHEMAS = frozenset({2, 3}) +BASELINE_SCHEMA = 2 def _sha256(path: Path) -> str: @@ -116,8 +118,19 @@ def verify( raise SystemExit("original raw snapshot SHA does not match Linker payload metadata") if baseline.get("snapshot_sha256") != original_sha: raise SystemExit("line baseline is not bound to the original raw snapshot") - if meta.get("schema_version") != 2 or baseline.get("schema_version") != 2: - raise SystemExit("recovery snapshot comparison requires schema 2") + # The Linker bumped meta.json to schema 3 in a9ae2d4 (2026-08-31) without + # changing ``snapshot.sha256`` — the only field read here. The line baseline + # manifest is still schema 2 (line_baseline.SCHEMA_VERSION). + if meta.get("schema_version") not in PAYLOAD_META_SCHEMAS: + raise SystemExit( + "recovery snapshot comparison requires Linker meta schema " + f"{sorted(PAYLOAD_META_SCHEMAS)}, got {meta.get('schema_version')!r}" + ) + if baseline.get("schema_version") != BASELINE_SCHEMA: + raise SystemExit( + f"recovery snapshot comparison requires line baseline schema {BASELINE_SCHEMA}, " + f"got {baseline.get('schema_version')!r}" + ) connections = [] for path in (original, rebuilt):