From 74878b472970f3a94161117abeed6965455ab1d6 Mon Sep 17 00:00:00 2001 From: Yusrah Mohammed Date: Sun, 31 May 2026 10:18:57 +0100 Subject: [PATCH] docs: add MVP roadmap and enforce tracked TODO policy (#120) --- DEBT.md | 1 + backend/src/services/api-latency-service.ts | 2 +- docs/ROADMAP.md | 89 ++++++++++++++ docs/repo-issue-backlog-2026-05.md | 2 + eslint.config.mjs | 9 ++ scripts/check-todos.mjs | 124 ++++++++++++-------- scripts/check-todos.test.mjs | 41 +++++++ 7 files changed, 217 insertions(+), 51 deletions(-) create mode 100644 docs/ROADMAP.md create mode 100644 scripts/check-todos.test.mjs diff --git a/DEBT.md b/DEBT.md index 5c785005..4fe82bd4 100644 --- a/DEBT.md +++ b/DEBT.md @@ -44,6 +44,7 @@ Adjust this list in `scripts/check-todos.mjs` (`CRITICAL_PATHS`) and here togeth |-------|----------|----------|-------|-------------|-------| | #496 | `client/app/api/csp-report/route.ts` | high | _unassigned_ | Replace stubbed PayPal integration with real client | 2026-05-29 | | #494 | `backend/...` | med | _unassigned_ | Price changes / consolidation suggestions fetched from DB | 2026-05-29 | +| #698 | `backend/src/services/api-latency-service.ts:68` | med | _unassigned_ | Redis-based API latency storage (currently in-memory fallback) | 2026-05-31 | > The rows above are seeded from existing issue summaries (`ISSUE_496_*`, > `ISSUE_494_*`). Verify the file paths and owners, then keep this table in sync diff --git a/backend/src/services/api-latency-service.ts b/backend/src/services/api-latency-service.ts index c352a3a9..68162926 100644 --- a/backend/src/services/api-latency-service.ts +++ b/backend/src/services/api-latency-service.ts @@ -65,7 +65,7 @@ export class ApiLatencyService { try { const redisStore = RateLimitRedisStore.getInstance(); if (redisStore.isAvailable()) { - // TODO: Implement Redis-based storage once we have access to raw Redis client + // TODO(#698): Implement Redis-based storage once we have access to raw Redis client this.recordToMemory(family, latencyMs); } else { this.recordToMemory(family, latencyMs); diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 00000000..f7f30fba --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,89 @@ +# SYNCRO MVP Roadmap + +**Backlog ID:** [#120](https://github.com/Calebux/SYNCRO/issues/120) +**Last updated:** 2026-05-31 +**Source backlog:** [repo-issue-backlog-2026-05.md](./repo-issue-backlog-2026-05.md) + +This document maps open work to MVP completion. Use it in sprint planning alongside [issue-triage-policy.md](./issue-triage-policy.md) and the [technical debt registry](../DEBT.md). + +--- + +## MVP exit criteria + +MVP is **done** when every criterion below is met. Each is verifiable in CI, ops dashboards, or a short manual checklist. + +| # | Criterion | How to verify | +|---|-----------|---------------| +| E1 | **Security baseline** — no client-side service-role keys; unified Next.js config; CSP and auth middleware enforced on all API routes | CI `validate-dependencies` job; [backlog #35](./repo-issue-backlog-2026-05.md#35--p0-eliminate-accidental-service-role-usage-from-client-server-code), [#598](./repo-issue-backlog-2026-05.md#598--p0-unify-conflicting-nextjs-configuration-files) closed | +| E2 | **Core product flows** — register, login, MFA, subscription CRUD, reminders, and analytics work end-to-end against production Supabase | Smoke suite in [SMOKE_TESTS.md](./SMOKE_TESTS.md) green; manual checklist in [CurrentState.md](./archive/CurrentState.md#success-metrics) | +| E3 | **Payments** — Stripe and Paystack webhooks process live events; no stubbed payment paths in `client/app/api/` or `client/lib/` | Webhook integration tests pass; zero `severity:high` `tech-debt` issues in payments area | +| E4 | **Ops quality** — per-route bundle budgets enforced; no budget violations on `main` | CI [bundle-size workflow](../.github/workflows/bundle-size.yml); [backlog #697](./repo-issue-backlog-2026-05.md#697--p2-tighten-bundle-size-budgets-for-the-client-app) | +| E5 | **Technical debt gate** — every TODO/FIXME in critical paths references a tracked GitHub issue | `node scripts/check-todos.mjs` exits 0; [DEBT.md](../DEBT.md) registry matches code | +| E6 | **Blockchain (testnet)** — subscription registry, renewal, escrow, and virtual-card contracts deployed and exercised on Stellar testnet | Contract test suite green; deployment runbook signed off | +| E7 | **Production readiness** — Sentry alerting, correlation IDs, and rollback playbooks documented and exercised once | [SENTRY_ALERT_ROUTING.md](./SENTRY_ALERT_ROUTING.md), [MIGRATION_ROLLBACK_PLAYBOOKS.md](./MIGRATION_ROLLBACK_PLAYBOOKS.md) drill completed | + +**Current MVP status:** ~90% — see [CurrentState.md](./archive/CurrentState.md). Primary gaps: live payment finalization (E3), mainnet contract audit (post-MVP), and remaining integration hardening (E7). + +--- + +## Milestones & issue clusters + +### M1 — Security & architecture foundation ✅ + +Foundation work that unblocks safe iteration. **Cluster complete.** + +| Issue | Priority | Epic | Status | +|-------|----------|------|--------| +| [#35](https://github.com/Calebux/SYNCRO/issues/35) — Remove service-role from client | P0 | Client API security | ✅ Done — [backlog notes](./repo-issue-backlog-2026-05.md#35--p0-eliminate-accidental-service-role-usage-from-client-server-code) | +| [#598](https://github.com/Calebux/SYNCRO/issues/598) — Unify Next.js config | P0 | Architecture | ✅ Done — [backlog notes](./repo-issue-backlog-2026-05.md#598--p0-unify-conflicting-nextjs-configuration-files) | +| ADR-001 | — | Frontend/backend split | ✅ Accepted — [adr/ADR-001-frontend-backend-split.md](./adr/ADR-001-frontend-backend-split.md) | + +### M2 — Production hardening 🔄 + +Hardening threads required before public MVP launch. + +| Cluster | Representative issues | Area labels | Target | +|---------|----------------------|-------------|--------| +| **Payments & webhooks** | #496 (PayPal stub), Stripe/Paystack webhook hardening | `area/client`, `area/backend` | E3 | +| **Bundle & performance** | [#697](https://github.com/Calebux/SYNCRO/issues/697) bundle budgets, #698 Redis API latency storage | `area/client`, `area/backend` | E4 | +| **Integrations** | #494 (DB-backed suggestions), Telegram bot enrichment, email parsing | `area/backend`, `area/client` | E2 | +| **Tech debt governance** | [#120](https://github.com/Calebux/SYNCRO/issues/120) roadmap, DEBT.md policy | `area/governance` | E5 | + +### M3 — MVP launch 🎯 + +Final gate before marking MVP complete. + +| Cluster | Scope | Exit criteria | +|---------|-------|---------------| +| **Live payments** | Production webhook secrets, idempotency, DLQ handling | E3 — 7-day production webhook success rate ≥ 99% | +| **Smoke & E2E** | Full user journey in CI/staging | E2 — all smoke tests green on release branch | +| **Incident readiness** | On-call runbooks, CSP/payment drills | E7 — one documented drill per quarter | +| **Contracts (testnet)** | Soroban renewal + escrow flows | E6 — contract integration tests on testnet | + +### M4 — Post-MVP (out of scope for exit) + +Tracked but not blocking MVP: mainnet contract deployment, advanced email parsing ML, multi-region payment expansion. + +--- + +## Completed issue clusters (historical) + +Use these for context when grooming new work — patterns and resolutions are documented in the backlog archive. + +| Cluster | Issues | Resolution doc | +|---------|--------|----------------| +| Client privilege escalation | #35 | [repo-issue-backlog-2026-05.md § #35](./repo-issue-backlog-2026-05.md#35--p0-eliminate-accidental-service-role-usage-from-client-server-code) | +| Next.js config drift | #598 | [repo-issue-backlog-2026-05.md § #598](./repo-issue-backlog-2026-05.md#598--p0-unify-conflicting-nextjs-configuration-files) | +| Bundle size enforcement | #697 | [repo-issue-backlog-2026-05.md § #697](./repo-issue-backlog-2026-05.md#697--p2-tighten-bundle-size-budgets-for-the-client-app) | +| Issue taxonomy & triage | #773 (merged) | [issue-triage-policy.md](./issue-triage-policy.md) | +| Tech debt CI policy | (this effort) | [DEBT.md](../DEBT.md), [INSTALL.md](../INSTALL.md) | + +--- + +## How to use this roadmap + +1. **Weekly grooming** — Pull the next open item from M2/M3 into sprint; confirm it maps to an exit criterion (E1–E7). +2. **New issues** — Tag with `area/`, `priority/`, and link the milestone (M2/M3) in the issue body. +3. **Closing MVP** — When E1–E7 are all green, open a release issue, run smoke tests, and update [CurrentState.md](./archive/CurrentState.md). + +**Related docs:** [DEBT.md](../DEBT.md) · [issue-triage-policy.md](./issue-triage-policy.md) · [branch-protection.md](./branch-protection.md) diff --git a/docs/repo-issue-backlog-2026-05.md b/docs/repo-issue-backlog-2026-05.md index bb886e27..0bfa986a 100644 --- a/docs/repo-issue-backlog-2026-05.md +++ b/docs/repo-issue-backlog-2026-05.md @@ -1,5 +1,7 @@ # Repository Issue Backlog — May 2026 +> **Roadmap:** Issue clusters and MVP exit criteria live in [ROADMAP.md](./ROADMAP.md). + ## #35 — [P0] Eliminate accidental service-role usage from client server code **Scope:** client-api diff --git a/eslint.config.mjs b/eslint.config.mjs index 05e726d1..390e0e03 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -13,6 +13,15 @@ const eslintConfig = defineConfig([ "build/**", "next-env.d.ts", ]), + { + rules: { + // Soft in-editor signal; authoritative enforcement is scripts/check-todos.mjs + "no-warning-comments": [ + "warn", + { terms: ["todo", "fixme"], location: "anywhere" }, + ], + }, + }, ]); export default eslintConfig; diff --git a/scripts/check-todos.mjs b/scripts/check-todos.mjs index c20c063f..afe41888 100644 --- a/scripts/check-todos.mjs +++ b/scripts/check-todos.mjs @@ -16,6 +16,7 @@ import { readFileSync, statSync } from "node:fs"; import { execSync } from "node:child_process"; +import { pathToFileURL } from "node:url"; // --- Configuration --------------------------------------------------------- @@ -78,39 +79,39 @@ function isCritical(path) { ); } -// --- Scan ------------------------------------------------------------------- - -const warnOnly = process.argv.includes("--warn-only"); - -const blocking = []; -const warnings = []; - -for (const file of getTrackedFiles()) { - if (isIgnored(file)) continue; - if (file.endsWith("check-todos.mjs")) continue; // don't scan the scanner - if (!SOURCE_EXTENSIONS.has(extOf(file))) continue; - - let content; - try { - if (statSync(file).isDirectory()) continue; - content = readFileSync(file, "utf8"); - } catch { - continue; // deleted / unreadable +function runScan({ warnOnly = false } = {}) { + const blocking = []; + const warnings = []; + + for (const file of getTrackedFiles()) { + if (isIgnored(file)) continue; + if (file.endsWith("check-todos.mjs")) continue; // don't scan the scanner + if (!SOURCE_EXTENSIONS.has(extOf(file))) continue; + + let content; + try { + if (statSync(file).isDirectory()) continue; + content = readFileSync(file, "utf8"); + } catch { + continue; // deleted / unreadable + } + + const lines = content.split("\n"); + lines.forEach((line, i) => { + if (!TODO_TOKEN.test(line)) return; + if (TRACKED_TODO.test(line)) return; // properly tracked, OK + + const entry = { + file, + line: i + 1, + text: line.trim().slice(0, 120), + }; + if (isCritical(file)) blocking.push(entry); + else warnings.push(entry); + }); } - const lines = content.split("\n"); - lines.forEach((line, i) => { - if (!TODO_TOKEN.test(line)) return; - if (TRACKED_TODO.test(line)) return; // properly tracked, OK - - const entry = { - file, - line: i + 1, - text: line.trim().slice(0, 120), - }; - if (isCritical(file)) blocking.push(entry); - else warnings.push(entry); - }); + return { blocking, warnings }; } // --- Report ----------------------------------------------------------------- @@ -122,27 +123,50 @@ function printGroup(label, entries) { } } -if (warnings.length) { - printGroup("⚠️ Untracked TODO/FIXME (non-critical, warning)", warnings); -} +function main() { + const warnOnly = process.argv.includes("--warn-only"); + const { blocking, warnings } = runScan({ warnOnly }); -if (blocking.length) { - printGroup("❌ Untracked TODO/FIXME in CRITICAL paths", blocking); - console.log( - "\nEvery TODO/FIXME in critical paths must reference a GitHub issue:\n" + - " // TODO(#123): short description\n" + - " // FIXME(#123): short description\n\n" + - "1. Open an issue, label it `tech-debt` + a severity.\n" + - "2. Reference its number in the comment.\n" + - "3. Add a row to DEBT.md.\n" - ); - if (!warnOnly) { - process.exit(1); + if (warnings.length) { + printGroup("⚠️ Untracked TODO/FIXME (non-critical, warning)", warnings); + } + + if (blocking.length) { + printGroup("❌ Untracked TODO/FIXME in CRITICAL paths", blocking); + console.log( + "\nEvery TODO/FIXME in critical paths must reference a GitHub issue:\n" + + " // TODO(#123): short description\n" + + " // FIXME(#123): short description\n\n" + + "1. Open an issue, label it `tech-debt` + a severity.\n" + + "2. Reference its number in the comment.\n" + + "3. Add a row to DEBT.md.\n" + ); + if (!warnOnly) { + process.exit(1); + } + } + + if (!blocking.length && !warnings.length) { + console.log("✅ No untracked TODO/FIXME found."); + } else if (!blocking.length) { + console.log("\n✅ No blocking issues (critical paths are clean)."); } } -if (!blocking.length && !warnings.length) { - console.log("✅ No untracked TODO/FIXME found."); -} else if (!blocking.length) { - console.log("\n✅ No blocking issues (critical paths are clean)."); +const isMain = + process.argv[1] && + import.meta.url === pathToFileURL(process.argv[1]).href; + +if (isMain) { + main(); } + +export { + CRITICAL_PATHS, + SOURCE_EXTENSIONS, + TODO_TOKEN, + TRACKED_TODO, + isCritical, + extOf, + isIgnored, +}; diff --git a/scripts/check-todos.test.mjs b/scripts/check-todos.test.mjs new file mode 100644 index 00000000..0b3821ef --- /dev/null +++ b/scripts/check-todos.test.mjs @@ -0,0 +1,41 @@ +#!/usr/bin/env node + +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { + TRACKED_TODO, + TODO_TOKEN, + isCritical, + extOf, + isIgnored, +} from "./check-todos.mjs"; + +describe("check-todos policy", () => { + it("detects TODO/FIXME tokens", () => { + assert.equal(TODO_TOKEN.test("// TODO: bare"), true); + assert.equal(TODO_TOKEN.test("// FIXME(#496): tracked"), true); + assert.equal(TODO_TOKEN.test("const autodocs = 1"), false); + }); + + it("accepts GitHub issue references in tracked format", () => { + assert.equal(TRACKED_TODO.test("// TODO(#491): migrate SDK"), true); + assert.equal(TRACKED_TODO.test("// FIXME(#496): stub"), true); + assert.equal(TRACKED_TODO.test("// TODO: no ref"), false); + assert.equal(TRACKED_TODO.test("// TODO(#abc): bad ref"), false); + }); + + it("classifies critical paths for auth, payments, and integrations", () => { + assert.equal(isCritical("client/lib/payment-service.ts"), true); + assert.equal(isCritical("client/app/api/auth/route.ts"), true); + assert.equal(isCritical("backend/src/services/telegram-bot-service.ts"), true); + assert.equal(isCritical("client/app/page.tsx"), false); + assert.equal(isCritical("docs/ROADMAP.md"), false); + }); + + it("ignores build artifacts and scans source extensions only", () => { + assert.equal(isIgnored("client/node_modules/foo.ts"), true); + assert.equal(isIgnored("client/lib/foo.ts"), false); + assert.equal(extOf("backend/src/x.ts"), ".ts"); + assert.equal(extOf("README"), ""); + }); +});