diff --git a/TASKS.md b/TASKS.md index 88c6fa1..a44e00a 100644 --- a/TASKS.md +++ b/TASKS.md @@ -469,3 +469,20 @@ No Stripe Connect code exists anywhere in the repo yet — `lib/stripe.js` only - `lib/__tests__/rateLimit.test.js`: 12/12 unit tests passing (4 new assertions covering isolation, token independence, and Meta webhook ceilings). - Full Vitest suite: 19/19 test files passed, 137/137 tests green (`yarn --ignore-engines test`). - Production build: `yarn --ignore-engines build` exit code 0, 75 static/dynamic routes compiled cleanly in 78s. + +--- + +## Session log (2026-10-17) — audit fixes: UK GDPR/PECR erasure, deletion callback, SMS opt-out (`fix/compliance`) + +Audit against `e493421`. Tests in `lib/__tests__/`. + +- ✅ **Public result PII** — superseded by #42, which deleted `GET /api/result/:id` outright (nothing called it). `toPublicResult` was dropped in the merge. `app/r/[id]` reads Firestore server-side and never renders the owner fields. +- ⚠️ **Meta data-deletion callback — NOT fixed, blocked.** Meta's `user_id` is the app-scoped Facebook *user* ID, but the Instagram/Messenger connect flows only take a pasted *Page* access token and validate it with `GET /{id}?fields=id,name` — no call returns a user ID, so `metaUserId` can't be stored and the callback still matches nothing (and still records `status: "completed"`). +- ✅ **Account erasure gaps** — `eraseUserData` also deletes `agencies/{uid}`, `inbound_tokens` (`uid`), `ghl_events` (`uid`), `invites` (`agencyId`), orphan `users` docs sharing the user's `stripeCustomerId`, and `arrayRemove`s the uid from another owner's agency `memberUids`. +- ✅ **Stripe cancel no longer swallowed** — `DELETE /api/account` aborts with a 502 *before* any erasure unless the cancel succeeds, Stripe returns `resource_missing`, or the sub is already `canceled`/`incomplete_expired` (`cancelSubscriptionBeforeErasure`, retrieve-first as in #42, now unit-tested). An `incomplete_expired` sub can't be cancelled, so the earlier cancel-first version would have blocked deletion for good. +- ✅ **No zombie `users` doc after deletion** — the cancel fires `customer.subscription.deleted` right after erasure; the webhook used `set(merge)` and recreated `users/{uid}`. It now uses `update()` and ignores NOT_FOUND. +- ✅ **SMS STOP bypass** — `/reminders/schedule` requires E.164 (spaces stripped, no country-code guessing); the cron fails any queued non-E.164 reminder (`invalid number`) and checks `smsSuppressed` against the normalized number. Same rule in `reminderService.js` (`normalizeE164`, shared by the live route). + +### Still open +- [ ] **Meta deletion callback: capture the Meta user ID** — add an OAuth (Facebook Login) connect flow or call `GET /debug_token` (needs an app access token: `META_APP_ID|META_APP_SECRET`) at connect time to store `metaUserId` on the channel doc, then match `collectionGroup('channels').where('metaUserId','==',user_id)` in the callback. Until then, don't report deletions as `completed`. Channels connected before that change will have no `metaUserId`. +- [ ] **Pin canary/beta dependencies to stable releases** — React canary, Zod canary, Stripe beta, date-fns alpha. diff --git a/app/api/[[...path]]/route.js b/app/api/[[...path]]/route.js index 96aad7b..b4e91a8 100644 --- a/app/api/[[...path]]/route.js +++ b/app/api/[[...path]]/route.js @@ -10,7 +10,11 @@ import { logError } from "@/lib/logger"; import { AppError } from "@/lib/errors"; import { buildOpenApiSpec } from "@/lib/openapi"; import { createAgent, isAgentAccessDenied } from "@/lib/services/agentService"; -import { eraseUserData } from "@/lib/services/accountService"; +import { + eraseUserData, + cancelSubscriptionBeforeErasure, + SUBSCRIPTION_CANCEL_FAILED_MESSAGE, +} from "@/lib/services/accountService"; import { createCheckoutSession, createPortalSession, @@ -54,6 +58,7 @@ import { testConnection as testEmailConnection, sendEmail } from "@/lib/email"; import { sendMail } from "@/lib/mail"; import { testTwilio, sendSMS } from "@/lib/sms"; +import { normalizeE164, E164_ERROR } from "@/lib/services/reminderService"; import { ghlValidate, ghlGetContact, @@ -1485,36 +1490,34 @@ Rules: // 1. Cancel Stripe subscription if active. On failure, stop before erasing: // a deleted user can't log in to cancel, so billing would run forever. + const userDoc = await db.collection("users").doc(uid).get(); + const subId = userDoc.data()?.stripeSubscriptionId; try { - const userDoc = await db.collection("users").doc(uid).get(); - const subId = userDoc.data()?.stripeSubscriptionId; - if (subId) { - const stripe = getStripe(); - const sub = await stripe.subscriptions.retrieve(subId); - if (!["canceled", "incomplete_expired"].includes(sub.status)) - await stripe.subscriptions.cancel(subId); - } + await cancelSubscriptionBeforeErasure({ + stripe: subId ? getStripe() : null, + subscriptionId: subId, + }); } catch (err) { - // A subscription Stripe no longer has can't bill anyone. - if (err?.code !== "resource_missing") { - logError("account deletion: stripe cancel error", err); - return handleCORS( - request, - NextResponse.json( - { - error: - "Could not cancel your subscription. Please retry or contact support.", - }, - { status: 502 }, - ), - ); - } + logError("account deletion: stripe cancel error", err, { uid }); + return handleCORS( + request, + NextResponse.json( + { error: SUBSCRIPTION_CANCEL_FAILED_MESSAGE }, + { status: 502 }, + ), + ); } // 2. Erase all Firestore data, subcollections included. On failure, stop // before deleting the Auth user so the request can be retried. try { - await eraseUserData({ db, uid }); + await eraseUserData({ + db, + FieldValue, + uid, + email: decoded.email, + emailVerified: decoded.email_verified, + }); } catch (err) { logError("account deletion: firestore erasure failed", err); return handleCORS( @@ -1944,9 +1947,9 @@ Rules: request, NextResponse.json({ error: "invalid JSON body" }, { status: 400 }), ); - const { to, scheduledAt, leadName } = body; + const { scheduledAt, leadName } = body; const when = Date.parse(scheduledAt); - if (!to || !Number.isFinite(when)) + if (!body.to || !Number.isFinite(when)) return handleCORS( request, NextResponse.json( @@ -1954,6 +1957,12 @@ Rules: { status: 400 }, ), ); + const to = normalizeE164(body.to); + if (!to) + return handleCORS( + request, + NextResponse.json({ error: E164_ERROR }, { status: 400 }), + ); const pendingRef = db .collection("reminders") @@ -1974,7 +1983,7 @@ Rules: pendingRef.doc(id).set({ id, uid: decoded.uid, - to: truncate(String(to), 40), + to, body: `Hi ${name}, reminder: your call is in ${o.label}.`, sendAt: new Date(sendAt), status: "pending", @@ -2035,11 +2044,19 @@ Rules: }); if (!claimed) continue; try { + // Reminders queued before E.164 validation may hold numbers no + // opt-out can match; never send to those. + const to = normalizeE164(r.to); + if (!to) { + failed++; + await doc.ref.update({ status: "failed", error: "invalid number" }); + continue; + } const suppressed = await db .collection("users") .doc(r.uid) .collection("smsSuppressed") - .doc(r.to) + .doc(to) .get(); if (suppressed.exists) { await doc.ref.update({ @@ -2057,7 +2074,7 @@ Rules: if (!chSnap.exists || !chSnap.data().connected) throw new Error("sms channel not connected"); const creds = JSON.parse(decrypt(chSnap.data().encryptedCreds)); - await sendSMS(creds, r.to, r.body); + await sendSMS(creds, to, r.body); sent++; } catch (e) { failed++; diff --git a/app/api/stripe/webhook/route.js b/app/api/stripe/webhook/route.js index b647902..f5f20d2 100644 --- a/app/api/stripe/webhook/route.js +++ b/app/api/stripe/webhook/route.js @@ -6,6 +6,9 @@ import { logError } from "@/lib/logger"; export const dynamic = "force-dynamic"; +// Firestore NOT_FOUND: update() on a doc that doesn't exist. +const GRPC_NOT_FOUND = 5; + export async function POST(request) { const sig = request.headers.get("stripe-signature"); const body = await request.text(); @@ -117,18 +120,21 @@ export async function POST(request) { .limit(1) .get() ).docs[0]?.id; + // Account deletion cancels the subscription and then erases users/{uid}, + // so this event usually lands after the doc is gone. Writing it with + // set+merge would recreate a doc for a deleted user; update() can't. if (docId) await db .collection("users") .doc(docId) - .set( - { - status: "canceled", - plan: "free", - updatedAt: FV.serverTimestamp(), - }, - { merge: true }, - ); + .update({ + status: "canceled", + plan: "free", + updatedAt: FV.serverTimestamp(), + }) + .catch((err) => { + if (err?.code !== GRPC_NOT_FOUND) throw err; + }); break; } case "invoice.paid": diff --git a/graphify-out/.graphify_labels.json b/graphify-out/.graphify_labels.json index 508bc74..0f68b68 100644 --- a/graphify-out/.graphify_labels.json +++ b/graphify-out/.graphify_labels.json @@ -11,14 +11,14 @@ "9": "package.json", "10": "@playwright/test", "11": "utils.js", - "12": "auth-context.js", + "12": "dashboard/page.js", "13": "alert-dialog.jsx", - "14": "menubar.jsx", + "14": "agentService.js", "15": "command.jsx", "16": "components.json", "17": "biome.json", "18": "form.jsx", - "19": "health/route.js", + "19": "rateLimit.js", "20": "prospectService.js", "21": "UnauthorizedError", "22": "toggle-group.jsx", @@ -26,7 +26,7 @@ "24": "carousel.jsx", "25": "input-otp.jsx", "26": "navigation-menu.jsx", - "27": "prospects.js", + "27": "logError", "28": "drawer.jsx", "29": "@sentry/nextjs", "30": "paths", @@ -40,8 +40,8 @@ "38": "ref_node_url", "39": "scripts", "40": "app/layout.js", - "41": "encryption.js", - "42": "Use cases by area", + "41": "context-menu.jsx", + "42": "handleRoute", "43": "webhookAuth.js", "44": "contact/page.js", "45": "privacy/page.js", @@ -55,13 +55,13 @@ "53": "DMForge", "54": "@radix-ui/react-aspect-ratio", "55": "@radix-ui/react-collapsible", - "56": "graphify in DMForge", + "56": "auth-context.js", "57": "next.config.js", "58": "tailwind.config.js", "59": "vercel.json", - "60": "email.js", + "60": "health/route.js", "61": "DMForge Deployment Runbook", - "62": "[id]/page.js", + "62": "Test Run 1 - Firebase Auth + Firestore E2E Testing", "63": "Bug Report Template", "64": "Feature Request Template", "65": "Pull Request Template", @@ -69,9 +69,11 @@ "67": "postcss.config.js", "68": "useAuth", "69": "__init__.py", + "70": "accordion.jsx", "71": "about/page.js", + "72": "complaints/page.js", + "73": "data-deletion/page.js", "83": "DMForge E2E — real-browser click tests (Playwright)", - "89": "Security Policy", "93": "vitest.config.mjs", "94": "CLEAN_CODE_REPORT.md", "95": "engines", diff --git a/graphify-out/GRAPH_REPORT.md b/graphify-out/GRAPH_REPORT.md index e9219c3..27fa077 100644 --- a/graphify-out/GRAPH_REPORT.md +++ b/graphify-out/GRAPH_REPORT.md @@ -1,17 +1,17 @@ -# Graph Report - friendly-easley-93ee36 (2026-10-16) +# Graph Report - compliance (2026-10-17) ## Corpus Check -- 201 files · ~96,028 words +- 209 files · ~101,662 words - Verdict: corpus is large enough that graph structure adds value. - Unclassified: 21 file(s) not represented in the graph (top: (none) 15, .example 1, .code-workspace 1) ## Summary -- 1236 nodes · 2735 edges · 81 communities (51 shown, 30 thin omitted) -- Extraction: 98% EXTRACTED · 2% INFERRED · 0% AMBIGUOUS · INFERRED: 53 edges (avg confidence: 0.93) +- 1276 nodes · 2834 edges · 83 communities (51 shown, 32 thin omitted) +- Extraction: 98% EXTRACTED · 2% INFERRED · 0% AMBIGUOUS · INFERRED: 61 edges (avg confidence: 0.93) - Token cost: 0 input · 0 output ## Graph Freshness -- Built from commit: `26c6f70f` +- Built from commit: `22d8157d` - Run `git rev-parse HEAD` and compare to check if the graph is stale. - Run `graphify update .` after code changes (no API cost). @@ -28,14 +28,14 @@ - package.json - @playwright/test - utils.js -- auth-context.js +- dashboard/page.js - alert-dialog.jsx -- menubar.jsx +- agentService.js - command.jsx - components.json - biome.json - form.jsx -- health/route.js +- rateLimit.js - prospectService.js - UnauthorizedError - toggle-group.jsx @@ -43,7 +43,7 @@ - carousel.jsx - input-otp.jsx - navigation-menu.jsx -- prospects.js +- logError - drawer.jsx - @sentry/nextjs - paths @@ -57,8 +57,8 @@ - ref_node_url - scripts - app/layout.js -- encryption.js -- Use cases by area +- context-menu.jsx +- handleRoute - webhookAuth.js - contact/page.js - privacy/page.js @@ -72,20 +72,21 @@ - DMForge - @radix-ui/react-aspect-ratio - @radix-ui/react-collapsible -- graphify in DMForge +- auth-context.js - next.config.js - vercel.json -- email.js +- health/route.js - DMForge Deployment Runbook -- [id]/page.js +- Test Run 1 - Firebase Auth + Firestore E2E Testing - Bug Report Template - Feature Request Template - Pull Request Template - Legal & Regulatory Compliance Notes - useAuth +- accordion.jsx - about/page.js +- complaints/page.js - DMForge E2E — real-browser click tests (Playwright) -- Security Policy - vitest.config.mjs - CLEAN_CODE_REPORT.md - engines @@ -97,26 +98,26 @@ ## God Nodes (most connected - your core abstractions) 1. `cn()` - 224 edges 2. `authFetch()` - 69 edges -3. `react` - 64 edges -4. `handleRoute()` - 56 edges +3. `handleRoute()` - 66 edges +4. `react` - 65 edges 5. `UnauthorizedError` - 54 edges -6. `ValidationError` - 42 edges +6. `ValidationError` - 46 edges 7. `lucide-react` - 34 edges 8. `logError()` - 30 edges 9. `useAuth()` - 29 edges -10. `next` - 28 edges +10. `next` - 29 edges ## Surprising Connections (you probably didn't know these) - `Recommendations` --references--> `authFetch()` [INFERRED] test_result.md → lib/auth-context.js - `Test Coverage` --references--> `authFetch()` [INFERRED] test_result.md → lib/auth-context.js -- `Health check (2026-10-16)` --references--> `UnauthorizedError` [INFERRED] - docs/GRAPHIFY.md → lib/errors.js -- `Health check (2026-10-16)` --references--> `ValidationError` [INFERRED] - docs/GRAPHIFY.md → lib/errors.js -- `Use cases by area` --references--> `checkLlmRateLimit()` [INFERRED] - docs/GRAPHIFY.md → lib/rateLimit.js +- `Specification & Background` --references--> `handleRoute()` [INFERRED] + project-tasks/inbound-rate-limit-tasklist.md → app/api/[[...path]]/route.js +- `[x] Task 3: Wire `checkInboundRateLimit` into `app/api/[[...path]]/route.js`` --references--> `handleRoute()` [INFERRED] + project-tasks/inbound-rate-limit-tasklist.md → app/api/[[...path]]/route.js +- `Use cases by area` --references--> `useAuth()` [INFERRED] + docs/GRAPHIFY.md → lib/auth-context.js ## Import Cycles - None detected. @@ -124,11 +125,11 @@ ## Hyperedges (group relationships) - **Design Governance Stack** — impeccable_design_context, figma_design_system, brand_tokens [EXTRACTED 1.00] -## Communities (81 total, 30 thin omitted) +## Communities (83 total, 32 thin omitted) ### Community 0 - "react" -Cohesion: 0.22 -Nodes (15): ProspectCard(), relTime(), STATUS_STYLE, STATUSES, ThreadDrawer(), GREETING, Badge(), badgeVariants (+7 more) +Cohesion: 0.20 +Nodes (16): ProspectCard(), relTime(), STATUS_STYLE, STATUSES, ThreadDrawer(), GREETING, Badge(), badgeVariants (+8 more) ### Community 1 - "dependencies" Cohesion: 0.03 @@ -151,12 +152,12 @@ Cohesion: 0.06 Nodes (41): Separator, components_ui_sheet_sheet, SheetContent, SheetDescription, SheetFooter(), SheetHeader(), SheetOverlay, SheetTitle (+33 more) ### Community 6 - "cn" -Cohesion: 0.06 -Nodes (50): Avatar, AvatarFallback, AvatarImage, CardContent, CardDescription, CardFooter, CardHeader, CardTitle (+42 more) +Cohesion: 0.05 +Nodes (52): Avatar, AvatarFallback, AvatarImage, CardContent, CardDescription, CardFooter, CardHeader, CardTitle (+44 more) ### Community 7 - "[[...path]]/route.js" Cohesion: 0.10 -Nodes (28): agentDenied(), ALLOWED_ORIGINS, CHANNEL_LABELS, CHAT_TURN_SCHEMA, DELETE, EMPTY_CHAT_STATE, formatPrice(), GET (+20 more) +Nodes (26): ALLOWED_ORIGINS, CHANNEL_LABELS, CHAT_TURN_SCHEMA, DELETE, EMPTY_CHAT_STATE, formatPrice(), GET, handleCORS() (+18 more) ### Community 8 - "functions/index.js" Cohesion: 0.08 @@ -164,27 +165,27 @@ Nodes (27): crypto, decrypt(), { defineSecret }, deriveKey(), ENCRYPTION_KEY, EN ### Community 9 - "package.json" Cohesion: 0.06 -Nodes (31): homepage, firebase-admin, license, name, packageManager, private, version, autoprefixer (+23 more) +Nodes (33): homepage, firebase-admin, license, name, packageManager, private, version, autoprefixer (+25 more) ### Community 10 - "@playwright/test" -Cohesion: 0.07 -Nodes (18): findPlaceholders(), leadFacingFields(), scriptPlaceholders(), { defineConfig, devices }, IMPORTANT: Defaults to localhost (safe for local/CI testing)., @playwright/test, { test, expect }, { test, expect } (+10 more) +Cohesion: 0.06 +Nodes (19): BOOKED_CONFIRMATION, deriveResultState(), toPublicResult(), { defineConfig, devices }, IMPORTANT: Defaults to localhost (safe for local/CI testing)., @playwright/test, { test, expect }, { test, expect } (+11 more) ### Community 11 - "utils.js" -Cohesion: 0.05 -Nodes (27): AccordionContent, AccordionItem, AccordionTrigger, Checkbox, HoverCardContent, PopoverContent, Progress, RadioGroup (+19 more) +Cohesion: 0.06 +Nodes (21): Checkbox, HoverCardContent, PopoverContent, Progress, RadioGroup, RadioGroupItem, ResizableHandle(), ResizablePanelGroup() (+13 more) -### Community 12 - "auth-context.js" -Cohesion: 0.16 -Nodes (7): Logo(), AuthCtx, auth, db, firebaseConfig, googleProvider, firebase +### Community 12 - "dashboard/page.js" +Cohesion: 0.17 +Nodes (5): generateMetadata(), getResult(), ResultPage(), Logo(), @testing-library/react ### Community 13 - "alert-dialog.jsx" Cohesion: 0.11 Nodes (20): AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter(), AlertDialogHeader(), AlertDialogOverlay, AlertDialogTitle (+12 more) -### Community 14 - "menubar.jsx" -Cohesion: 0.11 -Nodes (12): Menubar, MenubarCheckboxItem, MenubarContent, MenubarItem, MenubarLabel, MenubarRadioItem, MenubarSeparator, MenubarShortcut() (+4 more) +### Community 14 - "agentService.js" +Cohesion: 0.21 +Nodes (14): agentDenied(), AppError, chat(), chatJSON(), GEMINI_URL(), repairLLMJson(), toGemini(), findPlaceholders() (+6 more) ### Community 15 - "command.jsx" Cohesion: 0.11 @@ -202,21 +203,21 @@ Nodes (41): source, assist, actions, noUnusedImports, noUnusedVariables, files, Cohesion: 0.17 Nodes (13): FormControl, FormDescription, FormFieldContext, FormItem, FormItemContext, FormLabel, FormMessage, useFormField() (+5 more) -### Community 19 - "health/route.js" -Cohesion: 0.13 -Nodes (23): checkFirestore(), checkGemini(), checkStripe(), dynamic, GET(), chat(), chatJSON(), GEMINI_BASE() (+15 more) +### Community 19 - "rateLimit.js" +Cohesion: 0.15 +Nodes (18): buckets, checkInboundRateLimit(), checkLlmRateLimit(), checkRateLimit(), clientIp(), getLimiter(), hit(), hitMemory() (+10 more) ### Community 20 - "prospectService.js" Cohesion: 0.35 Nodes (14): NotFoundError, normalizeChannel(), normalizeStatus(), addProspectMessage(), appendProspectMessage(), createProspect(), deleteProspect(), getOrCreateInboundToken() (+6 more) ### Community 21 - "UnauthorizedError" -Cohesion: 0.07 -Nodes (73): AppError, BadGatewayError, ForbiddenError, UnauthorizedError, ValidationError, log(), logError(), sendMetaMessage() (+65 more) +Cohesion: 0.05 +Nodes (82): cleanSecret(), decrypt(), deriveKey(), encrypt(), getCurrentKey(), getDecryptKeys(), packAndEncrypt(), unpackAndDecrypt() (+74 more) ### Community 22 - "toggle-group.jsx" -Cohesion: 0.31 -Nodes (7): ToggleGroup, ToggleGroupContext, ToggleGroupItem, Toggle, toggleVariants, @radix-ui/react-toggle, @radix-ui/react-toggle-group +Cohesion: 0.17 +Nodes (12): Alert, AlertDescription, AlertTitle, alertVariants, ToggleGroup, ToggleGroupContext, ToggleGroupItem, Toggle (+4 more) ### Community 23 - "chart.jsx" Cohesion: 0.29 @@ -231,19 +232,19 @@ Cohesion: 0.33 Nodes (5): InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, input-otp ### Community 26 - "navigation-menu.jsx" -Cohesion: 0.15 -Nodes (13): Alert, AlertDescription, AlertTitle, alertVariants, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuList (+5 more) +Cohesion: 0.25 +Nodes (8): NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuList, NavigationMenuTrigger, navigationMenuTriggerStyle, NavigationMenuViewport, @radix-ui/react-navigation-menu -### Community 27 - "prospects.js" -Cohesion: 0.32 -Nodes (10): ghlCreateAppointment(), ghlCreateContact(), ghlGetContact(), ghlValidate(), headers(), onProspectBooked(), PROSPECT_CHANNELS, PROSPECT_STATUSES (+2 more) +### Community 27 - "logError" +Cohesion: 0.24 +Nodes (13): resolveSeats(), ghlCreateAppointment(), ghlCreateContact(), ghlGetContact(), ghlValidate(), headers(), logError(), onProspectBooked() (+5 more) ### Community 28 - "drawer.jsx" Cohesion: 0.22 Nodes (7): DrawerContent, DrawerDescription, DrawerFooter(), DrawerHeader(), DrawerOverlay, DrawerTitle, vaul ### Community 29 - "@sentry/nextjs" -Cohesion: 0.33 +Cohesion: 0.29 Nodes (3): onRequestError(), register(), @sentry/nextjs ### Community 30 - "paths" @@ -263,8 +264,8 @@ Cohesion: 0.08 Nodes (24): compilerOptions, alwaysStrict, esModuleInterop, exactOptionalPropertyTypes, forceConsistentCasingInFileNames, isolatedModules, lib, module (+16 more) ### Community 37 - "Project: DMForge — AI DM setter builder + Stripe-enabled SaaS" -Cohesion: 0.10 -Nodes (19): API endpoints (all working), Backend sub-agent log, Critical Issues Requiring Fix, Detailed Findings, Frontend sub-agent log, Key Findings, Notes, Pages (+11 more) +Cohesion: 0.14 +Nodes (13): API endpoints (all working), Backend sub-agent log, Frontend sub-agent log, Key Findings, Notes, Pages, Phase 2 NOT yet built, Project: DMForge — AI DM setter builder + Stripe-enabled SaaS (+5 more) ### Community 39 - "scripts" Cohesion: 0.14 @@ -274,17 +275,17 @@ Nodes (14): scripts, audit, build, dev, dev:emulated, emulators, start, test (+6 Cohesion: 0.07 Nodes (28): fetchSession(), metadata, Success(), app_globals, baseUrl, body, display, metadata (+20 more) -### Community 41 - "encryption.js" -Cohesion: 0.36 -Nodes (8): cleanSecret(), decrypt(), deriveKey(), encrypt(), getCurrentKey(), getDecryptKeys(), packAndEncrypt(), unpackAndDecrypt() +### Community 41 - "context-menu.jsx" +Cohesion: 0.18 +Nodes (10): ContextMenuCheckboxItem, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut(), ContextMenuSubContent (+2 more) -### Community 42 - "Use cases by area" -Cohesion: 0.22 -Nodes (21): dynamic, POST(), syncSubscription(), Command → use-case map, Health check (2026-10-16), Use cases by area, ensureInit(), getAdminAuth() (+13 more) +### Community 42 - "handleRoute" +Cohesion: 0.19 +Nodes (26): handleRoute(), dynamic, POST(), syncSubscription(), Blind spots (static analysis limits), Command → use-case map, Health check (2026-10-16), Use cases by area (+18 more) ### Community 43 - "webhookAuth.js" -Cohesion: 0.36 -Nodes (7): BLOCKED_SUFFIXES, isPrivateIPv4(), isPrivateIPv6(), isPublicHttpsUrl(), safeEqual(), verifyMetaSignature(), ref_net +Cohesion: 0.18 +Nodes (12): BLOCKED_SUFFIXES, isPrivateIPv4(), isPrivateIPv6(), isPublicHttpsUrl(), parseMetaSignedRequest(), safeEqual(), verifyMetaSignature(), ref_net (+4 more) ### Community 47 - "breadcrumb.jsx" Cohesion: 0.25 @@ -299,56 +300,56 @@ Cohesion: 0.67 Nodes (3): repository, type, url ### Community 53 - "DMForge" -Cohesion: 0.07 -Nodes (28): DMForge Project Rules, DMForge Brand Tokens, Commands, DMForge — Project Rules, graphify, Layout, Rules, Stack (+20 more) +Cohesion: 0.06 +Nodes (35): DMForge Project Rules, DMForge Brand Tokens, Commands, DMForge — Project Rules, graphify, Layout, Rules, Stack (+27 more) -### Community 56 - "graphify in DMForge" -Cohesion: 0.29 -Nodes (6): graphify in DMForge, How it stays fresh, Integrations available (not yet used here), Recommendations, What is excluded (`.graphifyignore`), What it is +### Community 56 - "auth-context.js" +Cohesion: 0.39 +Nodes (6): AuthCtx, auth, db, firebaseConfig, googleProvider, firebase -### Community 60 - "email.js" -Cohesion: 0.48 -Nodes (6): buildTransport(), resolveCreds(), sendEmail(), sendSystemEmail(), testConnection(), nodemailer +### Community 60 - "health/route.js" +Cohesion: 0.39 +Nodes (7): checkFirestore(), checkGemini(), checkStripe(), dynamic, GET(), GEMINI_BASE(), MODEL -### Community 62 - "[id]/page.js" -Cohesion: 0.83 -Nodes (3): generateMetadata(), getResult(), ResultPage() +### Community 62 - "Test Run 1 - Firebase Auth + Firestore E2E Testing" +Cohesion: 0.33 +Nodes (6): Critical Issues Requiring Fix, Detailed Findings, Recommendations, Test Artifacts, Test Coverage, Test Run 1 - Firebase Auth + Firestore E2E Testing ### Community 68 - "useAuth" -Cohesion: 0.13 -Nodes (20): App(), Nav(), Pricing(), checkout(), portal(), AuthModal(), google(), submit() (+12 more) +Cohesion: 0.15 +Nodes (18): App(), Nav(), Pricing(), checkout(), portal(), AuthModal(), google(), submit() (+10 more) + +### Community 70 - "accordion.jsx" +Cohesion: 0.40 +Nodes (4): AccordionContent, AccordionItem, AccordionTrigger, @radix-ui/react-accordion ### Community 83 - "DMForge E2E — real-browser click tests (Playwright)" Cohesion: 0.29 Nodes (6): DMForge E2E — real-browser click tests (Playwright), Install (one time, in the DMForge repo root), Note: keep test files out of the deployed bundle, Reuse for future projects, Run, What it covers (tests/e2e/smoke.spec.js) -### Community 89 - "Security Policy" -Cohesion: 0.50 -Nodes (3): Reporting a Vulnerability, Scope, Security Policy - ### Community 134 - "audit-code.js" Cohesion: 0.21 Nodes (10): ref_fs, ref_path, audit(), EXTENSIONS, fs, IGNORED_DIRS, main(), path (+2 more) ## Knowledge Gaps -- **349 isolated node(s):** `Tech Stack`, `Prerequisites`, `Setup`, `Local dev against the Firebase emulators (safe for webhook tests)`, `Running Tests` (+344 more) - These have ≤1 connection - possible missing edges or undocumented components. (Counts symbols only; 455 node(s) total have ≤1 connection when file, concept and rationale nodes are included.) -- **30 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes. +- **355 isolated node(s):** `cloudflare-api`, `cloudflare-ai-gateway`, `metadata`, `ALLOWED_ORIGINS`, `EMPTY_CHAT_STATE` (+350 more) + These have ≤1 connection - possible missing edges or undocumented components. (Counts symbols only; 472 node(s) total have ≤1 connection when file, concept and rationale nodes are included.) +- **32 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes. ## Suggested Questions _Questions this graph is uniquely positioned to answer:_ -- **Why does `cn()` connect `cn` to `react`, `useAuth`, `sidebar.jsx`, `Use cases by area`, `utils.js`, `alert-dialog.jsx`, `menubar.jsx`, `breadcrumb.jsx`, `command.jsx`, `form.jsx`, `toggle-group.jsx`, `chart.jsx`, `carousel.jsx`, `input-otp.jsx`, `navigation-menu.jsx`, `drawer.jsx`?** - _High betweenness centrality (0.177) - this node is a cross-community bridge._ -- **Why does `dependencies` connect `dependencies` to `package.json`?** - _High betweenness centrality (0.136) - this node is a cross-community bridge._ -- **Why does `next` connect `react` to `best/[slug]/page.js`, `useAuth`, `[[...path]]/route.js`, `about/page.js`, `app/layout.js`, `Use cases by area`, `package.json`, `auth-context.js`, `health/route.js`, `UnauthorizedError`, `proxy.js`, `[id]/page.js`, `settings/layout.js`?** - _High betweenness centrality (0.114) - this node is a cross-community bridge._ +- **Why does `cn()` connect `cn` to `react`, `sidebar.jsx`, `accordion.jsx`, `context-menu.jsx`, `handleRoute`, `utils.js`, `alert-dialog.jsx`, `breadcrumb.jsx`, `command.jsx`, `form.jsx`, `toggle-group.jsx`, `chart.jsx`, `carousel.jsx`, `input-otp.jsx`, `navigation-menu.jsx`, `drawer.jsx`?** + _High betweenness centrality (0.243) - this node is a cross-community bridge._ +- **Why does `authFetch()` connect `authFetch` to `react`, `useAuth`, `handleRoute`, `dashboard/page.js`, `auth-context.js`, `Test Run 1 - Firebase Auth + Firestore E2E Testing`?** + _High betweenness centrality (0.106) - this node is a cross-community bridge._ +- **Why does `next` connect `react` to `best/[slug]/page.js`, `useAuth`, `[[...path]]/route.js`, `about/page.js`, `app/layout.js`, `handleRoute`, `data-deletion/page.js`, `dashboard/page.js`, `package.json`, `proxy.js`, `logError`, `health/route.js`, `settings/layout.js`?** + _High betweenness centrality (0.095) - this node is a cross-community bridge._ - **Are the 3 inferred relationships involving `cn()` (e.g. with `Command → use-case map` and `Health check (2026-10-16)`) actually correct?** _`cn()` has 3 INFERRED edges - model-reasoned connections that need verification._ - **Are the 5 inferred relationships involving `authFetch()` (e.g. with `Command → use-case map` and `Health check (2026-10-16)`) actually correct?** _`authFetch()` has 5 INFERRED edges - model-reasoned connections that need verification._ -- **Are the 4 inferred relationships involving `handleRoute()` (e.g. with `Blind spots (static analysis limits)` and `Command → use-case map`) actually correct?** - _`handleRoute()` has 4 INFERRED edges - model-reasoned connections that need verification._ -- **What connects `Tech Stack`, `Prerequisites`, `Setup` to the rest of the system?** - _349 weakly-connected nodes found - possible documentation gaps or missing edges._ \ No newline at end of file +- **Are the 6 inferred relationships involving `handleRoute()` (e.g. with `Blind spots (static analysis limits)` and `Command → use-case map`) actually correct?** + _`handleRoute()` has 6 INFERRED edges - model-reasoned connections that need verification._ +- **What connects `cloudflare-api`, `cloudflare-ai-gateway`, `metadata` to the rest of the system?** + _355 weakly-connected nodes found - possible documentation gaps or missing edges._ \ No newline at end of file diff --git a/graphify-out/graph.html b/graphify-out/graph.html index b6319fb..cdb54be 100644 --- a/graphify-out/graph.html +++ b/graphify-out/graph.html @@ -63,12 +63,12 @@

Communities

-
1236 nodes · 2735 edges · 81 communities
+
1276 nodes · 2834 edges · 83 communities