Enforce quality gates on output publication
This commit is contained in:
@@ -7,6 +7,7 @@ from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from govoplan_core.auth import ApiPrincipal
|
||||
from govoplan_core.core.access import PrincipalRef
|
||||
from govoplan_core.core.change_sequence import ChangeSequenceEntry
|
||||
from govoplan_core.core.datasources import (
|
||||
CAPABILITY_DATASOURCE_ORIGINS,
|
||||
DatasourceAccessError,
|
||||
@@ -154,6 +155,7 @@ class DatasourceLifecycleTests(unittest.TestCase):
|
||||
DatasourceMaterializationRecord.__table__,
|
||||
DatasourceStageRecord.__table__,
|
||||
DatasourcePublicationRecord.__table__,
|
||||
ChangeSequenceEntry.__table__,
|
||||
],
|
||||
)
|
||||
self.Session = sessionmaker(bind=self.engine)
|
||||
@@ -175,6 +177,7 @@ class DatasourceLifecycleTests(unittest.TestCase):
|
||||
DatasourcePayloadRecord.__table__,
|
||||
DatasourceGovernanceReferenceRecord.__table__,
|
||||
DatasourceRecord.__table__,
|
||||
ChangeSequenceEntry.__table__,
|
||||
],
|
||||
)
|
||||
self.engine.dispose()
|
||||
@@ -651,6 +654,106 @@ class DatasourceLifecycleTests(unittest.TestCase):
|
||||
),
|
||||
)
|
||||
|
||||
def test_publication_enforces_quality_before_persisting_any_effect(self) -> None:
|
||||
producer = principal(scopes=(SOURCE_WRITE_SCOPE,))
|
||||
blocked = DatasourcePublicationRequest(
|
||||
producer_module="dataflow",
|
||||
producer_run_ref="dataflow-run:blocked",
|
||||
idempotency_key="blocked-output",
|
||||
name="Blocked output",
|
||||
source_name="blocked_output",
|
||||
rows=({"id": 1},),
|
||||
governance=DatasourceGovernance(
|
||||
quality_policy={
|
||||
"version": "minimum-two-v1",
|
||||
"rules": [
|
||||
{
|
||||
"id": "minimum-two",
|
||||
"type": "row_count",
|
||||
"minimum": 2,
|
||||
}
|
||||
],
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
DatasourceValidationError,
|
||||
r"quality or schema validation \(quality\.row_count\)",
|
||||
):
|
||||
self.provider.publish_rows(self.session, producer, request=blocked)
|
||||
|
||||
self.assertEqual(0, self.session.query(DatasourceRecord).count())
|
||||
self.assertEqual(
|
||||
0,
|
||||
self.session.query(DatasourceMaterializationRecord).count(),
|
||||
)
|
||||
self.assertEqual(
|
||||
0,
|
||||
self.session.query(DatasourcePublicationRecord).count(),
|
||||
)
|
||||
|
||||
def test_publication_preserves_quality_and_schema_evidence(self) -> None:
|
||||
producer = principal(scopes=(SOURCE_WRITE_SCOPE,))
|
||||
first = self.provider.publish_rows(
|
||||
self.session,
|
||||
producer,
|
||||
request=DatasourcePublicationRequest(
|
||||
producer_module="dataflow",
|
||||
producer_run_ref="dataflow-run:first",
|
||||
idempotency_key="governed-output-first",
|
||||
name="Governed output",
|
||||
source_name="governed_output",
|
||||
rows=({"id": 1},),
|
||||
governance=DatasourceGovernance(
|
||||
quality_policy={
|
||||
"version": "governed-output-v1",
|
||||
"rules": [
|
||||
{
|
||||
"id": "unique-id",
|
||||
"type": "unique",
|
||||
"fields": ["id"],
|
||||
},
|
||||
],
|
||||
"schema_policy": {"field_added_required": "warning"},
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
second = self.provider.publish_rows(
|
||||
self.session,
|
||||
producer,
|
||||
request=DatasourcePublicationRequest(
|
||||
producer_module="dataflow",
|
||||
producer_run_ref="dataflow-run:second",
|
||||
idempotency_key="governed-output-second",
|
||||
target_datasource_ref=first.datasource.ref,
|
||||
rows=({"id": 2, "result": "match"},),
|
||||
),
|
||||
)
|
||||
|
||||
validation = second.materialization.provenance["publication_validation"]
|
||||
self.assertTrue(validation["valid"])
|
||||
self.assertEqual("governed-output-v1", validation["policy_version"])
|
||||
self.assertEqual(
|
||||
"warning",
|
||||
validation["schema_change"]["classification"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"schema.field_added_required",
|
||||
validation["warnings"][0]["code"],
|
||||
)
|
||||
record = self.session.get(
|
||||
DatasourcePublicationRecord,
|
||||
second.ref.removeprefix("publication:"),
|
||||
)
|
||||
self.assertIsNotNone(record)
|
||||
assert record is not None
|
||||
self.assertEqual(
|
||||
validation["policy_hash"],
|
||||
record.details_["validation"]["policy_hash"],
|
||||
)
|
||||
|
||||
def test_publication_requires_source_write_scope(self) -> None:
|
||||
with self.assertRaises(DatasourceAccessError):
|
||||
self.provider.publish_rows(
|
||||
|
||||
Reference in New Issue
Block a user