173 lines
9.0 KiB
Python
173 lines
9.0 KiB
Python
"""Deterministic workflow materialized-view projection from canonical events."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Callable
|
|
|
|
|
|
PROTECTED_FIELDS = (
|
|
"quality_gate_status", "quality-gate-status",
|
|
"release_acceptance_status", "release-acceptance-status",
|
|
"unresolved_critical_risks", "unresolved-critical-risks",
|
|
"blocker-open", "blocker_open", "resume-condition",
|
|
"resume_condition_present", "resume-condition-satisfied",
|
|
"resume_condition_satisfied", "evidence-grade", "evidence_grade",
|
|
"human_gate_approved", "human-gate-approved",
|
|
"current-quality-event-id", "current-quality-event-ids",
|
|
"current-quality-artifact-id", "current-quality-artifact-sha256",
|
|
"current-completion-artifact-id", "current-completion-artifact-sha256",
|
|
"quality-panel-unmet",
|
|
)
|
|
|
|
|
|
def _latest_artifact_of_kind(artifacts: list[dict[str, Any]], kind: str) -> dict[str, Any] | None:
|
|
return next((artifact for artifact in reversed(artifacts or [])
|
|
if isinstance(artifact, dict) and artifact.get("artifact-kind") == kind), None)
|
|
|
|
|
|
def project_workflow(
|
|
ledger: dict[str, Any],
|
|
*,
|
|
trusted_artifacts: list[dict[str, Any]],
|
|
workflow_events: list[dict[str, Any]],
|
|
initial_stage: Callable[[str], str],
|
|
quality_panel_unmet: Callable[[str, list[dict[str, Any]], list[dict[str, Any]]], list[str]],
|
|
default_plan: str,
|
|
) -> dict[str, Any]:
|
|
"""Rebuild protected fields; caller-owned data is never accepted as runtime truth."""
|
|
projected = dict(ledger)
|
|
for key in PROTECTED_FIELDS:
|
|
projected.pop(key, None)
|
|
projected["artifacts"] = trusted_artifacts
|
|
quality_events: list[dict[str, Any]] = []
|
|
release_events: list[dict[str, Any]] = []
|
|
for event in workflow_events:
|
|
event_type = event.get("event-type")
|
|
if event_type == "workflow-initialized":
|
|
projected["stage"] = event.get("stage") or initial_stage(event.get("plan", default_plan))
|
|
projected["stage-status"] = "running"
|
|
projected["last-completed-stage"] = None
|
|
projected["completed-for-next-stage"] = None
|
|
projected.pop("blocked-from", None)
|
|
projected.pop("design-direction-approval", None)
|
|
projected.pop("experience-foundation-approval", None)
|
|
for key in (
|
|
"plan", "tier", "mode", "evidence-contract-version",
|
|
"parent-workflow-id", "product-decision-id",
|
|
"direction-input-brief-ref", "direction-input-brief-sha256",
|
|
):
|
|
if event.get(key) is not None:
|
|
projected[key] = event.get(key)
|
|
elif event_type == "state-transition":
|
|
projected["stage"] = event.get("to") or projected.get("stage")
|
|
projected["stage-status"] = "running"
|
|
projected["last-completed-stage"] = event.get("from")
|
|
projected["completed-for-next-stage"] = None
|
|
if event.get("to") == "blocked":
|
|
projected["blocked-from"] = event.get("from")
|
|
elif event.get("from") == "blocked":
|
|
projected.pop("blocked-from", None)
|
|
elif event_type == "stage-completed":
|
|
if event.get("stage") == projected.get("stage"):
|
|
projected["stage-status"] = "completed"
|
|
projected["last-completed-stage"] = event.get("stage")
|
|
projected["completed-for-next-stage"] = event.get("intended-next-stage")
|
|
elif event_type == "quality-gate-recorded":
|
|
quality_events.append(event)
|
|
elif event_type == "release-decision-recorded":
|
|
release_events.append(event)
|
|
elif event_type == "tier-escalated":
|
|
projected["tier"] = event.get("to-tier") or projected.get("tier")
|
|
elif event_type == "workflow-blocked":
|
|
projected["blocked-from"] = event.get("blocked-from") or projected.get("stage")
|
|
projected["stage"] = "blocked"
|
|
projected["stage-status"] = "running"
|
|
projected["completed-for-next-stage"] = None
|
|
projected["blocker-open"] = True
|
|
projected["resume-condition"] = event.get("resume-condition")
|
|
projected.pop("resume-condition-satisfied", None)
|
|
elif event_type == "workflow-resumed":
|
|
projected["stage"] = event.get("to") or projected.get("blocked-from") or projected.get("stage")
|
|
projected["stage-status"] = "running"
|
|
projected["completed-for-next-stage"] = None
|
|
projected.pop("blocked-from", None)
|
|
projected["blocker-open"] = False
|
|
projected["resume-condition-satisfied"] = True
|
|
elif event_type == "direction-approval-registered":
|
|
projected["design-direction-approval"] = {
|
|
"report-ref": event.get("report-ref"),
|
|
"report-sha256": event.get("report-sha256"),
|
|
"child-workflow-id": event.get("child-workflow-id"),
|
|
}
|
|
elif event_type == "experience-foundation-registered":
|
|
projected["experience-foundation-approval"] = {
|
|
"child-workflow-id": event.get("child-workflow-id"),
|
|
"product-decision-id": event.get("product-decision-id"),
|
|
"benchmark-id": event.get("benchmark-id"),
|
|
"benchmark-ref": event.get("benchmark-ref"),
|
|
"benchmark-sha256": event.get("benchmark-sha256"),
|
|
"strategy-id": event.get("strategy-id"),
|
|
"strategy-ref": event.get("strategy-ref"),
|
|
"strategy-sha256": event.get("strategy-sha256"),
|
|
"technical-id": event.get("technical-id"),
|
|
"technical-ref": event.get("technical-ref"),
|
|
"technical-sha256": event.get("technical-sha256"),
|
|
"operational-id": event.get("operational-id"),
|
|
"operational-ref": event.get("operational-ref"),
|
|
"operational-sha256": event.get("operational-sha256"),
|
|
"blueprint-id": event.get("blueprint-id"),
|
|
"blueprint-ref": event.get("blueprint-ref"),
|
|
"blueprint-sha256": event.get("blueprint-sha256"),
|
|
"wireframe-id": event.get("wireframe-id"),
|
|
"wireframe-ref": event.get("wireframe-ref"),
|
|
"wireframe-sha256": event.get("wireframe-sha256"),
|
|
}
|
|
|
|
completion = _latest_artifact_of_kind(trusted_artifacts, "completion-record")
|
|
if not completion:
|
|
return projected
|
|
completion_id = completion.get("artifact-id")
|
|
completion_sha = completion.get("artifact-sha256")
|
|
projected["current-completion-artifact-id"] = completion_id
|
|
projected["current-completion-artifact-sha256"] = completion_sha
|
|
current_quality = [
|
|
event for event in quality_events
|
|
if event.get("reviewed-artifact-id") == completion_id
|
|
and event.get("reviewed-artifact-sha256") == completion_sha
|
|
and any(
|
|
artifact.get("artifact-id") == event.get("review-artifact-id")
|
|
and artifact.get("artifact-sha256") == event.get("review-artifact-sha256")
|
|
for artifact in trusted_artifacts
|
|
)
|
|
]
|
|
latest_by_actor = {event.get("actor"): event for event in current_quality}
|
|
active_quality = list(latest_by_actor.values())
|
|
if active_quality:
|
|
failed = any(event.get("status") != "Passed" or event.get("blocker-open")
|
|
for event in active_quality)
|
|
projected["quality_gate_status"] = "Failed" if failed else "Passed"
|
|
projected["blocker-open"] = bool(projected.get("blocker-open")) or any(
|
|
bool(event.get("blocker-open")) for event in active_quality)
|
|
last_quality = active_quality[-1]
|
|
projected["current-quality-event-id"] = last_quality.get("workflow-event-id")
|
|
projected["current-quality-event-ids"] = sorted(
|
|
event.get("workflow-event-id") for event in active_quality if event.get("workflow-event-id")
|
|
)
|
|
projected["current-quality-artifact-id"] = last_quality.get("review-artifact-id")
|
|
projected["current-quality-artifact-sha256"] = last_quality.get("review-artifact-sha256")
|
|
panel_unmet = quality_panel_unmet(projected.get("tier"), trusted_artifacts, active_quality)
|
|
if panel_unmet:
|
|
projected["quality-panel-unmet"] = panel_unmet
|
|
projected.pop("quality_gate_status", None)
|
|
active_event_ids = projected.get("current-quality-event-ids") or []
|
|
for event in release_events:
|
|
if (event.get("reviewed-completion-artifact-id") != completion_id
|
|
or event.get("reviewed-completion-artifact-sha256") != completion_sha
|
|
or sorted(event.get("quality-event-set") or []) != active_event_ids):
|
|
continue
|
|
if event.get("status") == "Approved" and (
|
|
projected.get("quality_gate_status") != "Passed" or projected.get("blocker-open")):
|
|
continue
|
|
projected["release_acceptance_status"] = event.get("status")
|
|
projected["unresolved_critical_risks"] = bool(event.get("unresolved-critical-risks"))
|
|
return projected
|