fix(e2e): resume published chart response exactly
This commit is contained in:
8
Makefile
8
Makefile
@@ -71,7 +71,13 @@ e2e-run: ## Run chart-8 E2E for an explicit lab target (requires a mode-0600 LAB
|
||||
@test "$(TARGET)" = "lab" || { echo "TARGET=lab is required"; exit 2; }
|
||||
@test -n "$(LAB_PROFILE)" -a -f "$(LAB_PROFILE)" || { echo "LAB_PROFILE must name a mode-0600 explicit target/credentials-path profile"; exit 2; }
|
||||
@test -x "$(E2E_PYTHON)" || { echo "E2E_PYTHON must name an executable Python with backend dependencies (default: $(BACKEND)/.venv/bin/python)"; exit 2; }
|
||||
@cd $(BACKEND) && "$(E2E_PYTHON)" ../scripts/e2e-scripts/native_chart8_prepare.py --profile "$(LAB_PROFILE)" $(if $(filter 1,$(E2E_VALIDATE_ONLY)),--validate-only,--run-lifecycle)
|
||||
@case "$(E2E_MODE)" in \
|
||||
resume-published) mode=--resume-published ;; \
|
||||
fresh|'') mode=--run-lifecycle ;; \
|
||||
*) echo "E2E_MODE must be fresh or resume-published"; exit 2 ;; \
|
||||
esac; \
|
||||
if [ "$(E2E_VALIDATE_ONLY)" = "1" ]; then mode=--validate-only; fi; \
|
||||
cd $(BACKEND) && "$(E2E_PYTHON)" ../scripts/e2e-scripts/native_chart8_prepare.py --profile "$(LAB_PROFILE)" "$$mode"
|
||||
|
||||
lab-reset: ## Prepare/archive or explicitly apply the exact approved chart-8 row reset (PHASE=prepare|apply|verify)
|
||||
@test -n "$(SCOPE)" || { echo "SCOPE must name the mode-0600 exact lab reset scope JSON"; exit 2; }
|
||||
|
||||
@@ -161,6 +161,7 @@ def validate_target_descriptor(target: dict, seed: dict, release: dict) -> None:
|
||||
raise Blocked("NATIVE_CAPTURE_SCENARIO_CASES_REQUIRED")
|
||||
elif capture_scenario.get("mode") == "existing_mcp_revision":
|
||||
validate_existing_mcp_revision(capture_scenario)
|
||||
validate_published_resume_target(target)
|
||||
else:
|
||||
raise Blocked("NATIVE_CAPTURE_SCENARIO_MODE_UNSUPPORTED")
|
||||
numeric = target.get("numeric_verification")
|
||||
@@ -286,6 +287,20 @@ def validate_existing_mcp_revision(selection: dict) -> None:
|
||||
raise Blocked("NATIVE_CAPTURE_SCENARIO_REVISION_REQUIRED")
|
||||
|
||||
|
||||
def validate_published_resume_target(target: dict) -> None:
|
||||
resume = target.get("published_resume") if isinstance(target, dict) else None
|
||||
selection = target.get("catalog_selection") if isinstance(target, dict) else None
|
||||
if (target.get("catalog_mode") != "exact_revision"
|
||||
or not isinstance(selection, dict) or selection.get("kind") != "revision"
|
||||
or not _is_uuid(selection.get("catalog_id"))
|
||||
or not _is_uuid(selection.get("catalog_revision_id"))
|
||||
or not isinstance(resume, dict)
|
||||
or set(resume) != {"capture_artifact_id", "publication_operation_id"}
|
||||
or not _is_uuid(resume.get("capture_artifact_id"))
|
||||
or not _is_uuid(resume.get("publication_operation_id"))):
|
||||
raise Blocked("NATIVE_PUBLISHED_RESUME_INPUT_INVALID")
|
||||
|
||||
|
||||
def _is_uuid(value: object) -> bool:
|
||||
if type(value) is not str:
|
||||
return False
|
||||
@@ -489,8 +504,6 @@ def register_mcp_owned_scenario(driver: Driver, dashboard_id: int, model: dict,
|
||||
def select_m01_profile_coordinate(profile: dict, target: dict, dashboard_id: int, model: dict) -> dict:
|
||||
if not isinstance(profile, dict) or not isinstance(profile.get("coordinates"), list):
|
||||
raise Blocked("NATIVE_M01_PROFILE_COORDINATES_UNAVAILABLE")
|
||||
selection = target["capture_scenario"]
|
||||
metric_name = selection["metric_name"]
|
||||
charts = [chart for chart in model.get("charts", [])
|
||||
if chart.get("chart_uuid") == target["chart_uuid"]]
|
||||
if len(charts) != 1:
|
||||
@@ -501,15 +514,103 @@ def select_m01_profile_coordinate(profile: dict, target: dict, dashboard_id: int
|
||||
if isinstance(item, dict)
|
||||
and item.get("chart_id") == expected_chart_id
|
||||
and item.get("dataset_id") == expected_dataset_id
|
||||
and item.get("metric_name") == metric_name
|
||||
and item.get("metric_name") is None
|
||||
and item.get("reference_coordinate") == {
|
||||
"kind": "metric", "chart_uuid": target["chart_uuid"], "metric": metric_name,
|
||||
"kind": "chart", "chart_uuid": target["chart_uuid"], "metric": None,
|
||||
}]
|
||||
if type(expected_chart_id) is not int or len(coordinates) != 1:
|
||||
raise Blocked("NATIVE_M01_PROFILE_COORDINATE_AMBIGUOUS")
|
||||
return coordinates[0]
|
||||
|
||||
|
||||
def read_published_capture(driver: Driver, target: dict, model: dict) -> dict:
|
||||
"""Resume M01 only from an explicitly named, publicly read-back publication."""
|
||||
resume = target.get("published_resume")
|
||||
selection = target.get("catalog_selection")
|
||||
expected_capture = resume.get("capture_artifact_id") if isinstance(resume, dict) else None
|
||||
operation_id = resume.get("publication_operation_id") if isinstance(resume, dict) else None
|
||||
if (not isinstance(resume, dict) or set(resume) != {"capture_artifact_id", "publication_operation_id"}
|
||||
or not _is_uuid(expected_capture) or not _is_uuid(operation_id)
|
||||
or target.get("catalog_mode") != "exact_revision"
|
||||
or not isinstance(selection, dict) or selection.get("kind") != "revision"):
|
||||
raise Blocked("NATIVE_PUBLISHED_RESUME_INPUT_INVALID")
|
||||
|
||||
operation = driver.call("published-resume-operation-readback", "GET",
|
||||
f"/api/catalog-publications/{operation_id}")
|
||||
revision_response = driver.call("published-resume-revision-readback", "GET",
|
||||
"/api/dashboard-testing/baselines?" + urlencode({
|
||||
"catalog_id": selection["catalog_id"],
|
||||
"catalog_revision_id": selection["catalog_revision_id"],
|
||||
}))
|
||||
revision = revision_response.get("revision", {})
|
||||
if (operation.get("operation_id") != operation_id or operation.get("state") != "published"
|
||||
or operation.get("catalog_revision_id") != selection["catalog_revision_id"]
|
||||
or operation.get("catalog_digest") != revision.get("catalog_digest")
|
||||
or not operation.get("published_receipt")
|
||||
or operation.get("repository") != target["catalog_repository"]
|
||||
or operation.get("branch") != target["catalog_branch"]
|
||||
or revision.get("catalog_id") != selection["catalog_id"]
|
||||
or revision.get("catalog_revision_id") != selection["catalog_revision_id"]
|
||||
or revision.get("release_id") != target["release_identity"].get("id")):
|
||||
raise Blocked("NATIVE_PUBLISHED_RESUME_IDENTITY_MISMATCH")
|
||||
|
||||
wrappers = revision.get("entry_revisions")
|
||||
matches = [wrapper for wrapper in wrappers if isinstance(wrapper, dict)
|
||||
and wrapper.get("capture_artifact_id") == expected_capture
|
||||
and isinstance(wrapper.get("entry"), dict)]
|
||||
if len(matches) != 1:
|
||||
raise Blocked("NATIVE_PUBLISHED_RESUME_CAPTURE_MISSING")
|
||||
wrapper = matches[0]
|
||||
entry = wrapper["entry"]
|
||||
expected = entry.get("expected") or {}
|
||||
source = wrapper.get("reference_source") or {}
|
||||
native = entry.get("native_result") or {}
|
||||
authority = native.get("authority_record") or {}
|
||||
descriptor = authority.get("descriptor") or {}
|
||||
reference = descriptor.get("reference_coordinate") or {}
|
||||
chart = next((item for item in model.get("charts", [])
|
||||
if item.get("chart_uuid") == target["chart_uuid"]), None)
|
||||
if (wrapper.get("status") != "approved" or entry.get("status") != "approved"
|
||||
or expected.get("kind") != "chart_response"
|
||||
or entry.get("result_key", "missing") is not None
|
||||
or entry.get("chart_id") != descriptor.get("chart_id")
|
||||
or entry.get("dataset_id") != descriptor.get("dataset_id")
|
||||
or not isinstance(chart, dict)
|
||||
or descriptor.get("chart_id") != chart.get("chart_id")
|
||||
or descriptor.get("dataset_id") != chart.get("dataset_id")
|
||||
or descriptor.get("kind") != "chart_response"
|
||||
or authority.get("capture_artifact_id") != expected_capture
|
||||
or descriptor.get("selected_metric", "missing") is not None
|
||||
or reference != {"kind": "chart", "chart_uuid": target["chart_uuid"], "metric": None}
|
||||
or source.get("environment_id") != target["target_environment_id"]
|
||||
or source.get("dashboard_id") != target["resolved_target_dashboard_id"]
|
||||
or source.get("filters_hash") != target["expected_filters_hash"]
|
||||
or source.get("query_model_fingerprint") != model.get("query_model_fingerprint")
|
||||
or entry.get("release_version") != target["release_identity"].get("version")
|
||||
or entry.get("release_commit_hash") != target["release_identity"].get("commit_hash")):
|
||||
raise Blocked("NATIVE_PUBLISHED_RESUME_CAPTURE_IDENTITY_MISMATCH")
|
||||
|
||||
driver.ids.update(capture_artifact_id=expected_capture,
|
||||
published_catalog_revision_id=selection["catalog_revision_id"],
|
||||
publication_operation_id=operation_id)
|
||||
driver.record("published-capture-exact-resume", "PASS",
|
||||
catalog_id=selection["catalog_id"],
|
||||
catalog_revision_id=selection["catalog_revision_id"],
|
||||
operation_id=operation_id,
|
||||
capture_count=1, publication_state="published")
|
||||
return {"capture": {"release_id": target["release_identity"]["id"],
|
||||
"capture_artifact_ids": [expected_capture]},
|
||||
"catalog_revision": revision,
|
||||
"publication": operation,
|
||||
"catalog_selection": selection,
|
||||
"coordinate": {"chart_id": descriptor["chart_id"],
|
||||
"dataset_id": descriptor["dataset_id"],
|
||||
"chart_uuid": target["chart_uuid"],
|
||||
"dataset_uuid": target["chart_contract"]["dataset_uuid"]},
|
||||
"query_model_fingerprint": source["query_model_fingerprint"],
|
||||
"filters_hash": source["filters_hash"]}
|
||||
|
||||
|
||||
def register_m01_capture_scenario(driver: Driver, dashboard_id: int, model: dict,
|
||||
environment_id: str, target: dict) -> dict:
|
||||
"""Resolve the published chart-response source through the owner-scoped M01 MCP workflow."""
|
||||
@@ -1330,8 +1431,11 @@ def main() -> int:
|
||||
parser.add_argument("--output", type=Path)
|
||||
parser.add_argument("--validate-only", action="store_true",
|
||||
help="Validate explicit inputs without making API calls")
|
||||
parser.add_argument("--run-lifecycle", action="store_true",
|
||||
help="Run chart-8 capture, publication, pin, and three-run acceptance")
|
||||
lifecycle = parser.add_mutually_exclusive_group()
|
||||
lifecycle.add_argument("--run-lifecycle", action="store_true",
|
||||
help="Run chart-8 capture, publication, pin, and three-run acceptance")
|
||||
lifecycle.add_argument("--resume-published", action="store_true",
|
||||
help="Resume only M01 and numeric acceptance from one exact published capture")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
env_file, seed_path, release_path, target_path, output = explicit_inputs(args)
|
||||
@@ -1414,8 +1518,12 @@ def main() -> int:
|
||||
"target_dashboard_uuid": target["target_dashboard_uuid"],
|
||||
"chart_uuid": target["chart_uuid"]}},
|
||||
indent=2, ensure_ascii=False) + "\n")
|
||||
if args.run_lifecycle:
|
||||
captured = capture_approve_publish(driver, target, scenario, catalog, model)
|
||||
if args.run_lifecycle or args.resume_published:
|
||||
if args.resume_published:
|
||||
captured = read_published_capture(driver, target, model)
|
||||
pin_target = target
|
||||
else:
|
||||
captured = capture_approve_publish(driver, target, scenario, catalog, model)
|
||||
driver.ids.update(
|
||||
capture_artifact_id=(captured["capture"].get("capture_artifact_ids") or [None])[0],
|
||||
published_catalog_revision_id=captured["catalog_revision"].get("catalog_revision_id"),
|
||||
@@ -1426,7 +1534,8 @@ def main() -> int:
|
||||
dataset_id=captured["coordinate"]["dataset_id"],
|
||||
query_model_fingerprint=captured["query_model_fingerprint"],
|
||||
filters_hash=captured["filters_hash"], capture_count=1, publication_state="published")
|
||||
pin_target = target_for_published_revision(target, captured)
|
||||
if not args.resume_published:
|
||||
pin_target = target_for_published_revision(target, captured)
|
||||
pin_scenario = register_m01_capture_scenario(driver, dashboard_id, model,
|
||||
target["target_environment_id"], pin_target)
|
||||
driver.ids.update(scenario_id=pin_scenario["scenario_id"],
|
||||
@@ -1467,7 +1576,7 @@ def main() -> int:
|
||||
driver.flush()
|
||||
driver.client.close()
|
||||
stopped = any(step["status"] in {"BLOCKED", "NEEDS_REVIEW"} for step in driver.steps)
|
||||
if args.run_lifecycle:
|
||||
if args.run_lifecycle or args.resume_published:
|
||||
if not stopped and acceptance_completed:
|
||||
driver.flush()
|
||||
return 0
|
||||
|
||||
@@ -14,8 +14,10 @@ from common import Blocked # noqa: E402
|
||||
from native_chart8_prepare import ( # noqa: E402
|
||||
bind_seeded_catalog_selection,
|
||||
target_for_published_revision,
|
||||
select_m01_profile_coordinate,
|
||||
validate_lab_bootstrap_mode,
|
||||
validate_existing_mcp_revision,
|
||||
validate_published_resume_target,
|
||||
)
|
||||
|
||||
|
||||
@@ -42,6 +44,78 @@ def test_existing_mcp_revision_identity_requires_exact_uuid_values() -> None:
|
||||
validate_existing_mcp_revision({"scenario_id": "first", "revision_id": "latest"})
|
||||
|
||||
|
||||
def test_published_resume_requires_explicit_exact_revision_and_producer_ids() -> None:
|
||||
valid = {
|
||||
"catalog_mode": "exact_revision",
|
||||
"catalog_selection": {
|
||||
"kind": "revision",
|
||||
"catalog_id": "67ec18a8-3d66-443e-8324-cdb8476d8f16",
|
||||
"catalog_revision_id": "9ffd1709-6aa8-46ad-b997-447338116df1",
|
||||
},
|
||||
"published_resume": {
|
||||
"capture_artifact_id": "306a684b-f8dd-4bec-b90d-48ff75ac4f5d",
|
||||
"publication_operation_id": "58bd49d1-7845-405b-a43c-bcc44f7fdcf4",
|
||||
},
|
||||
}
|
||||
validate_published_resume_target(valid)
|
||||
invalid = {**valid, "catalog_selection": {**valid["catalog_selection"], "kind": "current"}}
|
||||
with pytest.raises(Blocked, match="NATIVE_PUBLISHED_RESUME_INPUT_INVALID"):
|
||||
validate_published_resume_target(invalid)
|
||||
|
||||
|
||||
def _m01_profile_coordinate(*, chart_id=8, dataset_id=4, kind="chart", chart_uuid="chart-uuid",
|
||||
metric=None) -> dict:
|
||||
return {
|
||||
"coordinate_id": f"coordinate-{chart_id}-{dataset_id}-{kind}-{metric}",
|
||||
"chart_id": chart_id,
|
||||
"dataset_id": dataset_id,
|
||||
"metric_name": metric,
|
||||
"reference_coordinate": {"kind": kind, "chart_uuid": chart_uuid, "metric": metric},
|
||||
}
|
||||
|
||||
|
||||
def _m01_profile(coordinates: list[dict]) -> tuple[dict, dict]:
|
||||
target = {"chart_uuid": "chart-uuid", "capture_scenario": {"metric_name": "selected_value_cents"}}
|
||||
return {"coordinates": coordinates}, target
|
||||
|
||||
|
||||
def test_m01_selects_exact_published_whole_chart_coordinate() -> None:
|
||||
metric = _m01_profile_coordinate(kind="metric", metric="selected_value_cents")
|
||||
chart = _m01_profile_coordinate()
|
||||
profile, target = _m01_profile([metric, chart])
|
||||
|
||||
selected = select_m01_profile_coordinate(profile, target, 2, {
|
||||
"charts": [{"chart_id": 8, "dataset_id": 4, "chart_uuid": "chart-uuid"}],
|
||||
})
|
||||
|
||||
assert selected == chart
|
||||
|
||||
|
||||
@pytest.mark.parametrize("coordinate", [
|
||||
_m01_profile_coordinate(chart_id=9),
|
||||
_m01_profile_coordinate(dataset_id=5),
|
||||
_m01_profile_coordinate(kind="metric", metric="selected_value_cents"),
|
||||
_m01_profile_coordinate(chart_uuid="other-chart"),
|
||||
])
|
||||
def test_m01_refuses_wrong_published_chart_coordinate(coordinate: dict) -> None:
|
||||
profile, target = _m01_profile([coordinate])
|
||||
|
||||
with pytest.raises(Blocked, match="NATIVE_M01_PROFILE_COORDINATE_AMBIGUOUS"):
|
||||
select_m01_profile_coordinate(profile, target, 2, {
|
||||
"charts": [{"chart_id": 8, "dataset_id": 4, "chart_uuid": "chart-uuid"}],
|
||||
})
|
||||
|
||||
|
||||
def test_m01_refuses_ambiguous_whole_chart_coordinates() -> None:
|
||||
chart = _m01_profile_coordinate()
|
||||
profile, target = _m01_profile([chart, chart.copy()])
|
||||
|
||||
with pytest.raises(Blocked, match="NATIVE_M01_PROFILE_COORDINATE_AMBIGUOUS"):
|
||||
select_m01_profile_coordinate(profile, target, 2, {
|
||||
"charts": [{"chart_id": 8, "dataset_id": 4, "chart_uuid": "chart-uuid"}],
|
||||
})
|
||||
|
||||
|
||||
def test_seeded_catalog_selection_is_bound_from_exact_official_result() -> None:
|
||||
target = {"catalog_mode": "seed_empty", "source_dashboard_id": 2}
|
||||
catalog = {"source_dashboard_id": 2, "selection": {
|
||||
|
||||
Reference in New Issue
Block a user