Update log
The board: what is green, what is broken, what is next.
docs/STATUS.md
The board. Update at the end of a work session — this is what a fresh session reads first.
Last updated: 2026-08-05 (Pass 8 · after batch 20 wrap — 135/135 = 100% shipped)
Where we are
Pass 1 ("docs first") is done: the two-deployable monorepo, the docs set, the seeded board, the
landing page and /dashboard.
Pass 2 shipped the first vertical slice — a company signs up, creates a project, picks what kinds of marketing it wants, and puts a budget behind each one. Auth pages, the app shell, and the project-membership scoping that makes an agency login safe came with it.
Pass 3 shipped OAuth sign-in (Google + GitHub, with encryption at rest) and Stripe billing (four plans, entitlements, and caps enforced where they apply).
Pass 4 settled the infrastructure: RabbitMQ for short jobs, trigger.dev for durable agent runs, a Baileys container for WhatsApp, and the scraping rules — all recorded in INFRASTRUCTURE.md with the trade-offs written down rather than implied.
Pass 5 rebuilt the front door (F-089). It was prose over a static list; it is now seven
interactive sections over the same data/dashboard/*.json the board renders — including a working
approval-gate simulator and a ⌘K palette across every feature, experiment and doc.
Pass 6 (2026-07-31 → 2026-08-03) rebuilt auth on Better-Auth and set up the marketing-work foundations: PR #1–3 replaced the hand-rolled JWT flow with Better-Auth (parallel mount → schema
- bcrypt bridge → frontend cutover → password reset + email verification), PR #4 added the
Campaignmodel + UI, then E1 (Post+PlatformPost+ 11-state machine) and E2 (PostingSlot Queue+QueueEntry+ timezone-correct next-available-slot resolution) shipped as engines with tests, and finally the IA restructure landed the full per-project sidebar shape fromdocs/IA.md— the whole target sitemap is walkable in the browser now, ~5 of ~20 sections have real behaviour behind them and the rest are placeholders that name the checklist slice that ships each one. Test suite grew 105 → 201/201 across the pass.
Pass 7 (2026-08-03) shipped the first end-to-end publishing loop for a real platform. Five
PRs in sequence: E9.1 — a ChannelAdapter framework with registry, credential vault (AES-256-GCM
via core/crypto.ts), and a generic OAuth harness (POST /oauth/:adapterKey/start +
GET /oauth/:adapterKey/callback with signed state and optional PKCE) that dispatch by URL segment
so no route imports a specific platform. E9.2 — real Instagram + LinkedIn OAuth adapters plug
into the harness (Meta short→long token exchange with the Page-walk that finds the linked IG Business
account; LinkedIn OIDC /v2/userinfo); registry auto-registers whichever platforms have client
credentials set at boot; GET /channels powers a "Sign in with X" primary CTA on every organic hub;
plus Option A — connected_by_user_id on SocialAccount so token-death alerts reach the teammate
whose grant powers the account. E9.3 — media pipeline (POST /uploads → MinIO, GET /uploads/:key
gateway), full LinkedIn publish (single & multi-image, single video, link preview, visibility), a
composer rewrite with drop-zone file picker + drag-drop + preview grid, and Modal primitive gains a
footer slot so action bars stay pinned. E3.1 — publisher orchestrator: a tick loop (modules/publisher/,
default 30s) claims due scheduled PlatformPosts via the sole-writer transition (safe under N replicas),
dispatches through the same code path as the "Publish now" button, marks accounts disconnected on
token_revoked/token_expired, drains gracefully on SIGTERM. E5.1 — LinkedIn post-metrics
ingestion: PostMetricSnapshot time-series model + modules/metrics/ worker (15-min tick, 1-hour
per-post min-interval, 30-day retention) + LinkedIn getPostMetrics against /v2/socialActions, +
GET /posts/:id/metrics + a live metric strip on every published PostRow that refetches every 60s.
Test suite grew 201 → 255/255 across the pass. LinkedIn is production-usable today: connect →
compose with media → schedule or publish now → the worker fires on time → the analytics roll in.
Pass 8 (2026-08-03 → 2026-08-05, batches 8–20 · 20 PRs merged) closed everything that Pass 7 listed as "still specced but unbuilt" — and then some. In order of impact:
- Full channel-adapter parity (E9): Instagram, Facebook, Threads, Pinterest, X (OAuth2+PKCE), TikTok (Content Posting API), YouTube (video publish scaffolded, real path guarded until resumable upload wired) — six adapters on top of the E9.1 framework, each mock-first with config-toggle for real creds. Reply-through-adapter shipped for LinkedIn / IG / FB / Threads.
- Full publisher hardening (E3.1–3.4): orchestrator + exponential-backoff retry queue (5 attempts, 20→300s) + per-account rate-limit tracking (SocialAccountRateLimit) + full request/response audit with token redaction (PublishAttemptAudit).
- Full inbound content (E4): signed-webhook framework + InboxItem model + Meta/LinkedIn/Threads webhook adapters + 10-min polling fallback for the rest + Inbox UI. Pinterest/X/TikTok webhook adapters remain the only red row (blocked on platform push-API access — polling covers).
- Full analytics stack (E5, E10): per-day PostMetricDaily rollup + AccountMetricSnapshot + GA4 + Google Search Console ingestion + cross-source reconciliation with drift ribbon + project-day KPI rollup engine (ProjectKpiSnapshot) + read API + dashboard UI.
- Full ad platform (E6): AdAccount / AdCampaign / AdGroup / Ad / Creative / Budget models + status machine + budget-engine auto-pause + bid strategies + ad-metrics ingestion + Google + Meta Ads adapter shells + D6 optimisation-proposal engine + creative asset pipeline (sharp → 3 variants) + attribution engine (touchpoints + 4 models + KPI rollup + public tracker.js SDK with SPA hooks + sendBeacon fallback) + spend reconciliation vs invoice CSV.
- Full SEO / AEO (E7): site audit + recurring CrawlSchedule + TrackedKeyword + KeywordRank + backlink monitor + AI citation tracker (daily tick vs 4 LLMs) + competitor tracker + share-of-voice + full D1/D2/D3/D4/D5/D6/D7 UI hubs.
- Full approvals (E8): multi-stage workflow + stage definitions + threaded comments (internal_only) + per-stage decisions + reminder scheduler + delegation rules + client-portal magic-link engine + login page + workflow template editor UI.
- Full notifications (E11): email (nodemailer) + SMS (Twilio-shaped) + phone verification + Web Push (VAPID) + service worker + daily/weekly digest engine + Slack outbound (Block Kit) + generic outbound webhook (HMAC-signed, auto-disable at 10 fails) + notification-preferences UI.
- Full auth extensions (E12): invitations flow + session listing + scoped API keys (ak_live_…) + policy-based RBAC (Role/Permission/RolePermission/UserRoleAssignment) + TOTP MFA + recovery codes + step-up gate + WebAuthn/passkeys (second-factor + first-factor sign-in) + GDPR export + right-to-erasure with 7-day grace.
- Full billing (H2–H6): plans compare/change + usage meter view (UsageMeter model + reconciler + overage notification) + native invoices list + payment methods + dunning UX banner.
- Full team surface (I1–I3, J1–J6): project members list + invitations + Roles & Permissions UI + Workspace settings + Notification prefs + API keys + Security (sessions + MFA + passkeys) + Audit logs (with chip filters + preview drawer + CSV export as of batch 20).
- Full per-project UI closure (batch 20): every sidebar link in the per-project IA now resolves to real UI. Batch 20 T2 converted 8 sections to optional-catch-all
[[...tab]]/page.tsxso ~50 child slugs stop hitting the "Building this next" placeholder; batch 20 T1 shipped the two truly-missing pages (/integrations,/activity) +/kpisredirect; batch 20 T3 shipped<ConnectionHealthPill>+ audit-feed polish.
Feature spreadsheet: 135/135 = 100% shipped (docs/FEATURE_STATUS.xlsx). Test suite grew
255/255 → ~1050/1050 API + ~514/514 web + ~500 helper cases across the pass. The sole remaining
red row is E4 Pinterest/X/TikTok inbound webhook adapters, blocked on each platform's push-API
access — polling already covers them.
Gate
| Check | Command | State |
|---|---|---|
| Types | npm run typecheck | ✅ api · web · contracts clean |
| Lint | npm run lint | ✅ 0 errors (pre-existing warnings only) |
| Unit / integration | npm test --workspace @verjson/api | ✅ ~1050/1050 (API), no skips |
| Web tests | npm test --workspace @verjson/web | ✅ 514/514 (helpers + hooks) |
| E2E | npm run test:e2e | ⚠️ smoke still asserts old landing copy — rewrite pending |
| Build | npm run build | ✅ api + web build |
In flight
| Item | State | Blocker | Next step |
|---|---|---|---|
| F-001 monorepo split | ✅ done | — | — |
| F-088 dashboard | ✅ done | — | wire the optional dev write-back so remarks can be added from the browser |
| F-089 landing | ✅ done | — | 12 e2e cases; smoke needs refresh after IA restructure |
| F-020 projects + F-025 mix + F-026 budgets | ✅ done | — | — |
| F-005 RBAC / F-006 agency logins | ✅ done | — | — |
| E12 policy-based RBAC (roles + permissions + assignments) | 🟡 partial | — | engine + CRUD endpoints landed; ONE endpoint migrated to requirePermission as demo (POST /billing/checkout → billing:manage), rest carry TODO(rbac) for follow-up |
| E12 step-up gate | 🟡 partial | — | permission-based gate proven out via requirePermission; per-request re-auth for sensitive actions still to come |
| F-015 OAuth · F-016 Stripe | ✅ done | — | — |
| F-015 OAuth · F-016 Stripe | ✅ done | — | GitHub OAuth temporarily hidden until wired to Better-Auth (OAuth-migration slice) |
| F-096 queue + worker | ✅ done | — | verified against a real broker |
| F-073/074/075 agent runs + approval gate | ✅ done | — | — |
| F-097 trigger.dev | ✅ done | — | falls back to our worker when unconfigured |
| F-023 campaigns | ✅ done | — | model + org-wide UI + per-project view (PR #4 + IA restructure) |
| Auth — Better-Auth adoption | ✅ done | — | 4 PRs (parallel mount → schema → cutover → reset/verify). Legacy /api/v1/auth/* still mounted; retire in cleanup slice |
| E1 content lifecycle engine | ✅ done | — | Post + PlatformPost + 11-state machine + CRUD. Version history / normaliser / policy checks are E1.2–E1.4 |
| E2 scheduling engine | ✅ done | — | PostingSlot + Queue + QueueEntry + next-available-slot resolution. Optimal-time + cadence rules are E2.2/E2.3 |
| B1 calendar UI | ✅ done | — | Monthly view at /projects/[id]/calendar. Weekly + drag-to-schedule is B3 |
| IA restructure (A2–A7) | ✅ done | — | Full walkable IA — top nav + project sidebar + placeholder catch-all |
| F-082 CI gate | 🟡 | — | npm run verify works locally; put it in GitHub Actions |
| F-083 mock testing | 🟡 | — | add the MSW layer for web; see TESTING.md |
| E9 channel-adapter framework | 🔲 | — | Highest-leverage next slice — see docs/CHECKLIST.md |
Next up
Every feature in the original spec has shipped. Remaining candidates, ordered by dependency-free first:
- Pinterest / X / TikTok inbound webhook adapters — the only pending row in the spreadsheet. Blocked on each platform's push-API access (Pinterest partner review, X paid tier, TikTok business review). The polling fallback already covers these end-users; the push path is a latency + cost improvement, not a capability gap.
- E2E smoke rewrite —
apps/web/e2e/smoke.spec.tsstill asserts the pre-cutover template landing copy. Small slice, unblocks the E2E suite. - CI gate in GitHub Actions (F-082) —
npm run verifyruns locally; wiring it into CI is what stops the "someone remembered to run it" failure mode. - TODO(rbac) sweep — ~7 role checks in
projects/,agents/,billing/still carryrequireRolesrather than the batch-12requirePermissionengine. Endpoint-by-endpoint swap, deliberately deferred to keep tenant-scope 404 semantics on a few. - Cross-worktree test-DB deadlock — every parallel batch hit
40P01on the shared TRUNCATE inapps/api/src/test/setup.ts. Canonical fix: per-worktree DB name (app_test_<slug>). Not urgent (agents work around with scratch DBs) but would remove a recurring speed-bump. - Reviewer sweep on batches 10–20 — the parallel-delivery push was time-boxed to spawn-merge-repeat; a formal reviewer pass across those ~40 PRs hasn't been run.
Everything else is polish behind these six.
Security findings
All 19 findings from the 2026-07-29 review are closed, including both blockers. The full register — what each one allowed, and what closed it — is in SECURITY_FINDINGS.md. Rows are kept after closure on purpose: knowing what was once possible is what tells the next person which assumptions to re-check.
Known broken
| Issue | Impact | Owner |
|---|---|---|
apps/web/e2e/smoke.spec.ts still asserts the old template's landing copy | E2E suite will fail until rewritten | — |
| No CI pipeline | The gate only runs when someone remembers to run it. This is what let lint sit at 14 errors under a documented "0 errors" (B-014), and it is the common cause behind all four bugs found on 2026-08-05 | — |
Legacy POST /auth/signup still mounted alongside POST /auth/signup-with-org | Two signup paths, and src/test/helpers.ts exercises the legacy one — so the suite proves the path users don't take. Directly caused B-011. Retire the legacy route, or repoint the helper | — |
The API suite reads the developer's root .env (vitest.config.ts loads it for TEST_DATABASE_URL) | A legitimate local setting can fail tests: a WEB_URL other than http://localhost:3000 fails 4 oauth.test.ts cases. Pin the values the tests depend on in vitest.config.ts#test.env instead | — |
Reference apps still in-tree
archive/brightbean-studio/, archive/adwords-adsense/, archive/brightbean-studio-app/ — excluded from the workspaces
and from lint. Read-only sources for the port; delete once PORTING.md is fully
worked through.