feat: add public cross-project calculation contract

This commit is contained in:
732642856
2026-07-16 12:18:37 +08:00
parent 1b91667087
commit fd06bdc2ee
4 changed files with 192 additions and 0 deletions
@@ -0,0 +1,12 @@
{
"schema_version": 1,
"privacy_scope": "public_synthetic_only",
"fixtures": [
{
"id": "public_synthetic_delhi_1990_noon_mean_lahiri",
"birth": {"synthetic": true, "year": 1990, "month": 1, "day": 1, "hour": 12, "minute": 0, "second": 0, "lat": 28.6139, "lon": 77.209, "tz": 5.5},
"effective": {"ayanamsa": "lahiri", "node_mode": "mean", "timezone_offset": 5.5},
"compatibility_hash": "257042461fe303aa3bff5a8333f65090832ce8a2be395f038523838d724b474b"
}
]
}
@@ -0,0 +1,4 @@
{
"schema_version": 1,
"entries": []
}
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""Validate public synthetic calculation fixtures shared across Jyotish projects."""
from __future__ import annotations
import argparse
import hashlib
import json
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT / "scripts") not in sys.path:
sys.path.insert(0, str(ROOT / "scripts"))
from jyotish_engine import compute_chart_data
PLANETS = ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu")
REQUIRED_LEDGER_FIELDS = {
"source_repository", "source_commit", "target_repository", "target_commit",
"change_class", "copied_files", "dependency_delta", "privacy_review",
"focused_tests", "hash_contract_result", "rollback",
}
def load_manifest(path: Path) -> dict[str, Any]:
manifest = json.loads(path.read_text(encoding="utf-8"))
if manifest.get("schema_version") != 1:
raise ValueError("fixture manifest schema_version must be 1")
if manifest.get("privacy_scope") != "public_synthetic_only":
raise ValueError("fixture manifest must be public_synthetic_only")
if not isinstance(manifest.get("fixtures"), list) or not manifest["fixtures"]:
raise ValueError("fixture manifest must contain fixtures")
return manifest
def load_ledger(path: Path) -> dict[str, Any]:
ledger = json.loads(path.read_text(encoding="utf-8"))
if ledger.get("schema_version") != 1 or not isinstance(ledger.get("entries"), list):
raise ValueError("sync ledger must contain schema_version=1 and entries array")
return ledger
def validate_ledger_entry(entry: dict[str, Any]) -> list[str]:
return sorted(REQUIRED_LEDGER_FIELDS - entry.keys())
def _calculate_fixture_chart(fixture: dict[str, Any]) -> dict[str, Any]:
birth = fixture["birth"]
effective = fixture["effective"]
chart, _asc_idx, _jd, _ayanamsa = compute_chart_data(
birth["year"], birth["month"], birth["day"], birth["hour"], birth["minute"],
birth["lat"], birth["lon"], birth["tz"], node_mode=effective["node_mode"],
second=birth.get("second", 0), ayanamsa_name=effective["ayanamsa"],
)
return chart
def _longitude(row: dict[str, Any]) -> float:
value = row.get("lon", row.get("degree"))
if not isinstance(value, (int, float)):
raise ValueError("chart row must provide numeric lon or degree")
return float(value)
def compatibility_payload(chart: dict[str, Any], fixture: dict[str, Any]) -> dict[str, Any]:
return {
"fixture_id": fixture["id"],
"birth": {key: value for key, value in fixture["birth"].items() if key != "synthetic"},
"effective": fixture["effective"],
"ascendant": {"sign": chart["ascendant"]["sign"], "lon": _longitude(chart["ascendant"])},
"planets": {
planet: {"sign": chart["planets"][planet]["sign"], "lon": _longitude(chart["planets"][planet])}
for planet in PLANETS
},
}
def compatibility_hash(chart: dict[str, Any], fixture: dict[str, Any]) -> str:
encoded = json.dumps(compatibility_payload(chart, fixture), ensure_ascii=True, sort_keys=True, separators=(",", ":")).encode("utf-8")
return hashlib.sha256(encoded).hexdigest()
def evaluate_manifest(path: Path) -> dict[str, Any]:
manifest = load_manifest(path)
fixtures = []
for fixture in manifest["fixtures"]:
chart = _calculate_fixture_chart(fixture)
actual = compatibility_hash(chart, fixture)
expected = fixture["compatibility_hash"]
fixtures.append({"id": fixture["id"], "expected_compatibility_hash": expected, "actual_compatibility_hash": actual, "matches": actual == expected})
return {"schema_version": 1, "manifest": str(path), "fixtures": fixtures, "matches": all(row["matches"] for row in fixtures)}
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--manifest", type=Path, default=ROOT / "references" / "cross_project_contract" / "fixture_manifest.v1.json")
parser.add_argument("--format", choices=("text", "json"), default="text")
parser.add_argument("--require-match", action="store_true")
args = parser.parse_args(argv)
report = evaluate_manifest(args.manifest)
if args.format == "json":
print(json.dumps(report, ensure_ascii=False, indent=2))
else:
for row in report["fixtures"]:
print(f"{row['id']}: {'match' if row['matches'] else 'mismatch'}")
return 0 if report["matches"] or not args.require_match else 1
if __name__ == "__main__":
raise SystemExit(main())
+63
View File
@@ -0,0 +1,63 @@
"""Public synthetic-fixture contract shared with the backup repository."""
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPTS = ROOT / "scripts"
if str(SCRIPTS) not in sys.path:
sys.path.insert(0, str(SCRIPTS))
import cross_project_contract as contract # noqa: E402
MANIFEST = ROOT / "references" / "cross_project_contract" / "fixture_manifest.v1.json"
LEDGER = ROOT / "references" / "cross_project_contract" / "sync_ledger.json"
def test_public_fixture_manifest_has_complete_effective_settings() -> None:
manifest = contract.load_manifest(MANIFEST)
fixture = manifest["fixtures"][0]
assert manifest["schema_version"] == 1
assert manifest["privacy_scope"] == "public_synthetic_only"
assert fixture["birth"]["synthetic"] is True
assert fixture["effective"] == {"ayanamsa": "lahiri", "node_mode": "mean", "timezone_offset": 5.5}
def test_local_calculation_matches_public_compatibility_hash() -> None:
report = contract.evaluate_manifest(MANIFEST)
assert report["matches"] is True
assert report["fixtures"][0]["matches"] is True
def test_comparator_reports_tampered_expected_hash(tmp_path: Path) -> None:
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
manifest["fixtures"][0]["compatibility_hash"] = "0" * 64
changed = tmp_path / "fixture_manifest.v1.json"
changed.write_text(json.dumps(manifest), encoding="utf-8")
report = contract.evaluate_manifest(changed)
assert report["matches"] is False
assert report["fixtures"][0]["matches"] is False
def test_compatibility_payload_normalizes_engine_degree_field() -> None:
fixture = contract.load_manifest(MANIFEST)["fixtures"][0]
chart = {
"ascendant": {"sign": "Aries", "degree": 1.25},
"planets": {planet: {"sign": "Aries", "degree": float(index)} for index, planet in enumerate(contract.PLANETS)},
}
payload = contract.compatibility_payload(chart, fixture)
assert payload["ascendant"]["lon"] == 1.25
assert payload["planets"]["Sun"]["lon"] == 0.0
def test_sync_ledger_requires_provenance_privacy_tests_hash_and_rollback() -> None:
ledger = contract.load_ledger(LEDGER)
assert ledger == {"schema_version": 1, "entries": []}
missing = contract.validate_ledger_entry({"source_repository": "x"})
assert "target_commit" in missing
assert "privacy_review" in missing
assert "rollback" in missing