@omnizap-system/omnizap 2.5.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/.clusterfuzzlite/Dockerfile +10 -0
  2. package/.env.example +907 -0
  3. package/.github/codeql/codeql-config.yml +10 -0
  4. package/.github/dependabot.yml +35 -0
  5. package/.github/workflows/ci.yml +73 -0
  6. package/.github/workflows/codeql.yml +106 -0
  7. package/.github/workflows/db-migration-check.yml +98 -0
  8. package/.github/workflows/dependency-review.yml +22 -0
  9. package/.github/workflows/deploy.yml +95 -0
  10. package/.github/workflows/release.yml +106 -0
  11. package/.github/workflows/security-attest-provenance.yml +51 -0
  12. package/.github/workflows/security-gitleaks.yml +34 -0
  13. package/.github/workflows/security-runner-hardening.yml +31 -0
  14. package/.github/workflows/security-scorecard.yml +44 -0
  15. package/.github/workflows/security-zap-baseline.yml +44 -0
  16. package/.github/workflows/security-zap-full-scan.yml +43 -0
  17. package/.github/workflows/security-zizmor.yml +36 -0
  18. package/.github/workflows/wiki-sync.yml +44 -0
  19. package/.gitleaks.toml +15 -0
  20. package/.prettierrc +34 -0
  21. package/CODE_OF_CONDUCT.md +114 -0
  22. package/LICENSE +56 -0
  23. package/README.md +110 -0
  24. package/SECURITY.md +110 -0
  25. package/app/config/index.js +4 -0
  26. package/app/configParts/adminIdentity.js +92 -0
  27. package/app/configParts/baileysConfig.js +1818 -0
  28. package/app/configParts/groupUtils.js +692 -0
  29. package/app/configParts/loggerConfig.js +394 -0
  30. package/app/configParts/messagePersistenceService.js +305 -0
  31. package/app/connection/baileysCompatibility.test.js +40 -0
  32. package/app/connection/baileysDbAuthState.js +344 -0
  33. package/app/connection/socketController.js +2243 -0
  34. package/app/controllers/messageController.js +7 -0
  35. package/app/controllers/messagePipeline/commandMiddleware.js +146 -0
  36. package/app/controllers/messagePipeline/conversationMiddleware.js +183 -0
  37. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +522 -0
  38. package/app/controllers/messagePipeline/postProcessingMiddleware.js +41 -0
  39. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +166 -0
  40. package/app/controllers/messageProcessingPipeline.js +699 -0
  41. package/app/modules/adminModule/AGENT.md +4056 -0
  42. package/app/modules/adminModule/adminAiHelpService.js +56 -0
  43. package/app/modules/adminModule/adminConfigRuntime.js +177 -0
  44. package/app/modules/adminModule/commandConfig.json +7122 -0
  45. package/app/modules/adminModule/groupCommandHandlers.js +1823 -0
  46. package/app/modules/adminModule/groupCommandHandlers.test.js +350 -0
  47. package/app/modules/adminModule/groupEventHandlers.js +399 -0
  48. package/app/modules/aiModule/AGENT.md +547 -0
  49. package/app/modules/aiModule/aiAiHelpService.js +14 -0
  50. package/app/modules/aiModule/aiConfigRuntime.js +135 -0
  51. package/app/modules/aiModule/catCommand.js +967 -0
  52. package/app/modules/aiModule/commandConfig.json +981 -0
  53. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +83 -0
  54. package/app/modules/gameModule/AGENT.md +196 -0
  55. package/app/modules/gameModule/commandConfig.json +366 -0
  56. package/app/modules/gameModule/diceCommand.js +42 -0
  57. package/app/modules/gameModule/gameAiHelpService.js +14 -0
  58. package/app/modules/gameModule/gameConfigRuntime.js +68 -0
  59. package/app/modules/menuModule/AGENT.md +205 -0
  60. package/app/modules/menuModule/commandConfig.json +366 -0
  61. package/app/modules/menuModule/common.js +316 -0
  62. package/app/modules/menuModule/menuAiHelpService.js +14 -0
  63. package/app/modules/menuModule/menuConfigRuntime.js +68 -0
  64. package/app/modules/menuModule/menus.js +66 -0
  65. package/app/modules/playModule/AGENT.md +321 -0
  66. package/app/modules/playModule/commandConfig.json +584 -0
  67. package/app/modules/playModule/playAiHelpService.js +14 -0
  68. package/app/modules/playModule/playCommand.js +1417 -0
  69. package/app/modules/playModule/playConfigRuntime.js +68 -0
  70. package/app/modules/quoteModule/AGENT.md +199 -0
  71. package/app/modules/quoteModule/commandConfig.json +366 -0
  72. package/app/modules/quoteModule/quoteAiHelpService.js +14 -0
  73. package/app/modules/quoteModule/quoteCommand.js +842 -0
  74. package/app/modules/quoteModule/quoteConfigRuntime.js +68 -0
  75. package/app/modules/rpgPokemonModule/AGENT.md +229 -0
  76. package/app/modules/rpgPokemonModule/commandConfig.json +386 -0
  77. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +795 -0
  78. package/app/modules/rpgPokemonModule/rpgBattleService.js +2110 -0
  79. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +770 -0
  80. package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
  81. package/app/modules/rpgPokemonModule/rpgPokemonAiHelpService.js +14 -0
  82. package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +174 -0
  83. package/app/modules/rpgPokemonModule/rpgPokemonConfigRuntime.js +68 -0
  84. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
  85. package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
  86. package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
  87. package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
  88. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1847 -0
  89. package/app/modules/rpgPokemonModule/rpgPokemonService.js +6839 -0
  90. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
  91. package/app/modules/statsModule/AGENT.md +320 -0
  92. package/app/modules/statsModule/commandConfig.json +540 -0
  93. package/app/modules/statsModule/globalRankingCommand.js +64 -0
  94. package/app/modules/statsModule/rankingCommand.js +41 -0
  95. package/app/modules/statsModule/rankingCommon.js +1305 -0
  96. package/app/modules/statsModule/statsAiHelpService.js +14 -0
  97. package/app/modules/statsModule/statsConfigRuntime.js +68 -0
  98. package/app/modules/stickerModule/AGENT.md +692 -0
  99. package/app/modules/stickerModule/addStickerMetadata.js +239 -0
  100. package/app/modules/stickerModule/commandConfig.json +1216 -0
  101. package/app/modules/stickerModule/convertToWebp.js +367 -0
  102. package/app/modules/stickerModule/stickerAiHelpService.js +14 -0
  103. package/app/modules/stickerModule/stickerCommand.js +446 -0
  104. package/app/modules/stickerModule/stickerConfigRuntime.js +68 -0
  105. package/app/modules/stickerModule/stickerConvertCommand.js +159 -0
  106. package/app/modules/stickerModule/stickerTextCommand.js +653 -0
  107. package/app/modules/stickerPackModule/AGENT.md +215 -0
  108. package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
  109. package/app/modules/stickerPackModule/autoPackCollectorService.js +357 -0
  110. package/app/modules/stickerPackModule/commandConfig.json +387 -0
  111. package/app/modules/stickerPackModule/domainEventOutboxRepository.js +227 -0
  112. package/app/modules/stickerPackModule/domainEvents.js +52 -0
  113. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +429 -0
  114. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +75 -0
  115. package/app/modules/stickerPackModule/semanticThemeClusterService.js +544 -0
  116. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +400 -0
  117. package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
  118. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +175 -0
  119. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +3702 -0
  120. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +559 -0
  121. package/app/modules/stickerPackModule/stickerClassificationService.js +557 -0
  122. package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +249 -0
  123. package/app/modules/stickerPackModule/stickerDomainEventBus.js +65 -0
  124. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +208 -0
  125. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +99 -0
  126. package/app/modules/stickerPackModule/stickerObjectStorageService.js +285 -0
  127. package/app/modules/stickerPackModule/stickerPackAiHelpService.js +14 -0
  128. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1148 -0
  129. package/app/modules/stickerPackModule/stickerPackConfigRuntime.js +68 -0
  130. package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +152 -0
  131. package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
  132. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +101 -0
  133. package/app/modules/stickerPackModule/stickerPackItemRepository.js +432 -0
  134. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +313 -0
  135. package/app/modules/stickerPackModule/stickerPackMessageService.js +268 -0
  136. package/app/modules/stickerPackModule/stickerPackRepository.js +450 -0
  137. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +179 -0
  138. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +271 -0
  139. package/app/modules/stickerPackModule/stickerPackService.js +733 -0
  140. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +32 -0
  141. package/app/modules/stickerPackModule/stickerPackUtils.js +107 -0
  142. package/app/modules/stickerPackModule/stickerStorageService.js +559 -0
  143. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +242 -0
  144. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +242 -0
  145. package/app/modules/systemMetricsModule/AGENT.md +193 -0
  146. package/app/modules/systemMetricsModule/commandConfig.json +344 -0
  147. package/app/modules/systemMetricsModule/pingCommand.js +399 -0
  148. package/app/modules/systemMetricsModule/systemMetricsAiHelpService.js +14 -0
  149. package/app/modules/systemMetricsModule/systemMetricsConfigRuntime.js +68 -0
  150. package/app/modules/tiktokModule/AGENT.md +196 -0
  151. package/app/modules/tiktokModule/commandConfig.json +366 -0
  152. package/app/modules/tiktokModule/tiktokAiHelpService.js +14 -0
  153. package/app/modules/tiktokModule/tiktokCommand.js +716 -0
  154. package/app/modules/tiktokModule/tiktokConfigRuntime.js +68 -0
  155. package/app/modules/userModule/AGENT.md +200 -0
  156. package/app/modules/userModule/commandConfig.json +386 -0
  157. package/app/modules/userModule/userAiHelpService.js +14 -0
  158. package/app/modules/userModule/userCommand.js +1155 -0
  159. package/app/modules/userModule/userConfigRuntime.js +68 -0
  160. package/app/modules/waifuPicsModule/AGENT.md +431 -0
  161. package/app/modules/waifuPicsModule/commandConfig.json +780 -0
  162. package/app/modules/waifuPicsModule/waifuPicsAiHelpService.js +14 -0
  163. package/app/modules/waifuPicsModule/waifuPicsCommand.js +586 -0
  164. package/app/modules/waifuPicsModule/waifuPicsConfigRuntime.js +68 -0
  165. package/app/observability/metrics.js +766 -0
  166. package/app/services/ai/aiHelpResponseCacheRepository.js +280 -0
  167. package/app/services/ai/aiLearningRepository.js +400 -0
  168. package/app/services/ai/commandConfigEnrichmentRepository.js +769 -0
  169. package/app/services/ai/commandConfigEnrichmentService.js +452 -0
  170. package/app/services/ai/commandConfigValidationService.js +443 -0
  171. package/app/services/ai/commandToolBuilderService.js +192 -0
  172. package/app/services/ai/conversationRouterService.js +516 -0
  173. package/app/services/ai/geminiService.js +115 -0
  174. package/app/services/ai/geminiService.test.js +87 -0
  175. package/app/services/ai/globalModuleAiHelpService.js +1412 -0
  176. package/app/services/ai/globalToolCallingService.js +203 -0
  177. package/app/services/ai/messageCommandExecutionService.js +391 -0
  178. package/app/services/ai/moduleAiHelpCoreService.js +1099 -0
  179. package/app/services/ai/moduleAiHelpWrapperFactory.js +65 -0
  180. package/app/services/ai/moduleCommandConfigRuntimeService.js +113 -0
  181. package/app/services/ai/moduleToolExecutorService.js +464 -0
  182. package/app/services/ai/moduleToolRegistryService.js +178 -0
  183. package/app/services/ai/toolCandidateSelectorService.js +781 -0
  184. package/app/services/auth/googleWebLinkService.js +80 -0
  185. package/app/services/auth/whatsappLoginLinkService.js +230 -0
  186. package/app/services/external/pokeApiService.js +398 -0
  187. package/app/services/group/groupMetadataService.js +311 -0
  188. package/app/services/infra/dbWriteQueue.js +874 -0
  189. package/app/services/infra/featureFlagService.js +131 -0
  190. package/app/services/infra/queueUtils.js +55 -0
  191. package/app/services/messaging/captchaService.js +491 -0
  192. package/app/services/messaging/messagePersistenceService.js +1 -0
  193. package/app/services/messaging/newsBroadcastService.js +347 -0
  194. package/app/services/sticker/stickerFocusService.js +347 -0
  195. package/app/services/sticker/stickerFocusService.test.js +43 -0
  196. package/app/store/aiPromptStore.js +38 -0
  197. package/app/store/conversationSessionStore.js +131 -0
  198. package/app/store/groupConfigStore.js +58 -0
  199. package/app/store/premiumUserStore.js +54 -0
  200. package/app/utils/antiLink/antiLinkModule.js +700 -0
  201. package/app/utils/http/getImageBufferModule.js +18 -0
  202. package/app/utils/json/jsonSanitizer.js +113 -0
  203. package/app/utils/json/jsonSanitizer.test.js +40 -0
  204. package/app/utils/systemMetrics/systemMetricsModule.js +88 -0
  205. package/app/workers/aiLearningWorker.js +605 -0
  206. package/app/workers/commandConfigEnrichmentWorker.js +242 -0
  207. package/database/index.js +2075 -0
  208. package/database/init.js +151 -0
  209. package/database/migrations/.gitkeep +0 -0
  210. package/database/migrations/20260307_d0_hardening_down.sql +64 -0
  211. package/database/migrations/20260307_d0_hardening_up.sql +79 -0
  212. package/database/migrations/20260307_d1_terms_acceptance_down.sql +11 -0
  213. package/database/migrations/20260307_d1_terms_acceptance_up.sql +37 -0
  214. package/database/migrations/20260307_d2_auth_hardening_down.sql +75 -0
  215. package/database/migrations/20260307_d2_auth_hardening_up.sql +100 -0
  216. package/database/migrations/20260314_d7_canonical_sender_down.sql +53 -0
  217. package/database/migrations/20260314_d7_canonical_sender_up.sql +114 -0
  218. package/database/migrations/20260406_d30_security_analytics_down.sql +95 -0
  219. package/database/migrations/20260406_d30_security_analytics_up.sql +292 -0
  220. package/database/migrations/20260407_d31_web_google_session_token_hardening_down.sql +2 -0
  221. package/database/migrations/20260407_d31_web_google_session_token_hardening_up.sql +17 -0
  222. package/database/migrations/20260408_d32_ai_help_response_cache_down.sql +1 -0
  223. package/database/migrations/20260408_d32_ai_help_response_cache_up.sql +22 -0
  224. package/database/migrations/20260409_d33_ai_learning_tables_down.sql +4 -0
  225. package/database/migrations/20260409_d33_ai_learning_tables_up.sql +52 -0
  226. package/database/migrations/20260410_d34_command_config_enrichment_down.sql +3 -0
  227. package/database/migrations/20260410_d34_command_config_enrichment_up.sql +48 -0
  228. package/database/schema.sql +1186 -0
  229. package/docker-compose.yml +104 -0
  230. package/docs/audits/stickerCatalogController-out-of-scope.md +103 -0
  231. package/docs/audits/stickerCatalogController-symbols.md +58 -0
  232. package/docs/compliance/acceptable-use-policy-2026-03-07.md +35 -0
  233. package/docs/compliance/dpa-b2b-standard-2026-03-07.md +80 -0
  234. package/docs/compliance/monthly-compliance-checklist-2026-03-07.md +88 -0
  235. package/docs/compliance/notice-and-takedown-policy-2026-03-07.md +34 -0
  236. package/docs/compliance/privacy-policy-2026-03-07.md +75 -0
  237. package/docs/compliance/subprocessors-inventory-2026-03-07.md +16 -0
  238. package/docs/database/production-db-evolution-runbook-2026q1.md +365 -0
  239. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +86 -0
  240. package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +77 -0
  241. package/docs/security/network-hardening-runbook-2026-03-07.md +137 -0
  242. package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +238 -0
  243. package/docs/seo/satellite-page-template.md +116 -0
  244. package/docs/seo/satellite-pages-phase1.json +364 -0
  245. package/docs/wiki/Home.md +120 -0
  246. package/docs/wiki/pair-extraordinaire-2026-03-08.md +3 -0
  247. package/docs/wiki/recent-changes-2026-03-08.md +47 -0
  248. package/ecosystem.prod.config.cjs +135 -0
  249. package/eslint.config.js +89 -0
  250. package/index.js +488 -0
  251. package/ml/clip_classifier/Dockerfile +18 -0
  252. package/ml/clip_classifier/README.md +118 -0
  253. package/ml/clip_classifier/adaptive_scoring.py +40 -0
  254. package/ml/clip_classifier/classifier.py +654 -0
  255. package/ml/clip_classifier/embedding_store.py +481 -0
  256. package/ml/clip_classifier/env_loader.py +15 -0
  257. package/ml/clip_classifier/llm_label_expander.py +144 -0
  258. package/ml/clip_classifier/main.py +213 -0
  259. package/ml/clip_classifier/requirements.txt +10 -0
  260. package/ml/clip_classifier/similarity_engine.py +74 -0
  261. package/new-logo.png +0 -0
  262. package/observability/alert-rules.yml +60 -0
  263. package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
  264. package/observability/grafana/dashboards/omnizap-overview.json +170 -0
  265. package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
  266. package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
  267. package/observability/loki-config.yml +38 -0
  268. package/observability/mysql-setup.sql +46 -0
  269. package/observability/prometheus.yml +35 -0
  270. package/observability/promtail-config.yml +84 -0
  271. package/observability/sticker-catalog-slo.md +83 -0
  272. package/observability/sticker-scale-hardening-rollout.md +128 -0
  273. package/package.json +144 -0
  274. package/public/apple-touch-icon.png +0 -0
  275. package/public/assets/css/commands-react.input.css +71 -0
  276. package/public/assets/css/create-pack-react.input.css +31 -0
  277. package/public/assets/css/home-react.input.css +106 -0
  278. package/public/assets/css/login-react.input.css +58 -0
  279. package/public/assets/css/stickers-react.input.css +18 -0
  280. package/public/assets/css/terms-react.input.css +115 -0
  281. package/public/assets/css/user-react.input.css +57 -0
  282. package/public/assets/images/brand-icon-192.png +0 -0
  283. package/public/assets/images/brand-logo-128.webp +0 -0
  284. package/public/assets/images/hero-banner-1280.jpg +0 -0
  285. package/public/comandos/commands-catalog.json +4517 -0
  286. package/public/css/api-docs.css +161 -0
  287. package/public/css/stickers-admin.css +1288 -0
  288. package/public/css/styles.css +679 -0
  289. package/public/css/systemadm/admin.css +474 -0
  290. package/public/css/systemadm/base.css +73 -0
  291. package/public/css/systemadm/components.css +662 -0
  292. package/public/css/systemadm/layout.css +229 -0
  293. package/public/css/systemadm/tokens.css +56 -0
  294. package/public/favicon-16x16.png +0 -0
  295. package/public/favicon-32x32.png +0 -0
  296. package/public/favicon.ico +0 -0
  297. package/public/js/apps/apiDocsApp.js +235 -0
  298. package/public/js/apps/commandsReactApp.js +528 -0
  299. package/public/js/apps/createPackApp.js +1646 -0
  300. package/public/js/apps/homeReactApp.js +942 -0
  301. package/public/js/apps/loginReactApp.js +496 -0
  302. package/public/js/apps/stickersAdminApp.js +1753 -0
  303. package/public/js/apps/stickersApp.js +3797 -0
  304. package/public/js/apps/termsReactApp.js +528 -0
  305. package/public/js/apps/userApp.js +2540 -0
  306. package/public/js/apps/userProfile/actions.js +66 -0
  307. package/public/js/apps/userReactApp.js +547 -0
  308. package/public/js/catalog.js +950 -0
  309. package/public/pages/api-docs.html +40 -0
  310. package/public/pages/aup.html +158 -0
  311. package/public/pages/comandos.html +41 -0
  312. package/public/pages/dpa.html +227 -0
  313. package/public/pages/home.html +45 -0
  314. package/public/pages/licenca.html +182 -0
  315. package/public/pages/login.html +40 -0
  316. package/public/pages/notice-and-takedown.html +234 -0
  317. package/public/pages/politica-de-privacidade.html +251 -0
  318. package/public/pages/seo-bot-whatsapp-para-grupo.html +350 -0
  319. package/public/pages/seo-bot-whatsapp-sem-programar.html +350 -0
  320. package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +350 -0
  321. package/public/pages/seo-como-criar-comandos-whatsapp.html +350 -0
  322. package/public/pages/seo-como-evitar-spam-no-whatsapp.html +350 -0
  323. package/public/pages/seo-como-moderar-grupo-whatsapp.html +350 -0
  324. package/public/pages/seo-como-organizar-comunidade-whatsapp.html +350 -0
  325. package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +350 -0
  326. package/public/pages/stickers-admin.html +31 -0
  327. package/public/pages/stickers-create.html +41 -0
  328. package/public/pages/stickers.html +45 -0
  329. package/public/pages/suboperadores.html +237 -0
  330. package/public/pages/termos-de-uso-texto-integral.html +241 -0
  331. package/public/pages/termos-de-uso.html +41 -0
  332. package/public/pages/user-password-reset.html +32 -0
  333. package/public/pages/user-systemadm.html +508 -0
  334. package/public/pages/user.html +39 -0
  335. package/public/robots.txt +9 -0
  336. package/public/site.webmanifest +24 -0
  337. package/public/sitemap.xml +98 -0
  338. package/schemas/command-config.schema.json +582 -0
  339. package/scripts/baileys-compat-smoke.mjs +12 -0
  340. package/scripts/cache-bust.mjs +142 -0
  341. package/scripts/deploy.sh +916 -0
  342. package/scripts/email-broadcast-terms-update.mjs +170 -0
  343. package/scripts/enrich-command-discovery-fields.mjs +286 -0
  344. package/scripts/generate-command-config-schema.mjs +273 -0
  345. package/scripts/generate-commands-catalog.mjs +308 -0
  346. package/scripts/generate-module-agents.mjs +631 -0
  347. package/scripts/generate-seo-satellite-pages.mjs +400 -0
  348. package/scripts/github-deploy-notify.mjs +174 -0
  349. package/scripts/github-release-notify.mjs +219 -0
  350. package/scripts/release.sh +599 -0
  351. package/scripts/run-codeql-local.sh +116 -0
  352. package/scripts/run-prettier-all.mjs +25 -0
  353. package/scripts/security-smoketest.mjs +581 -0
  354. package/scripts/sticker-catalog-loadtest.mjs +210 -0
  355. package/scripts/sticker-worker-task.mjs +119 -0
  356. package/scripts/sync-readme-snapshot.mjs +133 -0
  357. package/scripts/validate-command-config-schema.mjs +130 -0
  358. package/scripts/validate-command-configs.mjs +15 -0
  359. package/scripts/wiki-sync.sh +191 -0
  360. package/server/auth/googleWebAuth/googleWebAuthRuntime.js +62 -0
  361. package/server/auth/googleWebAuth/googleWebAuthService.js +807 -0
  362. package/server/auth/jwt/webJwtService.js +147 -0
  363. package/server/auth/stickerCatalogAuthContext.js +165 -0
  364. package/server/auth/termsAcceptance/termsAcceptanceHandler.js +189 -0
  365. package/server/auth/userPassword/index.js +14 -0
  366. package/server/auth/userPassword/userPasswordAuthService.js +422 -0
  367. package/server/auth/userPassword/userPasswordCrypto.js +199 -0
  368. package/server/auth/userPassword/userPasswordCrypto.test.js +76 -0
  369. package/server/auth/userPassword/userPasswordRecoveryService.js +728 -0
  370. package/server/auth/validation/authSchemas.js +236 -0
  371. package/server/auth/webAccount/webAccountHandlers.js +1434 -0
  372. package/server/controllers/admin/adminBanService.js +138 -0
  373. package/server/controllers/admin/adminPanelHandlers.js +2083 -0
  374. package/server/controllers/admin/stickerCatalogAdminContext.js +17 -0
  375. package/server/controllers/admin/systemAdminController.js +201 -0
  376. package/server/controllers/email/emailAutomationController.js +239 -0
  377. package/server/controllers/metricsController.js +21 -0
  378. package/server/controllers/seo/stickerCatalogSeoContext.js +514 -0
  379. package/server/controllers/sticker/nonCatalogHandlers.js +303 -0
  380. package/server/controllers/sticker/stickerCatalogController.js +4700 -0
  381. package/server/controllers/system/contactController.js +115 -0
  382. package/server/controllers/system/githubController.js +137 -0
  383. package/server/controllers/system/stickerCatalogSystemContext.js +758 -0
  384. package/server/controllers/system/storageController.js +154 -0
  385. package/server/controllers/system/systemController.js +135 -0
  386. package/server/controllers/system/systemMetricsController.js +156 -0
  387. package/server/controllers/system/visitController.js +90 -0
  388. package/server/controllers/userController.js +145 -0
  389. package/server/email/emailAutomationRuntime.js +225 -0
  390. package/server/email/emailAutomationService.js +125 -0
  391. package/server/email/emailOutboxRepository.js +282 -0
  392. package/server/email/emailTemplateService.js +480 -0
  393. package/server/email/emailTransportService.js +156 -0
  394. package/server/http/clientIp.js +95 -0
  395. package/server/http/httpRequestUtils.js +262 -0
  396. package/server/http/httpRequestUtils.test.js +80 -0
  397. package/server/http/httpServer.js +180 -0
  398. package/server/http/requestContext.js +20 -0
  399. package/server/http/siteRoutingUtils.js +87 -0
  400. package/server/index.js +1 -0
  401. package/server/middleware/cachePolicy.js +26 -0
  402. package/server/middleware/cachePolicyHelpers.js +1 -0
  403. package/server/middleware/endpointRateLimit.js +181 -0
  404. package/server/middleware/rateLimit.js +70 -0
  405. package/server/middleware/requireAdminAuth.js +48 -0
  406. package/server/middleware/securityHeaders.js +97 -0
  407. package/server/routes/admin/systemAdminRouter.js +64 -0
  408. package/server/routes/email/emailAutomationRouter.js +46 -0
  409. package/server/routes/health/healthRouter.js +41 -0
  410. package/server/routes/indexRouter.js +234 -0
  411. package/server/routes/metrics/metricsRouter.js +58 -0
  412. package/server/routes/static/staticPageRouter.js +134 -0
  413. package/server/routes/sticker/catalogHandlers/catalogAdminHttp.js +105 -0
  414. package/server/routes/sticker/catalogHandlers/catalogAuthHttp.js +77 -0
  415. package/server/routes/sticker/catalogHandlers/catalogPublicHttp.js +120 -0
  416. package/server/routes/sticker/catalogHandlers/catalogUploadHttp.js +83 -0
  417. package/server/routes/sticker/catalogRouter.js +77 -0
  418. package/server/routes/sticker/stickerApiRouter.js +84 -0
  419. package/server/routes/sticker/stickerDataRouter.js +145 -0
  420. package/server/routes/sticker/stickerSiteRouter.js +43 -0
  421. package/server/routes/user/userApiPaths.js +66 -0
  422. package/server/routes/user/userRouter.js +65 -0
  423. package/server/utils/safePath.js +26 -0
  424. package/utils/logger/loggerModule.js +35 -0
  425. package/vite.config.mjs +38 -0
@@ -0,0 +1,769 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ import logger from '#logger';
4
+ import { executeQuery, TABLES } from '../../../database/index.js';
5
+
6
+ const MAX_MODULE_KEY_LENGTH = 64;
7
+ const MAX_COMMAND_NAME_LENGTH = 64;
8
+ const MAX_TOOL_NAME_LENGTH = 64;
9
+ const MAX_MODEL_NAME_LENGTH = 80;
10
+ const MAX_SOURCE_LENGTH = 32;
11
+ const MAX_STATUS_LENGTH = 16;
12
+ const MAX_QUESTION_LENGTH = 512;
13
+ const MAX_KEYWORD_LENGTH = 80;
14
+ const MAX_PATTERN_LENGTH = 220;
15
+ const MAX_DESCRIPTION_LENGTH = 420;
16
+ const MAX_METHOD_LENGTH = 220;
17
+ const MAX_REVIEW_NOTES_LENGTH = 255;
18
+ const DEFAULT_EVENT_LIMIT = 50;
19
+ const MAX_EVENT_LIMIT = 300;
20
+ const DEFAULT_STATE_LIMIT = 10_000;
21
+ const MAX_STATE_LIMIT = 50_000;
22
+
23
+ const ALLOWED_SUGGESTION_STATUS = new Set(['pending', 'applied', 'rejected']);
24
+
25
+ const SUGGESTION_FIELDS = ['capability_keywords', 'faq_patterns', 'user_phrasings', 'metodos_de_uso_sugeridos', 'descricao_sugerida'];
26
+
27
+ let tableAvailabilityState = {
28
+ available: true,
29
+ unavailableLogged: false,
30
+ };
31
+
32
+ const normalizeText = (value) =>
33
+ String(value || '')
34
+ .trim()
35
+ .toLowerCase()
36
+ .normalize('NFD')
37
+ .replace(/[\u0300-\u036f]/g, '')
38
+ .replace(/[^a-z0-9\s/_.-]/g, ' ')
39
+ .replace(/\s+/g, ' ')
40
+ .trim();
41
+
42
+ const normalizeDisplayText = (value) =>
43
+ String(value || '')
44
+ .trim()
45
+ .replace(/\s+/g, ' ');
46
+
47
+ const normalizeModuleKey = (value) =>
48
+ normalizeText(value)
49
+ .replace(/[^a-z0-9_-]/g, '')
50
+ .slice(0, MAX_MODULE_KEY_LENGTH);
51
+
52
+ const normalizeCommandName = (value) =>
53
+ normalizeText(value)
54
+ .replace(/[^a-z0-9_-]/g, '')
55
+ .slice(0, MAX_COMMAND_NAME_LENGTH);
56
+
57
+ const normalizeToolName = (value) =>
58
+ normalizeText(value)
59
+ .replace(/[^a-z0-9_-]/g, '')
60
+ .slice(0, MAX_TOOL_NAME_LENGTH);
61
+
62
+ const normalizeSource = (value) =>
63
+ normalizeText(value)
64
+ .replace(/[^a-z0-9_-]/g, '')
65
+ .slice(0, MAX_SOURCE_LENGTH) || 'llm';
66
+
67
+ const sanitizeShortText = (value, maxLength) => {
68
+ const safe = normalizeDisplayText(value).slice(0, maxLength);
69
+ return safe || null;
70
+ };
71
+
72
+ const clamp01 = (value) => Math.max(0, Math.min(1, Number(value) || 0));
73
+
74
+ const normalizeListLimit = (value, fallback, maxLimit) => {
75
+ const parsed = Number.parseInt(String(value ?? ''), 10);
76
+ if (!Number.isFinite(parsed)) return fallback;
77
+ return Math.max(1, Math.min(maxLimit, parsed));
78
+ };
79
+
80
+ const normalizeStatus = (value) => {
81
+ const normalized = normalizeText(value)
82
+ .replace(/[^a-z]/g, '')
83
+ .slice(0, MAX_STATUS_LENGTH);
84
+ if (ALLOWED_SUGGESTION_STATUS.has(normalized)) return normalized;
85
+ return 'pending';
86
+ };
87
+
88
+ const parseJsonSafe = (value) => {
89
+ if (!value) return null;
90
+ if (typeof value === 'object') return value;
91
+ try {
92
+ return JSON.parse(String(value));
93
+ } catch {
94
+ return null;
95
+ }
96
+ };
97
+
98
+ const serializeJsonSafe = (value) => {
99
+ if (!value || typeof value !== 'object') return null;
100
+ try {
101
+ return JSON.stringify(value);
102
+ } catch {
103
+ return null;
104
+ }
105
+ };
106
+
107
+ const uniqueNormalizedList = (items = [], { maxItems = 12, maxLength = 120, kind = 'text' } = {}) => {
108
+ const source = Array.isArray(items) ? items : [];
109
+ const output = [];
110
+ const seen = new Set();
111
+
112
+ for (const rawItem of source) {
113
+ const display = normalizeDisplayText(rawItem);
114
+ if (!display) continue;
115
+
116
+ const normalized = kind === 'keyword' ? normalizeText(display).slice(0, maxLength) : display.slice(0, maxLength);
117
+ if (!normalized) continue;
118
+
119
+ const dedupKey = normalizeText(normalized);
120
+ if (!dedupKey || seen.has(dedupKey)) continue;
121
+ seen.add(dedupKey);
122
+ output.push(normalized);
123
+ if (output.length >= maxItems) break;
124
+ }
125
+
126
+ return output;
127
+ };
128
+
129
+ const normalizeSuggestionPayload = (payload) => {
130
+ const safePayload = payload && typeof payload === 'object' ? payload : {};
131
+
132
+ const capabilityKeywords = uniqueNormalizedList(safePayload.capability_keywords, {
133
+ maxItems: 24,
134
+ maxLength: MAX_KEYWORD_LENGTH,
135
+ kind: 'keyword',
136
+ });
137
+ const faqPatterns = uniqueNormalizedList(safePayload.faq_patterns, {
138
+ maxItems: 24,
139
+ maxLength: MAX_PATTERN_LENGTH,
140
+ });
141
+ const userPhrasings = uniqueNormalizedList(safePayload.user_phrasings, {
142
+ maxItems: 28,
143
+ maxLength: MAX_PATTERN_LENGTH,
144
+ });
145
+ const methodHints = uniqueNormalizedList(safePayload.metodos_de_uso_sugeridos, {
146
+ maxItems: 16,
147
+ maxLength: MAX_METHOD_LENGTH,
148
+ });
149
+ const descricaoSugerida = sanitizeShortText(safePayload.descricao_sugerida, MAX_DESCRIPTION_LENGTH);
150
+
151
+ return {
152
+ capability_keywords: capabilityKeywords,
153
+ faq_patterns: faqPatterns,
154
+ user_phrasings: userPhrasings,
155
+ metodos_de_uso_sugeridos: methodHints,
156
+ descricao_sugerida: descricaoSugerida,
157
+ };
158
+ };
159
+
160
+ const hasMeaningfulSuggestion = (payload) => {
161
+ if (!payload || typeof payload !== 'object') return false;
162
+ if (sanitizeShortText(payload.descricao_sugerida, MAX_DESCRIPTION_LENGTH)) return true;
163
+ for (const field of SUGGESTION_FIELDS) {
164
+ if (!Array.isArray(payload[field])) continue;
165
+ if (payload[field].length > 0) return true;
166
+ }
167
+ return false;
168
+ };
169
+
170
+ const buildSuggestionHash = ({ moduleKey, commandName, payload }) => {
171
+ const canonicalJson = JSON.stringify(normalizeSuggestionPayload(payload));
172
+ return createHash('sha256')
173
+ .update(`${normalizeModuleKey(moduleKey)}:${normalizeCommandName(commandName)}:${canonicalJson}`)
174
+ .digest('hex');
175
+ };
176
+
177
+ const handleTableError = (error, action, context = {}) => {
178
+ const errorCode = error?.code || error?.errorCode || error?.originalError?.code || null;
179
+ if (errorCode !== 'ER_NO_SUCH_TABLE') return false;
180
+
181
+ const shouldLog = !tableAvailabilityState.unavailableLogged;
182
+ tableAvailabilityState = {
183
+ available: false,
184
+ unavailableLogged: true,
185
+ };
186
+
187
+ if (shouldLog) {
188
+ logger.warn('Tabelas de enriquecimento de commandConfig indisponiveis.', {
189
+ action,
190
+ context,
191
+ errorCode,
192
+ });
193
+ }
194
+
195
+ return true;
196
+ };
197
+
198
+ const normalizeLearningEventRow = (row = {}) => ({
199
+ id: Number(row?.id || 0),
200
+ user_question: sanitizeShortText(row?.user_question, MAX_QUESTION_LENGTH) || '',
201
+ normalized_question: sanitizeShortText(row?.normalized_question, MAX_QUESTION_LENGTH) || '',
202
+ tool_suggested: normalizeToolName(row?.tool_suggested),
203
+ tool_executed: normalizeToolName(row?.tool_executed),
204
+ success: row?.success === 1 || row?.success === true,
205
+ confidence: clamp01(row?.confidence),
206
+ created_at: row?.created_at || null,
207
+ });
208
+
209
+ const normalizeSuggestionRow = (row = {}) => {
210
+ const parsedSuggestion = normalizeSuggestionPayload(parseJsonSafe(row?.suggestion_json));
211
+ return {
212
+ id: Number(row?.id || 0),
213
+ module_key: normalizeModuleKey(row?.module_key),
214
+ command_name: normalizeCommandName(row?.command_name),
215
+ source_tool: normalizeToolName(row?.source_tool),
216
+ source_event_id: Number(row?.source_event_id || 0),
217
+ user_question: sanitizeShortText(row?.user_question, MAX_QUESTION_LENGTH),
218
+ normalized_question: sanitizeShortText(row?.normalized_question, MAX_QUESTION_LENGTH),
219
+ suggestion: parsedSuggestion,
220
+ confidence: clamp01(row?.confidence),
221
+ model_name: sanitizeShortText(row?.model_name, MAX_MODEL_NAME_LENGTH),
222
+ source: normalizeSource(row?.source),
223
+ status: normalizeStatus(row?.status),
224
+ suggestion_hash: sanitizeShortText(row?.suggestion_hash, 64),
225
+ review_notes: sanitizeShortText(row?.review_notes, MAX_REVIEW_NOTES_LENGTH),
226
+ created_at: row?.created_at || null,
227
+ updated_at: row?.updated_at || null,
228
+ applied_at: row?.applied_at || null,
229
+ };
230
+ };
231
+
232
+ const normalizeStateRow = (row = {}) => ({
233
+ id: Number(row?.id || 0),
234
+ module_key: normalizeModuleKey(row?.module_key),
235
+ command_name: normalizeCommandName(row?.command_name),
236
+ overlay: normalizeSuggestionPayload(parseJsonSafe(row?.overlay_json)),
237
+ version: Math.max(0, Number(row?.version || 0)),
238
+ confidence: clamp01(row?.confidence),
239
+ last_suggestion_id: Number(row?.last_suggestion_id || 0),
240
+ updated_at: row?.updated_at || null,
241
+ created_at: row?.created_at || null,
242
+ });
243
+
244
+ const normalizeCursorValue = (value) => {
245
+ const parsed = Number.parseInt(String(value ?? ''), 10);
246
+ if (!Number.isFinite(parsed)) return 0;
247
+ return Math.max(0, parsed);
248
+ };
249
+
250
+ const suggestionsPayloadEquals = (left, right) => JSON.stringify(normalizeSuggestionPayload(left)) === JSON.stringify(normalizeSuggestionPayload(right));
251
+
252
+ export const isCommandConfigEnrichmentTableAvailable = () => tableAvailabilityState.available;
253
+
254
+ export async function getCommandConfigEnrichmentCursor() {
255
+ if (!isCommandConfigEnrichmentTableAvailable()) return 0;
256
+
257
+ try {
258
+ const rows = await executeQuery(
259
+ `SELECT last_learning_event_id
260
+ FROM ${TABLES.AI_COMMAND_CONFIG_ENRICHMENT_CURSOR}
261
+ WHERE id = 1
262
+ LIMIT 1`,
263
+ [],
264
+ );
265
+ const row = Array.isArray(rows) ? rows[0] : null;
266
+ return normalizeCursorValue(row?.last_learning_event_id);
267
+ } catch (error) {
268
+ if (handleTableError(error, 'command_config_enrichment_cursor_get')) return 0;
269
+
270
+ logger.warn('Falha ao ler cursor de enriquecimento de commandConfig.', {
271
+ action: 'command_config_enrichment_cursor_get_failed',
272
+ error: error?.message,
273
+ });
274
+ return 0;
275
+ }
276
+ }
277
+
278
+ export async function updateCommandConfigEnrichmentCursor(lastLearningEventId) {
279
+ if (!isCommandConfigEnrichmentTableAvailable()) return 0;
280
+
281
+ const safeCursor = normalizeCursorValue(lastLearningEventId);
282
+ try {
283
+ await executeQuery(
284
+ `INSERT INTO ${TABLES.AI_COMMAND_CONFIG_ENRICHMENT_CURSOR}
285
+ (id, last_learning_event_id)
286
+ VALUES (1, ?)
287
+ ON DUPLICATE KEY UPDATE
288
+ last_learning_event_id = GREATEST(last_learning_event_id, VALUES(last_learning_event_id)),
289
+ updated_at = CURRENT_TIMESTAMP()`,
290
+ [safeCursor],
291
+ );
292
+ return safeCursor;
293
+ } catch (error) {
294
+ if (
295
+ handleTableError(error, 'command_config_enrichment_cursor_update', {
296
+ cursor: safeCursor,
297
+ })
298
+ ) {
299
+ return 0;
300
+ }
301
+
302
+ logger.warn('Falha ao atualizar cursor de enriquecimento de commandConfig.', {
303
+ action: 'command_config_enrichment_cursor_update_failed',
304
+ cursor: safeCursor,
305
+ error: error?.message,
306
+ });
307
+ return 0;
308
+ }
309
+ }
310
+
311
+ export async function listLearningEventsForCommandConfigEnrichment({ afterId = 0, limit = DEFAULT_EVENT_LIMIT } = {}) {
312
+ if (!isCommandConfigEnrichmentTableAvailable()) return [];
313
+
314
+ const safeAfterId = normalizeCursorValue(afterId);
315
+ const safeLimit = normalizeListLimit(limit, DEFAULT_EVENT_LIMIT, MAX_EVENT_LIMIT);
316
+ try {
317
+ const rows = await executeQuery(
318
+ `SELECT id, user_question, normalized_question, tool_suggested, tool_executed,
319
+ success, confidence, created_at
320
+ FROM ${TABLES.AI_LEARNING_EVENTS}
321
+ WHERE id > ?
322
+ ORDER BY id ASC
323
+ LIMIT ?`,
324
+ [safeAfterId, safeLimit],
325
+ );
326
+
327
+ if (!Array.isArray(rows) || rows.length === 0) return [];
328
+ return rows.map((row) => normalizeLearningEventRow(row)).filter((row) => row.id > 0);
329
+ } catch (error) {
330
+ if (
331
+ handleTableError(error, 'command_config_enrichment_learning_events_list', {
332
+ afterId: safeAfterId,
333
+ limit: safeLimit,
334
+ })
335
+ ) {
336
+ return [];
337
+ }
338
+
339
+ logger.warn('Falha ao listar eventos de aprendizado para enriquecimento de commandConfig.', {
340
+ action: 'command_config_enrichment_learning_events_list_failed',
341
+ afterId: safeAfterId,
342
+ limit: safeLimit,
343
+ error: error?.message,
344
+ });
345
+ return [];
346
+ }
347
+ }
348
+
349
+ export async function saveCommandConfigEnrichmentSuggestion({ moduleKey, commandName, sourceTool = null, sourceEventId = null, question = null, normalizedQuestion = null, suggestion = {}, confidence = 0.5, modelName = null, source = 'llm', status = 'pending' } = {}) {
350
+ if (!isCommandConfigEnrichmentTableAvailable()) return null;
351
+
352
+ const safeModuleKey = normalizeModuleKey(moduleKey);
353
+ const safeCommandName = normalizeCommandName(commandName);
354
+ const safeSourceTool = normalizeToolName(sourceTool);
355
+ const safeSourceEventId = Number.parseInt(String(sourceEventId ?? ''), 10);
356
+ const safeQuestion = sanitizeShortText(question, MAX_QUESTION_LENGTH);
357
+ const safeNormalizedQuestion = sanitizeShortText(normalizedQuestion, MAX_QUESTION_LENGTH);
358
+ const safeSuggestion = normalizeSuggestionPayload(suggestion);
359
+ const safeConfidence = clamp01(confidence);
360
+ const safeModelName = sanitizeShortText(modelName, MAX_MODEL_NAME_LENGTH);
361
+ const safeSource = normalizeSource(source);
362
+ const safeStatus = normalizeStatus(status);
363
+
364
+ if (!safeModuleKey || !safeCommandName || !hasMeaningfulSuggestion(safeSuggestion)) {
365
+ return null;
366
+ }
367
+
368
+ const suggestionJson = serializeJsonSafe(safeSuggestion);
369
+ if (!suggestionJson) return null;
370
+
371
+ const suggestionHash = buildSuggestionHash({
372
+ moduleKey: safeModuleKey,
373
+ commandName: safeCommandName,
374
+ payload: safeSuggestion,
375
+ });
376
+
377
+ try {
378
+ await executeQuery(
379
+ `INSERT INTO ${TABLES.AI_COMMAND_CONFIG_ENRICHMENT_SUGGESTION}
380
+ (module_key, command_name, source_tool, source_event_id, user_question, normalized_question,
381
+ suggestion_json, confidence, model_name, source, status, suggestion_hash)
382
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
383
+ ON DUPLICATE KEY UPDATE
384
+ confidence = GREATEST(confidence, VALUES(confidence)),
385
+ status = CASE
386
+ WHEN status = 'applied' THEN 'applied'
387
+ WHEN status = 'rejected' AND VALUES(status) = 'pending' THEN 'rejected'
388
+ ELSE VALUES(status)
389
+ END,
390
+ updated_at = CURRENT_TIMESTAMP(),
391
+ id = LAST_INSERT_ID(id)`,
392
+ [safeModuleKey, safeCommandName, safeSourceTool || null, Number.isFinite(safeSourceEventId) && safeSourceEventId > 0 ? safeSourceEventId : null, safeQuestion, safeNormalizedQuestion, suggestionJson, Number(safeConfidence.toFixed(4)), safeModelName, safeSource, safeStatus, suggestionHash],
393
+ );
394
+
395
+ const rows = await executeQuery(
396
+ `SELECT id, module_key, command_name, source_tool, source_event_id, user_question, normalized_question,
397
+ suggestion_json, confidence, model_name, source, status, suggestion_hash, review_notes,
398
+ created_at, updated_at, applied_at
399
+ FROM ${TABLES.AI_COMMAND_CONFIG_ENRICHMENT_SUGGESTION}
400
+ WHERE suggestion_hash = ?
401
+ LIMIT 1`,
402
+ [suggestionHash],
403
+ );
404
+
405
+ const row = Array.isArray(rows) ? rows[0] : null;
406
+ return row ? normalizeSuggestionRow(row) : null;
407
+ } catch (error) {
408
+ if (
409
+ handleTableError(error, 'command_config_enrichment_suggestion_upsert', {
410
+ moduleKey: safeModuleKey,
411
+ commandName: safeCommandName,
412
+ })
413
+ ) {
414
+ return null;
415
+ }
416
+
417
+ logger.warn('Falha ao salvar sugestao de enriquecimento de commandConfig.', {
418
+ action: 'command_config_enrichment_suggestion_upsert_failed',
419
+ moduleKey: safeModuleKey,
420
+ commandName: safeCommandName,
421
+ error: error?.message,
422
+ });
423
+ return null;
424
+ }
425
+ }
426
+
427
+ export const mergeCommandConfigEnrichmentPayload = (basePayload = {}, patchPayload = {}) => {
428
+ const base = normalizeSuggestionPayload(basePayload);
429
+ const patch = normalizeSuggestionPayload(patchPayload);
430
+
431
+ const merged = {
432
+ capability_keywords: uniqueNormalizedList([...base.capability_keywords, ...patch.capability_keywords], { maxItems: 28, maxLength: MAX_KEYWORD_LENGTH, kind: 'keyword' }),
433
+ faq_patterns: uniqueNormalizedList([...base.faq_patterns, ...patch.faq_patterns], {
434
+ maxItems: 30,
435
+ maxLength: MAX_PATTERN_LENGTH,
436
+ }),
437
+ user_phrasings: uniqueNormalizedList([...base.user_phrasings, ...patch.user_phrasings], {
438
+ maxItems: 36,
439
+ maxLength: MAX_PATTERN_LENGTH,
440
+ }),
441
+ metodos_de_uso_sugeridos: uniqueNormalizedList([...base.metodos_de_uso_sugeridos, ...patch.metodos_de_uso_sugeridos], { maxItems: 18, maxLength: MAX_METHOD_LENGTH }),
442
+ descricao_sugerida: sanitizeShortText(base.descricao_sugerida, MAX_DESCRIPTION_LENGTH) || sanitizeShortText(patch.descricao_sugerida, MAX_DESCRIPTION_LENGTH) || null,
443
+ };
444
+
445
+ return normalizeSuggestionPayload(merged);
446
+ };
447
+
448
+ const getSuggestionById = async (suggestionId) => {
449
+ const safeId = Number.parseInt(String(suggestionId ?? ''), 10);
450
+ if (!Number.isFinite(safeId) || safeId <= 0) return null;
451
+
452
+ const rows = await executeQuery(
453
+ `SELECT id, module_key, command_name, source_tool, source_event_id, user_question, normalized_question,
454
+ suggestion_json, confidence, model_name, source, status, suggestion_hash, review_notes,
455
+ created_at, updated_at, applied_at
456
+ FROM ${TABLES.AI_COMMAND_CONFIG_ENRICHMENT_SUGGESTION}
457
+ WHERE id = ?
458
+ LIMIT 1`,
459
+ [safeId],
460
+ );
461
+
462
+ const row = Array.isArray(rows) ? rows[0] : null;
463
+ return row ? normalizeSuggestionRow(row) : null;
464
+ };
465
+
466
+ const upsertCommandConfigEnrichmentState = async ({ moduleKey, commandName, overlayPatch = {}, confidence = 0.5, lastSuggestionId = null } = {}) => {
467
+ const safeModuleKey = normalizeModuleKey(moduleKey);
468
+ const safeCommandName = normalizeCommandName(commandName);
469
+ const safeConfidence = clamp01(confidence);
470
+ const safeSuggestionId = Number.parseInt(String(lastSuggestionId ?? ''), 10);
471
+ const patch = normalizeSuggestionPayload(overlayPatch);
472
+ if (!safeModuleKey || !safeCommandName || !hasMeaningfulSuggestion(patch)) {
473
+ return {
474
+ changed: false,
475
+ version: 0,
476
+ state: null,
477
+ };
478
+ }
479
+
480
+ const existingRows = await executeQuery(
481
+ `SELECT id, module_key, command_name, overlay_json, version, confidence, last_suggestion_id,
482
+ updated_at, created_at
483
+ FROM ${TABLES.AI_COMMAND_CONFIG_ENRICHMENT_STATE}
484
+ WHERE module_key = ? AND command_name = ?
485
+ LIMIT 1`,
486
+ [safeModuleKey, safeCommandName],
487
+ );
488
+ const existingRow = Array.isArray(existingRows) ? existingRows[0] : null;
489
+ const existing = existingRow ? normalizeStateRow(existingRow) : null;
490
+
491
+ const mergedOverlay = mergeCommandConfigEnrichmentPayload(existing?.overlay || {}, patch);
492
+ const changed = !suggestionsPayloadEquals(existing?.overlay || {}, mergedOverlay);
493
+ const mergedJson = serializeJsonSafe(mergedOverlay);
494
+ if (!mergedJson) {
495
+ return {
496
+ changed: false,
497
+ version: existing?.version || 0,
498
+ state: existing,
499
+ };
500
+ }
501
+
502
+ if (!existing) {
503
+ await executeQuery(
504
+ `INSERT INTO ${TABLES.AI_COMMAND_CONFIG_ENRICHMENT_STATE}
505
+ (module_key, command_name, overlay_json, version, confidence, last_suggestion_id)
506
+ VALUES (?, ?, ?, 1, ?, ?)`,
507
+ [safeModuleKey, safeCommandName, mergedJson, Number(safeConfidence.toFixed(4)), Number.isFinite(safeSuggestionId) && safeSuggestionId > 0 ? safeSuggestionId : null],
508
+ );
509
+ } else if (changed) {
510
+ await executeQuery(
511
+ `UPDATE ${TABLES.AI_COMMAND_CONFIG_ENRICHMENT_STATE}
512
+ SET overlay_json = ?,
513
+ version = version + 1,
514
+ confidence = GREATEST(confidence, ?),
515
+ last_suggestion_id = ?,
516
+ updated_at = CURRENT_TIMESTAMP()
517
+ WHERE id = ?`,
518
+ [mergedJson, Number(safeConfidence.toFixed(4)), Number.isFinite(safeSuggestionId) && safeSuggestionId > 0 ? safeSuggestionId : null, existing.id],
519
+ );
520
+ } else {
521
+ await executeQuery(
522
+ `UPDATE ${TABLES.AI_COMMAND_CONFIG_ENRICHMENT_STATE}
523
+ SET confidence = GREATEST(confidence, ?),
524
+ last_suggestion_id = ?,
525
+ updated_at = CURRENT_TIMESTAMP()
526
+ WHERE id = ?`,
527
+ [Number(safeConfidence.toFixed(4)), Number.isFinite(safeSuggestionId) && safeSuggestionId > 0 ? safeSuggestionId : null, existing.id],
528
+ );
529
+ }
530
+
531
+ const rows = await executeQuery(
532
+ `SELECT id, module_key, command_name, overlay_json, version, confidence, last_suggestion_id,
533
+ updated_at, created_at
534
+ FROM ${TABLES.AI_COMMAND_CONFIG_ENRICHMENT_STATE}
535
+ WHERE module_key = ? AND command_name = ?
536
+ LIMIT 1`,
537
+ [safeModuleKey, safeCommandName],
538
+ );
539
+ const row = Array.isArray(rows) ? rows[0] : null;
540
+ const state = row ? normalizeStateRow(row) : null;
541
+
542
+ return {
543
+ changed,
544
+ version: state?.version || 0,
545
+ state,
546
+ };
547
+ };
548
+
549
+ export async function markCommandConfigEnrichmentSuggestionStatus({ suggestionId, status, reviewNotes = null } = {}) {
550
+ if (!isCommandConfigEnrichmentTableAvailable()) return false;
551
+
552
+ const safeId = Number.parseInt(String(suggestionId ?? ''), 10);
553
+ const safeStatus = normalizeStatus(status);
554
+ const safeReviewNotes = sanitizeShortText(reviewNotes, MAX_REVIEW_NOTES_LENGTH);
555
+ if (!Number.isFinite(safeId) || safeId <= 0) return false;
556
+
557
+ try {
558
+ const setAppliedAt = safeStatus === 'applied' ? 'applied_at = CURRENT_TIMESTAMP(),' : '';
559
+ const result = await executeQuery(
560
+ `UPDATE ${TABLES.AI_COMMAND_CONFIG_ENRICHMENT_SUGGESTION}
561
+ SET status = ?,
562
+ ${setAppliedAt}
563
+ review_notes = ?,
564
+ updated_at = CURRENT_TIMESTAMP()
565
+ WHERE id = ?`,
566
+ [safeStatus, safeReviewNotes, safeId],
567
+ );
568
+ return Number(result?.affectedRows || 0) > 0;
569
+ } catch (error) {
570
+ if (
571
+ handleTableError(error, 'command_config_enrichment_suggestion_mark_status', {
572
+ suggestionId: safeId,
573
+ status: safeStatus,
574
+ })
575
+ ) {
576
+ return false;
577
+ }
578
+
579
+ logger.warn('Falha ao atualizar status da sugestao de enriquecimento de commandConfig.', {
580
+ action: 'command_config_enrichment_suggestion_mark_status_failed',
581
+ suggestionId: safeId,
582
+ status: safeStatus,
583
+ error: error?.message,
584
+ });
585
+ return false;
586
+ }
587
+ }
588
+
589
+ export async function applyCommandConfigEnrichmentSuggestion({ suggestionId, reviewNotes = null } = {}) {
590
+ if (!isCommandConfigEnrichmentTableAvailable()) {
591
+ return {
592
+ applied: false,
593
+ changed: false,
594
+ version: 0,
595
+ state: null,
596
+ suggestion: null,
597
+ };
598
+ }
599
+
600
+ const safeSuggestionId = Number.parseInt(String(suggestionId ?? ''), 10);
601
+ if (!Number.isFinite(safeSuggestionId) || safeSuggestionId <= 0) {
602
+ return {
603
+ applied: false,
604
+ changed: false,
605
+ version: 0,
606
+ state: null,
607
+ suggestion: null,
608
+ };
609
+ }
610
+
611
+ try {
612
+ const suggestion = await getSuggestionById(safeSuggestionId);
613
+ if (!suggestion || !hasMeaningfulSuggestion(suggestion.suggestion)) {
614
+ return {
615
+ applied: false,
616
+ changed: false,
617
+ version: 0,
618
+ state: null,
619
+ suggestion: suggestion || null,
620
+ };
621
+ }
622
+ if (suggestion.status === 'rejected') {
623
+ return {
624
+ applied: false,
625
+ changed: false,
626
+ version: 0,
627
+ state: null,
628
+ suggestion,
629
+ };
630
+ }
631
+
632
+ const upsertResult = await upsertCommandConfigEnrichmentState({
633
+ moduleKey: suggestion.module_key,
634
+ commandName: suggestion.command_name,
635
+ overlayPatch: suggestion.suggestion,
636
+ confidence: suggestion.confidence,
637
+ lastSuggestionId: suggestion.id,
638
+ });
639
+
640
+ await markCommandConfigEnrichmentSuggestionStatus({
641
+ suggestionId: suggestion.id,
642
+ status: 'applied',
643
+ reviewNotes,
644
+ });
645
+
646
+ return {
647
+ applied: true,
648
+ changed: Boolean(upsertResult.changed),
649
+ version: Number(upsertResult.version || 0),
650
+ state: upsertResult.state || null,
651
+ suggestion,
652
+ };
653
+ } catch (error) {
654
+ if (
655
+ handleTableError(error, 'command_config_enrichment_suggestion_apply', {
656
+ suggestionId: safeSuggestionId,
657
+ })
658
+ ) {
659
+ return {
660
+ applied: false,
661
+ changed: false,
662
+ version: 0,
663
+ state: null,
664
+ suggestion: null,
665
+ };
666
+ }
667
+
668
+ logger.warn('Falha ao aplicar sugestao de enriquecimento de commandConfig.', {
669
+ action: 'command_config_enrichment_suggestion_apply_failed',
670
+ suggestionId: safeSuggestionId,
671
+ error: error?.message,
672
+ });
673
+ return {
674
+ applied: false,
675
+ changed: false,
676
+ version: 0,
677
+ state: null,
678
+ suggestion: null,
679
+ };
680
+ }
681
+ }
682
+
683
+ export async function listAppliedCommandConfigEnrichmentStates({ limit = DEFAULT_STATE_LIMIT } = {}) {
684
+ if (!isCommandConfigEnrichmentTableAvailable()) return [];
685
+
686
+ const safeLimit = normalizeListLimit(limit, DEFAULT_STATE_LIMIT, MAX_STATE_LIMIT);
687
+ try {
688
+ const rows = await executeQuery(
689
+ `SELECT id, module_key, command_name, overlay_json, version, confidence, last_suggestion_id,
690
+ updated_at, created_at
691
+ FROM ${TABLES.AI_COMMAND_CONFIG_ENRICHMENT_STATE}
692
+ ORDER BY updated_at DESC, id DESC
693
+ LIMIT ?`,
694
+ [safeLimit],
695
+ );
696
+ if (!Array.isArray(rows) || rows.length === 0) return [];
697
+ return rows.map((row) => normalizeStateRow(row)).filter((row) => row.id > 0 && row.module_key && row.command_name);
698
+ } catch (error) {
699
+ if (handleTableError(error, 'command_config_enrichment_state_list', { limit: safeLimit })) {
700
+ return [];
701
+ }
702
+
703
+ logger.warn('Falha ao listar estado aplicado de enriquecimento de commandConfig.', {
704
+ action: 'command_config_enrichment_state_list_failed',
705
+ limit: safeLimit,
706
+ error: error?.message,
707
+ });
708
+ return [];
709
+ }
710
+ }
711
+
712
+ export async function getCommandConfigEnrichmentVersion() {
713
+ if (!isCommandConfigEnrichmentTableAvailable()) {
714
+ return {
715
+ maxStateId: 0,
716
+ maxVersion: 0,
717
+ totalRows: 0,
718
+ version: '0:0:0',
719
+ };
720
+ }
721
+
722
+ try {
723
+ const rows = await executeQuery(
724
+ `SELECT
725
+ COALESCE(MAX(id), 0) AS max_state_id,
726
+ COALESCE(MAX(version), 0) AS max_version,
727
+ COALESCE(COUNT(*), 0) AS total_rows
728
+ FROM ${TABLES.AI_COMMAND_CONFIG_ENRICHMENT_STATE}`,
729
+ [],
730
+ );
731
+ const row = Array.isArray(rows) ? rows[0] : {};
732
+ const maxStateId = Number(row?.max_state_id || 0);
733
+ const maxVersion = Number(row?.max_version || 0);
734
+ const totalRows = Number(row?.total_rows || 0);
735
+ return {
736
+ maxStateId,
737
+ maxVersion,
738
+ totalRows,
739
+ version: `${maxStateId}:${maxVersion}:${totalRows}`,
740
+ };
741
+ } catch (error) {
742
+ if (handleTableError(error, 'command_config_enrichment_version_get')) {
743
+ return {
744
+ maxStateId: 0,
745
+ maxVersion: 0,
746
+ totalRows: 0,
747
+ version: '0:0:0',
748
+ };
749
+ }
750
+
751
+ logger.warn('Falha ao ler versao do estado de enriquecimento de commandConfig.', {
752
+ action: 'command_config_enrichment_version_get_failed',
753
+ error: error?.message,
754
+ });
755
+ return {
756
+ maxStateId: 0,
757
+ maxVersion: 0,
758
+ totalRows: 0,
759
+ version: '0:0:0',
760
+ };
761
+ }
762
+ }
763
+
764
+ export function resetCommandConfigEnrichmentTableStateForTests() {
765
+ tableAvailabilityState = {
766
+ available: true,
767
+ unavailableLogged: false,
768
+ };
769
+ }