diff --git a/agents/prompts/_generated/auditor.md b/agents/prompts/_generated/auditor.md index 5e64719ae..2fdc2699c 100644 --- a/agents/prompts/_generated/auditor.md +++ b/agents/prompts/_generated/auditor.md @@ -19,7 +19,6 @@ | `evidence` | `evidence(task_id: UUID)` | | `dm` | `dm(recipient: str, text: str, task_id: UUID | None = None, skill: str | None = None)` | | `read_a2a` | `read_a2a(see do_server)` | -| `task_time` | `task_time(task_id: UUID)` | | `approve_playbook` | `approve_playbook(playbook_id: UUID)` | | `reject_playbook` | `reject_playbook(playbook_id: UUID, reason: str)` | | `archive_playbook` | `archive_playbook(playbook_id: UUID)` | diff --git a/agents/prompts/_generated/cell_pm.md b/agents/prompts/_generated/cell_pm.md index 46a82ada5..5c54bd5a2 100644 --- a/agents/prompts/_generated/cell_pm.md +++ b/agents/prompts/_generated/cell_pm.md @@ -7,7 +7,6 @@ | Verb | Body schema | |------|-------------| -| `cancel_leaf` | `cancel_leaf(task_id: UUID, reason: str)` | | `complete` | `complete(task_id: UUID, notes: str)` | | `declare_coverage` | `declare_coverage(task_id: UUID, criteria: list[str])` | | `delegate` | `delegate(parent_task_id: UUID, title: str, description: str, assigned_to: str, team: str, task_type: str, nature: str, estimated_complexity: Complexity, acceptance_criteria: list[str], project_id: UUID | None = None, covers_parent_criteria: list[str] | None = None, intends_to_touch: list[str] | None = None, adds_migration: bool = False, touches_shared: bool = False, depends_on: list[UUID] | None = None)` | @@ -33,7 +32,6 @@ | `evidence` | `evidence(task_id: UUID)` | | `pr_update` | `pr_update(see do_server)` | | `draft_playbook` | `draft_playbook(title: str, problem: str, procedure: str, tags: list[str] = PydanticUndefined, source_task_id: UUID | None = None)` | -| `task_time` | `task_time(task_id: UUID)` | | `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)` | diff --git a/agents/prompts/_generated/developer.md b/agents/prompts/_generated/developer.md index 62a341624..9b53c3fa3 100644 --- a/agents/prompts/_generated/developer.md +++ b/agents/prompts/_generated/developer.md @@ -30,10 +30,7 @@ | `draft_playbook` | `draft_playbook(title: str, problem: str, procedure: str, tags: list[str] = PydanticUndefined, source_task_id: UUID | None = None)` | | `propose_video` | `propose_video(composition_id: str, x_caption: str, tiktok_caption: str, platforms: list[str], input_props: str | Any | None = None)` | | `request_sandbox` | `request_sandbox(services: list[str] | None = None, extensions: str | list[str] | None = None)` | -| `run_sandbox_tests` | `run_sandbox_tests(command: str, image: str | None = None, timeout_seconds: int | None = None)` | | `request_render` | `request_render(composition_id: str | None = None, orientation: str = 'vertical', frame_count: int = 8, input_props: str | Any | None = None)` | -| `preflight_diff` | `preflight_diff(task_id: UUID)` | -| `triage_failure` | `triage_failure(task_id: UUID, test_name: str, error_excerpt: str = '')` | | `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)` | diff --git a/agents/prompts/_generated/head_marketing.md b/agents/prompts/_generated/head_marketing.md index 8e4b9e9a4..85aa12688 100644 --- a/agents/prompts/_generated/head_marketing.md +++ b/agents/prompts/_generated/head_marketing.md @@ -21,7 +21,6 @@ | `notify` | `notify(target: str, text: str, priority: str = 'normal', task_id: UUID | None = None)` | | `evidence` | `evidence(task_id: UUID)` | | `nothing_to_propose` | `nothing_to_propose(task_id: UUID, reason: str)` | -| `task_time` | `task_time(task_id: UUID)` | | `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)` | diff --git a/agents/prompts/_generated/lifecycle-cell_pm.md b/agents/prompts/_generated/lifecycle-cell_pm.md index e2230c94a..f88d4f6f2 100644 --- a/agents/prompts/_generated/lifecycle-cell_pm.md +++ b/agents/prompts/_generated/lifecycle-cell_pm.md @@ -3,7 +3,6 @@ These are the only verbs the gateway will accept from you. Calling any other verb will be rejected with a Decision telling you the right one. -- **cancel_leaf**: Close a zero-diff leaf: a delegated child whose findings a merged sibling already fixed, so no legitimate diff remains and i_am_done/complete can never accept it. Refuses a target with any children of its own (not a leaf), any commit ahead of its base, or an open PR - those go through the normal review path, not this. `reason` is recorded as your journal:decision and on the task's audit trail. Cell PM: only your own coordination task's children. Main PM: any root's descendant. - **complete**: Cell PM merges the PR (leaf into the cell branch, or the gated cell→root PR into the root branch) + transitions to completed; Main PM escalates the root to the CEO (who merges root→master). The merge runs BEFORE the complete transition: TaskService.complete asserts the PR is already merged, so the choreographer verb body (cell_pm_complete / main_pm_complete) owns the merge-first ordering — no trailing pr_merge side_effect is declared here. - **declare_coverage**: Stamp parent acceptance criteria onto an existing child's parent_ac_refs after the fact — for a replacement child whose delegate omitted covers_parent_criteria. Or, targeting your OWN root/coordination task, declare criteria as root-owned (only your own machinery satisfies them — never push these into a cell). No status change; the verb body owns ownership + criterion validation. - **delegate**: Create a subtask under the current task. Validates the delegation chain (main_pm->cell_pm; cell_pm->its team's devs) and the assignee-vs-task_type rule (Cell PMs get planning-typed tasks; devs get code/research, UX devs also design). documentation is NOT delegatable — the lifecycle auto-creates the doc phase after the code subtask passes QA. diff --git a/agents/prompts/_generated/lifecycle-main_pm.md b/agents/prompts/_generated/lifecycle-main_pm.md index 4d8233d4a..256201e0e 100644 --- a/agents/prompts/_generated/lifecycle-main_pm.md +++ b/agents/prompts/_generated/lifecycle-main_pm.md @@ -3,7 +3,6 @@ These are the only verbs the gateway will accept from you. Calling any other verb will be rejected with a Decision telling you the right one. -- **cancel_leaf**: Close a zero-diff leaf: a delegated child whose findings a merged sibling already fixed, so no legitimate diff remains and i_am_done/complete can never accept it. Refuses a target with any children of its own (not a leaf), any commit ahead of its base, or an open PR - those go through the normal review path, not this. `reason` is recorded as your journal:decision and on the task's audit trail. Cell PM: only your own coordination task's children. Main PM: any root's descendant. - **complete**: Cell PM merges the PR (leaf into the cell branch, or the gated cell→root PR into the root branch) + transitions to completed; Main PM escalates the root to the CEO (who merges root→master). The merge runs BEFORE the complete transition: TaskService.complete asserts the PR is already merged, so the choreographer verb body (cell_pm_complete / main_pm_complete) owns the merge-first ordering — no trailing pr_merge side_effect is declared here. - **declare_coverage**: Stamp parent acceptance criteria onto an existing child's parent_ac_refs after the fact — for a replacement child whose delegate omitted covers_parent_criteria. Or, targeting your OWN root/coordination task, declare criteria as root-owned (only your own machinery satisfies them — never push these into a cell). No status change; the verb body owns ownership + criterion validation. - **delegate**: Create a subtask under the current task. Validates the delegation chain (main_pm->cell_pm; cell_pm->its team's devs) and the assignee-vs-task_type rule (Cell PMs get planning-typed tasks; devs get code/research, UX devs also design). documentation is NOT delegatable — the lifecycle auto-creates the doc phase after the code subtask passes QA. diff --git a/agents/prompts/_generated/main_pm.md b/agents/prompts/_generated/main_pm.md index ae06e4a98..1e93b940f 100644 --- a/agents/prompts/_generated/main_pm.md +++ b/agents/prompts/_generated/main_pm.md @@ -7,7 +7,6 @@ | Verb | Body schema | |------|-------------| -| `cancel_leaf` | `cancel_leaf(task_id: UUID, reason: str)` | | `complete` | `complete(task_id: UUID, notes: str)` | | `declare_coverage` | `declare_coverage(task_id: UUID, criteria: list[str])` | | `delegate` | `delegate(parent_task_id: UUID, title: str, description: str, assigned_to: str, team: str, task_type: str, nature: str, estimated_complexity: Complexity, acceptance_criteria: list[str], project_id: UUID | None = None, covers_parent_criteria: list[str] | None = None, intends_to_touch: list[str] | None = None, adds_migration: bool = False, touches_shared: bool = False, depends_on: list[UUID] | None = None)` | @@ -34,7 +33,6 @@ | `evidence` | `evidence(task_id: UUID)` | | `pr_update` | `pr_update(see do_server)` | | `draft_playbook` | `draft_playbook(title: str, problem: str, procedure: str, tags: list[str] = PydanticUndefined, source_task_id: UUID | None = None)` | -| `task_time` | `task_time(task_id: UUID)` | | `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)` | diff --git a/agents/prompts/_generated/pr_reviewer.md b/agents/prompts/_generated/pr_reviewer.md index 5bb211314..7bdae032f 100644 --- a/agents/prompts/_generated/pr_reviewer.md +++ b/agents/prompts/_generated/pr_reviewer.md @@ -26,4 +26,3 @@ | `read_a2a` | `read_a2a(see do_server)` | | `notify_list` | `notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20)` | | `notify_get` | `notify_get(notification_id: UUID)` | -| `task_time` | `task_time(task_id: UUID)` | diff --git a/agents/prompts/_generated/product_owner.md b/agents/prompts/_generated/product_owner.md index 11a09c207..bc0f086e0 100644 --- a/agents/prompts/_generated/product_owner.md +++ b/agents/prompts/_generated/product_owner.md @@ -21,7 +21,6 @@ | `notify` | `notify(target: str, text: str, priority: str = 'normal', task_id: UUID | None = None)` | | `evidence` | `evidence(task_id: UUID)` | | `nothing_to_propose` | `nothing_to_propose(task_id: UUID, reason: str)` | -| `task_time` | `task_time(task_id: UUID)` | | `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)` | diff --git a/agents/prompts/_generated/qa.md b/agents/prompts/_generated/qa.md index 3110368a0..e45f876cb 100644 --- a/agents/prompts/_generated/qa.md +++ b/agents/prompts/_generated/qa.md @@ -25,9 +25,7 @@ | `evidence` | `evidence(task_id: UUID)` | | `draft_playbook` | `draft_playbook(title: str, problem: str, procedure: str, tags: list[str] = PydanticUndefined, source_task_id: UUID | None = None)` | | `request_sandbox` | `request_sandbox(services: list[str] | None = None, extensions: str | list[str] | None = None)` | -| `run_sandbox_tests` | `run_sandbox_tests(command: str, image: str | None = None, timeout_seconds: int | None = None)` | | `request_render` | `request_render(composition_id: str | None = None, orientation: str = 'vertical', frame_count: int = 8, input_props: str | Any | None = None)` | -| `triage_failure` | `triage_failure(task_id: UUID, test_name: str, error_excerpt: str = '')` | | `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)` | diff --git a/agents/prompts/_generated/verbs.md b/agents/prompts/_generated/verbs.md index d7fc44563..c3870cb31 100644 --- a/agents/prompts/_generated/verbs.md +++ b/agents/prompts/_generated/verbs.md @@ -39,10 +39,7 @@ real tools live in their agent_sdk drivers, not role_config. | `draft_playbook` | `draft_playbook(title: str, problem: str, procedure: str, tags: list[str] = PydanticUndefined, source_task_id: UUID | None = None)` | | `propose_video` | `propose_video(composition_id: str, x_caption: str, tiktok_caption: str, platforms: list[str], input_props: str | Any | None = None)` | | `request_sandbox` | `request_sandbox(services: list[str] | None = None, extensions: str | list[str] | None = None)` | -| `run_sandbox_tests` | `run_sandbox_tests(command: str, image: str | None = None, timeout_seconds: int | None = None)` | | `request_render` | `request_render(composition_id: str | None = None, orientation: str = 'vertical', frame_count: int = 8, input_props: str | Any | None = None)` | -| `preflight_diff` | `preflight_diff(task_id: UUID)` | -| `triage_failure` | `triage_failure(task_id: UUID, test_name: str, error_excerpt: str = '')` | | `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)` | @@ -73,9 +70,7 @@ real tools live in their agent_sdk drivers, not role_config. | `evidence` | `evidence(task_id: UUID)` | | `draft_playbook` | `draft_playbook(title: str, problem: str, procedure: str, tags: list[str] = PydanticUndefined, source_task_id: UUID | None = None)` | | `request_sandbox` | `request_sandbox(services: list[str] | None = None, extensions: str | list[str] | None = None)` | -| `run_sandbox_tests` | `run_sandbox_tests(command: str, image: str | None = None, timeout_seconds: int | None = None)` | | `request_render` | `request_render(composition_id: str | None = None, orientation: str = 'vertical', frame_count: int = 8, input_props: str | Any | None = None)` | -| `triage_failure` | `triage_failure(task_id: UUID, test_name: str, error_excerpt: str = '')` | | `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)` | @@ -119,7 +114,6 @@ real tools live in their agent_sdk drivers, not role_config. | Verb | Body schema | |------|-------------| -| `cancel_leaf` | `cancel_leaf(task_id: UUID, reason: str)` | | `complete` | `complete(task_id: UUID, notes: str)` | | `declare_coverage` | `declare_coverage(task_id: UUID, criteria: list[str])` | | `delegate` | `delegate(parent_task_id: UUID, title: str, description: str, assigned_to: str, team: str, task_type: str, nature: str, estimated_complexity: Complexity, acceptance_criteria: list[str], project_id: UUID | None = None, covers_parent_criteria: list[str] | None = None, intends_to_touch: list[str] | None = None, adds_migration: bool = False, touches_shared: bool = False, depends_on: list[UUID] | None = None)` | @@ -145,7 +139,6 @@ real tools live in their agent_sdk drivers, not role_config. | `evidence` | `evidence(task_id: UUID)` | | `pr_update` | `pr_update(see do_server)` | | `draft_playbook` | `draft_playbook(title: str, problem: str, procedure: str, tags: list[str] = PydanticUndefined, source_task_id: UUID | None = None)` | -| `task_time` | `task_time(task_id: UUID)` | | `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)` | @@ -158,7 +151,6 @@ real tools live in their agent_sdk drivers, not role_config. | Verb | Body schema | |------|-------------| -| `cancel_leaf` | `cancel_leaf(task_id: UUID, reason: str)` | | `complete` | `complete(task_id: UUID, notes: str)` | | `declare_coverage` | `declare_coverage(task_id: UUID, criteria: list[str])` | | `delegate` | `delegate(parent_task_id: UUID, title: str, description: str, assigned_to: str, team: str, task_type: str, nature: str, estimated_complexity: Complexity, acceptance_criteria: list[str], project_id: UUID | None = None, covers_parent_criteria: list[str] | None = None, intends_to_touch: list[str] | None = None, adds_migration: bool = False, touches_shared: bool = False, depends_on: list[UUID] | None = None)` | @@ -185,7 +177,6 @@ real tools live in their agent_sdk drivers, not role_config. | `evidence` | `evidence(task_id: UUID)` | | `pr_update` | `pr_update(see do_server)` | | `draft_playbook` | `draft_playbook(title: str, problem: str, procedure: str, tags: list[str] = PydanticUndefined, source_task_id: UUID | None = None)` | -| `task_time` | `task_time(task_id: UUID)` | | `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)` | @@ -212,7 +203,6 @@ real tools live in their agent_sdk drivers, not role_config. | `notify` | `notify(target: str, text: str, priority: str = 'normal', task_id: UUID | None = None)` | | `evidence` | `evidence(task_id: UUID)` | | `nothing_to_propose` | `nothing_to_propose(task_id: UUID, reason: str)` | -| `task_time` | `task_time(task_id: UUID)` | | `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)` | @@ -244,7 +234,6 @@ real tools live in their agent_sdk drivers, not role_config. | `notify` | `notify(target: str, text: str, priority: str = 'normal', task_id: UUID | None = None)` | | `evidence` | `evidence(task_id: UUID)` | | `nothing_to_propose` | `nothing_to_propose(task_id: UUID, reason: str)` | -| `task_time` | `task_time(task_id: UUID)` | | `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)` | @@ -275,7 +264,6 @@ real tools live in their agent_sdk drivers, not role_config. | `evidence` | `evidence(task_id: UUID)` | | `dm` | `dm(recipient: str, text: str, task_id: UUID | None = None, skill: str | None = None)` | | `read_a2a` | `read_a2a(see do_server)` | -| `task_time` | `task_time(task_id: UUID)` | | `approve_playbook` | `approve_playbook(playbook_id: UUID)` | | `reject_playbook` | `reject_playbook(playbook_id: UUID, reason: str)` | | `archive_playbook` | `archive_playbook(playbook_id: UUID)` | @@ -312,38 +300,4 @@ real tools live in their agent_sdk drivers, not role_config. | `read_a2a` | `read_a2a(see do_server)` | | `notify_list` | `notify_list(unread_only: bool = True, pending_ack_only: bool = False, limit: int = 20)` | | `notify_get` | `notify_get(notification_id: UUID)` | -| `task_time` | `task_time(task_id: UUID)` | - -## devops - -### Flow verbs - -| Verb | Body schema | -|------|-------------| -| `claim_gate_review` | `claim_gate_review(task_id: UUID)` | -| `give_me_work` | `give_me_work()` | -| `i_am_done` | `i_am_done(task_id: UUID, notes: str = '', resolved_findings: list[ResolvedFindingInput] = PydanticUndefined)` | -| `i_am_idle` | `i_am_idle()` | -| `i_will_work_on` | `i_will_work_on(task_id: UUID, plan: str | None = None, steps: list[str | str] = PydanticUndefined, technical_considerations: list[str] = PydanticUndefined, risks: list[str | str] = PydanticUndefined, open_questions: list[str | str | bool] = PydanticUndefined)` | -| `open_pr` | `open_pr(task_id: UUID)` | -| `pr_fail` | `pr_fail(task_id: UUID, issues: list[str] = PydanticUndefined, findings: list[str | Any] = PydanticUndefined)` | -| `pr_pass` | `pr_pass(task_id: UUID, notes: str)` | -| `record_devops_review` | `record_devops_review(task_id: UUID, notes: str)` | -| `sync_branch` | `sync_branch(task_id: UUID, stash: bool = False)` | -| `unclaim` | `unclaim(task_id: UUID)` | - -### Content (do) tools - -| Tool | Body schema | -|------|-------------| -| `commit` | `commit(message: str, files: list[str] | None = None)` | -| `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, section: str | Any | None = None, done: str = '', next: str = '', where_to_look: list[str] | None = None)` | -| `evidence` | `evidence(task_id: UUID)` | -| `dm` | `dm(recipient: str, text: str, task_id: UUID | None = None, skill: str | None = None)` | -| `read_messages` | `read_messages()` | -| `read_a2a` | `read_a2a(see do_server)` | -| `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)` | -| `task_time` | `task_time(task_id: UUID)` | diff --git a/docs/rag/lifecycle/intent-verbs.md b/docs/rag/lifecycle/intent-verbs.md index ed72bfb19..d7558b979 100644 --- a/docs/rag/lifecycle/intent-verbs.md +++ b/docs/rag/lifecycle/intent-verbs.md @@ -1,16 +1,5 @@ # Intent Verbs (gateway-facing surface) -## cancel_leaf - -Close a zero-diff leaf: a delegated child whose findings a merged sibling already fixed, so no legitimate diff remains and i_am_done/complete can never accept it. Refuses a target with any children of its own (not a leaf), any commit ahead of its base, or an open PR - those go through the normal review path, not this. `reason` is recorded as your journal:decision and on the task's audit trail. Cell PM: only your own coordination task's children. Main PM: any root's descendant. - -**Allowed roles:** cell_pm, main_pm - -**Composes:** (no atomic actions) - -**Preconditions:** non_terminal - - ## claim_doc_task Claim awaiting_documentation. Returns evidence inline. @@ -24,7 +13,7 @@ Claim awaiting_documentation. Returns evidence inline. Claim an assembled-PR review task (awaiting_pr_review) WITHOUT transitioning it — mirrors QA's claim_review. The assembled diff and the parent task's acceptance criteria are returned inline. -**Allowed roles:** devops, pr_reviewer +**Allowed roles:** pr_reviewer **Composes:** (no atomic actions) @@ -109,7 +98,7 @@ Fail QA with concrete issues. Transitions to needs_revision. Return your most-actionable task or signal idle. -**Allowed roles:** cell_pm, developer, devops, documenter, main_pm, pr_reviewer, qa +**Allowed roles:** cell_pm, developer, documenter, main_pm, pr_reviewer, qa **Composes:** (no atomic actions) @@ -127,7 +116,7 @@ Escalate to PM. Logs a struggle journal entry. Submit work for QA. Auto-runs in_progress->verifying then verifying->awaiting_qa. Strict - PR must be open (call open_pr first) and >=1 commit. -**Allowed roles:** developer, devops +**Allowed roles:** developer **Composes:** submit_verification → submit_qa @@ -138,7 +127,7 @@ Submit work for QA. Auto-runs in_progress->verifying then verifying->awaiting_qa Signal you have no active work. PMs auto-pause owned in_progress tasks. -**Allowed roles:** auditor, cell_pm, developer, devops, documenter, head_marketing, main_pm, pr_reviewer, product_owner, prompter, qa, secretary +**Allowed roles:** auditor, cell_pm, developer, documenter, head_marketing, main_pm, pr_reviewer, product_owner, prompter, qa, secretary **Composes:** (no atomic actions) @@ -158,7 +147,7 @@ PM mirror of i_will_work_on for parent tasks. Claim, plan, transition to in_prog **Allowed roles:** cell_pm, main_pm -**Composes:** claim → set_plan +**Composes:** claim → set_plan → start **Preconditions:** plan @@ -167,9 +156,9 @@ PM mirror of i_will_work_on for parent tasks. Claim, plan, transition to in_prog Claim a task, set the plan, and transition to in_progress. Atomic - preconditions checked before any state mutation. -**Allowed roles:** developer, devops +**Allowed roles:** developer -**Composes:** claim → set_plan +**Composes:** claim → set_plan → start **Preconditions:** plan @@ -178,7 +167,7 @@ Claim a task, set the plan, and transition to in_progress. Atomic - precondition Push the branch and open a PR. Atomic - preconditions (assignee, >=1 commit, no prior PR) checked BEFORE any git operation. After success, call i_am_done. -**Allowed roles:** developer, devops +**Allowed roles:** developer **Composes:** (no atomic actions) @@ -209,7 +198,7 @@ Post one complete change-request to the external PR and finish the review task. Fail the assembled-PR review with concrete issues. Transitions awaiting_pr_review -> needs_revision, routed back like a QA fail. -**Allowed roles:** devops, pr_reviewer +**Allowed roles:** pr_reviewer **Composes:** pr_fail @@ -218,7 +207,7 @@ Fail the assembled-PR review with concrete issues. Transitions awaiting_pr_revie Pass the assembled-PR review. Transitions awaiting_pr_review -> awaiting_pm_review so the PM can merge. -**Allowed roles:** devops, pr_reviewer +**Allowed roles:** pr_reviewer **Composes:** pr_pass @@ -232,15 +221,6 @@ Hand a claimed/in_progress task to another developer in your own cell. The branc **Composes:** (no atomic actions) -## record_devops_review - -Record the DevOps infra-review verdict (pass) on an assembled-PR gate task WITHOUT transitioning it: the primary reviewer's pr_pass then composes. Use pr_fail instead to reject the PR. - -**Allowed roles:** devops - -**Composes:** (no atomic actions) - - ## request_changes Reject the merge review with concrete issues. Transitions awaiting_pm_review -> needs_revision, routed back like a QA fail (original developer for a leaf, revision PM for an assembled task). Use this for an AC/scope violation caught at merge review — never i_am_blocked/escalate, which have no revision routing. @@ -287,7 +267,7 @@ Cell PM opens the cell→root PR and moves the cell task into the PR-review gate Rebase your task's branch onto its current base THROUGH the gate (raw git is denied). Use when your branch has fallen behind its base — e.g. a sibling task's PR merged into the parent branch while you worked. Fetches origin, rebases head onto base, and force-pushes (with-lease). No DB state change. On conflicts the rebase is aborted and the conflicted files are returned — resolve by hand, commit, then sync_branch again. Pass stash=True to auto-stash uncommitted changes instead of refusing DIRTY_WORKSPACE; they are restored after the rebase. -**Allowed roles:** developer, devops +**Allowed roles:** developer **Composes:** (no atomic actions) @@ -325,7 +305,7 @@ PM unblocks a blocked task; restores pre-block state. Voluntarily release a claim back to pending. The work-in-progress branch is preserved. A PR reviewer who claimed an external review (in_progress) or a gate review (awaiting_pr_review) and cannot finish releases the claim here rather than wedging the lane until the stale-claim reaper. -**Allowed roles:** cell_pm, developer, devops, documenter, main_pm, pr_reviewer, qa +**Allowed roles:** cell_pm, developer, documenter, main_pm, pr_reviewer, qa **Composes:** (no atomic actions) diff --git a/docs/rag/lifecycle/status-transitions.md b/docs/rag/lifecycle/status-transitions.md index 766bdba3d..d7f39fdf1 100644 --- a/docs/rag/lifecycle/status-transitions.md +++ b/docs/rag/lifecycle/status-transitions.md @@ -13,10 +13,10 @@ | awaiting_pm_review | cancelled | cancel | cell_pm, ceo, main_pm | | awaiting_pm_review | completed | complete | cell_pm, main_pm | | awaiting_pm_review | needs_revision | request_changes | cell_pm, main_pm | -| awaiting_pr_review | awaiting_pm_review | pr_pass | devops, pr_reviewer | +| awaiting_pr_review | awaiting_pm_review | pr_pass | pr_reviewer | | awaiting_pr_review | cancelled | cancel | cell_pm, ceo, main_pm | -| awaiting_pr_review | claimed | claim | devops, pr_reviewer | -| awaiting_pr_review | needs_revision | pr_fail | devops, pr_reviewer | +| awaiting_pr_review | claimed | claim | pr_reviewer | +| awaiting_pr_review | needs_revision | pr_fail | pr_reviewer | | awaiting_qa | awaiting_documentation | qa_pass | qa | | awaiting_qa | cancelled | cancel | cell_pm, ceo, main_pm | | awaiting_qa | claimed | claim | qa | diff --git a/panel/lib/lifecycle.json b/panel/lib/lifecycle.json index e6d93f12f..c673a92ee 100644 --- a/panel/lib/lifecycle.json +++ b/panel/lib/lifecycle.json @@ -10,11 +10,6 @@ "needs_revision", "pending" ], - "devops": [ - "awaiting_pr_review", - "needs_revision", - "pending" - ], "documenter": [ "awaiting_documentation", "pending" @@ -34,17 +29,6 @@ ] }, "intents": [ - { - "allowed_roles": [ - "cell_pm", - "main_pm" - ], - "composes": [], - "description": "Close a zero-diff leaf: a delegated child whose findings a merged sibling already fixed, so no legitimate diff remains and i_am_done/complete can never accept it. Refuses a target with any children of its own (not a leaf), any commit ahead of its base, or an open PR - those go through the normal review path, not this. `reason` is recorded as your journal:decision and on the task's audit trail. Cell PM: only your own coordination task's children. Main PM: any root's descendant.", - "name": "cancel_leaf", - "pre_side_effects": [], - "side_effects": [] - }, { "allowed_roles": [ "documenter" @@ -57,7 +41,6 @@ }, { "allowed_roles": [ - "devops", "pr_reviewer" ], "composes": [], @@ -167,7 +150,6 @@ "allowed_roles": [ "cell_pm", "developer", - "devops", "documenter", "main_pm", "pr_reviewer", @@ -195,8 +177,7 @@ }, { "allowed_roles": [ - "developer", - "devops" + "developer" ], "composes": [ "submit_verification", @@ -212,7 +193,6 @@ "auditor", "cell_pm", "developer", - "devops", "documenter", "head_marketing", "main_pm", @@ -247,7 +227,8 @@ ], "composes": [ "claim", - "set_plan" + "set_plan", + "start" ], "description": "PM mirror of i_will_work_on for parent tasks. Claim, plan, transition to in_progress; from there delegate subtasks.", "name": "i_will_plan", @@ -256,12 +237,12 @@ }, { "allowed_roles": [ - "developer", - "devops" + "developer" ], "composes": [ "claim", - "set_plan" + "set_plan", + "start" ], "description": "Claim a task, set the plan, and transition to in_progress. Atomic - preconditions checked before any state mutation.", "name": "i_will_work_on", @@ -270,8 +251,7 @@ }, { "allowed_roles": [ - "developer", - "devops" + "developer" ], "composes": [], "description": "Push the branch and open a PR. Atomic - preconditions (assignee, >=1 commit, no prior PR) checked BEFORE any git operation. After success, call i_am_done.", @@ -308,7 +288,6 @@ }, { "allowed_roles": [ - "devops", "pr_reviewer" ], "composes": [ @@ -321,7 +300,6 @@ }, { "allowed_roles": [ - "devops", "pr_reviewer" ], "composes": [ @@ -342,16 +320,6 @@ "pre_side_effects": [], "side_effects": [] }, - { - "allowed_roles": [ - "devops" - ], - "composes": [], - "description": "Record the DevOps infra-review verdict (pass) on an assembled-PR gate task WITHOUT transitioning it: the primary reviewer's pr_pass then composes. Use pr_fail instead to reject the PR.", - "name": "record_devops_review", - "pre_side_effects": [], - "side_effects": [] - }, { "allowed_roles": [ "cell_pm", @@ -411,8 +379,7 @@ }, { "allowed_roles": [ - "developer", - "devops" + "developer" ], "composes": [], "description": "Rebase your task's branch onto its current base THROUGH the gate (raw git is denied). Use when your branch has fallen behind its base \u2014 e.g. a sibling task's PR merged into the parent branch while you worked. Fetches origin, rebases head onto base, and force-pushes (with-lease). No DB state change. On conflicts the rebase is aborted and the conflicted files are returned \u2014 resolve by hand, commit, then sync_branch again. Pass stash=True to auto-stash uncommitted changes instead of refusing DIRTY_WORKSPACE; they are restored after the rebase.", @@ -461,7 +428,6 @@ "allowed_roles": [ "cell_pm", "developer", - "devops", "documenter", "main_pm", "pr_reviewer", @@ -584,7 +550,6 @@ { "action": "pr_pass", "roles": [ - "devops", "pr_reviewer" ], "source": "awaiting_pr_review", @@ -603,7 +568,6 @@ { "action": "claim", "roles": [ - "devops", "pr_reviewer" ], "source": "awaiting_pr_review", @@ -612,7 +576,6 @@ { "action": "pr_fail", "roles": [ - "devops", "pr_reviewer" ], "source": "awaiting_pr_review", diff --git a/panel/src/app/(dashboard)/layout.tsx b/panel/src/app/(dashboard)/layout.tsx index 3f0765489..83e73123d 100644 --- a/panel/src/app/(dashboard)/layout.tsx +++ b/panel/src/app/(dashboard)/layout.tsx @@ -6,6 +6,7 @@ import { CommandPalette } from "@/components/layout/command-palette"; import { ScrollRestoration } from "@/components/scroll-restoration"; import { ScrollJumpButtons } from "@/components/scroll-jump-buttons"; import { RateLimitBanner } from "@/components/rate-limit/rate-limit-banner"; +import { ReviewPathOutageBanner } from "@/components/health/review-path-outage-banner"; import { MaintenanceBanner } from "@/components/maintenance/maintenance-banner"; import { AutoRefreshDriver } from "@/components/providers/auto-refresh-driver"; @@ -24,6 +25,7 @@ export default function DashboardLayout({
+ {/* pb-20 clears the fixed BottomTabBar on mobile; md+ has no bar. */}
diff --git a/panel/src/components/health/__tests__/review-path-outage-banner.test.tsx b/panel/src/components/health/__tests__/review-path-outage-banner.test.tsx new file mode 100644 index 000000000..73234eb5b --- /dev/null +++ b/panel/src/components/health/__tests__/review-path-outage-banner.test.tsx @@ -0,0 +1,80 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { ReviewPathOutageBanner } from "../review-path-outage-banner"; +import type { ReviewPathOutageStatus } from "@/lib/api/system-health"; + +// The backend health surface ships in parallel — the contract is mocked +// here, per the task brief. +const useReviewPathOutage = vi.fn< + () => ReviewPathOutageStatus | null | undefined +>(); +vi.mock("@/hooks/use-review-path-outage", () => ({ + useReviewPathOutage: () => useReviewPathOutage(), +})); + +function activeOutage(): ReviewPathOutageStatus { + return { + active: true, + outage_type: "review_path_tools", + window_start: "2026-09-05T09:00:00Z", + recovered: false, + }; +} + +describe("ReviewPathOutageBanner", () => { + beforeEach(() => { + useReviewPathOutage.mockReset(); + }); + + it("renders the outage banner while active", () => { + useReviewPathOutage.mockReturnValue(activeOutage()); + render(); + const banner = screen.getByRole("alert"); + expect(banner).toHaveTextContent("Review-path tools outage"); + expect(banner).toHaveTextContent("since"); + }); + + it("renders nothing when there is no outage", () => { + useReviewPathOutage.mockReturnValue({ + active: false, + outage_type: "review_path_tools", + window_start: null, + recovered: true, + }); + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); + + it("stays quiet while the backend surface has not landed (null)", () => { + useReviewPathOutage.mockReturnValue(null); + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); + + it("stays quiet while the first poll is in flight (undefined)", () => { + useReviewPathOutage.mockReturnValue(undefined); + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); + + it("disappears when recovery arrives", () => { + useReviewPathOutage.mockReturnValue(activeOutage()); + const { rerender, container } = render(); + expect(screen.getByRole("alert")).toBeInTheDocument(); + + useReviewPathOutage.mockReturnValue({ + active: false, + outage_type: "review_path_tools", + window_start: "2026-09-05T09:00:00Z", + recovered: true, + }); + rerender(); + expect(container).toBeEmptyDOMElement(); + }); + + it("is read-only: it offers no action buttons", () => { + useReviewPathOutage.mockReturnValue(activeOutage()); + render(); + expect(screen.queryByRole("button")).not.toBeInTheDocument(); + }); +}); diff --git a/panel/src/components/health/review-path-outage-banner.tsx b/panel/src/components/health/review-path-outage-banner.tsx new file mode 100644 index 000000000..edce871e7 --- /dev/null +++ b/panel/src/components/health/review-path-outage-banner.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { TriangleAlert } from "lucide-react"; +import { formatAbsoluteTimestamp } from "@/lib/utils"; +import { useReviewPathOutage } from "@/hooks/use-review-path-outage"; + +/** + * Read-only banner for an active review-path tool outage (evidence / + * git-readonly reads failing across tasks). Rendered while the backend + * reports the outage active; it disappears on recovery. Surfacing only — + * remediation is a PM/CEO decision, so there are deliberately no actions + * here. Quiet when the backend health surface hasn't landed yet. + */ +export function ReviewPathOutageBanner() { + const outage = useReviewPathOutage(); + + if (!outage?.active) { + return null; + } + + return ( +
+
+ + + Review-path tools outage + + {outage.window_start && ( + + since {formatAbsoluteTimestamp(outage.window_start)} + + )} + + evidence and git reads may fail — reviews resume automatically on + recovery + +
+
+ ); +} diff --git a/panel/src/hooks/use-review-path-outage.ts b/panel/src/hooks/use-review-path-outage.ts new file mode 100644 index 000000000..9bfce4df3 --- /dev/null +++ b/panel/src/hooks/use-review-path-outage.ts @@ -0,0 +1,29 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; +import { + systemHealthApi, + type ReviewPathOutageStatus, +} from "@/lib/api/system-health"; + +export const systemHealthKeys = { + all: ["system-health"] as const, + reviewPathOutage: () => [...systemHealthKeys.all, "review-path-outage"] as const, +}; + +/** + * Polls GET /api/system-health/review-path every 30s — the same cadence the + * maintenance-pause and rate-limit banners already use. An active review-path + * outage is unmissable, so the banner rides a background poll rather than a + * bootstrap payload: it self-clears on recovery without a reload. Polling is + * also the delivery choice that survives the backend slice landing later — + * until then the query resolves to null (no surface yet) and nothing renders. + */ +export function useReviewPathOutage(): ReviewPathOutageStatus | null | undefined { + const query = useQuery({ + queryKey: systemHealthKeys.reviewPathOutage(), + queryFn: () => systemHealthApi.getReviewPathOutage(), + refetchInterval: 30000, + }); + return query.data; +} diff --git a/panel/src/lib/api/__tests__/system-health.test.ts b/panel/src/lib/api/__tests__/system-health.test.ts new file mode 100644 index 000000000..d5b41bcac --- /dev/null +++ b/panel/src/lib/api/__tests__/system-health.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { systemHealthApi } from "../system-health"; + +// The real api client would hit the network; the backend slice may not be +// merged, so axios is mocked and the 404 → null degradation is pinned here. +vi.mock("../client", () => ({ + default: Object.assign(vi.fn(), { + isAxiosError: (e: unknown) => + !!e && typeof e === "object" && "isAxiosError" in e, + get: vi.fn(), + }), +})); + +import api from "../client"; + +const mockedGet = api.get as unknown as ReturnType; + +afterEach(() => { + mockedGet.mockReset(); +}); + +describe("systemHealthApi.getReviewPathOutage", () => { + it("returns the outage status from the health surface", async () => { + const status = { + active: true, + outage_type: "review_path_tools", + window_start: "2026-09-05T09:00:00Z", + recovered: false, + }; + mockedGet.mockResolvedValue({ data: status }); + await expect(systemHealthApi.getReviewPathOutage()).resolves.toEqual(status); + expect(mockedGet).toHaveBeenCalledWith("/system-health/review-path"); + }); + + it("degrades to null on a 404 (backend surface not merged yet)", async () => { + mockedGet.mockRejectedValue({ + isAxiosError: true, + response: { status: 404 }, + }); + await expect(systemHealthApi.getReviewPathOutage()).resolves.toBeNull(); + }); + + it("rethrows non-404 errors", async () => { + mockedGet.mockRejectedValue({ isAxiosError: true, response: { status: 500 } }); + await expect(systemHealthApi.getReviewPathOutage()).rejects.toEqual({ + isAxiosError: true, + response: { status: 500 }, + }); + }); +}); diff --git a/panel/src/lib/api/system-health.ts b/panel/src/lib/api/system-health.ts new file mode 100644 index 000000000..e05ee93d5 --- /dev/null +++ b/panel/src/lib/api/system-health.ts @@ -0,0 +1,51 @@ +import axios from "axios"; + +import api from "./client"; + +// ============================================================================= +// System health: review-path tool outage (frontend half of the frozen +// cross-cell contract; the backend cell owns the detection/health event and +// the exact response schema). +// +// GET /api/system-health/review-path +// -> { active, outage_type, window_start, recovered } +// +// Frozen contract fields: outage active flag, outage type +// ("review_path_tools"), window start timestamp, recovered state. Field +// names beyond these are the cells' shared choice; deviations are +// re-synced via main-pm and land here first. +// +// Read-only on the panel: this surfaces the outage, it never acts on it. +// ============================================================================= + +export type OutageType = "review_path_tools"; + +export interface ReviewPathOutageStatus { + /** True while the cross-task tool outage is ongoing. */ + active: boolean; + /** Which tool path is degraded; currently always "review_path_tools". */ + outage_type: OutageType; + /** ISO 8601 start of the outage window; null when no outage. */ + window_start: string | null; + /** True once reads recovered and the backend cleared the event. */ + recovered: boolean; +} + +export const systemHealthApi = { + // Returns null when the surface is not there yet (backend slice not + // merged, or a proxy strips the route) so callers degrade to "no banner" + // instead of erroring — the outage banner must never become its own + // outage. + getReviewPathOutage: async (): Promise => { + try { + const { data } = + await api.get("/system-health/review-path"); + return data; + } catch (error) { + if (axios.isAxiosError(error) && error.response?.status === 404) { + return null; + } + throw error; + } + }, +};