Commit History
-
feat(observability): log Meta Graph API version at startupβ
CognxSafeTrackBoth API and Worker now emit a β info log at boot showing the active META_GRAPH_API_VERSION, or a β οΈ warn if the version is below v21.0 (which signals a likely-deprecated version). Visible directly in Railway logs without digging into the code.d66ae60 -
fix: upgrade Meta Graph API from deprecated v18/v19 to v22.0β
CognxSafeTrackAll hardcoded v18.0 and v19.0 references replaced with a centralized META_GRAPH_API_VERSION env var (default: v22.0). v19.0 was deprecated in February 2026 causing messaging_limit_tier to silently disappear from the /clients daily-limit card. Also improves error visibility: the catch block in fetchMetaStatus now logs the full error instead of swallowing it silently, and a warn is emitted when messaging_limit_tier is absent even on a successful response (signals a missing BUSINESS_MANAGEMENT permission).b25d16e
-
fix(audit): resolve all remaining technical debt issuesβ
CognxSafeTrackC2 - analytics SQL injection: UNION detection, ALLOWED_TABLES allowlist, remove SQL from error responses to prevent schema leakage H1/H2 - override-feedback: move organizationId validation to top of handler before any DB mutation; scope enrollment.findFirst to org H6 - UserListPage: add pagination controls (PrΓ©cΓ©dent/Suivant, 20 per page) L4 - TrackDaysPage: await load() before closing editor to prevent stale data race L5 - admin.ts: replace getOrganizationId() with req.organizationId at 3 call sites, remove now-unused import M1 - EntityResolver: replace any[] types with proper Prisma payload types (ResolvedUser/Contact/Organization/Enrollment with exact include shapes) L8 - AI command fuzzy org match: return pending_confirmation when org found by partial name match, inject orgId for unambiguous confirmation retrya888244 -
fix(security): resolve critical and high technical debt from auditβ
CognxSafeTrackCRITICAL: - Exclude passwordHash from enrollment list response (admin.ts) - Add sanitizeOrg() to strip API keys/tokens from all org GET responses - Fix CORS wildcard on SSE stream; use request origin instead - Restrict super-admin routes to SUPER_ADMIN role only (was also allowing ADMIN) - Replace all Zod .parse() with .safeParse() β proper 400s instead of 500s (ai.ts, admin.ts) HIGH: - Increase ExerciseHandler Redis dedup lock from 30s to 300s - Add take:500 to unbounded trainingData.findMany (OOM risk) - Add take limits to message/contact findMany queries - Add Redis lock in EnrollHandler to prevent duplicate enrollments (TOCTOU) - Wrap override-feedback in try/catch; return 404 on P2025 - Use atomic increment for wallet credits (was TOCTOU race condition) - Scope messages/send to requesting org (was cross-org accessible) MEDIUM: - decryptSecrets returns new object instead of mutating argument in-place - GeminiProvider.generateStructuredData now validates response with Zod schema -β¦a8e18d6 -
feat: complete super-admin interface + enforce Day 11 photo requirementβ
CognxSafeTrackSuper-admin (3 missing pages + 3 new backend routes): - AuditLogs page: date-range / action filter, paginated table with inline JSON detail expansion - WhatsAppTemplates page: org directory with lazy per-org template fetch; category/status badges; expandable template panel per row - WhatsAppProfiles page: card grid showing org branding (logo, color, WABA ID, token age); inline edit form with live logo preview and color picker; PATCH /v1/super-admin/whatsapp/profiles/:id - Backend: GET /audit-logs, GET/PATCH /whatsapp/profiles, GET /whatsapp/templates added to super-admin.ts - SuperAdminLayout: 10 nav items (added Templates WA, Profils WA, Audit Logs); SuperAdminRouter wired to all new routes Day 11 photo enforcement (tasks/todo.md item resolved): - FeedbackHandler: if currentDay === 11 and no imageUrl, send multilingual photo prompt and return early β exercise cannot be validated without a team member photo8f2286f -
feat: CSV export, monthly report, and quota alert plan filterβ
CognxSafeTrack- BillingPage: add "CSV" download button that exports all wallet transactions (date, type, description, amount, balance) as UTF-8 CSV - scheduler.ts: add startMonthlyReportScheduler() β cron on 1st of month 07:30 UTC; sends per-org recap of AI calls, WhatsApp messages, top-3 features, credits recharged/spent, and current balance - index.ts: register startMonthlyReportScheduler() alongside existing crons - usage-tracker.ts: quota alert (85% threshold) now only fires for GROWTH, SCALE, and ENTERPRISE plans; STARTER alerts were high-noise / low-valuea24fb7f
-
fix(audit): resolve 15 critical and major technical debt issuesβ
CognxSafeTrack +1: Claude Sonnet 4.6Security: - campaigns.ts: prevent cross-tenant write by using req.organizationId instead of URL param - billing.ts: restrict org mode change to SUPER_ADMIN only (was incorrectly allowing ADMIN) - CommandHandler: guard SEED command with NODE_ENV !== 'production' - search.ts: return [] instead of fabricated mock data when SERPER_API_KEY absent - scheduler: filter out hard-stopped organizations from nudge and lesson delivery - payments.ts: HMAC signature verification for Wave and Orange Money webhooks + fix track.priceAmount Data integrity: - schema: TrackDay.dayNumber Float β Int, Enrollment.currentDay Float β Int - schema: add @@unique([userId, trackId]) to Enrollment to prevent race condition double-enrollment - Migration 20260513000004 applied to production DB Performance: - admin.ts /live-feed: fix N+1 queries with batch findMany + in-memory map - organizations.ts /:id/messages: add pagination (was unbounded, OOM risk) - crm-service.ts listMessages: add skip/limit/total support Coβ¦97f541f -
fix(jour11): real Gemini vision for team member extraction + correct mediaTypeβ
CognxSafeTrack +1: Claude Sonnet 4.6- ai-sdk: add extractTeamMemberFromImage() β uses callWithFailover with VISION capability (Gemini base64 inlineData), structured schema {name, role, bio} - FeedbackHandler: replace generateText() (text-only) with extractTeamMemberFromImage() so Day 11 photos are actually analyzed visually, not just via URL in text prompt - MediaHandler: fix hardcoded mediaType AUDIO β IMAGE for image mimeTypes; remove duplicate isImage declaration in spinner block Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>9d56788 -
fix: security hardening, real AI costs, traceId propagation & audit loggingβ
CognxSafeTrack +1: Claude Sonnet 4.6- S1: enforceOrgIsolation β organizationId always from JWT for non-SUPER_ADMIN (header ignored) - S4: auditService.log() on ORG_MODE_CHANGED, ORG_PERSONALITY_UPDATED, ORG_BYOK_KEYS_UPDATED - O1: traceId (UUID) generated at webhook, forwarded via x-trace-id header, injected into BullMQ job data, threaded through whatsapp-logic handler chain - D3: /v1/analytics/usage now aggregates real UsageEvent.costUsd + tokens (tokensIn/Out) with per-feature breakdown instead of token estimates - D4: BrandingDataSchema added to shared-types β brandingData strictly typed (logoUrl, primaryColor, orgName) instead of z.any() - A6: InboundHandler fetches user.language in parallel with org token, passes it to Whisper - A2: sendLessonDay() caches personalized lesson text in Redis 6h by (trackDayId, activity, language) - P1: sendLessonDay() parallelized β user+trackDay batched in one include, userProgress fetched in parallel - D2: ExerciseHandler Redis 30s dedup lock prevents double exercise submission Co-Aβ¦87afdf1 -
feat: agentic platform β text-to-sql, pedagogy advisor, security hardening & performanceβ
CognxSafeTrack +1: Claude Sonnet 4.6- Analytics: POST /v1/analytics/query β natural language β GPT-4o-mini β SELECT β results (Text-to-SQL) - Analytics UI: example chips, SQL results table, 'Voir SQL' toggle - Billing: role guard on change_organization_mode (ADMIN+ only) - Nav: /ai-setup accessible to all org members (not just super admin) - Scheduler: startPedagogyAdvisor() β Wednesday 08:30 UTC cron, at-risk nudges (5 languages) + email report - S1 security: organizationId always from JWT for non-super-admin (never from header) - A6: InboundHandler passes user.language to Whisper transcription (Wolof hint: 'fr') - A2: Redis 6h cache for personalized lessons keyed by (trackDayId, activity, language) - P1: sendLessonDay() parallelized from 4 sequential DB queries to 2 (user+trackDay batch, userProgress parallel) - D2: Redis 30s dedup lock on exercise submission to prevent double-submit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>5b8761d -
feat(jour11+schema): Jour 11 photoUrl on TeamMember + audit doc updatesβ
CognxSafeTrack +1: Claude Sonnet 4.6- TeamMember.photoUrl: new optional field stores the original WhatsApp image URL alongside the AI-extracted name/role/bio - extractAndSaveTeamMember(): now persists photoUrl field - Migration 20260513000003_add_team_member_photo_url - Audit doc: mark Semaine 3-4 fully complete, add Stripe + Jour 11 items Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>61fd7f5 -
feat: Claude Sonnet 4.6 provider + Stripe billing automationβ
CognxSafeTrack +1: Claude Sonnet 4.6AI: - packages/ai-sdk: ClaudeProvider implements LLMProvider via tool_use structured outputs + zod-to-json-schema - Claude registered at priority 120 (TEXT only), supersedes Gemini for feedback/lessons/chat β -40% cost, better French - ANTHROPIC_API_KEY added to API and worker env validation Stripe: - apps/api/src/routes/stripe.ts: POST /v1/stripe/webhook handles checkout.session.completed, subscription.updated/deleted, invoice.payment_failed/succeeded - Schema: stripeCustomerId + stripeSubscriptionId added to Organization - tenant.ts: 402 gate for isHardStopped orgs (x-api-key calls always pass) - ClientsManagementView: SUSPENDU badge on hard-stopped orgs - STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET added to config Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>4a0c3ba -
feat(agentic-week2-4): 5 major features β AI content generator, handoff, tags, soft-delete, SSEβ
CognxSafeTrack +1: Claude Sonnet 4.6## Agent CrΓ©ateur de Contenu (Semaine 3) - AIService.generateCurriculum() β structured GPT-4o prompt generates full Track + TrackDays - POST /v1/organizations/:id/content/ai-generate β persists curriculum directly to DB - TrackListPage: "GΓ©nΓ©rer avec IA" button + modal (description, numDays, language, audience) - Auto-redirects to the generated program days after creation ## Human Handoff Detection (Semaine 4) - AIAgentHandler: detects 15 multilingual handoff keywords (FR/EN/ES) - Sets Redis flag handoff:{userId}:{orgId} (24h) β pauses AI responses - Emails org admin with contact info + link to admin UI - Handles subsequent messages while in handoff mode gracefully ## Contact Segmentation by Tags (Semaine 2) - Contact.tags String[] added to Prisma schema (migration + db:push applied) - GET /contacts/tags β list all unique tags in org - PATCH /contacts/:id/tags β set tags on a contact - GET /contacts?tags=tag1,tag2 β filter contacts by tags (hasSome) ## SSE Real-time Updates (Semaineβ¦99f6bca -
feat(agentic-week1): conversational memory, RAG threshold, wallet alerts, weekly reports, campaign schedulingβ
CognxSafeTrack +1: Claude Sonnet 4.6- AIAgentHandler: Redis sliding window (20 entries, TTL 24h) for conversation history injected into system prompt β AI_AGENT now remembers context - IndexingService.searchRelevantContext: cosine threshold 0.70 β returns '' if no chunk is relevant so agent responds honestly instead of hallucinating - add-hnsw-index.ts: one-shot HNSW index script (m=16, ef=64) for ~10x faster pgvector cosine search on large knowledge bases - scheduler.ts: hourly wallet alert (email via Brevo if < 3 days runway, 6h Redis suppression) + weekly report every Monday 07:00 UTC with trend vs previous week and color-coded wallet status - queue.ts + campaigns.ts: sendAt ISO-8601 parameter on broadcast/campaign routes β BullMQ delay option schedules jobs natively, no cron needed - docs/agentic/: roadmap updated with Semaine 1 marked complete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>98240fd -
perf(worker): Sprint 3 β broadcast/campaign scalability fixesβ
CognxSafeTrack +1: Claude Sonnet 4.6H4: Replace per-contact prisma.message.create() inside the loop with a batch prisma.message.createMany() after each page β reduces N DB round-trips to 1 per page of 200 contacts. H5: Replace include:{contacts:true} (loads all contacts into memory) with cursor-based pagination (PAGE_SIZE=200) using broadcastLists relation filter. Large lists no longer OOM the worker. H6 (usage check): Move UsageService.checkAndIncrement() from per-contact to per-page with amount=page.length β reduces Redis INCRBY calls from N to N/200. H11: Add job.updateProgress() after each page in CampaignHandler so BullMQ's stall detector sees the job as alive during long campaigns; also provides live progress in BullBoard (0β99%). H10: Confirmed resolved β wallet:ok:{orgId} cache key is consistent across API middleware and worker since credit-guard.ts deletion in Sprint 2. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>30a407a -
refactor(worker): Sprint 2 β dead code removal and queue consolidationβ
CognxSafeTrack +1: Claude Sonnet 4.6H1: Delete credit-guard.ts (worker) and credit-cache.ts (api) β both fully superseded by wallet.ts/requireCredits.ts since the wallet migration. H6: Introduce lib/queues.ts with shared whatsappQueue and notificationQueue instances. Replace all 10 inline `new Queue(...)` calls across wallet.ts, usage-tracker.ts, whatsapp-logic.ts, scheduler.ts, and 4 handlers. Removes 10 redundant Redis connections; drops `q.close()` calls on ephemeral instances that were killing the shared connection. Shutdown handler now explicitly closes both shared queues. Scratch: Move apps/api/scratch/ one-shot DB scripts to scripts/db-ops/ so they don't ship in the production API bundle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>37fb9ce -
fix(security): Sprint 1 β five critical debt fixesβ
CognxSafeTrack +1: Claude Sonnet 4.6C1: Await debitWallet() in usage-tracker and broadcast/campaign handlers so billing failures are caught and logged, not silently swallowed. C2+C3: Rewrite debitWallet() as a single Prisma interactive transaction: balanceAfter now set atomically; isHardStopped flag set in the same transaction as the balance decrement β no window between debit and stop. C4: Add X-Hub-Signature-256 HMAC-SHA256 verification to POST /webhook. Uses preParsing hook for raw body capture; fails open when WHATSAPP_APP_SECRET is not configured. H3: Fix payment webhook race condition β use updateMany(where: PENDING) as atomic idempotency guard; only the request that flips PENDINGβCOMPLETED proceeds to enrollment creation. H7: Replace $queryRawUnsafe/$executeRawUnsafe with $queryRaw/$executeRaw Prisma.sql template literals in indexing.ts; user-controlled values (organizationId, limit) are properly parametrized. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>fae3e96 -
feat(billing): close 3 remaining wallet gaps from implementation planβ
CognxSafeTrack +1: Claude Sonnet 4.6β« sendTextMessage() wallet debit coverage: - Add organizationId?: string to sendTextMessage() config β calls trackWhatsAppSent() automatically after each successful send - NudgeHandler, ContentHandler, EnrollHandler, MediaHandler, AdminHandler: pass organizationId in tenantConfig so every outbound message triggers the debit without touching each callsite individually β― GET /billing/summary now includes wallet block: - wallet: { balance, isHardStopped, creditToFcfa, balanceFcfa } appended to every summary response β± BillingPage: wallet transaction table: - Table showing last 20 transactions (date, type, description, amount, balance after) - Type labels with emoji (recharge vs debit), BYOK badge, red/green amount colouring - Inserted between feature breakdown and chat widget Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>f5126bc -
feat(billing): implement full wallet/ledger system with hard-stop enforcementβ
CognxSafeTrack +1: Claude Sonnet 4.6- Add walletBalance + isHardStopped to Organization schema; WalletTransaction ledger model with full audit trail - wallet.ts (worker): debitWallet (atomic $transaction), creditWallet (with AuditLog), checkWalletBalance (Redis 30s cache), alert queuing (low/critical/exhausted/restored) - wallet.ts (api): creditWallet for admin top-up with cache invalidation - usage-tracker.ts: debitWallet integrated into trackAiUsage, trackAudioUsage, trackWhatsAppSent; BYOK orgs skipped - BroadcastHandler + CampaignHandler: bulk debit after send (BROADCAST_PER_USER per recipient) - requireCredits.ts: migrated from aiCreditsUsed/Limit to walletBalance/isHardStopped; shares Redis cache key with worker - whatsapp-logic.ts: replaced assertCredits/CreditExhaustedError with checkWalletBalance/WalletExhaustedError - ai.ts: added requireCredits preHandler to all 11 AI routes (was 4) - billing.ts: /admin/allocate now calls creditWallet(); new GET /wallet route (balance + 20 last txns) - BillingPage: wallet cardβ¦0fd3320 -
feat(billing): enforce AI credit limits across all surfacesβ
CognxSafeTrack +1: Claude Sonnet 4.6- credit-guard.ts (worker): assertCredits() blocks AI when creditsUsed >= creditsLimit; Redis cache 60s prevents DB hit on every message - whatsapp-logic.ts: credit guard injected before handler chain; RECHARGE keyword bypasses the guard so users can always request a payment link - CommandHandler: new RECHARGE keyword sends payment URL or contact message - i18n: credit_exhausted + recharge_link + recharge_contact in 5 languages - requireCredits middleware (api): 402 on personalize-lesson, tts, generate-feedback, crm/generate-campaign - billing route: POST /v1/billing/admin/allocate (SUPER_ADMIN only) supports set/add actions + optional usage reset + Redis cache invalidation - ClientsManagementView: "CrΓ©dits IA" button (SUPER_ADMIN only) per org opens allocation modal with set/add/reset controls - BillingPage: red exhaustion banner when creditsUsed >= creditsLimit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>6484585
-
feat(billing): complete billing system, push notifications, and tech debt fixesβ
CognxSafeTrack +1: Claude Sonnet 4.6- Usage tracking: instrument pedagogy, audio transcription, AI Agent mode (was silently untracked) - EmailHandler: implement templates 2 (password reset), 3 (quota alert), 4 (monthly report) - Quota alert: auto-queue email + push at 85% threshold via maybeQueueQuotaAlert - Push notifications: PushHandler + send-push BullMQ job; sendPushToUser scoped by organizationId to prevent cross-tenant reads - BillingPage: add credit recharge modal with 3 packs, null guard for missing orgId - i18n: add nav.billing key in fr/en/es/pt locales - Tests: billing.test.ts (10 tests), payments.test.ts (13 tests) - Tech debt: fix empty catch blocks in ai-pedagogy.ts, exclude usage tokens from CRM API response, pass organizationId to generateChat in AIAgentHandler - Docs: add backlog and business model to docs/business/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>8280d7d -
fix(seed): purge user enrollments and progress on SEED commandβ
CognxSafeTrack +1: Claude Sonnet 4.6SEED was resetting activity=null and businessProfile but leaving enrollments intact. Then INSCRIPTION's guard (!user.activity) skipped the hard reset, so the old enrollment survived. EnrollHandler found it and silently skipped welcome + send-content β bot gave no response after sector selection. SEED now deletes enrollment, userProgress, response, businessProfile and resets activity/city β same cleanup as INSCRIPTION's hard reset. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>326f2d2 -
fix(bot): eliminate double feedback, fix gemini model, fix multilingual promptsβ
CognxSafeTrack +1: Claude Sonnet 4.61. Double feedback (critical): Bridge was enqueuing both download-media AND handle-inbound for every audio/image message. Both paths independently transcribed the audio and called WhatsAppLogic.handleIncomingMessage β two generate-feedback jobs β user received two Coach responses. Fix: remove handle-inbound and send-message-direct from bridge for media. MediaHandler (download-media) already handles full pipeline. MediaHandler now sends a localized spinner after user lookup (sendSpinner flag). 2. gemini-1.5-pro β 404: gemini-1.5-pro is deprecated via Google API v1beta. Updated GeminiProvider to use gemini-2.0-flash for both flash and complex (pro) requests. 3. Multilingual deep-dive invitation (EN/ES/PT): action-feedback-standard.md only had FR and WO variants for the APPROFONDIR/SUITE invitation at end of feedback. Added EN, ES, PT β AI was mixing languages for English/Spanish/Portuguese users. 4. InboundHandler [object Object] bug: aiService.tranβ¦5f0a436 -
fix(onboarding): use user.activity guard instead of activeEnrollment for sector selectionβ
CognxSafeTrack +1: Claude Sonnet 4.6After INSCRIPTION hard reset, a stale enroll-user job in BullMQ can re-create an enrollment before the user's sector selection arrives. The old guard (!activeEnrollment) caused SEC_* messages to fall through to ExerciseHandler, which replied "Your answer is a bit short" instead of saving the sector. Using !user.activity is the correct signal: it is nulled out during INSCRIPTION reset and stays null until the sector is saved, regardless of enrollment state. Also adds apps/api/scratch/rotate_encryption_key.ts β a key-rotation script for the ERR_OSSL_BAD_DECRYPT issue: decrypts org secrets with OLD_ENCRYPTION_SECRET and re-encrypts with NEW_ENCRYPTION_SECRET, idempotently. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>3d23b2d
-
fix(worker): add missing decryption for whatsapp token in pedagogy and inbound handler
CognxSafeTrackc585823 -
fix(handlers): decrypt systemUserToken in all handlers via getCachedOrganizationβ
CognxSafeTrack +1: Claude Sonnet 4.6ContentHandler, NudgeHandler, CampaignHandler, BroadcastHandler, DirectMessageHandler, MediaHandler all had the same bug as EnrollHandler: reading org.systemUserToken raw (AES-encrypted) from Prisma, caching the encrypted string in Redis under org:config:*, then sending it to Meta β Authentication Error on every outbound job. Fixed all 6 by replacing the duplicated getTenantConfig implementation with getCachedOrganization() which calls decryptOrgSecrets() before returning. AdminHandler and MessageHandler were already correct. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>8d49433 -
fix(enroll): decrypt systemUserToken via getCachedOrganizationβ
CognxSafeTrack +1: Claude Sonnet 4.6EnrollHandler had its own getTenantConfig that read org.systemUserToken raw from the DB (AES-encrypted) and cached the encrypted value in Redis. Sending an encrypted token to the WhatsApp API β Authentication Error. Fixed: use getCachedOrganization() which calls decryptOrgSecrets() before returning, same pattern as MessageHandler (fixed in commit 2b96936). Removed the custom Redis caching β getCachedOrganization has its own L1+L2 cache. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>7335a7d -
fix(build+worker): restore seedDatabase export + DISABLE_WORKER_CONSUMER guardβ
CognxSafeTrack +1: Claude Sonnet 4.61. packages/database/seed.ts: restore re-export of seedDatabase. moduleResolution:node ignores package.json exports field β TypeScript resolves @repo/database/seed to root seed.ts. Without the re-export, tsc sees no seedDatabase export β TS2339 build failure. Runtime still uses dist/src/seed.js (via exports field) which is compiled CJS with no TypeScript syntax. 2. apps/whatsapp-worker/src/index.ts: add DISABLE_WORKER_CONSUMER guard. Both Railway and HuggingFace workers share the same BullMQ/Redis queue. When HuggingFace wins a send-message job, it fails with SSL EPROTO (HF cannot reach graph.facebook.com). Setting DISABLE_WORKER_CONSUMER=true on HuggingFace keeps the bridge running (accepts forwarded webhooks) but stops it from consuming jobs β only Railway's worker sends to Meta. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>76f2a6a -
fix(bot): harden all userProgress update/create patternsβ
CognxSafeTrack +1: Claude Sonnet 4.6NavigationHandler: - SUITE: replace userProgress.update() with upsert() inside $transaction to survive missing UserProgress records without crashing - APPROFONDIR: replace userProgress!.id (non-null assertion) with explicit null guard to eliminate TypeScript unsafe access ExerciseHandler: - Re-validation update: add .catch() β non-blocking, race-condition safe - iterationCount update: add .catch() β non-blocking, race-condition safe FeedbackHandler: - Wrap userProgress.update() in .catch() with P2025 upsert fallback so feedback is never lost if the UserProgress record was deleted between job enqueue and processing ContentHandler: - Graduation regex /^T(\d)-(FR|WO)$/ β /^T(\d+)-(FR|WO|EN|ES|PT)$/ EN/ES/PT users can now graduate to next level tracks - Create UserProgress atomically with Enrollment on graduation (same fix as EnrollHandler β missing record blocked SUITE navigation) - Translate congrats and premium messages into all 5 languages Co-Authored-By: Claude Sonnet β¦d5f6ec0 -
fix(bot): build error + multilingual sectors + UserProgress on enrollmentβ
CognxSafeTrack +1: Claude Sonnet 4.6Build: - Revert CommandHandler to dynamic import for seedDatabase β TypeScript moduleResolution:node resolves @repo/database/seed to root seed.ts which had no exports. Added re-export of seedDatabase there as fix. OnboardingHandler: - LEGACY_SECTORS now has multilingual labels (FR/WOLOF/EN/ES/PT) - Sector list displayed in the user's chosen language at step 2 - Sector label saved to activity in user's language (not hardcoded FR) EnrollHandler: - Create UserProgress atomically alongside Enrollment on first enroll. Missing UserProgress caused NavigationHandler SUITE to throw "Record not found" on userProgress.update, blocking all navigation. - Welcome message now sent in user's language (was hardcoded FR) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>b538b17 -
fix(audit): resolve technical debt + UX issues from 2026-05-11 auditβ
CognxSafeTrack +1: Claude Sonnet 4.6Backend: - Add missing DB indexes on Response.userId and Response.enrollmentId - Add 15s timeout with fallback on AI personalization in pedagogy.ts - Fix race condition in NavigationHandler with atomic $transaction - Fix OnboardingHandler hard reset: use FK cascades, 5-lang confirmation - Change seedDatabase to static import in CommandHandler - Add proper pagination to /enrollments admin endpoint UX/Admin: - Fix responsive grid on dashboard stats (1col mobile β 5col desktop) - Show filename + status label during file upload in ContactsPage - Add success/error toast with import summary on contact CSV/XLSX upload - Add inline form validation with field-level errors in ClientsManagementView - Wire up Copy button on AI-generated messages in ContactsPage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>9fe2cc7 -
feat(content): add EN/ES/PT translations for all 5 training tracksβ
CognxSafeTrack +1: Claude Sonnet 4.615 new track files (T1βT5 Γ EN/ES/PT) generated by GPT-4o from the French source tracks. All text fields translated: title, lessonText, exercisePrompt, videoCaption, exerciseCriteria goal/hints, buttonsJson titles. IDs, URLs, weights and thresholds preserved exactly. audioUrl is null in all translated tracks β TTS auto-generates on first lesson delivery via the existing OpenAI tts-1 pipeline. OnboardingHandler now routes ENβT1-EN, ESβT1-ES, PTβT1-PT directly (no more FR fallback). The existing seed loader picks up all new files automatically. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>018b222 -
feat(i18n): full 5-language support across all bot handlersβ
CognxSafeTrack +1: Claude Sonnet 4.6Extend EN/ES/PT support to NavigationHandler, ExerciseHandler, NudgeHandler, AdminHandler, FeedbackHandler, and EnrollHandler. All user-facing messages now use per-language string maps instead of WOLOF/FR binary checks. EnrollHandler now sends a localised "content coming soon" message when the requested track doesn't exist, instead of silently failing. OnboardingHandler falls back to T1-FR for EN/ES/PT until orgs create language-specific tracks via the admin dashboard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>f786c37 -
feat(bot): add EN/ES/PT language support in onboarding flowβ
CognxSafeTrack +1: Claude Sonnet 4.6Replace 2-button interactive picker with 5-language interactive list (FR, Wolof, EN, ES, PT). WhatsApp buttons are capped at 3, so the list format is now used throughout the language selection step. Adds getDefaultTrack() and getSectorConfirmMsg() helpers to map all 5 languages to their respective track IDs and localised confirmations. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>71969b1 -
fix(bot): add missing Contact.language migration + fix onboarding blockingβ
CognxSafeTrack +1: Claude Sonnet 4.6- Migration: add Contact.language column (was in schema, never deployed β caused EntityResolver to crash on every message) - sendInteractiveButtonMessage: fallback to plain text when WhatsApp API rejects interactive (mirrors existing sendInteractiveListMessage behavior) - OnboardingHandler: guard mid-onboarding hard reset (no activity = resend language buttons instead of wiping data); correct welcome copy for new users Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>e6916d6 -
refactor(debt): resolve all 10 technical debt items from auditβ
CognxSafeTrack +1: Claude Sonnet 4.6- Redis: singleton pattern (lib/redis.ts) in api + worker, removes 16+ new Redis() calls - Meta token: systemUserTokenIssuedAt field + weekly cron monitor (warn @50d, critical @55d) - Rate limiting: @fastify/rate-limit global 200/min, login 10/min, forgot-password 3/min - Dead code: delete WhatsAppConnectButton.tsx, archive one-off scripts - Silent catches: logger.warn(err) on all backend catch blocks, console.warn on frontend - Meta API types: 5 typed interfaces replace 6 as any casts in organization.ts - Frontend logger: lib/logger.ts wraps Sentry (active when VITE_SENTRY_DSN is set) - Migration: add systemUserTokenIssuedAt + metaBusinessId to Organization schema Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>a966957
-
fix(webhook+worker): restore gateway forwarding and fix token decryptionβ
CognxSafeTrack +1: Claude Sonnet 4.61. Gateway architecture restored (whatsapp.ts) - When RAILWAY_INTERNAL_URL is set (HF Space), forward raw payload to Railway worker bridge instead of adding directly to Redis. - Original architecture: HF β Railway bridge β Redis β Worker. - Root cause of INSCRIPTION silence: direct Redis queue from HF Space was failing because Redis connection doesn't work from HF containers. 2. MessageHandler: decrypt systemUserToken before use - Was reading org.systemUserToken raw (AES-encrypted) from prisma, sending encrypted string to Meta β auth failure. - Now uses getCachedOrganization() which calls decryptOrgSecrets(). 3. AdminHandler: eliminate double DB query + empty enrollmentId - Was calling prisma.enrollment.findFirst() twice for same record. - Validates enrollment exists before proceeding; adds mediaUrl to Response. 4. CommandHandler: route MENU_HISTORIQUE through queue - Direct sendInteractiveListMessage() call bypassed tenant config. - Now useβ¦2b96936 -
feat: backlog P0βP3 β toast system, payments, tenant isolation, feedback handler, i18n parityβ
CognxSafeTrack +1: Claude Sonnet 4.6P0 β Critical fixes - Fix generate-feedback job silently dropped: register FeedbackHandler in worker index.ts - Wire FeedbackHandler: AI feedback, Day 11 TeamMember extraction, atomicity enforced P0 β Toast system (replace all alert()) - Add useToast hook + ToastContext + Toaster component - Replace every alert() across admin pages with toast.success/error/warning/info - Migrate all raw fetch() in pages to api.upload() / api.get() / api.post() P1 β Type safety - CrmAIAssistant: icon prop any β React.ElementType - useAudioRecorder: add onError callback instead of alert() - api.ts: add upload() method for multipart FormData P1 β Payment gateway - Add payment-gateway.ts: Orange Money + Wave initiation and webhook parsing - Rewrite payments.ts routes: initiate, status, webhook handlers with auto-enrollment on SUCCESS P1 β AI regression tests - Add apps/api/test/regression/ai-gold-standards.test.ts (12 tests, 5 suites) P2 β Visual recruitment (Day 11) - FeedbackHandler extracts TeamMemβ¦6dd9bad
-
feat: implement whatsapp templates management with security hardening, audit logs, and crm integration
CognxSafeTrack0f2f80a -
chore: stabilization, audit fixes and project synchronization
CognxSafeTrack7b0c22b
-
fix: downgrade @fastify/view to v8 to match Fastify 4.x
CognxSafeTrack055daca -
feat: implement bulk whatsapp campaigns with BullMQ and modular activation refactor (isCrmActive/isEdTechActive)
CognxSafeTracke286845 -
fix: log silent catch blocks, remove emojis from production logsβ
CognxSafeTrack +1: Claude Sonnet 4.6- MediaHandler.ts: .catch(()=>({})) on normalization rules now logs warning with err context before returning empty fallback - indexing.ts: catch{} on URL parsing now logs at debug level; remove duplicate logger import - index.ts: remove emoji characters from logger calls (π β π π) β replaced with plain ASCII text prefixes for clean log aggregation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>3165bfa -
fix: security hardening, log all silent catch blocks, remove prisma castβ
CognxSafeTrack +1: Claude Sonnet 4.6Security: - whatsapp.ts: remove WHATSAPP_VERIFY_TOKEN hardcoded fallback β return 500 if env var is missing instead of silently accepting a public default - rateLimit.ts: key by req.ip (not spoofable x-organization-id header) Code quality β 7 empty catch blocks now log with context: - NudgeHandler, AdminHandler (+ missing logger import), EnrollHandler (x2), MediaHandler (x2), organization.ts invalidateOrgCache - admin.ts: remove (prisma as any).normalizationRule β model is in schema, cast was unnecessary Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>aa4f69f -
fix: batch scheduler updates, typed JWT user, log catch blocks, move scriptβ
CognxSafeTrack +1: Claude Sonnet 4.6- scheduler.ts: collect completed enrollment IDs in loop, single updateMany after β eliminates N queries per day for completions - ContentHandler.ts: log Redis cache failures instead of swallowing silently - index.ts: remove (request as any).user cast β now safe via FastifyJWT augmentation in types/fastify.d.ts - types/fastify.d.ts: augment @fastify/jwt FastifyJWT interface so request.user resolves to {id, role, organizationId} after jwtVerify(), not string|object|Buffer - delete-user.ts: moved from apps/api root to apps/api/scripts/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>2859b85 -
fix: worker cleanup await, Stripe upsert completeness, redundant schema indexβ
CognxSafeTrack +1: Claude Sonnet 4.6- cleanup.ts: cron callback now async/await so cleanWorkerTempFiles() errors are caught instead of silently dropped fire-and-forget - payments.ts: upsert update clause now includes status, amount, currency so a Stripe webhook replay fully idempotent instead of leaving stale fields - schema.prisma: remove redundant @@index([userId, trackId]) on UserProgress β the @@unique constraint already creates a B-tree index on those columns Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>ec9b5da -
feat: centralize AI logic in @repo /ai-sdk, harmonize worker HTTP clientsβ
CognxSafeTrack +1: Claude Sonnet 4.6Architecture refactoring: - Extract all AI providers (OpenAI, Gemini, ProviderRegistry, ffmpeg, search) from apps/api/src/services/ai/ into new packages/ai-sdk package - API's services/ai/index.ts is now a thin wrapper that injects prisma, redis and organization context into the shared AIService - Worker gains @repo/ai-sdk dependency; ai-pedagogy.ts delegates to aiService.generatePersonalizedLesson instead of duplicating logic - New apiClient (Axios, 30s timeout) and whatsappClient replace bare fetch calls in InboundHandler and MediaHandler for consistent error handling - Prisma seed fixed: Tracks now include organizationId for multi-tenant compliance Build: all 8 packages pass pnpm turbo run build from clean state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>2849f33 -
feat(crm): optimize multimedia pipeline and harden contact schemaβ
CognxSafeTrack- Refactored CrmConversationalDashboard into modular components and hooks (FileImporter, useAudioRecorder) - Centralized contact normalization and bulk import in ContactService - Implemented binary streaming for media proxy to reduce memory pressure - Hardened database schema with MediaType Enum for better data integrityc5fde49 -
feat: implement inbound audio/image transcription, secure media proxy, and client-side bulk contact import
CognxSafeTrack30d60ea
-
chore: CRM stabilization sprint completionβ
CognxSafeTrack- Refactor: Decomposed CrmConversationalDashboard into modular components (Inbox, AIAssistant, StatsHeader) - Fix: Centralized entity resolution with EntityResolver service (API & Worker) - Fix: Strict Prisma typing, removed 'as any' casts in organization and whatsapp routes - Fix: Implemented broadcast message persistence and accurate usage quota tracking - Schema: Added MessageStatus and status field to Message model4339e77