191 lines
7.2 KiB
Markdown
191 lines
7.2 KiB
Markdown
|
|
# 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
|