feat(gateway): decomposition coverage gate + AC visibility (guardrails spec 2)

The decomposition floor that pairs with the roll-up gate (spec 4): a PM
cannot finish decomposing a parent while one of its acceptance criteria has
no subtask responsible for it — the "two leaves, half the ACs silently
dropped" pattern. Three parts:

- Gate: i_am_idle is rejected for a cell_pm/main_pm whose owned parent still
  has criteria in unclaimed_parent_acceptance_criteria (claimed = referenced
  by any live, non-cancelled child). Distinct from the roll-up gate, which
  fires at submit_up/complete and demands a *completed* child; this fires
  earlier and asks only that every criterion be *claimed*. Safe-by-
  construction: inert until a PM declares coverage, so legacy / not-yet-
  adopted decompositions are never blocked.

- Visibility: PM-facing briefings (give_me_work, i_will_plan, submit_up) and
  every delegate response now carry parent_ac_coverage ({id,text,claimed,
  verified} per criterion) + unclaimed_parent_acs, so a PM can map subtasks
  to criterion ids via covers_parent_criteria and see what is still
  uncovered after each delegate. Off for leaf roles, so a developer's own
  criteria never surface as bogus "unclaimed" noise.

- Prompts: cell_pm / main_pm role prompts document covers_parent_criteria and
  the new idle enforcement in the existing Coverage discipline.

TaskService.{parent_ac_coverage,unclaimed_parent_acceptance_criteria} added
beside uncovered_parent_acceptance_criteria; all three refactored onto a
shared _parent_ac_ref_sets helper (keeps each under the xenon B ceiling,
preserves the committed roll-up behavior). Verb tables regenerated for the
new delegate param — the regen also syncs pre-existing table drift that was
never regenerated after earlier merges (read_messages, pass_review
ac_verdicts, board pitch). Two brand-new generated tables (prompter,
secretary) are left untracked pending a separate decision.
This commit is contained in:
Renn F
2026-06-16 03:49:00 +02:00
parent 5ce4570c85
commit 1fb723174a
14 changed files with 380 additions and 29 deletions
+42 -3
View File
@@ -36,6 +36,7 @@ as the source of truth for verb signatures.
| `notify_list` | `notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20)` |
| `notify_get` | `notify_get(notification_id: UUID)` |
| `notify_ack` | `notify_ack(notification_id: UUID)` |
| `read_messages` | `read_messages()` |
| `channels` | `channels()` |
## qa
@@ -49,7 +50,7 @@ as the source of truth for verb signatures.
| `give_me_work` | `give_me_work()` |
| `i_am_blocked` | `i_am_blocked(task_id: UUID, reason: str, blocker_type: str | None = None, what_needed: str | None = None)` |
| `i_am_idle` | `i_am_idle()` |
| `pass_review` | `pass_review(task_id: UUID, notes: str)` |
| `pass_review` | `pass_review(task_id: UUID, notes: str, ac_verdicts: list[str] | None = None)` |
| `resume` | `resume(task_id: UUID)` |
| `unclaim` | `unclaim(task_id: UUID)` |
@@ -64,6 +65,7 @@ as the source of truth for verb signatures.
| `notify_list` | `notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20)` |
| `notify_get` | `notify_get(notification_id: UUID)` |
| `notify_ack` | `notify_ack(notification_id: UUID)` |
| `read_messages` | `read_messages()` |
| `channels` | `channels()` |
## documenter
@@ -94,6 +96,7 @@ as the source of truth for verb signatures.
| `notify_list` | `notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20)` |
| `notify_get` | `notify_get(notification_id: UUID)` |
| `notify_ack` | `notify_ack(notification_id: UUID)` |
| `read_messages` | `read_messages()` |
| `channels` | `channels()` |
## cell_pm
@@ -103,7 +106,7 @@ as the source of truth for verb signatures.
| Verb | Body schema |
|------|-------------|
| `complete` | `complete(task_id: UUID, notes: str)` |
| `delegate` | `delegate(parent_task_id: UUID, title: str, description: str, assigned_to: str, team: str, task_type: str, nature: str, estimated_complexity: str, acceptance_criteria: list[str], project_id: UUID | None = None)` |
| `delegate` | `delegate(parent_task_id: UUID, title: str, description: str, assigned_to: str, team: str, task_type: str, nature: str, estimated_complexity: str, acceptance_criteria: list[str], project_id: UUID | None = None, covers_parent_criteria: list[str] | None = None)` |
| `escalate_up` | `escalate_up(task_id: UUID, reason: str)` |
| `give_me_work` | `give_me_work()` |
| `i_am_idle` | `i_am_idle()` |
@@ -130,6 +133,7 @@ as the source of truth for verb signatures.
| `notify_list` | `notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20)` |
| `notify_get` | `notify_get(notification_id: UUID)` |
| `notify_ack` | `notify_ack(notification_id: UUID)` |
| `read_messages` | `read_messages()` |
| `channels` | `channels()` |
## main_pm
@@ -139,7 +143,7 @@ as the source of truth for verb signatures.
| Verb | Body schema |
|------|-------------|
| `complete` | `complete(task_id: UUID, notes: str)` |
| `delegate` | `delegate(parent_task_id: UUID, title: str, description: str, assigned_to: str, team: str, task_type: str, nature: str, estimated_complexity: str, acceptance_criteria: list[str], project_id: UUID | None = None)` |
| `delegate` | `delegate(parent_task_id: UUID, title: str, description: str, assigned_to: str, team: str, task_type: str, nature: str, estimated_complexity: str, acceptance_criteria: list[str], project_id: UUID | None = None, covers_parent_criteria: list[str] | None = None)` |
| `escalate_to_ceo` | `escalate_to_ceo(task_id: UUID, reason: str)` |
| `escalate_up` | `escalate_up(task_id: UUID, reason: str)` |
| `give_me_work` | `give_me_work()` |
@@ -166,6 +170,7 @@ as the source of truth for verb signatures.
| `notify_list` | `notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20)` |
| `notify_get` | `notify_get(notification_id: UUID)` |
| `notify_ack` | `notify_ack(notification_id: UUID)` |
| `read_messages` | `read_messages()` |
| `channels` | `channels()` |
## product_owner
@@ -183,6 +188,7 @@ as the source of truth for verb signatures.
| Tool | Body schema |
|------|-------------|
| `note` | `note(text: str, scope: str = 'note', task_id: UUID | None = None, title: str | None = None, context: str = '', options: list[str | str] | None = None, chosen: str = '', rationale: str = '', consequences: list[str] | None = None, what_done: str = '', what_learned: str = '', what_struggled: str = '', next_steps: list[str] | None = None)` |
| `pitch` | `pitch(title: str, slug: str, problem: str, proposed_solution: str, target_cells: list[str])` |
| `say` | `say(channel: str, text: str, task_id: UUID | None = None)` |
| `dm` | `dm(recipient: str, text: str, task_id: UUID | None = None, skill: str | None = None)` |
| `notify` | `notify(target: str, text: str, priority: str = 'normal', task_id: UUID | None = None)` |
@@ -191,6 +197,7 @@ as the source of truth for verb signatures.
| `notify_list` | `notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20)` |
| `notify_get` | `notify_get(notification_id: UUID)` |
| `notify_ack` | `notify_ack(notification_id: UUID)` |
| `read_messages` | `read_messages()` |
| `channels` | `channels()` |
## head_marketing
@@ -208,6 +215,7 @@ as the source of truth for verb signatures.
| Tool | Body schema |
|------|-------------|
| `note` | `note(text: str, scope: str = 'note', task_id: UUID | None = None, title: str | None = None, context: str = '', options: list[str | str] | None = None, chosen: str = '', rationale: str = '', consequences: list[str] | None = None, what_done: str = '', what_learned: str = '', what_struggled: str = '', next_steps: list[str] | None = None)` |
| `pitch` | `pitch(title: str, slug: str, problem: str, proposed_solution: str, target_cells: list[str])` |
| `say` | `say(channel: str, text: str, task_id: UUID | None = None)` |
| `dm` | `dm(recipient: str, text: str, task_id: UUID | None = None, skill: str | None = None)` |
| `notify` | `notify(target: str, text: str, priority: str = 'normal', task_id: UUID | None = None)` |
@@ -216,6 +224,7 @@ as the source of truth for verb signatures.
| `notify_list` | `notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20)` |
| `notify_get` | `notify_get(notification_id: UUID)` |
| `notify_ack` | `notify_ack(notification_id: UUID)` |
| `read_messages` | `read_messages()` |
| `channels` | `channels()` |
## auditor
@@ -237,3 +246,33 @@ as the source of truth for verb signatures.
| `notify_get` | `notify_get(notification_id: UUID)` |
| `channels` | `channels()` |
## prompter
### Flow verbs
| Verb | Body schema |
|------|-------------|
| `i_am_idle` | `i_am_idle()` |
### Content (do) tools
| Tool | Body schema |
|------|-------------|
| `note` | `note(text: str, scope: str = 'note', task_id: UUID | None = None, title: str | None = None, context: str = '', options: list[str | str] | None = None, chosen: str = '', rationale: str = '', consequences: list[str] | None = None, what_done: str = '', what_learned: str = '', what_struggled: str = '', next_steps: list[str] | None = None)` |
| `evidence` | `evidence(task_id: UUID)` |
## secretary
### Flow verbs
| Verb | Body schema |
|------|-------------|
| `i_am_idle` | `i_am_idle()` |
### Content (do) tools
| Tool | Body schema |
|------|-------------|
| `note` | `note(text: str, scope: str = 'note', task_id: UUID | None = None, title: str | None = None, context: str = '', options: list[str | str] | None = None, chosen: str = '', rationale: str = '', consequences: list[str] | None = None, what_done: str = '', what_learned: str = '', what_struggled: str = '', next_steps: list[str] | None = None)` |
| `evidence` | `evidence(task_id: UUID)` |