#!/usr/bin/env python3 """Pure, fail-closed policy for bounded implementation-review-repair chains.""" from __future__ import annotations import json from dataclasses import dataclass from typing import Any import cli_lane_goal import supervisor_lineage as lineage SUPERVISOR_AUTHOR = "hermes-supervisor" STAMP_KEY = "supervisor" REVIEW_KIND = "review" REPAIR_KIND = "repair" DONE_STATUSES = frozenset({"done"}) INACTIVE_STATUSES = frozenset({"done", "blocked"}) COMMIT_KEYS = ("head_commit", "commit_sha", "commit", "sha", "revision") PR_KEYS = ("pull_request", "pr_url", "pr", "merge_request") BRANCH_KEYS = ("branch", "branch_name", "head_branch") SAFE_ACTIONS = frozenset({"none", "spawn", "ship", "clear_ready", "escalate"}) FORBIDDEN_PAYLOAD_KEYS = frozenset( {"merge", "approve", "close", "deploy", "provider", "model", "api_key", "metered"} ) @dataclass(frozen=True) class Limits: """Bounds and routing that gate every autonomous decision.""" max_cycles: int = 5 max_chains: int = 20 review_assignee: str = "cli-auto" repair_assignee: str = "cli-auto" @dataclass(frozen=True) class Decision: """One resolved supervisor intent. ``action`` is always in SAFE_ACTIONS.""" action: str reason: str = "" target_id: str = "" payload: dict[str, Any] | None = None def assert_safe(decision: Decision) -> Decision: """Reject any decision outside the allow-listed, non-metered action set.""" if decision.action not in SAFE_ACTIONS: raise ValueError(f"unsafe supervisor action: {decision.action!r}") payload = decision.payload or {} for key in FORBIDDEN_PAYLOAD_KEYS: if key in payload: raise ValueError(f"supervisor payload carries forbidden key {key!r}") return decision def field(task: Any, name: str, default: Any = None) -> Any: if isinstance(task, dict): return task.get(name, default) return getattr(task, name, default) def _as_dict(value: Any) -> dict[str, Any] | None: if isinstance(value, dict): return value if isinstance(value, str): try: parsed = json.loads(value) except (ValueError, TypeError): return None return parsed if isinstance(parsed, dict) else None return None def task_id(task: Any) -> str: return str(field(task, "id", "") or "") def metadata(task: Any) -> dict[str, Any]: return _as_dict(field(task, "metadata")) or {} def stamp(task: Any) -> dict[str, Any]: value = metadata(task).get(STAMP_KEY) return value if isinstance(value, dict) else {} def supervised_kind(task: Any) -> str | None: kind = stamp(task).get("kind") return kind if kind in (REVIEW_KIND, REPAIR_KIND) else None def is_done(task: Any) -> bool: return str(field(task, "status", "") or "") in DONE_STATUSES def is_lineage_only_anchor(task: Any) -> bool: """Keep only trusted, unparented legacy roots out of automated chains.""" meta = metadata(task) root = meta.get("supervisor_lineage") trusted = lineage.initial({"id": task_id(task), "metadata": {"supervisor_lineage": root}}) return (isinstance(root, dict) and STAMP_KEY not in meta and trusted is not None and trusted.root_task_id == task_id(task) and field(task, "result") == "Lineage-only migration anchor." and "Lineage-only anchor" in str(field(task, "body", "") or "")) def objective(task: Any) -> str: title = str(field(task, "title", "") or "") body = str(field(task, "body", "") or "") return f"{title}\n\n{body}".strip() def parents(task: Any) -> list[str]: raw = field(task, "parents") if raw is None: raw = field(task, "task_links") if isinstance(raw, dict): raw = raw.get("parents") ids: list[str] = [] if isinstance(raw, (list, tuple, set)): for item in raw: if isinstance(item, dict): ident = item.get("id") or item.get("parent") or item.get("task_id") else: ident = item if ident: ids.append(str(ident)) return ids def parse_result(task: Any) -> tuple[dict[str, Any] | None, str | None]: """Parse the terminal result, failing closed on anything non-object.""" raw = field(task, "result") if raw is None or raw == "" or raw == {}: return None, "task carries no result to interpret" parsed = _as_dict(raw) if parsed is None: return None, "task result is not a JSON object" return parsed, None def _first_key(source: Any, keys: tuple[str, ...]) -> str | None: if not isinstance(source, dict): return None for key in keys: value = source.get(key) if isinstance(value, str) and value.strip(): return value.strip() return None def _result_sources(task: Any, result: dict[str, Any] | None) -> list[dict[str, Any]]: sources = [metadata(task)] if isinstance(result, dict): sources.append(result) nested = _as_dict(result.get("metadata")) if nested is not None: sources.append(nested) return sources def extract_commit(task: Any, result: dict[str, Any] | None) -> str | None: for source in _result_sources(task, result): found = _first_key(source, COMMIT_KEYS) if found: return found return None def extract_pr(task: Any, result: dict[str, Any] | None) -> str | None: for source in _result_sources(task, result): found = _first_key(source, PR_KEYS) or _first_key(source, BRANCH_KEYS) if found: return found return None def _has_changes(result: dict[str, Any] | None) -> bool: changed = (result or {}).get("changed_files") return isinstance(changed, (list, tuple)) and len(changed) > 0 def _strings(value: Any) -> list[str]: if not isinstance(value, (list, tuple)): return [] return [str(item) for item in value if isinstance(item, str) and item.strip()] def _int(value: Any, default: int) -> int: if isinstance(value, bool) or not isinstance(value, (int, float)): return default return int(value) def _haystack(task: Any) -> str: return "\n".join( ( str(field(task, "title", "") or ""), str(field(task, "body", "") or ""), json.dumps(metadata(task), sort_keys=True, default=str), ) ) def _references(task: Any, ident: str) -> bool: if not ident: return False if ident in parents(task): return True return ident in _haystack(task) def _mentions_commit(task: Any, head_commit: str) -> bool: return bool(head_commit) and head_commit in _haystack(task) def _looks_like_review(task: Any) -> bool: if metadata(task).get("task_role") == cli_lane_goal.REVIEW_ROLE: return True role, _ = cli_lane_goal.task_role(objective(task)) if role == cli_lane_goal.REVIEW_ROLE: return True return "review" in str(field(task, "title", "") or "").lower() def _looks_like_repair(task: Any) -> bool: if metadata(task).get("task_role") == cli_lane_goal.IMPLEMENTATION_ROLE: return True title = str(field(task, "title", "") or "").lower() return "repair" in title or "fix" in title def existing_followup( tasks: list[Any], kind: str, root_id: str, head_commit: str ) -> bool: """True if any supervisor or external card already covers this revision.""" looks_like = _looks_like_review if kind == REVIEW_KIND else _looks_like_repair for task in tasks: st = stamp(task) if ( st.get("kind") == kind and str(st.get("root") or "") == root_id and str(st.get("head_commit") or "") == head_commit ): return True if ( _references(task, root_id) and _mentions_commit(task, head_commit) and looks_like(task) ): return True return False def active_chain_count(tasks: list[Any]) -> int: """Distinct chains with an in-flight (not done/blocked) supervised card.""" roots: set[str] = set() for task in tasks: if supervised_kind(task) is None: continue if str(field(task, "status", "") or "") in INACTIVE_STATUSES: continue root = str(stamp(task).get("root") or "") if root: roots.add(root) return len(roots) def _stamp_meta( kind: str, root: str, source: str, head_commit: str, cycle: int, chain: lineage.Lineage ) -> dict: stamped = { "kind": kind, "root": root, "parent": source, "head_commit": head_commit, "cycle": cycle, **chain.stamp_fields(), } return {STAMP_KEY: stamped} def _idempotency(kind: str, root: str, head_commit: str, cycle: int) -> str: return f"supervisor:{kind}:{root}:{head_commit}:{cycle}" def _findings_block(findings: list[str]) -> str: if not findings: return "" lines = "\n".join(f"- {item}" for item in findings) return f"\nReview findings to address:\n{lines}\n" def _build_review( root_id: str, source_id: str, head_commit: str, cycle: int, chain: lineage.Lineage, findings: list[str], assignee: str, ) -> dict[str, Any]: meta = _stamp_meta(REVIEW_KIND, root_id, source_id, head_commit, cycle, chain) meta["task_role"] = cli_lane_goal.REVIEW_ROLE body = ( "Hermes-Task-Role: review\n\n" f"Read-only review of commit {head_commit} on {chain.pull_request}, branch " f"{chain.branch}. Do not modify the implementation; " "make no code changes. Report a single SHIP or BLOCK verdict with " "evidence." + _findings_block(findings) ) linked = [root_id] if source_id == root_id else [root_id, source_id] return { "title": f"Review commit {head_commit[:12]} (cycle {cycle})", "body": body, "assignee": assignee, "parents": linked, "metadata": meta, "idempotency_key": _idempotency(REVIEW_KIND, root_id, head_commit, cycle), "initial_status": "running", } def _build_repair( root_id: str, review_id: str, head_commit: str, cycle: int, findings: list[str], assignee: str, chain: lineage.Lineage, ) -> dict[str, Any]: meta = _stamp_meta(REPAIR_KIND, root_id, review_id, head_commit, cycle, chain) meta["task_role"] = cli_lane_goal.IMPLEMENTATION_ROLE body = ( "Hermes-Task-Role: repair\n\n" f"Repair commit {head_commit} on existing PR {chain.pull_request}, branch " f"{chain.branch}; do not create a branch or PR. Commit and push the fix." + _findings_block(findings) ) return { "title": f"Repair review findings for {head_commit[:12]} (cycle {cycle})", "body": body, "assignee": assignee, "parents": [root_id, review_id], "metadata": meta, "idempotency_key": _idempotency(REPAIR_KIND, root_id, head_commit, cycle), "initial_status": "running", } def _latest_head(tasks: list[Any], chain: lineage.Lineage) -> str: """Return the newest verified repair head, never treating base drift as stale.""" newest = "" newest_cycle = 0 for candidate in tasks: if task_id(candidate) == chain.root_task_id: root_meta = metadata(candidate) value = _first_key(root_meta, ("live_pr_head", "latest_head_commit")) if value: return value candidate_stamp = stamp(candidate) if supervised_kind(candidate) != REPAIR_KIND or lineage.from_stamp(candidate_stamp) != chain: continue result, error = parse_result(candidate) commit = None if error else extract_commit(candidate, result) cycle = _int(candidate_stamp.get("cycle"), 0) if commit and cycle >= newest_cycle: newest, newest_cycle = commit, cycle return newest def _validated_chain(task: Any, tasks: list[Any]) -> lineage.Lineage | None: """Accept child lineage only when its root and stamped parent corroborate it.""" child_stamp = stamp(task) chain = lineage.from_stamp(child_stamp) if chain is None: return None root = next((item for item in tasks if task_id(item) == chain.root_task_id), None) if root is None or lineage.initial(root) != chain: return None parent_id = str(child_stamp.get("parent") or "") if parent_id == chain.root_task_id: return chain parent = next((item for item in tasks if task_id(item) == parent_id), None) return chain if parent is not None and lineage.from_stamp(stamp(parent)) == chain else None def plan_implementation(task: Any, tasks: list[Any], limits: Limits) -> Decision: tid = task_id(task) result, error = parse_result(task) role, _ = cli_lane_goal.task_role(objective(task), result or {}) if role != cli_lane_goal.IMPLEMENTATION_ROLE: # A done review-shaped card the supervisor did not create belongs to the # external shepherd's chain; never drive it from the implementation side. return Decision("none", "not a supervisor-owned implementation card") if error is not None: return Decision("escalate", f"implementation result unparseable: {error}", tid) commit = extract_commit(task, result) chain = lineage.initial(task) if commit is None: if _has_changes(result): return Decision( "escalate", "done implementation changed files but exposes no head commit to review", tid, ) return Decision("none", "implementation produced no commit to review") if chain is None: return Decision( "escalate", "implementation lacks trusted branch/PR lineage from its assignment payload", tid, ) if chain.root_task_id != tid: return Decision("escalate", "initial assignment root_task_id does not match task", tid) if existing_followup(tasks, REVIEW_KIND, tid, commit): return Decision("none", "a review already covers this commit") if active_chain_count(tasks) >= limits.max_chains: return Decision("none", "supervised-chain ceiling reached; deferring new review") payload = _build_review(tid, tid, commit, 1, chain, [], limits.review_assignee) return Decision("spawn", f"open review for {commit}", tid, payload) def plan_review(task: Any, tasks: list[Any], limits: Limits) -> Decision: st = stamp(task) review_id = task_id(task) root_id = str(st.get("root") or "") head_commit = str(st.get("head_commit") or "") cycle = _int(st.get("cycle"), 0) chain = _validated_chain(task, tasks) if not root_id or not head_commit or cycle <= 0 or chain is None or chain.root_task_id != root_id: return Decision( "escalate", "review card carries an incomplete supervisor stamp", review_id ) result, error = parse_result(task) if error is not None: return Decision("escalate", f"review result unparseable: {error}", review_id) verdict, problem = cli_lane_goal.review_verdict(result) if problem or verdict is None: detail = problem or "no explicit SHIP or BLOCK verdict" return Decision("escalate", f"review verdict ambiguous: {detail}", review_id) if verdict == "SHIP": current = _latest_head(tasks, chain) if current and current != head_commit: return Decision( "clear_ready", f"stale SHIP for {head_commit}; current verified chain head is {current}", root_id, {"commit": current, "stale_commit": head_commit}, ) return Decision( "ship", f"review shipped {head_commit}", root_id, {"commit": head_commit, "pr": chain.pull_request, "branch": chain.branch, "project": chain.project, "base_branch": chain.base_branch, "root_task_id": chain.root_task_id}, ) # BLOCK -> spawn a bounded repair, unless one exists or the budget is spent. if cycle >= limits.max_cycles: # Escalate the review card itself, not root_id: the impl stays # legitimately done, so re-targeting it re-fires every tick. return Decision( "escalate", f"repair cycle limit ({limits.max_cycles}) reached for chain {root_id}; " "escalating this review instead of spawning another repair", review_id, ) if existing_followup(tasks, REPAIR_KIND, root_id, head_commit): return Decision("none", "a repair already covers this commit") findings = _strings(result.get("findings")) payload = _build_repair( root_id, review_id, head_commit, cycle, findings, limits.repair_assignee, chain ) return Decision("spawn", f"open repair for {head_commit}", review_id, payload) def plan_repair(task: Any, tasks: list[Any], limits: Limits) -> Decision: st = stamp(task) repair_id = task_id(task) root_id = str(st.get("root") or "") old_commit = str(st.get("head_commit") or "") cycle = _int(st.get("cycle"), 0) chain = _validated_chain(task, tasks) if not root_id or cycle <= 0 or chain is None or chain.root_task_id != root_id: return Decision( "escalate", "repair card carries an incomplete supervisor stamp", repair_id ) result, error = parse_result(task) if error is not None: return Decision("escalate", f"repair result unparseable: {error}", repair_id) new_commit = extract_commit(task, result) if new_commit is None: return Decision( "escalate", "repair completed without an identifiable new commit", repair_id ) if new_commit == old_commit: return Decision( "escalate", "repair produced no new commit over the reviewed revision", repair_id, ) next_cycle = cycle + 1 if next_cycle > limits.max_cycles: return Decision( "escalate", f"repair cycle limit ({limits.max_cycles}) reached for chain {root_id}; " "escalating this repair instead of spawning a re-review", repair_id, ) if existing_followup(tasks, REVIEW_KIND, root_id, new_commit): return Decision("none", "a re-review already covers this commit") payload = _build_review( root_id, repair_id, new_commit, next_cycle, chain, [], limits.review_assignee ) return Decision("spawn", f"open re-review for {new_commit}", repair_id, payload) def plan(task: Any, tasks: list[Any], limits: Limits) -> Decision: """Resolve the single follow-up action for one terminal card.""" if not is_done(task): return Decision("none", "task is not in a terminal done state") kind = supervised_kind(task) if kind == REVIEW_KIND: return plan_review(task, tasks, limits) if kind == REPAIR_KIND: return plan_repair(task, tasks, limits) if is_lineage_only_anchor(task): return Decision("none", "lineage-only migration anchors require an explicit continuation") return plan_implementation(task, tasks, limits)