| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 |
- """Deterministic RuleSpec-to-plan compilation for production-line release."""
- from __future__ import annotations
- import hashlib
- import json
- from typing import Any
- from app.core.common.identifiers import ensure_governance_uid
- from app.core.data_rules.contracts import read_rule_spec, rule_spec_hash
- from app.core.data_rules.expressions import SUPPORTED_BACKENDS, backend_support
- COMPILER_VERSION = "dataops-rulespec-1.0"
- def _canonical_hash(value: Any) -> str:
- canonical = json.dumps(
- value,
- sort_keys=True,
- separators=(",", ":"),
- ensure_ascii=False,
- )
- return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
- def _rule_supported_backends(spec: dict[str, Any]) -> frozenset[str]:
- supported = SUPPORTED_BACKENDS
- for step in spec["steps"]:
- ast = step.get("expression_ast")
- if ast is not None:
- supported = supported & backend_support(ast)
- return frozenset(supported)
- def compile_rule_plan(
- rule_version: dict[str, Any], *, supported_backends: frozenset[str] | None = None
- ) -> dict[str, Any]:
- """Compile a published rule into a target-neutral immutable plan.
- This compiler deliberately emits no SQL or Python. M3 runtime adapters
- translate the closed RuleSpec operators for a deployment's bound backend.
- """
- if not isinstance(rule_version, dict):
- raise ValueError("rule version must be an object")
- if rule_version.get("status") != "published":
- raise ValueError("only published rule versions may be compiled")
- try:
- version_id = ensure_governance_uid(
- {"uid": str(rule_version.get("id"))}
- )
- except ValueError as exc:
- raise ValueError("rule version id must be a valid UUIDv7") from exc
- spec = read_rule_spec(rule_version.get("rule_spec"))
- digest = rule_spec_hash(spec)
- if rule_version.get("spec_hash") != digest:
- raise ValueError("published rule version spec hash does not match")
- derived_backends = _rule_supported_backends(spec)
- if supported_backends is None:
- supported = derived_backends
- elif (
- not isinstance(supported_backends, frozenset)
- or not supported_backends <= SUPPORTED_BACKENDS
- ):
- raise ValueError("supported_backends must be a concrete backend set")
- else:
- supported = supported_backends & derived_backends
- if not supported:
- raise ValueError("rule has no supported execution backend")
- quality_only = all(step["op"] == "assert" for step in spec["steps"])
- if quality_only:
- backend = "quality_check"
- elif "polars" in supported:
- backend = "polars_batch"
- elif supported & {"postgresql", "mysql"}:
- backend = "sql_pushdown"
- else:
- raise ValueError("rule has no supported compiler target")
- plan = {
- "schema_version": "1.0",
- "compiler_version": COMPILER_VERSION,
- "rule_version_id": version_id,
- "rule_spec_hash": digest,
- "input_schema_ref": spec["input_schema_ref"],
- "output_schema_ref": spec["output_schema_ref"],
- "null_policy": spec["null_policy"],
- "timezone": spec["timezone"],
- "supported_execution_backends": sorted(supported),
- "steps": spec["steps"],
- }
- return {
- "backend": backend,
- "compiler_version": COMPILER_VERSION,
- "plan": plan,
- "plan_hash": _canonical_hash(plan),
- }
|