4e7aa75ba5
Root-cause fix for the project-4444 class of deadlocks: any FAILED/ CANCELLED work item with downstream dependents used to block its whole tree forever, because the advancement gate required all dependencies to be APPROVED and nothing ever propagated or triaged failures. Settlement mechanism (work_item_transition.py): - compute doomed set (FAILED/CANCELLED seeds contagious through hard deps); settlement-released cards are treated as alive - three-way advancement gate: all-approved (unchanged) / settled-with- failures releases the nearest decision-capable card (manager parent -> failure-triage synthesis turn, rollup delivery/aggregate -> READY) with an atomic dependency_settlement stamp; released cards never oscillate back - claim/park/resume/dispatcher-tick all honor the stamp: runnable gates admit settled failed+stuck deps, parking excludes settled deps and re-arms triage when a failure raced the park, engine resume no longer re-locks released cards, the dispatcher tick releases rollup cards created after the failure - settlement cascade: once the settled card is APPROVED, stuck children the manager did not rebuild are cancelled (transitive closure over stamped stuck seeds, retried until every cancel lands) - info-class deps never block settlement; adaptive runnable gate now shares DEPENDENCY_CLASS_DEFAULT with the release gates Dispatch guard (company_mode.py): - NO_DELEGATION_JUSTIFICATION parsing tolerates markdown decoration (bold/lists/quotes/full-width colon) and rejects placeholder echoes across all artifact/metadata/content channels - retries exhausted no longer FAILs the work item: dispatch is a soft constraint, so the turn output is accepted as normal completion and annotated via manager_dispatch_guard_unresolved; the reminder loop is unchanged, and the mutation flag is now reset per turn so one past delegation can never mute future reminders - manager board context now surfaces failed/cancelled children with their preserved output and pending-cancellation stuck list so the triage turn can rebuild, accept partial results, or escalate Self-produced delegation output goes through review (persisted fact, single predicate): - the DONE transition classifies what a dispatch/intake/plan turn actually delivered from store ground truth (live children => delegated, none => self_produced) and persists turn_output_kind/-source on the WorkItem - is_manager_reviewable_turn honors the persisted marker, so the DONE routing, report spawn, report completion and recovery scans all read the same fact — this closes a pre-existing hole where NO_DELEGATION_JUSTIFICATION output auto-approved with no review at all - escalation requires a real agent manager above; top seats reporting to the human owner keep auto-approve (covered by final delivery's human acceptance) instead of minting unclaimable review cards - dispatcher tick reconciles reviewable cards stuck in AWAITING_MANAGER_REVIEW with no live report/review card by rebuilding the report card idempotently (legacy DBs, crash windows) Verified: 4444 tasks.db replay unwedges end to end; real-store report->review chain exercised without lifecycle mocks (mutation check confirms the tests bite); full suite failure set identical to a same-session HEAD baseline run (all remaining failures pre-existing or environment flakes reproduced at HEAD). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
362 lines
12 KiB
Python
362 lines
12 KiB
Python
"""Helpers for company work-item projection identity metadata."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Mapping
|
|
|
|
|
|
WORK_ITEM_PROJECTION_ID_KEY = "work_item_projection_id"
|
|
WORK_ITEM_TURN_TYPE_KEY = "work_item_turn_type"
|
|
GATE_REWORK_PROJECTION_ID_KEY = "rework_projection_id"
|
|
GATE_TARGET_PROJECTION_ID_KEY = "target_projection_id"
|
|
GATE_TARGET_PROJECTION_IDS_KEY = "target_projection_ids"
|
|
|
|
CANONICAL_WORK_ITEM_TURN_TYPES: frozenset[str] = frozenset(
|
|
{
|
|
"intake",
|
|
"dispatch",
|
|
"plan",
|
|
"setup",
|
|
"execute",
|
|
"review",
|
|
"report",
|
|
"followup",
|
|
"monitor",
|
|
"aggregate",
|
|
"deliver",
|
|
"self_evolution",
|
|
}
|
|
)
|
|
|
|
_TURN_TYPE_ALIASES: dict[str, str] = {
|
|
"delegate": "dispatch",
|
|
"delegation": "dispatch",
|
|
"delivery": "deliver",
|
|
"follow-up": "followup",
|
|
"follow_up": "followup",
|
|
"synthesis": "aggregate",
|
|
"synthesize": "aggregate",
|
|
"self-evolution": "self_evolution",
|
|
"self evolution": "self_evolution",
|
|
}
|
|
|
|
|
|
def _clean(value: Any) -> str:
|
|
return str(value or "").strip()
|
|
|
|
|
|
def normalize_work_item_turn_type(value: Any, *, fallback: str = "") -> str:
|
|
"""Normalize runtime/work-item turn-kind aliases to canonical names."""
|
|
normalized = _clean(value).lower() or _clean(fallback).lower()
|
|
return _TURN_TYPE_ALIASES.get(normalized, normalized)
|
|
|
|
|
|
def canonical_work_item_turn_type_for_kind(value: Any, *, fallback: str = "execute") -> str:
|
|
"""Map a WorkItem/runtime business kind to the canonical runtime turn type."""
|
|
normalized = normalize_work_item_turn_type(value, fallback="")
|
|
if normalized in CANONICAL_WORK_ITEM_TURN_TYPES:
|
|
return normalized
|
|
fallback_normalized = normalize_work_item_turn_type(fallback, fallback="")
|
|
if fallback_normalized in CANONICAL_WORK_ITEM_TURN_TYPES:
|
|
return fallback_normalized
|
|
return ""
|
|
|
|
|
|
def work_item_projection_id_from_metadata(
|
|
metadata: Mapping[str, Any] | None,
|
|
*,
|
|
fallback: str = "",
|
|
) -> str:
|
|
"""Read the canonical projected work-item task identity."""
|
|
if not metadata:
|
|
return _clean(fallback)
|
|
value = _clean(metadata.get(WORK_ITEM_PROJECTION_ID_KEY))
|
|
if value:
|
|
return value
|
|
return _clean(fallback)
|
|
|
|
|
|
def work_item_turn_type_from_metadata(
|
|
metadata: Mapping[str, Any] | None,
|
|
*,
|
|
fallback: str = "execute",
|
|
) -> str:
|
|
"""Read the canonical company work-item turn type."""
|
|
if not metadata:
|
|
return _clean(fallback).lower()
|
|
for key in (
|
|
WORK_ITEM_TURN_TYPE_KEY,
|
|
"work_kind",
|
|
"delegation_turn_kind",
|
|
):
|
|
value = normalize_work_item_turn_type(metadata.get(key))
|
|
if value:
|
|
return value
|
|
return normalize_work_item_turn_type(fallback)
|
|
|
|
|
|
def projection_id_for_task(task: Any) -> str:
|
|
"""Return the work-item projection identity for a projected Task."""
|
|
metadata = dict(getattr(task, "metadata", {}) or {})
|
|
return work_item_projection_id_from_metadata(
|
|
metadata,
|
|
fallback=_clean(getattr(task, "id", "")),
|
|
)
|
|
|
|
|
|
def turn_type_for_task(task: Any, *, fallback: str = "execute") -> str:
|
|
"""Return the work-item turn type for a projected Task."""
|
|
metadata = dict(getattr(task, "metadata", {}) or {})
|
|
return work_item_turn_type_from_metadata(metadata, fallback=fallback)
|
|
|
|
|
|
def projection_id_for_work_item(item: Any) -> str:
|
|
"""Return the projection identity for a DelegationWorkItem-like object."""
|
|
explicit_projection = _clean(getattr(item, "projection_id", ""))
|
|
if explicit_projection:
|
|
return explicit_projection
|
|
metadata = dict(getattr(item, "metadata", {}) or {})
|
|
return work_item_projection_id_from_metadata(
|
|
metadata,
|
|
fallback=(
|
|
_clean(getattr(item, "projection_id", ""))
|
|
or _clean(getattr(item, "work_item_id", ""))
|
|
),
|
|
)
|
|
|
|
|
|
def turn_type_for_work_item(item: Any, *, fallback: str = "execute") -> str:
|
|
"""Return the turn type for a DelegationWorkItem-like object."""
|
|
metadata = dict(getattr(item, "metadata", {}) or {})
|
|
return work_item_turn_type_from_metadata(
|
|
metadata,
|
|
fallback=_clean(getattr(item, "kind", "")) or fallback,
|
|
)
|
|
|
|
|
|
def canonical_turn_type_for_work_item(item: Any, *, fallback: str = "execute") -> str:
|
|
"""Return a canonical turn type for a WorkItem-like object or metadata."""
|
|
if isinstance(item, Mapping):
|
|
return work_item_turn_type_from_metadata(item, fallback=fallback)
|
|
return turn_type_for_work_item(item, fallback=fallback)
|
|
|
|
|
|
def _turn_type_for_value(value: Any, *, fallback: str = "") -> str:
|
|
if isinstance(value, Mapping):
|
|
return work_item_turn_type_from_metadata(value, fallback=fallback)
|
|
if hasattr(value, "metadata"):
|
|
return canonical_turn_type_for_work_item(value, fallback=fallback or "execute")
|
|
return canonical_work_item_turn_type_for_kind(value, fallback=fallback)
|
|
|
|
|
|
def is_delivery_turn(value_or_metadata: Any) -> bool:
|
|
"""Return True for final delivery turns, including legacy ``delivery`` alias."""
|
|
return _turn_type_for_value(value_or_metadata, fallback="") == "deliver"
|
|
|
|
|
|
def is_manager_reviewable_turn(value_or_metadata: Any) -> bool:
|
|
"""Return True when a finished WorkItem should enter manager review flow.
|
|
|
|
An explicit persisted ``turn_output_kind`` wins over the turn-type
|
|
default: delegation-kind turns (dispatch/intake/plan) are review-exempt
|
|
only because their deliverable is normally a child card set that gets
|
|
reviewed per child. When such a turn completed with the manager's own
|
|
work product instead, the done-transition stamps
|
|
``turn_output_kind=self_produced`` on the WorkItem and that output is
|
|
reviewable like any execute turn — every consumer of this predicate
|
|
(DONE routing, report spawn, report completion, recovery scans) follows
|
|
automatically.
|
|
"""
|
|
metadata: Mapping[str, Any] | None = None
|
|
if isinstance(value_or_metadata, Mapping):
|
|
metadata = value_or_metadata
|
|
elif hasattr(value_or_metadata, "metadata"):
|
|
metadata = getattr(value_or_metadata, "metadata", None) or {}
|
|
if metadata and str(metadata.get("turn_output_kind", "") or "").strip().lower() == "self_produced":
|
|
return True
|
|
turn_type = _turn_type_for_value(value_or_metadata, fallback="")
|
|
if not turn_type:
|
|
return False
|
|
return turn_type not in {"intake", "plan", "dispatch", "aggregate", "deliver", "self_evolution"}
|
|
|
|
|
|
def mark_work_item_projection(
|
|
metadata: Mapping[str, Any] | None = None,
|
|
*,
|
|
projection_id: str = "",
|
|
turn_type: str = "",
|
|
) -> dict[str, Any]:
|
|
"""Return metadata with canonical work-item projection keys only."""
|
|
result = dict(metadata or {})
|
|
projection = _clean(projection_id) or work_item_projection_id_from_metadata(result)
|
|
turn = normalize_work_item_turn_type(turn_type) or work_item_turn_type_from_metadata(result)
|
|
if projection:
|
|
result[WORK_ITEM_PROJECTION_ID_KEY] = projection
|
|
if turn:
|
|
result[WORK_ITEM_TURN_TYPE_KEY] = turn
|
|
return result
|
|
|
|
|
|
def mark_projected_work_item_task(
|
|
metadata: Mapping[str, Any] | None = None,
|
|
*,
|
|
projection_id: str = "",
|
|
turn_type: str = "",
|
|
) -> dict[str, Any]:
|
|
"""Return projected task/work-item metadata with canonical identity keys."""
|
|
return mark_work_item_projection(
|
|
metadata,
|
|
projection_id=projection_id,
|
|
turn_type=turn_type,
|
|
)
|
|
|
|
|
|
def work_item_identity_payload(
|
|
*,
|
|
projection_id: str = "",
|
|
turn_type: str = "",
|
|
source: Mapping[str, Any] | None = None,
|
|
include_empty: bool = False,
|
|
) -> dict[str, str]:
|
|
"""Build a canonical event/checkpoint/ws payload identity fragment."""
|
|
source_meta = dict(source or {})
|
|
projection = _clean(projection_id) or work_item_projection_id_from_metadata(source_meta, fallback="")
|
|
turn = normalize_work_item_turn_type(turn_type) or work_item_turn_type_from_metadata(source_meta, fallback="")
|
|
payload: dict[str, str] = {}
|
|
if projection or include_empty:
|
|
payload[WORK_ITEM_PROJECTION_ID_KEY] = projection
|
|
if turn or include_empty:
|
|
payload[WORK_ITEM_TURN_TYPE_KEY] = turn
|
|
return payload
|
|
|
|
|
|
def work_item_identity_payload_from_metadata(
|
|
metadata: Mapping[str, Any] | None,
|
|
*,
|
|
projection_id_fallback: str = "",
|
|
turn_type_fallback: str = "",
|
|
include_empty: bool = False,
|
|
) -> dict[str, str]:
|
|
"""Build a canonical payload identity fragment from metadata."""
|
|
source_meta = dict(metadata or {})
|
|
return work_item_identity_payload(
|
|
projection_id=work_item_projection_id_from_metadata(
|
|
source_meta,
|
|
fallback=projection_id_fallback,
|
|
),
|
|
turn_type=work_item_turn_type_from_metadata(
|
|
source_meta,
|
|
fallback=turn_type_fallback,
|
|
),
|
|
include_empty=include_empty,
|
|
)
|
|
|
|
|
|
def work_item_identity_payload_for_task(
|
|
task: Any,
|
|
*,
|
|
fallback_turn_type: str = "",
|
|
include_empty: bool = False,
|
|
) -> dict[str, str]:
|
|
"""Build a canonical payload identity fragment for a Task-like object."""
|
|
if task is None:
|
|
return work_item_identity_payload(
|
|
turn_type=fallback_turn_type,
|
|
include_empty=include_empty,
|
|
)
|
|
return work_item_identity_payload(
|
|
projection_id=projection_id_for_task(task),
|
|
turn_type=turn_type_for_task(task, fallback=fallback_turn_type),
|
|
include_empty=include_empty,
|
|
)
|
|
|
|
|
|
def migrate_work_item_projection_metadata(
|
|
metadata: Mapping[str, Any] | None,
|
|
*,
|
|
projection_id_fallback: str = "",
|
|
turn_type_fallback: str = "",
|
|
) -> tuple[dict[str, Any], bool]:
|
|
"""Normalize canonical projection metadata from canonical inputs only."""
|
|
before = dict(metadata or {})
|
|
result = dict(before)
|
|
projection = work_item_projection_id_from_metadata(
|
|
result,
|
|
fallback=projection_id_fallback,
|
|
)
|
|
turn = work_item_turn_type_from_metadata(
|
|
result,
|
|
fallback=turn_type_fallback or "execute",
|
|
)
|
|
if projection and not _clean(result.get(WORK_ITEM_PROJECTION_ID_KEY)):
|
|
result[WORK_ITEM_PROJECTION_ID_KEY] = projection
|
|
if turn and not _clean(result.get(WORK_ITEM_TURN_TYPE_KEY)):
|
|
result[WORK_ITEM_TURN_TYPE_KEY] = turn
|
|
return result, result != before
|
|
|
|
|
|
def rework_projection_id_for_gate(gate: Any, *, fallback: str = "") -> str:
|
|
"""Return the gate rework target as a work-item projection identity."""
|
|
metadata = dict(getattr(gate, "metadata", {}) or {})
|
|
return _clean(
|
|
metadata.get(GATE_REWORK_PROJECTION_ID_KEY)
|
|
or getattr(gate, "rework_projection_id", "")
|
|
or fallback
|
|
)
|
|
|
|
|
|
def mark_gate_rework_projection(gate: Any, projection_id: str) -> Any:
|
|
"""Attach projection-only gate rework identity."""
|
|
projection = _clean(projection_id)
|
|
metadata = dict(getattr(gate, "metadata", {}) or {})
|
|
if projection:
|
|
metadata[GATE_REWORK_PROJECTION_ID_KEY] = projection
|
|
setattr(gate, "metadata", metadata)
|
|
if hasattr(gate, "rework_projection_id"):
|
|
setattr(gate, "rework_projection_id", projection or None)
|
|
return gate
|
|
|
|
|
|
def target_projection_id_for_decision(decision: Any, *, fallback: str = "") -> str:
|
|
"""Return a gate-harness target as a work-item projection identity."""
|
|
return _clean(
|
|
getattr(decision, GATE_TARGET_PROJECTION_ID_KEY, "")
|
|
or fallback
|
|
)
|
|
|
|
|
|
def target_projection_ids_for_decision(decision: Any) -> list[str]:
|
|
"""Return all gate-harness targets as work-item projection identities."""
|
|
raw_ids = list(getattr(decision, GATE_TARGET_PROJECTION_IDS_KEY, []) or [])
|
|
if not raw_ids:
|
|
single = target_projection_id_for_decision(decision)
|
|
raw_ids = [single] if single else []
|
|
result: list[str] = []
|
|
seen: set[str] = set()
|
|
for item in raw_ids:
|
|
value = _clean(item)
|
|
if value and value not in seen:
|
|
seen.add(value)
|
|
result.append(value)
|
|
return result
|
|
|
|
|
|
def gate_rework_payload(
|
|
*,
|
|
rework_projection_id: str = "",
|
|
target_projection_id: str = "",
|
|
review_projection_id: str = "",
|
|
) -> dict[str, Any]:
|
|
"""Build projection-only gate/rework payload metadata."""
|
|
rework = _clean(rework_projection_id)
|
|
target = _clean(target_projection_id)
|
|
review = _clean(review_projection_id)
|
|
payload: dict[str, Any] = {}
|
|
if rework:
|
|
payload[GATE_REWORK_PROJECTION_ID_KEY] = rework
|
|
if target:
|
|
payload[GATE_TARGET_PROJECTION_ID_KEY] = target
|
|
if review:
|
|
payload["review_projection_id"] = review
|
|
return payload
|