Drift detection: model.Service.Drifted() compares desired and running version by digest (tag when the digest is unknown). services.awaiting_inventory hides drift between a job result and the next inventory write.
Poll and webhook jobs are refused for a drifted service; POST /services/{id}/restore (manual job to desired_ref) and POST /services/{id}/accept (running version becomes desired, guarded by actual = Service.RunningRef).
Move suggestions (store.MoveCandidates, Compose only) and POST /services/{id}/merge: the old row keeps its id, mode, desired_ref and history and takes the new location; the duplicate is deleted.
Backend only. The buttons and badges come with #28, on top of the Ledger UI (#18).
go test -race ./internal/agent/... ./internal/master/... ./internal/e2e/
go test -tags integration ./internal/master/store/ against postgres:17-alpine
On the server: migration 0005 applies; no drift shown after a normal update
On the server: manual docker compose up with another tag gives drift; webhook for an auto service is refused
On the server: restore and accept via POST; merge after moving a stack
## Summary
- Drift detection: `model.Service.Drifted()` compares desired and running version by digest (tag when the digest is unknown). `services.awaiting_inventory` hides drift between a job result and the next inventory write.
- Poll and webhook jobs are refused for a drifted service; `POST /services/{id}/restore` (manual job to `desired_ref`) and `POST /services/{id}/accept` (running version becomes desired, guarded by `actual` = `Service.RunningRef`).
- Move suggestions (`store.MoveCandidates`, Compose only) and `POST /services/{id}/merge`: the old row keeps its id, mode, `desired_ref` and history and takes the new location; the duplicate is deleted.
- Backend only. The buttons and badges come with #28, on top of the Ledger UI (#18).
Closes #10
## Test plan
- [x] `gofmt -l .`, `go vet ./...`, `go test ./...`
- [x] `go test -race ./internal/agent/... ./internal/master/... ./internal/e2e/`
- [x] `go test -tags integration ./internal/master/store/` against postgres:17-alpine
- [ ] On the server: migration 0005 applies; no drift shown after a normal update
- [ ] On the server: manual `docker compose up` with another tag gives drift; webhook for an auto service is refused
- [ ] On the server: restore and accept via POST; merge after moving a stack
Adds services.awaiting_inventory, Service.Drifted/RunsRef/RunningRef, AcceptRunning, MoveCandidates, MergeSources and MergeService, and maps a job for a deleted service to ErrNotFound.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
model.Service.Drifted()compares desired and running version by digest (tag when the digest is unknown).services.awaiting_inventoryhides drift between a job result and the next inventory write.POST /services/{id}/restore(manual job todesired_ref) andPOST /services/{id}/accept(running version becomes desired, guarded byactual=Service.RunningRef).store.MoveCandidates, Compose only) andPOST /services/{id}/merge: the old row keeps its id, mode,desired_refand history and takes the new location; the duplicate is deleted.Closes #10
Test plan
gofmt -l .,go vet ./...,go test ./...go test -race ./internal/agent/... ./internal/master/... ./internal/e2e/go test -tags integration ./internal/master/store/against postgres:17-alpinedocker compose upwith another tag gives drift; webhook for an auto service is refused