Skip to content

Commit 59692c2

Browse files
committed
Verify Dolt diffs and workflow triggers
1 parent 7ab2394 commit 59692c2

4 files changed

Lines changed: 98 additions & 27 deletions

File tree

‎src/openstatspec/sql/catalog_verification.py‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,11 @@ def verify_catalog_relations(
205205
if name
206206
)
207207

208-
from .workflow import PROFILE_ID, PROFILE_SCHEMA_VERSION, workflow_catalog
208+
from .workflow import (
209+
PROFILE_ID, PROFILE_SCHEMA_VERSION, TransformationError,
210+
_assert_trigger_definitions, _derived_trigger_sql,
211+
_validate_workflow_schema, workflow_catalog,
212+
)
209213

210214
workflow = workflow_catalog(MetaData())
211215
workflow_tables = {table.name: table for table in workflow.all()}
@@ -216,6 +220,10 @@ def verify_catalog_relations(
216220
for table in workflow_tables.values()
217221
):
218222
_reject(workflow_present)
223+
try:
224+
_validate_workflow_schema(connection, workflow)
225+
except TransformationError:
226+
_reject(workflow_present)
219227
identity_rows = connection.execute(
220228
select(workflow.transformation_profile_identity)
221229
).mappings().all()
@@ -228,6 +236,7 @@ def verify_catalog_relations(
228236
_reject({workflow.transformation_profile_identity.name})
229237
owned_tables.update(workflow_tables)
230238
for row in connection.execute(select(
239+
workflow.derived_dataset.c.derived_dataset_id,
231240
workflow.derived_dataset.c.physical_relation_name,
232241
workflow.derived_dataset.c.output_mode,
233242
)).mappings():
@@ -236,6 +245,16 @@ def verify_catalog_relations(
236245
owned_views.add(name)
237246
else:
238247
owned_tables.add(name)
248+
try:
249+
_assert_trigger_definitions(
250+
connection,
251+
_derived_trigger_sql(
252+
connection, str(row["derived_dataset_id"]), name,
253+
),
254+
code="derived_corrupt",
255+
)
256+
except TransformationError:
257+
_reject({name})
239258

240259
from .inplace_transform import apply_audit_catalog
241260

‎src/openstatspec/sql/wide.py‎

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -474,23 +474,33 @@ def _dolt_evidence_block(
474474
def _capture_dolt_state(
475475
connection: Any, *, profile_name: str, audit_relations: set[str],
476476
) -> dict[str, Any] | None:
477-
"""Capture the Dolt working-set identity without mutating it."""
478-
del audit_relations
477+
"""Capture and classify the Dolt working set without mutating it."""
478+
audit_relations = {str(name) for name in audit_relations}
479479
if profile_name != "dolt":
480480
return None
481481
identity = connection.exec_driver_sql(
482482
"SELECT DATABASE() AS database_name, ACTIVE_BRANCH() AS active_branch, "
483483
"DOLT_HASHOF('HEAD') AS head_hash"
484484
).mappings().one()
485+
status_rows = connection.exec_driver_sql(
486+
"SELECT table_name, staged, status FROM dolt_status "
487+
"ORDER BY table_name, staged, status"
488+
).mappings().all()
485489
state = {
486490
"database": str(identity["database_name"]).strip(),
487491
"active_branch": str(identity["active_branch"]).strip(),
488492
"head": str(identity["head_hash"]).strip(),
489493
"status": _dolt_evidence_block(
490-
connection.exec_driver_sql(
491-
"SELECT table_name, staged, status FROM dolt_status "
492-
"ORDER BY table_name, staged, status"
493-
).mappings().all(),
494+
status_rows, expected_keys=("table_name", "staged", "status"),
495+
),
496+
"diff_summaries": {},
497+
}
498+
unrelated = {
499+
"status": _dolt_evidence_block(
500+
(
501+
row for row in status_rows
502+
if str(dict(row).get("table_name") or "") not in audit_relations
503+
),
494504
expected_keys=("table_name", "staged", "status"),
495505
),
496506
"diff_summaries": {},
@@ -500,18 +510,33 @@ def _capture_dolt_state(
500510
("head_to_staged", "HEAD", "STAGED"),
501511
("staged_to_working", "STAGED", "WORKING"),
502512
):
513+
rows = connection.exec_driver_sql(
514+
"SELECT from_table_name, to_table_name, diff_type, "
515+
"data_change, schema_change "
516+
f"FROM DOLT_DIFF_SUMMARY('{left}', '{right}') "
517+
"ORDER BY from_table_name, to_table_name, diff_type"
518+
).mappings().all()
519+
expected_keys = (
520+
"from_table_name", "to_table_name", "diff_type",
521+
"data_change", "schema_change",
522+
)
503523
state["diff_summaries"][label] = _dolt_evidence_block(
504-
connection.exec_driver_sql(
505-
"SELECT from_table_name, to_table_name, diff_type, "
506-
"data_change, schema_change "
507-
f"FROM DOLT_DIFF_SUMMARY('{left}', '{right}') "
508-
"ORDER BY from_table_name, to_table_name, diff_type"
509-
).mappings().all(),
510-
expected_keys=(
511-
"from_table_name", "to_table_name", "diff_type",
512-
"data_change", "schema_change",
524+
rows, expected_keys=expected_keys,
525+
)
526+
unrelated["diff_summaries"][label] = _dolt_evidence_block(
527+
(
528+
row for row in rows
529+
if not {
530+
str(value) for value in (
531+
dict(row).get("from_table_name"),
532+
dict(row).get("to_table_name"),
533+
) if value
534+
} <= audit_relations
513535
),
536+
expected_keys=expected_keys,
514537
)
538+
state["unrelated_working_set"] = unrelated
539+
state["unrelated_sha256"] = _canonical_sha256(unrelated)
515540
state["snapshot_sha256"] = _canonical_sha256(state)
516541
return state
517542

@@ -545,9 +570,11 @@ def _require_dolt_success_identity(
545570
return
546571
if before is None or after is None or any(
547572
before[key] != after[key] for key in ("database", "active_branch", "head")
548-
):
573+
) or (
574+
"unrelated_sha256" in before or "unrelated_sha256" in after
575+
) and before.get("unrelated_sha256") != after.get("unrelated_sha256"):
549576
raise UnsupportedOperationError(
550-
f"Dolt database/branch/HEAD changed during {phase}."
577+
f"Dolt identity or unrelated working-set state changed during {phase}."
551578
)
552579

553580

@@ -676,10 +703,7 @@ def initialize_wide_catalog(
676703
)
677704
engine = create_engine(database_url)
678705
normative = normative_catalog(MetaData())
679-
audit_relations = {
680-
normative.fidelity_event.name,
681-
normative.operation.name,
682-
}
706+
audit_relations = {table.name for table in normative.all()}
683707
with _bound_catalog_transaction(
684708
engine=engine, profile_name=profile.name, active=active,
685709
audit_relations=audit_relations, phase="catalog initialization",
@@ -780,10 +804,7 @@ def create_wide_dataset(
780804
)
781805
engine = create_engine(database_url)
782806
normative = normative_catalog(MetaData())
783-
audit_relations = {
784-
normative.fidelity_event.name,
785-
normative.operation.name,
786-
}
807+
audit_relations = {table.name for table in normative.all()}
787808
with _bound_catalog_transaction(
788809
engine=engine, profile_name=profile.name, active=active_connection,
789810
audit_relations=audit_relations, phase="catalog initialization",
@@ -851,7 +872,7 @@ def create_wide_dataset(
851872
namespace_owned = True
852873
with _bound_catalog_transaction(
853874
engine=engine, profile_name=profile.name, active=active_connection,
854-
audit_relations=audit_relations, phase="import",
875+
audit_relations={*audit_relations, data_table.name}, phase="import",
855876
) as connection:
856877
if connection.execute(
857878
select(normative.dataset.c.dataset_id).where(or_(

‎tests/test_review_transaction_boundary.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ def capture(connection, **_kwargs):
5757
assert transaction_states == [False, True]
5858

5959

60+
def test_dolt_completion_rejects_unrelated_working_set_changes():
61+
before = {
62+
"database": "catalog", "active_branch": "main", "head": "abc123",
63+
"unrelated_sha256": "before",
64+
}
65+
after = {**before, "unrelated_sha256": "after"}
66+
67+
import pytest
68+
from openstatspec.core import UnsupportedOperationError
69+
70+
with pytest.raises(UnsupportedOperationError, match="unrelated working-set"):
71+
wide._require_dolt_success_identity(before, after, phase="export audit")
72+
73+
6074
def test_export_lifecycle_events_remain_linked_to_the_dataset(tmp_path):
6175
path = tmp_path / "export-events.sqlite"
6276
database_url = f"sqlite:///{path}"

‎tests/test_strict_catalog_review.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,23 @@ def test_read_and_export_reject_obsolete_catalog_relations(tmp_path):
9595
)
9696

9797

98+
def test_missing_workflow_trigger_is_rejected_by_core_catalog_verification(
99+
tmp_path,
100+
):
101+
path = tmp_path / "workflow-trigger-drift.sqlite"
102+
database = f"sqlite:///{path}"
103+
openstatspec.initialize_catalog(database_url=database)
104+
engine = create_engine(database)
105+
with engine.begin() as connection:
106+
create_workflow_catalog(connection, workflow_catalog(MetaData()))
107+
connection.exec_driver_sql(
108+
"drop trigger oss_transformation_run_no_delete"
109+
)
110+
111+
with pytest.raises(UnsupportedOperationError, match="incompatible"):
112+
openstatspec.initialize_catalog(database_url=database)
113+
114+
98115
def test_verified_workflow_profile_remains_catalog_owned(tmp_path):
99116
path = tmp_path / "workflow-owned.sqlite"
100117
database = f"sqlite:///{path}"

0 commit comments

Comments
 (0)