← Files taskplaneARCHIVED FILE
taskplane/workflow.py
14.4 KB · Oct 2, 2026 · 00:29 UTC
"""Pure workflow rules. Native authority and artifact I/O belong to adapters."""
from __future__ import annotations
from copy import deepcopy
from typing import Any
import uuid
from .primitives import content_fingerprint
PHASES = ("product", "design", "plan", "build", "evaluate", "engineering", "retro")
ENTRY_PHASES = ("product", "design", "engineering")
OUTPUT_FIELDS = {
"product": ("scope", "acceptance_criteria", "non_goals", "dependencies", "task_outline", "finding_references"),
"design": ("approach", "alternatives", "interfaces_and_state", "authority_boundaries", "failure_and_recovery", "graph_impact", "acceptance_test_map"),
"plan": ("task_dag", "ownership", "write_scope", "acceptance_coverage", "verification_strategy", "integration_order"),
"build": ("change_inventory", "task_acceptance_map", "build_checks", "known_gaps"),
"evaluate": ("criterion_results", "source_test_fingerprints", "regression_evidence", "unknowns_and_failures"),
"engineering": ("findings", "severity", "lens_coverage", "source_locations", "requirements_comparison", "remaining_risk"),
"retro": ("accepted_outcome", "actual_verification", "deferred_items_and_owners", "lessons"),
}
BINDING_FIELDS = ("workspace", "root", "run", "visit", "checkpoint", "revision",
"manifest_digest", "scope_digest")
class Refusal(ValueError):
"""A mandatory workflow refusal, never a telemetry warning."""
def __init__(self, reason: str, detail: str):
self.reason = reason
self.detail = detail
super().__init__(detail)
def result(self) -> dict[str, Any]:
return {"status": "capability_blocked" if self.reason == "unsupported_authority" else "blocked",
"reason": self.reason, "detail": self.detail}
def require(condition: object, reason: str, detail: str) -> None:
if not condition:
raise Refusal(reason, detail)
def visit(phase: str) -> dict[str, Any]:
return {"id": uuid.uuid4().hex, "phase": phase, "work": "working",
"decision": "not_requested", "packet": None, "superseded": False}
def validate_scope(scope: dict[str, Any]) -> None:
require(isinstance(scope, dict), "invalid_evidence", "Scope must be an object.")
require(scope.get("execution_contract") in (None, "native-default/v1"),
"invalid_evidence", "Unknown execution contract.")
criteria = scope.get("criteria")
require(isinstance(criteria, list) and criteria and
all(isinstance(c, str) and c.strip() for c in criteria) and len(set(criteria)) == len(criteria),
"invalid_evidence", "Scope needs unique acceptance criterion IDs.")
paths = scope.get("paths")
require(isinstance(paths, dict) and set(paths) == set(PHASES),
"invalid_evidence", "Declare exact write paths for all seven phases.")
assert isinstance(paths, dict)
for phase in PHASES:
values = paths[phase]
require(isinstance(values, list) and all(isinstance(p, str) and p for p in values),
"invalid_evidence", f"Invalid write scope for {phase}.")
require(isinstance(scope.get("verification_inputs", []), list),
"invalid_evidence", "Verification inputs must be a path list.")
def new_state(workspace: str, root: str, run: str, scope: dict[str, Any], *,
entry: str = "product", standalone: bool = False) -> dict[str, Any]:
require(entry in ENTRY_PHASES and (standalone or entry == "product"),
"approval_required", "Full delivery begins at Product; standalone entry cannot grant Build.")
validate_scope(scope)
return {"schema": "taskplane.workflow/v1", "workspace": workspace, "root": root,
"run": run, "revision": 0, "scope": deepcopy(scope),
"visits": [visit(p) for p in ((entry,) if standalone else PHASES)],
"index": 0, "decisions": {}, "history": [], "finished": False}
def current(state: dict[str, Any]) -> dict[str, Any]:
return dict(state["visits"][state["index"]])
def accepted_plan(state: dict[str, Any]) -> dict[str, Any]:
plans = [v for v in state["visits"][:state["index"]]
if v["phase"] == "plan" and not v["superseded"]]
require(plans and plans[-1]["decision"] == "approved" and plans[-1]["packet"],
"approval_required", "A current accepted Plan is required for Build or repair.")
return dict(plans[-1])
def validate_state(state: dict[str, Any]) -> None:
"""Reject structurally corrupt protected records without inventing repairs."""
from . import workflow_approval as approval
validate_scope(state["scope"])
approval.validate_history(state)
require(type(state["revision"]) is int and state["revision"] >= 0
and type(state["index"]) is int and isinstance(state["visits"], list)
and 0 <= state["index"] < len(state["visits"])
and isinstance(state["decisions"], dict) and isinstance(state["history"], list)
and type(state["finished"]) is bool,
"state_unavailable", "Invalid protected state fields.")
ids = set()
for stage in state["visits"]:
require(isinstance(stage, dict) and isinstance(stage.get("id"), str) and stage["id"]
and stage["id"] not in ids and stage.get("phase") in PHASES
and stage.get("work") in ("working", "ready", "evidence_blocked")
and stage.get("decision") in ("not_requested", "awaiting_human_approval", "approved",
"changes_requested", "rejected", "cancelled", "stale")
and type(stage.get("superseded")) is bool and "packet" in stage,
"state_unavailable", "Invalid protected visit.")
ids.add(stage["id"])
packet = stage["packet"]
if packet is None:
require(stage["decision"] == "not_requested", "state_unavailable", "Decision has no packet.")
continue
require(isinstance(packet, dict) and packet.get("phase") == stage["phase"]
and packet.get("visit") == stage["id"] and isinstance(packet.get("checkpoint"), str)
and all(isinstance(packet.get(k), dict) for k in ("manifest", "source_manifest", "context", "output")),
"state_unavailable", "Invalid protected packet.")
if stage["decision"] in ("approved", "changes_requested", "rejected", "cancelled"):
require(any(isinstance(d, dict) and approval.decision_authorized(state, d)
and d.get("choice") == stage["decision"] and isinstance(d.get("binding"), dict)
and d["binding"].get("visit") == stage["id"]
and d["binding"].get("manifest_digest") == content_fingerprint(packet)
for d in state["decisions"].values()),
"state_unavailable", "Stored decision has no matching native record.")
require(not state["finished"] or all(v["superseded"] or v["decision"] == "approved" for v in state["visits"]),
"state_unavailable", "Finished route contains unaccepted visits.")
def binding(state: dict[str, Any], packet: dict[str, Any]) -> dict[str, Any]:
return {**{key: state[key] for key in ("workspace", "root", "run", "revision")},
"visit": current(state)["id"], "checkpoint": packet["checkpoint"],
"manifest_digest": content_fingerprint(packet),
"scope_digest": content_fingerprint(state["scope"])}
def submit(state: dict[str, Any], packet: dict[str, Any]) -> dict[str, Any]:
require(not state["finished"], "approval_required", "This route is already accepted.")
s = deepcopy(state)
stage = s["visits"][s["index"]]
require(packet.get("phase") == stage["phase"] and packet.get("visit") == stage["id"],
"invalid_evidence", "Output must belong to the current stage visit.")
if stage["packet"]:
s["history"].append({"visit": stage["id"], "packet": stage["packet"], "decision": stage["decision"]})
stage.update(packet=deepcopy(packet), work="ready", decision="awaiting_human_approval")
s["revision"] += 1
stage["packet_revision"] = s["revision"]
return s
def decide(state: dict[str, Any], verified: dict[str, Any]) -> dict[str, Any]:
"""Consume a profile-validated decision; adapters own its stated assurance."""
event_id = verified.get("event_id")
require(isinstance(event_id, str) and event_id, "unsupported_authority", "Native event identity is missing.")
from . import workflow_approval as approval
require(approval.decision_authorized(state, verified),
"unsupported_authority", "Decision needs explicit human approval or a valid run-bound policy.")
if verified.get("kind") == "policy":
require(approval.automatic_decision(state, verified["assessment"]) == verified,
"invalid_evidence", "Automatic decision does not match the validated assessment.")
if state.get("profile") == "native_workflow":
require(verified.get("assurance") == "observed" and isinstance(verified.get("provenance"), dict),
"invalid_evidence", "Native workflow decisions must disclose observed provenance.")
else:
require("assurance" not in verified or verified["assurance"] != "observed",
"unsupported_authority", "Observed local provenance cannot authorize a protected host profile.")
if event_id in state["decisions"]:
require(state["decisions"][event_id] == verified, "stale_checkpoint", "Conflicting native event replay.")
return deepcopy(state)
stage = current(state)
require(stage["decision"] == "awaiting_human_approval" and stage["packet"],
"approval_required", "Submit the current stage's evidence before requesting a decision.")
expected = binding(state, stage["packet"])
require(verified.get("binding") == expected, "stale_checkpoint",
"Decision does not match the current root, visit, revision and artifact checkpoint.")
choice = verified.get("choice")
require(choice in ("approved", "changes_requested", "rejected", "cancelled"),
"invalid_evidence", "Unknown human decision.")
s = deepcopy(state)
stage = s["visits"][s["index"]]
stage["decision"] = choice
stage["work"] = "ready" if choice == "approved" else "working"
s["decisions"][event_id] = deepcopy(verified)
if choice != "approved" or stage["packet"].get("route_change"):
approval.suspend(s, "Human intervention or route change requires renewed automatic authorization.")
if choice == "approved" and stage["phase"] == "plan":
# The accepted Plan narrows the outer requested scope to actual Build grants.
s["scope"]["paths"]["build"] = list(stage["packet"]["output"]["write_scope"])
change = stage["packet"].get("route_change")
if choice == "approved" and change:
require(stage["phase"] in ("design", "evaluate", "engineering"),
"invalid_evidence", "This stage cannot extend the delivery route.")
kind = change["kind"]
stages: tuple[str, ...]
if kind == "delivery":
require(all(v["superseded"] for v in s["visits"][s["index"] + 1:]),
"invalid_evidence", "Delivery extension applies only at the end of the current scope.")
validate_scope(change["scope"])
# Acceptance ends the standalone scope. Its evidence remains historical;
# later authorized Build edits must not stale the initiating review.
stage["superseded"] = True
s["scope"] = deepcopy(change["scope"])
stages = PHASES
else:
require(kind == "repair" and stage["phase"] in ("evaluate", "engineering"),
"invalid_evidence", "Only evaluation or review can propose a scoped repair.")
plan = accepted_plan(s)
plan_index = next(i for i, v in enumerate(s["visits"]) if v["id"] == plan["id"])
require(any(v["phase"] == "build" and not v["superseded"]
and v["decision"] == "approved" and v["packet"]
for v in s["visits"][plan_index + 1:s["index"]]),
"approval_required", "Repair requires an accepted Build after the current Plan.")
s["scope"]["paths"]["build"] = list(plan["packet"]["output"]["write_scope"])
stages = PHASES[3:]
# Supersede implementation verification, retaining decisions and old packets as history.
for old in s["visits"]:
if old["phase"] in PHASES[3:]:
old["superseded"] = True
s["history"].append({"route_change": deepcopy(change), "event_id": event_id})
s["visits"].extend(visit(p) for p in stages)
s["revision"] += 1
return s
def advance(state: dict[str, Any], phase: str) -> dict[str, Any]:
require(not state["finished"], "approval_required", "The accepted route cannot be reopened by progress.")
stage = current(state)
require(stage["decision"] == "approved", "approval_required", f"Human or authorized policy approval for {stage['phase']} is required.")
next_index = next((i for i in range(state["index"] + 1, len(state["visits"]))
if not state["visits"][i]["superseded"]), None)
require(next_index is not None, "approval_required", "No next phase is authorized; finish the accepted scope.")
assert next_index is not None
require(state["visits"][next_index]["phase"] == phase,
"approval_required", "Skipped, backward or undeclared phase transition.")
s = deepcopy(state)
s["index"] = next_index
s["revision"] += 1
return s
def finish(state: dict[str, Any]) -> dict[str, Any]:
require(all(v["superseded"] or v["decision"] == "approved" for v in state["visits"]),
"approval_required", "Every required stage visit needs current human acceptance or valid policy acceptance.")
s = deepcopy(state)
if not s["finished"]:
s["finished"] = True
s["revision"] += 1
return s
def invalidate(state: dict[str, Any], visit_id: str, reason: str) -> dict[str, Any]:
s = deepcopy(state)
first = next(i for i, v in enumerate(s["visits"]) if v["id"] == visit_id)
for v in s["visits"][first:]:
if not v["superseded"] and v["packet"]:
v["decision"] = "stale"
v["work"] = "evidence_blocked"
s["index"] = first
s["finished"] = False
s["revision"] += 1
from . import workflow_approval as approval
approval.suspend(s, "Evidence drift requires renewed authorization: " + reason)
s["history"].append({"invalidated_visit": visit_id, "reason": reason})
return s
SHA-256: 0e9ff42737ec7fda16bded3a1cda08f6cdd8e6f3aa101f96efedfe74bc012bc4