Merge origin/main (phase 5, drift and merge) into feat/ledger-ui-services-agents-18
Conflicts: - CLAUDE.md: keep both sets of gotchas (drift/merge from main, SSE ping and swarm_cluster_id from this branch). - internal/imageref/imageref_test.go: keep both TestIsDigest (main) and TestIsRegistryHost (this branch). - internal/master/web/server.go: take main's renderServiceError and make it render through s.page, so service error pages get the sidebar nav data. Also renumber this branch's migration 0005_agent_cluster.sql to 0006_agent_cluster.sql, after main's 0005_drift_merge.sql. Claude-Session: https://claude.ai/code/session_01GAZsp3P7Qto6JnhipxWUJQ
This commit is contained in:
@@ -65,6 +65,11 @@ DATABASE_URL=postgres://... go test -tags integration ./internal/master/store/
|
||||
list in `known_jobs`, and only then accepts jobs for that connection.
|
||||
- **The webhook never creates services; new services always start `manual`.** The upsert
|
||||
does not touch `update_mode`.
|
||||
- **Drift is never corrected automatically; poll/webhook jobs are refused for a drifted
|
||||
service** (`jobs.Start`, `ReasonDrifted`). Only a manual job, `RestoreDesired` or an
|
||||
accept gets it out.
|
||||
- **Merging is never automatic; merge is the only path that deletes a `services` row**
|
||||
(`store.MergeService`). The old row keeps its id and takes the duplicate's location.
|
||||
- **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.
|
||||
@@ -139,6 +144,25 @@ DATABASE_URL=postgres://... go test -tags integration ./internal/master/store/
|
||||
newer suggestion, and a digest-pinned pick does not clear a digest-less (webhook) one.
|
||||
- **Job output is capped at `protocol.MaxJobOutput` (128 KiB, tail).** NUL bytes are
|
||||
stripped by the hub because Postgres text rejects them.
|
||||
- **Drift compares by digest, not by string** (`model.RunsRef`): `repo:tag@D` is run by
|
||||
`repo:tag` reporting digest D. Same digest under another tag is not drift. Different
|
||||
repo strings (`nginx` vs `docker.io/library/nginx`) are.
|
||||
- **`services.awaiting_inventory`** is true from a successful `FinishJob` until the next
|
||||
inventory write for the row; `Service.Drifted()` is false meanwhile, since `desired_ref`
|
||||
is ahead of `actual_ref` for a debounce interval after every job.
|
||||
- **Accept never touches `last_tag_digest`, `available_*` or `update_mode`**, and the
|
||||
`actual` form field (`Service.RunningRef()`, ref plus digest) is only a guard against a
|
||||
changed service; the value written comes from the row. `RestoreDesired` sets
|
||||
`last_tag_digest` to the drifted digest when it is another digest of the desired tag and
|
||||
`last_tag_digest` is empty or equals the desired digest (otherwise it holds back a newer
|
||||
digest and stays).
|
||||
- **`awaiting_inventory` can hide real drift** until the next inventory change when a job
|
||||
result arrives after the snapshot that shows the drift (an agent reconnect resends its
|
||||
outbox after the snapshot), or when Swarm coalesces an update and a rollback into one
|
||||
debounce interval. This is accepted.
|
||||
- **Merge lock order is agent, then service rows by id** (as in inventory writes), and the
|
||||
duplicate row is deleted before the old row is updated, because `location_key` is unique
|
||||
and not deferrable. A removal racing a merge can be lost; the next snapshot fixes it.
|
||||
- **The SSE stream sends a named `ping` event every 25 s**; the sidebar Live indicator relies on it.
|
||||
- **Agents persist `swarm_cluster_id`.** Only managers report it, so workers show no role.
|
||||
|
||||
@@ -149,6 +173,7 @@ DATABASE_URL=postgres://... go test -tags integration ./internal/master/store/
|
||||
- Phase 2 plan: `docs/superpowers/plans/2026-10-12-master-agent-phase-2-jobs.md`
|
||||
- Phase 3 plan: `docs/superpowers/plans/2026-10-12-master-agent-phase-3-webhook.md`
|
||||
- Phase 4 plan: `docs/superpowers/plans/2026-10-13-master-agent-phase-4-polling.md`
|
||||
- Phase 5 plan: `docs/superpowers/plans/2026-10-13-master-agent-phase-5-drift-merge.md`
|
||||
- Original single-instance design (historical):
|
||||
`docs/superpowers/specs/2026-05-22-package-updater-design.md`
|
||||
- Consumer-side CI integration: `gitea-action/` (calls the master's `POST /update`)
|
||||
|
||||
Reference in New Issue
Block a user