@oneuptime/common 14.0.9 → 14.0.10

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 (383) hide show
  1. package/Models/DatabaseModels/AutoRemediationSuggestion.ts +60 -0
  2. package/Models/DatabaseModels/CephCluster.ts +213 -0
  3. package/Models/DatabaseModels/DatabaseServer.ts +213 -0
  4. package/Models/DatabaseModels/DockerHost.ts +213 -0
  5. package/Models/DatabaseModels/DockerSwarmCluster.ts +213 -0
  6. package/Models/DatabaseModels/GlobalConfig.ts +1 -1
  7. package/Models/DatabaseModels/Host.ts +213 -0
  8. package/Models/DatabaseModels/Index.ts +2 -0
  9. package/Models/DatabaseModels/PodmanHost.ts +213 -0
  10. package/Models/DatabaseModels/ProxmoxCluster.ts +213 -0
  11. package/Models/DatabaseModels/ResourceAiAgent.ts +419 -0
  12. package/Models/DatabaseModels/RunnerJob.ts +144 -0
  13. package/Models/DatabaseModels/VMwareVCenter.ts +213 -0
  14. package/Server/API/AutoRemediationAPI.ts +392 -1
  15. package/Server/API/ResourceAiAccessAPI.ts +1669 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.ts +423 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  18. package/Server/Infrastructure/Semaphore.ts +22 -0
  19. package/Server/Middleware/TelemetryIngest.ts +15 -5
  20. package/Server/Services/AnalyticsDatabaseService.ts +45 -1
  21. package/Server/Services/AutoRemediationRuleEngineService.ts +940 -112
  22. package/Server/Services/CephClusterService.ts +109 -1
  23. package/Server/Services/DatabaseServerService.ts +116 -1
  24. package/Server/Services/DockerHostService.ts +109 -1
  25. package/Server/Services/DockerSwarmClusterService.ts +113 -1
  26. package/Server/Services/HostService.ts +126 -4
  27. package/Server/Services/MetricRecordingRuleService.ts +47 -0
  28. package/Server/Services/PodmanHostService.ts +109 -1
  29. package/Server/Services/ProxmoxClusterService.ts +110 -1
  30. package/Server/Services/ResourceAiAccessService.ts +1439 -0
  31. package/Server/Services/ResourceAiAgentJobService.ts +202 -0
  32. package/Server/Services/ResourceAiAgentService.ts +2432 -0
  33. package/Server/Services/RunnerJobService.ts +611 -4
  34. package/Server/Services/TelemetryUsageBillingService.ts +28 -0
  35. package/Server/Services/TraceRecordingRuleService.ts +33 -0
  36. package/Server/Services/VMwareVCenterService.ts +110 -1
  37. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1434 -32
  38. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +935 -21
  39. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +25 -1
  40. package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +577 -0
  41. package/Server/Utils/AI/ResourceAccess/ResourceAccessContext.ts +271 -0
  42. package/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.ts +45 -0
  43. package/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.ts +964 -0
  44. package/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.ts +333 -0
  45. package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +873 -0
  46. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +72 -2
  47. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +72 -5
  48. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +72 -5
  49. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +396 -13
  50. package/Server/Utils/AutoRemediation/RemediationVerifier.ts +35 -2
  51. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +9 -1
  52. package/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.ts +920 -0
  53. package/Server/Utils/SessionReplay/SessionReplayUsage.ts +84 -6
  54. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +35 -15
  55. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +33 -15
  56. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +21 -1
  57. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +298 -224
  58. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +35 -15
  59. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +389 -175
  60. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +366 -143
  61. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.ts +163 -0
  62. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.ts +396 -0
  63. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.ts +418 -0
  64. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.ts +152 -0
  65. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.ts +251 -0
  66. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.ts +214 -0
  67. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +93 -0
  68. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +18 -6
  69. package/Tests/App/Dashboard/InvestigationInfrastructureTools.test.tsx +506 -0
  70. package/Tests/App/Dashboard/RemediationSuggestionCardDescription.test.tsx +209 -0
  71. package/Tests/App/Dashboard/RemediationSuggestionCardResource.test.tsx +317 -0
  72. package/Tests/App/Dashboard/ResourceAiAccessSettingsUtil.test.ts +921 -0
  73. package/Tests/App/Dashboard/ResourceAiAgentInstall.test.ts +860 -0
  74. package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +1721 -0
  75. package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +1002 -0
  76. package/Tests/App/Dashboard/ResourceAiInsightsPage.test.tsx +931 -0
  77. package/Tests/App/Dashboard/ResourceAiNavigation.test.tsx +575 -0
  78. package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +38 -7
  79. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +33 -1
  80. package/Tests/Models/DatabaseModels/ResourceAiAccessColumns.test.ts +738 -0
  81. package/Tests/Models/DatabaseModels/ResourceAiAgentModel.test.ts +842 -0
  82. package/Tests/Server/API/AutoRemediationApproveResourceRound.test.ts +835 -0
  83. package/Tests/Server/API/ResourceAiAccessAPI.test.ts +2730 -0
  84. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +60 -0
  85. package/Tests/Server/Infrastructure/Postgres/AddResourceAiAgentsMigration.test.ts +659 -0
  86. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +31 -0
  87. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +63 -5
  88. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +103 -5
  89. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +19 -1
  90. package/Tests/Server/Services/AutoRemediationResourceRuleEngine.test.ts +1279 -0
  91. package/Tests/Server/Services/DatabaseServerService.test.ts +55 -0
  92. package/Tests/Server/Services/GroupTelemetryUsageExcludeNames.test.ts +299 -0
  93. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +44 -0
  94. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +45 -6
  95. package/Tests/Server/Services/RecordingRuleReservedMetricName.test.ts +171 -0
  96. package/Tests/Server/Services/ResourceAiAccessChangeAuthorization.test.ts +256 -0
  97. package/Tests/Server/Services/ResourceAiAccessService.test.ts +1373 -0
  98. package/Tests/Server/Services/ResourceAiAgentJobService.test.ts +375 -0
  99. package/Tests/Server/Services/ResourceAiAgentServiceHelpers.test.ts +1114 -0
  100. package/Tests/Server/Services/ResourceAiAgentServiceLifecycle.test.ts +1050 -0
  101. package/Tests/Server/Services/ResourceAiAgentServiceRegister.test.ts +2251 -0
  102. package/Tests/Server/Services/ResourceAiSettingsCreate.test.ts +373 -0
  103. package/Tests/Server/Services/ResourceAiSettingsPermission.test.ts +1042 -0
  104. package/Tests/Server/Services/ResourceServiceDeleteCleansUpAi.test.ts +319 -0
  105. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +105 -0
  106. package/Tests/Server/Services/RunnerJobEnqueueResourceCommand.test.ts +986 -0
  107. package/Tests/Server/Services/RunnerJobResourceCommandLane.test.ts +211 -0
  108. package/Tests/Server/Services/RunnerJobResourceCommandTimeoutAndRedaction.test.ts +352 -0
  109. package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +218 -0
  110. package/Tests/Server/TestingUtils/Services/FakeRunnerJobCount.ts +145 -0
  111. package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +453 -0
  112. package/Tests/Server/Utils/AI/InvestigationInfrastructureReport.test.ts +721 -0
  113. package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +94 -1
  114. package/Tests/Server/Utils/AI/RemediationCommandToolsResource.test.ts +1530 -0
  115. package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +1249 -0
  116. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +117 -0
  117. package/Tests/Server/Utils/AI/RemediationResourceCopyParity.test.ts +463 -0
  118. package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +722 -0
  119. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAccessContext.test.ts +266 -0
  120. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.test.ts +1220 -0
  121. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.test.ts +569 -0
  122. package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +830 -0
  123. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorResource.test.ts +717 -0
  124. package/Tests/Server/Utils/AutoRemediation/RemediationVerifierResourceFollowUp.test.ts +338 -0
  125. package/Tests/Server/Utils/Monitor/Criteria/SessionReplayBudgetTemplateCriteria.test.ts +422 -0
  126. package/Tests/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.test.ts +1647 -0
  127. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +143 -1
  128. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +33 -3
  129. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsAccountNotLinked.test.ts +1093 -0
  130. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.test.ts +1049 -0
  131. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.test.ts +1676 -0
  132. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCards.test.ts +2884 -0
  133. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.test.ts +2490 -0
  134. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmit.test.ts +2293 -0
  135. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmitServerTimezone.test.ts +386 -0
  136. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.test.ts +1477 -0
  137. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.test.ts +1967 -0
  138. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsStripHtmlTags.test.ts +92 -0
  139. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsSubmittedFormRemoval.test.ts +1819 -0
  140. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.test.ts +1033 -0
  141. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezoneServerZone.test.ts +610 -0
  142. package/Tests/Server/Utils/Workspace/MicrosoftTeamsBotMessageHandling.test.ts +2610 -0
  143. package/Tests/Server/Utils/Workspace/MicrosoftTeamsCreateCommandsEndToEnd.test.ts +4672 -0
  144. package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +190 -65
  145. package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +10 -4
  146. package/Tests/Types/AutoRemediation/AiRemediationCommandPlanResourceCommand.test.ts +305 -0
  147. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +277 -13
  148. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +113 -0
  149. package/Tests/Types/Monitor/RumAlertTemplates.test.ts +773 -2
  150. package/Tests/Types/ResourceAiAgent/AiResourceType.test.ts +419 -0
  151. package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +517 -0
  152. package/Tests/Types/Runbook/RunbookStepType.test.ts +147 -8
  153. package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +769 -1
  154. package/Tests/Utils/AiRemediation/Resource/CephCommandPolicy.test.ts +1653 -0
  155. package/Tests/Utils/AiRemediation/Resource/CephOutputRedaction.test.ts +204 -0
  156. package/Tests/Utils/AiRemediation/Resource/DatabaseCommandPolicy.test.ts +1077 -0
  157. package/Tests/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.test.ts +558 -0
  158. package/Tests/Utils/AiRemediation/Resource/DatabaseQueryRedactor.test.ts +834 -0
  159. package/Tests/Utils/AiRemediation/Resource/DockerCliGrammar.test.ts +747 -0
  160. package/Tests/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.test.ts +1259 -0
  161. package/Tests/Utils/AiRemediation/Resource/DockerOutputRedaction.test.ts +386 -0
  162. package/Tests/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.test.ts +782 -0
  163. package/Tests/Utils/AiRemediation/Resource/GovcCommandPolicy.test.ts +2259 -0
  164. package/Tests/Utils/AiRemediation/Resource/HostCommandPolicy.test.ts +2019 -0
  165. package/Tests/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.test.ts +2497 -0
  166. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicy.test.ts +1305 -0
  167. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.test.ts +585 -0
  168. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactor.test.ts +334 -0
  169. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactorCopyParity.test.ts +98 -0
  170. package/Tests/Utils/AiRemediation/Resource/ResourcePolicyImportClosure.test.ts +391 -0
  171. package/Tests/Utils/AiRemediation/ResourceAiAgentPolicyCopyParity.test.ts +497 -0
  172. package/Tests/Utils/SessionReplay/SessionReplayBudgetMetricType.test.ts +383 -0
  173. package/Types/AI/ResourceAiAccessApi.ts +140 -0
  174. package/Types/AI/ResourceAiAccessPermissions.ts +126 -0
  175. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +183 -2
  176. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +55 -22
  177. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +35 -3
  178. package/Types/Monitor/RumAlertTemplates.ts +351 -4
  179. package/Types/ResourceAiAgent/AiResourceType.ts +310 -0
  180. package/Types/ResourceAiAgent/ResourceAiAccess.ts +574 -0
  181. package/Types/Rum/SessionReplayBudgetMetricType.ts +47 -0
  182. package/Types/Runbook/RunbookStepType.ts +29 -0
  183. package/Types/Telemetry/TelemetryIngestSurface.ts +14 -2
  184. package/Utils/AI/InvestigationReport.ts +121 -15
  185. package/Utils/AiRemediation/Resource/CephCommandPolicy.ts +1936 -0
  186. package/Utils/AiRemediation/Resource/DatabaseCommandPolicy.ts +166 -0
  187. package/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.ts +1532 -0
  188. package/Utils/AiRemediation/Resource/DatabaseQueryRedactor.ts +1394 -0
  189. package/Utils/AiRemediation/Resource/DockerCliGrammar.ts +1839 -0
  190. package/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.ts +490 -0
  191. package/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.ts +687 -0
  192. package/Utils/AiRemediation/Resource/GovcCommandPolicy.ts +2096 -0
  193. package/Utils/AiRemediation/Resource/HostCommandPolicy.ts +2982 -0
  194. package/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.ts +1679 -0
  195. package/Utils/AiRemediation/Resource/ResourceCommandPolicy.ts +851 -0
  196. package/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.ts +593 -0
  197. package/Utils/AiRemediation/Resource/ResourceOutputRedactor.ts +2812 -0
  198. package/Utils/SessionReplay/SessionReplayBudgetMetricType.ts +184 -0
  199. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
  200. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
  201. package/build/dist/Models/DatabaseModels/CephCluster.js +219 -0
  202. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  203. package/build/dist/Models/DatabaseModels/DatabaseServer.js +219 -0
  204. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
  205. package/build/dist/Models/DatabaseModels/DockerHost.js +219 -0
  206. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  207. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +219 -0
  208. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
  209. package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
  210. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  211. package/build/dist/Models/DatabaseModels/Host.js +219 -0
  212. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  213. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  214. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  215. package/build/dist/Models/DatabaseModels/PodmanHost.js +219 -0
  216. package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +219 -0
  218. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  219. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js +439 -0
  220. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js.map +1 -0
  221. package/build/dist/Models/DatabaseModels/RunnerJob.js +145 -0
  222. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  223. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +219 -0
  224. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
  225. package/build/dist/Server/API/AutoRemediationAPI.js +231 -1
  226. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  227. package/build/dist/Server/API/ResourceAiAccessAPI.js +1119 -0
  228. package/build/dist/Server/API/ResourceAiAccessAPI.js.map +1 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js +168 -0
  230. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js.map +1 -0
  231. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  232. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Semaphore.js +6 -0
  234. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  235. package/build/dist/Server/Middleware/TelemetryIngest.js +12 -5
  236. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  237. package/build/dist/Server/Services/AnalyticsDatabaseService.js +26 -1
  238. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  239. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +601 -3
  240. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  241. package/build/dist/Server/Services/CephClusterService.js +104 -0
  242. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  243. package/build/dist/Server/Services/DatabaseServerService.js +99 -0
  244. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -1
  245. package/build/dist/Server/Services/DockerHostService.js +104 -0
  246. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  247. package/build/dist/Server/Services/DockerSwarmClusterService.js +104 -0
  248. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  249. package/build/dist/Server/Services/HostService.js +114 -2
  250. package/build/dist/Server/Services/HostService.js.map +1 -1
  251. package/build/dist/Server/Services/MetricRecordingRuleService.js +46 -0
  252. package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -1
  253. package/build/dist/Server/Services/PodmanHostService.js +104 -0
  254. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  255. package/build/dist/Server/Services/ProxmoxClusterService.js +104 -0
  256. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  257. package/build/dist/Server/Services/ResourceAiAccessService.js +1028 -0
  258. package/build/dist/Server/Services/ResourceAiAccessService.js.map +1 -0
  259. package/build/dist/Server/Services/ResourceAiAgentJobService.js +173 -0
  260. package/build/dist/Server/Services/ResourceAiAgentJobService.js.map +1 -0
  261. package/build/dist/Server/Services/ResourceAiAgentService.js +1687 -0
  262. package/build/dist/Server/Services/ResourceAiAgentService.js.map +1 -0
  263. package/build/dist/Server/Services/RunnerJobService.js +344 -5
  264. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  265. package/build/dist/Server/Services/TelemetryUsageBillingService.js +26 -0
  266. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  267. package/build/dist/Server/Services/TraceRecordingRuleService.js +37 -0
  268. package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -1
  269. package/build/dist/Server/Services/VMwareVCenterService.js +104 -0
  270. package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
  271. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1029 -30
  272. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  273. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +633 -28
  274. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  275. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +21 -1
  276. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  277. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +330 -0
  278. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -0
  279. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js +160 -0
  280. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js.map +1 -0
  281. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js +33 -0
  282. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js.map +1 -0
  283. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js +640 -0
  284. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js.map +1 -0
  285. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js +226 -0
  286. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js.map +1 -0
  287. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +600 -0
  288. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -0
  289. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +50 -4
  290. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  291. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +55 -4
  292. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  293. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +55 -4
  294. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  295. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +290 -13
  296. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  297. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +29 -1
  298. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
  299. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +9 -1
  300. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  301. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js +627 -0
  302. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js.map +1 -0
  303. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +60 -5
  304. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -1
  305. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +19 -15
  306. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  307. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +19 -15
  308. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  309. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +17 -1
  310. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  311. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +183 -209
  312. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  313. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +19 -15
  314. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  315. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +233 -167
  316. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
  317. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +263 -110
  318. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  319. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js +129 -0
  320. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js.map +1 -0
  321. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js +266 -0
  322. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js.map +1 -0
  323. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js +258 -0
  324. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js.map +1 -0
  325. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js +104 -0
  326. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js.map +1 -0
  327. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js +183 -0
  328. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js.map +1 -0
  329. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js +123 -0
  330. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js.map +1 -0
  331. package/build/dist/Types/AI/ResourceAiAccessApi.js +30 -0
  332. package/build/dist/Types/AI/ResourceAiAccessApi.js.map +1 -0
  333. package/build/dist/Types/AI/ResourceAiAccessPermissions.js +98 -0
  334. package/build/dist/Types/AI/ResourceAiAccessPermissions.js.map +1 -0
  335. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +116 -29
  336. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  337. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +47 -21
  338. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  339. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +5 -0
  340. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  341. package/build/dist/Types/Monitor/RumAlertTemplates.js +199 -3
  342. package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
  343. package/build/dist/Types/ResourceAiAgent/AiResourceType.js +242 -0
  344. package/build/dist/Types/ResourceAiAgent/AiResourceType.js.map +1 -0
  345. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +275 -0
  346. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -0
  347. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js +45 -0
  348. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js.map +1 -0
  349. package/build/dist/Types/Runbook/RunbookStepType.js +27 -0
  350. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  351. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +13 -2
  352. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  353. package/build/dist/Utils/AI/InvestigationReport.js +71 -11
  354. package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
  355. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js +1462 -0
  356. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js.map +1 -0
  357. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js +122 -0
  358. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js.map +1 -0
  359. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js +1139 -0
  360. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js.map +1 -0
  361. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js +1060 -0
  362. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js.map +1 -0
  363. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js +1301 -0
  364. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js.map +1 -0
  365. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js +370 -0
  366. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js.map +1 -0
  367. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js +498 -0
  368. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js.map +1 -0
  369. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js +1565 -0
  370. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js.map +1 -0
  371. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js +2206 -0
  372. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js.map +1 -0
  373. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js +1129 -0
  374. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js.map +1 -0
  375. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js +565 -0
  376. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js.map +1 -0
  377. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js +408 -0
  378. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js.map +1 -0
  379. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js +1910 -0
  380. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js.map +1 -0
  381. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js +160 -0
  382. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js.map +1 -0
  383. package/package.json +1 -1
@@ -0,0 +1,834 @@
1
+ import {
2
+ DATABASE_LITERAL_PLACEHOLDER,
3
+ DATABASE_REDACTED_MARKER,
4
+ DATABASE_UNREDACTABLE_OUTPUT,
5
+ DatabaseQueryRedaction,
6
+ looksLikeSqlStatement,
7
+ normalizeSqlText,
8
+ redactDatabaseOutput,
9
+ redactDatabaseQueryText,
10
+ redactRedisCommandArguments,
11
+ } from "../../../../Utils/AiRemediation/Resource/DatabaseQueryRedactor";
12
+ import {
13
+ RESOURCE_REDACTED_MARKER,
14
+ getResourceOutputRedactionHooks,
15
+ redactResourceCommandOutput,
16
+ redactResourceCommandOutputWithCount,
17
+ } from "../../../../Utils/AiRemediation/Resource/ResourceOutputRedactor";
18
+ import AiResourceType from "../../../../Types/ResourceAiAgent/AiResourceType";
19
+ import { describe, expect, test } from "@jest/globals";
20
+
21
+ /*
22
+ * Contract under test — DatabaseQueryRedactor, the "db" output hook.
23
+ *
24
+ * - normalizeSqlText: every string literal (all quoting styles and
25
+ * prefixes, dollar-quoted bodies) becomes '?', every number literal ?,
26
+ * a double-quoted word "?" unless it can only be a name; bind
27
+ * parameters, names and backtick identifiers stay.
28
+ * - redactDatabaseOutput finds statements in JSON string values (by key or
29
+ * by shape) and in plain text (fields, table columns, continuation
30
+ * lines), masks single-quoted literals anywhere in plain text, and masks
31
+ * credentials and row data: PASSWORD / IDENTIFIED BY values, Redis AUTH /
32
+ * HELLO / MIGRATE / CONFIG SET / ACL SETUSER secrets, data values of
33
+ * Redis write commands, credential settings, InnoDB record dumps.
34
+ * - Output that holds none of these passes through byte for byte; JSON
35
+ * stays valid JSON; the hook is idempotent, total and fails closed.
36
+ * - Wired as ResourceOutputRedactor's "db" hook, before the generic rules.
37
+ */
38
+
39
+ function redact(text: string): string {
40
+ return redactDatabaseOutput({
41
+ resourceType: AiResourceType.DatabaseServer,
42
+ program: "db",
43
+ text,
44
+ }).text;
45
+ }
46
+
47
+ function redactWithCount(text: string): DatabaseQueryRedaction {
48
+ return redactDatabaseOutput({
49
+ resourceType: AiResourceType.DatabaseServer,
50
+ program: "db",
51
+ text,
52
+ });
53
+ }
54
+
55
+ describe("the markers", () => {
56
+ test("a literal becomes ?, a credential the generic marker", () => {
57
+ expect(DATABASE_LITERAL_PLACEHOLDER).toBe("?");
58
+ expect(DATABASE_REDACTED_MARKER).toBe(RESOURCE_REDACTED_MARKER);
59
+ });
60
+ });
61
+
62
+ describe("normalizeSqlText", () => {
63
+ test.each([
64
+ [
65
+ "SELECT * FROM users WHERE email = 'a@b.com'",
66
+ "SELECT * FROM users WHERE email = '?'",
67
+ 1,
68
+ ],
69
+ ["SELECT * FROM t WHERE id = 42", "SELECT * FROM t WHERE id = ?", 1],
70
+ [
71
+ "UPDATE t SET a = 3.14, b = 1e9, c = 0x1F WHERE id = -7",
72
+ "UPDATE t SET a = ?, b = ?, c = ? WHERE id = -?",
73
+ 4,
74
+ ],
75
+ ["SELECT .5, 1., 12.5e-3", "SELECT ?, ?, ?", 3],
76
+ [
77
+ "SELECT $1, $2::int FROM t1 WHERE t1.col2 = $3",
78
+ "SELECT $1, $2::int FROM t1 WHERE t1.col2 = $3",
79
+ 0,
80
+ ],
81
+ ["SELECT 'it''s', 'a\\'b'", "SELECT '?', '?'", 2],
82
+ ["SELECT 'a\\' , 'secret'", "SELECT '?'", 1],
83
+ [
84
+ "SELECT E'\\x41', N'x', X'4142', B'101', _utf8mb4'abc', U&'d0061t'",
85
+ "SELECT E'?', N'?', X'?', B'?', _utf8mb4'?', U&'?'",
86
+ 6,
87
+ ],
88
+ ["DO $$ BEGIN PERFORM 1; END $$", "DO $$?$$", 1],
89
+ ["SELECT $fn$ secret $fn$ FROM x", "SELECT $fn$?$fn$ FROM x", 1],
90
+ ["SELECT $tag$ never closed", "SELECT $tag$?", 1],
91
+ [
92
+ 'SELECT "users"."email" FROM "users" WHERE "id" = 5',
93
+ 'SELECT "users"."email" FROM "users" WHERE "?" = ?',
94
+ 2,
95
+ ],
96
+ [
97
+ 'SELECT * FROM t WHERE name = "bob"',
98
+ 'SELECT * FROM t WHERE name = "?"',
99
+ 1,
100
+ ],
101
+ [
102
+ 'ALTER USER bob IDENTIFIED BY "hunter2"',
103
+ 'ALTER USER bob IDENTIFIED BY "?"',
104
+ 1,
105
+ ],
106
+ [
107
+ 'ALTER USER bob IDENTIFIED WITH caching_sha2_password AS "hash"',
108
+ 'ALTER USER bob IDENTIFIED WITH caching_sha2_password AS "?"',
109
+ 1,
110
+ ],
111
+ [
112
+ "CREATE USER x WITH PASSWORD 'hunter2'",
113
+ "CREATE USER x WITH PASSWORD '?'",
114
+ 1,
115
+ ],
116
+ [
117
+ 'INSERT INTO "orders" ("id", "note") VALUES (1, "x")',
118
+ 'INSERT INTO "orders" ("?", "?") VALUES (?, "?")',
119
+ 4,
120
+ ],
121
+ ["SELECT `col1` FROM `t2` LIMIT 10", "SELECT `col1` FROM `t2` LIMIT ?", 1],
122
+ ["SELECT 'unterminated", "SELECT '?'", 1],
123
+ [
124
+ "INSERT INTO t VALUES (1, 'a'), (2, 'b')",
125
+ "INSERT INTO t VALUES (?, '?'), (?, '?')",
126
+ 4,
127
+ ],
128
+ ["SELECT 1col FROM t", "SELECT 1col FROM t", 0],
129
+ [
130
+ "SELECT 'é' FROM données WHERE x = 5",
131
+ "SELECT '?' FROM données WHERE x = ?",
132
+ 2,
133
+ ],
134
+ [
135
+ "SELECT * FROM t WHERE ts > now() - interval '5 minutes'",
136
+ "SELECT * FROM t WHERE ts > now() - interval '?'",
137
+ 1,
138
+ ],
139
+ [
140
+ "SELECT * FROM t /* controller='users',id=7 */",
141
+ "SELECT * FROM t /* controller='?',id=? */",
142
+ 2,
143
+ ],
144
+ ["SELECT '?' FROM t WHERE a = ?", "SELECT '?' FROM t WHERE a = ?", 0],
145
+ ["", "", 0],
146
+ ])("%p", (sql: string, expected: string, count: number) => {
147
+ expect(normalizeSqlText(sql)).toEqual({
148
+ text: expected,
149
+ redactionCount: count,
150
+ });
151
+ expect(redactDatabaseQueryText(sql)).toBe(expected);
152
+ });
153
+
154
+ test.each([
155
+ ["SELECT * FROM users WHERE email = 'a@b.com' AND id = 42"],
156
+ ['SELECT "a" FROM "b" WHERE "c" = "d" AND e IN (1, 2.5, 0x3)'],
157
+ ["DO $x$ select 1 $x$; SELECT E'a\\'b', 'c''d'"],
158
+ ])("is idempotent: %p", (sql: string) => {
159
+ const once: DatabaseQueryRedaction = normalizeSqlText(sql);
160
+ const twice: DatabaseQueryRedaction = normalizeSqlText(once.text);
161
+
162
+ expect(twice.text).toBe(once.text);
163
+ expect(twice.redactionCount).toBe(0);
164
+ });
165
+
166
+ test("a non-string is empty", () => {
167
+ expect(normalizeSqlText(null as unknown as string)).toEqual({
168
+ text: "",
169
+ redactionCount: 0,
170
+ });
171
+ });
172
+ });
173
+
174
+ describe("looksLikeSqlStatement", () => {
175
+ test.each([
176
+ ["SELECT 1", true],
177
+ [" select * from t", true],
178
+ ["(SELECT 1) UNION (SELECT 2)", true],
179
+ ["/* app=web */ UPDATE t SET a = 1", true],
180
+ ["-- note\nDELETE FROM t", true],
181
+ ["WITH x AS (SELECT 1) SELECT * FROM x", true],
182
+ ["BEGIN;", true],
183
+ ["SELECT*FROM t", true],
184
+ ["selection of rows", false],
185
+ ["set_config", false],
186
+ ["active", false],
187
+ ["idle in transaction", false],
188
+ ["client backend", false],
189
+ ["", false],
190
+ ])("%p: %p", (text: string, expected: boolean) => {
191
+ expect(looksLikeSqlStatement(text)).toBe(expected);
192
+ });
193
+ });
194
+
195
+ describe("statements in JSON output", () => {
196
+ test("a query column is normalized; every other value stays", () => {
197
+ const text: string = JSON.stringify([
198
+ {
199
+ pid: 4242,
200
+ usename: "app",
201
+ state: "active",
202
+ query: "SELECT * FROM users WHERE email = 'a@b.com' AND id = 42",
203
+ backend_type: "client backend",
204
+ duration_ms: 1234,
205
+ },
206
+ ]);
207
+ const result: DatabaseQueryRedaction = redactWithCount(text);
208
+
209
+ expect(JSON.parse(result.text)).toEqual([
210
+ {
211
+ pid: 4242,
212
+ usename: "app",
213
+ state: "active",
214
+ query: "SELECT * FROM users WHERE email = '?' AND id = ?",
215
+ backend_type: "client backend",
216
+ duration_ms: 1234,
217
+ },
218
+ ]);
219
+ expect(result.redactionCount).toBe(2);
220
+ });
221
+
222
+ test("pretty-printed JSON keeps its layout", () => {
223
+ const text: string = JSON.stringify(
224
+ { pid: 1, query: "select 1 where x = 'y'" },
225
+ null,
226
+ 2,
227
+ );
228
+
229
+ expect(redact(text)).toBe(
230
+ JSON.stringify({ pid: 1, query: "select ? where x = '?'" }, null, 2),
231
+ );
232
+ });
233
+
234
+ test.each([
235
+ [
236
+ "info",
237
+ "/* app */ select * from t where id = 5",
238
+ "/* app */ select * from t where id = ?",
239
+ ],
240
+ ["INFO", "SELECT 7", "SELECT ?"],
241
+ [
242
+ "digest_text",
243
+ "SELECT * FROM `t` WHERE `a` = 3",
244
+ "SELECT * FROM `t` WHERE `a` = ?",
245
+ ],
246
+ ["sql_text", "UPDATE t SET a = 'b'", "UPDATE t SET a = '?'"],
247
+ [
248
+ "query_sample_text",
249
+ "DELETE FROM t WHERE id = 9",
250
+ "DELETE FROM t WHERE id = ?",
251
+ ],
252
+ [
253
+ "query",
254
+ "idle-in-transaction since 12:00 'x'",
255
+ "idle-in-transaction since ?:? '?'",
256
+ ],
257
+ ["text", "DELETE FROM t WHERE id = 5", "DELETE FROM t WHERE id = ?"],
258
+ ["note", "not a statement 5 'x'", "not a statement 5 'x'"],
259
+ ])("key %s", (key: string, value: string, expected: string) => {
260
+ const text: string = JSON.stringify({ [key]: value, n: 3 });
261
+
262
+ expect(JSON.parse(redact(text))).toEqual({ [key]: expected, n: 3 });
263
+ });
264
+
265
+ test("escaped quotes and line breaks survive as valid JSON", () => {
266
+ const text: string = JSON.stringify({
267
+ query: "SELECT \"a\" FROM t\nWHERE b = 'x' AND c = 5",
268
+ calls: 12,
269
+ });
270
+ const redacted: string = redact(text);
271
+
272
+ expect(JSON.parse(redacted)).toEqual({
273
+ query: "SELECT \"?\" FROM t\nWHERE b = '?' AND c = ?",
274
+ calls: 12,
275
+ });
276
+ });
277
+
278
+ test("a key named like a query with a number value is left alone", () => {
279
+ const text: string =
280
+ '{"opcounters": {"insert": 5, "query": 10, "update": 3}}';
281
+
282
+ expect(redact(text)).toBe(text);
283
+ });
284
+
285
+ test("a value under a query key is normalized even when it does not start like a statement", () => {
286
+ expect(redact('{"query": "<IDLE> 42"}')).toBe('{"query": "<IDLE> ?"}');
287
+ });
288
+
289
+ test("JSON with no statements passes through byte for byte", () => {
290
+ const text: string = JSON.stringify(
291
+ {
292
+ state: "active",
293
+ backend_type: "client backend",
294
+ wait_event: "ClientRead",
295
+ numbers: [1, 2, 3],
296
+ version: "PostgreSQL 16.4 on x86_64",
297
+ },
298
+ null,
299
+ 4,
300
+ );
301
+
302
+ expect(redact(text)).toBe(text);
303
+ });
304
+ });
305
+
306
+ describe("statements in plain-text output", () => {
307
+ test.each([
308
+ [
309
+ "psql-style table: the next column stays",
310
+ "pid | state | query | secs\n123 | active | SELECT * FROM users WHERE email = 'a@b.com' AND id = 42 | 5",
311
+ "pid | state | query | secs\n123 | active | SELECT * FROM users WHERE email = '?' AND id = ? | 5",
312
+ ],
313
+ [
314
+ "tab-separated: the next column stays",
315
+ "123\tactive\tSELECT * FROM t WHERE id = 42\t5",
316
+ "123\tactive\tSELECT * FROM t WHERE id = ?\t5",
317
+ ],
318
+ [
319
+ "vertical key: value",
320
+ "Id: 7\nInfo: UPDATE accounts SET balance = 100 WHERE id = 3\nTime: 12",
321
+ "Id: 7\nInfo: UPDATE accounts SET balance = ? WHERE id = ?\nTime: 12",
322
+ ],
323
+ [
324
+ "a multi-line statement",
325
+ "query: SELECT *\n FROM users\n WHERE email = 'a@b.com'\n AND id = 42\n LIMIT 10",
326
+ "query: SELECT *\n FROM users\n WHERE email = '?'\n AND id = ?\n LIMIT ?",
327
+ ],
328
+ [
329
+ "a statement at the start of a line",
330
+ "SELECT 1 WHERE x = 'a'",
331
+ "SELECT ? WHERE x = '?'",
332
+ ],
333
+ [
334
+ "a column gap",
335
+ "42 active DELETE FROM t WHERE id = 5",
336
+ "42 active DELETE FROM t WHERE id = ?",
337
+ ],
338
+ [
339
+ "a clause cut by a tab continues",
340
+ "SELECT *\tFROM t WHERE id = 42",
341
+ "SELECT *\tFROM t WHERE id = ?",
342
+ ],
343
+ [
344
+ "InnoDB status statement lines",
345
+ "MySQL thread id 12, OS thread handle 1234, query id 56 localhost root updating\nUPDATE t SET a = 5 WHERE id = 7",
346
+ "MySQL thread id 12, OS thread handle 1234, query id 56 localhost root updating\nUPDATE t SET a = ? WHERE id = ?",
347
+ ],
348
+ ])("%s", (_name: string, text: string, expected: string) => {
349
+ expect(redact(text)).toBe(expected);
350
+ });
351
+
352
+ test.each([
353
+ ["used_memory:1048576\nrole:master\ndb0:keys=5,expires=0,avg_ttl=0"],
354
+ ["connections: 42 of 100"],
355
+ ["id=5 addr=10.0.0.1:5000 name= age=10 idle=0 db=0 cmd=auth user=default"],
356
+ ["It's fine, don't worry; count 5"],
357
+ [
358
+ "state: idle in transaction\nwait_event_type: Lock\nbackend_type: client backend",
359
+ ],
360
+ ['max_connections = 100\nwork_mem = 4MB\nsearch_path = "$user", public'],
361
+ ["PostgreSQL 16.4 on x86_64-pc-linux-gnu, compiled by gcc 12.2.0, 64-bit"],
362
+ ["Uptime: 12345 Threads: 4 Questions: 99 Slow queries: 0"],
363
+ [
364
+ "LATEST DETECTED DEADLOCK\n------------------------\n2024-01-01 10:00:00 0x7f",
365
+ ],
366
+ ])("no statement, no change: %p", (text: string) => {
367
+ expect(redact(text)).toBe(text);
368
+ });
369
+
370
+ test.each([
371
+ ["Error: Unknown database 'foo'", "Error: Unknown database '?'"],
372
+ ["near 'password=abc' at line 1", "near '?' at line 1"],
373
+ ["note: N'x' and E'y'", "note: N'?' and E'?'"],
374
+ ["a 'b'c' d", "a '?' d"],
375
+ ["open 'never closed", "open '?'"],
376
+ ])(
377
+ "a single-quoted literal is masked anywhere: %p",
378
+ (text: string, expected: string) => {
379
+ expect(redact(text)).toBe(expected);
380
+ },
381
+ );
382
+ });
383
+
384
+ describe("credentials", () => {
385
+ test.each([
386
+ [
387
+ "CREATE USER x WITH PASSWORD 'hunter2'",
388
+ "CREATE USER x WITH PASSWORD '[redacted]'",
389
+ ],
390
+ [
391
+ "ALTER ROLE x ENCRYPTED PASSWORD 'md5abc'",
392
+ "ALTER ROLE x ENCRYPTED PASSWORD '[redacted]'",
393
+ ],
394
+ [
395
+ "SET PASSWORD = PASSWORD('hunter2')",
396
+ "SET PASSWORD = PASSWORD('[redacted]')",
397
+ ],
398
+ ['password = "hunter2"', 'password = "[redacted]"'],
399
+ [
400
+ 'Info: ALTER USER bob IDENTIFIED BY "hunter2"',
401
+ 'Info: ALTER USER bob IDENTIFIED BY "[redacted]"',
402
+ ],
403
+ [
404
+ "CREATE USER bob IDENTIFIED BY 'hunter2'",
405
+ "CREATE USER bob IDENTIFIED BY '[redacted]'",
406
+ ],
407
+ [
408
+ "ALTER USER x IDENTIFIED WITH caching_sha2_password AS '$A$005$abc'",
409
+ "ALTER USER x IDENTIFIED WITH caching_sha2_password AS '[redacted]'",
410
+ ],
411
+ [
412
+ "ALTER USER x IDENTIFIED WITH mysql_native_password BY 'pw'",
413
+ "ALTER USER x IDENTIFIED WITH mysql_native_password BY '[redacted]'",
414
+ ],
415
+ ])("%p", (text: string, expected: string) => {
416
+ expect(redact(text)).toBe(expected);
417
+ });
418
+
419
+ test.each([
420
+ ["password authentication failed for user bob"],
421
+ ["ALTER USER bob PASSWORD EXPIRE"],
422
+ ['{"password_encryption": "scram-sha-256"}'],
423
+ ["password_encryption: scram-sha-256"],
424
+ ])(
425
+ "a phrase with no quoted value is left for the generic rules: %p",
426
+ (text: string) => {
427
+ expect(redact(text)).toBe(text);
428
+ },
429
+ );
430
+
431
+ test("a password inside a JSON query value is masked and the JSON stays valid", () => {
432
+ const text: string = JSON.stringify({
433
+ info: "ALTER USER bob IDENTIFIED BY 'hunter2'",
434
+ });
435
+ const redacted: string = redact(text);
436
+
437
+ expect(redacted).not.toContain("hunter2");
438
+ expect(JSON.parse(redacted)).toEqual({
439
+ info: "ALTER USER bob IDENTIFIED BY '[redacted]'",
440
+ });
441
+ });
442
+
443
+ test.each([
444
+ [
445
+ '{"name": "requirepass", "value": "hunter2"}',
446
+ '{"name": "requirepass", "value": "[redacted]"}',
447
+ ],
448
+ [
449
+ '{"name": "masterauth", "setting": "hunter2"}',
450
+ '{"name": "masterauth", "setting": "[redacted]"}',
451
+ ],
452
+ [
453
+ '{"name": "primaryauth", "value": "Hunter2Secret!"}',
454
+ '{"name": "primaryauth", "value": "[redacted]"}',
455
+ ],
456
+ [
457
+ '[{"Variable_name": "some_password", "Value": "x"}]',
458
+ '[{"Variable_name": "some_password", "Value": "[redacted]"}]',
459
+ ],
460
+ [
461
+ '{"name": "work_mem", "setting": "4096"}',
462
+ '{"name": "work_mem", "setting": "4096"}',
463
+ ],
464
+ ])(
465
+ "credential settings listed as name/value: %p",
466
+ (text: string, expected: string) => {
467
+ expect(redact(text)).toBe(expected);
468
+ },
469
+ );
470
+ });
471
+
472
+ describe("Redis secrets and data", () => {
473
+ test.each([
474
+ ['["AUTH","hunter2"]', '["AUTH","[redacted]"]'],
475
+ ['["auth","default","pw"]', '["auth","[redacted]","[redacted]"]'],
476
+ [
477
+ '{"args": ["AUTH", "hunter2"], "id": 5}',
478
+ '{"args": ["AUTH", "[redacted]"], "id": 5}',
479
+ ],
480
+ [
481
+ '["HELLO","3","AUTH","default","pw","SETNAME","x"]',
482
+ '["HELLO","3","AUTH","[redacted]","[redacted]","SETNAME","x"]',
483
+ ],
484
+ [
485
+ '["MIGRATE","h","6379","k","0","5000","AUTH","pw","KEYS","a"]',
486
+ '["MIGRATE","h","6379","k","0","5000","AUTH","[redacted]","KEYS","a"]',
487
+ ],
488
+ [
489
+ '["MIGRATE","h","6379","k","0","5000","AUTH2","u","pw"]',
490
+ '["MIGRATE","h","6379","k","0","5000","AUTH2","[redacted]","[redacted]"]',
491
+ ],
492
+ [
493
+ '["CONFIG","SET","requirepass","hunter2","maxmemory","1gb"]',
494
+ '["CONFIG","SET","requirepass","[redacted]","maxmemory","1gb"]',
495
+ ],
496
+ [
497
+ '["ACL","SETUSER","bob","on",">pw1","#abc","~*"]',
498
+ '["ACL","SETUSER","bob","on","[redacted]","[redacted]","~*"]',
499
+ ],
500
+ [
501
+ '["SET","session:1","{\\"user\\":1}","EX","60"]',
502
+ '["SET","session:1","[redacted]","[redacted]","[redacted]"]',
503
+ ],
504
+ [
505
+ '["HSET","user:1","email","a@b.com"]',
506
+ '["HSET","user:1","[redacted]","[redacted]"]',
507
+ ],
508
+ [
509
+ '["requirepass","x","maxmemory","0"]',
510
+ '["requirepass","[redacted]","maxmemory","0"]',
511
+ ],
512
+ ['["GET","k"]', '["GET","k"]'],
513
+ ['["active","idle"]', '["active","idle"]'],
514
+ ['["AUTH"]', '["AUTH"]'],
515
+ ])("%s", (text: string, expected: string) => {
516
+ expect(redact(text)).toBe(expected);
517
+ });
518
+
519
+ test("pretty-printed SLOWLOG arrays keep their layout", () => {
520
+ const text: string = JSON.stringify(
521
+ [{ id: 1, durationMicros: 12000, args: ["SET", "k", "secretvalue"] }],
522
+ null,
523
+ 2,
524
+ );
525
+
526
+ expect(JSON.parse(redact(text))).toEqual([
527
+ { id: 1, durationMicros: 12000, args: ["SET", "k", "[redacted]"] },
528
+ ]);
529
+ });
530
+
531
+ test.each([
532
+ ["AUTH hunter2", "AUTH [redacted]"],
533
+ ["auth default hunter2", "auth [redacted]"],
534
+ ["1) AUTH default hunter2", "1) AUTH [redacted]"],
535
+ ["cmd: AUTH hunter2", "cmd: AUTH [redacted]"],
536
+ ['{"cmd": "AUTH hunter2"}', '{"cmd": "AUTH [redacted]"}'],
537
+ ["HELLO 3 AUTH default hunter2", "HELLO 3 AUTH [redacted] [redacted]"],
538
+ [
539
+ "MIGRATE host 6379 key 0 5000 AUTH pw KEYS a",
540
+ "MIGRATE host 6379 key 0 5000 AUTH [redacted] KEYS a",
541
+ ],
542
+ [
543
+ 'MIGRATE host 6379 "" 0 5000 COPY AUTH2 user pw KEYS a',
544
+ 'MIGRATE host 6379 "" 0 5000 COPY AUTH2 [redacted] [redacted] KEYS a',
545
+ ],
546
+ ["config set requirepass hunter2", "config set requirepass [redacted]"],
547
+ ['1) "requirepass"\n2) "hunter2"', '1) "requirepass"\n2) "[redacted]"'],
548
+ ["masterauth\nhunter2", "masterauth\n[redacted]"],
549
+ ["primaryauth\nHunter2Secret", "primaryauth\n[redacted]"],
550
+ [
551
+ '1) "primaryauth"\n2) "Hunter2Secret"',
552
+ '1) "primaryauth"\n2) "[redacted]"',
553
+ ],
554
+ [
555
+ "config set primaryauth Hunter2Secret",
556
+ "config set primaryauth [redacted]",
557
+ ],
558
+ ])("plain text %p", (text: string, expected: string) => {
559
+ expect(redact(text)).toBe(expected);
560
+ });
561
+
562
+ test.each([
563
+ ["id=5 addr=10.0.0.1:5000 cmd=auth user=default"],
564
+ ["authentication: ok"],
565
+ ["oauth tokens expire"],
566
+ ])("CLIENT LIST and prose keep their words: %p", (text: string) => {
567
+ expect(redact(text)).toBe(text);
568
+ });
569
+ });
570
+
571
+ describe("redactRedisCommandArguments: a command masked before it is printed", () => {
572
+ test.each([
573
+ [
574
+ ["AUTH", "app", "hunter2"],
575
+ ["AUTH", "[redacted]", "[redacted]"],
576
+ ],
577
+ [
578
+ ["HSET", "user:1", "email", "a@b.com"],
579
+ ["HSET", "user:1", "[redacted]", "[redacted]"],
580
+ ],
581
+ [
582
+ ["CONFIG", "SET", "requirepass", "x", "maxmemory", "1gb"],
583
+ ["CONFIG", "SET", "requirepass", "[redacted]", "maxmemory", "1gb"],
584
+ ],
585
+ [
586
+ ["GET", "k"],
587
+ ["GET", "k"],
588
+ ],
589
+ [
590
+ ["SET", "k", "?"],
591
+ ["SET", "k", "?"],
592
+ ],
593
+ [
594
+ ["SMOVE", "pending", "done", "card-4111"],
595
+ ["SMOVE", "pending", "[redacted]", "[redacted]"],
596
+ ],
597
+ [
598
+ ["HSETEX", "h", "EX", "60", "FIELDS", "1", "f", "v"],
599
+ [
600
+ "HSETEX",
601
+ "h",
602
+ "[redacted]",
603
+ "[redacted]",
604
+ "[redacted]",
605
+ "[redacted]",
606
+ "[redacted]",
607
+ "[redacted]",
608
+ ],
609
+ ],
610
+ [
611
+ ["LPOS", "l", "john@example.com"],
612
+ ["LPOS", "l", "[redacted]"],
613
+ ],
614
+ [
615
+ ["ZSCORE", "z", "john@example.com"],
616
+ ["ZSCORE", "z", "[redacted]"],
617
+ ],
618
+ [
619
+ ["JSON.MSET", "d", "$", '{"a":1}'],
620
+ ["JSON.MSET", "d", "[redacted]", "[redacted]"],
621
+ ],
622
+ [
623
+ ["JSON.ARRAPPEND", "d", "$.tags", '"vip"'],
624
+ ["JSON.ARRAPPEND", "d", "[redacted]", "[redacted]"],
625
+ ],
626
+ [
627
+ ["BF.ADD", "seen", "john@example.com"],
628
+ ["BF.ADD", "seen", "[redacted]"],
629
+ ],
630
+ ])("%j", (args: Array<string>, expected: Array<string>) => {
631
+ expect(redactRedisCommandArguments(args)).toEqual(expected);
632
+ });
633
+
634
+ test("what is not a string is read as its text; what is not a list is nothing", () => {
635
+ expect(redactRedisCommandArguments(["SET", "k", 42, null])).toEqual([
636
+ "SET",
637
+ "k",
638
+ "[redacted]",
639
+ "[redacted]",
640
+ ]);
641
+ expect(
642
+ redactRedisCommandArguments("AUTH x" as unknown as Array<string>),
643
+ ).toEqual([]);
644
+ });
645
+
646
+ test("the hook masks the same arguments in a printed array", () => {
647
+ const args: Array<string> = ["JSON.MSET", "d", "$", '{"a":1}'];
648
+
649
+ expect(redact(JSON.stringify(args))).toBe(
650
+ JSON.stringify(redactRedisCommandArguments(args)),
651
+ );
652
+ });
653
+ });
654
+
655
+ describe("InnoDB record dumps", () => {
656
+ test("the row bytes are masked, the field's number and length stay", () => {
657
+ const text: string = [
658
+ "Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0",
659
+ " 0: len 4; hex 80000001; asc ;;",
660
+ " 3: len 15; hex 626f62406578616d706c652e636f6d; asc bob@example.com;;",
661
+ " 4: len 30; hex 6162; asc a;;b;...(truncated);",
662
+ ].join("\n");
663
+
664
+ expect(redact(text)).toBe(
665
+ [
666
+ "Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0",
667
+ " 0: len 4; hex [redacted]; asc [redacted];;",
668
+ " 3: len 15; hex [redacted]; asc [redacted];;",
669
+ " 4: len 30; hex [redacted]; asc [redacted];",
670
+ ].join("\n"),
671
+ );
672
+ expect(redact(text)).not.toContain("bob@example.com");
673
+ });
674
+ });
675
+
676
+ describe("the whole hook", () => {
677
+ test("counts every masked value", () => {
678
+ expect(
679
+ redactWithCount(
680
+ "SELECT 1 WHERE x = 'a'\nAUTH hunter2\n 0: len 4; hex 80000001; asc ;;",
681
+ ).redactionCount,
682
+ ).toBe(4);
683
+ });
684
+
685
+ test.each([
686
+ ["SELECT * FROM users WHERE email = 'a@b.com' AND id = 42"],
687
+ ['[{"query": "select 1 where a = \'b\'", "args": ["AUTH", "pw"]}]'],
688
+ [
689
+ "ALTER USER x IDENTIFIED BY 'pw'\nconfig set requirepass pw\n 0: len 4; hex 80000001; asc ;;",
690
+ ],
691
+ ['{"name": "requirepass", "value": "x"}'],
692
+ ])("is idempotent: %p", (text: string) => {
693
+ const once: DatabaseQueryRedaction = redactWithCount(text);
694
+ const twice: DatabaseQueryRedaction = redactWithCount(once.text);
695
+
696
+ expect(twice.text).toBe(once.text);
697
+ expect(twice.redactionCount).toBe(0);
698
+ });
699
+
700
+ test.each([[null], [undefined], [42], [{}], [""]])(
701
+ "text %p redacts to an empty string",
702
+ (text: unknown) => {
703
+ expect(
704
+ redactDatabaseOutput({
705
+ resourceType: AiResourceType.DatabaseServer,
706
+ program: "db",
707
+ text: text as string,
708
+ }),
709
+ ).toEqual({ text: "", redactionCount: 0 });
710
+ },
711
+ );
712
+
713
+ test("a missing request redacts to an empty string", () => {
714
+ expect(
715
+ redactDatabaseOutput(
716
+ null as unknown as Parameters<typeof redactDatabaseOutput>[0],
717
+ ),
718
+ ).toEqual({ text: "", redactionCount: 0 });
719
+ });
720
+
721
+ test("output that cannot be normalized is withheld, never passed on", () => {
722
+ let reads: number = 0;
723
+ const request: Parameters<typeof redactDatabaseOutput>[0] = {
724
+ resourceType: AiResourceType.DatabaseServer,
725
+ program: "db",
726
+ get text(): string {
727
+ reads++;
728
+
729
+ if (reads > 2) {
730
+ throw new Error("unreadable output");
731
+ }
732
+
733
+ return "SELECT 1 WHERE email = 'a@b.com'";
734
+ },
735
+ };
736
+
737
+ expect(redactDatabaseOutput(request)).toEqual({
738
+ text: DATABASE_UNREDACTABLE_OUTPUT,
739
+ redactionCount: 1,
740
+ });
741
+ });
742
+
743
+ test("hostile shapes of 50 KB finish quickly and change nothing they need not", () => {
744
+ const size: number = 50 * 1024;
745
+ const cases: Array<string> = [
746
+ "'".repeat(size),
747
+ "a'".repeat(size / 2),
748
+ '"'.repeat(size),
749
+ `"${'\\"'.repeat(size / 2)}`,
750
+ "SELECT ".repeat(size / 7),
751
+ '["'.repeat(size / 2),
752
+ "$a$".repeat(size / 3),
753
+ "hello ".repeat(size / 6),
754
+ "migrate ".repeat(size / 8),
755
+ "PASSWORD '".repeat(size / 10),
756
+ "len 1; hex 00; asc ".repeat(size / 19),
757
+ '{"name": "'.repeat(size / 10),
758
+ "SELECT `".repeat(size / 8),
759
+ "SELECT 1\n".repeat(size / 9),
760
+ ];
761
+ const started: number = Date.now();
762
+
763
+ for (const text of cases) {
764
+ const result: DatabaseQueryRedaction = redactWithCount(text);
765
+
766
+ expect(typeof result.text).toBe("string");
767
+ expect(result.text).not.toBe(DATABASE_UNREDACTABLE_OUTPUT);
768
+ }
769
+
770
+ // Generous: a catastrophic (exponential) regex would take minutes.
771
+ expect(Date.now() - started).toBeLessThan(60_000);
772
+ });
773
+ });
774
+
775
+ describe("wired into ResourceOutputRedactor as the db hook", () => {
776
+ test("the db program has this hook", () => {
777
+ expect(getResourceOutputRedactionHooks("db")).toContain(
778
+ redactDatabaseOutput,
779
+ );
780
+ });
781
+
782
+ test("the hook runs before the generic rules, and both apply", () => {
783
+ const text: string = [
784
+ "query: SELECT * FROM t WHERE x = 'a' AND id = 42",
785
+ "DB_PASSWORD=hunter2",
786
+ "url: postgres://app:hunter2@db:5432/app",
787
+ '{"password": "hunter2"}',
788
+ "AUTH hunter2",
789
+ ].join("\n");
790
+ const redacted: string = redactResourceCommandOutput({
791
+ resourceType: AiResourceType.DatabaseServer,
792
+ program: "db",
793
+ text,
794
+ });
795
+
796
+ expect(redacted).not.toContain("hunter2");
797
+ expect(redacted).toContain(
798
+ "query: SELECT * FROM t WHERE x = '?' AND id = ?",
799
+ );
800
+ expect(redacted).toContain("DB_PASSWORD=[redacted]");
801
+ expect(
802
+ redactResourceCommandOutputWithCount({
803
+ resourceType: AiResourceType.DatabaseServer,
804
+ program: "db",
805
+ text,
806
+ }).redactionCount,
807
+ ).toBeGreaterThanOrEqual(6);
808
+ });
809
+
810
+ test("other programs never get query normalization", () => {
811
+ const text: string = "SELECT 1 WHERE x = 'a'";
812
+
813
+ expect(
814
+ redactResourceCommandOutput({
815
+ resourceType: AiResourceType.DockerHost,
816
+ program: "docker",
817
+ text,
818
+ }),
819
+ ).toBe(text);
820
+ });
821
+
822
+ test("plain db output passes through", () => {
823
+ const text: string =
824
+ "PostgreSQL 16.4 on x86_64-pc-linux-gnu\nmax_connections: 100";
825
+
826
+ expect(
827
+ redactResourceCommandOutput({
828
+ resourceType: AiResourceType.DatabaseServer,
829
+ program: "db",
830
+ text,
831
+ }),
832
+ ).toBe(text);
833
+ });
834
+ });