Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 60 additions & 6 deletions plugins/stewards/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ existing bb-gate-hooks pipeline; workers for bounded tasks.
| **Project steward** | One per bb project. Reads every binding's status record, sets priorities and holds, routes escalations, writes the morning digest. |
| **Worker** | A short-lived child thread a steward starts for one bounded task: implement, fix CI, review a round. Archived with its steward. |
| **Playbook** | A file in `.bb/stewards/playbooks/`: adoption, phases keyed by entity state, the ready gate, allowed actions, models, terminal states. |
| **Project file** | `.bb/stewards/project.json`: Linear teams, excluded repos, adoption label, nightly schedule, the project steward's model. |
| **Project file** | `.bb/stewards/project.json`: the project's conventions — Linear teams, excluded repos, adoption label, nightly schedule, the project steward's model, `deps`, `bigDecisions`. |
| **Roles file** | `.bb/stewards/roles.json`: per-person watches keyed by GitHub login — project steward, ops namespaces, Sentry projects, Dependabot repos (BB-105). |
| **Spec** | What the entity should become: the playbook's terminal state, the ticket's description and acceptance criteria, its spec comments, and standing guidance. |
| **Snapshot** | The observed state, built by plugin code. |
| **Status record** | What a steward writes at the end of every judgment turn. |
Expand Down Expand Up @@ -76,7 +77,9 @@ These were settled while designing; each changes what gets built.
truth, so compaction loses nothing important.
- **Opt-in by label** for tickets and pull requests: `bb-managed`.
- **Every pull request in the project's repos gets a review steward.** Not
only the ones your review is requested on.
only the ones your review is requested on — until a `roles.json` names a
project steward, when a review steward is made only where your review is
requested (BB-105).
- **Comments, not description edits.** A steward adds to a ticket with
comments. It edits the description only when the scope changes.
- **Approval before substantial implementation.** A ticket must be fully
Expand Down Expand Up @@ -123,12 +126,15 @@ These were settled while designing; each changes what gets built.
repos.json owned by multi-repo; the GitHub repos
stewards/
project.json Linear teams, excluded repos, label, nightly,
project steward
project steward, deps, bigDecisions
roles.json per-person watches keyed by GitHub login:
projectSteward, ops, Sentry, Dependabot (BB-105)
playbooks/
linear-tickets.md
github-prs.md
github-reviews.md
ops-watch.md only read when ops.targets lists something (§18)
ops-watch.md only read when a role watches a namespace or
Sentry project (§18)
```

### `project.json`
Expand Down Expand Up @@ -166,6 +172,54 @@ These were settled while designing; each changes what gets built.
- `bb stewards check` validates the file and every playbook against Linear and
GitHub.

### `roles.json`

The watches with no natural owner are assigned here, keyed by the signed-in
GitHub login the github plugin reads. Every server runs only its own entry, so
two developers never watch the same namespace, Sentry project or Dependabot
repo, and a project has one project steward. Ticket stewards follow the Linear
assignee and review stewards follow the GitHub requested reviewer; neither
needs an entry.

```jsonc
{
"version": 1,
"roles": {
"jane": {
"projectSteward": true,
"ops": {
"k8sNamespaces": ["bb-gate-production"],
"context": "bc-prod",
"team": "OPS",
"autoAdopt": ["urgent"],
"sentry": { "projects": [{ "org": "acme", "project": "web" }], "resolveOnDone": false },
"dependabot": "all"
}
},
"sam": { "ops": { "k8sNamespaces": ["devbox-production"], "dependabot": ["acme/web"] } }
}
}
```

- A login with no entry runs no watches and is not the project steward for
the project; the home page says `No role for <login> in roles.json`. A file
that is present but whose login cannot be read runs no roles — never every
role — and says so.
- The project steward's server reports cross-server problems: a watch nobody
runs, a watch two logins run, and no or two project stewards. A role whose
machine lacks a credential (no kubectl, no Sentry token) is that server's
own `bb stewards check` line.
- A `project.json` that still carries `ops` runs exactly as before on every
server and adds one problem line; once `roles.json` is present it wins, and
the migration line stays until the `ops` block is removed.
- A parse error keeps the last good copy in the plugin's own store; nothing
per-server is ever written under `.bb`.
- Each server keeps its own bindings, so the project steward's server cannot
see another server's. An escalation routed to `project` on a server that is
not the project steward waits on its own binding; cross-server routing is a
later change. A review binding adopted before `roles.json` landed keeps
reviewing until its pull request closes, and is detached by hand to stop.

### Reading `.bb`

`.bb` here is the multi-repo project's own repo — the one holding `repos.json`,
Expand Down Expand Up @@ -197,8 +251,8 @@ HEAD; a checkout with local commits is left as it is and read as it is.
|---|---|---|
| `ticket` | `bb-managed` added to an issue in a listed Linear team | `linear-tickets` |
| `pr` | `bb-managed` added to a pull request in a repo from `repos.json` not in `github.exclude` | `github-prs` |
| `review` | a non-draft pull request, authored by someone other than you, opened or marked ready in a repo from `repos.json` not in `github.exclude` | `github-reviews` |
| `ops` | a target listed in the project file's `ops.targets` (§18) | `ops-watch` |
| `review` | a non-draft pull request, authored by someone other than you, opened or marked ready in a repo from `repos.json` not in `github.exclude`; once `roles.json` names a project steward, only where your review is requested | `github-reviews` |
| `ops` | a namespace, Sentry project or Dependabot repo in this server's `roles.json` entry (§18) | `ops-watch`, `deps-alerts` |

- The plugin hears triggers through bb-gate-hooks `team` and `repo` leases,
which already exist. The nightly pass re-lists labelled tickets and open
Expand Down
73 changes: 49 additions & 24 deletions plugins/stewards/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ bb. [DESIGN.md](DESIGN.md) is the design; this is how it is built and run.
of its first 96 turns on bithub, so stewards were top-level.) A project's
stewards share one sidebar section, named after the project; see
DESIGN.md §15.
- **Ops stewards.** Each target in the project file's `ops.targets` (a
Kubernetes namespace or a Sentry project) gets an `ops` steward, reporting to the project
- **Ops stewards.** Each namespace and Sentry project in a login's
`roles.json` `ops` entry gets an `ops` steward, reporting to the project
steward, for as long as it is listed. The plugin looks at the target with
`kubectl` on the project's machine, the developer's own, as them (through
its host entry, [host.ts](host.ts), and [kubectl.ts](kubectl.ts)): pods,
Expand All @@ -43,9 +43,9 @@ bb. [DESIGN.md](DESIGN.md) is the design; this is how it is built and run.
wall. Until kubectl can read a target, its binding waits and `bb stewards
check` says "cannot read the target". DESIGN.md §18.
- **Dependency stewards.** GitHub scans; the stewards turn Dependabot alerts
into tickets. Every repo in `repos.json` not in `github.exclude` (or the
explicit `{ kind: "dependabot", repo }` targets in `ops.targets`) gets an
`ops` binding that runs `deps-alerts` and is woken by the
into tickets. A login whose role lists `dependabot` (or `dependabot: "all"`)
gets an `ops` binding for each named repo, or every repo in `repos.json` not
in `github.exclude` for `"all"`; each runs `deps-alerts` and is woken by the
`dependabot_alert` doorbell; the github plugin reads the alerts with the
developer's token and leases each repo as a `dependabot_repo` (a `repo`
lease does not receive alert doorbells). Severity sets the Linear priority
Expand Down Expand Up @@ -508,6 +508,7 @@ HEAD through multi-repo's `readProjectFiles` and `repoSet`:
```
stewards/
project.json Linear teams, excluded repos, label, nightly, project steward model
roles.json per-person watches keyed by GitHub login (ops, Sentry, Dependabot)
playbooks/
linear-tickets.md front matter + one ## section per phase
github-prs.md
Expand All @@ -523,25 +524,49 @@ fails to parse is reported and bindings keep running under the last good
version. Project file fields beyond the design: `nightly.until`,
`nightly.max`, `playbooks` (which playbook governs each kind),
`reviews.enabled`, `adoption.perPass`, `bigDecisions` (what reaches you;
defaults in [config.ts](config.ts)), and `ops`: `targets` (`{ kind:
"k8s-namespace", name }`, at most ten), `team` (where tickets are filed; the
first of `linear.teams` by default, and it must be one of them) and
`context` (the kubeconfig context kubectl uses; the machine's current one
when left out) and `autoAdopt` (Linear priorities, `urgent`, `high`, `medium`, `low`, whose
tickets the ops steward labels for a steward itself when it files them).
A `{ kind: "sentry-project", org, project }` target watches a Sentry
project with the plugin's **Sentry token** setting (a user auth token with
`project:read`, `event:read` and `event:write`; `Sentry API` for a
self-hosted server), rung by bb-gate-hooks the moment an issue is created
or regresses and backed by the same periodic look; the Sentry issue id is
the fingerprint, and the wake carries the issue's title, culprit,
permalink, counts and latest stack trace ([sentry.ts](sentry.ts), DESIGN.md
§18). With `ops.sentry.resolveOnDone` on (it defaults off), the look
resolves a mapped issue whose ticket is Done while Sentry still shows it
unresolved — the plugin's only Sentry write — and notes it in the look;
`event:write` is the scope that write needs.
`ops-watch.md` is read only when `ops.targets` lists a k8s namespace or a
Sentry project.
defaults in [config.ts](config.ts)) and `deps` (below). `project.json` holds
conventions only; the watches that need an owner live in `roles.json`.

`roles.json` (BB-105) assigns what each person's server runs. It is keyed by
the signed-in GitHub login the github plugin reads, and every server runs only
its own entry: no entry means no watches and no project steward for that
server, and its home page says `No role for <login> in roles.json`. An entry
is `{ projectSteward?: boolean, ops?: { k8sNamespaces, context?, team?,
autoAdopt?, sentry?: { projects: [{ org, project }], resolveOnDone? },
dependabot?: [repo, …] | "all" } }`. `k8sNamespaces` are watched with
kubectl on that person's machine (their kubeconfig `context` when named);
`team` is where their ops tickets are filed (the first of `linear.teams` by
default, and it must be one of them); `autoAdopt` names the Linear priorities
whose tickets the ops steward labels for a steward itself when it files them.
`sentry.projects` watches Sentry projects with the plugin's **Sentry token**
setting (a user auth token with `project:read`, `event:read` and
`event:write`; `Sentry API` for a self-hosted server), rung by bb-gate-hooks
the moment an issue is created or regresses and backed by the same periodic
look; the Sentry issue id is the fingerprint, and the wake carries the issue's
title, culprit, permalink, counts and latest stack trace
([sentry.ts](sentry.ts), DESIGN.md §18). With `sentry.resolveOnDone` on (it
defaults off), the look resolves a mapped issue whose ticket is Done while
Sentry still shows it unresolved — the plugin's only Sentry write — and notes
it in the look; `event:write` is the scope that write needs. `dependabot`
lists the repos whose alerts that person watches, or `"all"` for every repo in
`repos.json` not in `github.exclude`. `ops-watch.md` is read only when a role
watches a namespace or a Sentry project.

The project steward's server reports cross-server configuration problems: a
watch nobody runs, a watch two logins run, and no or two project stewards. A
role whose credential is missing on a machine (no kubectl, no Sentry token)
is that server's own `bb stewards check` line, not a project-wide problem.
A `project.json` that still carries `ops` runs exactly as before on every
server and adds one problem line saying the watches now belong in
`roles.json`; once `roles.json` is present it wins, and the migration line
stays.

Each server keeps its own bindings, so the project steward's server does not
see another server's. An escalation routed to `project` on a server that is
not the project steward therefore waits on its own binding; cross-server
routing is a later change. A review binding adopted before `roles.json`
landed keeps reviewing until its pull request closes — detach one by hand to
stop it.

The `deps` object is the dependency watch: `sla` (a duration or `asap` per
severity; critical→urgent, high→high, medium→medium, low→low), `batch` (the
Expand Down
3 changes: 3 additions & 0 deletions plugins/stewards/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1981,6 +1981,9 @@ function StewardsSidebarCore({
<p className="p-3 text-muted-foreground">No projects yet.</p>
) : (
<div className="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto p-1">
{data !== null && data.roleProblem !== null ? (
<p className="rounded-md border border-border bg-state-hover px-2 py-1 text-xs text-attention">{data.roleProblem}</p>
) : null}
{active.blocked.length > 0 ? (
<SidebarSection title="Blocked" count={active.blocked.length}>
<ul className="flex flex-col">{rows(active.blocked)}</ul>
Expand Down
Loading
Loading