compiler.py 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. """Deterministic RuleSpec-to-plan compilation for production-line release."""
  2. from __future__ import annotations
  3. import hashlib
  4. import json
  5. from typing import Any
  6. from app.core.common.identifiers import ensure_governance_uid
  7. from app.core.data_rules.contracts import rule_spec_hash, validate_rule_spec
  8. COMPILER_VERSION = "dataops-rulespec-1.0"
  9. def _canonical_hash(value: Any) -> str:
  10. canonical = json.dumps(
  11. value,
  12. sort_keys=True,
  13. separators=(",", ":"),
  14. ensure_ascii=False,
  15. )
  16. return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
  17. def compile_rule_plan(rule_version: dict[str, Any]) -> dict[str, Any]:
  18. """Compile a published rule into a target-neutral immutable plan.
  19. This compiler deliberately emits no SQL or Python. M3 runtime adapters
  20. translate the closed RuleSpec operators for a deployment's bound backend.
  21. """
  22. if not isinstance(rule_version, dict):
  23. raise ValueError("rule version must be an object")
  24. if rule_version.get("status") != "published":
  25. raise ValueError("only published rule versions may be compiled")
  26. try:
  27. version_id = ensure_governance_uid(
  28. {"uid": str(rule_version.get("id"))}
  29. )
  30. except ValueError as exc:
  31. raise ValueError("rule version id must be a valid UUIDv7") from exc
  32. spec = validate_rule_spec(rule_version.get("rule_spec"))
  33. digest = rule_spec_hash(spec)
  34. if rule_version.get("spec_hash") != digest:
  35. raise ValueError("published rule version spec hash does not match")
  36. quality_only = all(step["op"] == "assert" for step in spec["steps"])
  37. backend = "quality_check" if quality_only else "polars_batch"
  38. plan = {
  39. "schema_version": "1.0",
  40. "compiler_version": COMPILER_VERSION,
  41. "rule_version_id": version_id,
  42. "rule_spec_hash": digest,
  43. "input_schema_ref": spec["input_schema_ref"],
  44. "output_schema_ref": spec["output_schema_ref"],
  45. "null_policy": spec["null_policy"],
  46. "timezone": spec["timezone"],
  47. "steps": spec["steps"],
  48. }
  49. return {
  50. "backend": backend,
  51. "compiler_version": COMPILER_VERSION,
  52. "plan": plan,
  53. "plan_hash": _canonical_hash(plan),
  54. }