65 KiB
meSEO Deep Audit Inventory
Date: 2026-06-10 Commit: 07e37ef (HEAD, main) Author: Claude Code -- audit-only session Purpose: Onboarding reference for new dev team. Grounds all claims in actual file paths, commit hashes, and code. Where something cannot be confirmed from code, it is marked UNCONFIRMED.
Table of Contents
- Session and Phase History
- Sidebar Pages
- Core Platform Architecture
- Audit Flows
- AI Strategist
- JSX Dashboards
- Guardrails
- Customer-Facing Surfaces
- Schema and Crons
- Risk Register
- Patent Map
1. Session and Phase History
Pre-git-history phases (reconstructed from code evidence)
The visible git history begins at 2026-05-25 (phase 30B). The code itself, however, contains evidence of a complete multi-phase build predating that commit. The following phases are reconstructed from what exists in the codebase today.
Phase 1-3: Foundation
Evidence: prisma/schema.prisma multi-tenant models; src/lib/auth/, src/middleware.ts, Clerk provider in src/app/layout.tsx.
- Multi-tenant architecture:
Organization,Brand,User,OrgMembership,BrandMembership - Clerk authentication: userId sync, organization context,
BrandMembershiprole enforcement - Brand model:
domain,industry,locations[],primaryServices[],secondaryServices[],plan,billingCycle,planExpiresAt,healthScore,isDemo - Organization + subscription management
- Onboarding flow:
src/app/onboarding/page.tsx - Settings:
src/app/settings/page.tsx(tabs: profile, integrations, notifications, white-label) - Brands Hub: multi-brand switcher,
src/app/brands-hub/page.tsx - Pricing page:
src/app/pricing/page.tsx
Phase 4-6: Data Layer + Integrations
Evidence: src/lib/services/ga4-sync.ts, src/app/api/gsc/backfill/route.ts, src/lib/integrations/nango.ts, prisma/schema.prisma BrandIntegration model.
Nango OAuth provider (src/lib/integrations/nango.ts):
- Universal OAuth for GSC, GA4, GBP, Bing
getValidAccessToken(brandId, integrationId)-- refreshes if expiringgetCredentials(brandId, integrationId)-- returns stored credentials- Credentials stored encrypted in
BrandIntegration.credentialsEnc(AES-256-GCM) - Token expiry tracked in
BrandIntegration.tokenExpiresAt
GSC backfill (src/app/api/gsc/backfill/route.ts):
POST /api/gsc/backfill-- backfills 16 months of historical data- Dimensions:
["date", "page"]and["date", "query"] - Pagination: 25,000 rows per batch; handles multi-batch responses
GscPageupsert:brandId, pageUrl, clicks, impressions, ctr, position, dateGscQueryupsert:brandId, query, clicks, impressions, ctr, position, date- Pre-deletes date range to prevent duplicate rows
- Verifies connection via totals call before backfilling
GA4 sync (src/lib/services/ga4-sync.ts):
storeGa4Data(brandId, rows, startDate, endDate)-- upserts daily snapshots- Safeguard: detects inflated key events (>3x sessions) and skips conversion writes
MetricSnapshotupsert:{ brandId, date, source: "ga4", sessions, users, pageviews, conversions, bounceRate }getGa4Summary(brandId, days)-- summary query for dashboard/AI avatar
MetricSnapshot + ChannelMetric (schema):
MetricSnapshot: daily per-brand per-source (ga4|gsc|site_tag|daily|manual|seed)ChannelMetric: daily per-channel breakdown (organic|ai_search|social|gbp|direct)
Integrations page (src/app/integrations/page.tsx):
- Connect/disconnect: GSC, GA4, GBP, Bing
- Status indicators per integration
- API:
/api/integrations/connect,/api/integrations/disconnect
Phase 7-9: Core Audit Runner
Evidence: src/lib/audit/audit-runner.ts (2038 lines), 26 dimension aggregator files under src/lib/audit/aggregators/, 7 synthesizer files under src/lib/audit/synthesizers/, src/lib/audit/types.ts (900+ lines).
audit-runner.ts -- main orchestration:
- 7-phase pipeline: (1) brand context, (2) data fetch, (3) external APIs (DataForSEO + Firecrawl), (4) dimension aggregation, (5) LLM synthesis, (6) output assembly, (7) DB write
- Cost tracking constants:
COST_PER_CRAWL_PAGE = 0.0001,COST_PER_SONNET_CALL = 0.05 - Cost ceilings: $4.00 first-party, $2.00 third-party -- hard abort if exceeded
- Degraded mode: triggers when
pages.length === 0, uses Site Tag telemetry + GSC data - Prompt caching:
cache_control: { type: "ephemeral" }on narrative synthesis input - External calls: DataForSEO (
/on_page/task_post,/backlinks/summary/live,/serp/google/organic/live/advanced), Firecrawl (5-10 key pages, markdown + metadata), Anthropic
AuditInput type (src/lib/audit/types.ts):
40+ optional input fields including: brandId, domain, businessName, industry, locations[], primaryServices[], targetAudience, pages[], gscData, ga4Data, siteTagData, competitors[], competitorGaps[], backlinks, aiVisibility, trackedKeywords[], conversionData, adData, signalData, userEngagementData, existingAudit, analysisConfig
SeoSection type (src/lib/audit/types.ts):
40+ optional output fields including: overallScore, headline, summary, keyFindings[], priorityActions[], competitiveLandscape, organicSearch, technicalHealth, conversionGap, eeat, aiSearchVerification, aiEnginePersonality, voiceSearch, pageSpeed, backlinks, multiTouch, revenueAttribution, executiveBrief, strategicRoadmap, implementationAppendix, industryContext, dimensionScores
DimensionScores: technicalSeo, contentMetadata, aiVisibility, conversionOptimization, userEngagement
26 dimension aggregators (in src/lib/audit/aggregators/):
Each returns a typed object fed into the LLM context. Input = slices of AuditInput. Output = dimension-specific analysis.
| Aggregator | Domain |
|---|---|
backlinks.ts |
Domain authority, link quality, toxicity scoring |
brand-authority.ts |
Trust signals, E-E-A-T surface area |
competitor-deep-dive.ts |
Per-competitor detailed breakdown |
competitor-landscape.ts |
Market positioning, threat levels |
competitor-visual.ts |
Side-by-side metric comparison |
content-metadata.ts |
Title/meta/H1 coverage and quality |
conversion-heatmap.ts |
Conversion-weighted session data |
conversion-opportunity.ts |
Revenue gap vs. competitors |
ecommerce.ts |
Product listing, checkout funnel signals |
eeat.ts |
Expertise-Authority-Trust signals |
executive-brief.ts |
Score + business impact narrative |
industry-benchmark.ts |
Brand vs. vertical median KPIs |
key-findings.ts |
Top 5-7 prioritized findings assembly |
live-visual-annotations.ts |
Real-time behavioral overlay signals |
lost-opportunity.ts |
Revenue impact of ranking gaps |
multi-touch-ai.ts |
Multi-touch attribution across channels |
organic-search.ts |
Click/impression/position from GSC |
page-speed.ts |
Core Web Vitals (LCP/CLS/INP/TTFB/FCP) |
page-strategic-pass.ts |
Per-page content/technical recommendations |
predictive-conversion.ts |
ML-based conversion probability estimates |
priority-actions.ts |
Ranked action item assembly |
revenue-attribution.ts |
Per-channel revenue attribution |
schema-opportunity.ts |
Schema markup gap analysis |
signal-center.ts |
Rank/traffic/competitor signal aggregation |
snippet-capture.ts |
Snippet-by-snippet capture tactics |
strategic-roadmap.ts |
90-day implementation roadmap |
7 synthesizer files (in src/lib/audit/synthesizers/):
Each receives aggregated dimension data and returns narrative + structured output.
| Synthesizer | Model | Timeout | Notes |
|---|---|---|---|
seo-synthesizer.ts |
Sonnet 4.6 (narrative) + Haiku 4.5 (structured) | 280s + 45s in Promise.all |
Primary: 14K tokens. 14 industry lenses. Vendor confidentiality enforced (service names stripped). |
real-user-technical.ts |
-- | -- | Surfaces non-SEO issues from RealUserMetric |
ecommerce.ts |
-- | -- | E-commerce product/checkout funnel lens |
multilingual.ts |
-- | -- | hreflang and regional content gaps |
compliance.ts |
-- | -- | HIPAA/GDPR/legal compliance signals |
b2b.ts |
-- | -- | B2B SEO lens |
industry-classifier.ts |
-- | -- | Derives industry string[] from domain/content/integrations (commit 3fc9729) |
Industry lenses (14 categories): healthcare-medical, b2b-saas, ecommerce, local-services, professional-services, finance, legal, real-estate, nonprofit, hospitality, higher-ed, b2b, dentist, other. industryContext persisted to seoSection JSONB (commit ce67953).
Phase 10-12: AI + Public Audit
Evidence: src/app/api/public-audit/route.ts, src/lib/services/ai-visibility-checker.ts (250+ lines), src/app/ai-strategist/page.tsx, src/lib/ai-strategist/artifacts.ts.
Public audit flow (src/app/api/public-audit/route.ts):
- No auth required
- Lead capture: firstName, lastName, email, companyName, phoneNumber, websiteUrl
- Rate limit: 3 req/IP/hour
- OTP email verification -- returning leads (matched by email) bypass OTP
- Same synthesizer pipeline as first-party audit
LockedFirstPartyTeaser.tsxshows upgrade CTA to anonymous usersAudit.auditTokenstores the shareable audit URL
AI visibility checker (src/lib/services/ai-visibility-checker.ts):
- Uses Anthropic API with
web_search_20250305tool - 7 AI sources checked: ChatGPT, Perplexity, Gemini, Claude, Copilot, Phind, You.com
checkAiVisibility(brandId, query)-- returnsAiCheckResult { mentioned, position, context, sentiment, sourceUrls }- Results stored in
AiVisibilityCheck+ aggregate toAiVisibilitySnapshot - Weekly cron at
0 0 * * 1
AI Strategist (src/app/ai-strategist/page.tsx, 1342 lines):
- Conversational interface
- Context: brand profile + GSC + Site Tag + AnalysisJob.result + paid media + LLMO context
- Artifacts:
<artifact>tags rendered in iframe sandbox viaArtifactRenderer - Models: claude-sonnet-4-6, 12K max output tokens, 120s maxDuration
- Every call logged to
AiInteraction(userId, brandId, feature, guardrailFlags[])
Phase 13-15: Content + Execution + Competitive
Evidence: src/app/keyword-research/page.tsx, src/app/rank-tracker/page.tsx, src/app/api/cron/rank-check/route.ts, src/app/backlinks/page.tsx, src/app/competitors/page.tsx, src/app/api/content-briefs/generate/route.ts, src/app/execution-hub/page.tsx.
Keyword research (src/app/keyword-research/page.tsx):
- Deep-link:
?keyword=Xfrom Topic Map / Execution Hub - AI analysis output (Claude): summary, topic clusters, quick wins, gap analysis, question keywords, avoid keywords, content plan
- Each cluster:
name, keywords[], contentStrategy, priority, estimatedTrafficPotential - Search history:
GET /api/keyword-research/analyze?brandId=... - maxDuration: analyze 120s, search 60s
Rank tracker (src/app/rank-tracker/page.tsx):
GET /api/cron/rank-check(daily, 300s maxDuration)- DataForSEO
/serplive checks perTrackedKeyword - Cost cap: soft $5 USD/day, hard 100 keywords/brand
- Summary cards: total tracked, avg position, top-3/10/20/not-ranking counts
- Distribution chart with click-to-filter, movers panel (improved/declined)
TrackedKeyword: keyword, location, device, targetUrl, tags[], isActiveKeywordRanking: per-day position + previousPosition + checkedAt
Backlinks (src/app/backlinks/page.tsx):
Backlink: sourceDomain, sourceUrl, targetUrl, anchorText, linkType, domainAuthority, relevanceScore, toxicityScore, qualityTier, toxicityReasons[], status (active|lost|disavowed)BacklinkSnapshot: historical totals + avgDA + toxicCount- Features: quality distribution chart, toxic panel, gap analysis (competitor backlinks we lack), trend chart (monthly new/lost), ref domain list with DA ranking
- DataForSEO backlinks API: daily
backlink-synccron
Competitors (src/app/competitors/page.tsx):
Competitor: domain, name, type, isActive, lastCrawledAt, detectedCmsCompetitorSnapshot: date, pageCount, newPages, movement, rankChange, aiMentions, trafficEstimate- Features: discovery + manual add, sitemap monitoring, ranking comparison, AI strategic analysis (Claude), change timeline, content gaps
CompetitorDiscoveryJob: async discovery with progress tracking
Content briefs (src/app/api/content-briefs/generate/route.ts):
POST /api/content-briefs/generate- Types:
"seo"(keyword-optimized editorial, H1/H2 structure, FAQ, meta) and"llmo"(Question-Answer-Explanation-Insight cadence for AI citation) - Model:
callLlm()from@/lib/ai-avatar/llm-client - Humanization toggle, tone options (professional|conversational|persuasive)
ContentBrief: type, status (draft|review|approved|published), primaryKeyword, sections (JSON), humanizationMode, contentScore
Execution Hub (src/app/execution-hub/page.tsx, 73KB):
Task: title, category (seo|llmo|gbp|schema|content|technical|web-dev), status (todo|in_progress|completed), priority, source, sourceId, pipelineStage, briefId, assignedTo, dueDate- Views: Calendar, Pipeline board
- Tasks linked to audit/signal sources via sourceId
Pre-git-history continuation: supporting systems
Email reports (src/lib/services/email-report-generator.ts):
- Sections (configurable per schedule): GSC, GA4, Site Tag, Technical Audit, Rank Tracker, Conversions, Page Lifecycle, UX Signals
- Frequency: daily|weekly|biweekly|monthly
- AI enrichment: Claude analysis of snapshot data -> summary, wins, concerns, recommendation (JSON)
- Timezone support from
User.timezone(fallback: America/New_York) EmailReportSchedule: brandId, frequency, sections[], time, lastSentAtSentEmailReport: scheduleId, recipientEmail, sentAt, contentHash
Alert system (src/lib/services/signals.ts):
14 signal generators, all returning Signal[]:
generateTechnicalAuditSignals,generatePerformanceAuditSignals,generateDeadPageSignalsgenerateConversionSignals,generateExecutionSignals,generateAiVisibilitySignalsgenerateSiteTagSignals,generatePageLifecycleSignals,generateRankTrackingSignalsgenerateCompetitorSignals,generateDetectedSignals,generateAiRecommendationSignalsgenerateDataFreshnessSignals,generateAiRecommendationSignals
Signal shape:
{
id, title, description,
category: "issue"|"win"|"opportunity"|"revenue"|"ai"|"execution",
priority: "critical"|"high"|"medium"|"low",
status: "new"|"viewed"|"actioned",
timestamp, relatedPage?, relatedChannel?,
source, issueIds?, suggestedAction?, competitor?,
revenueImpact?: "high"|"medium"|"low"
}
Dismissal TTL: 14 days (synthetic signals re-surface after this period). Revenue impact derivation: rules-based on category + priority + content keywords.
CRM integration (src/lib/services/crm.ts):
- Supported CRMs: HubSpot, Salesforce, HighLevel, manual
CrmDeal: stages (lead|qualified|proposal|negotiation|closed_won|closed_lost), status (open|won|lost), value, closeDate, contactName, contactEmail, externalIdgetDeals(brandId, range?, filters?)-- with status/stage/source filteringgetPipelineSummary(brandId, range?)-- pipeline value, win rate, by-source breakdownupsertDeal()-- create/update via externalId (sync with external CRM)CrmDeallinks toConversionEventviaconversionrelationRevenueAttributiontracks conversion -> deal attribution
Page lifecycle (src/lib/services/page-lifecycle.ts, src/app/page-launch-tracker/page.tsx):
resolveSiteFromTag(siteId, pageUrl)-- validates TrackedSite + domain allowlisttoPathKey(url)-- normalizes to canonical/path(strips protocol/host/query/hash)buildFullUrl(brandDomain, path)-- reconstructs absolute URLsPageRecord: url, currentStatus, firstSeenAt, lastSeenAt, redirect chainsPageStatusChange: status transitions with timestampsRedirectRecord: source, target, statusCodePageDailyMetric: GSC-joined per-URL daily clicks/impressions- Page launch detection: joins Site Tag first-seen dates with GSC baselines; tracks post-launch deltas
Admin overview (src/app/api/admin/overview/route.ts):
- Aggregates: total orgs/brands/users, active brands (7-day), integration counts (GSC/GA4/GBP/Site Tags)
- AI usage: today/week/month + costUsd
- Event coverage: 13 categories (Core, Forms, Phone, Booking, Video, E-commerce, UX Signals, Performance, Social, Content, Competitor, Chat, Scheduling)
- Data sources:
TrackedEventcount by eventType,SiteEvent,RealUserMetric - In-memory 60s TTL cache
Dashboard (src/app/dashboard/page.tsx):
- Module-scope Map (60s TTL) + localStorage (12h TTL) for cache
- 3-phase loading: Phase -1 = localStorage fallback (instant), Phase 0 = server snapshot (
/api/dashboard/summary), Phase 1 = live fetch with 20s AbortController timeout - 9 customizable sections: KPI Cards, Paid Media, Technical Issues, Execution Hub, Recommendations, AI Visibility, Competitors, Search Performance (lazy-loaded), Quick Actions
- Channel toggle: GA4 split All Traffic vs. Organic Search
- API:
/api/dashboard,/api/dashboard/summary,/api/dashboard/search-performance,/api/conversion-intelligence/attribution
public/t.js (client tracking tag, 162.8 KB minified):
- 74 tracked event types across 13 categories (full list in Section 8)
- Session stitching:
meseo_sid+meseo_stscookies;meseo_xsid/meseo_xstsfor cross-subdomain - Client session ID: random string, persisted in cookies + memory
- SPA support:
page_viewon route changes (history API + URL polling) - Event batching: buffered, flushed on timeout or batch-full
- Collection endpoint:
POST /api/collect
Collect route (src/app/api/collect/route.ts):
- Batch path:
{ site_id, visitor_id?, events: [...] }(up to 200 events) - Single-event path (legacy): full validation + rate check
- Domain allowlist enforcement (TrackedSite.allowedDomains)
createManyfor batch inserts; singlecreatefor legacy path- Side effects (when
SITE_TAG_SIDE_EFFECTS_ENABLED=true): platform detection, integration detection, external conversion initiation - Request size limit: 64 KB
Git log overview
106 commits on main from 2026-05-25 to 2026-06-10. Commit-message phases (19, 19A.x, 19.5x, 20A) do not map 1:1 to roadmap numbering (13-27). Reconciliation is below.
Phase groups (git history)
Phase 16 (docs/validation) -- 2026-05-26
cadef8edocs(phase-16): add validation and auth architecture referenced9417b6docs(phase-16): replace em dash substitutes- Files:
docs/architecture/phase-16-validation.md,docs/architecture/CONVENTIONS.md - Shipped: Documentation only. No code changes.
Phase 17 (Voice Search Eligibility) -- 2026-05-26
705d2b3feat(phase-17): voice search eligibility audit backend14c639ffeat(phase-17): voice search eligibility dashboard component4711328feat(phase-17): add Voice Search Eligibility section to PDF audit report- Shipped: Full voice search eligibility pipeline -- schema, synthesizer output, dashboard component, PDF section.
Phase 19 / 19A.x (Technical Audit + Site Tag Performance) -- 2026-05-27 to 2026-05-28
25+ commits. Sub-milestones:
| Commit | Date | What shipped |
|---|---|---|
545a6c7 |
05-27 | Phase 19A: technical audit page scaffold (mock data) |
88b8dde |
05-27 | Wire technical audit data layer lib |
e976da2 |
05-27 | Wire audit trigger buttons |
91c41ce |
05-27 | Wire technical audit page to real seoSection data |
e2b1a64 |
05-27 | Hide site tag banner when installed |
715e470 |
05-27 | Wire technical audit to background refresh + notifications |
3d1c64e |
05-27 | Add SiteTagDailyRollup cron pre-aggregation |
27553b7 |
05-27 | Remove analytics block from site-tag polling route |
f909229 |
05-27 | t.js error dedupe + rate limit + counter-based capture |
7593eb8 |
05-27 | Redis caching layer for analytics (5-min TTL) |
959a662 |
05-28 | Phase 19A.20: loading states, tier label clarification |
5488ffd |
05-28 | Phase 19.5: multilingual, compliance, ecommerce, B2B synthesizers |
08420cc |
05-28 | Phase 19A.21: graceful stats degradation + audit page cache |
3b7228f |
05-28 | Fix 19A.22: wire SiteTagDailyRollup fast path into getTrafficSummary |
0572928 |
05-28 | Fix 19A.23: register form_filled, defensive tier guard |
51135cf |
05-28 | Phase 19A.25: expand SiteTagDailyRollup with conversion/event columns |
1db278e |
05-28 | Fix 19A.26: use real siteTagInstalled on audit page |
003e7d6 |
05-28 | Fix 19A.26.1: raise first-party audit maxDuration to 800s |
8b1b5a8 |
05-28 | Fix 19A.26.2: fix third site-tag banner false-positive |
d5e5396 |
05-28 | Fix 19A.27: base maturity banner on rollup active-days |
321fdb2 |
05-28 | Phase 19A.28: universal siteTagInstalled in brand context |
4dfb003 |
05-28 | Fix 19A.28 followup: suppress banner during brand switch |
dd72106 |
05-28 | Fix 19A.28 switch: close banner flash during brand switch |
8c761f9 |
05-28 | Run crawl-independent audit sections in degraded mode |
1f1fe87 |
05-28 | Raise DFS polling window 240s -> 300s |
4f96111 |
05-28 | Fix 19A.29: rollup-ize confidence route + Redis cache + hard 30s timeout |
Phase 20A.x (White-Label) -- 2026-05-28
| Commit | What shipped |
|---|---|
6bf61ee |
Add WhiteLabelConfig + CustomDomain + DomainStatus schema |
151f654 |
White-label settings tab + API route |
c961a65 |
Active-org isolation in white-label API + getEventTrends rollup fast path |
Market Position Block 1 and 2 -- 2026-05-28
| Commit | What shipped |
|---|---|
cdb15a9 |
feat(market-position-block1): 6 new Prisma models (ProbeRun, VisibilityObservation, CompetitorPanel, QueryCorpusEntry, CalibrationRun, MarketPositionSnapshot) + 2 enums |
c735d74 |
feat(market-position-block2): canonicalization + corpus seeding |
c018c80 |
fix(market-position-block2): conversion types, N-scan perf, stop-words |
Phase 30B (Platform Detection + Plugin Version) -- 2026-05-25 to 2026-05-29
| Commit | What shipped |
|---|---|
1bad836 |
Detect + store site platform from page_view events |
b2abf55 |
Platform confidence as strings, tighten signals, fix regexes |
55fad07 |
Plugin version extraction via version() on ANALYTICS_PLUGINS |
8f5df21 |
Gate bounded-async side effects behind SITE_TAG_SIDE_EFFECTS_ENABLED |
Phase 32 + 33 (Dedup + Conversion Tier Fixes) -- 2026-05-25
| Commit | What shipped |
|---|---|
f22100e |
Fix visitorId mismatch Path A vs Path B |
8abc2a0 |
Propagate synthetic flag from TrackedEvent to SiteConversion |
b4a44b6 |
Promote visitor_id to top-level collect body field |
2946d61 |
Fix conversion tier dropdown revert from polling |
6282295 |
Wire canonicalConversionType into BrandConversionConfig override |
Session: Ecommerce + Shopify -- 2026-05-29 to 2026-06-02
| Commit | What shipped |
|---|---|
1c25751 |
orderId column, ingest extraction, API route, UI tab |
6c4e8e0 |
Shopify Web Pixel purchase ingest + lineItems column |
9eece0a |
Extend event buffer flush 10s -> 30s, flush-on-full safety valve |
4601e07 |
Consolidate plugin_detected to single plugins[] event per page |
dad4b60 |
Exclude ingest beacon paths from middleware matcher |
7b6e608 |
1%-sampled drop-reason logging on /event |
f677a5f |
Per-session dedup for plugin_detected, phone_number_visible, call_tracking_detected |
Session: Site Audit Phase 1 -- 2026-06-04
| Commit | What shipped |
|---|---|
791d921 |
SiteAuditRun, SiteAuditIssue, SiteAuditPage schema + renderTier on Brand |
62610b6 |
Crawler service, API routes, cron cleanup |
d207a05 |
Default crawl_delay 200ms, expose zero as opt-in |
b0881e5 |
Replace BrandMembership filter with verifyBrandAccess() on all user-facing routes |
b62fdae |
Use waitUntil to keep Lambda alive after response |
3ce9c07 |
Enforce 60s minimum before accepting DFS "finished" |
f4b4582 |
Resolve canonical host before DFS submit; guard 0-page runs |
9ad1005 |
Cheap pre-reject in /event, sweep stuck runs |
Session: Connection-Pool + Cron Fixes -- 2026-06-05
| Commit | What shipped |
|---|---|
accf550 |
Remove recomputeSessionScore from ingest hot path (stops 25P03 leaks) |
5990d96 |
Attach submissionId token to form_submit |
2d1c1b9 |
Unified phone_call dedup across both write paths |
c376913 |
Restore precompute-dashboard to every 30 min, add budget guard |
6a69290 |
Schedule site-audit-cleanup cron, per-tick cap + debounce |
636ac66 |
Stand up Inngest infrastructure (client, serve route, ping function) |
Session: Site Tag 504 Fixes -- 2026-06-08 (this session)
| Commit | What shipped |
|---|---|
543f271 |
Route 28d analytics onto SiteTagDailyRollup; bound all multi-day raw scans |
d6dfed4 |
Bound activePagesCount to today to stay under withDeadline |
6c8cdae |
Add ?debugTiming=1 instrumentation to GET /api/site-tag |
07e37ef |
Bound all 9 getReconciliationSummary queries to 48h window |
Phase numbering reconciliation
- Phases actually committed: 16, 17, 19/19A, 19.5, 20A, 30B, 32, 33
- Phases reconstructed from code (pre-git): 1-15 (see above)
- Phases not in git or code: 18, 21-29 (not built or labelled differently)
- Market Position is labelled "block1/block2"
- Site Audit is labelled "Phase 1" internally with no roadmap number
2. Sidebar Pages
| Page | Route | File | Wiring |
|---|---|---|---|
| Dashboard | /dashboard |
src/app/dashboard/page.tsx |
Full |
| Audit | /audit |
src/app/audit/page.tsx |
Full |
| Technical Audit | /technical-audit |
src/app/technical-audit/page.tsx |
Full |
| AI Strategist | /ai-strategist |
src/app/ai-strategist/page.tsx |
Full |
| Site Tag | /site-tag |
src/app/site-tag/page.tsx |
Full |
| Site Tag Analytics | /site-tag-analytics |
src/app/site-tag-analytics/page.tsx |
Full |
| Conversion Intelligence | /conversion-intelligence |
src/app/conversion-intelligence/page.tsx |
Full |
| Rank Tracker | /rank-tracker |
src/app/rank-tracker/page.tsx |
Full |
| Keyword Research | /keyword-research |
src/app/keyword-research/page.tsx |
Full |
| Backlinks | /backlinks |
src/app/backlinks/page.tsx |
Full |
| Site Performance Audit | /site-performance-audit |
src/app/site-performance-audit/page.tsx |
Full |
| Competitors | /competitors |
src/app/competitors/page.tsx |
Full |
| Industry Intel | /industry-intel |
src/app/industry-intel/page.tsx |
Full |
| AI Visibility | /ai-visibility |
src/app/ai-visibility/page.tsx |
Full |
| Signals | /signals |
src/app/signals/page.tsx |
Full |
| Execution Hub | /execution-hub |
src/app/execution-hub/page.tsx |
Full |
| Page Launch Tracker | /page-launch-tracker |
src/app/page-launch-tracker/page.tsx |
Full |
| Content Brief | /content-brief |
src/app/content-brief/page.tsx |
Full |
| LLMO Content Brief | /llmo-content-brief |
src/app/llmo-content-brief/page.tsx |
Full |
| Schema Generator | /schema-generator |
src/app/schema-generator/page.tsx |
Partial (UI-driven, not auto-populated) |
| Dead Pages | /dead-pages |
src/app/dead-pages/page.tsx |
Full |
| Broken Links | /broken-links |
src/app/broken-links/page.tsx |
Full |
| Report Studio | /report-studio |
src/app/report-studio/page.tsx |
Full |
| Content Audit | /content-audit |
src/app/content-audit/page.tsx |
Full |
| Recommendations | /recommendations |
src/app/recommendations/page.tsx |
Full |
| Writing Assistant | /writing-assistant |
src/app/writing-assistant/page.tsx |
Full |
| Email Reports | /email-reports |
src/app/email-reports/page.tsx |
Full |
| Notifications | /notifications |
src/app/notifications/page.tsx |
Full |
| Integrations | /integrations |
src/app/integrations/page.tsx |
Full |
| Settings | /settings |
src/app/settings/page.tsx |
Full |
| Brands Hub | /brands-hub |
src/app/brands-hub/page.tsx |
Full |
| Client Portal Setup | /client-portal-setup |
src/app/client-portal-setup/page.tsx |
Full |
| Local GEO | /local-geo |
src/app/local-geo/page.tsx |
Stubbed -- "Coming Soon" |
| Paid Media | /paid-media |
src/app/paid-media/page.tsx |
Full |
| Onboarding | /onboarding |
src/app/onboarding/page.tsx |
Full |
| Pricing | /pricing |
src/app/pricing/page.tsx |
Full |
| Admin | /admin |
src/app/admin/page.tsx |
Full (admin-only) |
Key page details
Dashboard (/dashboard)
- Data sources:
MetricSnapshot,SiteTagDailyRollup(fast path),ConversionEvent,CompetitorSnapshot,ChannelMetric - API calls:
/api/dashboard/summary,/api/dashboard/search-performance,/api/conversion-intelligence/attribution - Cache strategy: 60s TTL module-scope Map + 12h TTL localStorage
- Range controls:
1d | 7d | 28d | 90d | month | custom
Site Tag (/site-tag)
- Data sources:
TrackedSite,TrackedEvent(recent 15),SiteTagDailyRollup(fast path),SourceEvent,CanonicalEvent - API:
GET /api/site-tag?brandId=xxx(polls every 30s) - Tabs: Setup/Snippet, Domain Status, Custom Events, Plugins Detected, Reconciliation
- Wiring notes: 07e37ef bounded reconciliation to 48h; 543f271 uses rollup fast path for today counts; d6dfed4 bounded activePagesCount to today; 6c8cdae added
?debugTiming=1block-level timing
Site Tag Analytics (/site-tag-analytics)
- Data sources:
SiteTagDailyRollup(primary),TrackedEvent(live fallback),HeatmapEvent,RealUserMetric,SiteConversion - API:
GET /api/site-tag/analytics?brandId=xxx&range=30d - Tabs: Traffic, Conversions, UX Signals, Heatmap, Ecommerce (conditional), AI Attribution, Video
- Deadline: 20s
HANDLER_DEADLINE_MS; returnspartial: trueon timeout
AI Visibility (/ai-visibility)
- Data sources:
AiVisibilitySnapshot,AiVisibilityCheck,AiVisibilityQuery - API:
GET /api/ai-visibility?summary=true&range=30d,/api/ai-visibility/queries,/check - Feature gate:
ai_visibilityflag -- Professional+ plan - Cron: Weekly
0 0 * * 1via/api/cron/ai-visibility(maxDuration 300s)
Execution Hub (/execution-hub)
- Data sources:
Task(source, sourceId link to audit/signal/dead-page) - API: Task CRUD via
/api/tasks - Categories: seo|llmo|gbp|schema|content|technical|web-dev
- Views: Calendar, Pipeline board
Page Launch Tracker (/page-launch-tracker)
- Data sources:
PageRecord,PageLaunch,PageLaunchSnapshot, GSC click/impression join - API:
/api/page-launches - Features: lifecycle from first detection (sitemap/GSC/tag), inbound redirect tracking, daily delta chart
Local GEO (/local-geo)
- Status: COMING SOON -- intentionally stubbed
GbpLocation,GbpReview,GbpMetricmodels and dailygbp-synccron are active- Page deliberately disabled to avoid empty-state before end-to-end wiring
Gaps and unknowns: /topic-map wiring status UNCONFIRMED (POC visualization). issue-detail wiring UNCONFIRMED.
3. Core Platform Architecture
Multi-tenant model
Organization (1) -> Brand (N) -> TrackedSite (1)
Organization (1) -> OrgMembership (N) -> User
Brand (1) -> BrandMembership (N) -> User
Auth: Clerk JWT. verifyBrandAccess() (commit b0881e5) replaces raw BrandMembership filter on all user-facing routes.
Integration layer
Nango (src/lib/integrations/nango.ts):
- Supports: GSC, GA4, GBP, Bing
getValidAccessToken(brandId, integration)-- auto-refreshgetCredentials(brandId, integration)-- stored inBrandIntegration.credentialsEnc(AES-256-GCM)BrandIntegration.tokenExpiresAttracked; marks expired on 2+ refresh failures- Nango token refresh cron: hourly
/api/cron/nango-sync
Connection pool:
DATABASE_URLquery params:connection_limit=20, pool_timeout=30- Neon pooler endpoint for multiplexing
recomputeSessionScoreremoved from ingest hot path (accf550) -- was causing 25P03 idle-in-transaction leaks
Redis cache
File: src/lib/cache/redis-cache.ts
- Thin wrapper over
redisnpm client; resolvesREDIS_URLenv var - Fails open (silently disabled) if
REDIS_URLnot set - Used by: analytics endpoints (5-min TTL), confidence route (30s timeout), pulse heartbeat
- Commit:
7593eb8(analytics),4f96111(confidence),2d38e9e(pulse)
Inngest
Status: Infrastructure only. No production functions beyond ping.
src/lib/inngest/functions/ping.ts: eventtest/ping- Serve route registered, client configured
- Commit:
636ac66
prisma-safety-check
File: scripts/prisma-safety-check.mjs
- Blocks
prisma db pushif diff contains:DROP COLUMN,DROP TABLE,DROP CONSTRAINT,DROP INDEX,ALTER COLUMN ... DROP - Protects the ~22GB
SiteEventtable indexes (commitecd5a48) - Override:
ALLOW_SCHEMA_DROPS=true - Self-test:
scripts/test-safety-check.mjs
4. Audit Flows
First-party technical audit (authenticated brand)
Entry: Brand member clicks "Run Audit" on /technical-audit.
Route chain:
POST /api/technical-audit/run(maxDuration 800s)- Auth:
verifyBrandAccess()(commitb0881e5) - Creates
TechnicalAuditrow (status: pending) - Calls
runTechnicalAudit()insidewaitUntil()so Lambda stays alive after 200 (commitb62fdae)
- Auth:
GET /api/technical-audit/[auditId]/status-- polled every 5s- Returns
{ status, pagesScanned, errorCount, warningCount, passedCount }
- Returns
GET /api/technical-audit/history-- historical runs
DataForSEO integration:
- Task submission:
submitCrawl(domain)insidesite-audit-runner.ts-- POST to DFS/v3/on_page/task_post - Payload:
url,max_crawl_pages,crawl_delay(default 200ms, commitd207a05),store_raw_html: false - Minimum 60s before accepting DFS "finished" (commit
3ce9c07) - Polling window: 300s (raised from 240s in
1f1fe87) - Results:
fetchAllCrawledPages(),fetchRedirectChains(),fetchDuplicateTitles(),fetchDuplicateDescriptions() - On DFS unconfigured: limited-mode banner (commit
4028dd2)
Synthesizer pipeline (inside runSiteAudit()):
seo-synthesizer.ts: Sonnet 4.6 (14K tokens, 280s) + Haiku 4.5 (1.5K tokens, 45s) inPromise.allreal-user-technical.ts,ecommerce.ts,multilingual.ts,compliance.ts,b2b.tsindustry-classifier.ts: returnsstring[]for multi-industry support
Crawl-independent sections (run in degraded mode when DFS crawl fails, commit 8c761f9):
SERP competitor analysis, GSC performance, Site Tag signals, AI visibility.
Cron cleanup: /api/cron/site-audit-cleanup -- re-kicks stuck >15 min, fails >2 hours (commit 6a69290).
Third-party / public audit
Entry: Anonymous visitor at public audit URL.
Route chain:
POST /api/public-audit/...-- no auth, validates domain, issues OTP- Lead capture: firstName, lastName, email, companyName, phoneNumber, websiteUrl
- Rate limit: 3 req/IP/hour
- Returning leads (by email) bypass OTP
- Same synthesizer pipeline as first-party (degraded crawl-independent sections)
- maxDuration 800s
LockedFirstPartyTeaser.tsxCTA for anonymous users
Audit runner internals (pre-git, audit-runner.ts)
7-phase orchestration (src/lib/audit/audit-runner.ts, 2038 lines):
- Brand context assembly (BrandProfile, integrations, plan)
- Data fetch (GSC/GA4/Site Tag/competitors in parallel)
- External APIs: DataForSEO SERP + backlinks + on-page, Firecrawl 5-10 key pages (markdown + metadata)
- 26 dimension aggregators (run in parallel batches)
- LLM synthesis (Sonnet 4.6 narrative + Haiku 4.5 structured)
- Output assembly (SeoSection object, 40+ fields)
- DB write (AnalysisJob upsert, score history)
Cost controls:
COST_PER_CRAWL_PAGE = 0.0001,COST_PER_SONNET_CALL = 0.05- Hard abort at $4.00 (first-party), $2.00 (third-party)
- Prompt caching:
cache_control: { type: "ephemeral" }on synthesis input
Degraded mode: pages.length === 0 triggers Site Tag telemetry fallback.
5. AI Strategist
Page: src/app/ai-strategist/page.tsx (1342 lines)
API: POST /api/ai-strategist/chat (maxDuration 120s)
Context sources
Assembled by getEnrichmentContext() and buildEnrichmentPrompt():
- Brand profile: name, industry, locations[], primaryServices[]
- DashboardData: SEO score, AI visibility, crawl health, monthly traffic
- GSC metrics: searches, impressions, CTR, avg position
- Site Tag metrics: sessions, conversions, top pages (SiteTagDailyRollup or live)
- AnalysisJob.result: full JSON grounding for recommendations
- Paid media: ad spend, ROAS (if AdPlatformIntegration configured)
- LLMO context: AI visibility snapshot if feature enabled
Models + logging
- claude-sonnet-4-6, 12K max output tokens (
ARTIFACT_MAX_TOKENS) - Every call logged to
AiInteraction(userId, brandId, feature, userPrompt 4KB cap, aiResponse 16KB cap, guardrailFlags[], flagSeverity) trackEvent()->PlatformEvent- Admin surface:
/admin/ai-monitor
Artifacts
<artifact>tags in response extracted byparseArtifacts()fromsrc/lib/ai-strategist/artifacts.ts- Rendered in iframe sandbox via
ArtifactRenderer-- supports JSX dashboards, markdown, structured reports - Session persistence:
StrategistSession,StrategistMessage
Gaps and unknowns: Token budget enforcement beyond max_tokens UNCONFIRMED. PII guardrail detection logic in guardrailFlags was not read from code.
6. JSX Dashboards
Audit dashboard components (46 files)
Location: src/components/audit/dashboard/
Each receives a slice of the Analysis object from AnalysisJob.result.
| Component | Renders |
|---|---|
AIConversionGapChart.tsx |
AI-driven conversion gap analysis |
AiEnginePersonalitySection.tsx |
LLM engine personality for brand |
AiSearchVerificationSection.tsx |
AI search mention rates |
AiSnippetEligibilitySection.tsx |
Featured snippet capture readiness |
AiSourceAttributionComparisonSection.tsx |
First-party vs third-party attribution |
AuditHero.tsx |
Report hero with brand + score |
AuditTOC.tsx |
Table of contents with section links |
BacklinkProfileSection.tsx |
Backlink quality, toxicity, domain authority |
BehavioralActionBridgeSection.tsx |
Site Tag behavioral signals -> actions |
BilingualContentSection.tsx |
Multi-language content gaps |
BrandAuthorityScoreSection.tsx |
Domain authority + trust signals |
CompetitiveLandscapeSection.tsx |
Top competitors, overlap, threat level |
CompetitorDeepDiveSection.tsx |
Per-competitor detailed analysis |
CompetitorVisualComparisonSection.tsx |
Side-by-side competitor metrics |
ConversionHeatMapSection.tsx |
Conversion-weighted heatmap overlays |
EeatAuditSection.tsx |
E-E-A-T signals |
ExecutiveBriefSection.tsx |
Executive summary prose |
ExecutiveSummaryCard.tsx |
Summary card with score breakdown |
ImplementationAppendixSection.tsx |
Implementation roadmap table |
IndustryBenchmarkSection.tsx |
Brand vs industry median |
KeyFindingsSection.tsx |
Top 5-7 prioritized findings |
LockedFirstPartyTeaser.tsx |
Upgrade CTA for anonymous audits |
LostOpportunityCalculatorSection.tsx |
Revenue impact of ranking gaps |
LiveVisualAnnotationsSection.tsx |
Real-time behavioral annotations |
MethodologySection.tsx |
Audit methodology explanation |
MultiTouchAiPathSection.tsx |
Multi-touch attribution AI path |
OrganicSearchSection.tsx |
Organic traffic, clicks, impressions |
PageSpeedInsightsDeepDiveSection.tsx |
Core Web Vitals deep dive |
PageStrategicPassSection.tsx |
Per-page strategic recommendations |
PredictiveConversionScoringSection.tsx |
ML-based conversion probability |
PriorityActionsSection.tsx |
Ranked action items |
QuickWinsGrid.tsx |
Fast-ROI action grid |
RevenueAttributionSection.tsx |
Revenue per channel |
SchemaOpportunityForecastSection.tsx |
Schema markup gap analysis |
ScoreBreakdown.tsx |
Score sub-component breakdown |
ScoreProjection.tsx |
Score trend projection |
SignalCenterSection.tsx |
Signal aggregation |
SnippetCaptureStrategySection.tsx |
Snippet capture tactics |
StrategicRoadmapSection.tsx |
90-day implementation roadmap |
TechnicalHealthSection.tsx |
Technical crawl health summary |
ThirdVsFirstPartyComparison.tsx |
GA4/GSC vs Site Tag comparison |
VoiceSearchEligibilitySection.tsx |
Voice search schema + NLP readiness |
index.ts |
Barrel export |
Other component directories
src/components/ai-strategist/artifact-renderer.tsx-- iframe sandbox renderersrc/components/execution-hub/calendar-view/-- task calendarsrc/components/execution-hub/pipeline-board/-- kanban boardsrc/lib/audit/pdf/--@react-pdf/renderer:AuditPdf.tsx,ExecutiveAuditPdf.tsx,pages/,styles.ts
7. Guardrails
Deadline wrappers
withDeadline (src/app/api/site-tag/analytics/route.ts lines 46-51):
async function withDeadline<T>(promise: Promise<T>, ms: number): Promise<Raced<T>> {
return Promise.race([
promise.then((data) => ({ winner: "ok", data })),
new Promise<Raced<T>>((resolve) => setTimeout(() => resolve({ winner: "deadline" }), ms)),
]);
}
HANDLER_DEADLINE_MS = 20_000(20s)- On deadline: returns
{ partial: true, analytics: null }witheventsToday: 0from spread defaults
maxDuration settings:
| Route | maxDuration |
|---|---|
/api/site-audit/run |
800s |
/api/technical-audit/run |
800s |
/api/cron/site-tag-rollup |
300s |
/api/cron/ai-visibility |
300s |
/api/cron/rank-check |
300s |
/api/cron/ai-citation-check |
300s |
/api/ai-strategist/chat |
120s |
/api/keyword-research/analyze |
120s |
/api/keyword-research/search |
60s |
/api/performance-audit/run |
60s |
waitUntil usage: /api/site-audit/run keeps Lambda alive after 200 (commit b62fdae).
Rate limits
Site Tag ingest (src/lib/services/site-tag-cache.ts):
- 100 events/min per siteId
- In-memory token bucket, resets every 60s
- Returns 200 on drop (not 429) to prevent client console flood
t.js error rate limit (commit f909229):
- Counter-based per-session dedup for JS errors
Public audit (src/app/api/public-audit/route.ts):
- 3 req/IP/hour
Rollup fast paths
SiteTagDailyRollup fast path (src/lib/services/site-tag-analytics.ts):
_getTrafficSummaryUncached,_getConversionSummaryUncached,getOutboundClickSummary- Guard:
range === "30d" || range === "28d" || range === "7d"(extended to 28d in commit543f271) - Threshold:
rollupRows.length >= expectedDays - 1 - Turns 1M+ row
TrackedEventgroup-by into a 30-row indexed lookup on(brandId, date)
Rollup cron (/api/cron/site-tag-rollup, hourly 0 * * * *, maxDuration 300s):
- Force-recomputes today + yesterday on every run
- Backfills last 30 days if row missing or
conversionsByType IS NULL - UTC midnight date key:
new Date(Date.UTC(y, m, d))
Today rollup fast path (setup page) (src/app/api/site-tag/route.ts):
siteTagDailyRollup.findFirstforeventsToday,eventBreakdown,sessionsToday- Falls back to live
TrackedEventonly if row absent oreventCountsByTypeis null
activePagesCount bound (commit d6dfed4):
COUNT(DISTINCT CASE ...)bounded totimestamp >= todayStart(was 7 days)- CASE expression cannot use an index regardless of window; the bound limits rows scanned
Dedup and dedup resolvers
submissionId token (commit 5990d96):
sessionStorage-keyed token attached toform_submit- 30-second fixed-from-first window prevents re-fire duplicates
Rapid-fire dedup query (/api/collect/route.ts):
WHERE brandId = ? AND sessionId = ? AND pageUrl = ? AND conversionType = ? AND timestamp >= NOW() - 30s- Index:
@@index([brandId, sessionId, pageUrl, conversionType, timestamp])
Phone call dedup (commit 2d1c1b9):
- Unified across Path A (
/api/collect) and Path B (/api/site-tag/conversion)
Multi-source dedup (CanonicalEvent system) (src/lib/services/site-tag-reconciliation.ts):
ingestSourceEvent(): matches existing CanonicalEvent within +-5 min by eventType + pageUrlreconcileTrackedEvents(): fire-and-forget, max 500 TrackedEvents per call, NO date filter (R1)getReconciliationSummary(): all 9 queries bounded to 48h (commit07e37ef)
Deterministic dedup clustering (src/lib/conversions/dedup.ts):
- SHA1-based
computeDedupKey()for form/phone events - Survivor selection: method priority -> touchpoint richness -> earliest timestamp
isDuplicateOfpointer written; never hard-delete
Cron dedup (/api/cron/dedup-conversions, daily):
- Processes unresolved conversions (
isDuplicateOf IS NULL) - Backfills
classifiedChannelafter dedup
Connection pool protection
DATABASE_URL:connection_limit=20, pool_timeout=30SITE_TAG_SIDE_EFFECTS_ENABLEDflag (src/app/api/collect/route.tsline 43): gates all bounded-async side effects in ingest hot path (commit8f5df21)
Middleware exclusions
Ingest beacon paths excluded from Next.js middleware matcher (commit dad4b60) to eliminate per-request middleware overhead.
8. Customer-Facing Surfaces
Actions a customer can run
| Surface | Action | Route |
|---|---|---|
| Audit page | Trigger full site audit | POST /api/site-audit/run |
| Technical audit | Run Lighthouse + schema crawl | POST /api/technical-audit/run |
| AI Strategist | Send message | POST /api/ai-strategist/chat |
| AI Strategist | Load/save session | StrategistSession CRUD |
| Site Tag | Install snippet | GET /api/site-tag/script/[name] |
| Site Tag | Add/remove domain | PATCH /api/site-tag action=add_domain / remove_domain |
| Site Tag | Toggle active | PATCH /api/site-tag action=toggle_status |
| Site Tag | Regenerate key | PATCH /api/site-tag action=regenerate_key |
| Conversion config | Set per-type tier | POST /api/brands/[id]/conversion-config |
| Keyword Research | Search keywords | POST /api/keyword-research/search |
| Keyword Research | Analyze gap | POST /api/keyword-research/analyze |
| Keyword Research | Send to tracker | POST /api/keyword-research/send-to-tracker |
| Content Brief | Generate brief | POST /api/content-briefs/generate |
| Schema Generator | Generate JSON-LD | POST /api/schema/generate-for-brief |
| Execution Hub | Create/update/complete task | POST/PATCH /api/tasks |
| Integrations | Connect/disconnect | /api/integrations/connect, /disconnect |
| Email Reports | Create/edit schedule | POST /api/email-reports |
| Alert Rules | Create/edit alert | Alert CRUD endpoints |
| White-label | Configure portal | PUT /api/white-label |
| Client Portal | Invite client user | POST /api/client-portal/users/invite |
| AI Visibility | Add/manage queries | /api/ai-visibility/queries CRUD |
| AI Visibility | Trigger check | POST /api/ai-visibility/check |
| Competitors | Discover / add | /api/competitors/discover |
| Page Launch Tracker | Mark as launched | POST /api/page-launches |
| GSC | Backfill history | POST /api/gsc/backfill |
Downloadable reports
| Report | Format(s) | Route |
|---|---|---|
| Full audit report | PDF (comprehensive 40-50pp + executive 6pp) | GET /api/admin/audits/[id]/pdf?mode=comprehensive |
| Report Studio custom | PDF, DOCX, Markdown, JSON | POST /api/report-studio/export |
| Report Studio PPTX | PPTX | POST /api/report-studio/export/pptx |
| Compliance export | JSON | GET /api/admin/compliance/events (admin-only) |
| Platform metrics | CSV/JSON | GET /api/admin/export/platform-metrics |
Site Tag event types (74 total)
| Category | Events |
|---|---|
| Core | page_view, session_start |
| Forms | form_start, form_submit |
| Phone | click_to_call, sms_click, phone_number_visible, call_tracking_detected |
| Booking | booking_submit |
| Video | video_play |
| E-commerce | purchase (Shopify Web Pixel + standard) |
| Engagement | outbound_click, file_download, pdf_opened, content_engagement |
| Competitors | competitor_visit, competitor_referral, competitive_journey |
| UX Signals | rage_click, dead_click, scroll_bounce (via SiteEvent) |
| Performance | web_vitals, slow_interaction, js_error, resource_error |
| Custom | plugin_detected, custom_event |
9. Schema and Crons
Prisma models (129 models, 268 indexes)
Core: Users and Orgs
User-- Clerk-synced, email, timezone, emailPrefs (JSON)Organization-- name, slug, planOrgMembership-- userId + orgId uniqueBrandMembership-- per-brand role (owner|admin|member|viewer)
Brands
Brand-- domain, industry, locations[], primaryServices[], renderTier, isDemo, healthScoreBrandIntegration-- integrationId, credentialsEnc (AES-256-GCM), tokenExpiresAt, statusBrandConversionConfig-- per-type isCounted + tierOverride + displayLabelBrandProfile-- logoUrl, brandColors[], brandVoice, extractedAtBrandAsset-- filename, url, type, fileSize, dimensions
Analytics + Metrics
MetricSnapshot-- daily per-brand per-source (ga4|gsc|site_tag|daily|manual|seed)ChannelMetric-- daily per-channel (organic|ai_search|social|gbp|direct)ConversionEvent-- type, channel, value, landingPage
Site Tag (Real-Time)
TrackedSite-- siteId (ms_prefix), trackingKey (secret), allowedDomains[], customEvents (JSON), detectedPlatform (JSON: name/version/confidence/category/hosting)TrackedSession-- sessionId (unique), device, browser, country, city, channelGroup, predictedConversionScoreTrackedEvent-- eventType, pageUrl, timestamp, synthetic, metadata (JSON)- Indexes:
(trackedSiteId, eventType, timestamp),(trackedSiteId, pageUrl, timestamp),(trackedSiteId, timestamp),(timestamp)
- Indexes:
SiteConversion-- conversionType, visitorId, sessionId, pageUrl, formProvider, phoneNumber, touchpoints (JSON), conversionValue, orderId, lineItems (JSON), isDuplicateOf, invalidatedReason, classifiedChannel, submissionId- Indexes:
(brandId, timestamp),(brandId, isDuplicateOf),(brandId, sessionId, pageUrl, conversionType, timestamp)
- Indexes:
HeatmapEvent-- visitorId, sessionId, pageUrl, eventType, maxScrollPercent, sectionTimes (JSON), pageWidth/HeightRealUserMetric-- lcp, cls, inp, ttfb, fcp, device, connectionSiteEvent-- eventType (rage_click|dead_click|hesitation|exit_intent|js_error|outbound_click|...), eventData (JSON)- Indexes:
(trackedSiteId, eventType, timestamp),(sessionId, timestamp)(commitecd5a48)
- Indexes:
SiteTagDailyRollup-- brandId, date, pageViews, sessions, conversions, topPages (JSON), byReferrer (JSON), conversionsByType (JSON), conversionsByTier (JSON), eventCountsByType (JSON), outboundClickCount- Unique:
(brandId, date)
- Unique:
PageRecord,PageDailyMetric,NavSnapshot,PageSEOSnapshot
Conversions + Dedup
CanonicalEvent-- eventType, pageUrl, timestamp, confidence (0-100), status (verified|inferred|unmatched)SourceEvent-- source (site_tag|ga4|gtm|gsc|crm), isDuplicate, rawData (JSON)
Audit + Analysis
Audit-- auditToken, sections (JSON), completedAtAuditScoreHistory-- healthScore, errorCount, warningCountSiteAuditRun-- status, taskId (DataForSEO), pagesProcessed, crawlErrorSiteAuditPage-- url, title, h1, wordCount, statusCode, crawlHealthSiteAuditIssue-- type, severity (error|warning|notice), affectedUrls (JSON), evidence (JSON), statusTechnicalAudit,TechnicalIssue,PerformanceAudit,PerformancePageScore
AI Visibility + Market Position
AiVisibilityCheck,AiVisibilityQuery,AiVisibilitySnapshotProbeRun-- vertical, locale, engine, status (enum ProbeRunStatus: PENDING|RUNNING|COMPLETED|FAILED), sampleCountVisibilityObservation-- probeRunId, query, engine, brandKey, mentioned, position, cited, sentiment, sourceUrls[], sampleIndexCompetitorPanel-- vertical, locale, members[]; unique(vertical, locale)QueryCorpusEntry-- vertical, locale, query, source (enum QuerySource: FRAGMENT_SEEDED|CATEGORY_COVERAGE|CURATED), conversionWeightCalibrationRun-- vertical, version, coefficients (JSON), fittedAt, sampleSizeMarketPositionSnapshot-- brandId, vertical, locale, calibratedScore, conversionWeightedSov, rawSov, rankInPanel, panelSize, divergence, stabilityBand, computedAt
Execution + Content
Task-- title, category, status, priority, source, sourceId, dueDate, pipelineStage, briefId, assignedToContentBrief-- type (seo|llmo), status, primaryKeyword, sections (JSON), humanizationMode, contentScoreSchemaMarkup-- type, markup (JSON-LD), appliedPages (JSON)
Page Lifecycle
PageLaunch,PageLaunchSnapshot-- baseline vs current GSC metricsDeadPage,DeadPageSource-- 404 detection + redirect suggestions
Competitors
Competitor,CompetitorSnapshot,CompetitorRanking,CompetitorPage,CompetitorDiscoveryJob
Backlinks
Backlink-- sourceDomain, anchorText, linkType, toxicityScore, qualityTier, status (active|lost|disavowed)BacklinkSnapshot-- totals, avgDA, toxicCount
GSC + GBP
GscQuery,GscPage-- daily click/impression/positionGbpLocation,GbpReview,GbpMetric
Rank Tracking
TrackedKeyword-- keyword, location, device, isActiveKeywordRanking-- position, previousPosition, serpFeatures[], source
CRM + Revenue
CrmDeal,RevenueAttribution,UserJourney
Email + Notifications
EmailReportSchedule,SentEmailReport,AlertRule,Notification
White-Label + Portal
WhiteLabelConfig-- orgId (unique), customDomain, logoUrl, primaryColor, removeMeseoBrandingCustomDomain-- hostname (unique), status (enum DomainStatus: PENDING|VERIFIED|FAILED), verificationTokenClientPortal-- brandId (unique), allowedPages[], hideSourceBrandingClientUser-- email, role, status, accessToken
AI Strategist
AnalysisJob-- type, status, prompt, result (JSON), shareToken, clientVersion (JSON), hiddenSections (JSON)StrategistSession,StrategistMessage
Logging + Telemetry
AiUsageLog-- provider, model, feature, inputTokens, outputTokens, estimatedCostAiInteraction-- userId, brandId, feature, userPrompt (4KB), aiResponse (16KB), guardrailFlags[], flagSeverityPlatformEvent,PlatformLog,ComplianceEvent
Predictions + Other
Prediction,DashboardSnapshot,IndustryBenchmark,AdCampaignData,AdClickEvent,AdPlatformIntegration
Cron jobs (34 routes under /api/cron/)
| Route | Schedule | maxDuration | Key models | Notes |
|---|---|---|---|---|
site-tag-rollup |
Hourly 0 * * * * |
300s | TrackedEvent, SiteTagDailyRollup | Pre-aggregates last 30 days. Force-recomputes today+yesterday. Commit 3d1c64e. |
site-audit-cleanup |
Every 5 min | -- | SiteAuditRun | Re-kicks stuck >15 min; fails >2 hours. Commit 6a69290. |
rank-check |
Daily | 300s | TrackedKeyword, KeywordRanking | DataForSEO live SERP. Soft cap $5/day, hard cap 100 keywords/brand. |
ai-visibility |
Weekly 0 0 * * 1 |
300s | AiVisibilityQuery, AiVisibilitySnapshot | 7-source mention checks. |
signal-detection |
Hourly | -- | MetricSnapshot, ConversionEvent | Anomaly detection -> AlertRule -> Notification. |
dedup-conversions |
Daily | -- | SiteConversion | Deterministic cluster dedup + classifiedChannel backfill. |
ai-citation-check |
Weekly | 300s | AiCitation | Citation verification. |
backlink-sync |
Daily | -- | Backlink, BacklinkSnapshot | DataForSEO new/lost + toxicity scoring. |
gbp-sync |
Daily | -- | GbpLocation, GbpReview, GbpMetric | Google Business Profile sync. |
email-reports |
Per schedule (nextSendAt) | -- | EmailReportSchedule, SentEmailReport | Assemble + send scheduled reports. |
industry-intel |
Daily | -- | IndustryBriefing | External event correlation. |
competitor-crawl |
Weekly | -- | Competitor, CompetitorSnapshot | Sitemap monitoring, threat scoring. |
paid-media-rebuild |
Daily | -- | AdCampaignData | Ad spend + conversion reconciliation. |
track-outcomes |
Daily | -- | Task, RecommendationOutcome | ROI scoring for completed recommendations. |
ai-strategy-recommendations |
Weekly | -- | AnalysisJob | Re-generate AI recs for all brands. |
data-retention |
Daily | -- | TrackedEvent, SiteConversion, SiteEvent | Delete rows older than brand.dataRetentionDays (default 395 days). |
nango-sync |
Hourly | -- | BrandIntegration (Nango) | OAuth token refresh. |
refresh-tokens |
Daily | -- | BrandIntegration (Google) | Google OAuth refresh. |
check-alerts |
Every 5 min | -- | AlertRule, Notification | Evaluate rules, fire notifications. |
compute-benchmarks |
Weekly | -- | IndustryBenchmark | Platform-wide industry median aggregation. |
brand-health |
Daily | -- | Brand (healthScore) | Synthetic health score + email on decline. |
platform-snapshot |
1st of month | -- | PlatformSnapshot | Monthly MAU/DAU/churn/feature-adoption archive. |
cleanup-stale-audits |
Daily | -- | Audit, TechnicalAudit | Delete stale >90 days. |
precompute-dashboard |
Every 30 min | -- | MetricSnapshot | Pre-aggregate KPIs with budget guard. Commit c376913. |
integration-sync |
Hourly | -- | BrandIntegration, GSC/GA4/GBP | Sync search data from connected integrations. |
signals-competitor-regional |
Hourly | -- | Competitor, CompetitorSnapshot | Regional threat detection. |
signals-site-tag-ai |
Hourly | -- | SiteConversion, TrackedSession | AI signal detection from behavioral data. |
Gaps and unknowns: Schedule values are from comments in each route file; vercel.json was not read. competitor-discovery-queue and brand-sitemap-crawl exist but their schedules and enabled status were not confirmed.
10. Risk Register
Items not yet fully converted to rollup/bounded paths.
Active risks
R1 -- reconcileTrackedEvents is unbounded (fire-and-forget, pool risk)
- File:
src/lib/services/site-tag-reconciliation.tslines 152-188 - Fetches up to 500
TrackedEventrows with NO date filter. For a high-volume brand, the oldest 500 rows are weeks/months old; each unmatched event fires 1findFirst+ 1-2create= up to ~1500 serial round-trips. - Status: Fire-and-forget (
void) so it does not block HTTP response, but holds DB connections. - Fix: Add
timestamp: { gte: window48h }to thefindMany.
R2 -- SourceEvent lacks a composite (trackedSiteId, timestamp) index
- File:
prisma/schema.prisma SourceEventonly has@@index([trackedSiteId, source])and@@index([trackedSiteId, isDuplicate]). The 48h bound (07e37ef) filters ontimestampbut without a(trackedSiteId, timestamp)index, Postgres must post-filter.- Risk level: Medium (48h bound still dramatically reduces scan; not as fast as it could be).
- Fix: Add
@@index([trackedSiteId, timestamp])toSourceEvent.
R3 -- hourlySample findMany on TrackedEvent (setup page)
- File:
src/app/api/site-tag/route.ts prisma.trackedEvent.findMany({ where: { trackedSiteId, timestamp >= todayStart }, take: 1000 }). Runs on every 30s poll.(trackedSiteId, timestamp)index exists so this should be fast, but for very high volume brands the today-bounded scan still reads a large index range before thetake: 1000stops it.- Risk level: Low. Monitor via
?debugTiming=1.
R4 -- customEventsList fallback: 90-day TrackedEvent.groupBy
- File:
src/app/api/site-tag/route.ts - If
SiteTagDailyRolluphas no rows (new brand, cron lag), falls back totrackedEvent.groupByover 90 days. - Risk level: Low for established brands. High for new brands if cron is delayed.
R5 -- domainStatus per-domain count queries
- File:
src/app/api/site-tag/route.ts - For each allowed domain: 2 parallel queries (
count+findFirst). WithstartsWith(sargable on(trackedSiteId, pageUrl, timestamp)index) andPromise.all, this is acceptable for 1-3 domains. - Risk level: Low for 1-3 domains. Medium for brands with 10+ allowed domains.
R6 -- Live TrackedEvent aggregate fallback when rollup missing
- File:
src/lib/services/site-tag-analytics.ts - If
rollupRows.length < expectedDays - 1, falls back to full-rangeTrackedEventgroup-by scans. - Risk level: Low for steady-state brands. Medium for brands recovering from cron failures.
R7 -- getEventTrends always runs live (intentional revert)
- Commit:
40990aa revert(getEventTrends): remove rollup early-return, always run live query - 30-day
TrackedEvent.groupByper call. - Risk level: Medium -- depends on call frequency and brand volume.
R8 -- getSiteTagInstalledAt and getSiteTagMaturity internals not confirmed
- Called in
Promise.allat end of setup page handler (outsidewithDeadline). - Risk level: UNCONFIRMED.
R9 -- site-tag-rollup cron processes ALL brands serially
- File:
src/app/api/cron/site-tag-rollup/route.ts - Outer loop:
for site of sites, inner loop:for date of datesToProcess. 9 DB round-trips per iteration. At 100 brands x 30 days = 2700 iterations x 9 = 24,300 DB calls within 300s maxDuration. - Risk level: Medium at scale. Would fail silently (partial rollup) if maxDuration hit.
Resolved risks (this session and prior)
activePagesCount7-day unbounded CASE expression -- resolvedd6dfed4getReconciliationSummary9 unbounded full-table scans -- resolved07e37efrecomputeSessionScorein ingest hot path -- resolvedaccf550
11. Patent Map
Site Tag ID system
Status: WORKING IMPLEMENTATION
public/t.js+ sourcepublic/t.source.js-- served from/api/site-tag/script/[name]- Session stitching:
TrackedSessionwithpredictedConversionScoreML field - Dedup: submissionId token, rapid-fire query, SHA1 clustering,
isDuplicateOfpointer - Attribution:
touchpoints JSON,firstTouchChannel,lastTouchChannel,classifiedChannel - Platform detection:
detectedPlatformJSON onTrackedSite(name, version, confidence, category, hosting) - Plugin detection:
plugin_detectedevents withplugins[]payload
Multi-source dedup + CanonicalEvent
Status: WORKING IMPLEMENTATION
CanonicalEvent+SourceEventmodels; 5-source reconciliation (site_tag|ga4|gtm|gsc|crm)ingestSourceEvent(): +-5 min match window, confidence boosting (75 + sourceCount*8, max 100)computeDedupKey(): SHA1 hash, deterministic survivor selection- Daily
dedup-conversionscron for backfill - Note:
reconcileTrackedEventsstill has unbounded oldest-first scan (R1)
Conversion funnel (multi-tier)
Status: WORKING IMPLEMENTATION
CONVERSION_TIERSinsrc/lib/conversions/tiers.ts: 41 conversion types across 3 tiers (completed|intent|signal)BrandConversionConfig: per-type override (isCounted, tierOverride, displayLabel)SiteConversion: full funnel data including touchpoints, value, orderId, lineItems (JSON)- Configure Conversions modal in Site Tag Analytics UI
AI Strategist
Status: WORKING IMPLEMENTATION
POST /api/ai-strategist/chat(120s, claude-sonnet-4-6)- Context: brand profile + GSC + Site Tag + AnalysisJob.result + paid media + LLMO
- Artifact renderer: JSX dashboard generation in iframe sandbox
- Session persistence + guardrail logging
AI Visibility / LLMO attribution
Status: WORKING IMPLEMENTATION (base), PARTIAL (market position calibration)
AiVisibilityCheck,AiVisibilityQuery,AiVisibilitySnapshot-- fully wired- Weekly cron, 7-source visibility checker using Anthropic
web_search_20250305tool - Market Position Block 1+2: schema + corpus seeding implemented (
cdb15a9,c735d74) - Calibration runner producing
MarketPositionSnapshot.calibratedScore: schema-only, no runner service confirmed
Heatmap
Status: WORKING IMPLEMENTATION
HeatmapEventmodel,POST /api/site-tag/heatmap,GET /api/site-tag/analytics/heatmap- UI tab on Site Tag Analytics
Execution Hub
Status: WORKING IMPLEMENTATION
Taskmodel: 7 categories, pipeline stage, briefId, assignedTo- Calendar view + pipeline board
- AI explanations per task
Audit Runner (7-phase pipeline)
Status: WORKING IMPLEMENTATION
- 2038-line orchestration, 26 dimension aggregators, 7 synthesizers
- $4/$2 cost ceilings; prompt caching; degraded mode
- 14-industry lens, vendor confidentiality enforced
Report Studio
Status: WORKING IMPLEMENTATION
- Section reordering, custom assembly
- PDF, DOCX, Markdown, JSON, PPTX exports
@react-pdf/rendererfor audit PDFs (comprehensive + executive modes)
Multi-tenant / White-label
Status: WORKING IMPLEMENTATION
WhiteLabelConfig: per-org logo, colors, domain, removeMeseoBrandingCustomDomain: PENDING|VERIFIED|FAILED with Vercel domain integrationClientPortal: allowedPages[], hideSourceBranding, per-user roles
Local GEO
Status: SCHEMA + CRON READY, UI STUBBED
GbpLocation,GbpReview,GbpMetric+ dailygbp-synccron active/local-geointentionally shows "Coming Soon"- Local ranking and AI search citation at location level: ABSENT
Gaps and unknowns:
- Market Position calibration runner: Block 3+ (models + seeding exist, runner not found)
PageLaunchSnapshotdata population mechanism (GSC direct vs MetricSnapshot): UNCONFIRMED- Inngest: infrastructure only, no production functions beyond ping
- Voice search synthesizer call path not fully traced from audit-runner.ts
End of inventory. All code paths confirmed via file reads or agent exploration unless marked UNCONFIRMED or ABSENT. Commit hashes reference dmazzei-star/meseo main branch as of 2026-06-10 HEAD 07e37ef.