@kb-labs/rest-api-app 1.3.0

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 (255) hide show
  1. package/.kb/agents/sessions/session-1771531145393-ws3uh6q/turns.json +42 -0
  2. package/.kb/agents/sessions/session-1771548930486-ui6rtvm/turns.json +33 -0
  3. package/.kb/agents/sessions/session-1771549691485-nibqroq/turns.json +133 -0
  4. package/.kb/agents/sessions/session-1771550943182-dkwqdjc/turns.json +130 -0
  5. package/.kb/agents/sessions/session-1771551564182-cr4bwzn/turns.json +349 -0
  6. package/.kb/agents/sessions/session-1771552401370-g4ztwd1/turns.json +206 -0
  7. package/.kb/commit/current/plan.json +248 -0
  8. package/.kb/commit/current/status.json +472 -0
  9. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/health.d.ts +10 -0
  10. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/health.js +215 -0
  11. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/health.js.map +1 -0
  12. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/list.d.ts +10 -0
  13. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/list.js +271 -0
  14. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/list.js.map +1 -0
  15. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/logs.d.ts +10 -0
  16. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/logs.js +230 -0
  17. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/logs.js.map +1 -0
  18. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/metrics.d.ts +10 -0
  19. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/metrics.js +228 -0
  20. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/metrics.js.map +1 -0
  21. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/run.d.ts +11 -0
  22. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/run.js +281 -0
  23. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/run.js.map +1 -0
  24. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/status.d.ts +10 -0
  25. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/status.js +266 -0
  26. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/status.js.map +1 -0
  27. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/workflow-run.d.ts +11 -0
  28. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/workflow-run.js +284 -0
  29. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/workflow-run.js.map +1 -0
  30. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/flags-WLdpSZl4.d.ts +77 -0
  31. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/index.d.ts +2 -0
  32. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/index.js +441 -0
  33. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/index.js.map +1 -0
  34. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/manifest.d.ts +196 -0
  35. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/manifest.js +441 -0
  36. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/manifest.js.map +1 -0
  37. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/cron-list-handler.d.ts +19 -0
  38. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/cron-list-handler.js +48 -0
  39. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/cron-list-handler.js.map +1 -0
  40. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/index.d.ts +6 -0
  41. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/index.js +170 -0
  42. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/index.js.map +1 -0
  43. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-cancel-handler.d.ts +12 -0
  44. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-cancel-handler.js +52 -0
  45. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-cancel-handler.js.map +1 -0
  46. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-detail-handler.d.ts +22 -0
  47. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-detail-handler.js +52 -0
  48. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-detail-handler.js.map +1 -0
  49. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-logs-handler.d.ts +13 -0
  50. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-logs-handler.js +52 -0
  51. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-logs-handler.js.map +1 -0
  52. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-steps-handler.d.ts +9 -0
  53. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-steps-handler.js +43 -0
  54. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-steps-handler.js.map +1 -0
  55. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/jobs-list-handler.d.ts +26 -0
  56. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/jobs-list-handler.js +63 -0
  57. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/jobs-list-handler.js.map +1 -0
  58. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/pending-approvals-handler.d.ts +9 -0
  59. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/pending-approvals-handler.js +46 -0
  60. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/pending-approvals-handler.js.map +1 -0
  61. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/resolve-approval-handler.d.ts +16 -0
  62. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/resolve-approval-handler.js +58 -0
  63. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/resolve-approval-handler.js.map +1 -0
  64. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/run-detail-handler.d.ts +9 -0
  65. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/run-detail-handler.js +46 -0
  66. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/run-detail-handler.js.map +1 -0
  67. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/runs-list-handler.d.ts +11 -0
  68. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/runs-list-handler.js +50 -0
  69. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/runs-list-handler.js.map +1 -0
  70. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/stats-handler.d.ts +7 -0
  71. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/stats-handler.js +39 -0
  72. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/stats-handler.js.map +1 -0
  73. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-detail-handler.d.ts +10 -0
  74. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-detail-handler.js +46 -0
  75. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-detail-handler.js.map +1 -0
  76. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-cancel-handler.d.ts +13 -0
  77. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-cancel-handler.js +47 -0
  78. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-cancel-handler.js.map +1 -0
  79. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-handler.d.ts +14 -0
  80. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-handler.js +53 -0
  81. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-handler.js.map +1 -0
  82. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-runs-handler.d.ts +13 -0
  83. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-runs-handler.js +54 -0
  84. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-runs-handler.js.map +1 -0
  85. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflows-list-handler.d.ts +12 -0
  86. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflows-list-handler.js +51 -0
  87. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflows-list-handler.js.map +1 -0
  88. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/logs-channel.d.ts +17 -0
  89. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/logs-channel.js +133 -0
  90. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/logs-channel.js.map +1 -0
  91. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/progress-channel.d.ts +11 -0
  92. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/progress-channel.js +46 -0
  93. package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/progress-channel.js.map +1 -0
  94. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/health.d.ts +10 -0
  95. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/health.js +215 -0
  96. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/health.js.map +1 -0
  97. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/list.d.ts +10 -0
  98. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/list.js +271 -0
  99. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/list.js.map +1 -0
  100. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/logs.d.ts +10 -0
  101. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/logs.js +230 -0
  102. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/logs.js.map +1 -0
  103. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/metrics.d.ts +10 -0
  104. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/metrics.js +228 -0
  105. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/metrics.js.map +1 -0
  106. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/run.d.ts +11 -0
  107. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/run.js +281 -0
  108. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/run.js.map +1 -0
  109. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/status.d.ts +10 -0
  110. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/status.js +266 -0
  111. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/status.js.map +1 -0
  112. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/workflow-run.d.ts +11 -0
  113. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/workflow-run.js +284 -0
  114. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/workflow-run.js.map +1 -0
  115. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/flags-WLdpSZl4.d.ts +77 -0
  116. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/index.d.ts +2 -0
  117. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/index.js +441 -0
  118. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/index.js.map +1 -0
  119. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/manifest.d.ts +196 -0
  120. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/manifest.js +441 -0
  121. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/manifest.js.map +1 -0
  122. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/cron-list-handler.d.ts +19 -0
  123. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/cron-list-handler.js +48 -0
  124. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/cron-list-handler.js.map +1 -0
  125. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/index.d.ts +6 -0
  126. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/index.js +170 -0
  127. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/index.js.map +1 -0
  128. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-cancel-handler.d.ts +12 -0
  129. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-cancel-handler.js +52 -0
  130. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-cancel-handler.js.map +1 -0
  131. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-detail-handler.d.ts +22 -0
  132. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-detail-handler.js +52 -0
  133. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-detail-handler.js.map +1 -0
  134. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-logs-handler.d.ts +13 -0
  135. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-logs-handler.js +52 -0
  136. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-logs-handler.js.map +1 -0
  137. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-steps-handler.d.ts +9 -0
  138. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-steps-handler.js +43 -0
  139. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-steps-handler.js.map +1 -0
  140. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/jobs-list-handler.d.ts +26 -0
  141. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/jobs-list-handler.js +63 -0
  142. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/jobs-list-handler.js.map +1 -0
  143. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/pending-approvals-handler.d.ts +9 -0
  144. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/pending-approvals-handler.js +46 -0
  145. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/pending-approvals-handler.js.map +1 -0
  146. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/resolve-approval-handler.d.ts +16 -0
  147. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/resolve-approval-handler.js +58 -0
  148. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/resolve-approval-handler.js.map +1 -0
  149. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/run-detail-handler.d.ts +9 -0
  150. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/run-detail-handler.js +46 -0
  151. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/run-detail-handler.js.map +1 -0
  152. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/runs-list-handler.d.ts +11 -0
  153. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/runs-list-handler.js +50 -0
  154. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/runs-list-handler.js.map +1 -0
  155. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/stats-handler.d.ts +7 -0
  156. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/stats-handler.js +39 -0
  157. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/stats-handler.js.map +1 -0
  158. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-detail-handler.d.ts +10 -0
  159. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-detail-handler.js +46 -0
  160. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-detail-handler.js.map +1 -0
  161. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-cancel-handler.d.ts +13 -0
  162. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-cancel-handler.js +47 -0
  163. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-cancel-handler.js.map +1 -0
  164. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-handler.d.ts +14 -0
  165. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-handler.js +53 -0
  166. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-handler.js.map +1 -0
  167. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-runs-handler.d.ts +13 -0
  168. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-runs-handler.js +54 -0
  169. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-runs-handler.js.map +1 -0
  170. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflows-list-handler.d.ts +12 -0
  171. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflows-list-handler.js +51 -0
  172. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflows-list-handler.js.map +1 -0
  173. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/logs-channel.d.ts +17 -0
  174. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/logs-channel.js +133 -0
  175. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/logs-channel.js.map +1 -0
  176. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/progress-channel.d.ts +11 -0
  177. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/progress-channel.js +46 -0
  178. package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/progress-channel.js.map +1 -0
  179. package/README.md +54 -0
  180. package/eslint.config.js +23 -0
  181. package/package.json +72 -0
  182. package/src/.kb/agents/sessions/session-1773470583796-ximqnfk/kpi-baseline.json +17 -0
  183. package/src/.kb/memory/sessions/session-1773470583796-ximqnfk/fact-sheet.json +1 -0
  184. package/src/bootstrap.ts +233 -0
  185. package/src/diagnostics/header-debug.ts +37 -0
  186. package/src/events/hub.ts +60 -0
  187. package/src/events/index.ts +1 -0
  188. package/src/index.ts +13 -0
  189. package/src/manifest.ts +21 -0
  190. package/src/middleware/__tests__/cache.spec.ts +184 -0
  191. package/src/middleware/__tests__/envelope.spec.ts +268 -0
  192. package/src/middleware/__tests__/metrics.redis.spec.ts +64 -0
  193. package/src/middleware/__tests__/rate-limit.spec.ts +283 -0
  194. package/src/middleware/__tests__/request-id.spec.ts +160 -0
  195. package/src/middleware/cache.ts +67 -0
  196. package/src/middleware/envelope.ts +136 -0
  197. package/src/middleware/error-guard.ts +69 -0
  198. package/src/middleware/index.ts +37 -0
  199. package/src/middleware/metrics.ts +810 -0
  200. package/src/middleware/mock-mode.ts +44 -0
  201. package/src/middleware/prom-metrics.ts +284 -0
  202. package/src/middleware/rate-limit-routes.ts +73 -0
  203. package/src/middleware/rate-limit.ts +72 -0
  204. package/src/middleware/request-id.ts +41 -0
  205. package/src/middleware/request-timeout.ts +69 -0
  206. package/src/middleware/security-headers.ts +24 -0
  207. package/src/middleware/startup-guard.ts +107 -0
  208. package/src/observability/service-contract.ts +192 -0
  209. package/src/plugins/index.ts +118 -0
  210. package/src/routes/__tests__/adapters-date-helpers.spec.ts +332 -0
  211. package/src/routes/__tests__/adapters.spec.ts +680 -0
  212. package/src/routes/__tests__/cache.spec.ts +264 -0
  213. package/src/routes/__tests__/events.spec.ts +434 -0
  214. package/src/routes/__tests__/health.spec.ts +414 -0
  215. package/src/routes/__tests__/logs.spec.ts +494 -0
  216. package/src/routes/__tests__/observability.spec.ts +392 -0
  217. package/src/routes/__tests__/plugins.spec.ts +931 -0
  218. package/src/routes/__tests__/workflow-management.spec.ts +698 -0
  219. package/src/routes/__tests__/workflows.spec.ts +204 -0
  220. package/src/routes/adapter-call.ts +220 -0
  221. package/src/routes/adapters.ts +1206 -0
  222. package/src/routes/analytics.ts +328 -0
  223. package/src/routes/cache.ts +104 -0
  224. package/src/routes/debug-routes.ts +95 -0
  225. package/src/routes/events.ts +115 -0
  226. package/src/routes/health.ts +348 -0
  227. package/src/routes/hello.ts +47 -0
  228. package/src/routes/index.ts +395 -0
  229. package/src/routes/jobs.ts +253 -0
  230. package/src/routes/logs.ts +718 -0
  231. package/src/routes/metrics.ts +120 -0
  232. package/src/routes/observability.ts +1388 -0
  233. package/src/routes/openapi.ts +101 -0
  234. package/src/routes/platform.ts +143 -0
  235. package/src/routes/plugins.ts +998 -0
  236. package/src/routes/readiness.ts +83 -0
  237. package/src/routes/workflow-management.ts +469 -0
  238. package/src/routes/workflows.ts +256 -0
  239. package/src/server.ts +203 -0
  240. package/src/services/historical-metrics.ts +496 -0
  241. package/src/services/incident-analyzer.ts +289 -0
  242. package/src/services/incident-detector.ts +691 -0
  243. package/src/services/incident-schema.sql +76 -0
  244. package/src/services/incident-storage.ts +593 -0
  245. package/src/services/system-metrics-collector.ts +244 -0
  246. package/src/types/fastify.d.ts +58 -0
  247. package/src/utils/__tests__/sse-auth.spec.ts +113 -0
  248. package/src/utils/path-helpers.ts +15 -0
  249. package/src/utils/repo.ts +42 -0
  250. package/src/utils/schema.ts +20 -0
  251. package/src/utils/sse-auth.ts +81 -0
  252. package/test-sandbox.sh +105 -0
  253. package/tsconfig.build.json +15 -0
  254. package/tsconfig.json +9 -0
  255. package/tsup.config.ts +18 -0
@@ -0,0 +1,810 @@
1
+ import { performance } from 'node:perf_hooks';
2
+ import type { FastifyInstance } from 'fastify';
3
+ type RedisStatus = {
4
+ enabled: boolean;
5
+ healthy: boolean;
6
+ roles: { publisher?: string | null; subscriber?: string | null; cache?: string | null };
7
+ };
8
+ import { OperationMetricsTracker } from '@kb-labs/shared-http';
9
+ import {
10
+ httpRequestDuration,
11
+ httpRequestsTotal,
12
+ httpErrorsTotal,
13
+ serviceActiveOperations,
14
+ serviceOperationDuration,
15
+ serviceOperationTotal,
16
+ tenantRequestsTotal,
17
+ tenantErrorsTotal,
18
+ tenantRequestDuration,
19
+ } from './prom-metrics';
20
+ import { platform } from '@kb-labs/core-runtime';
21
+
22
+ /**
23
+ * @module @kb-labs/rest-api-app/middleware/metrics
24
+ * Metrics collection middleware
25
+ */
26
+
27
+ /**
28
+ * Metrics data
29
+ */
30
+ type RedisRole = 'publisher' | 'subscriber' | 'cache';
31
+
32
+ interface RedisMetrics {
33
+ updates: number;
34
+ healthyTransitions: number;
35
+ unhealthyTransitions: number;
36
+ lastUpdateTs: number | null;
37
+ lastHealthyTs: number | null;
38
+ lastUnhealthyTs: number | null;
39
+ lastStatus: {
40
+ enabled: boolean;
41
+ healthy: boolean;
42
+ roles: Record<RedisRole, string | null>;
43
+ } | null;
44
+ roleStates: Record<RedisRole, Record<string, number>>;
45
+ }
46
+
47
+ interface Metrics {
48
+ requests: {
49
+ total: number;
50
+ active: number;
51
+ byMethod: Record<string, number>;
52
+ byStatus: Record<string, number>;
53
+ byRoute: Record<string, number>;
54
+ byTenant: Record<string, number>; // ← Multi-tenancy support
55
+ };
56
+ latency: {
57
+ total: number;
58
+ count: number;
59
+ min: number;
60
+ max: number;
61
+ average: number;
62
+ histogram: Map<string, RouteLatencyStats>;
63
+ };
64
+ errors: {
65
+ total: number;
66
+ byCode: Record<string, number>;
67
+ };
68
+ timestamps: {
69
+ startTime: number;
70
+ lastRequest: number;
71
+ };
72
+ headers: {
73
+ filteredInbound: number;
74
+ filteredOutbound: number;
75
+ sensitiveInbound: number;
76
+ validationErrors: number;
77
+ varyApplied: number;
78
+ dryRunDecisions: number;
79
+ byPlugin: Record<string, HeaderPluginMetrics>;
80
+ };
81
+ redis: RedisMetrics;
82
+ // ← Multi-tenancy support
83
+ perTenant: Map<string, {
84
+ total: number;
85
+ errors: number;
86
+ totalLatency: number;
87
+ }>;
88
+ }
89
+
90
+ type RouteLatencyStats = {
91
+ count: number;
92
+ total: number;
93
+ max: number;
94
+ byStatus: Record<string, number>;
95
+ };
96
+
97
+ type HeaderPluginMetrics = {
98
+ filteredInbound: number;
99
+ filteredOutbound: number;
100
+ validationErrors: number;
101
+ varyApplied: number;
102
+ sensitiveInbound: number;
103
+ };
104
+
105
+ type HeaderMetricsEntry = {
106
+ pluginId?: string;
107
+ routeId?: string;
108
+ filteredInbound: number;
109
+ filteredOutbound: number;
110
+ sensitiveInbound: number;
111
+ validationErrors: number;
112
+ varyApplied: number;
113
+ dryRun: boolean;
114
+ };
115
+
116
+ export type PluginsMetricsSnapshot = {
117
+ total: number;
118
+ succeeded: number;
119
+ failed: number;
120
+ elapsedMs: number;
121
+ };
122
+
123
+ type PluginsMetricsDetails = Record<string, {
124
+ routes: number;
125
+ status: 'ok' | 'failed';
126
+ durationMs: number;
127
+ lastError?: string;
128
+ }>;
129
+
130
+ class PluginsMetricsCollector {
131
+ private metrics: PluginsMetricsSnapshot & { details: PluginsMetricsDetails } = this.createDefault();
132
+
133
+ private createDefault(): PluginsMetricsSnapshot & { details: PluginsMetricsDetails } {
134
+ return {
135
+ total: 0,
136
+ succeeded: 0,
137
+ failed: 0,
138
+ elapsedMs: 0,
139
+ details: {},
140
+ };
141
+ }
142
+
143
+ reset(): void {
144
+ this.metrics = this.createDefault();
145
+ }
146
+
147
+ recordSuccess(pluginId: string, routes: number, durationMs: number): void {
148
+ this.metrics.total += 1;
149
+ this.metrics.succeeded += 1;
150
+ this.metrics.elapsedMs += durationMs;
151
+ this.metrics.details[pluginId] = {
152
+ routes,
153
+ status: 'ok',
154
+ durationMs,
155
+ };
156
+ }
157
+
158
+ recordFailure(pluginId: string, error: string): void {
159
+ this.metrics.total += 1;
160
+ this.metrics.failed += 1;
161
+ this.metrics.details[pluginId] = {
162
+ routes: 0,
163
+ status: 'failed',
164
+ durationMs: 0,
165
+ lastError: error,
166
+ };
167
+ }
168
+
169
+ getSnapshot(): PluginsMetricsSnapshot {
170
+ return {
171
+ total: this.metrics.total,
172
+ succeeded: this.metrics.succeeded,
173
+ failed: this.metrics.failed,
174
+ elapsedMs: Number(this.metrics.elapsedMs.toFixed(2)),
175
+ };
176
+ }
177
+
178
+ getDetails(): PluginsMetricsDetails {
179
+ return this.metrics.details;
180
+ }
181
+ }
182
+
183
+ export interface MetricsSnapshot {
184
+ requests: Metrics['requests'] & {
185
+ success: number;
186
+ clientErrors: number;
187
+ serverErrors: number;
188
+ };
189
+ latency: Omit<Metrics['latency'], 'histogram'> & {
190
+ histogram: Array<{
191
+ route: string;
192
+ count: number;
193
+ total: number;
194
+ max: number;
195
+ byStatus: Record<string, number>;
196
+ budgetMs: number | null;
197
+ pluginId?: string;
198
+ }>;
199
+ };
200
+ perPlugin: Array<{
201
+ pluginId?: string;
202
+ total: number;
203
+ totalDuration: number;
204
+ maxDuration: number;
205
+ statuses: Record<string, number>;
206
+ }>;
207
+ perTenant: Array<{
208
+ tenantId: string;
209
+ total: number;
210
+ errors: number;
211
+ avgLatencyMs: number;
212
+ }>;
213
+ errors: Metrics['errors'];
214
+ timestamps: Metrics['timestamps'];
215
+ headers: {
216
+ filteredInbound: number;
217
+ filteredOutbound: number;
218
+ sensitiveInbound: number;
219
+ validationErrors: number;
220
+ varyApplied: number;
221
+ dryRunDecisions: number;
222
+ perPlugin: Array<{
223
+ pluginId: string;
224
+ filteredInbound: number;
225
+ filteredOutbound: number;
226
+ validationErrors: number;
227
+ varyApplied: number;
228
+ sensitiveInbound: number;
229
+ }>;
230
+ };
231
+ redis: {
232
+ updates: number;
233
+ healthyTransitions: number;
234
+ unhealthyTransitions: number;
235
+ lastUpdateTs: number | null;
236
+ lastHealthyTs: number | null;
237
+ lastUnhealthyTs: number | null;
238
+ lastStatus: {
239
+ enabled: boolean;
240
+ healthy: boolean;
241
+ roles: Record<RedisRole, string | null>;
242
+ } | null;
243
+ roleStates: Array<{
244
+ role: RedisRole;
245
+ states: Array<{ state: string; count: number }>;
246
+ }>;
247
+ };
248
+ }
249
+
250
+ class MetricsCollector {
251
+ private metrics: Metrics = this.createMetrics();
252
+ private pluginsMetrics = new PluginsMetricsCollector();
253
+ private lastPluginSnapshot: PluginsMetricsSnapshot | null = null;
254
+ private pluginRouteBudgets = new Map<string, { budgetMs: number | null; pluginId?: string }>();
255
+
256
+ private createMetrics(): Metrics {
257
+ return {
258
+ requests: {
259
+ total: 0,
260
+ active: 0,
261
+ byMethod: {},
262
+ byStatus: {},
263
+ byRoute: {},
264
+ byTenant: {}, // ← Multi-tenancy support
265
+ },
266
+ latency: {
267
+ total: 0,
268
+ count: 0,
269
+ min: Infinity,
270
+ max: 0,
271
+ average: 0,
272
+ histogram: new Map<string, RouteLatencyStats>(),
273
+ },
274
+ errors: {
275
+ total: 0,
276
+ byCode: {},
277
+ },
278
+ timestamps: {
279
+ startTime: Date.now(),
280
+ lastRequest: Date.now(),
281
+ },
282
+ headers: {
283
+ filteredInbound: 0,
284
+ filteredOutbound: 0,
285
+ sensitiveInbound: 0,
286
+ validationErrors: 0,
287
+ varyApplied: 0,
288
+ dryRunDecisions: 0,
289
+ byPlugin: {},
290
+ },
291
+ redis: this.createRedisMetrics(),
292
+ perTenant: new Map(), // ← Multi-tenancy support
293
+ };
294
+ }
295
+
296
+ private createRedisMetrics(): RedisMetrics {
297
+ return {
298
+ updates: 0,
299
+ healthyTransitions: 0,
300
+ unhealthyTransitions: 0,
301
+ lastUpdateTs: null,
302
+ lastHealthyTs: null,
303
+ lastUnhealthyTs: null,
304
+ lastStatus: null,
305
+ roleStates: {
306
+ publisher: {},
307
+ subscriber: {},
308
+ cache: {},
309
+ },
310
+ };
311
+ }
312
+
313
+ recordRequest(method: string, route: string, statusCode: number, durationMs: number, tenantId?: string): void {
314
+ this.metrics.requests.total++;
315
+ this.metrics.requests.byMethod[method] = (this.metrics.requests.byMethod[method] || 0) + 1;
316
+ const statusGroup = `${Math.floor(statusCode / 100)}xx`;
317
+ this.metrics.requests.byStatus[statusGroup] = (this.metrics.requests.byStatus[statusGroup] || 0) + 1;
318
+
319
+ const normalizedRoute = normalizeRoute(route);
320
+ if (normalizedRoute) {
321
+ const routeBucket = `${method} ${normalizedRoute}`;
322
+ this.metrics.requests.byRoute[routeBucket] = (this.metrics.requests.byRoute[routeBucket] || 0) + 1;
323
+ updateLatencyHistogram(this.metrics.latency.histogram, routeBucket, durationMs, statusCode);
324
+ }
325
+
326
+ // ← Multi-tenancy support: track by tenant
327
+ if (tenantId) {
328
+ this.metrics.requests.byTenant[tenantId] = (this.metrics.requests.byTenant[tenantId] || 0) + 1;
329
+
330
+ const tenantStats = this.metrics.perTenant.get(tenantId) || {
331
+ total: 0,
332
+ errors: 0,
333
+ totalLatency: 0,
334
+ };
335
+ tenantStats.total++;
336
+ tenantStats.totalLatency += durationMs;
337
+ if (statusCode >= 400) {
338
+ tenantStats.errors++;
339
+ }
340
+ this.metrics.perTenant.set(tenantId, tenantStats);
341
+ }
342
+
343
+ this.metrics.latency.count++;
344
+ this.metrics.latency.total += durationMs;
345
+ this.metrics.latency.min = Math.min(this.metrics.latency.min, durationMs);
346
+ this.metrics.latency.max = Math.max(this.metrics.latency.max, durationMs);
347
+ this.metrics.latency.average = this.metrics.latency.total / this.metrics.latency.count;
348
+ this.metrics.timestamps.lastRequest = Date.now();
349
+ }
350
+
351
+ incrementActiveRequests(): number {
352
+ this.metrics.requests.active += 1;
353
+ return this.metrics.requests.active;
354
+ }
355
+
356
+ decrementActiveRequests(): number {
357
+ this.metrics.requests.active = Math.max(0, this.metrics.requests.active - 1);
358
+ return this.metrics.requests.active;
359
+ }
360
+
361
+ getActiveRequests(): number {
362
+ return this.metrics.requests.active;
363
+ }
364
+
365
+ recordError(errorCode: string): void {
366
+ this.metrics.errors.total++;
367
+ this.metrics.errors.byCode[errorCode] = (this.metrics.errors.byCode[errorCode] || 0) + 1;
368
+ }
369
+
370
+ recordHeaderMetrics(entry: HeaderMetricsEntry): void {
371
+ const headers = this.metrics.headers;
372
+ headers.filteredInbound += Math.max(0, entry.filteredInbound);
373
+ headers.filteredOutbound += Math.max(0, entry.filteredOutbound);
374
+ headers.sensitiveInbound += Math.max(0, entry.sensitiveInbound);
375
+ headers.validationErrors += Math.max(0, entry.validationErrors);
376
+ headers.varyApplied += Math.max(0, entry.varyApplied);
377
+ if (entry.dryRun) {
378
+ headers.dryRunDecisions += Math.max(
379
+ 0,
380
+ entry.filteredInbound + entry.filteredOutbound + entry.validationErrors
381
+ );
382
+ }
383
+
384
+ if (entry.pluginId) {
385
+ const existing: HeaderPluginMetrics = headers.byPlugin[entry.pluginId] ?? {
386
+ filteredInbound: 0,
387
+ filteredOutbound: 0,
388
+ validationErrors: 0,
389
+ varyApplied: 0,
390
+ sensitiveInbound: 0,
391
+ };
392
+ existing.filteredInbound += Math.max(0, entry.filteredInbound);
393
+ existing.filteredOutbound += Math.max(0, entry.filteredOutbound);
394
+ existing.validationErrors += Math.max(0, entry.validationErrors);
395
+ existing.varyApplied += Math.max(0, entry.varyApplied);
396
+ existing.sensitiveInbound += Math.max(0, entry.sensitiveInbound);
397
+ headers.byPlugin[entry.pluginId] = existing;
398
+ }
399
+ }
400
+
401
+ recordRedisStatus(status: RedisStatus | null): void {
402
+ if (!status) {
403
+ return;
404
+ }
405
+ const now = Date.now();
406
+ const redisMetrics = this.metrics.redis;
407
+ const previous = redisMetrics.lastStatus;
408
+
409
+ redisMetrics.updates += 1;
410
+ redisMetrics.lastUpdateTs = now;
411
+ if (status.healthy) {
412
+ redisMetrics.lastHealthyTs = now;
413
+ } else {
414
+ redisMetrics.lastUnhealthyTs = now;
415
+ }
416
+
417
+ if (previous && previous.healthy !== status.healthy) {
418
+ if (status.healthy) {
419
+ redisMetrics.healthyTransitions += 1;
420
+ } else {
421
+ redisMetrics.unhealthyTransitions += 1;
422
+ }
423
+ }
424
+
425
+ const normalizedRoles: Record<RedisRole, string | null> = {
426
+ publisher: status.roles.publisher ?? null,
427
+ subscriber: status.roles.subscriber ?? null,
428
+ cache: status.roles.cache ?? null,
429
+ };
430
+
431
+ (Object.keys(normalizedRoles) as RedisRole[]).forEach((role) => {
432
+ const state = normalizedRoles[role] ?? 'unknown';
433
+ const bucket = state ?? 'unknown';
434
+ redisMetrics.roleStates[role][bucket] =
435
+ (redisMetrics.roleStates[role][bucket] ?? 0) + 1;
436
+ });
437
+
438
+ redisMetrics.lastStatus = {
439
+ enabled: status.enabled,
440
+ healthy: status.healthy,
441
+ roles: normalizedRoles,
442
+ };
443
+ }
444
+
445
+ getMetrics(): MetricsSnapshot {
446
+ const avg = this.metrics.latency.count > 0
447
+ ? this.metrics.latency.total / this.metrics.latency.count
448
+ : 0;
449
+
450
+ const perPluginMap = new Map<string, {
451
+ pluginId?: string;
452
+ total: number;
453
+ totalDuration: number;
454
+ maxDuration: number;
455
+ statuses: Record<string, number>;
456
+ }>();
457
+
458
+ const histogramArray = Array.from(this.metrics.latency.histogram.entries()).map(([route, stats]) => {
459
+ const entry = this.pluginRouteBudgets.get(route);
460
+ const pluginId = entry?.pluginId;
461
+ if (pluginId) {
462
+ let aggregate = perPluginMap.get(pluginId);
463
+ if (!aggregate) {
464
+ aggregate = {
465
+ pluginId,
466
+ total: 0,
467
+ totalDuration: 0,
468
+ maxDuration: 0,
469
+ statuses: {},
470
+ };
471
+ perPluginMap.set(pluginId, aggregate);
472
+ }
473
+ aggregate.total += stats.count;
474
+ aggregate.totalDuration += stats.total;
475
+ aggregate.maxDuration = Math.max(aggregate.maxDuration, stats.max);
476
+ for (const [statusCode, count] of Object.entries(stats.byStatus)) {
477
+ aggregate.statuses[statusCode] = (aggregate.statuses[statusCode] || 0) + count;
478
+ }
479
+ }
480
+
481
+ return {
482
+ route,
483
+ count: stats.count,
484
+ total: stats.total,
485
+ max: stats.max,
486
+ byStatus: { ...stats.byStatus },
487
+ budgetMs: entry?.budgetMs ?? null,
488
+ pluginId,
489
+ };
490
+ });
491
+
492
+ const headerPerPlugin = Object.entries(this.metrics.headers.byPlugin).map(([pluginId, data]) => ({
493
+ pluginId,
494
+ filteredInbound: data.filteredInbound,
495
+ filteredOutbound: data.filteredOutbound,
496
+ validationErrors: data.validationErrors,
497
+ varyApplied: data.varyApplied,
498
+ sensitiveInbound: data.sensitiveInbound,
499
+ }));
500
+
501
+ const redisRoleStates = (Object.keys(this.metrics.redis.roleStates) as RedisRole[]).map((role) => ({
502
+ role,
503
+ states: Object.entries(this.metrics.redis.roleStates[role]).map(([state, count]) => ({
504
+ state,
505
+ count,
506
+ })),
507
+ }));
508
+
509
+ const lastRedisStatus = this.metrics.redis.lastStatus
510
+ ? {
511
+ enabled: this.metrics.redis.lastStatus.enabled,
512
+ healthy: this.metrics.redis.lastStatus.healthy,
513
+ roles: { ...this.metrics.redis.lastStatus.roles },
514
+ }
515
+ : null;
516
+
517
+ // Serialize perTenant Map to Array
518
+ const perTenantArray = Array.from(this.metrics.perTenant.entries()).map(([tenantId, stats]) => ({
519
+ tenantId,
520
+ total: stats.total,
521
+ errors: stats.errors,
522
+ avgLatencyMs: stats.total > 0 ? stats.totalLatency / stats.total : 0,
523
+ }));
524
+
525
+ // Calculate success/error counts from byStatus
526
+ const success = (this.metrics.requests.byStatus['2xx'] || 0) + (this.metrics.requests.byStatus['3xx'] || 0);
527
+ const clientErrors = this.metrics.requests.byStatus['4xx'] || 0;
528
+ const serverErrors = this.metrics.requests.byStatus['5xx'] || 0;
529
+
530
+ return {
531
+ requests: {
532
+ ...this.metrics.requests,
533
+ success,
534
+ clientErrors,
535
+ serverErrors,
536
+ },
537
+ latency: {
538
+ total: this.metrics.latency.total,
539
+ count: this.metrics.latency.count,
540
+ min: this.metrics.latency.min,
541
+ max: this.metrics.latency.max,
542
+ average: avg,
543
+ histogram: histogramArray,
544
+ },
545
+ perPlugin: Array.from(perPluginMap.values()),
546
+ perTenant: perTenantArray,
547
+ errors: { ...this.metrics.errors, byCode: { ...this.metrics.errors.byCode } },
548
+ timestamps: { ...this.metrics.timestamps },
549
+ headers: {
550
+ filteredInbound: this.metrics.headers.filteredInbound,
551
+ filteredOutbound: this.metrics.headers.filteredOutbound,
552
+ sensitiveInbound: this.metrics.headers.sensitiveInbound,
553
+ validationErrors: this.metrics.headers.validationErrors,
554
+ varyApplied: this.metrics.headers.varyApplied,
555
+ dryRunDecisions: this.metrics.headers.dryRunDecisions,
556
+ perPlugin: headerPerPlugin,
557
+ },
558
+ redis: {
559
+ updates: this.metrics.redis.updates,
560
+ healthyTransitions: this.metrics.redis.healthyTransitions,
561
+ unhealthyTransitions: this.metrics.redis.unhealthyTransitions,
562
+ lastUpdateTs: this.metrics.redis.lastUpdateTs,
563
+ lastHealthyTs: this.metrics.redis.lastHealthyTs,
564
+ lastUnhealthyTs: this.metrics.redis.lastUnhealthyTs,
565
+ lastStatus: lastRedisStatus,
566
+ roleStates: redisRoleStates,
567
+ },
568
+ };
569
+ }
570
+
571
+ reset(): void {
572
+ this.metrics = this.createMetrics();
573
+ this.pluginsMetrics.reset();
574
+ this.lastPluginSnapshot = null;
575
+ }
576
+
577
+ beginPluginMount(): PluginsMetricsCollector {
578
+ this.pluginsMetrics.reset();
579
+ return this.pluginsMetrics;
580
+ }
581
+
582
+ completePluginMount(logger: { info: (...args: any[]) => void; warn: (...args: any[]) => void }): PluginsMetricsSnapshot | null {
583
+ const snapshot = this.pluginsMetrics.getSnapshot();
584
+ this.lastPluginSnapshot = snapshot;
585
+ if (snapshot.total === 0) {
586
+ return snapshot;
587
+ }
588
+
589
+ logger.info('Plugin mount metrics', snapshot);
590
+ if (snapshot.failed > 0) {
591
+ logger.warn('Plugin mount failures', this.pluginsMetrics.getDetails());
592
+ }
593
+
594
+ this.pluginsMetrics.reset();
595
+ return snapshot;
596
+ }
597
+
598
+ getLastPluginMountSnapshot(): PluginsMetricsSnapshot | null {
599
+ return this.lastPluginSnapshot;
600
+ }
601
+
602
+ registerRouteBudget(
603
+ method: string,
604
+ routePath: string,
605
+ budgetMs: number | null | undefined,
606
+ pluginId?: string
607
+ ): void {
608
+ const normalized = normalizeRoute(routePath);
609
+ if (!normalized) {
610
+ return;
611
+ }
612
+ const bucket = `${method.toUpperCase()} ${normalized}`;
613
+ this.pluginRouteBudgets.set(bucket, { budgetMs: budgetMs ?? null, pluginId });
614
+ }
615
+
616
+ resetPluginRouteBudgets(): void {
617
+ this.pluginRouteBudgets.clear();
618
+ }
619
+
620
+ getRouteBudget(method: string, routePath: string): number | null {
621
+ const normalized = normalizeRoute(routePath);
622
+ if (!normalized) {
623
+ return null;
624
+ }
625
+ const bucket = `${method.toUpperCase()} ${normalized}`;
626
+ const entry = this.pluginRouteBudgets.get(bucket);
627
+ return entry ? entry.budgetMs ?? null : null;
628
+ }
629
+ }
630
+
631
+ export const metricsCollector = new MetricsCollector();
632
+ export const restDomainOperationMetrics = new OperationMetricsTracker();
633
+
634
+ /**
635
+ * Sampling strategy for high-frequency endpoints to prevent analytics overload.
636
+ *
637
+ * - /metrics: 1:100 (Prometheus scraping happens every few seconds)
638
+ * - /health, /ready, /observability/health, /observability/describe: 1:10
639
+ * - All other endpoints: 1:1 (track everything)
640
+ *
641
+ * Uses deterministic sampling based on request counter to ensure even distribution.
642
+ */
643
+ let requestCounter = 0;
644
+ function shouldSampleRequest(route: string): boolean {
645
+ requestCounter = (requestCounter + 1) % 100;
646
+
647
+ // High-frequency metrics endpoints (Prometheus scraping) - sample 1:100
648
+ if (route === '/api/v1/metrics') {
649
+ return requestCounter === 0; // Only track every 100th request
650
+ }
651
+
652
+ // Medium-frequency health and observability probes - sample 1:10
653
+ if (
654
+ route === '/api/v1/health' ||
655
+ route === '/api/v1/ready' ||
656
+ route === '/api/v1/observability/health' ||
657
+ route === '/api/v1/observability/describe'
658
+ ) {
659
+ return requestCounter % 10 === 0; // Only track every 10th request
660
+ }
661
+
662
+ // All other endpoints - track every request (1:1)
663
+ return true;
664
+ }
665
+
666
+ export function registerMetricsMiddleware(server: FastifyInstance): void {
667
+ server.addHook('onRequest', (request, _reply, done) => {
668
+ request.kbMetricsStart = performance.now();
669
+ serviceActiveOperations.set(metricsCollector.incrementActiveRequests());
670
+ done();
671
+ });
672
+
673
+ server.addHook('onResponse', (request, reply, done) => {
674
+ const start = request.kbMetricsStart ?? performance.now();
675
+ const duration = Math.max(performance.now() - start, 0);
676
+ const method = (request.method || request.raw.method || 'GET').toUpperCase();
677
+ const routePath = request.routeOptions?.url ?? request.url;
678
+
679
+ // Extract tenantId from header or env var
680
+ const tenantId = (request.headers['x-tenant-id'] as string | undefined) ?? process.env.KB_TENANT_ID ?? 'default';
681
+
682
+ // Log request completion with method + full URL + status code + duration
683
+ if ((request as any).kbLogger) {
684
+ const fullUrl = request.url;
685
+ const durationMs = Math.round(duration);
686
+ (request as any).kbLogger.info(`✓ ${method} ${fullUrl} ${reply.statusCode} ${durationMs}ms`, {
687
+ statusCode: reply.statusCode,
688
+ durationMs,
689
+ });
690
+ }
691
+
692
+ // Record to legacy collector
693
+ metricsCollector.recordRequest(method, routePath ?? request.url, reply.statusCode, duration, tenantId);
694
+ if (reply.statusCode >= 400) {
695
+ metricsCollector.recordError(String(reply.statusCode));
696
+ }
697
+
698
+ // Record to prom-client metrics
699
+ const normalizedRoute = normalizeRoute(routePath ?? request.url) ?? 'unknown';
700
+ const statusCode = String(reply.statusCode);
701
+ const operation = `http.${method} ${normalizedRoute}`;
702
+
703
+ // Record request duration histogram (automatically calculates p50/p95/p99)
704
+ httpRequestDuration.observe(
705
+ {
706
+ method,
707
+ route: normalizedRoute,
708
+ status_code: statusCode,
709
+ tenant: tenantId,
710
+ plugin: '', // Will be populated from route metadata if available
711
+ },
712
+ duration
713
+ );
714
+
715
+ // Record request counter
716
+ httpRequestsTotal.inc({
717
+ method,
718
+ route: normalizedRoute,
719
+ status_code: statusCode,
720
+ tenant: tenantId,
721
+ plugin: '',
722
+ });
723
+
724
+ // Record errors
725
+ if (reply.statusCode >= 400) {
726
+ httpErrorsTotal.inc({
727
+ status_code: statusCode,
728
+ error_code: String((reply as any).errorCode ?? reply.statusCode),
729
+ tenant: tenantId,
730
+ });
731
+ tenantErrorsTotal.inc({ tenant: tenantId });
732
+ }
733
+
734
+ serviceOperationTotal.inc({
735
+ operation,
736
+ status: reply.statusCode >= 400 ? 'error' : 'ok',
737
+ });
738
+ serviceOperationDuration.observe(
739
+ {
740
+ operation,
741
+ status: reply.statusCode >= 400 ? 'error' : 'ok',
742
+ },
743
+ duration,
744
+ );
745
+
746
+ // Record tenant metrics
747
+ tenantRequestsTotal.inc({ tenant: tenantId });
748
+ tenantRequestDuration.observe({ tenant: tenantId }, duration);
749
+
750
+ // Track to analytics for long-term storage (async, fire-and-forget)
751
+ // This enables quarterly/annual reports and endpoint usage analysis
752
+ // Skip OPTIONS (CORS preflight) - they don't carry useful information
753
+ if (method !== 'OPTIONS') {
754
+ const { analytics } = platform;
755
+ if (analytics) {
756
+ // Sample high-frequency probe endpoints (1:100 for metrics, 1:10 for health/ready/observability)
757
+ // This prevents overwhelming analytics with 142k+ ping-pong events
758
+ const shouldSample = shouldSampleRequest(normalizedRoute);
759
+
760
+ if (shouldSample) {
761
+ analytics.track('api.request', {
762
+ endpoint: normalizedRoute,
763
+ method,
764
+ statusCode: reply.statusCode,
765
+ durationMs: Math.round(duration),
766
+ tenantId,
767
+ pluginId: (request as any).kbPluginId ?? undefined,
768
+ clientVersion: request.headers['x-client-version'] as string | undefined,
769
+ userAgent: request.headers['user-agent'] as string | undefined,
770
+ requestId: request.id as string | undefined,
771
+ }).catch(() => {
772
+ // Silently ignore analytics errors - should not affect request handling
773
+ });
774
+ }
775
+ }
776
+ }
777
+
778
+ serviceActiveOperations.set(metricsCollector.decrementActiveRequests());
779
+ done();
780
+ });
781
+ }
782
+
783
+ function normalizeRoute(route: string): string | null {
784
+ if (!route) {
785
+ return null;
786
+ }
787
+ const routePath = route.split('?')[0];
788
+ if (!routePath) {
789
+ return null;
790
+ }
791
+ return routePath.replace(/\/[0-9a-fA-F-]{6,}/g, '/:id');
792
+ }
793
+
794
+ function updateLatencyHistogram(
795
+ histogram: Map<string, RouteLatencyStats>,
796
+ bucket: string,
797
+ durationMs: number,
798
+ statusCode: number
799
+ ): void {
800
+ let stats = histogram.get(bucket);
801
+ if (!stats) {
802
+ stats = { count: 0, total: 0, max: 0, byStatus: {} };
803
+ histogram.set(bucket, stats);
804
+ }
805
+ stats.count += 1;
806
+ stats.total += durationMs;
807
+ stats.max = Math.max(stats.max, durationMs);
808
+ const statusKey = `${statusCode}`;
809
+ stats.byStatus[statusKey] = (stats.byStatus[statusKey] || 0) + 1;
810
+ }