failproofai 1.0.1 → 1.0.2-beta.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 (389) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +6 -6
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
  24. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js +5 -4
  26. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
  29. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -3
  31. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/logout/route.js +4 -4
  33. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/status/route.js +4 -4
  35. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  38. package/.next/standalone/.next/server/app/audit/page.js +2 -2
  39. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/index.html +1 -1
  42. package/.next/standalone/.next/server/app/index.rsc +15 -15
  43. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  44. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  45. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  46. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/page.js +2 -2
  48. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +66 -22
  51. package/.next/standalone/.next/server/app/policies/page.js +5 -4
  52. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  55. package/.next/standalone/.next/server/app/project/[name]/page.js +2 -2
  56. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  59. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  64. package/.next/standalone/.next/server/app/projects/page.js +2 -2
  65. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  68. package/.next/standalone/.next/server/app/settings/page.js +1 -1
  69. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/chunks/{[externals]__1qzqcio._.js → [externals]__0e32-7c._.js} +1 -1
  72. package/.next/standalone/.next/server/chunks/[externals]__1s462yl._.js +3 -0
  73. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +3 -0
  74. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p8poxd._.js → [root-of-the-server]__0igxt32._.js} +2 -2
  76. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0neqlu-._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t9oqnn._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/_185gyk9._.js +3 -0
  83. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +5 -6
  84. package/.next/standalone/.next/server/chunks/lib_telemetry-id_ts_1k8gj9o._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_09d5c79._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js} +1 -1
  86. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1d2g73o._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js} +1 -1
  87. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/src_hooks_0xbw3bu._.js +12 -0
  89. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
  93. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0spkm68._.js → [root-of-the-server]__07gm3zl._.js} +21 -20
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
  99. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07tbbf3._.js → [root-of-the-server]__0zig5sv._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__192nhvh._.js +5 -0
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
  102. package/.next/standalone/.next/server/chunks/ssr/{_0lvyuv4._.js → _00e9tnf._.js} +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/ssr/{_0u5zauv._.js → _12l4a_k._.js} +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +13 -13
  108. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/src_hooks_08o7bqo._.js +10 -0
  112. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  113. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  114. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  115. package/.next/standalone/.next/server/middleware.js +1 -1
  116. package/.next/standalone/.next/server/pages/404.html +1 -1
  117. package/.next/standalone/.next/server/pages/500.html +1 -1
  118. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  119. package/.next/standalone/.next/server/server-reference-manifest.json +71 -27
  120. package/.next/standalone/.next/static/chunks/{37fcpc36x9gpe.js → 0eqky-7n7dgdm.js} +1 -1
  121. package/.next/standalone/.next/static/chunks/{1ide1wl2f7zi1.js → 0sa9v5mhc8861.js} +13 -13
  122. package/.next/standalone/.next/static/chunks/1mfzsja6ftim5.css +2 -0
  123. package/.next/standalone/.next/static/chunks/{3k7mxbp9hiom0.js → 1nuqytbta44af.js} +1 -1
  124. package/.next/standalone/.next/static/chunks/1sl2xmn9inx3y.js +6 -0
  125. package/.next/standalone/.next/static/chunks/{2_2lhuojq2xop.js → 2aawsjld9ied_.js} +1 -1
  126. package/.next/standalone/.next/static/chunks/2gn7ut4svofm2.js +1 -0
  127. package/.next/standalone/.next/static/chunks/{31s1smp-g7gks.js → 2hs7ycvsav7v2.js} +1 -1
  128. package/.next/standalone/.next/static/chunks/{0k5050ezjvv49.js → 2i2yt3hzjredm.js} +1 -1
  129. package/.next/standalone/.next/static/chunks/{3c808qrfcq0im.js → 35xg6retpe6rp.js} +1 -1
  130. package/.next/standalone/.next/static/chunks/{2zpp1we2jv6tb.js → 3y54nw0i7dsjl.js} +1 -1
  131. package/.next/standalone/SECURITY.md +5 -4
  132. package/.next/standalone/app/actions/get-hooks-config.ts +108 -17
  133. package/.next/standalone/app/actions/pack-actions.ts +153 -0
  134. package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
  135. package/.next/standalone/app/audit/_components/share-templates.ts +13 -13
  136. package/.next/standalone/app/policies/hooks-client.tsx +320 -14
  137. package/.next/standalone/components/navbar.tsx +2 -28
  138. package/.next/standalone/components/reach-developers.tsx +8 -1
  139. package/.next/standalone/docs-old/agenteye/python-sdk-skill.mdx +1 -1
  140. package/.next/standalone/docs-old/agenteye/python-sdk.mdx +59 -35
  141. package/.next/standalone/docs-old/i18n/README.ar.md +1 -0
  142. package/.next/standalone/docs-old/i18n/README.de.md +1 -0
  143. package/.next/standalone/docs-old/i18n/README.es.md +1 -0
  144. package/.next/standalone/docs-old/i18n/README.fr.md +1 -0
  145. package/.next/standalone/docs-old/i18n/README.he.md +1 -0
  146. package/.next/standalone/docs-old/i18n/README.hi.md +1 -0
  147. package/.next/standalone/docs-old/i18n/README.it.md +1 -0
  148. package/.next/standalone/docs-old/i18n/README.ja.md +1 -0
  149. package/.next/standalone/docs-old/i18n/README.ko.md +1 -0
  150. package/.next/standalone/docs-old/i18n/README.pt-br.md +1 -0
  151. package/.next/standalone/docs-old/i18n/README.ru.md +1 -0
  152. package/.next/standalone/docs-old/i18n/README.tr.md +1 -0
  153. package/.next/standalone/docs-old/i18n/README.vi.md +1 -0
  154. package/.next/standalone/docs-old/i18n/README.zh.md +1 -0
  155. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +431 -0
  156. package/.next/standalone/fp-cloud-cli/LICENSE +42 -0
  157. package/.next/standalone/fp-cloud-cli/MANIFEST.in +10 -0
  158. package/.next/standalone/fp-cloud-cli/README.md +259 -0
  159. package/.next/standalone/fp-cloud-cli/fp_cli/__init__.py +10 -0
  160. package/.next/standalone/fp-cloud-cli/fp_cli/__main__.py +4 -0
  161. package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +64 -0
  162. package/.next/standalone/fp-cloud-cli/fp_cli/_context.py +332 -0
  163. package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -0
  164. package/.next/standalone/fp-cloud-cli/fp_cli/analytics.py +432 -0
  165. package/.next/standalone/fp-cloud-cli/fp_cli/analytics_config.py +77 -0
  166. package/.next/standalone/fp-cloud-cli/fp_cli/analytics_registry.py +83 -0
  167. package/.next/standalone/fp-cloud-cli/fp_cli/app.py +492 -0
  168. package/.next/standalone/fp-cloud-cli/fp_cli/auth.py +160 -0
  169. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +1694 -0
  170. package/.next/standalone/fp-cloud-cli/fp_cli/commands/__init__.py +0 -0
  171. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +214 -0
  172. package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +407 -0
  173. package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +445 -0
  174. package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +1054 -0
  175. package/.next/standalone/fp-cloud-cli/fp_cli/commands/auth_cmds.py +512 -0
  176. package/.next/standalone/fp-cloud-cli/fp_cli/commands/errors_cmds.py +190 -0
  177. package/.next/standalone/fp-cloud-cli/fp_cli/commands/evals_cmds.py +161 -0
  178. package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +159 -0
  179. package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +416 -0
  180. package/.next/standalone/fp-cloud-cli/fp_cli/commands/guardrails_cmds.py +148 -0
  181. package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +472 -0
  182. package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +407 -0
  183. package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +63 -0
  184. package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +319 -0
  185. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +499 -0
  186. package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +378 -0
  187. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +151 -0
  188. package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +150 -0
  189. package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +35 -0
  190. package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +404 -0
  191. package/.next/standalone/fp-cloud-cli/fp_cli/config.py +330 -0
  192. package/.next/standalone/fp-cloud-cli/fp_cli/dates.py +78 -0
  193. package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +345 -0
  194. package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +98 -0
  195. package/.next/standalone/fp-cloud-cli/fp_cli/models.py +891 -0
  196. package/.next/standalone/fp-cloud-cli/fp_cli/orgs.py +30 -0
  197. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +6593 -0
  198. package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +208 -0
  199. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +290 -0
  200. package/.next/standalone/fp-cloud-cli/fp_cli/py.typed +0 -0
  201. package/.next/standalone/fp-cloud-cli/fp_cli/select.py +322 -0
  202. package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +53 -0
  203. package/.next/standalone/fp-cloud-cli/pyproject.toml +106 -0
  204. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +302 -0
  205. package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +8 -0
  206. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +254 -0
  207. package/.next/standalone/fp-cloud-cli/tests/__init__.py +0 -0
  208. package/.next/standalone/fp-cloud-cli/tests/conftest.py +92 -0
  209. package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +644 -0
  210. package/.next/standalone/fp-cloud-cli/tests/test_analytics.py +385 -0
  211. package/.next/standalone/fp-cloud-cli/tests/test_audits.py +950 -0
  212. package/.next/standalone/fp-cloud-cli/tests/test_auth.py +98 -0
  213. package/.next/standalone/fp-cloud-cli/tests/test_auth_mode.py +272 -0
  214. package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +118 -0
  215. package/.next/standalone/fp-cloud-cli/tests/test_client.py +299 -0
  216. package/.next/standalone/fp-cloud-cli/tests/test_commands.py +814 -0
  217. package/.next/standalone/fp-cloud-cli/tests/test_config.py +99 -0
  218. package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +340 -0
  219. package/.next/standalone/fp-cloud-cli/tests/test_dates.py +76 -0
  220. package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +481 -0
  221. package/.next/standalone/fp-cloud-cli/tests/test_facets.py +170 -0
  222. package/.next/standalone/fp-cloud-cli/tests/test_failproofai_home.py +722 -0
  223. package/.next/standalone/fp-cloud-cli/tests/test_fp_home_contract.py +198 -0
  224. package/.next/standalone/fp-cloud-cli/tests/test_hardening.py +186 -0
  225. package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +127 -0
  226. package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +496 -0
  227. package/.next/standalone/fp-cloud-cli/tests/test_list.py +59 -0
  228. package/.next/standalone/fp-cloud-cli/tests/test_multivalue.py +183 -0
  229. package/.next/standalone/fp-cloud-cli/tests/test_no_customer_identifiers.py +168 -0
  230. package/.next/standalone/fp-cloud-cli/tests/test_operator.py +492 -0
  231. package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +692 -0
  232. package/.next/standalone/fp-cloud-cli/tests/test_output.py +1732 -0
  233. package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +191 -0
  234. package/.next/standalone/fp-cloud-cli/tests/test_readme_matches_reality.py +125 -0
  235. package/.next/standalone/fp-cloud-cli/tests/test_review_fixes.py +161 -0
  236. package/.next/standalone/fp-cloud-cli/tests/test_telemetry_completeness.py +90 -0
  237. package/.next/standalone/fp-cloud-cli/tests/test_usage.py +115 -0
  238. package/.next/standalone/fp-cloud-cli/tests/test_v1_origin_diagnostic.py +70 -0
  239. package/.next/standalone/fp-cloud-cli/tests/test_v1_routing.py +214 -0
  240. package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +104 -0
  241. package/.next/standalone/fp-cloud-cli/uv.lock +552 -0
  242. package/.next/standalone/package.json +29 -22
  243. package/.next/standalone/sdk/python/CHANGELOG.md +70 -0
  244. package/.next/standalone/sdk/python/LICENSE +42 -0
  245. package/.next/standalone/sdk/python/MANIFEST.in +21 -0
  246. package/.next/standalone/sdk/python/README.md +466 -0
  247. package/.next/standalone/sdk/python/docs/README.md +249 -0
  248. package/.next/standalone/sdk/python/docs/_shared/README.md +43 -0
  249. package/.next/standalone/sdk/python/docs/_shared/__init__.py +195 -0
  250. package/.next/standalone/sdk/python/docs/crewai/README.md +328 -0
  251. package/.next/standalone/sdk/python/docs/crewai/examples/quickstart.py +61 -0
  252. package/.next/standalone/sdk/python/docs/crewai/examples/research_crew.py +92 -0
  253. package/.next/standalone/sdk/python/docs/langgraph/README.md +481 -0
  254. package/.next/standalone/sdk/python/docs/langgraph/examples/quickstart.py +51 -0
  255. package/.next/standalone/sdk/python/docs/langgraph/examples/research_agent.py +86 -0
  256. package/.next/standalone/sdk/python/docs/langgraph/examples/supervisor_handoff.py +83 -0
  257. package/.next/standalone/sdk/python/docs/llama_index/README.md +332 -0
  258. package/.next/standalone/sdk/python/docs/llama_index/examples/quickstart.py +62 -0
  259. package/.next/standalone/sdk/python/docs/llama_index/examples/research_agent.py +93 -0
  260. package/.next/standalone/sdk/python/docs/manual/README.md +491 -0
  261. package/.next/standalone/sdk/python/docs/manual/examples/quickstart.py +57 -0
  262. package/.next/standalone/sdk/python/docs/manual/examples/research_agent.py +133 -0
  263. package/.next/standalone/sdk/python/docs/pydantic_ai/README.md +323 -0
  264. package/.next/standalone/sdk/python/docs/pydantic_ai/examples/quickstart.py +48 -0
  265. package/.next/standalone/sdk/python/docs/pydantic_ai/examples/research_agent.py +94 -0
  266. package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +131 -0
  267. package/.next/standalone/sdk/python/failproofai_sdk/_context.py +218 -0
  268. package/.next/standalone/sdk/python/failproofai_sdk/_environment.py +78 -0
  269. package/.next/standalone/sdk/python/failproofai_sdk/_events.py +752 -0
  270. package/.next/standalone/sdk/python/failproofai_sdk/_resolver.py +118 -0
  271. package/.next/standalone/sdk/python/failproofai_sdk/_runtime.py +21 -0
  272. package/.next/standalone/sdk/python/failproofai_sdk/_schema.py +317 -0
  273. package/.next/standalone/sdk/python/failproofai_sdk/_scopes.py +422 -0
  274. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -0
  275. package/.next/standalone/sdk/python/failproofai_sdk/_writer.py +750 -0
  276. package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +274 -0
  277. package/.next/standalone/sdk/python/failproofai_sdk/integrations/_compat.py +247 -0
  278. package/.next/standalone/sdk/python/failproofai_sdk/integrations/_core.py +1076 -0
  279. package/.next/standalone/sdk/python/failproofai_sdk/integrations/crewai.py +1669 -0
  280. package/.next/standalone/sdk/python/failproofai_sdk/integrations/langchain.py +2151 -0
  281. package/.next/standalone/sdk/python/failproofai_sdk/integrations/llama_index.py +1588 -0
  282. package/.next/standalone/sdk/python/failproofai_sdk/integrations/pydantic_ai.py +977 -0
  283. package/.next/standalone/sdk/python/failproofai_sdk/py.typed +0 -0
  284. package/.next/standalone/sdk/python/pyproject.toml +92 -0
  285. package/.next/standalone/sdk/python/skill/SKILL.md +399 -0
  286. package/.next/standalone/sdk/python/skill/agents/openai.yaml +8 -0
  287. package/.next/standalone/sdk/python/skill/references/events.md +234 -0
  288. package/.next/standalone/sdk/python/skill/references/frameworks.md +314 -0
  289. package/.next/standalone/sdk/python/skill/references/install.md +100 -0
  290. package/.next/standalone/sdk/python/skill/references/integration.md +352 -0
  291. package/.next/standalone/sdk/python/tests/__init__.py +0 -0
  292. package/.next/standalone/sdk/python/tests/conftest.py +169 -0
  293. package/.next/standalone/sdk/python/tests/integrations/__init__.py +6 -0
  294. package/.next/standalone/sdk/python/tests/integrations/test_crewai.py +1299 -0
  295. package/.next/standalone/sdk/python/tests/integrations/test_langchain.py +2338 -0
  296. package/.next/standalone/sdk/python/tests/integrations/test_llama_index.py +1576 -0
  297. package/.next/standalone/sdk/python/tests/integrations/test_pydantic_ai.py +1102 -0
  298. package/.next/standalone/sdk/python/tests/test_context.py +410 -0
  299. package/.next/standalone/sdk/python/tests/test_docs.py +411 -0
  300. package/.next/standalone/sdk/python/tests/test_durability.py +1352 -0
  301. package/.next/standalone/sdk/python/tests/test_encoding.py +571 -0
  302. package/.next/standalone/sdk/python/tests/test_integrations.py +1127 -0
  303. package/.next/standalone/sdk/python/tests/test_no_customer_identifiers.py +214 -0
  304. package/.next/standalone/sdk/python/tests/test_packaging.py +185 -0
  305. package/.next/standalone/sdk/python/tests/test_resolver_umbrella.py +178 -0
  306. package/.next/standalone/sdk/python/tests/test_scopes.py +487 -0
  307. package/.next/standalone/sdk/python/tests/test_sdk.py +923 -0
  308. package/.next/standalone/sdk/python/tests/test_server_contract.py +650 -0
  309. package/.next/standalone/sdk/python/tests/test_site_docs.py +371 -0
  310. package/.next/standalone/sdk/python/tests/test_skill_snippets.py +165 -0
  311. package/.next/standalone/sdk/python/tests/test_spool_contract.py +287 -0
  312. package/.next/standalone/sdk/python/tests/test_spool_creation.py +360 -0
  313. package/.next/standalone/sdk/python/tests/test_wire_format.py +279 -0
  314. package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +378 -0
  315. package/.next/standalone/sdk/python/uv.lock +6005 -0
  316. package/.next/standalone/server.js +1 -1
  317. package/README.md +77 -21
  318. package/bin/failproofai.mjs +145 -11
  319. package/dist/cli.mjs +4482 -2205
  320. package/dist/index.js +5 -5
  321. package/dist/worker.mjs +2399 -1978
  322. package/package.json +29 -22
  323. package/policy-pack/SHA256SUMS +2 -0
  324. package/policy-pack/failproofai-pack.json +732 -0
  325. package/policy-pack/failproofai-pack.mjs +2017 -0
  326. package/scripts/build-policy-pack.mjs +121 -0
  327. package/scripts/changelog-section.py +77 -0
  328. package/scripts/prune-standalone.mjs +3 -0
  329. package/scripts/python-version.py +232 -0
  330. package/scripts/translate-docs/cli.ts +21 -0
  331. package/scripts/translate-docs/mintlify-nav.ts +85 -13
  332. package/scripts/translate-docs/translator.ts +66 -11
  333. package/src/audit/cache.ts +40 -3
  334. package/src/audit/cli.ts +1 -1
  335. package/src/audit/index.ts +1 -1
  336. package/src/audit/replay.ts +89 -3
  337. package/src/audit/report.ts +5 -5
  338. package/src/audit/schedule-cli.ts +49 -53
  339. package/src/hooks/builtin-policies.ts +217 -579
  340. package/src/hooks/cloud-enrollment-cli.ts +66 -27
  341. package/src/hooks/cloud-managed-policies.ts +6 -1
  342. package/src/hooks/configure-wizard.ts +1 -1
  343. package/src/hooks/custom-hooks-loader.ts +89 -6
  344. package/src/hooks/fp-home.ts +92 -4
  345. package/src/hooks/fp-reset.ts +26 -2
  346. package/src/hooks/handler.ts +181 -11
  347. package/src/hooks/harness-cli.ts +79 -25
  348. package/src/hooks/hook-activity-store.ts +13 -3
  349. package/src/hooks/install-prompt.ts +25 -3
  350. package/src/hooks/manager.ts +418 -172
  351. package/src/hooks/migrations.ts +2 -2
  352. package/src/hooks/pack-cli.ts +505 -0
  353. package/src/hooks/pack-failclosed.ts +189 -0
  354. package/src/hooks/pack-manifest.ts +344 -0
  355. package/src/hooks/pack-store.ts +930 -0
  356. package/src/hooks/policy-catalog.ts +535 -0
  357. package/src/hooks/policy-evaluator.ts +24 -16
  358. package/src/hooks/policy-presets.ts +4 -2
  359. package/src/hooks/policy-registry.ts +8 -2
  360. package/src/hooks/policy-types.ts +26 -0
  361. package/src/hooks/session-pause-cli.ts +33 -9
  362. package/src/hooks/tui.ts +570 -0
  363. package/src/hooks/types.ts +15 -1
  364. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
  365. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
  366. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0agdw2a._.js +0 -3
  367. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d1u_na._.js +0 -3
  368. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
  369. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
  370. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +0 -3
  371. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
  372. package/.next/standalone/.next/server/chunks/_1ger0a2._.js +0 -3
  373. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
  374. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_1iiwm5e._.js +0 -3
  375. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kc8_v5._.js +0 -4
  376. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kgy9da._.js +0 -4
  377. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mhqk5a._.js +0 -4
  378. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pltpn8._.js +0 -4
  379. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qkcyf-._.js +0 -4
  380. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w0x_ok._.js +0 -4
  381. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_dfocn._.js +0 -4
  382. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bfex62._.js +0 -5
  383. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kzapg5._.js +0 -6
  384. package/.next/standalone/.next/static/chunks/36fw6yk1s6x_f.js +0 -1
  385. package/.next/standalone/.next/static/chunks/3tzq0260n17ae.js +0 -6
  386. package/.next/standalone/.next/static/chunks/3wq9x0c-7ld5d.css +0 -2
  387. /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_buildManifest.js +0 -0
  388. /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_clientMiddlewareManifest.js +0 -0
  389. /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_ssgManifest.js +0 -0
@@ -0,0 +1,644 @@
1
+ """Alerting: alert definitions + incident triage."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ import httpx
8
+ import respx
9
+
10
+ from fp_cli import output
11
+ from fp_cli.app import app
12
+ from fp_cli.commands import _write
13
+
14
+ BASE = "http://dash.test"
15
+
16
+
17
+ # --- alerts -----------------------------------------------------------------
18
+
19
+
20
+ @respx.mock
21
+ def test_alerts_list_json(logged_in, runner):
22
+ respx.get(f"{BASE}/api/alerts").mock(
23
+ return_value=httpx.Response(200, json=[{"id": "a1", "name": "p95", "trigger_kind": "metric_threshold", "severity": "warning", "enabled": True, "open_incidents": 0}])
24
+ )
25
+ result = runner.invoke(app, ["--json", "alerts", "list"])
26
+ assert result.exit_code == 0, result.output
27
+ assert json.loads(result.stdout)["alerts"][0]["id"] == "a1"
28
+
29
+
30
+ @respx.mock
31
+ def test_alerts_list_human_boxed(logged_in, runner):
32
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[
33
+ {"id": "a1", "name": "live", "trigger_kind": "custom_sql", "severity": "critical", "enabled": True,
34
+ "open_incidents": 1, "created_at": "2026-06-28T00:00:00Z", "last_attempted_at": "2026-06-28T00:00:00Z"},
35
+ {"id": "a2", "name": "old", "trigger_kind": "metric_threshold", "severity": "warning", "enabled": False,
36
+ "open_incidents": 0, "created_at": "2026-06-20T00:00:00Z", "last_attempted_at": None},
37
+ ]))
38
+ result = runner.invoke(app, ["alerts", "list"])
39
+ assert result.exit_code == 0, result.output
40
+ out = result.stdout + result.stderr
41
+ assert "alerts" in out and "newest first" in out
42
+ for c in ("created", "name", "by", "trigger", "severity", "last alert"): # no status column
43
+ assert c in out
44
+ assert "status" not in out # the status column was removed
45
+ assert "never" in out # null last-alert
46
+ assert "on" in out and "off" in out # the on/off split is in the footer
47
+ assert "critical" in out and "warning" in out # footer severities
48
+
49
+
50
+ @respx.mock
51
+ def test_alerts_show_by_name_json(logged_in, runner):
52
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[_FULL_ALERT]))
53
+ result = runner.invoke(app, ["--json", "alerts", "show", "p95"]) # by NAME
54
+ assert result.exit_code == 0, result.output
55
+ assert json.loads(result.stdout)["trigger_spec"] == {"metric": "latency", "op": ">", "value": 100}
56
+
57
+
58
+ @respx.mock
59
+ def test_alerts_show_not_found_exits_6(logged_in, runner):
60
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[_FULL_ALERT]))
61
+ result = runner.invoke(app, ["alerts", "show", "ghost"])
62
+ assert result.exit_code == 6
63
+
64
+
65
+ @respx.mock
66
+ def test_alerts_show_human_cards(logged_in, runner):
67
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[_FULL_ALERT]))
68
+ result = runner.invoke(app, ["alerts", "show", "p95"])
69
+ assert result.exit_code == 0, result.output
70
+ out = result.stdout
71
+ assert "p95" in out and "warning" in out and "enabled" in out
72
+ assert "trigger" in out and "fire when" in out and "latency" in out # parsed metric_threshold
73
+ assert "evaluation" in out and "checks every" in out
74
+ assert "channels" in out and "email" in out
75
+
76
+
77
+ @respx.mock
78
+ def test_alerts_create_from_file(logged_in, runner, tmp_path):
79
+ payload = {"name": "p95", "trigger_kind": "metric_threshold", "trigger_spec": {"metric": "latency", "op": ">", "value": 100}, "severity": "warning", "eval_interval_secs": 60}
80
+ f = tmp_path / "alert.json"
81
+ f.write_text(json.dumps(payload))
82
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[])) # no name collision
83
+ route = respx.post(f"{BASE}/api/alerts").mock(return_value=httpx.Response(201, json={"id": "a9", "created_at": "t"}))
84
+ result = runner.invoke(app, ["--json", "alerts", "create", "p95", "--file", str(f)]) # name is positional
85
+ assert result.exit_code == 0, result.output
86
+ body = json.loads(route.calls.last.request.content)
87
+ assert body["name"] == "p95"
88
+ assert body["trigger_kind"] == "metric_threshold"
89
+
90
+
91
+ @respx.mock
92
+ def test_alerts_create_name_collision_exits_2(logged_in, runner):
93
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[{"id": "a1", "name": "errs"}]))
94
+ result = runner.invoke(app, ["--json", "alerts", "create", "errs", "--trigger-kind",
95
+ "metric_threshold", "--trigger-spec", '{"metric":"x","op":">","value":1}'])
96
+ assert result.exit_code == 2
97
+ assert "already exists" in json.loads(result.stdout)["error"]
98
+
99
+
100
+ @respx.mock
101
+ def test_alerts_create_human_renders_card(logged_in, runner, tmp_path):
102
+ # Human mode: pre-check (no collision) → POST → re-read the canonical alert → render the card.
103
+ payload = {"name": "errs", "trigger_kind": "metric_threshold",
104
+ "trigger_spec": {"metric": "error_count", "op": ">", "value": 50, "window_secs": 900},
105
+ "severity": "warning", "eval_interval_secs": 300}
106
+ f = tmp_path / "a.json"
107
+ f.write_text(json.dumps(payload))
108
+ respx.get(f"{BASE}/api/alerts").mock(side_effect=[
109
+ httpx.Response(200, json=[]), # pre-check: no collision
110
+ httpx.Response(200, json=[{"id": "a9", "name": "errs", "enabled": True, "trigger_kind": "metric_threshold",
111
+ "trigger_spec": payload["trigger_spec"], "severity": "warning",
112
+ "eval_interval_secs": 300, "min_breaches": 1, "eval_window": 1, "channels": []}]),
113
+ ])
114
+ respx.post(f"{BASE}/api/alerts").mock(return_value=httpx.Response(201, json={"id": "a9", "created_at": "t"}))
115
+ result = runner.invoke(app, ["alerts", "create", "errs", "--file", str(f)]) # name positional
116
+ assert result.exit_code == 0, result.output
117
+ out = result.stdout
118
+ assert "alert created" in out and "errs" in out
119
+ assert "fire when error_count > 50 over 15m" in out and "channels" in out
120
+
121
+
122
+ @respx.mock
123
+ def test_alerts_update_rename_collision_exits_2(logged_in, runner):
124
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[
125
+ _FULL_ALERT, {**_FULL_ALERT, "id": "a2", "name": "taken"}]))
126
+ result = runner.invoke(app, ["--json", "alerts", "update", "p95", "--name", "taken", "--yes"])
127
+ assert result.exit_code == 2
128
+ assert "already exists" in json.loads(result.stdout)["error"]
129
+
130
+
131
+ @respx.mock
132
+ def test_alerts_update_human_renders_card(logged_in, runner):
133
+ # First GET resolves the name (old state); the post-PUT re-fetch returns the new state.
134
+ respx.get(f"{BASE}/api/alerts").mock(side_effect=[
135
+ httpx.Response(200, json=[_FULL_ALERT]),
136
+ httpx.Response(200, json=[{**_FULL_ALERT, "severity": "critical"}]),
137
+ ])
138
+ respx.put(f"{BASE}/api/alerts/a1").mock(return_value=httpx.Response(200, json={"id": "a1", "updated_at": "t"}))
139
+ result = runner.invoke(app, ["alerts", "update", "p95", "--severity", "critical", "--yes"])
140
+ assert result.exit_code == 0, result.output
141
+ out = result.stdout
142
+ assert "alert updated" in out and "p95" in out
143
+ assert "critical" in out and "trigger" in out
144
+
145
+
146
+ def test_alerts_create_validation_local(logged_in, runner, tmp_path):
147
+ # eval_interval_secs below 30 fails locally before any HTTP call.
148
+ payload = {"name": "x", "trigger_kind": "metric_threshold", "trigger_spec": {}, "eval_interval_secs": 10}
149
+ f = tmp_path / "a.json"
150
+ f.write_text(json.dumps(payload))
151
+ result = runner.invoke(app, ["alerts", "create", "--file", str(f)])
152
+ assert result.exit_code == 2
153
+
154
+
155
+ def test_alerts_create_bad_trigger_kind(logged_in, runner):
156
+ result = runner.invoke(app, ["alerts", "create", "x", "--trigger-kind", "bogus", "--trigger-spec", "{}"])
157
+ assert result.exit_code == 2
158
+
159
+
160
+ _FULL_ALERT = {
161
+ "id": "a1",
162
+ "name": "p95",
163
+ "description": "latency guard",
164
+ "enabled": True,
165
+ "trigger_kind": "metric_threshold",
166
+ "trigger_spec": {"metric": "latency", "op": ">", "value": 100},
167
+ "min_breaches": 1,
168
+ "eval_window": 1,
169
+ "eval_interval_secs": 300,
170
+ "severity": "warning",
171
+ "channels": [{"kind": "email", "recipients": ["x@y.z"]}],
172
+ }
173
+
174
+
175
+ @respx.mock
176
+ def test_alerts_update_flag_only_by_name_resends_full_body(logged_in, runner):
177
+ # A flag-only edit resolves the NAME via the list, then PUTs the WHOLE alert back with just
178
+ # the changed field — the server's PUT is a full replace.
179
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[_FULL_ALERT]))
180
+ put = respx.put(f"{BASE}/api/alerts/a1").mock(return_value=httpx.Response(200, json={"id": "a1", "updated_at": "t"}))
181
+ result = runner.invoke(app, ["--json", "alerts", "update", "p95", "--description", "off-hours", "--yes"])
182
+ assert result.exit_code == 0, result.output
183
+ assert put.called
184
+ body = json.loads(put.calls.last.request.content)
185
+ assert body["description"] == "off-hours" # the changed field
186
+ assert body["name"] == "p95" # everything else carried over
187
+ assert body["trigger_kind"] == "metric_threshold"
188
+ assert body["trigger_spec"] == {"metric": "latency", "op": ">", "value": 100}
189
+ assert body["severity"] == "warning"
190
+ assert body["channels"] == [{"kind": "email", "recipients": ["x@y.z"]}]
191
+ assert body["enabled"] is True # preserved (no enable/disable flag any more)
192
+
193
+
194
+ @respx.mock
195
+ def test_alerts_update_flag_only_can_change_severity(logged_in, runner):
196
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[_FULL_ALERT]))
197
+ put = respx.put(f"{BASE}/api/alerts/a1").mock(return_value=httpx.Response(200, json={"id": "a1", "updated_at": "t"}))
198
+ result = runner.invoke(app, ["--json", "alerts", "update", "p95", "--severity", "critical", "--yes"])
199
+ assert result.exit_code == 0, result.output
200
+ body = json.loads(put.calls.last.request.content)
201
+ assert body["severity"] == "critical"
202
+ assert body["enabled"] is True # untouched, preserved from the existing alert
203
+
204
+
205
+ @respx.mock
206
+ def test_alerts_update_missing_alert_is_not_found(logged_in, runner):
207
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[_FULL_ALERT]))
208
+ put = respx.put(f"{BASE}/api/alerts/a1").mock(return_value=httpx.Response(200, json={}))
209
+ result = runner.invoke(app, ["alerts", "update", "nope", "--severity", "critical", "--yes"])
210
+ assert result.exit_code == 6, result.output
211
+ assert not put.called
212
+
213
+
214
+ @respx.mock
215
+ def test_alerts_update_with_file_is_full_replace(logged_in, runner, tmp_path):
216
+ # The --file path is a straight replace (the list read only resolves the name → id).
217
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[_FULL_ALERT]))
218
+ payload = {"name": "renamed", "trigger_kind": "metric_threshold", "trigger_spec": {"metric": "latency", "op": ">", "value": 1}, "severity": "info", "eval_interval_secs": 60}
219
+ f = tmp_path / "alert.json"
220
+ f.write_text(json.dumps(payload))
221
+ put = respx.put(f"{BASE}/api/alerts/a1").mock(return_value=httpx.Response(200, json={"id": "a1", "updated_at": "t"}))
222
+ result = runner.invoke(app, ["--json", "alerts", "update", "p95", "--file", str(f), "--yes"])
223
+ assert result.exit_code == 0, result.output
224
+ body = json.loads(put.calls.last.request.content)
225
+ assert body["name"] == "renamed"
226
+ assert body["severity"] == "info"
227
+
228
+
229
+ @respx.mock
230
+ def test_alerts_delete_by_name(logged_in, runner):
231
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[_FULL_ALERT]))
232
+ route = respx.delete(f"{BASE}/api/alerts/a1").mock(return_value=httpx.Response(204))
233
+ result = runner.invoke(app, ["--json", "alerts", "delete", "p95", "--yes"])
234
+ assert result.exit_code == 0, result.output
235
+ assert route.called
236
+ body = json.loads(result.stdout)
237
+ assert body["deleted"] is True and body["name"] == "p95"
238
+
239
+
240
+ @respx.mock
241
+ def test_alerts_delete_not_found_exits_6(logged_in, runner):
242
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[_FULL_ALERT]))
243
+ result = runner.invoke(app, ["alerts", "delete", "ghost", "--yes"])
244
+ assert result.exit_code == 6
245
+
246
+
247
+ @respx.mock
248
+ def test_alerts_test_sends_by_name(logged_in, runner):
249
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[_FULL_ALERT]))
250
+ respx.post(f"{BASE}/api/alerts/a1/test").mock(
251
+ return_value=httpx.Response(200, json={"ok": True, "synthetic_incident_id": "i1"})
252
+ )
253
+ result = runner.invoke(app, ["--json", "alerts", "test", "p95", "--yes"])
254
+ assert result.exit_code == 0, result.output
255
+ assert json.loads(result.stdout)["ok"] is True
256
+
257
+
258
+ @respx.mock
259
+ def test_alerts_test_human_renders_dispatch(logged_in, runner):
260
+ respx.get(f"{BASE}/api/alerts").mock(return_value=httpx.Response(200, json=[_FULL_ALERT]))
261
+ respx.post(f"{BASE}/api/alerts/a1/test").mock(
262
+ return_value=httpx.Response(200, json={"ok": True, "synthetic_incident_id": "i1"})
263
+ )
264
+ result = runner.invoke(app, ["alerts", "test", "p95", "--yes"])
265
+ assert result.exit_code == 0, result.output
266
+ out = result.stdout + result.stderr
267
+ assert "test notification sent for" in out and "p95" in out
268
+ assert "dispatched to" in out and "email" in out # _FULL_ALERT's email channel
269
+ assert "delivery isn't confirmed" in out # honest note (issue #183)
270
+
271
+
272
+ # --- incidents --------------------------------------------------------------
273
+
274
+
275
+ @respx.mock
276
+ def test_incidents_list_by_state(logged_in, runner):
277
+ route = respx.get(f"{BASE}/api/issues").mock(
278
+ return_value=httpx.Response(200, json=[{"id": "i1", "alert_name": "p95", "alert_severity": "warning", "state": "firing", "opened_at": "t", "assignees": []}])
279
+ )
280
+ result = runner.invoke(app, ["--json", "issues", "list", "--state", "firing"])
281
+ assert result.exit_code == 0, result.output
282
+ assert json.loads(result.stdout)["issues"][0]["id"] == "i1"
283
+ assert route.calls.last.request.url.params["state"] == "firing"
284
+
285
+
286
+ @respx.mock
287
+ def test_incidents_list_by_alert_uses_alert_path(logged_in, runner):
288
+ route = respx.get(f"{BASE}/api/alerts/a1/issues").mock(return_value=httpx.Response(200, json=[]))
289
+ result = runner.invoke(app, ["--json", "issues", "list", "--alert-id", "a1"])
290
+ assert result.exit_code == 0, result.output
291
+ assert route.called
292
+
293
+
294
+ @respx.mock
295
+ def test_incidents_count_json(logged_in, runner):
296
+ respx.get(f"{BASE}/api/issues/count").mock(return_value=httpx.Response(200, json={"count": 3}))
297
+ result = runner.invoke(app, ["--json", "issues", "count"])
298
+ assert result.exit_code == 0, result.output
299
+ assert json.loads(result.stdout)["count"] == 3
300
+
301
+
302
+ @respx.mock
303
+ def test_incident_ack(logged_in, runner):
304
+ route = respx.post(f"{BASE}/api/issues/i1/ack").mock(return_value=httpx.Response(200, json={}))
305
+ result = runner.invoke(app, ["--json", "issues", "ack", "i1"])
306
+ assert result.exit_code == 0, result.output
307
+ assert route.called
308
+
309
+
310
+ @respx.mock
311
+ def test_incident_assign_sends_array(logged_in, runner):
312
+ route = respx.post(f"{BASE}/api/issues/i1/assign").mock(return_value=httpx.Response(200, json={}))
313
+ result = runner.invoke(app, ["--json", "issues", "assign", "i1", "--assignee", "a@x.com", "--assignee", "b@x.com"])
314
+ assert result.exit_code == 0, result.output
315
+ assert json.loads(route.calls.last.request.content) == {"assignees": ["a@x.com", "b@x.com"]}
316
+
317
+
318
+ @respx.mock
319
+ def test_incident_comment_add_from_body(logged_in, runner):
320
+ respx.post(f"{BASE}/api/issues/i1/comments").mock(
321
+ return_value=httpx.Response(201, json={"id": "c1", "incident_id": "i1", "author_email": "me@test", "body": "hi", "created_at": "t"})
322
+ )
323
+ result = runner.invoke(app, ["--json", "issues", "comment-add", "i1", "--body", "hi"])
324
+ assert result.exit_code == 0, result.output
325
+ assert json.loads(result.stdout)["id"] == "c1"
326
+
327
+
328
+ @respx.mock
329
+ def test_incident_resolve_requires_yes(logged_in, runner):
330
+ route = respx.post(f"{BASE}/api/issues/i1/resolve").mock(return_value=httpx.Response(200, json={}))
331
+ result = runner.invoke(app, ["--json", "issues", "resolve", "i1", "--yes"])
332
+ assert result.exit_code == 0, result.output
333
+ assert route.called
334
+
335
+
336
+ @respx.mock
337
+ def test_incident_open_standalone(logged_in, runner):
338
+ respx.post(f"{BASE}/api/issues").mock(
339
+ return_value=httpx.Response(201, json={"id": "i9", "newly_opened": True, "state": "firing"})
340
+ )
341
+ result = runner.invoke(app, ["--json", "issues", "open", "--summary", "manual",
342
+ "--title", "checkout 500s", "--severity", "critical"])
343
+ assert result.exit_code == 0, result.output
344
+ assert json.loads(result.stdout)["id"] == "i9"
345
+ # The title has to reach the wire — the server REQUIRES it on the orphan
346
+ # path, and omitting it is what made this command 422 unconditionally.
347
+ sent = json.loads(respx.calls.last.request.content)
348
+ assert sent["title"] == "checkout 500s" and sent["severity"] == "critical"
349
+
350
+
351
+ @respx.mock
352
+ def test_incident_open_standalone_without_title_exits_2(logged_in, runner):
353
+ """A standalone open with no --title is rejected client-side, before the
354
+ request: the server can only answer 422, so spending a round-trip on a
355
+ known-bad body just turns a usage error into a server error."""
356
+ route = respx.post(f"{BASE}/api/issues").mock(
357
+ return_value=httpx.Response(201, json={"id": "i9"}))
358
+ result = runner.invoke(app, ["issues", "open", "--summary", "manual"])
359
+ assert result.exit_code == 2, result.output
360
+ assert "--title is required" in result.output
361
+ assert not route.called
362
+
363
+
364
+ @respx.mock
365
+ def test_incident_open_linked_uses_alert_path(logged_in, runner):
366
+ route = respx.post(f"{BASE}/api/alerts/a1/issues").mock(
367
+ return_value=httpx.Response(201, json={"id": "i9", "newly_opened": True, "state": "firing"})
368
+ )
369
+ result = runner.invoke(app, ["--json", "issues", "open", "--summary", "x", "--alert-id", "a1"])
370
+ assert result.exit_code == 0, result.output
371
+ assert route.called
372
+
373
+
374
+ # --- incidents: redesigned human UI + edge cases ----------------------------
375
+
376
+ _FULL_INCIDENT = {
377
+ "id": "1f5803aaaaaabbbbcccc000000009826",
378
+ "alert_id": "a1",
379
+ "alert_name": "p95 latency",
380
+ "alert_severity": "critical",
381
+ "state": "acknowledged",
382
+ "opened_at": "2026-06-28T00:00:00Z",
383
+ "acknowledged_by": "ops@example.com",
384
+ "assignees": ["a@example.com"],
385
+ "breach_summary": "p95 = 1240ms > 1000ms",
386
+ "comments": [
387
+ {"id": "c1", "author_email": "ops@example.com", "body": "looking into it", "created_at": "2026-06-28T00:01:00Z"},
388
+ {"id": "c2", "author_email": "x@example.com", "body": None, "created_at": "2026-06-28T00:02:00Z", "deleted_at": "2026-06-28T00:03:00Z"},
389
+ ],
390
+ "subscribers": [{"email": "ops@example.com", "source": "ack", "subscribed_at": "2026-06-28T00:01:00Z"}],
391
+ "activity": [{"kind": "opened", "actor": "system", "at": "2026-06-28T00:00:00Z"},
392
+ {"kind": "acknowledged", "actor": "ops@example.com", "at": "2026-06-28T00:01:00Z"}],
393
+ }
394
+
395
+
396
+ @respx.mock
397
+ def test_incidents_list_human_boxed(logged_in, runner):
398
+ respx.get(f"{BASE}/api/issues").mock(return_value=httpx.Response(200, json=[
399
+ {"id": "1f5803aaaaaabbbbcccc000000009826", "alert_name": "p95", "alert_severity": "critical",
400
+ "state": "firing", "opened_at": "2026-06-28T00:00:00Z", "assignees": ["a@example.com"]},
401
+ {"id": "z", "alert_name": None, "alert_severity": "info", "state": "resolved",
402
+ "opened_at": "2026-06-20T00:00:00Z", "assignees": []},
403
+ ]))
404
+ result = runner.invoke(app, ["issues", "list"])
405
+ assert result.exit_code == 0, result.output
406
+ out = result.stdout + result.stderr
407
+ assert "issues" in out
408
+ for c in ("alert", "severity", "state", "opened", "assignees"):
409
+ assert c in out
410
+ assert "1f58" in out # short id is the handle
411
+ assert "firing" in out and "resolved" in out # state words + footer distribution
412
+
413
+
414
+ @respx.mock
415
+ def test_incidents_list_invalid_state_exits_2(logged_in, runner):
416
+ result = runner.invoke(app, ["issues", "list", "--state", "bogus"])
417
+ assert result.exit_code == 2
418
+
419
+
420
+ @respx.mock
421
+ def test_incidents_list_limit_zero_exits_2(logged_in, runner):
422
+ result = runner.invoke(app, ["issues", "list", "--limit", "0"])
423
+ assert result.exit_code == 2
424
+
425
+
426
+ @respx.mock
427
+ def test_incidents_count_human_card(logged_in, runner):
428
+ respx.get(f"{BASE}/api/issues/count").mock(return_value=httpx.Response(200, json={"count": 4}))
429
+ result = runner.invoke(app, ["issues", "count", "--state", "firing"])
430
+ assert result.exit_code == 0, result.output
431
+ out = result.stdout
432
+ assert "issues" in out and "4" in out and "firing issues" in out
433
+
434
+
435
+ @respx.mock
436
+ def test_incidents_show_human_cards(logged_in, runner):
437
+ respx.get(f"{BASE}/api/issues/i1").mock(return_value=httpx.Response(200, json=_FULL_INCIDENT))
438
+ result = runner.invoke(app, ["issues", "show", "i1"])
439
+ assert result.exit_code == 0, result.output
440
+ out = result.stdout
441
+ assert "p95 latency" in out and "critical" in out and "acknowledged" in out
442
+ assert "breach" in out and "1240ms" in out
443
+ assert "comments" in out and "looking into it" in out and "(deleted)" in out
444
+ assert "subscribers" in out and "ops@example.com" in out
445
+ assert "activity" in out and "opened" in out and "system" in out
446
+
447
+
448
+ @respx.mock
449
+ def test_incidents_show_not_found_exits_6(logged_in, runner):
450
+ respx.get(f"{BASE}/api/issues/ghost").mock(return_value=httpx.Response(404, json={"error": "Not found."}))
451
+ result = runner.invoke(app, ["issues", "show", "ghost"])
452
+ assert result.exit_code == 6, result.output
453
+ assert "no issue" in result.stderr
454
+ assert "HTTP" not in (result.stdout + result.stderr) # never leak raw HTTP status
455
+
456
+
457
+ @respx.mock
458
+ def test_incidents_show_not_found_json(logged_in, runner):
459
+ respx.get(f"{BASE}/api/issues/ghost").mock(return_value=httpx.Response(404, json={"error": "Not found."}))
460
+ result = runner.invoke(app, ["--json", "issues", "show", "ghost"])
461
+ assert result.exit_code == 6
462
+ assert "no issue" in json.loads(result.stdout)["error"]
463
+
464
+
465
+ @respx.mock
466
+ def test_incidents_ack_human(logged_in, runner):
467
+ respx.post(f"{BASE}/api/issues/i1/ack").mock(return_value=httpx.Response(200, json={}))
468
+ result = runner.invoke(app, ["issues", "ack", "i1"])
469
+ assert result.exit_code == 0, result.output
470
+ assert "acknowledged issue" in result.stderr
471
+
472
+
473
+ @respx.mock
474
+ def test_incidents_assign_non_operator_clean_error(logged_in, runner):
475
+ respx.post(f"{BASE}/api/issues/i1/assign").mock(
476
+ return_value=httpx.Response(422, json={"error": "a@x.com is not an operator"})
477
+ )
478
+ result = runner.invoke(app, ["issues", "assign", "i1", "--assignee", "a@x.com"])
479
+ assert result.exit_code == 1, result.output # ApiError → exit 1
480
+ assert "not an operator" in result.stderr
481
+ assert "HTTP" not in (result.stdout + result.stderr)
482
+
483
+
484
+ @respx.mock
485
+ def test_incidents_assign_non_operator_json(logged_in, runner):
486
+ respx.post(f"{BASE}/api/issues/i1/assign").mock(
487
+ return_value=httpx.Response(422, json={"error": "a@x.com is not an operator"})
488
+ )
489
+ result = runner.invoke(app, ["--json", "issues", "assign", "i1", "--assignee", "a@x.com"])
490
+ assert result.exit_code == 1
491
+ body = json.loads(result.stdout)
492
+ assert "not an operator" in body["error"] and body["status"] == 422
493
+
494
+
495
+ @respx.mock
496
+ def test_incidents_resolve_human_yes(logged_in, runner):
497
+ respx.post(f"{BASE}/api/issues/i1/resolve").mock(return_value=httpx.Response(200, json={}))
498
+ result = runner.invoke(app, ["issues", "resolve", "i1", "--yes"])
499
+ assert result.exit_code == 0, result.output
500
+ assert "resolved issue" in result.stderr
501
+
502
+
503
+ @respx.mock
504
+ def test_incidents_resolve_declined_emits_the_json_cancel_envelope(logged_in, runner, monkeypatch):
505
+ # Both docstrings promise `{cancelled: true}` under --json on a declined prompt, and
506
+ # every other write command emits it. `should_prompt` is forced here because --json
507
+ # normally auto-proceeds, which is what let these two paths drift to a stderr line
508
+ # only: a caller reading stdout would have got an empty document at exit 0.
509
+ monkeypatch.setattr(_write, "should_prompt", lambda *a, **k: True)
510
+ monkeypatch.setattr(output, "confirm_incident_resolve", lambda *a, **k: False)
511
+ respx.get(f"{BASE}/api/issues/i1").mock( # the prompt names the alert, so it reads first
512
+ return_value=httpx.Response(200, json={"id": "i1", "alert_name": "p95", "status": "open"}))
513
+ resolve = respx.post(f"{BASE}/api/issues/i1/resolve").mock(return_value=httpx.Response(200, json={}))
514
+ result = runner.invoke(app, ["--json", "issues", "resolve", "i1"])
515
+ assert result.exit_code == 0, result.output
516
+ assert json.loads(result.stdout) == {"cancelled": True}
517
+ assert not resolve.called
518
+
519
+
520
+ @respx.mock
521
+ def test_incidents_comment_delete_declined_emits_the_json_cancel_envelope(logged_in, runner, monkeypatch):
522
+ monkeypatch.setattr(_write, "should_prompt", lambda *a, **k: True)
523
+ monkeypatch.setattr(output, "confirm_incident_comment_delete", lambda *a, **k: False)
524
+ respx.get(f"{BASE}/api/issues/i1/comments").mock(return_value=httpx.Response(200, json=[
525
+ {"id": "c1", "incident_id": "i1", "author_email": "me@test", "body": "typo", "created_at": "t"}]))
526
+ delete = respx.delete(f"{BASE}/api/issues/i1/comments/c1").mock(return_value=httpx.Response(204))
527
+ result = runner.invoke(app, ["--json", "issues", "comment-delete", "i1", "c1"])
528
+ assert result.exit_code == 0, result.output
529
+ assert json.loads(result.stdout) == {"cancelled": True}
530
+ assert not delete.called
531
+
532
+
533
+ @respx.mock
534
+ def test_incidents_comment_add_human_card(logged_in, runner):
535
+ respx.post(f"{BASE}/api/issues/i1/comments").mock(
536
+ return_value=httpx.Response(201, json={"id": "c1", "incident_id": "i1", "author_email": "me@test", "body": "on it", "created_at": "t"})
537
+ )
538
+ result = runner.invoke(app, ["issues", "comment-add", "i1", "--body", "on it"])
539
+ assert result.exit_code == 0, result.output
540
+ out = result.stdout
541
+ assert "comment added" in out and "on it" in out
542
+
543
+
544
+ def test_incidents_comment_add_neither_exits_2(logged_in, runner):
545
+ result = runner.invoke(app, ["issues", "comment-add", "i1"])
546
+ assert result.exit_code == 2
547
+
548
+
549
+ @respx.mock
550
+ def test_incidents_comment_delete_not_found_exits_6(logged_in, runner):
551
+ respx.get(f"{BASE}/api/issues/i1/comments").mock(return_value=httpx.Response(200, json=[]))
552
+ result = runner.invoke(app, ["issues", "comment-delete", "i1", "cX", "--yes"])
553
+ assert result.exit_code == 6, result.output
554
+ assert "no comment" in result.stderr
555
+
556
+
557
+ @respx.mock
558
+ def test_incidents_comment_delete_human_yes(logged_in, runner):
559
+ respx.get(f"{BASE}/api/issues/i1/comments").mock(return_value=httpx.Response(200, json=[
560
+ {"id": "c1", "incident_id": "i1", "author_email": "me@test", "body": "typo", "created_at": "2026-06-28T00:00:00Z"}]))
561
+ route = respx.delete(f"{BASE}/api/issues/i1/comments/c1").mock(return_value=httpx.Response(204))
562
+ result = runner.invoke(app, ["issues", "comment-delete", "i1", "c1", "--yes"])
563
+ assert result.exit_code == 0, result.output
564
+ assert route.called
565
+ assert "deleted comment" in result.stderr
566
+
567
+
568
+ @respx.mock
569
+ def test_incidents_comment_delete_json(logged_in, runner):
570
+ respx.get(f"{BASE}/api/issues/i1/comments").mock(return_value=httpx.Response(200, json=[
571
+ {"id": "c1", "incident_id": "i1", "author_email": "me@test", "body": "typo", "created_at": "t"}]))
572
+ respx.delete(f"{BASE}/api/issues/i1/comments/c1").mock(return_value=httpx.Response(204))
573
+ result = runner.invoke(app, ["--json", "issues", "comment-delete", "i1", "c1", "--yes"])
574
+ assert result.exit_code == 0, result.output
575
+ body = json.loads(result.stdout)
576
+ assert body["deleted"] is True and body["id"] == "c1"
577
+
578
+
579
+ @respx.mock
580
+ def test_incidents_comment_list_human(logged_in, runner):
581
+ respx.get(f"{BASE}/api/issues/i1/comments").mock(return_value=httpx.Response(200, json=[
582
+ {"id": "c1", "author_email": "ops@example.com", "body": "db pool exhausted", "created_at": "2026-06-28T00:00:00Z"},
583
+ {"id": "c2", "author_email": "x@example.com", "body": None, "created_at": "2026-06-28T00:01:00Z", "deleted_at": "t"},
584
+ ]))
585
+ result = runner.invoke(app, ["issues", "comment-list", "i1"])
586
+ assert result.exit_code == 0, result.output
587
+ out = result.stdout
588
+ assert "comments" in out and "db pool exhausted" in out and "(deleted)" in out
589
+
590
+
591
+ @respx.mock
592
+ def test_incidents_subscribers_human(logged_in, runner):
593
+ respx.get(f"{BASE}/api/issues/i1/subscribers").mock(return_value=httpx.Response(200, json=[
594
+ {"email": "ops@example.com", "source": "creator", "subscribed_at": "2026-06-28T00:00:00Z"}]))
595
+ result = runner.invoke(app, ["issues", "subscribers", "i1"])
596
+ assert result.exit_code == 0, result.output
597
+ out = result.stdout
598
+ assert "subscribers" in out and "ops@example.com" in out and "creator" in out
599
+
600
+
601
+ @respx.mock
602
+ def test_incidents_subscribe_human(logged_in, runner):
603
+ respx.post(f"{BASE}/api/issues/i1/subscribe").mock(return_value=httpx.Response(200, json={}))
604
+ result = runner.invoke(app, ["issues", "subscribe", "i1"])
605
+ assert result.exit_code == 0, result.output
606
+ assert "subscribed" in result.stderr and "you" in result.stderr
607
+
608
+
609
+ @respx.mock
610
+ def test_incidents_open_human_card(logged_in, runner):
611
+ respx.post(f"{BASE}/api/issues").mock(
612
+ return_value=httpx.Response(201, json={"id": "i9", "newly_opened": True, "state": "firing"}))
613
+ respx.get(f"{BASE}/api/issues/i9").mock(return_value=httpx.Response(200, json={
614
+ "id": "i9", "state": "firing", "alert_severity": "critical", "title": "checkout 500s",
615
+ "opened_at": "2026-06-28T00:00:00Z"}))
616
+ result = runner.invoke(app, ["issues", "open", "--summary", "manual page",
617
+ "--title", "checkout 500s", "--severity", "critical"])
618
+ assert result.exit_code == 0, result.output
619
+ out = result.stdout
620
+ assert "issue opened" in out and "manual page" in out and "critical" in out
621
+ assert "checkout 500s" in out # the title is the card's hero line
622
+
623
+
624
+ def test_incidents_open_bad_severity_exits_2(logged_in, runner):
625
+ result = runner.invoke(app, ["issues", "open", "--summary", "x", "--severity", "bogus"])
626
+ assert result.exit_code == 2
627
+
628
+
629
+ @respx.mock
630
+ def test_issues_show_malformed_id_is_not_found(logged_in, runner):
631
+ """The mirror of test_audits_finding_malformed_id_is_not_found, and it did not pass.
632
+
633
+ The server's path extractor answers a non-UUID id with a 400 and a PLAIN-TEXT body, which
634
+ the dashboard converts to the generic {"error": "upstream returned non-JSON response"}.
635
+ `_fail` tested `status >= 500`, so the remap never fired: this exited 1 carrying that
636
+ internal phrase, while the audits sibling — the same code with `>= 400` — exited 6 with a
637
+ usable message. Anything branching on exit 6 to mean not-found took the wrong arm silently.
638
+ """
639
+ respx.get(f"{BASE}/api/issues/not-a-uuid").mock(
640
+ return_value=httpx.Response(400, json={"error": "upstream returned non-JSON response"})
641
+ )
642
+ result = runner.invoke(app, ["--json", "issues", "show", "not-a-uuid"])
643
+ assert result.exit_code == 6
644
+ assert "no issue not-a-uuid" in json.loads(result.stdout)["error"]