Assess issue and project triage readiness, and write the interim runbook

17d665bd3dd3 · AtlantisPleb · · parent a40a799818a2

Assess issue and project triage readiness, and write the interim runbook

Records what the production issues-and-projects system implements at
a40a799, ranks the gaps between it and public issue submission, and
specifies the triage process operators can run today, including the
console-based membership grants required until a members surface ships.

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • added docs/2026-08-21-issue-project-triage-runbook.md

Diff

1 file changed, +396 -0

docs/2026-08-21-issue-project-triage-runbook.md added +396

@@ -0,0 +1,396 @@

1
# Issue and project triage assessment and runbook
2
3
Date: 2026-08-21
4
5
Status: Assessed against production at commit `a40a799`. The runbook describes
6
the interim process you can run today; the gap list describes what must change
7
before community issue submission works without manual steps.
8
9
## Purpose
10
11
OpenAgents just cut over to production, the first repository
12
(`OpenAgentsInc/openagents.com`) is being imported, and the goal is to start
13
building a public backlog: people read issues and submit them, maintainers
14
triage them, pull requests stay closed to the public for now.
15
16
This document has three parts:
17
18
1. What the issues-and-projects system implements today.
19
2. The gaps that stand between that implementation and public triage, ranked.
20
3. A triage process and runbook you can operate with what ships now.
21
22
## Part 1: What is implemented
23
24
### Identity and authority
25
26
- Sign-in is GitHub OAuth. Namespaces are keyed to immutable GitHub numeric
27
  IDs; logins are mutable projections.
28
- Browser mutations use the signed session. API writes use a personal access
29
  token (`oa_pat_…`) with the exact `forge:write` scope, created and revoked at
30
  `/settings/api-tokens`; plaintext is shown once.
31
- Repository roles are `owner`, `maintainer`, `contributor` (writable), and
32
  `viewer` (read-only). Every GitHub sign-in used to grant `contributor` on the
33
  initial repository; commit `348dcc8` removed that auto-grant, so membership
34
  is now explicit.
35
36
### Repositories and Git transport
37
38
- Create, one-time GitHub import, list, and view work through both the browser
39
  (`/repositories`, `/repositories/new`, `/repositories/import/github`) and
40
  the API (`POST /api/v3/user/repos`, `POST /api/v3/user/repos/imports`, and
41
  organization equivalents). Provisioning runs through a durable outbox.
42
- Git smart HTTP serves `https://openagents.com/{owner}/{repo}.git` plus a
43
  legacy `/git` compatibility route. Public repositories allow anonymous clone
44
  and fetch. Push requires repository membership with a writable role,
45
  authenticated by PAT over HTTP Basic or the CLI credential helper. Machine
46
  (`smct_…`) and operator lanes are separate.
47
- The CLI (`@openagentsinc/cli`, published on npm) handles auth login,
48
  `repo create|import|list|view|clone`, and Git credential setup.
49
- Public code browsing (repository home, commit, blob) reads from the WAL and
50
  requires no sign-in.
51
52
### Issues
53
54
The GitHub-shaped subset is implemented end to end:
55
56
- List, get, create, update, close, reopen with `state_reason`
57
  (`completed`, `not_planned`, `duplicate`, `reopened`).
58
- Comments (list, create, edit, delete).
59
- Issue numbers are repository-local; composite foreign keys make cross-
60
  repository references impossible at the database level.
61
- JSON API: anonymous public reads on `/api/v3/repos/{owner}/{repo}/issues`;
62
  token-authenticated writes.
63
- Web UI (`/:owner/:repo/issues`) has open/closed tabs, inline state changes
64
  with close reasons, inline assignee toggles, label and milestone pickers in
65
  the issue rail, and a comment timeline.
66
67
### Labels, milestones, and assignees
68
69
- Full CRUD through UI and API for labels and milestones.
70
- Assignees resolve to active repository members with writable roles;
71
  assignment of arbitrary logins is rejected.
72
73
### Projects V2
74
75
- Bounded subset: list, get, create projects; add items; update item field
76
  values; list fields.
77
- The board (`/:owner/:repo/projects/:number`) renders columns from the values
78
  of a "Status" project field ("To Do", "In Progress", "Done") and links items
79
  to issues.
80
81
### Analytics
82
83
PostHog captures `issue_created`, `issue_updated`, `issue_commented`,
84
`label_created`, `milestone_created`, `project_created`, and
85
`project_item_added` at the domain-context choke points, so triage activity is
86
measurable from day one.
87
88
### Tests
89
90
The issues-and-projects layer went from 33% to 98% line coverage in the
91
August coverage push; 23 previously untested modules including all eight
92
LiveViews are covered. Two real defects were found and fixed this way
93
(including a `ProjectShowLive` that had never rendered).
94
95
## Part 2: Gaps between here and public triage
96
97
Ranked by how much they block the stated goal. Items 1–3 block it outright.
98
99
### 1. No membership management surface — the blocker
100
101
`OpenAgents.Repositories.add_member/3` exists, writes audit records, and has
102
no caller anywhere in product code. OAuth no longer auto-grants membership.
103
Consequence: nobody who signs up from now on can see or touch issues, and
104
there is no UI, API, or admin page that fixes that. Today the only way to add
105
a maintainer or reporter is a production console command (see the runbook).
106
107
**Recommendation:** ship the smallest possible surface first — an owner-only
108
"Members" section on the repository settings page that lists members and adds
109
one by GitHub login with a role. It needs one context function that already
110
exists, audit records that already exist, and one LiveView.
111
112
### 2. Issue pages require writable membership
113
114
Every issue LiveView resolves the repository with
115
`Repositories.get_writable_by_path!/3`, which raises when the viewer lacks a
116
writable-role membership. A signed-in non-member gets an exception page instead
117
of a 404 or a read-only view, and an anonymous visitor cannot view issues at
118
all even on a public repository — while anonymous code browsing works.
119
120
The read path already exists: `get_visible_by_path!/3` returns public
121
repositories to anyone and private ones to any member, including `viewer`.
122
123
**Recommendation:** split reads from writes in the issue LiveViews. Read with
124
`get_visible_by_path!` (plus an anonymous path for public repositories), gate
125
every mutation behind writability, and render a clean "sign in to interact"
126
state otherwise. This is also what makes the public backlog readable, which is
127
the point of having one.
128
129
### 3. Submission policy is undecided
130
131
Today, opening an issue requires a writable membership, because `contributor`
132
was the only door in. That conflates two different grants: "can report bugs"
133
and "can push code". GitHub's model separates them: anyone signed in can open
134
an issue on a public repository; only collaborators can label, assign,
135
close, or edit.
136
137
**Recommendation:** adopt the GitHub model. Allow any active signed-in user to
138
create issues and comments on public repositories, recorded with their author
139
attribution, and keep every other mutation behind membership. This matches the
140
existing data model (issues already carry an author), keeps the API contract
141
unchanged, and turns "submit an issue" into a link instead of a manual grant.
142
143
Until that lands, the interim policy in Part 3 uses manual grants.
144
145
### 4. New repositories have no default labels
146
147
A freshly created repository has an empty label set, so the first triage pass
148
has nothing to attach. **Recommendation:** seed GitHub's default set on
149
repository creation (`bug`, `documentation`, `duplicate`, `enhancement`,
150
`good first issue`, `help wanted`, `invalid`, `question`, `wontfix`). Until
151
then, seed once per repository through the API (scripted in the runbook).
152
153
### 5. List ergonomics stop at open/closed tabs
154
155
The issue list has state tabs only: no filter by label, assignee, or milestone;
156
no search; no sorting; no pagination (the list loads every row). The design
157
ruling in `2026-08-20-linear-design-github-shape.md` notes grouping, sorting,
158
and filtering are free — arithmetic over existing responses — so these are low
159
risk. **Recommendation:** filter-by-label and filter-by-assignee first, then
160
pagination, then text search.
161
162
### 6. Concurrent triage has no live updates
163
164
Issue pages load once; there is no PubSub subscription, so two maintainers
165
triaging simultaneously see stale rows until reload. Repository provisioning
166
already demonstrates the broadcast pattern. **Recommendation:** subscribe the
167
issue index and show views to a per-repository topic and re-stream on change.
168
169
### 7. Known compatibility sharp edges
170
171
Pinned by tests, still present at `a40a799`. None block triage, but each will
172
bite a scripted client eventually:
173
174
- `LabelJSON` renders label URLs with `URI.encode_www_form/1`, so a label
175
  named `good first issue` advertises a URL its own endpoint cannot resolve.
176
- `PATCH .../labels/:name` ignores `new_name`; renaming is impossible through
177
  the API.
178
- Adding a nonexistent label to an issue returns 404 where GitHub creates the
179
  label.
180
- Removing a label an issue does not have is a silent no-op where GitHub
181
  returns 404.
182
183
**Recommendation:** fix the URL encoding first (it is a correctness bug in
184
advertised data), batch the rest into one compatibility pass.
185
186
### 8. Notifications do not exist yet
187
188
There is no email and no mention machinery; the mailer is development-only.
189
A backlog stays healthy when reporters hear back. Acceptable to defer while
190
the community is small and responses happen in chat, but it should not be
191
deferred past the first hundred external issues.
192
193
### 9. Pull requests stay out — deliberately
194
195
Pull requests, reviews, and fork workflows are unimplemented, and per the
196
current direction they should stay that way while the backlog forms. The Git
197
plane already enforces the useful half of this: only members with writable
198
roles can push, so external contribution is limited to issues until you decide
199
otherwise.
200
201
## Part 3: Triage runbook
202
203
### Roles
204
205
| Role | Can do |
206
| --- | --- |
207
| `owner` | Everything, plus member management (console until gap 1 closes) |
208
| `maintainer` | Triage: label, assign, milestone, close, edit any issue |
209
| `contributor` | Open and comment on issues; push if granted Git access |
210
| `viewer` | Read private-repository content; no writes |
211
212
While gap 2 stands, all of these roles also gate *viewing* the web UI, which
213
is why setup begins with grants.
214
215
### One-time production setup
216
217
Run these once, after the import finishes and the repository reports `ready`.
218
219
1. Verify the repository exists and is public:
220
221
   ```sh
222
   curl -s https://openagents.com/api/v3/repos/OpenAgentsInc/openagents.com | grep visibility
223
   ```
224
225
2. Seed the default labels. Run once per missing label:
226
227
   ```sh
228
   TOKEN=oa_pat_your_token   # created at /settings/api-tokens, forge:write scope
229
   BASE=https://openagents.com/api/v3/repos/OpenAgentsInc/openagents.com/labels
230
231
   for spec in bug:d73a4a documentation:0075ca duplicate:cfd3d7 \
232
     enhancement:a2eeef "good first issue:7057ff" help wanted:008672 \
233
     invalid:e4e669 question:d876e3 wontfix:ffffff; do
234
     name=${spec%%:*}; color=${spec#*:}
235
     curl -s -X POST "$BASE" -H "Authorization: Bearer $TOKEN" \
236
       -H "Content-Type: application/json" \
237
       -d "{\"name\": \"$name\", \"color\": \"$color\"}" >/dev/null
238
   done
239
   ```
240
241
3. Grant memberships to the initial maintainers. Until gap 1 closes this needs
242
   a console session on a production node:
243
244
   ```elixir
245
   alias OpenAgents.{Accounts, Repo, Repositories}
246
247
   repo = Repositories.get_by_path!("OpenAgentsInc", "openagents.com")
248
   user = Repo.get_by!(Accounts.User, github_login: "their-github-login")
249
   Repositories.add_member(repo, user, "maintainer")
250
   ```
251
252
   Grant `maintainer` to everyone who will run triage; keep `owner` to two or
253
   three people. Each call writes an `Audit` record.
254
255
4. Create the first milestone so work has a destination:
256
257
   ```sh
258
   curl -s -X POST "$BASE/../milestones" -H "Authorization: Bearer $TOKEN" \
259
     -H "Content-Type: application/json" \
260
     -d '{"title": "Backlog hygiene", "description": "First triage sweep", "state": "open"}'
261
   ```
262
263
5. Create a project for the public board:
264
265
   ```sh
266
   curl -s -X POST "https://openagents.com/api/v3/OpenAgentsInc/projectsV2" \
267
     -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
268
     -d '{"title": "openagents.com roadmap"}'
269
   ```
270
271
   Then add its Status field values ("To Do", "In Progress", "Done") through
272
   the project fields API, and pin high-signal issues to the board with
273
   `project_item_added`.
274
275
6. Record who holds which role in your own notes; there is no UI to look it up
276
   until gap 1 closes.
277
278
### Label vocabulary
279
280
Use GitHub's defaults and nothing else for now. The standing design ruling is
281
that priority labels are conventions, not contracts, so ordering lives in
282
milestones and the project board, not in label names.
283
284
| Label | Means | Who applies it |
285
| --- | --- | --- |
286
| `bug` | Something is broken | Triage |
287
| `enhancement` | New capability or improvement | Triage |
288
| `documentation` | Docs only | Triage |
289
| `good first issue` | Scoped enough for a newcomer | Maintainer |
290
| `help wanted` | Community input invited | Maintainer |
291
| `question` | Needs an answer before it is work | Triage; close as completed once answered |
292
| `duplicate` | Repeats another issue | Triage; close with reason `duplicate` |
293
| `invalid` | Not a real report | Triage; close with reason `not_planned` |
294
| `wontfix` | Real but declined | Maintainer; close with reason `not_planned` |
295
296
### The daily triage loop
297
298
Ten minutes, ideally same time each day:
299
300
1. Open `https://openagents.com/OpenAgentsInc/openagents.com/issues?state=open`.
301
2. For each unlabeled issue: reproduce or reason about the report, then apply
302
   one primary label (`bug`, `enhancement`, `documentation`, or `question`).
303
   If the report cannot be evaluated, ask your question as a comment and leave
304
   it labeled `question`.
305
3. Assign an owner to everything labeled `bug` or `enhancement` that you
306
   intend to act on. Unassigned means "nobody owns this"; say that honestly
307
   rather than leaving it ambiguous.
308
4. Close what is not work:
309
   - Duplicate: close with reason `duplicate`, comment with the issue number.
310
   - Declined: close with reason `not_planned`, apply `wontfix`.
311
   - Answered questions: close with reason `completed`.
312
5. Move anything scheduled onto the project board and into a milestone. The
313
   board is the promise; the milestone is the deadline.
314
6. Sweep the closed tab briefly: a wrong close is cheaper to catch the same
315
   day than next week.
316
317
Everything above mutates through either the issue rail in the UI or the
318
equivalent PATCH calls below.
319
320
### The weekly review
321
322
1. Walk the project board column by column. "In Progress" older than a week
323
   gets an owner, a milestone, or moves back to "To Do".
324
2. Check the milestone: scope fixed, due date honest, nothing stuck.
325
3. Skim PostHog for `issue_created` volume versus `issue_commented` — a rising
326
   creation rate with flat response rate means triage is falling behind.
327
4. Confirm every new triage participant has a membership row (console check
328
   until the members UI exists).
329
5. File the meta-issue: anything about the triage process itself that hurt
330
   this week becomes an issue labeled `enhancement` on this same tracker.
331
332
### Interim community submission policy
333
334
Until gaps 1–3 close, the honest flow for an outside reporter is:
335
336
1. They sign in with GitHub at `openagents.com`.
337
2. You grant their account `contributor` from the console (step 3 of setup).
338
3. They open issues and comment; they cannot push unless you separately intend
339
   them to, since Git push checks the same membership table but is a distinct
340
   decision you control by role.
341
342
Publish this expectation wherever you announce the tracker, or hold public
343
submissions until the GitHub-model change lands — a crash page for eager
344
first-time reporters is worse than "issue tracker opens next week".
345
346
### API recipes
347
348
Read (anonymous, public repository):
349
350
```sh
351
curl -s "https://openagents.com/api/v3/repos/OpenAgentsInc/openagents.com/issues?state=open"
352
```
353
354
Create (requires `forge:write` token held by a member):
355
356
```sh
357
curl -s -X POST "https://openagents.com/api/v3/repos/OpenAgentsInc/openagents.com/issues" \
358
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
359
  -d '{"title": "Search returns duplicates", "body": "Steps to reproduce...", "labels": ["bug"]}'
360
```
361
362
Close as not planned:
363
364
```sh
365
curl -s -X PATCH "https://openagents.com/api/v3/repos/OpenAgentsInc/openagents.com/issues/42" \
366
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
367
  -d '{"state": "closed", "state_reason": "not_planned"}'
368
```
369
370
Label and assign in one update: `labels` takes names, `assignees` takes
371
logins, and both replace the full set, so send the complete desired lists.
372
373
### Measuring triage health
374
375
PostHog events land at the domain boundary, so the funnel is queryable:
376
377
- Median time from `issue_created` to first `issue_commented` by a maintainer:
378
  your response-time promise.
379
- `issue_created` count per week versus issues closed per week: backlog
380
  direction.
381
- Share of issues carrying zero labels after 24 hours: triage loop discipline.
382
383
Track them as three insights on one dashboard before the first announcement;
384
retrofitting measurement onto a neglected backlog is miserable.
385
386
## Guardrails
387
388
- Do not widen Git push beyond repository members; that is the mechanism that
389
  keeps public participation issue-shaped while pull requests remain closed.
390
- Do not grant `maintainer` casually: triage roles can close and rewrite
391
  anyone's issues.
392
- Keep every membership change flowing through `add_member/3` so audit
393
  records accumulate; when the members UI lands it inherits the trail.
394
- When you fix gaps 1–3, update this document the same day; a runbook that
395
  says "use the console" one commit longer than necessary is how stale
396
  instructions become incident reports.

This page updates live while a promote is in flight · changelog