docs(rag): close the corpus drift — findings/collision/fast-path/forge/env-ladder coverage (#596)

Seventeen-page sweep of the agent-facing KB against shipped behavior:
required covers_parent_criteria and per-AC criteria_verified reach the
QA/PM/task-tools pages (the QA docs also named non-callable pass_review/
fail_review — the MCP tools are pass/fail); collision_context lands in the
QA/gate/planning evidence docs; the possibilities matrix gets its own
architecture page + config entry; the auditor page gains its missing
waive_finding and playbook-curation verbs; git-pr-types.md is rewritten
off the long-dead is_root_pr model; PR/workspace/git-error pages stop
assuming GitHub (forge-agnostic + env-ladder semantics).

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-19 19:00:48 +02:00
committed by GitHub
co-authored by Renn F
parent 700dbcd285
commit 862c0b27cd
17 changed files with 217 additions and 47 deletions
+32 -8
View File
@@ -1,20 +1,40 @@
# Git PR Types
| `is_root_pr` | Target | Reviewer / Merger | Content |
|--------------|--------|-------------------|---------|
| `True` | `master` | CEO approves; Main PM opens + merges | Full task tree, all commits, all agent links |
| `False` | parent branch | Cell PM merges | Task commits only, scoped to the cell |
There is no `is_root_pr` field or shortcut — every assembled PR now passes through the in-path **PR-review gate** (`awaiting_pr_review`) before a PM merges it. This page is the quick-reference; `docs/rag/lifecycle/status-transitions.md` and `docs/rag/architecture/review-findings.md` cover the mechanics in depth.
## How PRs Are Created
## The three PR kinds
| Kind | Opened by | Target | Gate reviewer | Merged by |
|------|-----------|--------|----------------|-----------|
| Leaf PR | Developer's `open_pr(task_id)` | The parent (cell) task's branch | none — QA reviews the diff directly, no PR-gate | Cell PM's `complete(task_id, notes)` |
| Cell→root PR | Cell PM's `submit_up(task_id, notes)` | The root task's branch | The cell's PR reviewer (be/fe/ux-pr-reviewer) via `pr_pass`/`pr_fail` | Cell PM's `complete(task_id, notes)`, after `pr_pass` |
| Root→master PR | Main PM's `submit_root(task_id, notes)` | The project's env-ladder **head rung** (`roboco.models.env_branches.head_branch`, typically `master` — never a literal string, always read through the shim) | The main PR reviewer (pr-reviewer-1) via `pr_pass`/`pr_fail` | The CEO, from the panel, after Main PM's `complete` escalates to `awaiting_ceo_approval` |
A leaf dev task and a branchless coordination root (product fan-out, MegaTask umbrella) skip the PR-review gate entirely — there's no assembled PR for a reviewer to gate.
## How PRs are created
There is **no** `roboco_git_create_pr` MCP tool. PRs are side-effects of lifecycle transitions, driven by the choreographer:
- **Leaf PR (cell-scoped, `is_root_pr=False`)**: Opened automatically when the assigned developer calls `open_pr(task_id)` after their `commit(...)` calls. Merged when the Cell PM calls `complete(task_id, notes)` after QA + docs sign off.
- **Master PR (`is_root_pr=True`)**: Opened by the choreographer when the **Main PM** calls `complete(task_id, notes)` on the root parent task. Merged by the CEO via the dashboard once all cell-scoped PRs have been merged into it.
- **Leaf PR**: opened automatically when the assigned developer calls `open_pr(task_id)` after their `commit(...)` calls (`verifying -> awaiting_qa`).
- **Cell→root PR**: opened by `submit_up(task_id, notes)` — enters `awaiting_pr_review`.
- **Root→master PR**: opened by `submit_root(task_id, notes)` — enters `awaiting_pr_review`. Targets the project's **head rung**, not literal `master` — a project with no declared environment ladder resolves this from `projects.default_branch` via the read-time shim, so nothing changes for a project that hasn't opted into a multi-rung ladder. See `CLAUDE.md` "Env-branches ladder".
Title and body are generated from the task templates in `roboco/templates/git/pr_*.py`. Don't hand-write PR descriptions in the agent prompts — they'll be overridden.
## The PR-review gate (assembled PRs only)
`submit_up` and `submit_root` land the task on `awaiting_pr_review`, not directly on `awaiting_pm_review`. A reviewer must `claim_gate_review(task_id)` then verdict:
- `pr_pass(task_id, notes)` -> `awaiting_pm_review`, the PM merges via `complete`.
- `pr_fail(task_id, findings=[...])` -> `needs_revision`, routed back to the PM that submitted it, same as a QA fail.
`pr_pass` additionally refuses while the PR's own CI is red or unresolvable; a repo with no CI configured passes through cleanly. On non-GitHub forges the verdict is posted differently: GitHub and Gitea both support a real "request changes" review, but GitLab has no such primitive, so a `pr_fail` verdict on a GitLab-backed project posts as a plain MR note rather than a blocking review — the task still transitions to `needs_revision` normally regardless of forge. See `docs/rag/roles/pr-reviewer.md`.
## PR labels
Every fleet-opened PR is best-effort labeled with the org-structure vocabulary (`derive_pr_labels`, `roboco/foundation/policy/pr_labels.py`): `to master` (today, only the root→master PR) vs `to slave`, `root` for an assembled root PR, `MegaTask` for a batch-carrying task, and a layer label (`main-pm` / `cell/{team}` / `subtask/{team}`) — so a human triaging the PR queue on the forge sees which tree and org layer a PR belongs to at a glance.
## Auto-Checkout
Branches and checkout are handled automatically:
@@ -22,3 +42,7 @@ Branches and checkout are handled automatically:
- `i_will_work_on(task_id)` (devs) creates the task's branch and checks it out in the agent's workspace.
- `i_will_plan(task_id, plan)` (PMs) does the same for parent tasks.
- Workspace dirty? The verb returns an error envelope; clean up first with `commit(...)` or escalate via `i_am_blocked(task_id, reason)`.
## Forge-agnostic
None of the above changes shape by forge — GitHub, Gitea, and GitLab (`projects.git_provider`) all route through the same `submit_up`/`submit_root`/`pr_pass`/`pr_fail` verbs and the same task states. Don't assume a PR lives at a `github.com` URL; `pr_url` always carries the real forge URL for whichever provider the project is registered against.
+8 -4
View File
@@ -4,7 +4,7 @@
PRs are opened **before** QA review, not during `awaiting_documentation`. The choreographer creates the PR as a side-effect of the developer's `open_pr(task_id)` transition (`verifying → awaiting_qa`).
This is by design: QA reviews the real PR diff on GitHub, and the downstream PM/CEO approval chain operates on a PR that already exists.
This is by design: QA reviews the real PR diff on the project's forge, and the downstream PM/CEO approval chain operates on a PR that already exists.
You do **not** call any tool to create a PR. There is no `roboco_git_create_pr` MCP tool.
@@ -64,9 +64,13 @@ There is no `roboco_git_merge_pr` MCP tool.
## Prerequisites
- **Git token:** the project must have an encrypted GitHub PAT set on `projects.git_token_encrypted`. Without it, the workspace clone — and therefore everything downstream — fails with `WorkspaceError`.
- **Token scope:** `repo` (for branch push, PR create, PR merge).
- **Merge target:** the root→master PR targets the project's env-ladder **head** rung (`roboco.models.env_branches.head_branch`, typically `master`) — a project with no declared environment ladder resolves this straight from `projects.default_branch` via the read-time shim, so this is unchanged for every project that hasn't opted into a multi-rung ladder.
- **Git token:** the project must have an encrypted token set on `projects.git_token_encrypted`. Without it, the workspace clone — and therefore everything downstream — fails with `WorkspaceError`. The field is historically named for GitHub PATs but works for any forge a project is registered against (GitHub, Gitea, GitLab — `projects.git_provider`); see "Forge-agnostic git" below.
- **Token scope:** `repo` on GitHub/Gitea; an equivalent `api`/`write_repository` scope on GitLab (for branch push, PR/MR create, PR/MR merge).
- **Merge target:** every dev/cell/root PR — never just the root→master one — targets the project's env-ladder **head** rung (`roboco.models.env_branches.head_branch`, typically `master`) — a project with no declared environment ladder resolves this straight from `projects.default_branch` via the read-time shim, so this is unchanged for every project that hasn't opted into a multi-rung ladder. A middle ladder rung (e.g. `qa`/`stag`) is never a PR target for dev/cell/root work — the only thing that ever lands a PR on a middle or prod rung is the `EnvSyncEngine` cascade (see `CLAUDE.md` "Env-branches ladder"), which is a platform-authored sync, not something you open. `sync_branch` and `submit_up` resolve their target from the task's own parent hierarchy (`resolve_parent_branch`), not the ladder directly — the ladder only surfaces at the two edge cases where a task has no branched ancestor: a project-root task's branch cut, and `submit_root`'s PR target.
## Forge-agnostic git
The PR/CI/review surface is provider-routed (`roboco/services/forge/`) — GitHub, Gitea, and GitLab are all supported per-project (`projects.git_provider`), and `GitService` never branches on which one a project uses. From your side, `pr_number` and `pr_url` are always real values regardless of forge — `pr_url` is the actual forge URL (a Gitea/GitLab instance host, never assumed to be `github.com`). One real asymmetry: GitLab has no "request changes" review primitive, so a `pr_fail`/change-request verdict on a GitLab-backed project posts as a plain MR note rather than a blocking review — the task still moves to `needs_revision` normally either way, only the PR-visible signal differs. Don't hardcode `github.com` in any URL you construct or reason about.
## Troubleshooting
+9
View File
@@ -59,9 +59,16 @@ pass(
"pytest 1635 passed; ruff and mypy clean. "
"PR #123."
),
criteria_verified=[
{"criterion": "429 on the 101st request in the window", "evidence": "test_rate_limit_boundary passes at rate_limit.py:88"},
{"criterion": "Redis key TTL matches the configured window", "evidence": "test_ttl_matches_window asserts TTL=60"},
{"criterion": "Tests cover happy path + boundary", "evidence": "3 new tests in test_rate_limit.py, all pass"},
],
)
```
`criteria_verified` is required whenever the task has acceptance criteria — one entry per criterion, matched by AC id or exact text, `evidence` non-empty and capped at 500 chars. Missing a criterion, or naming one the task doesn't have, is rejected with the still-unverified criteria listed. Each entry renders deterministically into `qa_notes` as `[AC] <criterion> — verified: <evidence>`, so a gestalt "looks good" pass with no per-criterion trace is structurally impossible. A zero-AC task imposes no requirement.
Result:
- Task advances to `awaiting_documentation`
@@ -104,6 +111,8 @@ Result:
If the task has failed before, `claim_review` returns `prior_findings` — the FULL ledger, every round, newest first — alongside the usual PR diff. Check each prior finding against the current diff one at a time before deciding: a finding still unaddressed is a fail, not a pass with a note. Passing (`pass`) bulk-verifies every `addressed` QA-origin finding in the same transaction — that verification IS the confirmation the fix landed.
`claim_review` also returns `collision_context` whenever this task has same-parent siblings that collide with it (overlapping declared file globs, or both adding a migration) — worth a glance before you pass, since an overlap you don't expect can explain an otherwise-mysterious diff hunk.
## Reflect (recommended)
After pass or fail, journal the review for future QA agents to learn from:
+8 -4
View File
@@ -47,7 +47,9 @@ delegate(
## Acceptance-Criteria Coverage
When you decompose a parent task, declare which parent criteria each subtask is responsible for with **`covers_parent_criteria`** (a list of the parent's `acceptance_criteria_ids`). This is what lets the org prove a decomposition covers the parent's full intent — and it drives two gates and your coverage briefing.
When you decompose a parent task, declare which parent criteria each subtask is responsible for with **`covers_parent_criteria`** (a list of the parent's `acceptance_criteria_ids`, or the criterion's exact text). This is what lets the org prove a decomposition covers the parent's full intent — and it drives two gates and your coverage briefing.
**`covers_parent_criteria` is required, not optional, whenever the parent has any acceptance criteria.** `delegate` refuses a child with no `covers_parent_criteria` declared — "'<title>' declares no covers_parent_criteria, but the parent has acceptance criteria to decompose" — and a ref that resolves to neither an AC id nor exact text is rejected too, naming the valid criteria so you don't have to guess. It's only optional when the parent itself carries zero acceptance criteria. You don't have to cover everything in one `delegate` call — a wave may deliberately leave criteria for a later delegate — but every subtask you DO create must name what it covers.
After `i_will_plan` and after each `delegate`, your envelope carries a coverage view of the parent so you can see what is still unmapped:
@@ -59,7 +61,9 @@ Two gates build on the coverage link:
- **Decomposition floor** — you cannot go `i_am_idle` on a parent while a criterion is still unclaimed. Delegate (or `reassign`) subtasks until every criterion is covered.
- **Roll-up gate** — a parent cannot `complete`, `submit_up`, or `escalate_to_ceo` unless every criterion traces to a child that **passed QA** on it.
Both gates are **safe-by-construction**: they stay inert until you start declaring `covers_parent_criteria`, so a decomposition that never declares coverage is never blocked. Declaring coverage is how you opt your parent into the guarantee.
Since `delegate` now requires `covers_parent_criteria` on every child of a parent with acceptance criteria, both gates are live from the first subtask on for any such parent — there's no longer a way to decompose without opting in. A parent with zero acceptance criteria is exempt from both, since there's nothing to trace coverage to.
The `verified` half of `parent_ac_coverage` isn't automatic — QA's `pass_review` (called via the `pass` tool) requires its own `criteria_verified` on the SUBTASK's own acceptance criteria before it can move a child to `awaiting_documentation`. Two distinct requirements on the same coverage chain: `covers_parent_criteria` (down, at delegate time — this subtask maps to those parent criteria) and `criteria_verified` (up, at QA pass time — each of THIS task's own criteria has concrete evidence). See `docs/rag/roles/qa.md` for the QA-side requirement.
## Delegating Code Work: Per-Dev Queues
@@ -89,8 +93,8 @@ The task hierarchy is capped at `MAX_TASK_DEPTH = 4` levels (depths 03). The
All code tasks follow the git workflow:
- **Branches are auto-created when a developer claims the task** via `i_will_work_on` — no manual branch creation
- Root tasks: branch created from the default branch (main/master)
- Subtasks: branch forked from the parent's branch
- Root tasks: branch created from the project's env-ladder **head rung** (typically `master`/`main`) — a project with no declared environment ladder resolves this from `projects.default_branch` via the read-time shim, so nothing changes for a project that hasn't opted into a multi-rung ladder
- Subtasks: branch forked from the parent's branch (unaffected by the env ladder — this is pure task-hierarchy resolution)
Coordination/parent tasks that only plan and delegate (no code) do not need a branch of their own.