feat(consistency): coordination hygiene checks and MCP workplan_id aliases
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Add C-25..C-30 fix-consistency checks for blocked-workplan inbox sweeps,
stale unread triage, workplan ID prefix/collision lint, and SCOPE freshness.
Extend brief generation and get_domain_summary with inbox hygiene warnings.
Complete workplan_id aliases on remaining MCP tools and retry transient
_api_get failures to reduce false stale-reference errors under load.
This commit is contained in:
tegwick 2026-07-08 00:56:21 +02:00
parent cc656a2b16
commit 9693946755
5 changed files with 819 additions and 58 deletions

View file

@ -344,6 +344,31 @@ def get_domain_summary(domain_slug: str) -> str:
if goal_guidance:
result["goal_guidance"] = goal_guidance
inbox_hygiene: dict[str, Any] = {}
try:
from scripts.consistency_check import collect_inbox_hygiene, STALE_UNREAD_DAYS
except ImportError:
collect_inbox_hygiene = None # type: ignore[assignment]
STALE_UNREAD_DAYS = 3
if collect_inbox_hygiene is not None:
for repo in repos:
repo_slug = repo["slug"]
hygiene = collect_inbox_hygiene(API_BASE, repo_slug)
if (
hygiene["stale_unread_count"]
or hygiene["missing_thread"]
or hygiene["work_requests_unpromoted"]
):
inbox_hygiene[repo_slug] = {
"stale_unread_count": hygiene["stale_unread_count"],
"stale_unread_days": STALE_UNREAD_DAYS,
"stale_unread": hygiene["stale_unread"][:5],
"missing_thread_count": len(hygiene["missing_thread"]),
"work_requests_unpromoted": hygiene["work_requests_unpromoted"][:3],
}
if inbox_hygiene:
result["inbox_hygiene"] = inbox_hygiene
# Compact capabilities list (type + title + repo_slug only, capped at 20)
caps_raw = _get("/capability-catalog/", {"domain": domain_slug, "status": "active"})
if isinstance(caps_raw, list):
@ -416,9 +441,21 @@ def list_tasks(
@mcp.tool()
def list_blocked_tasks(workstream_id: str | None = None) -> str:
"""List all waiting tasks, optionally filtered by workstream_id."""
return json.dumps(_get("/tasks", {"status": "wait", "workstream_id": workstream_id}), indent=2)
def list_blocked_tasks(
workplan_id: str | None = None,
workstream_id: str | None = None,
) -> str:
"""List all waiting tasks, optionally filtered by workplan.
Args:
workplan_id: UUID of the workplan (preferred).
workstream_id: legacy alias for workplan_id.
"""
parent_id = workplan_id or workstream_id
return json.dumps(
_get("/tasks", {"status": "wait", "workplan_id": parent_id, "workstream_id": parent_id}),
indent=2,
)
@mcp.tool()
@ -879,17 +916,25 @@ def clear_human_flag(task_id: str) -> str:
@mcp.tool()
def list_human_interventions(workstream_id: str | None = None) -> str:
def list_human_interventions(
workplan_id: str | None = None,
workstream_id: str | None = None,
) -> str:
"""List all tasks flagged for human intervention.
Returns tasks where needs_human=True, optionally filtered to one workstream.
Returns tasks where needs_human=True, optionally filtered to one workplan.
Use this at session start to surface Bernd's action items.
Args:
workstream_id: optional UUID to scope results to one workstream
workplan_id: optional UUID to scope results to one workplan (preferred).
workstream_id: legacy alias for workplan_id.
"""
parent_id = workplan_id or workstream_id
return json.dumps(
_get("/tasks", {"needs_human": "true", "workstream_id": workstream_id}),
_get(
"/tasks",
{"needs_human": "true", "workplan_id": parent_id, "workstream_id": parent_id},
),
indent=2,
)
@ -899,6 +944,7 @@ def record_decision(
title: str,
decision_type: str = "pending",
topic_id: str | None = None,
workplan_id: str | None = None,
workstream_id: str | None = None,
description: str | None = None,
rationale: str | None = None,
@ -914,17 +960,19 @@ def record_decision(
title: decision title
decision_type: made | pending
topic_id: optional topic UUID
workstream_id: optional workstream UUID (at least one required)
workplan_id: optional workplan UUID (preferred; at least one required)
workstream_id: legacy alias for workplan_id
description: optional context
rationale: reasoning behind the decision
decided_by: person/agent who decided
deadline: ISO datetime string for when decision is needed
"""
parent_id = workplan_id or workstream_id
decision = _post("/decisions", {
"title": title,
"decision_type": decision_type,
"topic_id": topic_id,
"workstream_id": workstream_id,
"workplan_id": parent_id,
"description": description,
"rationale": rationale,
"decided_by": decided_by,
@ -935,7 +983,8 @@ def record_decision(
progress_error = _emit_progress_event("record_decision", decision, {
"topic_id": topic_id,
"workstream_id": workstream_id,
"workplan_id": parent_id,
"workstream_id": parent_id,
"decision_id": decision["id"],
"event_type": "decision_recorded",
"summary": f"Decision recorded ({decision_type}): {title}",
@ -1075,9 +1124,16 @@ def update_workplan_status(workplan_id: str, status: str) -> str:
@mcp.tool()
def update_workstream_status(workstream_id: str, status: str) -> str:
def update_workstream_status(
status: str,
workplan_id: str | None = None,
workstream_id: str | None = None,
) -> str:
"""Legacy alias for update_workplan_status."""
return _update_workplan_status_impl(workstream_id, status, tool_name="update_workstream_status")
parent_id = workplan_id or workstream_id
if not parent_id:
return _json_result(_mcp_error("update_workstream_status", "workplan_id is required"))
return _update_workplan_status_impl(parent_id, status, tool_name="update_workstream_status")
@mcp.tool()
@ -1104,7 +1160,8 @@ def update_workplan(
@mcp.tool()
def update_workstream(
workstream_id: str,
workplan_id: str | None = None,
workstream_id: str | None = None,
title: str | None = None,
description: str | None = None,
owner: str | None = None,
@ -1113,8 +1170,11 @@ def update_workstream(
status: str | None = None,
) -> str:
"""Legacy alias for update_workplan."""
parent_id = workplan_id or workstream_id
if not parent_id:
return _json_result(_mcp_error("update_workstream", "workplan_id is required"))
return _update_workplan_impl(
workstream_id,
parent_id,
title=title,
description=description,
owner=owner,
@ -1277,16 +1337,22 @@ def create_workplan_dependency(
@mcp.tool()
def create_dependency(
from_workstream_id: str,
from_workplan_id: str | None = None,
from_workstream_id: str | None = None,
to_workplan_id: str | None = None,
to_workstream_id: str | None = None,
to_task_id: str | None = None,
relationship_type: str = "blocks",
description: str | None = None,
) -> str:
"""Legacy alias for create_workplan_dependency."""
source_id = from_workplan_id or from_workstream_id
target_id = to_workplan_id or to_workstream_id
if not source_id:
return _json_result(_mcp_error("create_dependency", "from_workplan_id is required"))
return _create_dependency_impl(
from_workplan_id=from_workstream_id,
to_workplan_id=to_workstream_id,
from_workplan_id=source_id,
to_workplan_id=target_id,
to_task_id=to_task_id,
relationship_type=relationship_type,
description=description,
@ -1294,23 +1360,30 @@ def create_dependency(
@mcp.tool()
def list_dependencies(workstream_id: str) -> str:
"""Return all dependency edges touching a workstream (both directions).
def list_dependencies(
workplan_id: str | None = None,
workstream_id: str | None = None,
) -> str:
"""Return all dependency edges touching a workplan (both directions).
The response distinguishes edges where this workstream is the dependent
The response distinguishes edges where this workplan is the dependent
(depends_on) from edges where it is the blocker (blocks).
Args:
workstream_id: UUID of the workstream to inspect
workplan_id: UUID of the workplan to inspect (preferred).
workstream_id: legacy alias for workplan_id.
"""
edges = _get(f"/workplans/{workstream_id}/dependencies")
parent_id = workplan_id or workstream_id
if not parent_id:
return _json_result(_mcp_error("list_dependencies", "workplan_id is required"))
edges = _get(f"/workplans/{parent_id}/dependencies")
depends_on = [
e for e in edges
if e.get("from_workplan_id", e.get("from_workstream_id")) == workstream_id
if e.get("from_workplan_id", e.get("from_workstream_id")) == parent_id
]
blocks = [
e for e in edges
if e.get("to_workplan_id", e.get("to_workstream_id")) == workstream_id
if e.get("to_workplan_id", e.get("to_workstream_id")) == parent_id
]
return json.dumps({"depends_on": depends_on, "blocks": blocks}, indent=2)
@ -1329,6 +1402,7 @@ def register_extension_point(
priority: str = "medium",
ep_id: str | None = None,
topic_id: str | None = None,
workplan_id: str | None = None,
workstream_id: str | None = None,
) -> str:
"""Register a discovered extension point — optional future functionality not yet committed.
@ -1345,13 +1419,15 @@ def register_extension_point(
priority: low | medium | high | critical
ep_id: optional human-readable ID, e.g. EP-CUST-001 (auto-assigned if omitted)
topic_id: UUID of related topic
workstream_id: UUID of related workstream
workplan_id: UUID of related workplan (preferred)
workstream_id: legacy alias for workplan_id
"""
parent_id = workplan_id or workstream_id
ep = _post("/extension-points", {
"domain": domain, "title": title, "ep_type": ep_type,
"description": description, "location": location,
"priority": priority, "ep_id": ep_id,
"topic_id": topic_id, "workstream_id": workstream_id,
"topic_id": topic_id, "workplan_id": parent_id,
})
_post("/progress", {
"summary": f"Extension point registered: [{ep.get('ep_id') or ep['id'][:8]}] {title} ({ep_type}, {domain})",
@ -1406,6 +1482,7 @@ def register_technical_debt(
severity: str = "medium",
td_id: str | None = None,
topic_id: str | None = None,
workplan_id: str | None = None,
workstream_id: str | None = None,
) -> str:
"""Register a technical debt item — a known quality compromise to address later.
@ -1422,13 +1499,15 @@ def register_technical_debt(
severity: low | medium | high | critical
td_id: optional human-readable ID, e.g. TD-CUST-001
topic_id: UUID of related topic
workstream_id: UUID of related workstream
workplan_id: UUID of related workplan (preferred)
workstream_id: legacy alias for workplan_id
"""
parent_id = workplan_id or workstream_id
td = _post("/technical-debt", {
"domain": domain, "title": title, "debt_type": debt_type,
"description": description, "location": location,
"severity": severity, "td_id": td_id,
"topic_id": topic_id, "workstream_id": workstream_id,
"topic_id": topic_id, "workplan_id": parent_id,
})
_post("/progress", {
"summary": f"Technical debt registered: [{td.get('td_id') or td['id'][:8]}] {title} ({debt_type}, {severity}, {domain})",
@ -2032,6 +2111,7 @@ def register_contribution(
target_org: str | None = None,
target_repo: str | None = None,
body_path: str | None = None,
related_workplan_id: str | None = None,
related_workstream_id: str | None = None,
notes: str | None = None,
) -> str:
@ -2043,20 +2123,22 @@ def register_contribution(
target_org: GitHub org or owner of the upstream project
target_repo: Repository name of the upstream project
body_path: Relative path to the Markdown artifact file in the repo
related_workstream_id: UUID of the related workstream (optional)
related_workplan_id: UUID of the related workplan (preferred, optional)
related_workstream_id: legacy alias for related_workplan_id
notes: Any additional notes (optional)
"""
parent_id = related_workplan_id or related_workstream_id
contrib = _post("/contributions", {
"type": type,
"title": title,
"target_org": target_org,
"target_repo": target_repo,
"body_path": body_path,
"related_workstream_id": related_workstream_id,
"related_workplan_id": parent_id,
"notes": notes,
})
_post("/progress", {
"workstream_id": related_workstream_id,
"workplan_id": parent_id,
"event_type": "contribution_registered",
"summary": f"Contribution registered [{type.upper()}]: {title}",
"author": "custodian",
@ -2441,6 +2523,7 @@ def request_capability(
capability_type: str,
requesting_agent: str,
requesting_domain: str,
requesting_workplan_id: str | None = None,
requesting_workstream_id: str | None = None,
priority: str = "medium",
blocking_task_id: str | None = None,
@ -2454,17 +2537,19 @@ def request_capability(
capability_type: Category (e.g. 'infrastructure', 'api', 'data', 'security')
requesting_agent: Your agent identifier (e.g. 'net-kingdom-worker')
requesting_domain: Your domain slug (e.g. 'custodian')
requesting_workstream_id: UUID of your workstream (optional)
requesting_workplan_id: UUID of your workplan (preferred, optional)
requesting_workstream_id: legacy alias for requesting_workplan_id
priority: low | medium | high | critical (default: medium)
blocking_task_id: UUID of the task blocked until this is fulfilled (optional)
"""
parent_id = requesting_workplan_id or requesting_workstream_id
req = _post("/capability-requests", {
"title": title,
"description": description,
"capability_type": capability_type,
"requesting_agent": requesting_agent,
"requesting_domain": requesting_domain,
"requesting_workstream_id": requesting_workstream_id,
"requesting_workplan_id": parent_id,
"priority": priority,
"blocking_task_id": blocking_task_id,
})
@ -2487,6 +2572,7 @@ def patch_capability_request(
catalog_entry_id: Optional[str] = None,
priority: Optional[str] = None,
blocking_task_id: Optional[str] = None,
fulfilling_workplan_id: Optional[str] = None,
fulfilling_workstream_id: Optional[str] = None,
) -> dict:
"""Correct mutable metadata on a capability request.
@ -2500,11 +2586,13 @@ def patch_capability_request(
catalog_entry_id: Correct catalog entry UUID. Re-derives fulfilling domain.
priority: New priority (low/medium/high/critical).
blocking_task_id: UUID of the task this request unblocks on completion.
fulfilling_workstream_id: UUID of the workstream delivering this capability.
fulfilling_workplan_id: UUID of the workplan delivering this capability (preferred).
fulfilling_workstream_id: legacy alias for fulfilling_workplan_id.
Returns:
Updated capability request dict, or {"error": "..."}.
"""
parent_id = fulfilling_workplan_id or fulfilling_workstream_id
body: dict = {}
if catalog_entry_id is not None:
body["catalog_entry_id"] = catalog_entry_id
@ -2512,8 +2600,8 @@ def patch_capability_request(
body["priority"] = priority
if blocking_task_id is not None:
body["blocking_task_id"] = blocking_task_id
if fulfilling_workstream_id is not None:
body["fulfilling_workstream_id"] = fulfilling_workstream_id
if parent_id is not None:
body["fulfilling_workplan_id"] = parent_id
if not body:
return {"error": "no fields provided to patch"}
@ -2923,6 +3011,7 @@ def record_token_event(
tokens_in: int,
tokens_out: int,
task_id: Optional[str] = None,
workplan_id: Optional[str] = None,
workstream_id: Optional[str] = None,
repo_id: Optional[str] = None,
model: Optional[str] = None,
@ -2942,7 +3031,8 @@ def record_token_event(
tokens_in: Input token count
tokens_out: Output token count
task_id: UUID of the task (nullable)
workstream_id: UUID of the workstream (nullable; auto-filled from task)
workplan_id: UUID of the workplan (preferred; nullable; auto-filled from task)
workstream_id: legacy alias for workplan_id
repo_id: UUID of the managed repo (nullable)
model: Model identifier, e.g. 'claude-sonnet-4-6'
agent: Agent name, e.g. 'custodian', 'ralph'
@ -2951,11 +3041,12 @@ def record_token_event(
note: Free-text note
session_id: Agent session identifier
"""
parent_id = workplan_id or workstream_id
body = {
"tokens_in": tokens_in,
"tokens_out": tokens_out,
"task_id": task_id,
"workstream_id": workstream_id,
"workplan_id": parent_id,
"repo_id": repo_id,
"model": model,
"agent": agent,
@ -2976,8 +3067,8 @@ def record_token_event(
}
# Append running total for the task if available
scope_id = task_id or workstream_id
scope = "task" if task_id else ("workstream" if workstream_id else None)
scope_id = task_id or parent_id
scope = "task" if task_id else ("workstream" if parent_id else None)
if scope and scope_id:
summary = _get("/token-events/summary", {"scope": scope, "id": scope_id})
if "error" not in summary: