Files
mcpctl/docs/epics.md

191 lines
7.2 KiB
Markdown
Raw Normal View History

docs(agentic-teams): document the new resources, and make the webhook reachable Five resources shipped with no documentation and one documented lie: docs/inference-tasks.md still told people `mcpctl get tasks` lists the inference queue. Work items own that name now. Adds docs/epics.md (the Epic -> EpicTask -> Task hierarchy, the state machine, risk-scaled review gates, the accept-merges gate, and reconciliation), docs/gitproviders.md (forge registration, discovery/import, merge-on-accept, post-merge reopen) and docs/agent-authority.md (the three run modes, and plainly which two are not implemented). Two things found while writing them, both fixed rather than filed: - `webhookSecretKey` was read by the webhook route but writable from NOWHERE -- absent from the create schema, update schema, service, CLI and apply spec. Since the endpoint fails closed on a null key, post-merge CI failures could never reopen their task; the board would keep claiming `merged` for work that broke the build. Adds --webhook-secret-key, the schema fields and the apply key, with tests covering persist / omit / explicit-null / reject-empty. Omission deliberately does not clear the column, so an unrelated edit cannot silently disarm the webhook. - src/cli/src/commands/create.ts contained a literal NUL byte inside a string literal (a binary-file check written with the raw character rather than an escape). Identical behaviour, but it made the whole file read as binary, so `grep -I` skipped it entirely and 1120 lines of CLI were invisible to every text search. Replaced with the escape. The Epic write path (criteria/status/tradeoffs) is API-only -- documented as such rather than papered over, and filed as task 180. Full suite 2612 green; lint at baseline 794/77. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4wNHWf7xSwnZCWpJcyv9p
2026-08-16 00:14:58 +01:00
# Epics, roadmap nodes and work items
Three resources, one hierarchy:
```
Epic the vision, and the criteria delivery is measured against
└─ EpicTask a roadmap node; owns a subset of the Epic's criteria
└─ Task a work item that moves through states and lands a PR
```
The split exists because "every task is done" and "we delivered the thing" are
different claims. Tasks close; criteria are what say whether closing them
amounted to anything. Nothing derives one from the other automatically — see
[Reconciliation](#reconciliation).
## Epic
The versioned statement of intent. `content` is the vision prose; `doneCriteria`
is the list the work is graded against.
A criterion is `id=text`, and prefixing the id with `q:` marks it a **quality**
criterion rather than an outcome one. Both count toward completeness — grading
only the mechanical half is how you end up with every task done and no car.
```sh
mcpctl create epic checkout-rewrite \
--project payments \
--content-file vision.md \
--criterion 'cart=A cart survives a session drop' \
--criterion 'q:latency=p99 checkout under 400ms'
mcpctl get epics
mcpctl describe epic checkout-rewrite # vision, criteria, completeness, reconciliation, roadmap
```
`status` is `open`, `delivered`, `delivered_with_tradeoffs` or `cancelled`.
Closing an Epic with unmet criteria requires the tradeoffs path — you may ship
something short of the vision, but not while calling it whole.
> **API only, no CLI yet.** Marking a criterion `met`, setting status and
> recording tradeoffs are `POST /api/v1/epics/:id/{criteria,status,tradeoffs}`.
> The CLI reads all of it (`describe epic`) but cannot write it. Tracked as
> task 180.
## EpicTask
A roadmap node. Its `--criterion` ids must match ids on the parent Epic: the
node is claiming *"these parts of the vision are mine"*.
```sh
mcpctl create epictask cart-persistence \
--epic checkout-rewrite \
--criterion 'cart=A cart survives a session drop' \
--depends-on session-store
mcpctl get epictasks
mcpctl describe epictask cart-persistence
```
`--depends-on` builds the dependency graph `describe epic` renders as a roadmap.
## Task
The unit an agent actually works on. It carries a `risk` level, which decides
how much review it must survive, and optionally a repo and a pull request.
```sh
mcpctl create task cart-cookie-ttl \
--epictask cart-persistence \
--title 'Extend cart cookie TTL to 30 days' \
--risk normal \
--repo <repo-id>
mcpctl get tasks
mcpctl describe task cart-cookie-ttl # state, legal moves, verdicts, git, history
```
> **`tasks` means work items.** It used to mean the durable inference queue,
> which now answers only to `inference-tasks` / `itasks`. See
> [inference-tasks.md](inference-tasks.md).
### States
```
todo → in_progress → building → self_testing → qa_review
↓ ↓
done ← security_review → sre_review
↓
awaiting_review ⇄ (reject → in_progress)
↓ accept
merged → reopened → in_progress
```
Every state can go back to `in_progress` and out to `cancelled`; a failed build
goes *back* to work rather than forward. `merged` has exactly one exit,
`reopened`, and only CI drives it (see [gitproviders.md](gitproviders.md#post-merge-failure)).
```sh
mcpctl tasks advance cart-cookie-ttl building --note 'kicked off CI'
mcpctl tasks history cart-cookie-ttl
```
Transitions are compare-and-swap: two agents racing the same task, one wins and
the other gets a 409 naming the state it lost to.
### Risk decides the gates
| Risk | Must pass before `done` |
|---|---|
| `trivial` | nothing — docs, formatting |
| `normal` | `qa_review` |
| `high` | `qa_review`, `security_review`, `sre_review` |
Verification effort scales with risk instead of running one fixed pipeline over
everything. A verdict is pinned to the commit it judged, so a later push
invalidates it rather than silently carrying forward:
```sh
mcpctl tasks verdict cart-cookie-ttl \
--stage qa_review --commit 4f2a91c --pass \
--summary 'covers the session-drop path; added a regression test'
```
Reaching `done` without the required passing verdicts is refused, and the error
names which stage is missing.
### The human gate
`done` means it left the developer's loop, not that it shipped. `awaiting_review`
is where a human decides:
```sh
mcpctl tasks accept cart-cookie-ttl --note 'ships with the friday release'
mcpctl tasks reject cart-cookie-ttl --note 'cookie is set before consent — move it after'
```
**Accept lands the code.** If the task has a PR, accept merges it through the
repo's gitprovider and records the merge commit. If the merge fails — conflict,
or someone pushed after the review — the task stays un-merged and says why. A
stale head is refused rather than merged, because accepting a commit nobody
reviewed is the one thing the gate exists to prevent.
Reject requires its comment. That comment is what the agent works from, so an
empty rejection is a bug report with the contents removed.
## Reconciliation
`describe epic` prints claimed completeness and, directly under it, what the
delivered work actually says. Those two disagree by design:
- **Completeness** counts `met` flags. Nothing flips them automatically, so an
Epic whose every Task is merged still reads 0% until someone asserts the
vision was met.
- **Reconciliation** walks the roadmap and reports which criteria have merged
work behind them, which are claimed but undelivered, and which no roadmap node
owns at all (`unowned`).
Attribution is by criterion id, which is a *convention* — nothing enforces that
an EpicTask's criteria ids exist on the parent. Reconciliation reports where the
convention doesn't hold rather than guessing an owner. It never writes `met`:
deriving it from task state would assert that finishing the tasks delivered the
vision, which is exactly the assumption under audit.
## RBAC
| Resource | Verbs |
|---|---|
| `epics` | `view`, `create`, `edit`, `delete` — `edit` also covers status/criteria/tradeoffs |
| `epictasks` | `view`, `create`, `edit`, `delete` — `edit` also covers dependency edges |
| `tasks` | `view`, `create`, `edit`, `delete`, **`run`** |
`run:tasks` is the gated one: advancing state, recording verdicts, accepting and
rejecting are `run`, not `create`. A reviewer who may drive tasks through the
board should not need permission to invent them, and — more to the point —
anyone who can *file* a task should not thereby be able to *accept* one.
```sh
mcpctl create rbac task-reviewer --role run --resource tasks
mcpctl create rbac epic-reader --role view --resource epics
```
Bindings written before the `tasks` / `inference-tasks` split are rewritten once
at startup, so an old `view:tasks` grant keeps authorizing the inference queue
and does not silently become authority over work items.
## See also
- [gitproviders.md](gitproviders.md) — forges, repo import, merge and post-merge reopen
- [agent-authority.md](agent-authority.md) — whose authority an agent acts under
- [inference-tasks.md](inference-tasks.md) — the durable inference queue that used to own this name