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,481 @@
1
+ """The deploy planner, the race check, and source resolution.
2
+
3
+ These are tested hard because they are the two places this feature can destroy
4
+ something: `PUT /enforcement/deployments/{id}` is a FULL REPLACE with no
5
+ server-side lock, so a wrong resulting set is a permanent silent undeploy, and a
6
+ missed race is somebody else's change gone with a 200 on screen.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import io
11
+
12
+ import pytest
13
+
14
+ from fp_cli.enforcement import (
15
+ DeployPlan,
16
+ RefError,
17
+ check_race,
18
+ latest_versions,
19
+ parse_ref,
20
+ plan_deploy,
21
+ read_source,
22
+ resolve_ref,
23
+ )
24
+ from fp_cli.errors import ApiError
25
+ from fp_cli.models import PolicyRef, PolicyVersion
26
+
27
+
28
+ def ref(pid, version=1, effect="enforce"):
29
+ return PolicyRef(id=pid, version=version, effect=effect)
30
+
31
+
32
+ def pv(pid, version=1, archived=False):
33
+ return PolicyVersion(
34
+ id=pid, version=version, description="", sha256="", source=None,
35
+ created_at="", created_by=None, disabled=False, archived=archived,
36
+ )
37
+
38
+
39
+ # ── parsing ──────────────────────────────────────────────────────────────────
40
+
41
+
42
+ @pytest.mark.parametrize(
43
+ "token,expected",
44
+ [
45
+ ("a", ("a", None, None)),
46
+ ("a@3", ("a", 3, None)),
47
+ ("a:observe", ("a", None, "observe")),
48
+ ("a@3:observe", ("a", 3, "observe")),
49
+ ("a@3:enforce", ("a", 3, "enforce")),
50
+ ("no-force-push.v2_x", ("no-force-push.v2_x", None, None)),
51
+ ],
52
+ )
53
+ def test_parse_ref_shapes(token, expected):
54
+ assert parse_ref(token) == expected
55
+
56
+
57
+ @pytest.mark.parametrize("token", ["", " ", "a@", "a@x", "a:", "a:enforced", "a b", "a@1:bad"])
58
+ def test_parse_ref_rejects_junk(token):
59
+ with pytest.raises(RefError):
60
+ parse_ref(token)
61
+
62
+
63
+ def test_an_unknown_effect_names_the_valid_ones():
64
+ """The message has to say what IS allowed — 'invalid effect' helps nobody."""
65
+ with pytest.raises(RefError, match="enforce, observe"):
66
+ parse_ref("a:audit")
67
+
68
+
69
+ # ── version and effect resolution ────────────────────────────────────────────
70
+
71
+
72
+ def test_add_of_an_already_deployed_policy_keeps_its_version():
73
+ """`--add` must not silently upgrade.
74
+
75
+ A machine pinned to v1 while v3 exists is pinned deliberately. Treating a
76
+ bare `--add` as "give me the newest" would roll the fleet forward on a
77
+ command whose author was only reordering.
78
+ """
79
+ got = resolve_ref("a", latest={"a": 3}, current={"a": ref("a", 1)})
80
+ assert (got.version, got.effect) == (1, "enforce")
81
+
82
+
83
+ def test_add_of_a_new_policy_takes_the_latest_version():
84
+ got = resolve_ref("a", latest={"a": 3}, current={})
85
+ assert got.version == 3
86
+
87
+
88
+ def test_an_explicit_version_always_wins():
89
+ got = resolve_ref("a@2", latest={"a": 3}, current={"a": ref("a", 1)})
90
+ assert got.version == 2
91
+
92
+
93
+ def test_effect_is_inherited_then_defaults_to_enforce():
94
+ assert resolve_ref("a", latest={"a": 1}, current={"a": ref("a", 1, "observe")}).effect == "observe"
95
+ assert resolve_ref("a", latest={"a": 1}, current={}).effect == "enforce"
96
+ assert resolve_ref("a:observe", latest={"a": 1}, current={}).effect == "observe"
97
+
98
+
99
+ def test_an_unpublished_policy_is_refused_before_any_write():
100
+ with pytest.raises(RefError, match="no published policy"):
101
+ resolve_ref("ghost", latest={"a": 1}, current={})
102
+
103
+
104
+ def test_latest_versions_ignores_archived():
105
+ assert latest_versions([pv("a", 1), pv("a", 3, archived=True), pv("b", 2)]) == {"a": 1, "b": 2}
106
+
107
+
108
+ # ── the planner: the thing that decides what gets written ────────────────────
109
+
110
+
111
+ def test_add_preserves_everything_already_deployed():
112
+ """The whole reason --add exists. A full replace built from the delta alone
113
+ would drop `b` and `c` here, permanently, with a 200."""
114
+ plan = plan_deploy(
115
+ "m", current=[ref("b"), ref("c")], base=4, add=["a"], latest={"a": 1},
116
+ )
117
+ assert [p.id for p in plan.result] == ["a", "b", "c"]
118
+ assert [p.id for p in plan.added] == ["a"]
119
+ assert [p.id for p in plan.unchanged] == ["b", "c"]
120
+ assert plan.removed == []
121
+
122
+
123
+ def test_remove_takes_exactly_one_out():
124
+ plan = plan_deploy("m", current=[ref("a"), ref("b")], base=1, remove=["a"])
125
+ assert [p.id for p in plan.result] == ["b"]
126
+ assert [p.id for p in plan.removed] == ["a"]
127
+
128
+
129
+ def test_removing_something_not_deployed_is_refused():
130
+ """Silently succeeding would let a typo read as "already gone"."""
131
+ with pytest.raises(RefError, match="not deployed"):
132
+ plan_deploy("m", current=[ref("a")], base=1, remove=["b"])
133
+
134
+
135
+ def test_set_replaces_the_whole_set():
136
+ plan = plan_deploy(
137
+ "m", current=[ref("a"), ref("b")], base=2, replace=["c"], latest={"c": 5},
138
+ )
139
+ assert [p.id for p in plan.result] == ["c"]
140
+ assert [p.id for p in plan.removed] == ["a", "b"]
141
+ assert plan.result[0].version == 5
142
+
143
+
144
+ def test_set_cannot_be_mixed_with_add_or_remove():
145
+ """"exactly these" and "these as well" have no single reading."""
146
+ with pytest.raises(RefError, match="cannot be combined"):
147
+ plan_deploy("m", current=[], base=None, replace=["a"], add=["b"], latest={"a": 1, "b": 1})
148
+
149
+
150
+ def test_a_version_or_effect_change_is_reported_as_changed_not_add_remove():
151
+ plan = plan_deploy(
152
+ "m", current=[ref("a", 1, "enforce")], base=3, add=["a@2:observe"], latest={"a": 2},
153
+ )
154
+ assert plan.added == [] and plan.removed == []
155
+ was, now = plan.changed[0]
156
+ assert (was.version, was.effect) == (1, "enforce")
157
+ assert (now.version, now.effect) == (2, "observe")
158
+
159
+
160
+ def test_a_noop_is_detectable_so_the_cli_can_skip_the_write():
161
+ plan = plan_deploy("m", current=[ref("a")], base=1, add=["a"], latest={"a": 1})
162
+ assert plan.is_noop is True
163
+
164
+
165
+ def test_deploying_to_a_machine_with_nothing_yet():
166
+ plan = plan_deploy("m", current=None, base=None, add=["a"], latest={"a": 2})
167
+ assert [p.label for p in plan.result] == ["a@2:enforce"]
168
+ assert plan.base is None
169
+
170
+
171
+ def test_the_result_is_sorted_so_two_equal_sets_serialise_identically():
172
+ plan = plan_deploy("m", current=[], base=None, add=["c", "a", "b"],
173
+ latest={"a": 1, "b": 1, "c": 1})
174
+ assert [p.id for p in plan.result] == ["a", "b", "c"]
175
+
176
+
177
+ def test_plan_json_carries_the_diff_a_harness_would_otherwise_recompute():
178
+ plan = plan_deploy("m", current=[ref("b")], base=1, add=["a"], latest={"a": 1})
179
+ d = plan.to_dict()
180
+ assert d["machineId"] == "m" and d["base"] == 1 and d["noop"] is False
181
+ assert [p["id"] for p in d["result"]] == ["a", "b"]
182
+ assert [p["id"] for p in d["added"]] == ["a"]
183
+
184
+
185
+ # ── the race check ───────────────────────────────────────────────────────────
186
+
187
+
188
+ def test_a_clean_write_is_base_plus_one():
189
+ check_race(4, 5) # no raise
190
+
191
+
192
+ def test_a_skipped_generation_means_someone_else_wrote():
193
+ with pytest.raises(ApiError, match="someone else deployed"):
194
+ check_race(4, 7)
195
+
196
+
197
+ def test_a_repeated_generation_is_also_a_race():
198
+ with pytest.raises(ApiError):
199
+ check_race(4, 4)
200
+
201
+
202
+ def test_a_first_deployment_has_no_base_to_check():
203
+ check_race(None, 1) # no raise
204
+
205
+
206
+ def test_the_race_message_says_a_deploy_replaces():
207
+ """The operator's next move depends on knowing it did not merge."""
208
+ with pytest.raises(ApiError, match="REPLACES"):
209
+ check_race(1, 9)
210
+
211
+
212
+ # ── source input ─────────────────────────────────────────────────────────────
213
+
214
+
215
+ def test_source_from_a_path(tmp_path):
216
+ f = tmp_path / "p.mjs"
217
+ f.write_text("export default {}")
218
+ assert read_source(str(f)) == "export default {}"
219
+
220
+
221
+ def test_source_from_an_at_path(tmp_path):
222
+ f = tmp_path / "p.mjs"
223
+ f.write_text("x")
224
+ assert read_source(f"@{f}") == "x"
225
+
226
+
227
+ def test_source_from_explicit_stdin():
228
+ assert read_source("-", stdin=io.StringIO("piped"), isatty=False) == "piped"
229
+
230
+
231
+ def test_source_from_a_pipe_with_no_argument():
232
+ assert read_source(None, stdin=io.StringIO("piped"), isatty=False) == "piped"
233
+
234
+
235
+ def test_source_from_a_paste_prompts_first():
236
+ """On a TTY, blocking on stdin without saying so is indistinguishable from a hang."""
237
+ called = []
238
+ out = read_source(None, stdin=io.StringIO("pasted"), isatty=True, prompt=lambda: called.append(1))
239
+ assert out == "pasted" and called == [1]
240
+
241
+
242
+ def test_a_missing_file_names_the_path():
243
+ with pytest.raises(RefError, match="no such file"):
244
+ read_source("/nope/definitely-not-here.mjs")
245
+
246
+
247
+ # ── the JSON contract ────────────────────────────────────────────────────────
248
+
249
+
250
+ def test_models_emit_the_server_shape_not_pythons():
251
+ """`vars()` would leak snake_case into a contract that is camelCase
252
+ everywhere else — a difference a harness finds at runtime, not in review."""
253
+ from fp_cli.models import Deployment, Machine, PolicyVersion
254
+
255
+ pv_keys = set(PolicyVersion.from_dict({"id": "a", "version": 1}).to_dict())
256
+ assert "createdAt" in pv_keys and "created_at" not in pv_keys
257
+
258
+ dep_keys = set(Deployment.from_dict({"machineId": "m", "deployment": 1}).to_dict())
259
+ assert "machineId" in dep_keys and "machine_id" not in dep_keys
260
+
261
+ m = Machine.from_dict({"machineId": "m", "deployment": 3, "appliedDeployment": 1})
262
+ keys = set(m.to_dict())
263
+ assert "appliedDeployment" in keys
264
+ assert not [k for k in keys if "_" in k], keys
265
+
266
+
267
+ def test_drift_is_intent_ahead_of_delivery():
268
+ """The one field the CLI computes, and the reason `fleet diff` exists: a
269
+ machine can be deployed-to and still enforcing an older set."""
270
+ from fp_cli.models import Machine
271
+
272
+ def m(intended, delivered):
273
+ return Machine.from_dict({"machineId": "m", "deployment": intended,
274
+ "appliedDeployment": delivered})
275
+
276
+ assert m(3, 1).drifted is True # behind
277
+ assert m(3, None).drifted is True # never collected anything
278
+ assert m(3, 3).drifted is False # in sync
279
+ assert m(None, None).drifted is False # nothing deployed: not drift
280
+
281
+
282
+ def test_a_nul_byte_in_source_is_refused_with_a_readable_reason():
283
+ """Reaching Postgres with a NUL returns a bare "database error" — an internal
284
+ failure shown to somebody who most likely pointed the command at a binary
285
+ file. Catching it here turns that into a sentence."""
286
+ with pytest.raises(RefError, match="NUL byte"):
287
+ read_source("-", stdin=io.StringIO("export default {}\x00\x01"), isatty=False)
288
+
289
+
290
+ def test_the_nul_check_covers_every_input_shape(tmp_path):
291
+ """A guard on one of five paths is not a guard."""
292
+ f = tmp_path / "bin.mjs"
293
+ f.write_text("ok\x00bad")
294
+ with pytest.raises(RefError, match="NUL byte"):
295
+ read_source(str(f))
296
+ with pytest.raises(RefError, match="NUL byte"):
297
+ read_source(f"@{f}")
298
+ with pytest.raises(RefError, match="NUL byte"):
299
+ read_source(None, stdin=io.StringIO("a\x00b"), isatty=False)
300
+ with pytest.raises(RefError, match="NUL byte"):
301
+ read_source(None, stdin=io.StringIO("a\x00b"), isatty=True, prompt=lambda: None)
302
+
303
+
304
+ def test_ordinary_unicode_is_not_mistaken_for_binary():
305
+ """Emoji and CJK are legitimate policy content; only NUL is refused."""
306
+ assert read_source("-", stdin=io.StringIO("// 日本語 🎌\n"), isatty=False) == "// 日本語 🎌\n"
307
+
308
+
309
+ # ── disabled policies ────────────────────────────────────────────────────────
310
+
311
+
312
+ def test_adding_a_disabled_policy_is_refused_before_the_plan_is_built():
313
+ """The server rejects it anyway — but only after the CLI has drawn a plan
314
+ and asked the operator to confirm it, so the last thing on screen is a
315
+ change that cannot happen under a prompt that implied it could."""
316
+ with pytest.raises(RefError, match="disabled"):
317
+ plan_deploy("m", current=[], base=None, add=["a"],
318
+ latest={"a": 1}, disabled={"a"})
319
+
320
+
321
+ def test_the_refusal_names_the_command_that_fixes_it():
322
+ with pytest.raises(RefError, match="policies enable a"):
323
+ plan_deploy("m", current=[], base=None, add=["a"],
324
+ latest={"a": 1}, disabled={"a"})
325
+
326
+
327
+ def test_set_checks_disabled_too():
328
+ """`--set` resolves refs by the same path; a gap in one is a gap in both."""
329
+ with pytest.raises(RefError, match="disabled"):
330
+ plan_deploy("m", current=[], base=None, replace=["a"],
331
+ latest={"a": 1}, disabled={"a"})
332
+
333
+
334
+ def test_a_disabled_policy_already_deployed_can_still_be_removed():
335
+ """Defensive, for a state the server normally prevents.
336
+
337
+ Disabling REMOVES a policy from every deployment carrying it (verified
338
+ against a live server: generation 16 held it, disabling minted 17 without
339
+ it), so a disabled policy should not appear in `current` at all. If one ever
340
+ does — a stale read, a server that changes this — refusing the removal would
341
+ leave it stuck on the machine with no CLI path off."""
342
+ plan = plan_deploy("m", current=[ref("a")], base=1, remove=["a"], disabled={"a"})
343
+ assert [p.id for p in plan.removed] == ["a"]
344
+
345
+
346
+ def test_an_unrelated_add_does_not_re_resolve_what_is_already_there():
347
+ """Only the refs you name are resolved. Re-resolving the whole set would
348
+ make an unrelated `--add` fail because of something already on the machine
349
+ — the same trap in reverse."""
350
+ plan = plan_deploy("m", current=[ref("a")], base=1, add=["b"],
351
+ latest={"a": 1, "b": 1}, disabled={"a"})
352
+ assert [p.id for p in plan.result] == ["a", "b"]
353
+ assert [p.id for p in plan.unchanged] == ["a"]
354
+
355
+
356
+ def test_disabled_ids_ignores_archived():
357
+ """An archived policy is already excluded from `latest`, so listing it here
358
+ too would produce 'disabled' for something that no longer exists."""
359
+ from fp_cli.enforcement import disabled_ids
360
+ pols = [pv("live"), pv("off"), pv("gone", archived=True)]
361
+ pols[1].disabled = True
362
+ pols[2].disabled = True
363
+ assert disabled_ids(pols) == {"off"}
364
+
365
+
366
+ # ── machine labels ───────────────────────────────────────────────────────────
367
+
368
+
369
+ def test_an_operator_rename_wins_over_the_machines_own_label():
370
+ """The bug this fixes: `fleet rename` reported success and `fleet list` kept
371
+ showing `-`. The server stores the operator's name in `labelOverride`, a
372
+ DIFFERENT column from the machine's self-asserted `label`, and reading only
373
+ the latter made the rename invisible. Mirrors `machinePicker.ts`."""
374
+ from fp_cli.models import Machine
375
+ m = Machine.from_dict({"machineId": "m", "label": "self-named",
376
+ "labelOverride": "operator-named"})
377
+ assert m.display_label == "operator-named"
378
+
379
+
380
+ def test_the_machines_own_label_is_used_when_there_is_no_override():
381
+ from fp_cli.models import Machine
382
+ assert Machine.from_dict({"machineId": "m", "label": "self-named"}).display_label == "self-named"
383
+
384
+
385
+ def test_no_label_at_all_is_none_not_an_empty_string():
386
+ """The renderer substitutes a dash; an empty string would print as blank."""
387
+ from fp_cli.models import Machine
388
+ assert Machine.from_dict({"machineId": "m"}).display_label is None
389
+ assert Machine.from_dict({"machineId": "m", "label": " ",
390
+ "labelOverride": ""}).display_label is None
391
+
392
+
393
+ def test_both_label_fields_survive_into_json():
394
+ """A harness may want to know which of the two it is looking at."""
395
+ from fp_cli.models import Machine
396
+ d = Machine.from_dict({"machineId": "m", "label": "a", "labelOverride": "b"}).to_dict()
397
+ assert d["label"] == "a" and d["labelOverride"] == "b"
398
+
399
+
400
+ # ── review round: what these commands got wrong ──────────────────────────────
401
+ #
402
+ # Every test below stands for a bug that shipped in the first cut of these
403
+ # commands and passed every test that existed at the time. They are grouped
404
+ # because they share a shape: the command did something defensible and then
405
+ # described it wrongly, or classified its own failure wrongly.
406
+
407
+
408
+ def test_a_binary_file_is_refused_by_name_not_by_traceback(tmp_path):
409
+ """`policies publish x logo.png` printed a Python traceback.
410
+
411
+ `read_source` caught `OSError`, but decoding happens inside `read()` and
412
+ raises `UnicodeDecodeError`, which is a `ValueError` — so it sailed past the
413
+ handler and out through Click as a rich traceback with internal paths in it.
414
+ The NUL-byte guard could not save this: it inspects text, and a file that
415
+ fails to decode never becomes text.
416
+ """
417
+ from fp_cli.enforcement import RefUsageError
418
+
419
+ f = tmp_path / "logo.png"
420
+ f.write_bytes(b"\x89PNG\r\n\x1a\n\xff\xfe\x00\x01binary")
421
+ for value in (str(f), f"@{f}"):
422
+ with pytest.raises(RefUsageError, match="not UTF-8 text"):
423
+ read_source(value)
424
+
425
+
426
+ def test_a_binary_pipe_is_refused_the_same_way():
427
+ """`cat logo.png | fp policies publish x` reaches a different branch of
428
+ `read_source` than a path does, and used to traceback from that one too."""
429
+ from fp_cli.enforcement import RefUsageError
430
+
431
+ class _Undecodable:
432
+ def isatty(self):
433
+ return False
434
+
435
+ def read(self):
436
+ raise UnicodeDecodeError("utf-8", b"\xff\xfe", 0, 1, "invalid start byte")
437
+
438
+ with pytest.raises(RefUsageError, match="not UTF-8 text"):
439
+ read_source("-", stdin=_Undecodable())
440
+ with pytest.raises(RefUsageError, match="not UTF-8 text"):
441
+ read_source(None, stdin=_Undecodable(), isatty=False)
442
+
443
+
444
+ def test_mistyping_a_flag_is_a_usage_error_not_an_api_error():
445
+ """These all exited 1 ("the server returned an error") for mistakes the
446
+ server never saw. The CLI documents 2 for usage, and `--since` / `--expect`
447
+ in these same commands already use it; a script branching on exit codes
448
+ could not tell a typo from a rejected write."""
449
+ from fp_cli.enforcement import RefUsageError
450
+
451
+ for token in ("", "bad ref!!", "policy:banana"):
452
+ with pytest.raises(RefUsageError):
453
+ parse_ref(token)
454
+
455
+ with pytest.raises(RefUsageError, match="cannot be combined"):
456
+ plan_deploy("m", current=[], base=1, add=("a",), replace=("b",))
457
+
458
+ with pytest.raises(RefUsageError, match="no such file"):
459
+ read_source("/nope/definitely-not-here.mjs")
460
+
461
+
462
+ def test_usage_errors_are_still_ref_errors():
463
+ """The split must not break `except RefError`, which is what every call site
464
+ and every earlier test in this file catches."""
465
+ from fp_cli.enforcement import RefUsageError
466
+
467
+ assert issubclass(RefUsageError, RefError)
468
+ with pytest.raises(RefError):
469
+ parse_ref("bad ref!!")
470
+
471
+
472
+ def test_a_server_refusal_stays_an_api_error():
473
+ """The other half of the contract: naming a policy that does not exist is
474
+ not a typo the caller can fix by re-reading their own command line, and it
475
+ keeps exit 1. Widening the usage class to cover it would have made every
476
+ "this does not exist" indistinguishable from a malformed flag."""
477
+ from fp_cli.enforcement import RefUsageError
478
+
479
+ with pytest.raises(RefError) as caught:
480
+ resolve_ref("ghost", latest={}, current={})
481
+ assert not isinstance(caught.value, RefUsageError)
@@ -0,0 +1,170 @@
1
+ """Read/query completion: errors, eval-aggregate, events filters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ import httpx
8
+ import respx
9
+
10
+ from fp_cli.app import app
11
+
12
+ BASE = "http://dash.test"
13
+
14
+
15
+ @respx.mock
16
+ def test_errors_aggregate_json(logged_in, runner):
17
+ respx.get(f"{BASE}/api/events/error_summary").mock(
18
+ return_value=httpx.Response(
19
+ 200,
20
+ json={"total": 5, "sessions": 2, "agents": 1, "last_ts": "2026-01-01T00:00:00Z", "bins": [1, 2]},
21
+ )
22
+ )
23
+ result = runner.invoke(app, ["--json", "errors", "--aggregate", "--since", "24h"])
24
+ assert result.exit_code == 0, result.output
25
+ assert json.loads(result.stdout)["total"] == 5
26
+
27
+
28
+ @respx.mock
29
+ def test_errors_aggregate_card(logged_in, runner):
30
+ respx.get(f"{BASE}/api/events/error_summary").mock(
31
+ return_value=httpx.Response(
32
+ 200, json={"total": 66, "sessions": 62, "agents": 6, "last_ts": "2026-01-01T00:00:00Z", "bins": []}
33
+ )
34
+ )
35
+ result = runner.invoke(app, ["errors", "--aggregate", "--since", "24h"])
36
+ assert result.exit_code == 0, result.output
37
+ assert "errors-aggregate" in result.stdout and "66" in result.stdout
38
+ assert "errored events" in result.stdout and "62 sessions" in result.stdout
39
+
40
+
41
+ @respx.mock
42
+ def test_errors_list_fetches_errored_events(logged_in, runner):
43
+ # errors now reads the LIGHT, payload-free feed (/api/events/summary) — server-computed
44
+ # `summary`/`is_error`, no payload.
45
+ route = respx.get(f"{BASE}/api/events/summary").mock(
46
+ return_value=httpx.Response(
47
+ 200,
48
+ json={
49
+ "events": [
50
+ {"id": 1, "session_id": "sess-1", "agent_id": "a", "event_type": "error", "ts": "t",
51
+ "environment": "prod", "summary": "TimeoutError: upstream timed out",
52
+ "is_error": True, "error_type": "TimeoutError", "output_tokens": None}
53
+ ],
54
+ "next_cursor": None,
55
+ },
56
+ )
57
+ )
58
+ # bare `errors` now LISTS errored events (errored=true), not the summary
59
+ result = runner.invoke(app, ["--json", "errors", "--search", "boom", "--search", "oops", "--error-type", "TimeoutError"])
60
+ assert result.exit_code == 0, result.output
61
+ data = json.loads(result.stdout)
62
+ assert data["errors"][0]["event_type"] == "error" # keyed under "errors", light rows
63
+ assert data["errors"][0]["summary"] == "TimeoutError: upstream timed out" # server summary, no payload
64
+ assert "payload" not in data["errors"][0] or not data["errors"][0]["payload"] # never the fat column
65
+ params = route.calls.last.request.url.params
66
+ assert params["errored"] == "true"
67
+ assert params["error_type"] == "TimeoutError"
68
+ assert params.get_list("search") == ["boom", "oops"] # repeated free-text params
69
+
70
+
71
+ @respx.mock
72
+ def test_errors_list_table(logged_in, runner):
73
+ respx.get(f"{BASE}/api/events/summary").mock(
74
+ return_value=httpx.Response(
75
+ 200,
76
+ json={
77
+ "events": [
78
+ {"id": 1, "session_id": "sess-20260622-4b90b240", "agent_id": "agent-orderbot",
79
+ "event_type": "error", "ts": "2026-06-22T17:51:34Z",
80
+ "summary": "RateLimitError: upstream timed out", "is_error": True,
81
+ "error_type": "RateLimitError", "output_tokens": None, "environment": "prod"}
82
+ ],
83
+ "next_cursor": None,
84
+ },
85
+ )
86
+ )
87
+ result = runner.invoke(app, ["errors", "--since", "24h"])
88
+ assert result.exit_code == 0, result.output
89
+ # the 80-col CliRunner truncates cells; just assert the error-themed box rendered
90
+ assert "errors" in result.stdout and "newest first" in result.stdout
91
+
92
+
93
+ @respx.mock
94
+ def test_evals_aggregate_json(logged_in, runner):
95
+ route = respx.get(f"{BASE}/api/evaluations/aggregate").mock(
96
+ return_value=httpx.Response(
97
+ 200,
98
+ json={
99
+ "total": 10,
100
+ "status_counts": {"done": 8, "error": 1, "timeout": 1},
101
+ "score_stats": [{"key": "helpfulness", "count": 8, "avg": 0.7, "min": 0.1, "max": 1.0, "p50": 0.75}],
102
+ "timeline": {"bucket_unit": "hour", "from": None, "to": "t", "points": []},
103
+ },
104
+ )
105
+ )
106
+ result = runner.invoke(app, ["--json", "evals", "--aggregate", "--since", "7d", "--env", "prod"])
107
+ assert result.exit_code == 0, result.output
108
+ data = json.loads(result.stdout)
109
+ assert data["status_counts"]["done"] == 8
110
+ assert data["score_stats"][0]["key"] == "helpfulness"
111
+ assert route.calls.last.request.url.params["environment"] == "prod" # filters thread through
112
+
113
+
114
+ @respx.mock
115
+ def test_evals_aggregate_table(logged_in, runner):
116
+ respx.get(f"{BASE}/api/evaluations/aggregate").mock(
117
+ return_value=httpx.Response(
118
+ 200,
119
+ json={
120
+ "total": 10,
121
+ "status_counts": {"done": 8, "error": 2, "timeout": 0},
122
+ "score_stats": [{"key": "helpfulness", "count": 8, "avg": 0.66, "min": 0.1, "max": 1.0, "p50": 0.6}],
123
+ "timeline": {"bucket_unit": "hour", "from": None, "to": "t", "points": []},
124
+ },
125
+ )
126
+ )
127
+ result = runner.invoke(app, ["evals", "--aggregate", "--since", "7d"])
128
+ assert result.exit_code == 0, result.output
129
+ # totals card + score-stats panel
130
+ assert "eval-aggregate" in result.stdout and "evals" in result.stdout
131
+ assert "success rate" in result.stdout
132
+ assert "score stats" in result.stdout and "helpfulness" in result.stdout
133
+
134
+
135
+ @respx.mock
136
+ def test_events_filters_threaded(logged_in, runner):
137
+ # A broad events read (no --session-id/--full) uses the light feed.
138
+ route = respx.get(f"{BASE}/api/events/summary").mock(
139
+ return_value=httpx.Response(200, json={"events": [], "next_cursor": None})
140
+ )
141
+ result = runner.invoke(
142
+ app,
143
+ ["--json", "events", "--env", "prod,staging", "--event-type", "tool_use,tool_result", "--order", "asc"],
144
+ )
145
+ assert result.exit_code == 0, result.output
146
+ params = route.calls.last.request.url.params
147
+ assert params["environment"] == "prod,staging"
148
+ assert params["event_type"] == "tool_use,tool_result"
149
+ assert params["order"] == "asc"
150
+
151
+
152
+ def test_events_bad_order_usage_error(logged_in, runner):
153
+ result = runner.invoke(app, ["events", "--order", "sideways"])
154
+ assert result.exit_code == 2
155
+
156
+
157
+ def test_events_nonpositive_limit_usage_error(logged_in, runner):
158
+ # --limit 0 / negative is rejected client-side (not passed through to the server).
159
+ assert runner.invoke(app, ["events", "--limit", "0"]).exit_code == 2
160
+ assert runner.invoke(app, ["events", "-n", "-5"]).exit_code == 2
161
+
162
+
163
+ def test_sessions_nonpositive_limit_usage_error(logged_in, runner):
164
+ assert runner.invoke(app, ["sessions", "--limit", "0"]).exit_code == 2
165
+ assert runner.invoke(app, ["sessions", "-n", "-5"]).exit_code == 2
166
+
167
+
168
+ def test_sessions_nonpositive_limit_usage_error(logged_in, runner):
169
+ assert runner.invoke(app, ["sessions", "--limit", "0"]).exit_code == 2
170
+ assert runner.invoke(app, ["sessions", "-n", "-5"]).exit_code == 2