How to prove the W5 milestone (JWT gate on routerd / archiverd / deliverd-fcm / deliverd-telegram) is done.
W3 shipped the JWT gate on admind and the shared
authd.NewFromEnv() helper, but left the other four
HTTP services out-of-scope (they were NATS-only consumers
with no admin HTTP). W5 picks up the work: add a real
admin route to each service, gate it with the same JWT
middleware, ship tests and smoke coverage.
Concretely, the W5 exit criteria are:
cmd/routerd, cmd/archiverd,
cmd/deliverd-fcm, cmd/deliverd-telegram has a
wireAdminRoutes(mux, …, logger) helper that:
BA_AUTHD_JWT_SECRET is unset (the LAN
deploy path keeps working unchanged).ad.RequireAuth /
ad.RequireRole when the secret is set.routerd: GET /v1/admin/dedupe/state (read) +
POST /v1/admin/dedupe/flush (destructive, admin role).archiverd: POST /v1/admin/archiver/run (any
authenticated user; the archiver is idempotent).deliverd-fcm: GET /v1/admin/dlq +
GET /v1/admin/dlq/{id} (channel=fcm, cross-channel
safety: non-fcm row returns 404).deliverd-telegram: same shape (channel=telegram).dedupe.Collapser. archiverd has a coalescing test
(pre-filled channel → 202).go build ./..., go vet ./..., go test -count=1 ./...
all clean.scripts/m13a_smoke.sh covers the W5 routes end-to-end
against a running stack (checks 9–12).docker-compose.yml passes BA_AUTHD_JWT_SECRET to all
four new services (sharing the same secret authd uses).cd /root/.openclaw/workspace/broad-announce
go build ./...
go vet ./...
go test -count=1 ./cmd/routerd ./cmd/archiverd ./cmd/deliverd-fcm \
./cmd/deliverd-telegram ./internal/dlq
Expected:
ok git3.techno-world.net/lrosales/broad-announce/cmd/routerd
ok git3.techno-world.net/lrosales/broad-announce/cmd/archiverd
ok git3.techno-world.net/lrosales/broad-announce/cmd/deliverd-fcm
ok git3.techno-world.net/lrosales/broad-announce/cmd/deliverd-telegram
? git3.techno-world.net/lrosales/broad-announce/internal/dlq [no test files]
Test counts:
| Package | Tests | Subtests | Notes |
|---|---|---|---|
| cmd/routerd | 5 | 4 | incl. end-to-end Collapser flush |
| cmd/archiverd | 5 | 3 | incl. coalescing path |
| cmd/deliverd-fcm | 3 | 3 | any-authenticated-user can read |
| cmd/deliverd-telegram | 3 | 3 | any-authenticated-user can read |
| Total new (W5) | 16 | 13 |
cd /root/.openclaw/workspace/broad-announce
go test -count=1 -v -run 'TestWireAdminRoutes|TestAdminDedupe|TestAdminRunNow|TestAdminRolePolicy|TestAdminDLQ' \
./cmd/routerd ./cmd/archiverd ./cmd/deliverd-fcm ./cmd/deliverd-telegram
Each of the four services should show:
TestWireAdminRoutes_NoSecret_Disabled — when
BA_AUTHD_JWT_SECRET is unset, the admin routes return
404 (they're not registered). This is the LAN-deploy
backward-compat path.TestWireAdminRoutes_WithSecret_Gated — when the secret
is set, the admin routes return 401 without a token.routerd adds:
TestAdminDedupeState_Auth — an authenticated user can
read the dedupe state JSON.TestAdminRolePolicy_ViewerReadOnly_AdminCanFlush —
viewer can read state (200), cannot flush (403);
tenant_admin and super_admin can flush (200).TestAdminDedupeFlush_DrainsPending — end-to-end: a
pending collapse is observed, the handler is called, the
pending count drops to 0.archiverd adds:
TestAdminRunNow_Auth_Fires — an authenticated user can
trigger a run; the trigger channel receives the signal.TestAdminRunNow_Coalesces — a second concurrent
request returns 202 with triggered=false,
reason=already_pending.TestAdminRolePolicy_ViewerCanTrigger — viewer,
tenant_admin, super_admin can all fire (idempotent op).deliverd-fcm and deliverd-telegram add:
TestAdminDLQ_AnyAuthenticatedUser_CanList — viewer,
tenant_admin, super_admin can all read the per-channel
DLQ (the gate passes; the handler then runs the SQL
query).cd /root/.openclaw/workspace/broad-announce
docker compose up -d --build
bash scripts/bootstrap-super-admin.sh
bash scripts/m13a_smoke.sh
Expected: all 12 sections green, summary shows
$PASS passed, $FAIL failed with 0 failed.
The W5-specific checks (9–12) verify:
/v1/admin/dedupe/state and
/v1/admin/dedupe/flush:
/v1/admin/archiver/run:
/v1/admin/dlq:
"channel":"fcm"./v1/admin/dlq:
"channel":"telegram".To confirm the W5 gate doesn't break the LAN-only deploy
(no BA_AUTHD_JWT_SECRET):
cd /root/.openclaw/workspace/broad-announce
# Comment out the BA_AUTHD_JWT_SECRET env on the four
# new services in docker-compose.yml (or just leave it
# empty), then:
docker compose up -d --build
curl -s -o /dev/null -w "%{http_code}\n" \
http://127.0.0.1:8801/v1/admin/dedupe/state
# expect: 404 (route unregistered)
The internal/dlq/query.go package is the shared SQL
helper used by both per-channel admin endpoints. The
List query has a 30-day hard cap on created_at (same
as admind's global DLQ endpoint). The Get query
returns the full row including payload, with a
pgx.ErrNoRows → (nil, nil) idiom for idempotent
callers.
The archiverd runLoop got a new triggerCh parameter
(buffered to size 1). The loop now selects on
{ctx.Done, tick.C, triggerCh}. The default cadence
(BA_ARCHIVERD_RUN_EVERY_SECONDS, 3600s) is unchanged;
the admin route is a side door that fires a run on
demand.
The cross-channel safety net in the per-channel DLQ
endpoints (row.Channel != "fcm" → 404) prevents a
deliverd-fcm from accidentally exposing telegram-channel
DLQ rows. The global /v1/dlq in admind doesn't need
this; it serves the full cross-channel view.
M13a_PLAN.md § W5 — the design rationale and exit
criteria.scripts/m13a_smoke.sh — the E2E smoke, including the
W5 checks 9–12.internal/authd/middleware.go — RequireAuth and
RequireRole (W2). The W5 admin routes are thin
wrappers around these.