Skip to content

Commit 2fb6e5f

Browse files
Pigbibicodex
andcommitted
Bind Firestore handoff to exact legacy bytes
Co-Authored-By: Codex <noreply@openai.com>
1 parent 6d03bc5 commit 2fb6e5f

2 files changed

Lines changed: 186 additions & 0 deletions

File tree

src/publish.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from copy import deepcopy
55
from dataclasses import dataclass
66
import hashlib
7+
import json
78
from pathlib import Path
89
import re
910
from typing import Any
@@ -26,6 +27,10 @@
2627
"btc_cycle_indicators.json",
2728
)
2829

30+
LIVE_POOL_LEGACY_EXACT_BYTES_CONTRACT_VERSION = (
31+
"qsl.crypto_live_pool_legacy_exact_bytes.v1"
32+
)
33+
2934

3035
@dataclass(frozen=True)
3136
class PublishSettings:
@@ -304,6 +309,47 @@ def build_firestore_payload(
304309
) -> dict[str, Any]:
305310
symbol_map = dict(artifacts.live_pool_legacy["symbols"])
306311
symbols = list(symbol_map.keys())
312+
exact_bytes = artifacts.live_pool_legacy_path.read_bytes()
313+
try:
314+
exact_text = exact_bytes.decode("utf-8")
315+
except UnicodeDecodeError as exc:
316+
raise ValueError("live_pool_legacy.json exact bytes must be valid UTF-8.") from exc
317+
try:
318+
exact_payload = json.loads(exact_text)
319+
except json.JSONDecodeError as exc:
320+
raise ValueError("live_pool_legacy.json exact bytes must contain valid JSON.") from exc
321+
if not isinstance(exact_payload, dict):
322+
raise ValueError("live_pool_legacy.json exact bytes must contain a JSON object.")
323+
324+
exact_digest = hashlib.sha256(exact_bytes).hexdigest()
325+
manifest_entry = artifacts.artifact_manifest.get("artifacts", {}).get(
326+
"live_pool_legacy", {}
327+
)
328+
identity_entry = artifacts.runtime_evidence_identity.get("artifacts", {}).get(
329+
"live_pool_legacy", {}
330+
)
331+
if (
332+
manifest_entry.get("sha256") != exact_digest
333+
or identity_entry.get("sha256") != exact_digest
334+
):
335+
raise ValueError(
336+
"live_pool_legacy.json exact bytes digest mismatch with artifact manifest or runtime identity."
337+
)
338+
339+
if (
340+
exact_payload.get("as_of_date") != artifacts.as_of_date
341+
or exact_payload.get("version") != artifacts.version
342+
or exact_payload.get("mode") != settings.mode
343+
or exact_payload.get("symbols") != artifacts.live_pool_legacy.get("symbols")
344+
or exact_payload.get("symbol_map")
345+
!= artifacts.live_pool_legacy.get("symbol_map")
346+
or list(exact_payload.get("symbols", {})) != symbols
347+
or exact_payload.get("symbol_map") != symbol_map
348+
):
349+
raise ValueError(
350+
"live_pool_legacy.json exact bytes do not match Firestore convenience fields."
351+
)
352+
307353
generated_at = pd.Timestamp.now(tz="UTC").isoformat()
308354
return {
309355
"as_of_date": artifacts.as_of_date,
@@ -325,6 +371,11 @@ def build_firestore_payload(
325371
"generated_at": generated_at,
326372
"source_project": settings.source_project,
327373
"runtime_evidence_identity": deepcopy(artifacts.runtime_evidence_identity),
374+
"live_pool_legacy_exact_bytes": {
375+
"contract_version": LIVE_POOL_LEGACY_EXACT_BYTES_CONTRACT_VERSION,
376+
"encoding": "utf-8",
377+
"utf8_text": exact_text,
378+
},
328379
}
329380

330381

tests/test_release_contract.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from src.publish import (
1212
PublishSettings,
13+
ReleaseArtifacts,
1314
build_firestore_payload,
1415
build_release_manifest,
1516
build_storage_layout,
@@ -31,6 +32,21 @@ def sha256_file(path: Path) -> str:
3132
return digest.hexdigest()
3233

3334

35+
def build_publish_settings() -> PublishSettings:
36+
return PublishSettings(
37+
enabled=False,
38+
dry_run=True,
39+
mode="core_major",
40+
project_id=None,
41+
cloud_bucket=None,
42+
cloud_root_prefix="crypto-live-pool-pipelines",
43+
firestore_collection="strategy",
44+
firestore_document="CRYPTO_LIVE_POOL_ROTATION_LIVE_POOL",
45+
source_project="crypto-live-pool-pipelines",
46+
upload_current_pointer=False,
47+
)
48+
49+
3450
class ReleaseContractValidationTests(unittest.TestCase):
3551
def build_outputs(
3652
self,
@@ -177,6 +193,21 @@ def build_outputs(
177193
},
178194
)
179195

196+
def build_runtime_identity_outputs(self, root: Path) -> Path:
197+
self.build_outputs(
198+
root,
199+
include_manifest=True,
200+
include_runtime_evidence_identity=True,
201+
)
202+
return root / "data" / "output"
203+
204+
def load_publish_context(
205+
self, output_dir: Path
206+
) -> tuple[ReleaseArtifacts, PublishSettings, dict[str, object]]:
207+
artifacts = load_release_artifacts(output_dir, "core_major")
208+
settings = build_publish_settings()
209+
return artifacts, settings, build_storage_layout(settings, artifacts)
210+
180211
def test_validate_release_outputs_accepts_consistent_contract(self) -> None:
181212
with tempfile.TemporaryDirectory() as tmp_dir:
182213
root = Path(tmp_dir)
@@ -236,6 +267,110 @@ def test_identity_is_canonical_across_artifact_release_and_firestore(self) -> No
236267
self.assertEqual(release_manifest["runtime_evidence_identity"], identity)
237268
self.assertEqual(firestore_payload["runtime_evidence_identity"], identity)
238269

270+
def test_firestore_payload_preserves_exact_legacy_artifact_bytes(self) -> None:
271+
with tempfile.TemporaryDirectory() as tmp_dir:
272+
root = Path(tmp_dir)
273+
output_dir = self.build_runtime_identity_outputs(root)
274+
legacy_path = output_dir / "live_pool_legacy.json"
275+
legacy_payload = json.loads(legacy_path.read_text(encoding="utf-8"))
276+
exact_bytes = ("\n" + json.dumps(legacy_payload, separators=(", ", ": ")) + "\n").encode(
277+
"utf-8"
278+
)
279+
legacy_path.write_bytes(exact_bytes)
280+
281+
manifest_path = output_dir / "artifact_manifest.json"
282+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
283+
exact_digest = hashlib.sha256(exact_bytes).hexdigest()
284+
manifest["artifacts"]["live_pool_legacy"]["sha256"] = exact_digest
285+
manifest["runtime_evidence_identity"]["artifacts"]["live_pool_legacy"][
286+
"sha256"
287+
] = exact_digest
288+
write_json(manifest_path, manifest)
289+
290+
artifacts, settings, storage_layout = self.load_publish_context(output_dir)
291+
firestore_payload = build_firestore_payload(
292+
settings,
293+
artifacts,
294+
storage_layout,
295+
)
296+
297+
handoff = firestore_payload["live_pool_legacy_exact_bytes"]
298+
self.assertEqual(
299+
handoff["contract_version"],
300+
"qsl.crypto_live_pool_legacy_exact_bytes.v1",
301+
)
302+
self.assertEqual(handoff["encoding"], "utf-8")
303+
self.assertEqual(handoff["utf8_text"].encode("utf-8"), exact_bytes)
304+
305+
def test_firestore_payload_rejects_mutated_legacy_bytes_with_unchanged_identity(self) -> None:
306+
with tempfile.TemporaryDirectory() as tmp_dir:
307+
root = Path(tmp_dir)
308+
output_dir = self.build_runtime_identity_outputs(root)
309+
artifacts, settings, storage_layout = self.load_publish_context(output_dir)
310+
with artifacts.live_pool_legacy_path.open("ab") as handle:
311+
handle.write(b"\n")
312+
313+
with self.assertRaisesRegex(ValueError, "digest mismatch"):
314+
build_firestore_payload(
315+
settings,
316+
artifacts,
317+
storage_layout,
318+
)
319+
320+
def test_firestore_payload_rejects_top_level_convenience_field_mismatch(self) -> None:
321+
for field in ("symbols", "symbol_map"):
322+
with self.subTest(field=field), tempfile.TemporaryDirectory() as tmp_dir:
323+
root = Path(tmp_dir)
324+
output_dir = self.build_runtime_identity_outputs(root)
325+
artifacts, settings, storage_layout = self.load_publish_context(output_dir)
326+
if field == "symbols":
327+
artifacts.live_pool_legacy[field].pop("TRXUSDT")
328+
else:
329+
artifacts.live_pool_legacy[field]["TRXUSDT"] = {
330+
"base_asset": "MISMATCH"
331+
}
332+
333+
with self.assertRaisesRegex(ValueError, "convenience fields"):
334+
build_firestore_payload(
335+
settings,
336+
artifacts,
337+
storage_layout,
338+
)
339+
340+
def test_firestore_payload_rejects_invalid_legacy_artifact_bytes(self) -> None:
341+
cases = {
342+
"invalid_utf8": (b"\xff", "valid UTF-8"),
343+
"invalid_json": (b"{", "valid JSON"),
344+
"non_object": (b"[]", "JSON object"),
345+
}
346+
for label, (invalid_bytes, expected_error) in cases.items():
347+
with self.subTest(label=label), tempfile.TemporaryDirectory() as tmp_dir:
348+
root = Path(tmp_dir)
349+
output_dir = self.build_runtime_identity_outputs(root)
350+
artifacts, settings, storage_layout = self.load_publish_context(output_dir)
351+
artifacts.live_pool_legacy_path.write_bytes(invalid_bytes)
352+
353+
with self.assertRaisesRegex(ValueError, expected_error):
354+
build_firestore_payload(
355+
settings,
356+
artifacts,
357+
storage_layout,
358+
)
359+
360+
def test_firestore_payload_rejects_missing_legacy_artifact(self) -> None:
361+
with tempfile.TemporaryDirectory() as tmp_dir:
362+
root = Path(tmp_dir)
363+
output_dir = self.build_runtime_identity_outputs(root)
364+
artifacts, settings, storage_layout = self.load_publish_context(output_dir)
365+
artifacts.live_pool_legacy_path.unlink()
366+
367+
with self.assertRaises(FileNotFoundError):
368+
build_firestore_payload(
369+
settings,
370+
artifacts,
371+
storage_layout,
372+
)
373+
239374
def test_artifact_byte_mutation_breaks_runtime_identity_before_publish(self) -> None:
240375
with tempfile.TemporaryDirectory() as tmp_dir:
241376
root = Path(tmp_dir)

0 commit comments

Comments
 (0)