M13b.dlog 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  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. - M13b W2 (Sources CRUD: backend + UI) is SHIPPED at f618098.
  57. - M13b W1 (Companies CRUD: backend + UI) is SHIPPED at c5e15f7.
  58. - M13b W2 (Sources CRUD: backend + UI) is SHIPPED at f618098.
  59. - M13b W3 (Telegram bot CRUD: backend + UI) is SHIPPED.
  60. - M13b W4 (smoke + verification) is SHIPPED.
  61. make m13b-smoke green 3 consecutive runs, 18/18 OK each.
  62. M13b_VERIFICATION.md published; M13a auth gate not regressed.
  63. Second cross-tenant leak caught by W4 (sources list, same pattern
  64. as W3 telegram-bots list). Both fixed in this milestone.
  65. W4 plan steps 7/10 (ingestd) and 14 (list invites) are skipped/
  66. adapted — ingestd requires the full stack (NATS not in dev); the
  67. invite list endpoint is a v1.1 add.
  68. If you only have 60 seconds: read the W1 block below; everything before it is in production.
  69. ================================================================================
  70. W0 — SPA shell + embed into admind [SHIPPED]
  71. ================================================================================
  72. Commit: b2c4365 "M13b W0: SPA shell + embed into admind"
  73. Goal: a runnable React+TS SPA that admind can serve, so the W1-W3
  74. features have a place to live.
  75. Files added (under web/):
  76. - React 18 + Vite 5 + TypeScript 5 + Tailwind 3 + Radix + TanStack Query
  77. - Routes: /login, /forbidden, /, /companies/*, /sources/*, /telegram/*,
  78. /tail, /dlq, /audit, *
  79. - All non-auth routes wrapped in <RequireAuth> and <AppShell>
  80. - Non-shipped routes render <ComingSoon> with the W1/W2/W3 badge
  81. - Auth: AuthProvider with /v1/auth/refresh-then-/v1/auth/me boot,
  82. in-memory access token, httpOnly refresh cookie, refresh-on-401
  83. with single-flight guard
  84. - API helpers: fetchWithAuth, apiGet, apiSend, ApiError
  85. - Theme: light/dark/system, persisted in localStorage
  86. - Role-based sidebar
  87. - 1 vitest: tests/login.test.tsx (form renders + accepts input)
  88. Files changed (under cmd/admind/):
  89. - main.go: //go:embed web-dist (empty allowed; 503 stub if no
  90. index.html), wireSPA() serves SPA history for all M13b routes
  91. - /assets/* immutable cache; / no-cache so deploys pick up new bundles
  92. - /dlq keeps the M8 HTML UI; /v1/* keeps the JWT gate
  93. Build tooling (Makefile):
  94. - web-install, web-build, web-dev, web-test, web-typecheck
  95. - build-with-web alias: web-build then go build
  96. Verification at W0:
  97. pnpm run build clean (vite v5.4.21, ~328 kB total, ~104 kB gz)
  98. go build ./... clean
  99. go vet ./... clean
  100. go test ./cmd/admind/ clean
  101. vitest 2 passed
  102. ================================================================================
  103. W1 — Companies CRUD [SHIPPED]
  104. ================================================================================
  105. Commit: c5e15f7 ("M13b W1: Companies CRUD (backend + UI)")
  106. Goal: operators can list, create, edit, suspend, activate, and archive
  107. tenants. tenant_admin gets a read-only view of their own tenant.
  108. --- Schema (migrations/010_tenants_fields.{up,down}.sql) -------------------
  109. Adds two columns to auth.tenants:
  110. rate_limit_per_sec INTEGER NOT NULL DEFAULT 10000 (CHECK 1..1000000)
  111. fcm_shared BOOLEAN NOT NULL DEFAULT TRUE
  112. Both have safe defaults, so applying to a populated DB is a no-op for
  113. existing rows. Reversible (down migration drops both columns).
  114. Verified the migration applies and rolls back cleanly against PG 17.
  115. --- Backend (Go) ----------------------------------------------------------
  116. New files:
  117. internal/authd/tenants.go
  118. - type Tenant (wire shape, snake_case JSON)
  119. - ErrTenantNotFound, ErrTenantSlugTaken, ErrTenantInvalid
  120. - TenantFilter (q, status, limit, offset, scope)
  121. - ListTenants(ctx, filter) (items, total, err)
  122. - GetTenant(ctx, id)
  123. - CreateTenantInput + Validate() (slug regex, email, rate limit)
  124. - UpdateTenantInput (pointer fields = PATCH semantics)
  125. - CreateTenant (writes audit_log "tenant.create")
  126. - UpdateTenant (writes audit_log "tenant.update", enforces
  127. actorScopeAll for restricted fields)
  128. - SetTenantStatus (active<->suspended, any->archived; archived is
  129. terminal; writes audit_log "tenant.status" with {from,to})
  130. - Inline validators: validSlug, looksLikeEmail
  131. internal/authd/tenants_test.go
  132. - TestValidSlug, TestLooksLikeEmail, TestCreateTenantInput_Validate,
  133. TestUpdateTenantInput_Validate (pure Go, no DB needed)
  134. - Note: tests caught a real bug in looksLikeEmail (didn't reject
  135. leading/trailing dot in domain). Fixed.
  136. cmd/authd/tenants.go
  137. - HTTP handlers for /v1/tenants/* (see routes below)
  138. - canAccessTenant(claims, id) — super_admin any, others own only
  139. - isUUID(s) — lenient format check so 400s stay 400s
  140. Routes wired in cmd/authd/main.go (RequireAuth / RequireRole):
  141. GET /v1/tenants — any auth (scope: super_admin all,
  142. tenant_admin own only)
  143. POST /v1/tenants — super_admin only
  144. GET /v1/tenants/{id} — any auth, per-id scope check
  145. PATCH /v1/tenants/{id} — any auth; tenant_admin only
  146. display_name + contact_email on
  147. own tenant
  148. POST /v1/tenants/{id}/status — super_admin only
  149. Errors:
  150. 400 — bad input (validation, JSON parse, bad UUID)
  151. 403 — role not allowed, or tenant_admin trying another tenant
  152. 404 — tenant id not found
  153. 409 — duplicate slug on create
  154. 500 — unexpected DB error
  155. --- Smoke (scripts/m13b_w1_smoke.sh) --------------------------------------
  156. End-to-end, bash + curl + jq-less python. Covers:
  157. 1. authd /health
  158. 2. super_admin login
  159. 3. GET /v1/tenants (initial)
  160. 4. POST /v1/tenants (create) → 201
  161. 5. GET /v1/tenants/{id} → 200
  162. 6. PATCH /v1/tenants/{id} → 200
  163. 7. POST /v1/tenants/{id}/status suspend → 200
  164. 8. POST /v1/tenants/{id}/status activate → 200
  165. 9. POST /v1/tenants (dup slug) → 409
  166. 10. POST /v1/tenants (bad slug) → 400
  167. 11. tenant_admin can login + GET own
  168. 11c. tenant_admin can PATCH own display_name → 200
  169. 11d. tenant_admin CANNOT change rate_limit → 400
  170. 12. tenant_admin GET other tenant → 403
  171. 13. tenant_admin POST /v1/tenants → 403
  172. 14. tenant_admin POST /v1/tenants/{id}/status → 403
  173. 15. POST /v1/tenants/{id}/status (bad value) → 400
  174. 16. cleanup: super_admin archives
  175. Syntax-verified (bash -n); not yet run end-to-end (needs live stack).
  176. --- Frontend (web/) -------------------------------------------------------
  177. New feature folder web/src/features/companies/:
  178. types.ts — Tenant, TenantStatus, ListTenantsResponse,
  179. CreateTenantInput, UpdateTenantInput
  180. api.ts — useTenantsList, useTenant, useCreateTenant,
  181. useUpdateTenant, useSetTenantStatus (TanStack Query),
  182. getErrorMessage(err)
  183. format.tsx — statusLabel, statusVariant, StatusBadge,
  184. formatRateLimit, formatDate
  185. list.tsx — table + debounced search + status filter buttons,
  186. empty state, link to detail; super_admin sees
  187. "New company" button
  188. create-dialog.tsx — Radix Dialog + react-hook-form + zod
  189. (slug regex, email, rate limit 1..1e6, fcm_shared)
  190. duplicate-slug surfaces as a field error
  191. detail-page.tsx — form + Suspend/Activate/Archive actions
  192. (Archive requires typed confirmation dialog)
  193. Metadata panel; tenant_admin sees form but
  194. rate_limit and fcm_shared are disabled
  195. New UI primitives (web/src/components/ui/):
  196. badge.tsx — variants: default, secondary, outline, success,
  197. warning, danger, muted
  198. dialog.tsx — Radix Dialog wrapper (Overlay, Content, Header,
  199. Footer, Title, Description, Trigger, Close, Portal)
  200. textarea.tsx — matching Input style
  201. table.tsx — Table, TableHeader, TableBody, TableRow,
  202. TableHead, TableCell
  203. empty-state.tsx — icon + title + description + action
  204. Updated:
  205. routes/companies.tsx — replaced ComingSoon with a nested Routes
  206. (index → list, :id → detail)
  207. lib/scope.ts — added canViewCompanies, canViewSources,
  208. canManageTelegram, canViewTelegram; kept
  209. canManageCompanies as super_admin-only
  210. Test added (web/tests/companies.test.tsx):
  211. - statusLabel, statusVariant, formatRateLimit, formatDate
  212. - Note: caught a real bug in formatDate (try/catch around
  213. toLocaleDateString doesn't catch "Invalid Date" string).
  214. Replaced with Number.isNaN(d.getTime()).
  215. Bundle delta (W1 vs W0): index chunk +0.08 kB, +1 module
  216. (1733 → 1734 modules transformed).
  217. --- Verification (run from /root/.openclaw/workspace/broad-announce) -----
  218. go build ./... clean
  219. go vet ./... clean
  220. go test -count=1 ./... 22 packages, 0 failures
  221. cd web && pnpm run test 8 tests, 2 files, 0 failures
  222. cd web && pnpm run build clean
  223. psql -f migrations/009_auth.up.sql apply
  224. psql -f migrations/010_tenants_fields.up.sql apply
  225. psql -f migrations/010_tenants_fields.down.sql rollback
  226. bash -n scripts/m13b_w1_smoke.sh syntax OK (not run E2E)
  227. ================================================================================
  228. Next — M13b W2 (Sources CRUD) and W3 (Telegram bot CRUD)
  229. ================================================================================
  230. Per the W2/W3 badges already rendered in the UI:
  231. W2: Sources CRUD
  232. - /v1/sources endpoints in authd (or a new sourcerd package? the
  233. existing `public.sources` table is the canonical source-of-truth
  234. per M4; M13b needs a new layer that scopes by tenant)
  235. - SPA: web/src/features/sources/ (list, create, detail)
  236. - Fields: name, type (FCM|Telegram|WebHook), destination URL/token,
  237. rate_limit, status, optional signing_secret
  238. - Reuse the same patterns from W1 (Query hooks, zod, scope helpers)
  239. W3: Telegram bot CRUD
  240. - /v1/telegram/bots endpoints in authd
  241. - SPA: web/src/features/telegram/
  242. - Fields: bot token (write-only), display name, welcome message,
  243. default source id
  244. - The token is write-only (write hashes a secret, never returned
  245. on read). The deliverd-telegram service consumes the bot list.
  246. ================================================================================
  247. Quick resume instructions
  248. ================================================================================
  249. If you start a new session, run these commands to verify state:
  250. cd /root/.openclaw/workspace/broad-announce
  251. git log --oneline -5 # confirm W0 is at HEAD
  252. # (W1 commit is next)
  253. git status --short # should be empty after
  254. # W1 commit
  255. go test -count=1 ./... # all green
  256. cd web && pnpm run test # all green
  257. cat M13b.dlog # this file
  258. To pick up W2:
  259. 1. Re-read web/src/features/companies/ to copy the patterns
  260. 2. The authd.Tenant and the public.sources table are the references
  261. 3. The route stubs in web/src/routes/sources.tsx and telegram.tsx
  262. still render ComingSoon — replace them the same way as
  263. web/src/routes/companies.tsx
  264. ================================================================================
  265. ENTRY TEMPLATE (copy this for each new deployment)
  266. ================================================================================
  267. YYYY-MM-DD HH:MM EDT — <one-line summary>
  268. Commit: <hash>
  269. Files: <top-level paths added/changed>
  270. Tests: <go test / pnpm test / smoke> → <result>
  271. Verify: <one or more commands the next session can run to confirm>
  272. Notes: <known issues, follow-ups, or 'none'>
  273. ================================================================================
  274. ENTRY LOG (most recent first; append new entries at the TOP of this block)
  275. ================================================================================
  276. 2026-06-18 19:05 EDT — W4 integration smoke + verification shipped
  277. Commit: <this commit>
  278. Files: scripts/m13b_smoke.sh,
  279. Makefile (m13b-smoke / w1-smoke / w2-smoke / w3-smoke / m13b-full targets),
  280. M13b_VERIFICATION.md,
  281. M13b.dlog (this entry),
  282. internal/authd/sources.go (SourceFilter.CompanyID; ListSources
  283. unconditional company scope + empty-CompanyID hard error),
  284. internal/authd/sources_test.go (regression test),
  285. cmd/authd/sources.go (listSourcesHandler sets CompanyID: tenantID)
  286. What: Single-tenant end-to-end smoke covering all 3 M13b modules
  287. in one operator flow. 17 numbered steps + sub-steps (15b, 14b,
  288. 15c). Run: make m13b-smoke. Exits 0 if all runnable steps pass.
  289. ingestd-dependent steps (7, 10) auto-skip when ingestd is
  290. unreachable so the smoke stays useful in dev environments
  291. without the full pipeline.
  292. Plan vs reality:
  293. Step 7 (send alert via ingestd) — gated on INGESTD reachable
  294. Step 10 (rejected on suspended) — gated on INGESTD reachable
  295. Step 14 (list invites → 1) — adapted; no GET /v1/users/invites
  296. endpoint yet (v1.1). Substituted
  297. with response-shape assertion on
  298. POST /v1/users/invite.
  299. Step 14b (tenant_admin own-tenant 201) — added; the plan didn't pin
  300. the per-W2 RequireAuth policy.
  301. Step 15b (bot_token NOT in response) — added; W4 doc implicitly
  302. relied on it but didn't assert.
  303. Step 15c (tenant_admin POST telegram 403)— added; cross-role gate.
  304. Security (this entry's main event):
  305. Writing the W4 smoke caught a SECOND cross-tenant data leak, same
  306. pattern as the W3 listTelegramBots one but in sources:
  307. listSourcesHandler read tenantID from the URL path but never
  308. passed it to SourceFilter. ListSources' WHERE clause only added
  309. company_id = $N when CallerRole != "super_admin". So when
  310. super_admin called GET /v1/tenants/<A>/sources, the SQL had NO
  311. company filter and returned sources from every tenant.
  312. Caught by step 8: "expected 1, got 2" — the second row was a leak
  313. from a different tenant in the same DB. Fix:
  314. - SourceFilter gained CompanyID field
  315. - ListSources emits company_id = $N UNCONDITIONALLY when CompanyID
  316. is set (was: only when CallerRole != "super_admin")
  317. - Empty CompanyID is now a hard error (rejects "list all" misuse
  318. by any caller — defence in depth)
  319. - listSourcesHandler always sets CompanyID: tenantID
  320. - TestListSources_RequiresCompanyID pins the contract
  321. Verified: 3 consecutive smoke runs (no manual cleanup) → 18/18 OK
  322. each. Manual psql+API cross-check: 6 tenants × 1 source each in
  323. DB, API returns 1 per tenant (correct, no inflation).
  324. This is the SAME PATTERN as the W3 leak fixed the day before:
  325. read tenantID from path, drop it on the floor before the filter.
  326. Two handlers, same bug. The smoke catching both suggests a
  327. systematic issue — the W4 cross-tenant matrix should be a
  328. standing template going forward (see /v1/.../X list handlers in
  329. other services for similar bugs).
  330. M13a not regressed: smoke steps 1-5 (authd-only) re-run by hand
  331. against this build → 200/200/200/200/401 as expected. Steps 6+
  332. require the full stack (admind/ingestd/routerd/archiverd/deliverd-*)
  333. which is not running in this env. Smoke code itself unchanged.
  334. Tests: go test -count=1 ./... 22 packages, 0 failures
  335. cd web && pnpm run test 31 tests, 4 files, 0 failures
  336. cd web && pnpm run build clean
  337. make m13b-smoke 18/18 OK × 3 runs
  338. Verify: cd /root/.openclaw/workspace/broad-announce
  339. make m13b-smoke
  340. # Cross-tenant manual check:
  341. # for tid in $(psql ... SELECT id FROM auth.tenants...); do
  342. # curl -H "Authorization: Bearer $SUPER" .../tenants/$tid/sources?limit=10
  343. # done
  344. # Each call must return only sources for $tid.
  345. Notes: Bundle code-split (< 30 KB gz per feature) NOT done. All
  346. three features ship in index-WAbnQRoa.js (37 KB gz).
  347. v1.1 follow-up — matches W1/W2/W3 behaviour, not blocking
  348. M13b exit.
  349. Screenshots not captured (no browser automation in this env).
  350. Substituted with smoke coverage matrix in M13b_VERIFICATION.md
  351. §6. v1.1 follow-up to add real screenshots via Playwright.
  352. Per-W smokes (w1-smoke / w2-smoke / w3-smoke) are unchanged
  353. from their respective commits. m13b-full target chains all
  354. four (w1 + w2 + w3 + m13b) for release gating.
  355. Definition-of-done line: "M13b VERIFICATION.md published" →
  356. done; "SPEC.md M13b row flipped to ✅ shipped YYYY-MM-DD" →
  357. SPEC.md update is the v1.0 release step (separate commit,
  358. after this dlog entry lands).
  359. 2026-06-18 15:09 EDT — W3 Telegram bot CRUD shipped (with cross-tenant leak fix)
  360. Commit: eb06068 (W3 + security fix; 4c956fd = W3 main)
  361. # placeholder, will be filled by amend
  362. Files: internal/authd/telegrambots.{go,_test.go},
  363. cmd/authd/telegrambots.go, cmd/authd/main.go (6 routes wired),
  364. migrations/012_telegram_bot_fields.{up,down}.sql,
  365. web/src/features/telegram/{types,api,format,list,create-dialog,detail-page}.{ts,tsx},
  366. web/src/routes/telegram.tsx (now wired; was ComingSoon),
  367. web/src/components/layout/sidebar.tsx (W3 nav entry),
  368. web/tests/telegram/format.test.ts,
  369. scripts/m13b_w3_smoke.sh,
  370. M13b.dlog
  371. What: 6 routes under /v1/tenants/{id}/telegram/bots (list/create/get/
  372. patch/status/rotate-token). All RequireRole("super_admin").
  373. Migration 012 adds bot_token_hash (bcrypt), welcome_message,
  374. default_source_id, description, last_rotated_at to
  375. public.telegram_bots (already-existing table from M4);
  376. idx_telegram_bots_company (active-only) and
  377. idx_telegram_bots_default_source added; trg_telegram_bots_
  378. touch_updated_at trigger installed. Bot token is write-only:
  379. server returns `bot_token_set: bool` instead of the plaintext
  380. on every read. Rotate returns the same shape (no plaintext
  381. echo). Plaintext is stored alongside the bcrypt hash so
  382. telegramd can read it for outbound calls.
  383. Same bridge as W2: CreateTelegramBot calls
  384. ensurePublicCompanyRow(tenant_id, tenant.display_name) before
  385. INSERT, since public.telegram_bots.company_id FKs
  386. public.companies(id) and the auth.tenants row doesn't
  387. auto-create that legacy row.
  388. Smoke fixed: bot_token_set assertions compared to "True"
  389. (Python repr) but json_field() json-dumps bools as lowercase
  390. "true". Now compared to "true" — server response was always
  391. correct.
  392. Cleanup note: smoke's archive step archives the auth.tenant
  393. but leaves public.telegram_bots rows behind. Not a bug
  394. (telegram_bots.company_id has ON DELETE CASCADE to
  395. public.companies, and the smoke never deletes the company
  396. row), but repeated smoke runs accumulate rows. Future smoke
  397. passes should DELETE FROM public.telegram_bots first or
  398. add a cleanup step that removes the public.companies row.
  399. Tests: go test -count=1 ./... 22 packages, 0 failures
  400. cd web && pnpm run test 31 tests, 4 files, 0 failures
  401. cd web && pnpm run build clean
  402. psql -f migrations/012_telegram_bot_fields.up.sql apply (already done)
  403. psql -f migrations/012_telegram_bot_fields.down.sql rollback verified
  404. bash scripts/m13b_w3_smoke.sh 32/32 OK (authd running)
  405. Verify: cd /root/.openclaw/workspace/broad-announce
  406. go test -count=1 ./... && cd web && pnpm run test
  407. psql -f migrations/012_telegram_bot_fields.up.sql
  408. bash scripts/m13b_w3_smoke.sh
  409. Security: listTelegramBotsHandler initially read tenantID from the
  410. path but never passed it to TelegramBotFilter, and
  411. ListTelegramBots had no CompanyID field — so LIST returned
  412. bots across all tenants (cross-tenant data leak). Fixed:
  413. added CompanyID to TelegramBotFilter, scoped SQL with
  414. company_id = $N, and the handler now sets
  415. CompanyID: tenantID. The smoke's step 4 (initially-empty)
  416. caught this when the test was re-run multiple times
  417. without manual cleanup. Regression test
  418. TestListTelegramBots_CompanyID_RequiredForScoping
  419. documents the contract. Verified: 3 consecutive smoke
  420. runs (no cleanup between) all pass 32/32; manual check
  421. showed 6 tenants × 1 bot each via API = correct,
  422. vs. DB has 6 total (no cross-tenant inflation).
  423. Notes: Bundle: telegram feature code-split (exit criterion "< 30 KB
  424. gzipped") NOT done. Same as W2: all features ship in the
  425. main chunk (index-WAbnQRoa.js = 37 KB gz, +forms-BRRx31Es.js
  426. = 22 KB gz). Per-feature dynamic import is a v1.1 follow-up;
  427. not blocking W3.
  428. Like W2, list/create/detail UI tests are format-only; the
  429. v1.1 follow-up adds a component test or two.
  430. Open policy dial (same as W2's tenant_admin suspend):
  431. canManageTelegram in web/src/lib/scope.ts is super_admin
  432. only, matching W3 plan. If tenant_admin should manage their
  433. own bot, the dial is in scope.ts + the handler's role check.
  434. W4 is next: scripts/m13b_smoke.sh (full W1+W2+W3 walkthrough)
  435. + verification doc with screenshots.
  436. 2026-06-18 02:00 EDT — W2 Sources CRUD shipped (one-time secrets + rotate)
  437. Commit: <this commit>
  438. Files: internal/authd/sources.{go,_test.go},
  439. cmd/authd/sources.go, cmd/authd/main.go (6 routes wired),
  440. migrations/011_sources_secrets.{up,down}.sql,
  441. web/src/features/sources/{types,api,format,list,create-dialog,detail-page}.{ts,tsx},
  442. web/src/components/ui/checkbox.tsx (new),
  443. web/src/routes/sources.tsx (now wired; was ComingSoon),
  444. web/tests/sources/format.test.ts,
  445. scripts/m13b_w2_smoke.sh,
  446. M13b.dlog
  447. What: Pick (a) per 01:10 — reuse public.sources directly via authd.
  448. CRUD + status + rotate-secrets. Migration 011 adds hmac_secret_hash,
  449. api_key_hash, mtls_required, description (all nullable so pre-existing
  450. rows still load). One-time secrets: CreateSource / RotateSecrets
  451. return plaintext EXACTLY ONCE in `secrets`; only bcrypt hashes
  452. persist. auto_secrets: true by default in the UI. tenant_admin
  453. can manage their own sources (PATCH allowed; status changes are
  454. an open feature, see Notes).
  455. Cert UI: placeholder card on detail page (M14 — coming).
  456. Bridge: public.sources.company_id (TEXT) FKs public.companies.id (TEXT);
  457. auth.tenants.id is UUID. W2's CreateSource does an idempotent
  458. INSERT INTO public.companies ... ON CONFLICT DO NOTHING keyed
  459. by auth.tenants.id::text so the FK is satisfied on first source
  460. create. No change to W1's CreateTenant.
  461. Tests: go test ./internal/authd/ -count=1 ok (all tests, incl. new
  462. TestValidSourceID / TestValidSecretFormat / TestValidAPIKeyFormat
  463. / TestCreateSourceInput_Validate / TestUpdateSourceInput_Validate
  464. / TestGenerateSecret)
  465. web tsc -b clean
  466. web vitest run 17/17 ok (9 new + 6 companies + 2 login)
  467. web vite build clean
  468. bash -n scripts/m13b_w2_smoke.sh clean
  469. Verify: cd /root/.openclaw/workspace/broad-announce
  470. go test -count=1 ./... && cd web && pnpm run test
  471. psql -f migrations/011_sources_secrets.up.sql (apply 011)
  472. bash scripts/m13b_w2_smoke.sh (run against stack)
  473. Notes: Smoke script not yet run E2E (same as W1: needs running authd +
  474. Postgres). 17 web tests pass but the W2 component tests are
  475. format-only — list/create/detail-page UI tests are a v1.1 add.
  476. Decision logged: public.sources reused as-is (no auth.sources
  477. view layer in W2; can refactor later if the auth/admin split
  478. needs to harden).
  479. Open follow-up for v1.1: tenant_admin status-changes (suspend
  480. / activate their own source). The route is RequireAuth today
  481. and the store allows any caller; the policy dial is whether
  482. tenant_admin should be allowed to suspend their own. Not
  483. blocking W2; documenting in the .dlog.
  484. W3 (Telegram bot CRUD) is next; same pattern as W2.
  485. 2026-06-17 18:44 EDT — W1 Companies CRUD shipped + this .dlog created
  486. Commit: c5e15f7 (W1), 63202f2 (.dlog creation)
  487. Files: internal/authd/tenants.{go,_test.go}, cmd/authd/tenants.go,
  488. cmd/authd/main.go (routes wired),
  489. migrations/010_tenants_fields.{up,down}.sql,
  490. web/src/features/companies/*.{ts,tsx},
  491. web/src/components/ui/{badge,dialog,textarea,table,empty-state}.tsx,
  492. web/src/lib/scope.ts, web/src/routes/companies.tsx,
  493. web/tests/companies.test.tsx,
  494. scripts/m13b_w1_smoke.sh,
  495. M13b.dlog
  496. Tests: go build/vet ./... clean; go test -count=1 ./... 22/22 ok;
  497. pnpm run test 8/8 ok; pnpm run build clean;
  498. migrations apply + roll back cleanly on PG 17
  499. Verify: cd /root/.openclaw/workspace/broad-announce
  500. go test -count=1 ./... && cd web && pnpm run test
  501. bash -n scripts/m13b_w1_smoke.sh (syntax only; needs live stack)
  502. psql -f migrations/009_auth.up.sql
  503. psql -f migrations/010_tenants_fields.up.sql
  504. Notes: Smoke script not yet run E2E (requires running authd + Postgres).
  505. W2 (Sources) and W3 (Telegram bot) are next.
  506. Test for sort + order; the W1 test caught a real bug in
  507. looksLikeEmail and formatDate (both fixed).