plan §4.1/§1.5: polling primary + read-only CI; webhook is optional manual-admin
Finalize trigger model per operator: polling is the primary trigger (outbound, read-only, no admin); the server never self-registers webhooks (that needs admin) — webhook is an optional push optimization an admin registers manually, documented in enroll-recipe.md. Commenter auth via org-membership endpoint (read-level), not the admin-only permission endpoint. Bot's required privilege is read + comment + org-membership, never repo-admin. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -82,7 +82,7 @@ secret value into the repo, a commit, a log, or the dashboard** (§9) — refere
|
||||
|---|---|---|
|
||||
| **Tailscale auth key** (joins cc-ci's tailnet `taila4a0bf.ts.net`) | `/srv/cc-ci/.testenv` → `TS_AUTH_KEY` (Tailscale SaaS key, keyID ends `CNTRL`) | Used to bring up the userspace tailscaled (below). It's reusable; re-run `tailscale up` with it if the node drops. |
|
||||
| **cc-ci SSH (root)** | private key `~/.ssh/cc-ci-root-ed25519`; config `Host cc-ci` in `~/.ssh/config` | Just run `ssh cc-ci` (logs in as **root**). The pubkey is already in cc-ci's `/root/.ssh/authorized_keys`. |
|
||||
| **Gitea bot account** | `/srv/cc-ci/.testenv` → `GITEA_USERNAME` (`autonomic-bot`), `GITEA_PASSWORD`, `GITEA_URL` (`git.autonomic.zone`) | Basic-auth to the Gitea API, or mint a scoped token: `POST https://$GITEA_URL/api/v1/users/$GITEA_USERNAME/tokens`. Used to create/push the `cc-ci` repo, read recipe repos, comment on PRs, and register `!testme` webhooks. |
|
||||
| **Gitea bot account** | `/srv/cc-ci/.testenv` → `GITEA_USERNAME` (`autonomic-bot`), `GITEA_PASSWORD`, `GITEA_URL` (`git.autonomic.zone`) | Basic-auth to the Gitea API, or mint a scoped token: `POST https://$GITEA_URL/api/v1/users/$GITEA_USERNAME/tokens`. Used to push the `cc-ci` project repo, read recipe repos, comment on PRs, and poll for `!testme` (read-level; the bot does not register webhooks). |
|
||||
|
||||
Load them in a shell with: `set -a; . /srv/cc-ci/.testenv; set +a` (don't echo the values).
|
||||
|
||||
@ -134,9 +134,12 @@ without the auth key.
|
||||
- **Registry pull credentials** (e.g. Docker Hub) — *recommended* to avoid anonymous pull-rate
|
||||
limits breaking deploys under load. Treat a rate-limit failure traced to this as a finding, then
|
||||
request creds. Store sops-encrypted in `secrets/`.
|
||||
- **Gitea bot permissions** (a grant, not a secret) — confirm `autonomic-bot` can: create/push
|
||||
`recipe-maintainers/cc-ci`, read the recipe repos to be enrolled, comment on their PRs, and add
|
||||
webhooks to them. If any is missing, that's a `## Blocked` item for the operator to fix.
|
||||
- **Gitea bot permissions** (a grant, not a secret) — **least privilege: read, not admin.** The bot
|
||||
needs: write on its own `recipe-maintainers/cc-ci` project repo; **read** + **comment** on the
|
||||
recipe repos under test; and **org membership** in `recipe-maintainers` (read-level — used both to
|
||||
authorize commenters via the members endpoint and to read members). It does **not** need repo-admin
|
||||
and does **not** register webhooks (that's an optional manual admin task, §4.1). If a needed grant
|
||||
is missing, that's a `## Blocked` item for the operator.
|
||||
|
||||
---
|
||||
|
||||
@ -329,26 +332,33 @@ Bridge posts/updates a Gitea PR comment with the run URL and (on completion) pas
|
||||
|
||||
- The bridge is a tiny service (Go or Python+FastAPI). Keep it dependency-light; it's a NixOS
|
||||
systemd service behind Traefik at e.g. `ci.commoninternet.net/hook` (§4.0).
|
||||
- **Trigger mode: webhook OR poll, mutually exclusive, flag-selected (SETTLED).** Two
|
||||
implementations exist, but **only one runs at a time**, chosen by env (e.g. `BRIDGE_TRIGGER_MODE=
|
||||
webhook|poll`): (1) the Gitea `issue_comment` **webhook** — the default/primary, low-latency push
|
||||
path (confirmed working); (2) **polling** the Gitea API for new `!testme` comments — kept in the
|
||||
codebase but **disabled by default**, the fallback you flip on when webhook delivery isn't arriving
|
||||
(e.g. a gateway/network hiccup, as bit M3 early on). Polling reverses direction (cc-ci →
|
||||
git.autonomic.zone, outbound — the reliably-working path) at ≤60s to satisfy D1. Because the modes
|
||||
are exclusive, no cross-path dedupe is needed; just don't re-fire already-seen comments when poll
|
||||
mode is switched on. Either mode alone satisfies D1.
|
||||
- **Commenter auth uses effective permission, not the collaborators list.** The repo's explicit
|
||||
collaborator list is empty — the bot *and* the real maintainers (`trav`/`notplants`) all reach
|
||||
`recipe-maintainers/cc-ci` as **org owners**, so `GET /collaborators/{user}` 404s for everyone and
|
||||
a naive is-collaborator check rejects all legitimate `!testme`. Authorize instead via
|
||||
`GET /repos/{repo}/collaborators/{user}/permission` and require `owner`/`admin`/`write` (rejects
|
||||
`read`/`none`/404 → still satisfies §6's non-collaborator-rejection check; fail-closed on any API
|
||||
error). The bot token needs repo-admin to read another user's permission — fine, it's org owner.
|
||||
- Enrollment = registering the Gitea webhook on a recipe repo (script in `runner/` or documented
|
||||
in `enroll-recipe.md`) + ensuring a `tests/<recipe>/` dir exists. The `autonomic-bot` account is
|
||||
**admin on the `recipe-maintainers` org**, so it can create repos there and add webhooks to any
|
||||
recipe repo — no extra grant needed.
|
||||
- **Trigger: POLLING is primary; webhook is an optional, admin-registered push optimization
|
||||
(SETTLED).** Hard constraint: **the CI server/bot must run on READ-level access — never repo-admin.**
|
||||
- **Polling (primary, default):** the bridge polls the Gitea API for new `!testme` comments on
|
||||
enrolled repos at ≤60s (satisfies D1). This is **outbound** (cc-ci → git.autonomic.zone, the
|
||||
reliably-working direction) and needs only **read**. It is the source of truth for triggering.
|
||||
- **Webhook (optional):** the bridge keeps its `/hook` endpoint so a Gitea `issue_comment` webhook,
|
||||
**if present**, gives lower latency. But the **server does NOT self-register webhooks** (that
|
||||
needs repo-admin, which we refuse to require). Registration is a **manual admin task, documented**
|
||||
in `docs/enroll-recipe.md` (URL `https://ci.commoninternet.net/hook`, event `issue_comment`,
|
||||
content-type `json`, the shared HMAC secret, and the note that the Gitea instance must allow the
|
||||
host). The two paths are mutually exclusive in effect; don't double-fire a comment seen by both.
|
||||
- (Webhook delivery on this instance was flaky early on — `last_status: None` — so polling being
|
||||
primary is also the robust choice, not just the low-privilege one.)
|
||||
- **Commenter auth via org membership (read-level — no admin).** The repo's explicit collaborator
|
||||
list is empty: the bot *and* the maintainers (`trav`/`notplants`) all reach the repo as
|
||||
`recipe-maintainers` **org members/owners**, so `GET /collaborators/{user}` 404s for everyone, and
|
||||
`GET /collaborators/{user}/permission` would authorize correctly but **requires repo-admin** — which
|
||||
we refuse. Instead authorize with **`GET /orgs/recipe-maintainers/members/{user}`** (204 = member =
|
||||
authorized; 404 = rejected) — readable by any **org member** (read-level), verified to admit
|
||||
`trav`/`notplants`/the bot and reject non-members. Note `public_members` is hidden here, so use the
|
||||
authenticated `members` endpoint (bot must be an org member, still read-level). Fail-closed on
|
||||
error. Zero-privilege fallback: a configured allowlist of usernames. (Still satisfies §6's
|
||||
non-collaborator-rejection check.)
|
||||
- Enrollment = adding the recipe to the bridge's **poll list** + ensuring a `tests/<recipe>/` dir
|
||||
exists. The bot needs only **read** on the recipe repo (+ comment-back to post status). Registering
|
||||
a webhook is **optional and operator/admin-side** (documented in `enroll-recipe.md`), never required
|
||||
for CI to work.
|
||||
- **Recipe mirror+PR flow (how a recipe gets a testable PR).** Recipe repos under test live on the
|
||||
**private mirror** `git.autonomic.zone/recipe-maintainers/<recipe>`, mirrored from the **official
|
||||
upstream `git.coopcloud.tech`**. To bring a recipe under CI: `abra recipe fetch <recipe>` (pulls
|
||||
|
||||
Reference in New Issue
Block a user