This repository has been archived on 2026-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
orchestrator/agent-team/tests/test_gate_kind_coverage.py

80 lines
3.5 KiB
Python
Raw Normal View History

"""Guard: every ``pending_questions`` ``kind`` has a coordinator delivery path.
A human-gate interrupt ``kind`` is NOT delivered for free. The responder/notify
path is not kind-agnostic: a ``kind`` added to the ``pending_questions`` CHECK
without a coordinator branch makes the task suspend FOREVER — no Slack post, no
durable ledger row, the human can never answer. That was a real CRITICAL gap
when ``confluence_approval`` was first wired (the node + graph were complete but
the coordinator only branched on ``plan_decision``). See the memory
``reference_agent_team_extension_gotchas``.
This test pins the invariant from the schema (the single source of truth for
allowed kinds): every non-default kind must have a ``*_KIND`` constant in
``agent_team.graph`` AND an explicit reference to it in ``coordinator.py`` (its
delivery branch). A new kind therefore cannot ship without its delivery wiring.
"""
from __future__ import annotations
import re
from pathlib import Path
from agent_team import coordinator as coordinator_mod
from agent_team import graph as graph_mod
from agent_team.db import schema as schema_mod
# ``clarify`` is delivered by the DEFAULT notify path (the fallthrough in
# ``_post_resume_followups`` / ``start_task``), so it needs no dedicated branch.
# Every OTHER kind must be keyed on its own ``graph.*_KIND`` constant.
_DEFAULT_DELIVERED_KINDS = {"clarify"}
def _declared_kinds() -> set[str]:
"""The kind values the ``pending_questions`` CHECK allows (source of truth)."""
match = re.search(r"kind IN \(([^)]*)\)", schema_mod.PENDING_QUESTIONS_DDL)
assert match, (
"could not find the pending_questions kind CHECK in PENDING_QUESTIONS_DDL"
)
return set(re.findall(r"'([^']+)'", match.group(1)))
def _kind_constant_names() -> dict[str, str]:
"""Map each kind VALUE to the ``graph.*_KIND`` constant NAME that holds it."""
out: dict[str, str] = {}
for name in dir(graph_mod):
if name.endswith("_KIND"):
value = getattr(graph_mod, name)
if isinstance(value, str):
out[value] = name
return out
def test_declared_kinds_parse_sane() -> None:
# Guards the regex itself: if PENDING_QUESTIONS_DDL is reshaped and the CHECK
# stops parsing, this fails loudly instead of silently passing an empty set.
kinds = _declared_kinds()
assert {"clarify", "plan_decision"} <= kinds
def test_every_pending_question_kind_has_coordinator_delivery() -> None:
kinds = _declared_kinds()
constants = _kind_constant_names()
coordinator_src = Path(coordinator_mod.__file__).read_text()
for kind in sorted(kinds - _DEFAULT_DELIVERED_KINDS):
const = constants.get(kind)
assert const, (
f"pending_questions kind {kind!r} has no `*_KIND` constant in "
"agent_team.graph — define one and give it a coordinator delivery "
"branch (see reference_agent_team_extension_gotchas)."
)
assert f"graph_mod.{const}" in coordinator_src, (
f"pending_questions kind {kind!r} ({const}) has no delivery branch in "
"coordinator.py — a kind added to the CHECK without a coordinator "
"branch suspends the task forever (no Slack post, no ledger row). Add "
"a branch keyed on graph_mod." + (const or "<KIND>") + " in "
"_post_resume_followups, start_task, AND the redelivery sweep, and "
"make the interrupt payload carry a question_set "
"(see reference_agent_team_extension_gotchas)."
)