fix(crm-agent): replace generateText+JSON.parse with generateStructuredData for intent detection
CognxSafeTrack
3b3d6cd
The CRM command agent was always falling back to GENERAL_CHAT and
showing the menu again because LLMs wrap JSON in markdown fences,
breaking JSON.parse. Switch to generateStructuredData with a Zod schema
which guarantees structured output regardless of LLM quirks.
Also exposes generateStructuredData as a public method on AIService.
fix(audit): resolve remaining TypeScript any types, string enums, and Json validation
CognxSafeTrack+1: Claude Sonnet 4.6
2a3e2a1
TypeScript types (ai-sdk):
- Define AIServicePrismaClient structural interface — replaces prisma: any
- Define TenantSecrets interface — replaces getTenantSecrets: () => Promise<any>
- Define BusinessProfileContext interface — replaces businessProfile?: any in 3 methods
- Define BusinessProfileExtraction interface — extractBusinessProfile now fully typed
- Define ContactData interface — replaces contact: any in generateCrmCampaign
- exerciseCriteria? typed as Record<string, unknown> instead of any
- searchResults now typed as SearchResult[] instead of any[]
DB enums:
- CampaignHistory.status: String → CampaignStatus enum (SENT | DELIVERED | READ | FAILED)
- AnalyticsLog.eventType: String → AnalyticsEventType enum (CLICK | READ | RESPONSE)
- Migration 20260514000001 applied to production DB
Zod validation:
- TrackDaySchema: add exerciseCriteria (z.record) and badges (z.array(z.string))
- ai.ts /crm/generate-campaign: enforce contact.phoneNumber (required) via Zod
Runtime safety:
- wh…
fix(audit): resolve remaining minor technical debt issues
CognxSafeTrack+1: Claude Sonnet 4.6
d74c1b3
AI/BYOK:
- generateText now respects tenant BYOK keys via getProvidersForTenant() (was using global registry)
- generateText now applies exponential backoff retry via AIService.withRetry()
Backend:
- notifications.ts: return 503 instead of 200+undefined when VAPID_PUBLIC_KEY absent
- BillingPage: support WA number now reads from VITE_SUPPORT_WA_NUMBER env var (was hardcoded fake number)
- KnowledgeBasePage: show toast.error on delete failure (was silently swallowed)
DB:
- Add @@index([organizationId, exerciseStatus]) on UserProgress for live-feed query performance
- Add @@index([createdAt]) on AuditLog for time-range admin queries
- Migration 20260513000005 applied to production DB
i18n:
- Add knowledge.delete_error translation key to all 4 locales (fr, en, es, pt)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(audit): resolve 15 critical and major technical debt issues
CognxSafeTrack+1: Claude Sonnet 4.6
97f541f
Security:
- 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…
feat: gold standards tests (15 days + STT + vision) & botName editable in AI setup
CognxSafeTrack+1: Claude Sonnet 4.6
1c602ea
- ai-gold-standards: 40 test cases covering all 15 lesson days, STT smoke, vision
(day 11 imageUrl), CRM broadcast/conversation, curriculum (FR+EN), pitch deck EN,
business profile extraction days 1-6 with quality assertions
- AIAgentSetup: botName field now editable (was hardcoded 'Agent IA'); loaded from
personality API on mount, saved with PATCH; falls back to 'Agent IA' if blank
- i18n: add bot_name_label/placeholder/hint keys to FR, EN, ES, PT locales
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delete stripe.ts route file, remove import and /v1/stripe registration from app.ts,
drop stripe npm dependency, remove deprecated stripeWebhookRoute stub from payments.ts,
and remove stripeCustomerId from admin Organization interface.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- resolveOrgId now uses findFirst with OR [slug, id] so literal custom IDs
like "default-org-id" (the seed org XAMLÉ Global) are resolved correctly
instead of returning 404
- ContactsPage.tsx: pass selectedOrgId to analytics/usage call — was missing
the x-organization-id header, causing systematic 400 on the contacts page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(agentic-week2-4): 5 major features — AI content generator, handoff, tags, soft-delete, SSE
CognxSafeTrack+1: Claude Sonnet 4.6
99f6bca
## 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…
- Add missing /conversations route to App.tsx (was nav-linked but unregistered)
- Apply resolveOrgId() to all 5 KB routes so slug-based org IDs (e.g. xamle-admin-org) no longer cause 404/400
- TemplatesPage: detect WABA-not-configured 400 and show amber banner instead of silently failing
- KnowledgeBasePage: show toast on index-kb 400 (no KB URL) with actionable message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
feat(admin-chat): make AI copilot a full super-admin via platform knowledge + agentic tools
CognxSafeTrack+1: Claude Sonnet 4.6
9061927
- Inject PLATFORM_KNOWLEDGE constant into all /chat system prompts: detailed
descriptions of all 4 modes (EDTECH/CRM_MARKETING/AI_AGENT/CUSTOMER_SERVICE),
plans & credit pricing, WhatsApp setup (WABA ID/Business ID/token), template
rules (variables, MARKETING vs UTILITY), KB indexing, wallet/hard-stop logic
- Remove "contactez le support" fallback — AI now answers all platform questions
- Add OpenAI function-calling tools for agentic admin actions:
- get_organization_settings: read full org config
- change_organization_mode: switch org mode (EDTECH/CRM_MARKETING/AI_AGENT/CUSTOMER_SERVICE)
- update_ai_agent_personality: update coreMission, tone, temperature
- Implement two-pass completion loop: tool calls executed against Prisma, results
fed back for final natural-language answer with actionPerformed metadata
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Phases 1-5 : masquage JSON avancé, chat IA global (AdminChat),
onboarding WhatsApp guidé, templates assistés IA, agent IA setup guidé
- i18n exhaustif : ~70 nouvelles clés en FR/EN/ES/PT pour tous les
composants ajoutés (AdminChat, AIAgentSetup, SettingsPage,
TemplatesPage, OnboardingWizard)
- Chat IA multilingue : l'assistant répond désormais dans la langue
de l'utilisateur (FR/EN/ES/PT) sur toutes les pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Frontend (BillingPage):
- Renommage "Wallet" → "Crédits disponibles" (langage non-technique)
- Badge projection dynamique "À ce rythme : encore ~X jours" calculé
depuis les transactions des 7 derniers jours
- Questions suggérées cliquables (quick-reply chips) avant le 1er message
- Suppression "Tokens entrants/sortants" (jargon incompréhensible)
- Transactions filtrées : BYOK masqué, layout liste simple vs tableau
- Section "Où vont vos crédits IA ?" remplace le jargon feature/type
- Indicateur de frappe animé (3 points) dans le chat
- Packs recharge avec explication "≈ N messages IA" et badge populaire
- Texte WhatsApp clarifié : "Gratuit pour vous — Meta facture directement"
Backend (billing.ts /chat):
- Wallet balance + isHardStopped ajoutés dans le contexte IA
→ l'assistant peut maintenant répondre "combien de crédits me reste-t-il"
- Burn rate (débit moyen 7j) calculé et injecté : "À ce rythme,
votre solde durera encore X jours"
- Statut wallet (SUSPENDU / bas / actif) expl…
refactor(worker): Sprint 2 — dead code removal and queue consolidation
CognxSafeTrack+1: Claude Sonnet 4.6
37fb9ce
H1: 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>
fix(security): Sprint 1 — five critical debt fixes
CognxSafeTrack+1: Claude Sonnet 4.6
fae3e96
C1: 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>
feat(billing): close 3 remaining wallet gaps from implementation plan
CognxSafeTrack+1: Claude Sonnet 4.6
f5126bc
⑫ 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>
feat(billing): implement full wallet/ledger system with hard-stop enforcement
CognxSafeTrack+1: Claude Sonnet 4.6
0fd3320
- 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…
feat(billing): enforce AI credit limits across all surfaces
CognxSafeTrack+1: Claude Sonnet 4.6
6484585
- 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>
feat(billing): complete billing system, push notifications, and tech debt fixes
CognxSafeTrack+1: Claude Sonnet 4.6
8280d7d
- 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>
fix(onboarding): use user.activity guard instead of activeEnrollment for sector selection
CognxSafeTrack+1: Claude Sonnet 4.6
3d23b2d
After 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>
fix(api): secure storage of ai api keys by encrypting them
CognxSafeTrack
cec25aa
fix(audit): resolve technical debt + UX issues from 2026-05-11 audit
CognxSafeTrack+1: Claude Sonnet 4.6
9fe2cc7
Backend:
- 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>
chore: cleanup old scripts and sync files
CognxSafeTrack
4a46753
refactor(debt): resolve all 10 technical debt items from audit
CognxSafeTrack+1: Claude Sonnet 4.6
a966957
- 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>
fix(prisma): add migration file for metaBusinessId + remove as-any casts
CognxSafeTrack+1: Claude Sonnet 4.6
53e72e6
Creates the proper migration SQL for the metaBusinessId column that was
applied directly via ALTER TABLE. Removes (org as any) casts now that
the Prisma client is regenerated with the correct types.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(meta-status): sync daily limit + quality rating live from Meta phone number API
CognxSafeTrack+1: Claude Sonnet 4.6
8aa43f4
fetchMetaStatus now calls GET /{phoneNumberId}?fields=messaging_limit_tier,quality_rating
to get the real tier from Meta (TIER_1K, TIER_10K, TIER_100K, UNLIMITED) instead of
a hardcoded value inferred from WABA status.
Frontend: TIER_LABELS map for human-readable labels, QualityDot badge (green/amber/red)
displayed next to the limit. dailyLimitLabel now uses the real tier from the API.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(meta-status): store metaBusinessId for reliable business verification
CognxSafeTrack+1: Claude Sonnet 4.6
70a5a84
Add optional `metaBusinessId` field to Organization (schema + shared-types).
fetchMetaStatus now queries /{businessId}?fields=verification_status directly
when metaBusinessId is stored — bypasses WABA edge permission restrictions
that block test WABAs and certain system user token scopes.
Direct setup form gains a "Business ID Meta" input that saves via PUT /organizations/:id.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(meta-status): add on_behalf_of_business_info fallback for business verification
CognxSafeTrack+1: Claude Sonnet 4.6
bde43ec
When the `business` edge on WABA returns nothing (system user token scope or
test WABA restrictions), fall back to `on_behalf_of_business_info` + direct
business node query for verification_status. Covers verified businesses whose
WABA doesn't expose the business edge directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(meta-status): split combined Meta API call + fix business badge for test WABAs
CognxSafeTrack+1: Claude Sonnet 4.6
7e2166c
Split `account_review_status,business{...}` into two independent calls so
test WABAs (which forbid the `business` field) no longer poison WABA status.
Business verification is now optional/non-fatal (wrapped in try/catch).
BusinessBadge and BusinessVerificationCell now show "—" with neutral styling
when `businessId` is undefined (field inaccessible) instead of misleading
"Non vérifiée" amber warning + "Vérifier →" link.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(whatsapp-setup): direct config mode for platform-owner accounts
CognxSafeTrack+1: Claude Sonnet 4.6
2f67731
Backend:
- accessToken now optional in /whatsapp-setup — falls back to
WHATSAPP_ACCESS_TOKEN env var (for platform-owner self-setup)
Frontend:
- "Reconfigurer WhatsApp" button now opens a choice modal instead of
launching Embedded Signup directly
- Option A (Direct): enter WABA ID + optional token — for accounts already
configured on Meta (token from env var used if field left empty)
- Option B (Embedded Signup): Facebook login popup — for new client accounts
- Phone Number ID pre-filled from existing DB record
- Auto-refreshes Meta status after successful save
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
chore: force redeploy v1.1.2
CognxSafeTrack
3b4544a
feat(meta): live Meta verification status + WhatsApp setup fixes
CognxSafeTrack+1: Claude Sonnet 4.6
bd9eb5b
- GET /v1/organizations/:id/meta-status — fetches WABA account_review_status
and business verification_status live from Meta Graph API, cached 1h in Redis
- POST /v1/organizations/:id/meta-status/refresh — force-invalidates the cache
- ClientsManagementView: replaced static metaVerificationStatus field with live
per-org Meta status (WABA badge, Business badge, derived daily limit)
- launchEmbeddedSignup: fixed race condition — now waits for both the OAuth code
(FB.login) and WABA/phone IDs (WA_EMBEDDED_SIGNUP_EVENT) before resolving
- /whatsapp-setup: exchanges OAuth code for long-lived Meta token (60 days)
before storing; bypasses exchange if token already starts with EAA
- OnboardingWizard: removed broken WhatsApp step (3 steps: welcome, legal, AI)
- OrganizationSchema: removed non-existent DB fields (contractSigned,
contractSignedAt, contractSignerName, metaVerificationStatus, dailyMessageLimit)
that caused prisma.organization.create() to throw Unknown arg at runtime
Re…
fix(webhook): forward to localhost:8082 worker bridge, not RAILWAY_INTERNAL_URL
CognxSafeTrack+1: Claude Sonnet 4.6
c12937b
RAILWAY_INTERNAL_URL points to Railway's public URL (port 443 → API on 7860),
not the Worker bridge on 8082. Since both HF Space and Railway run the Worker
bridge locally in the same container, forward to http://localhost:8082 directly.
The bridge owns org resolution, WEBHOOK mode handling, and all Redis queue ops.
Remove now-unused whatsappQueue and extractWhatsAppPayload imports from API route.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(webhook+worker): restore gateway forwarding and fix token decryption
CognxSafeTrack+1: Claude Sonnet 4.6
2b96936
1. 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…
chore: trigger redeploy for technical debt resolution verification
CognxSafeTrack
44f1818
revert: remove startup seed function — data inserted directly in DB
CognxSafeTrack+1: Claude Sonnet 4.6
6f838af
WhatsAppPhoneNumber (969048009628694 -> default-org-id) and
systemUserToken for XAMLÉ Global seeded directly via script.
No runtime seed needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(seed): auto-register global WhatsApp phone number from env vars on startup
CognxSafeTrack+1: Claude Sonnet 4.6
34691b4
On API start, if WHATSAPP_PHONE_NUMBER_ID + WHATSAPP_ACCESS_TOKEN are set,
finds the org whose systemUserToken (decrypted) matches the env token and
upserts WhatsAppPhoneNumber so webhook routing works without manual setup.
Fixes first/global organization breaking after multi-org migration.
Idempotent — no-op if record already exists.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(webhook): handle all message types via extractWhatsAppPayload + true Async-First
CognxSafeTrack+1: Claude Sonnet 4.6
ad9d288
Previous fix only extracted message.text?.body, silently dropping:
- interactive/button_reply (LANG_FR, LANG_WO, SUITE, APPROFONDIR, sector choices)
- interactive/list_reply (sector selection list)
Now uses extractWhatsAppPayload() from @repo/shared-types which handles all types.
Responds 200 before setImmediate enqueue (true Async-First, < 1ms to Meta).
Zod schema simplified to z.any() for messages content (Meta types are unbounded).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(webhook): resolve org by phone_number_id instead of wabaId, enqueue async
CognxSafeTrack+1: Claude Sonnet 4.6
15d45f6
Two silent failures broke INSCRIPTION since multi-org was added:
1. Lookup by wabaId failed when org.wabaId was unset → org=null → message dropped
2. whatsappService.handleIncomingMessage does not exist → TypeError swallowed by catch
Fix: resolve organizationId from value.metadata.phone_number_id via
getOrganizationByPhoneNumberId(), then enqueue handle-inbound to BullMQ.
Restores Async-First rule (webhook responds 200 immediately, worker processes).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
P0 — 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…
fix(auth): validateApiKey accepts Bearer pattern used by worker
CognxSafeTrack+1: Claude Sonnet 4.6
c5813d5
The worker's api-client.ts sends Authorization: Bearer ADMIN_API_KEY,
not x-api-key. Without this fix all worker→API internal calls would
get 401 after the preHandler security chain was wired.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(security): wire auth middleware chain in app.ts preHandler
CognxSafeTrack+1: Claude Sonnet 4.6
cbaf159
validateApiKey, verifyJwt, enforceOrgIsolation were created during
the debt refactor but never imported — admin routes were reachable
without a valid JWT token.
Chain: API key (worker) → skip JWT | user → jwtVerify →
enforceOrgIsolation → injectTenantConfig → runWithTenant
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: update production port to 7860 for Hugging Face compatibility and add audit report
CognxSafeTrack
10211c3
fix: resolve login 400 — auto-resolve organizationId from email when omitted
CognxSafeTrack+1: Claude Sonnet 4.6
4e2a593
Backend now falls back to findUserByEmailOnly() when the frontend
doesn't send organizationId (deployed admin still uses old build).
Password verification still required — auth security unchanged.
Also adds organizationId field to login form for first-time users,
and removes last default-org-id references from tenant.tsx.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat: implement whatsapp templates management with security hardening, audit logs, and crm integration