Skip to content

SCRUM-219 — Config: release history, rollback, promote, audit feed

Plan ref: CFG-B9 (docs/11-admin-plane-plan.md). Stacked on SCRUM-218. Completes the Config API surface in doc 02 §5.

Routes

Route Role Behaviour
GET /channels/{ch}/releases?before=&limit= viewer {head_release_id, releases:[{release_id, manifest_sha256, min_client_version, message, created_by, created_at, is_head}], next_before} newest first
POST /channels/{ch}/rollback {release_id, base_release_id} admin head locked; stale base → 409 stale_release; release not of this channel → 404; already the head → 409 no_changes. Moves the pointer only — no new release row. Audit release.rollback {from,to}, pg_notify
POST /channels/{ch}/promote?from=<ch> {base_release_id, message?} admin the ladder is dev → staging → live; from must be the channel directly below (live?from=staging, staging?from=dev), else 400. Head locked, source head share-locked; builds a new release for ch with the source's config/packs/min_client_version (shared builder with Publish); identical to the current head → 409 no_changes. Audit release.promote, pg_notify
GET /audit?cursor=&limit=&action=&actor=&from=&to= viewer {entries:[{id, at, actor_id, actor_name, source:"config", action, target, details}], next_cursor} — the fixed shape the Dashboard merges; cursor is opaque base64url of {"before":id}

How to verify

cd services/config
export CONFIG_TEST_DATABASE_URL='postgres://USER:PASS@127.0.0.1:5433/config_test?sslmode=disable'
go vet ./... && go test -race -count=1 ./...
go test -count=1 -v -run 'Rollback|Promote|ListReleases|ListAudit|ReleaseOps|Audit' ./internal/...
Test Proves
store.TestListReleasesPagingHeadAndIsHead paging, is_head, head_release_id
store.TestRollbackMovesPointerAuditsAndNotifies publish A then B on dev, roll back to A: head A, no new release row, one audit, NOTIFY dev; foreign release 404; to the head 409; stale base 409
store.TestPromoteCopiesContentAndNotifies staging?from=dev: new release, channel:"staging", new id, identical config/packs, sha parity with the jsonb re-derivation; again → 409 no_changes; NOTIFY staging
store.TestListAuditFiltersAndPaging ordering, action/actor/time filters, cursor paging
api.Test{ListReleases,Rollback,PromoteLadder,PromoteBody,Audit}Validation, TestAuditCursor* 400 cases incl. live?from=dev, malformed cursor
server.TestReleaseOpsThroughTheLiveServer the whole flow over HTTP with real JWTs; live_ops 403 on rollback/promote

Tests mutate only dev/staging (not live), under advisory locks, restoring heads (checked afterwards: dev/staging/live back on 1/2/3).

Results at time of writing

  • go vet, go test -race ./... against Postgres 16: pass (8 packages).

How it was built

DeepSeek run scoped (Landlock) to services/config (318 s, ~59k output tokens). Claude review: Rollback/Promote transactions read, suite run, head restoration checked. No changes needed.