docs: webhook contract, auto mode and phase 3 plan (#8)

This commit is contained in:
2026-10-12 20:43:11 +02:00
parent 71d66846f2
commit f25a7a7779
5 changed files with 274 additions and 16 deletions
+18 -5
View File
@@ -37,6 +37,7 @@ DATABASE_URL=postgres://... go test -tags integration ./internal/master/store/
- `internal/master/store` — Postgres access and migrations
- `internal/master/hub` — agent connections: auth, per-connection writer, routing, job reconcile on hello
- `internal/master/jobs` — decides whether a service may be updated and starts a job on the right agent
- `internal/master/webhook` — `POST /update`: records the available version, starts jobs for auto services, waits for them
- `internal/master/web`, `webauth` — HTMX UI behind OIDC login, SSE signals
- `internal/master/events` — in-process publish/subscribe for UI refresh signals
- `internal/master/model` — domain types shared by the above
@@ -59,7 +60,10 @@ DATABASE_URL=postgres://... go test -tags integration ./internal/master/store/
- **Unknown message types are logged and ignored; malformed messages close the connection.**
- **Reconcile before routable**: the hub fails a reconnecting agent's jobs it does not
list in `known_jobs`, and only then accepts jobs for that connection.
- **Auth**: agents by token over pinned TLS; people by OIDC. Every state-changing UI
- **The webhook never creates services; new services always start `manual`.** The upsert
does not touch `update_mode`.
- **Auth**: agents by token over pinned TLS; people by OIDC; CI by the `UPDATER_API_KEY`
bearer token on `POST /update` only. Every state-changing UI
request is a POST with a CSRF token.
## Gotchas
@@ -89,7 +93,16 @@ DATABASE_URL=postgres://... go test -tags integration ./internal/master/store/
- **Swarm "succeeded" means the daemon accepted the new spec**, not that tasks converged.
- **Swarm updates need manager-node API access** for the agent doing the update.
- **The image's default entrypoint is `updater-agent`.** The master overrides it with
`updater-master`. There is no `POST /update` until phase 3.
`updater-master`.
- **The webhook waits via a broker signal plus a store re-read, with a fallback poll**
(the broker drops signals for slow subscribers). Marking and starting jobs runs on
`context.WithoutCancel`, so a client that disconnects cannot leave a queued job no agent
knows about; only the waiting stops. The master never fails or cancels a timed-out job.
- **A webhook job's target is `<image_repo>:<request tag>`**, built from the service's own
repo, never from the request's image string. With an explicit target the "follows no tag"
refusal does not apply; removed, self, built and container still do.
- **`available_*` is cleared by `FinishJob`** on success when `available_at <= job.created_at`
(a job created after the suggestion deployed the newest version).
- **Job output is capped at `protocol.MaxJobOutput` (128 KiB, tail).** NUL bytes are
stripped by the hub because Postgres text rejects them.
@@ -98,10 +111,10 @@ DATABASE_URL=postgres://... go test -tags integration ./internal/master/store/
- Master/agent spec: `docs/superpowers/specs/2026-10-12-master-agent-design.md` (authoritative)
- Phase 1 plan: `docs/superpowers/plans/2026-10-12-master-agent-phase-1-inventory.md`
- Phase 2 plan: `docs/superpowers/plans/2026-10-12-master-agent-phase-2-jobs.md`
- Original single-instance design (historical, its webhook is gone until phase 3):
- Phase 3 plan: `docs/superpowers/plans/2026-10-12-master-agent-phase-3-webhook.md`
- Original single-instance design (historical):
`docs/superpowers/specs/2026-05-22-package-updater-design.md`
- Consumer-side CI integration: `gitea-action/` (calls `POST /update`, which no longer
exists until phase 3)
- Consumer-side CI integration: `gitea-action/` (calls the master's `POST /update`)
## Conventions