Complete governed reporting execution and publication

This commit is contained in:
2026-08-04 02:23:24 +02:00
parent a81151391b
commit 8337aec19a
29 changed files with 3611 additions and 81 deletions
+278 -17
View File
@@ -37,6 +37,12 @@ from govoplan_reporting.backend.db.models import (
ReportingQualityResult,
)
from govoplan_reporting.backend.definitions import get_definition, list_definitions
from govoplan_reporting.backend.domain import ReportingDefinitionRecord
from govoplan_reporting.backend.governance import require_definition_action
from govoplan_reporting.backend.postgres_planner import (
POSTGRES_PLANNER_VERSION,
execute_postgres_query,
)
from govoplan_reporting.backend.query_engine import (
QUERY_ENGINE_VERSION,
DefaultChartRenderer,
@@ -99,7 +105,12 @@ class SqlReportingRunner:
*,
execution_id: str,
) -> Mapping[str, object] | None:
return get_execution(_session(session), principal, execution_id=execution_id)
return get_execution(
_session(session),
principal,
execution_id=execution_id,
registry=self.registry,
)
def execute_report(
@@ -126,6 +137,13 @@ def execute_report(
if report_record.status != "active":
raise ReportingExecutionError("Only active report definitions can run.")
report = ReportDefinition.model_validate(report_record.payload)
report_decision = require_definition_action(
session,
principal,
registry=registry,
record=report_record,
action="run",
)
semantic_record = get_definition(
session,
principal,
@@ -138,6 +156,13 @@ def execute_report(
"The report's pinned semantic model is unavailable or inactive."
)
semantic = SemanticModelDefinition.model_validate(semantic_record.payload)
semantic_decision = require_definition_action(
session,
principal,
registry=registry,
record=semantic_record,
action="view",
)
dataset_record = get_definition(
session,
principal,
@@ -150,8 +175,15 @@ def execute_report(
"The report's pinned analytical dataset is unavailable or inactive."
)
dataset = DatasetDefinition.model_validate(dataset_record.payload)
dataset_decision = require_definition_action(
session,
principal,
registry=registry,
record=dataset_record,
action="view",
)
bound_parameters = _bind_parameters(report, parameters)
effective_query = query or report.default_query
effective_query = _enforce_query_access(report, query or report.default_query)
clean_idempotency_key = _required(
idempotency_key,
"Reporting execution idempotency key",
@@ -176,7 +208,19 @@ def execute_report(
request_sha256=request_sha256,
)
if replay is not None:
return _execution_payload(replay, report=report, registry=registry)
delivery = _authorize_execution_delivery(
session,
principal,
registry=registry,
row=replay,
report_record=report_record,
)
return _execution_payload(
replay,
report=report,
registry=registry,
delivery_authorization=delivery,
)
started_at = utc_now()
execution = ReportingExecution(
tenant_id=_tenant(principal),
@@ -232,7 +276,14 @@ def execute_report(
output_hash=source.output_hash,
source_fingerprints=source.source_fingerprints,
)
result = execute_semantic_query(authorized_rows, semantic, effective_query)
result = execute_postgres_query(
session,
rows=authorized_rows,
dataset=dataset,
semantic_model=semantic,
query=effective_query,
) or execute_semantic_query(authorized_rows, semantic, effective_query)
diagnostics.extend(result.diagnostics)
output_hash = _sha256(
{
"rows": result.rows,
@@ -244,7 +295,12 @@ def execute_report(
execution.status = "succeeded"
execution.source_fingerprints = _json_value(source.source_fingerprints)
execution.output_hash = output_hash
execution.executor_version = f"{QUERY_ENGINE_VERSION}+{source.executor_version}"
planner_version = (
POSTGRES_PLANNER_VERSION
if any(item.get("code") == "postgresql_semantic_plan" for item in result.diagnostics)
else QUERY_ENGINE_VERSION
)
execution.executor_version = f"{planner_version}+{source.executor_version}"
execution.result_schema = list(result.schema)
execution.result_rows = list(result.rows)
execution.total_rows = result.total_rows
@@ -259,11 +315,35 @@ def execute_report(
"report_content_hash": report_record.content_hash,
"semantic_model_content_hash": semantic_record.content_hash,
"dataset_content_hash": dataset_record.content_hash,
"definition_governance": {
"report": report_decision.to_dict(),
"semantic_model": semantic_decision.to_dict(),
"dataset": dataset_decision.to_dict(),
},
"access_explanation": _access_explanation(
report,
effective_query,
source_rows=len(normalized_rows),
authorized_rows=len(authorized_rows),
row_policy=policy_provenance,
),
}
execution.finished_at = utc_now()
session.flush()
_emit_execution_event(session, execution, report_record.name)
return _execution_payload(execution, report=report, registry=registry)
delivery = _authorize_execution_delivery(
session,
principal,
registry=registry,
row=execution,
report_record=report_record,
)
return _execution_payload(
execution,
report=report,
registry=registry,
delivery_authorization=delivery,
)
except Exception as exc:
execution.status = "failed"
execution.finished_at = utc_now()
@@ -284,6 +364,7 @@ def get_execution(
principal: object,
*,
execution_id: str,
registry: object | None = None,
) -> dict[str, object] | None:
row = (
session.query(ReportingExecution)
@@ -304,10 +385,18 @@ def get_execution(
)
if report_record is None:
return None
delivery = _authorize_execution_delivery(
session,
principal,
registry=registry,
row=row,
report_record=report_record,
)
return _execution_payload(
row,
report=ReportDefinition.model_validate(report_record.payload),
registry=None,
registry=registry,
delivery_authorization=delivery,
)
@@ -317,16 +406,15 @@ def list_executions(
*,
report_id: str,
limit: int = 100,
registry: object | None = None,
) -> tuple[dict[str, object], ...]:
if (
get_definition(
session,
principal,
definition_kind="report",
definition_id=report_id,
)
is None
):
current_report = get_definition(
session,
principal,
definition_kind="report",
definition_id=report_id,
)
if current_report is None:
return ()
rows = (
session.query(ReportingExecution)
@@ -338,7 +426,46 @@ def list_executions(
.limit(max(1, min(limit, 200)))
.all()
)
return tuple(_execution_payload(row, report=None, registry=None) for row in rows)
authorization_cache: dict[tuple[int, int, int], dict[str, object]] = {}
payloads: list[dict[str, object]] = []
for row in rows:
key = (
row.report_revision,
row.semantic_model_revision,
row.dataset_revision,
)
report_record = (
current_report
if current_report.revision == row.report_revision
else get_definition(
session,
principal,
definition_kind="report",
definition_id=row.report_id,
revision=row.report_revision,
)
)
if report_record is None:
continue
delivery = authorization_cache.get(key)
if delivery is None:
delivery = _authorize_execution_delivery(
session,
principal,
registry=registry,
row=row,
report_record=report_record,
)
authorization_cache[key] = delivery
payloads.append(
_execution_payload(
row,
report=ReportDefinition.model_validate(report_record.payload),
registry=registry,
delivery_authorization=delivery,
)
)
return tuple(payloads)
def _read_dataset(
@@ -717,11 +844,144 @@ def _evaluate_assertion(
}
def _enforce_query_access(
report: ReportDefinition,
query: ReportQuery,
) -> ReportQuery:
policy = report.access_policy
hidden_dimensions = _policy_strings(policy, "hidden_dimensions")
hidden_measures = _policy_strings(policy, "hidden_measures")
requested_dimensions = set(query.dimensions)
requested_dimensions.update(item.dimension for item in query.filters)
if query.pivot is not None:
requested_dimensions.update(query.pivot.rows)
requested_dimensions.update(query.pivot.columns)
requested_measures = set(query.measures)
if query.pivot is not None:
requested_measures.update(query.pivot.measures)
blocked = (requested_dimensions & hidden_dimensions) | (
requested_measures & hidden_measures
)
if blocked:
raise PermissionError(
"Policy hides requested Reporting fields: "
+ ", ".join(sorted(blocked))
)
if "run" in _policy_strings(policy, "disabled_actions"):
raise PermissionError(_policy_reason(policy, "run"))
return query
def _access_explanation(
report: ReportDefinition,
query: ReportQuery,
*,
source_rows: int,
authorized_rows: int,
row_policy: Mapping[str, object],
) -> dict[str, object]:
policy = report.access_policy
hidden_dimensions = sorted(_policy_strings(policy, "hidden_dimensions"))
hidden_measures = sorted(_policy_strings(policy, "hidden_measures"))
disabled_actions = sorted(_policy_strings(policy, "disabled_actions"))
reasons = policy.get("reasons")
return {
"hidden_dimensions": hidden_dimensions,
"hidden_measures": hidden_measures,
"hidden_rows": max(0, source_rows - authorized_rows),
"disabled_actions": disabled_actions,
"reasons": dict(reasons) if isinstance(reasons, Mapping) else {},
"row_policy": dict(row_policy),
"effective_query": query.model_dump(mode="json"),
}
def _authorize_execution_delivery(
session: Session,
principal: object,
*,
registry: object | None,
row: ReportingExecution,
report_record: ReportingDefinitionRecord,
) -> dict[str, object]:
report_decision = require_definition_action(
session,
principal,
registry=registry,
record=report_record,
action="view",
)
semantic_record = get_definition(
session,
principal,
definition_kind="semantic_model",
definition_id=row.semantic_model_id,
revision=row.semantic_model_revision,
)
dataset_record = get_definition(
session,
principal,
definition_kind="dataset",
definition_id=row.dataset_id,
revision=row.dataset_revision,
)
if semantic_record is None or dataset_record is None:
raise PermissionError(
"The source definitions for this report result are no longer accessible."
)
semantic_decision = require_definition_action(
session,
principal,
registry=registry,
record=semantic_record,
action="view",
)
dataset_decision = require_definition_action(
session,
principal,
registry=registry,
record=dataset_record,
action="view",
)
dataset = DatasetDefinition.model_validate(dataset_record.payload)
_empty, row_policy = _apply_row_policy(
session,
principal,
registry=registry,
dataset_id=dataset_record.definition_id,
dataset_revision=dataset_record.revision,
dataset=dataset,
rows=(),
)
return {
"checked": True,
"report": report_decision.to_dict(),
"semantic_model": semantic_decision.to_dict(),
"dataset": dataset_decision.to_dict(),
"row_policy": dict(row_policy),
}
def _policy_strings(policy: Mapping[str, object], key: str) -> set[str]:
raw = policy.get(key, ())
if not isinstance(raw, (list, tuple, set, frozenset)):
return set()
return {str(item) for item in raw if str(item).strip()}
def _policy_reason(policy: Mapping[str, object], action: str) -> str:
reasons = policy.get("reasons")
if isinstance(reasons, Mapping) and str(reasons.get(action) or "").strip():
return str(reasons[action])
return f"Policy disables the Reporting {action} action."
def _execution_payload(
row: ReportingExecution,
*,
report: ReportDefinition | None,
registry: object | None,
delivery_authorization: Mapping[str, object] | None = None,
) -> dict[str, object]:
payload: dict[str, object] = {
"execution_id": row.execution_id,
@@ -747,6 +1007,7 @@ def _execution_payload(
"started_at": _datetime_text(row.started_at),
"finished_at": _datetime_text(row.finished_at),
"actor_id": row.actor_id,
"delivery_authorization": dict(delivery_authorization or {}),
}
if row.status == "succeeded" and report is not None:
result = QueryResult(