test: verify pinned dataflow publication composition
This commit is contained in:
@@ -23,6 +23,8 @@ The initial provider path is:
|
|||||||
4. Any datasource may expose a frozen state for reproducible execution evidence.
|
4. Any datasource may expose a frozen state for reproducible execution evidence.
|
||||||
5. Dataflow stores an opaque datasource reference, state policy, and expected
|
5. Dataflow stores an opaque datasource reference, state policy, and expected
|
||||||
fingerprint.
|
fingerprint.
|
||||||
|
6. A pinned Dataflow run may publish a complete bounded result as a new
|
||||||
|
immutable materialization through an idempotent Datasources capability.
|
||||||
|
|
||||||
Database, REST/HTTP, LDAP/directory, managed file, watched-directory, feed, and
|
Database, REST/HTTP, LDAP/directory, managed file, watched-directory, feed, and
|
||||||
stream providers fit behind the same origin contract. Provider-specific
|
stream providers fit behind the same origin contract. Provider-specific
|
||||||
@@ -51,21 +53,30 @@ kind of Dataflow or leaking either module into Core.
|
|||||||
## Current Implementation
|
## Current Implementation
|
||||||
|
|
||||||
- Core graph and datasource contracts are versioned at `0.1.0`.
|
- Core graph and datasource contracts are versioned at `0.1.0`.
|
||||||
- Workflow exposes node-library discovery and definition validation APIs.
|
- Workflow exposes a reusable graph editor, node-library discovery, validation,
|
||||||
|
tenant-isolated definitions, immutable revisions, and activation pinning.
|
||||||
- Datasources exposes catalogue, origins, staging, promotion, preview,
|
- Datasources exposes catalogue, origins, staging, promotion, preview,
|
||||||
materialization history, refresh, freeze, and retirement APIs.
|
materialization history, refresh, freeze, retirement, and producer
|
||||||
|
publication APIs.
|
||||||
- Datasources WebUI exposes all current lifecycle views.
|
- Datasources WebUI exposes all current lifecycle views.
|
||||||
- Connectors adapts existing tabular snapshots to datasource origins.
|
- Connectors adapts existing tabular snapshots to datasource origins.
|
||||||
- Dataflow consumes only Datasources catalogue/lifecycle capabilities and can
|
- Dataflow consumes only Datasources catalogue/lifecycle capabilities and can
|
||||||
request current, live, or latest-frozen state.
|
request current, live, or latest-frozen state.
|
||||||
|
- Dataflow exposes a pinned run-lifecycle capability and a Run/Publish surface.
|
||||||
|
Its first synchronous runner records lineage and terminal state, publishes
|
||||||
|
only complete bounded results, and retains output datasource/materialization
|
||||||
|
references.
|
||||||
|
- The focused composition check proves Connector origin -> Datasource ->
|
||||||
|
pinned Dataflow run -> frozen published materialization, including replay.
|
||||||
|
|
||||||
## Next Slices
|
## Next Slices
|
||||||
|
|
||||||
1. Persist and version Workflow definitions, then reuse the graph UI shell.
|
1. Add persisted Workflow instances, resumable transitions, human activities,
|
||||||
|
retry policy, and event subscriptions against pinned definition revisions.
|
||||||
2. Add SQL database and governed REST origin providers with credential-envelope
|
2. Add SQL database and governed REST origin providers with credential-envelope
|
||||||
references and bounded pushdown.
|
references and bounded pushdown.
|
||||||
3. Add managed-file and directory origins.
|
3. Add managed-file and directory origins.
|
||||||
4. Add datasource quality rules, schema compatibility policy, retention, and
|
4. Add datasource quality rules, schema compatibility policy, retention, and
|
||||||
promotion approvals.
|
promotion approvals.
|
||||||
5. Publish Dataflow outputs as new materializations and expose run lifecycle to
|
5. Add asynchronous Dataflow workers and durable artifact-backed outputs for
|
||||||
Workflow.
|
runs that exceed the synchronous row/time/byte limits.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Exercise the connector -> datasource -> dataflow capability path."""
|
"""Exercise connector -> datasource -> dataflow publication capabilities."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -9,7 +9,17 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
from govoplan_connectors.backend.db.models import ConnectorTabularSource
|
from govoplan_connectors.backend.db.models import ConnectorTabularSource
|
||||||
from govoplan_core.auth import ApiPrincipal
|
from govoplan_core.auth import ApiPrincipal
|
||||||
from govoplan_core.core.access import PrincipalRef
|
from govoplan_core.core.access import PrincipalRef
|
||||||
from govoplan_core.core.datasources import datasource_catalogue, datasource_lifecycle
|
from govoplan_core.core.dataflows import (
|
||||||
|
DataflowPublicationTarget,
|
||||||
|
DataflowRunRequest,
|
||||||
|
dataflow_run_lifecycle,
|
||||||
|
)
|
||||||
|
from govoplan_core.core.datasources import (
|
||||||
|
DatasourceReadRequest,
|
||||||
|
datasource_catalogue,
|
||||||
|
datasource_lifecycle,
|
||||||
|
datasource_publication,
|
||||||
|
)
|
||||||
from govoplan_core.core.modules import ModuleContext
|
from govoplan_core.core.modules import ModuleContext
|
||||||
from govoplan_core.core.tabular_sources import (
|
from govoplan_core.core.tabular_sources import (
|
||||||
TabularSnapshotInput,
|
TabularSnapshotInput,
|
||||||
@@ -22,11 +32,18 @@ from govoplan_dataflow.backend.schemas import (
|
|||||||
GraphNode,
|
GraphNode,
|
||||||
GraphPosition,
|
GraphPosition,
|
||||||
PipelineGraph,
|
PipelineGraph,
|
||||||
|
PipelineCreateRequest,
|
||||||
PipelinePreviewRequest,
|
PipelinePreviewRequest,
|
||||||
)
|
)
|
||||||
from govoplan_dataflow.backend.service import preview_pipeline
|
from govoplan_dataflow.backend.db.models import (
|
||||||
|
DataflowPipeline,
|
||||||
|
DataflowPipelineRevision,
|
||||||
|
DataflowRun,
|
||||||
|
)
|
||||||
|
from govoplan_dataflow.backend.service import create_pipeline, preview_pipeline
|
||||||
from govoplan_datasources.backend.db.models import (
|
from govoplan_datasources.backend.db.models import (
|
||||||
DatasourceMaterializationRecord,
|
DatasourceMaterializationRecord,
|
||||||
|
DatasourcePublicationRecord,
|
||||||
DatasourceRecord,
|
DatasourceRecord,
|
||||||
DatasourceStageRecord,
|
DatasourceStageRecord,
|
||||||
)
|
)
|
||||||
@@ -47,6 +64,10 @@ def main() -> int:
|
|||||||
DatasourceRecord.__table__,
|
DatasourceRecord.__table__,
|
||||||
DatasourceMaterializationRecord.__table__,
|
DatasourceMaterializationRecord.__table__,
|
||||||
DatasourceStageRecord.__table__,
|
DatasourceStageRecord.__table__,
|
||||||
|
DatasourcePublicationRecord.__table__,
|
||||||
|
DataflowPipeline.__table__,
|
||||||
|
DataflowPipelineRevision.__table__,
|
||||||
|
DataflowRun.__table__,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
session_factory = sessionmaker(bind=engine)
|
session_factory = sessionmaker(bind=engine)
|
||||||
@@ -55,7 +76,15 @@ def main() -> int:
|
|||||||
writer = tabular_snapshot_writer(registry)
|
writer = tabular_snapshot_writer(registry)
|
||||||
lifecycle = datasource_lifecycle(registry)
|
lifecycle = datasource_lifecycle(registry)
|
||||||
catalogue = datasource_catalogue(registry)
|
catalogue = datasource_catalogue(registry)
|
||||||
if writer is None or lifecycle is None or catalogue is None:
|
publisher = datasource_publication(registry)
|
||||||
|
runner = dataflow_run_lifecycle(registry)
|
||||||
|
if (
|
||||||
|
writer is None
|
||||||
|
or lifecycle is None
|
||||||
|
or catalogue is None
|
||||||
|
or publisher is None
|
||||||
|
or runner is None
|
||||||
|
):
|
||||||
raise RuntimeError("Datasource composition capabilities are incomplete.")
|
raise RuntimeError("Datasource composition capabilities are incomplete.")
|
||||||
|
|
||||||
origin = writer.create_snapshot(
|
origin = writer.create_snapshot(
|
||||||
@@ -102,8 +131,73 @@ def main() -> int:
|
|||||||
raise RuntimeError(f"Unexpected Dataflow rows: {result.rows!r}")
|
raise RuntimeError(f"Unexpected Dataflow rows: {result.rows!r}")
|
||||||
if result.source_fingerprints[0]["source_ref"] != datasource.ref:
|
if result.source_fingerprints[0]["source_ref"] != datasource.ref:
|
||||||
raise RuntimeError("Dataflow lineage did not retain the datasource reference.")
|
raise RuntimeError("Dataflow lineage did not retain the datasource reference.")
|
||||||
|
pipeline = create_pipeline(
|
||||||
|
session,
|
||||||
|
tenant_id="tenant-1",
|
||||||
|
actor_id="account-1",
|
||||||
|
payload=PipelineCreateRequest(
|
||||||
|
name="Monthly case output",
|
||||||
|
status="active",
|
||||||
|
graph=_graph(
|
||||||
|
datasource_ref=datasource.ref,
|
||||||
|
fingerprint=datasource.fingerprint,
|
||||||
|
),
|
||||||
|
editor_mode="graph",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
run_request = DataflowRunRequest(
|
||||||
|
pipeline_ref=f"pipeline:{pipeline.id}",
|
||||||
|
revision=1,
|
||||||
|
idempotency_key="composition-run-1",
|
||||||
|
publication=DataflowPublicationTarget(
|
||||||
|
name="Monthly case result",
|
||||||
|
source_name="monthly_case_result",
|
||||||
|
freeze=True,
|
||||||
|
frozen_label="Composition evidence",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
published = runner.start_run(
|
||||||
|
session,
|
||||||
|
principal,
|
||||||
|
request=run_request,
|
||||||
|
)
|
||||||
|
replayed = runner.start_run(
|
||||||
|
session,
|
||||||
|
principal,
|
||||||
|
request=run_request,
|
||||||
|
)
|
||||||
|
if published.status != "succeeded":
|
||||||
|
raise RuntimeError(f"Dataflow publication failed: {published.error}")
|
||||||
|
if replayed.ref != published.ref or not replayed.replayed:
|
||||||
|
raise RuntimeError("Dataflow run idempotency did not replay the prior run.")
|
||||||
|
if (
|
||||||
|
not published.output_datasource_ref
|
||||||
|
or not published.output_materialization_ref
|
||||||
|
):
|
||||||
|
raise RuntimeError("Dataflow publication did not retain output references.")
|
||||||
|
output = catalogue.read_datasource(
|
||||||
|
session,
|
||||||
|
principal,
|
||||||
|
request=DatasourceReadRequest(
|
||||||
|
datasource_ref=published.output_datasource_ref,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if list(output.rows) != expected_rows:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Unexpected published Dataflow rows: {list(output.rows)!r}"
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
output.materialization is None
|
||||||
|
or output.materialization.ref != published.output_materialization_ref
|
||||||
|
or output.materialization.frozen_at is None
|
||||||
|
):
|
||||||
|
raise RuntimeError(
|
||||||
|
"Published Datasource materialization is not pinned and frozen."
|
||||||
|
)
|
||||||
engine.dispose()
|
engine.dispose()
|
||||||
print("Connector -> Datasources -> Dataflow composition passed.")
|
print(
|
||||||
|
"Connector -> Datasources -> pinned Dataflow publication composition passed."
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ cd /mnt/DATA/git/govoplan-dataflow/webui
|
|||||||
cd /mnt/DATA/git/govoplan-datasources/webui
|
cd /mnt/DATA/git/govoplan-datasources/webui
|
||||||
"$NPM" run typecheck
|
"$NPM" run typecheck
|
||||||
|
|
||||||
|
cd /mnt/DATA/git/govoplan-workflow/webui
|
||||||
|
"$NPM" run typecheck
|
||||||
|
|
||||||
cd /mnt/DATA/git/govoplan-mail/webui
|
cd /mnt/DATA/git/govoplan-mail/webui
|
||||||
"$NPM" run test:mail-ui
|
"$NPM" run test:mail-ui
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user