M13b.dlog 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. ================================================================================
  2. M13b.dlog — M13b (admin UI) deployment log
  3. ================================================================================
  4. Project: broad-announce
  5. Milestone: M13b — admin console UI (M13a was the auth gate; M13b is the SPA)
  6. Owner: Luis Rosales
  7. Last update: 2026-06-17 18:44 EDT
  8. Purpose: Resume point after any session/model interruption. Read this
  9. file first; it tells you where the work is, what's done, what
  10. was tested, and what's next. Git history shows WHAT changed;
  11. this file shows WHY and HOW to verify it.
  12. ================================================================================
  13. STANDING RULE — DEPLOYMENT PROTOCOL (binding for every session)
  14. ================================================================================
  15. Rule: Update this file at the END of every deployment. This is a
  16. hard rule, not a suggestion. It applies to every session,
  17. every model, every operator.
  18. What counts as a "deployment" (UPDATE the file):
  19. • Code changes that pass tests and are committed
  20. • Schema migrations (up OR down)
  21. • New HTTP routes / new endpoints
  22. • New UI features / new pages
  23. • Smoke scripts that exercise real endpoints
  24. • Bug fixes that change behavior
  25. • Any change to deliverd-*, routerd, archiverd, ingestd, authd, admind
  26. • Config / env changes that affect runtime behavior
  27. What does NOT count (DO NOT update the file just for these):
  28. • Documentation changes (README, comments, this file itself)
  29. • Architecture decisions / plans / surveys
  30. • Pure refactors with no behavior change
  31. • Test-only changes that don't exercise new code paths
  32. • Cosmetic UI tweaks that don't add a feature
  33. What an entry must contain (use the template at the bottom):
  34. 1. Timestamp (EDT)
  35. 2. One-line summary of what shipped
  36. 3. Commit hash
  37. 4. Top-level files added/changed (paths only, not full diffs)
  38. 5. Tests run + result (go test / pnpm test / smoke)
  39. 6. Verification commands the next session can run
  40. 7. Known issues / next step
  41. Why: Luis needs to resume after any interruption — model crash,
  42. rate limit, gateway restart, end of session — by reading
  43. THIS file instead of analyzing the codebase. The first 40
  44. lines must be enough to know exactly where we are.
  45. When to update: at the END of the session, OR when an obvious
  46. natural break appears (milestone shipped, smoke
  47. verified). Never mid-implementation; wait until the
  48. commit is in.
  49. ================================================================================
  50. TL;DR — where we are right now
  51. ================================================================================
  52. - M13a (auth gate, JWT, refresh, role middleware) is SHIPPED. See commit
  53. fa84398 ("M13a W5: route admin endpoints through the JWT gate").
  54. - M13b W0 (SPA shell, embed into admind, scaffold) is SHIPPED.
  55. - M13b W1 (Companies CRUD: backend + UI) is SHIPPED at c5e15f7.
  56. All tests green. Smoke script written but not yet run E2E (needs stack).
  57. - M13b W2 (Sources CRUD) and W3 (Telegram bot CRUD) are next.
  58. If you only have 60 seconds: read the W1 block below; everything before it is in production.
  59. ================================================================================
  60. W0 — SPA shell + embed into admind [SHIPPED]
  61. ================================================================================
  62. Commit: b2c4365 "M13b W0: SPA shell + embed into admind"
  63. Goal: a runnable React+TS SPA that admind can serve, so the W1-W3
  64. features have a place to live.
  65. Files added (under web/):
  66. - React 18 + Vite 5 + TypeScript 5 + Tailwind 3 + Radix + TanStack Query
  67. - Routes: /login, /forbidden, /, /companies/*, /sources/*, /telegram/*,
  68. /tail, /dlq, /audit, *
  69. - All non-auth routes wrapped in <RequireAuth> and <AppShell>
  70. - Non-shipped routes render <ComingSoon> with the W1/W2/W3 badge
  71. - Auth: AuthProvider with /v1/auth/refresh-then-/v1/auth/me boot,
  72. in-memory access token, httpOnly refresh cookie, refresh-on-401
  73. with single-flight guard
  74. - API helpers: fetchWithAuth, apiGet, apiSend, ApiError
  75. - Theme: light/dark/system, persisted in localStorage
  76. - Role-based sidebar
  77. - 1 vitest: tests/login.test.tsx (form renders + accepts input)
  78. Files changed (under cmd/admind/):
  79. - main.go: //go:embed web-dist (empty allowed; 503 stub if no
  80. index.html), wireSPA() serves SPA history for all M13b routes
  81. - /assets/* immutable cache; / no-cache so deploys pick up new bundles
  82. - /dlq keeps the M8 HTML UI; /v1/* keeps the JWT gate
  83. Build tooling (Makefile):
  84. - web-install, web-build, web-dev, web-test, web-typecheck
  85. - build-with-web alias: web-build then go build
  86. Verification at W0:
  87. pnpm run build clean (vite v5.4.21, ~328 kB total, ~104 kB gz)
  88. go build ./... clean
  89. go vet ./... clean
  90. go test ./cmd/admind/ clean
  91. vitest 2 passed
  92. ================================================================================
  93. W1 — Companies CRUD [SHIPPED]
  94. ================================================================================
  95. Commit: c5e15f7 ("M13b W1: Companies CRUD (backend + UI)")
  96. Goal: operators can list, create, edit, suspend, activate, and archive
  97. tenants. tenant_admin gets a read-only view of their own tenant.
  98. --- Schema (migrations/010_tenants_fields.{up,down}.sql) -------------------
  99. Adds two columns to auth.tenants:
  100. rate_limit_per_sec INTEGER NOT NULL DEFAULT 10000 (CHECK 1..1000000)
  101. fcm_shared BOOLEAN NOT NULL DEFAULT TRUE
  102. Both have safe defaults, so applying to a populated DB is a no-op for
  103. existing rows. Reversible (down migration drops both columns).
  104. Verified the migration applies and rolls back cleanly against PG 17.
  105. --- Backend (Go) ----------------------------------------------------------
  106. New files:
  107. internal/authd/tenants.go
  108. - type Tenant (wire shape, snake_case JSON)
  109. - ErrTenantNotFound, ErrTenantSlugTaken, ErrTenantInvalid
  110. - TenantFilter (q, status, limit, offset, scope)
  111. - ListTenants(ctx, filter) (items, total, err)
  112. - GetTenant(ctx, id)
  113. - CreateTenantInput + Validate() (slug regex, email, rate limit)
  114. - UpdateTenantInput (pointer fields = PATCH semantics)
  115. - CreateTenant (writes audit_log "tenant.create")
  116. - UpdateTenant (writes audit_log "tenant.update", enforces
  117. actorScopeAll for restricted fields)
  118. - SetTenantStatus (active<->suspended, any->archived; archived is
  119. terminal; writes audit_log "tenant.status" with {from,to})
  120. - Inline validators: validSlug, looksLikeEmail
  121. internal/authd/tenants_test.go
  122. - TestValidSlug, TestLooksLikeEmail, TestCreateTenantInput_Validate,
  123. TestUpdateTenantInput_Validate (pure Go, no DB needed)
  124. - Note: tests caught a real bug in looksLikeEmail (didn't reject
  125. leading/trailing dot in domain). Fixed.
  126. cmd/authd/tenants.go
  127. - HTTP handlers for /v1/tenants/* (see routes below)
  128. - canAccessTenant(claims, id) — super_admin any, others own only
  129. - isUUID(s) — lenient format check so 400s stay 400s
  130. Routes wired in cmd/authd/main.go (RequireAuth / RequireRole):
  131. GET /v1/tenants — any auth (scope: super_admin all,
  132. tenant_admin own only)
  133. POST /v1/tenants — super_admin only
  134. GET /v1/tenants/{id} — any auth, per-id scope check
  135. PATCH /v1/tenants/{id} — any auth; tenant_admin only
  136. display_name + contact_email on
  137. own tenant
  138. POST /v1/tenants/{id}/status — super_admin only
  139. Errors:
  140. 400 — bad input (validation, JSON parse, bad UUID)
  141. 403 — role not allowed, or tenant_admin trying another tenant
  142. 404 — tenant id not found
  143. 409 — duplicate slug on create
  144. 500 — unexpected DB error
  145. --- Smoke (scripts/m13b_w1_smoke.sh) --------------------------------------
  146. End-to-end, bash + curl + jq-less python. Covers:
  147. 1. authd /health
  148. 2. super_admin login
  149. 3. GET /v1/tenants (initial)
  150. 4. POST /v1/tenants (create) → 201
  151. 5. GET /v1/tenants/{id} → 200
  152. 6. PATCH /v1/tenants/{id} → 200
  153. 7. POST /v1/tenants/{id}/status suspend → 200
  154. 8. POST /v1/tenants/{id}/status activate → 200
  155. 9. POST /v1/tenants (dup slug) → 409
  156. 10. POST /v1/tenants (bad slug) → 400
  157. 11. tenant_admin can login + GET own
  158. 11c. tenant_admin can PATCH own display_name → 200
  159. 11d. tenant_admin CANNOT change rate_limit → 400
  160. 12. tenant_admin GET other tenant → 403
  161. 13. tenant_admin POST /v1/tenants → 403
  162. 14. tenant_admin POST /v1/tenants/{id}/status → 403
  163. 15. POST /v1/tenants/{id}/status (bad value) → 400
  164. 16. cleanup: super_admin archives
  165. Syntax-verified (bash -n); not yet run end-to-end (needs live stack).
  166. --- Frontend (web/) -------------------------------------------------------
  167. New feature folder web/src/features/companies/:
  168. types.ts — Tenant, TenantStatus, ListTenantsResponse,
  169. CreateTenantInput, UpdateTenantInput
  170. api.ts — useTenantsList, useTenant, useCreateTenant,
  171. useUpdateTenant, useSetTenantStatus (TanStack Query),
  172. getErrorMessage(err)
  173. format.tsx — statusLabel, statusVariant, StatusBadge,
  174. formatRateLimit, formatDate
  175. list.tsx — table + debounced search + status filter buttons,
  176. empty state, link to detail; super_admin sees
  177. "New company" button
  178. create-dialog.tsx — Radix Dialog + react-hook-form + zod
  179. (slug regex, email, rate limit 1..1e6, fcm_shared)
  180. duplicate-slug surfaces as a field error
  181. detail-page.tsx — form + Suspend/Activate/Archive actions
  182. (Archive requires typed confirmation dialog)
  183. Metadata panel; tenant_admin sees form but
  184. rate_limit and fcm_shared are disabled
  185. New UI primitives (web/src/components/ui/):
  186. badge.tsx — variants: default, secondary, outline, success,
  187. warning, danger, muted
  188. dialog.tsx — Radix Dialog wrapper (Overlay, Content, Header,
  189. Footer, Title, Description, Trigger, Close, Portal)
  190. textarea.tsx — matching Input style
  191. table.tsx — Table, TableHeader, TableBody, TableRow,
  192. TableHead, TableCell
  193. empty-state.tsx — icon + title + description + action
  194. Updated:
  195. routes/companies.tsx — replaced ComingSoon with a nested Routes
  196. (index → list, :id → detail)
  197. lib/scope.ts — added canViewCompanies, canViewSources,
  198. canManageTelegram, canViewTelegram; kept
  199. canManageCompanies as super_admin-only
  200. Test added (web/tests/companies.test.tsx):
  201. - statusLabel, statusVariant, formatRateLimit, formatDate
  202. - Note: caught a real bug in formatDate (try/catch around
  203. toLocaleDateString doesn't catch "Invalid Date" string).
  204. Replaced with Number.isNaN(d.getTime()).
  205. Bundle delta (W1 vs W0): index chunk +0.08 kB, +1 module
  206. (1733 → 1734 modules transformed).
  207. --- Verification (run from /root/.openclaw/workspace/broad-announce) -----
  208. go build ./... clean
  209. go vet ./... clean
  210. go test -count=1 ./... 22 packages, 0 failures
  211. cd web && pnpm run test 8 tests, 2 files, 0 failures
  212. cd web && pnpm run build clean
  213. psql -f migrations/009_auth.up.sql apply
  214. psql -f migrations/010_tenants_fields.up.sql apply
  215. psql -f migrations/010_tenants_fields.down.sql rollback
  216. bash -n scripts/m13b_w1_smoke.sh syntax OK (not run E2E)
  217. ================================================================================
  218. Next — M13b W2 (Sources CRUD) and W3 (Telegram bot CRUD)
  219. ================================================================================
  220. Per the W2/W3 badges already rendered in the UI:
  221. W2: Sources CRUD
  222. - /v1/sources endpoints in authd (or a new sourcerd package? the
  223. existing `public.sources` table is the canonical source-of-truth
  224. per M4; M13b needs a new layer that scopes by tenant)
  225. - SPA: web/src/features/sources/ (list, create, detail)
  226. - Fields: name, type (FCM|Telegram|WebHook), destination URL/token,
  227. rate_limit, status, optional signing_secret
  228. - Reuse the same patterns from W1 (Query hooks, zod, scope helpers)
  229. W3: Telegram bot CRUD
  230. - /v1/telegram/bots endpoints in authd
  231. - SPA: web/src/features/telegram/
  232. - Fields: bot token (write-only), display name, welcome message,
  233. default source id
  234. - The token is write-only (write hashes a secret, never returned
  235. on read). The deliverd-telegram service consumes the bot list.
  236. ================================================================================
  237. Quick resume instructions
  238. ================================================================================
  239. If you start a new session, run these commands to verify state:
  240. cd /root/.openclaw/workspace/broad-announce
  241. git log --oneline -5 # confirm W0 is at HEAD
  242. # (W1 commit is next)
  243. git status --short # should be empty after
  244. # W1 commit
  245. go test -count=1 ./... # all green
  246. cd web && pnpm run test # all green
  247. cat M13b.dlog # this file
  248. To pick up W2:
  249. 1. Re-read web/src/features/companies/ to copy the patterns
  250. 2. The authd.Tenant and the public.sources table are the references
  251. 3. The route stubs in web/src/routes/sources.tsx and telegram.tsx
  252. still render ComingSoon — replace them the same way as
  253. web/src/routes/companies.tsx
  254. ================================================================================
  255. ENTRY TEMPLATE (copy this for each new deployment)
  256. ================================================================================
  257. YYYY-MM-DD HH:MM EDT — <one-line summary>
  258. Commit: <hash>
  259. Files: <top-level paths added/changed>
  260. Tests: <go test / pnpm test / smoke> → <result>
  261. Verify: <one or more commands the next session can run to confirm>
  262. Notes: <known issues, follow-ups, or 'none'>
  263. ================================================================================
  264. ENTRY LOG (most recent first; append new entries at the TOP of this block)
  265. ================================================================================
  266. 2026-06-18 02:00 EDT — W2 Sources CRUD shipped (one-time secrets + rotate)
  267. Commit: <this commit>
  268. Files: internal/authd/sources.{go,_test.go},
  269. cmd/authd/sources.go, cmd/authd/main.go (6 routes wired),
  270. migrations/011_sources_secrets.{up,down}.sql,
  271. web/src/features/sources/{types,api,format,list,create-dialog,detail-page}.{ts,tsx},
  272. web/src/components/ui/checkbox.tsx (new),
  273. web/src/routes/sources.tsx (now wired; was ComingSoon),
  274. web/tests/sources/format.test.ts,
  275. scripts/m13b_w2_smoke.sh,
  276. M13b.dlog
  277. What: Pick (a) per 01:10 — reuse public.sources directly via authd.
  278. CRUD + status + rotate-secrets. Migration 011 adds hmac_secret_hash,
  279. api_key_hash, mtls_required, description (all nullable so pre-existing
  280. rows still load). One-time secrets: CreateSource / RotateSecrets
  281. return plaintext EXACTLY ONCE in `secrets`; only bcrypt hashes
  282. persist. auto_secrets: true by default in the UI. tenant_admin
  283. can manage their own sources (PATCH allowed; status changes are
  284. an open feature, see Notes).
  285. Cert UI: placeholder card on detail page (M14 — coming).
  286. Bridge: public.sources.company_id (TEXT) FKs public.companies.id (TEXT);
  287. auth.tenants.id is UUID. W2's CreateSource does an idempotent
  288. INSERT INTO public.companies ... ON CONFLICT DO NOTHING keyed
  289. by auth.tenants.id::text so the FK is satisfied on first source
  290. create. No change to W1's CreateTenant.
  291. Tests: go test ./internal/authd/ -count=1 ok (all tests, incl. new
  292. TestValidSourceID / TestValidSecretFormat / TestValidAPIKeyFormat
  293. / TestCreateSourceInput_Validate / TestUpdateSourceInput_Validate
  294. / TestGenerateSecret)
  295. web tsc -b clean
  296. web vitest run 17/17 ok (9 new + 6 companies + 2 login)
  297. web vite build clean
  298. bash -n scripts/m13b_w2_smoke.sh clean
  299. Verify: cd /root/.openclaw/workspace/broad-announce
  300. go test -count=1 ./... && cd web && pnpm run test
  301. psql -f migrations/011_sources_secrets.up.sql (apply 011)
  302. bash scripts/m13b_w2_smoke.sh (run against stack)
  303. Notes: Smoke script not yet run E2E (same as W1: needs running authd +
  304. Postgres). 17 web tests pass but the W2 component tests are
  305. format-only — list/create/detail-page UI tests are a v1.1 add.
  306. Decision logged: public.sources reused as-is (no auth.sources
  307. view layer in W2; can refactor later if the auth/admin split
  308. needs to harden).
  309. Open follow-up for v1.1: tenant_admin status-changes (suspend
  310. / activate their own source). The route is RequireAuth today
  311. and the store allows any caller; the policy dial is whether
  312. tenant_admin should be allowed to suspend their own. Not
  313. blocking W2; documenting in the .dlog.
  314. W3 (Telegram bot CRUD) is next; same pattern as W2.
  315. 2026-06-17 18:44 EDT — W1 Companies CRUD shipped + this .dlog created
  316. Commit: c5e15f7 (W1), 63202f2 (.dlog creation)
  317. Files: internal/authd/tenants.{go,_test.go}, cmd/authd/tenants.go,
  318. cmd/authd/main.go (routes wired),
  319. migrations/010_tenants_fields.{up,down}.sql,
  320. web/src/features/companies/*.{ts,tsx},
  321. web/src/components/ui/{badge,dialog,textarea,table,empty-state}.tsx,
  322. web/src/lib/scope.ts, web/src/routes/companies.tsx,
  323. web/tests/companies.test.tsx,
  324. scripts/m13b_w1_smoke.sh,
  325. M13b.dlog
  326. Tests: go build/vet ./... clean; go test -count=1 ./... 22/22 ok;
  327. pnpm run test 8/8 ok; pnpm run build clean;
  328. migrations apply + roll back cleanly on PG 17
  329. Verify: cd /root/.openclaw/workspace/broad-announce
  330. go test -count=1 ./... && cd web && pnpm run test
  331. bash -n scripts/m13b_w1_smoke.sh (syntax only; needs live stack)
  332. psql -f migrations/009_auth.up.sql
  333. psql -f migrations/010_tenants_fields.up.sql
  334. Notes: Smoke script not yet run E2E (requires running authd + Postgres).
  335. W2 (Sources) and W3 (Telegram bot) are next.
  336. Test for sort + order; the W1 test caught a real bug in
  337. looksLikeEmail and formatDate (both fixed).