feat(datasources): govern approvals and retention
Module Package Release / publish-packages (push) Successful in 11s

This commit is contained in:
2026-08-22 19:37:44 +02:00
parent b54d1919e4
commit 7a7654cc0f
24 changed files with 2753 additions and 28 deletions
@@ -17,6 +17,7 @@ from govoplan_core.core.dsar import (
)
from govoplan_datasources.backend.db.models import (
DatasourceGovernanceReferenceRecord,
DatasourceLifecycleEvidenceRecord,
DatasourceMaterializationRecord,
DatasourcePayloadRecord,
DatasourcePublicationRecord,
@@ -35,6 +36,7 @@ _DIRECT_ALIASES = {
"payload_id": ("datasources.payload",),
"stage_id": ("datasources.stage",),
"publication_id": ("datasources.publication",),
"lifecycle_evidence_id": ("datasources.lifecycle_evidence",),
}
_RESOURCE_MODELS = {
"datasource": DatasourceRecord,
@@ -43,6 +45,7 @@ _RESOURCE_MODELS = {
"datasource_payload": DatasourcePayloadRecord,
"datasource_stage": DatasourceStageRecord,
"datasource_publication": DatasourcePublicationRecord,
"datasource_lifecycle_evidence": DatasourceLifecycleEvidenceRecord,
}
@@ -284,6 +287,10 @@ def _direct_matches(
DatasourcePublicationRecord,
"datasource_publication",
),
"lifecycle_evidence_id": (
DatasourceLifecycleEvidenceRecord,
"datasource_lifecycle_evidence",
),
}[selector]
row = _one(
session,
@@ -421,6 +428,11 @@ def _canonical_matches(
DatasourcePublicationRecord.created_by.in_(actor_ids),
"datasource_publication",
),
(
DatasourceLifecycleEvidenceRecord,
DatasourceLifecycleEvidenceRecord.actor_ref.in_(actor_ids),
"datasource_lifecycle_evidence",
),
)
matches: list[_Match] = []
for model, condition, resource_type in specs:
@@ -472,6 +484,8 @@ def _direct_category(session: Session, resource_type: str, row: Any) -> str:
)
if not referenced:
return "unreferenced_datasource_payload"
if resource_type == "datasource_lifecycle_evidence":
return "datasource_operator_attribution"
return {
"datasource": "datasource_configuration",
"datasource_governance_reference": "datasource_governance_configuration",
@@ -479,6 +493,7 @@ def _direct_category(session: Session, resource_type: str, row: Any) -> str:
"datasource_payload": "referenced_datasource_payload",
"datasource_stage": "promoted_datasource_stage",
"datasource_publication": "immutable_datasource_publication",
"datasource_lifecycle_evidence": "datasource_operator_attribution",
}[resource_type]
@@ -498,6 +513,22 @@ def _root_datasource_ids(session: Session, match: _Match) -> set[str]:
)
.all()
}
if match.resource_type == "datasource_lifecycle_evidence":
if row.subject_ref.startswith("datasource:"):
return {row.subject_ref.removeprefix("datasource:")}
if row.subject_ref.startswith("stage:"):
stage = session.get(
DatasourceStageRecord,
row.subject_ref.removeprefix("stage:"),
)
return {stage.target_datasource_id} if stage and stage.target_datasource_id else set()
if row.subject_ref.startswith("materialization:"):
materialization = session.get(
DatasourceMaterializationRecord,
row.subject_ref.removeprefix("materialization:"),
)
return {materialization.datasource_id} if materialization else set()
return set()
return {row.datasource_id}
@@ -505,7 +536,7 @@ def _correlates(match: _Match, actor_ids: tuple[str, ...]) -> bool:
row = match.row
return any(
str(getattr(row, field, "") or "") in actor_ids
for field in ("created_by", "updated_by")
for field in ("created_by", "updated_by", "actor_ref")
)
@@ -521,6 +552,7 @@ def _directly_targets(selectors: _Selectors, match: _Match) -> bool:
"datasource_payload": ("payload_id", "id"),
"datasource_stage": ("stage_id", "id"),
"datasource_publication": ("publication_id", "id"),
"datasource_lifecycle_evidence": ("lifecycle_evidence_id", "id"),
}[match.resource_type]
value = _strip_prefix(selectors.direct.get(selector, ""))
if value == str(getattr(row, field)):
@@ -540,6 +572,12 @@ def _root_datasource_ids_for_row(match: _Match) -> set[str]:
return {row.target_datasource_id} if row.target_datasource_id else set()
if match.resource_type == "datasource_payload":
return set()
if match.resource_type == "datasource_lifecycle_evidence":
return (
{row.subject_ref.removeprefix("datasource:")}
if row.subject_ref.startswith("datasource:")
else set()
)
return {row.datasource_id}
@@ -605,6 +643,13 @@ def _record_data(resource_type: str, row: Any) -> dict[str, object]:
"created_at": _iso(row.created_at),
"updated_at": _iso(row.updated_at),
}
if resource_type == "datasource_lifecycle_evidence":
return {
"subject_ref": row.subject_ref,
"event_type": row.event_type,
"policy_version": row.policy_version,
"occurred_at": _iso(row.occurred_at),
}
if resource_type == "datasource_governance_reference":
return {"relation": row.relation}
if resource_type == "datasource_materialization":
@@ -700,7 +745,13 @@ def _title(resource_type: str) -> str:
def _observed_at(row: Any) -> datetime | None:
for field in ("promoted_at", "source_timestamp", "updated_at", "created_at"):
for field in (
"promoted_at",
"source_timestamp",
"occurred_at",
"updated_at",
"created_at",
):
value = getattr(row, field, None)
if isinstance(value, datetime):
return _aware(value)