Implements ADR 0031: native issues, the owner Inbox, and the moderated public issue form. It contains the three stacked branches feat/issues → feat/inbox → feat/issue-form, and each phase was reviewed before the next.
What it does
- Issues:
- Issues share the per-repo number with merge requests, so
#nis one or the other and links redirect both ways. - New scope
issue:write. Agents can create, comment, close and reopen issues without moderation. - Per-repo setting
issues: off|owner|public, default off. A repo with issues off answers the same 404 as an unknown repo. - CLI
ermite issue create|list|show|comment|close|reopenandrepo edit --issues. - Web: an Issues tab, live updates, and owner forms.
- Issues share the per-repo number with merge requests, so
- Inbox (
/-/inbox, owner only):- Counters at the top, then the sections Awaiting moderation, Open merge requests and Open issues.
- Repository filter, and an
ownerlive stream across all repositories. - Button at the top left of the home page, shown only when signed in.
- Public form (mode
public):- JavaScript required. A same-origin worker solves a proof of work signed by the server (18 to 21 bits, adaptive).
- Honeypot field and a minimum fill time of 3 s.
- Single-use challenge and form token.
- Limits of 3/hour and 10/day per network, and a cap of 200 pending issues, both reserved atomically.
- Bans last 30 days and are keyed on an HMAC of the address. No IP is written to disk.
- Until the proof of work is valid, every refusal is the same generic 400, so nothing reveals a ban, a limit or a full queue.
- Moderation (approve, reject, reject and ban) happens in the Inbox and over the API.
- Approved anonymous issues render untrusted Markdown. Pending issues render as plain text.
- Storage: schema 3 → 4. The upgrade adds
issue:writeto stored tokens that already hold every scope, so they keep their owner rights.
Verification
- Phase reviews (go-reviewer) and a JS review (ts-reviewer). All findings are fixed, including:
- comment loss after retries;
- issue events delivered after issues were turned off;
- the bypass of the per-network limit and of the cap under concurrency;
- the ban oracle;
- the emoji/NFC counters;
- restoring the page from bfcache.
- Final wrap-up review: every point is fixed except the deferred one listed below.
- End-to-end test in headless Chromium at production difficulty, measured as:
- a proof-of-work solve of 0.25 to 4.4 s;
- moderation, then approval as
#1; - 403 without
Origin, 400 on token reuse, 429 withRetry-After; - the no-JS message shown instead of the form.
go vet,go test -race ./...(full suite), hexcheck, templ drift, buf generate, the mocks andgo mod tidyall pass. All 155 JS tests pass. All commits are signed.
Before leaving draft
- Merge MR !1 (
feat/mr-notes) first. Then rebase this branch on main and extend the v3 → v4 upgrade so that it grantsmr:reviewandissue:writeto tokens holding the 9 original scopes. Also restoremr:reviewinclient.AllScopes()and drop the temporaryUnimplementedMR note handlers inrpc. Do not deploy main between the two merges: existing all-scope tokens would lose their owner rights. - Merge the ermite-api MR (
feat/issue-form) and tag it. Replace the localreplace bnema.dev/ermite-apiwithgo get bnema.dev/ermite-api@<tag>, then rungo mod tidy. - Deferred (GO-004, scaling): the home badge and every Inbox event compute the full Overview. This is fine on a solo instance; a lighter count-only path can come later.
- Behind a reverse proxy, set
ERMITE_TRUSTED_PROXIES. Without it, every visitor shares the proxy's per-network limit.