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,287 @@
1
+ """The SDK must write where the shipped daemons read.
2
+
3
+ This is the cross-component test, and it exists because the divergence it checks
4
+ for is invisible from either side alone: the SDK's own tests pass while writing
5
+ to a directory nothing watches, and the daemon's own tests pass while watching a
6
+ directory nothing writes to. Both suites green, zero events uploaded, no error
7
+ anywhere. Silent total data loss is the failure mode this file is here to make
8
+ loud.
9
+
10
+ Two daemons read this SDK's spool, and only one of them is in this repository:
11
+
12
+ * ``failproofaid`` — here, in ``crates/fpai-collect``. Watches BOTH roots.
13
+ Checkable, and checked below without skipping.
14
+ * ``agenteye-collector`` — in the private AgentEye repository. Watches only
15
+ ``$AGENTEYE_HOME`` / ``~/.agenteye`` (which this SDK no longer resolves,
16
+ but the daemon still WATCHES). Checkable only when a checkout is on
17
+ disk, so it is opt-in via ``FP_AGENTEYE_ROOT``.
18
+
19
+ The predecessor of this file gated EVERY test in it on a source file from that
20
+ other repository, so all four skipped in normal runs — including the three that
21
+ assert nothing but this SDK's own behaviour and need no other repo at all. A
22
+ test that always skips is not a guard. Hence the split below, and hence
23
+ ``FAILPROOFAI_SDK_REQUIRE_CONTRACT=1``, which CI sets to turn the remaining
24
+ skips into failures so "the file moved" can never quietly read as "green".
25
+ """
26
+ import os
27
+ import re
28
+ from pathlib import Path
29
+
30
+ import pytest
31
+
32
+ from failproofai_sdk import _resolver
33
+
34
+ # tests/ -> python/ -> sdk/ -> repo root.
35
+ #
36
+ # Guarded, because `parents[3]` raises IndexError on a shallower tree — and a
37
+ # shallower tree is exactly the packaged-sdist case `_read_sibling` below is
38
+ # written to handle gracefully. Unguarded, it raised at IMPORT, which pytest
39
+ # reports as a collection error and which aborts the WHOLE suite rather than
40
+ # skipping the one file that needs the repo. Reproduced by copying `sdk/python`
41
+ # somewhere on its own and running pytest: 428 passing tests became `1 error`.
42
+ _HERE = Path(__file__).resolve()
43
+ REPO_ROOT = _HERE.parents[3] if len(_HERE.parents) > 3 else _HERE.parent
44
+ FPAI_COLLECT_CONFIG = REPO_ROOT / "crates" / "fpai-collect" / "src" / "config.rs"
45
+ FP_HOME_TS = REPO_ROOT / "src" / "hooks" / "fp-home.ts"
46
+
47
+ #: Set by CI. Turns "the source I read is missing" from a skip into a failure.
48
+ REQUIRE = os.environ.get("FAILPROOFAI_SDK_REQUIRE_CONTRACT", "").strip().lower() in {
49
+ "1",
50
+ "true",
51
+ "yes",
52
+ "on",
53
+ }
54
+
55
+
56
+ def _read_sibling(path: Path) -> str:
57
+ """Source of a same-repo file, or skip/fail depending on ``REQUIRE``.
58
+
59
+ Missing means one of two things: this is an installed sdist (fine — there is
60
+ no repo to read), or somebody moved the file (not fine). ``REQUIRE``
61
+ distinguishes them, because from in here they look identical.
62
+ """
63
+ if path.is_file():
64
+ return path.read_text(encoding="utf-8")
65
+ message = (
66
+ f"{path.relative_to(REPO_ROOT) if REPO_ROOT in path.parents else path} is "
67
+ "missing. In a packaged sdist that is expected. In the repository it means "
68
+ "the file moved, and this contract is now unguarded — re-point this test at "
69
+ "the new location rather than deleting it."
70
+ )
71
+ if REQUIRE:
72
+ pytest.fail(message)
73
+ pytest.skip(message)
74
+
75
+
76
+ # ─────────────────────────────────────────────────────────────────────────────
77
+ # This SDK's own resolution rule. Depends on nothing outside this package, so
78
+ # it never skips, ever.
79
+ # ─────────────────────────────────────────────────────────────────────────────
80
+
81
+
82
+ def test_sdk_default_is_the_umbrella(tmp_path, monkeypatch):
83
+ """No configuration: the umbrella root, which failproofaid watches."""
84
+ monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path))
85
+ monkeypatch.delenv("AGENTEYE_HOME", raising=False)
86
+ monkeypatch.delenv("FAILPROOFAI_HOME", raising=False)
87
+ _resolver.set_base_dir(None)
88
+
89
+ assert _resolver.get_base_dir() == tmp_path / ".failproofai" / "custom-agents"
90
+
91
+
92
+ def test_the_legacy_root_is_not_reachable_through_agenteye_home(tmp_path, monkeypatch):
93
+ """The SDK side of the contract: this package writes to the umbrella, period.
94
+
95
+ `$AGENTEYE_HOME` used to sit above the default here, so exporting it for
96
+ `agenteye-collector` — the component below that genuinely reads it — moved
97
+ this SDK's spool too, as an unasked-for side effect. It no longer resolves
98
+ anything in this package. The daemon assertions further down still require
99
+ `failproofaid` to WATCH `~/.agenteye/events`, which is what keeps already
100
+ written batches and unupgraded SDKs collected.
101
+ """
102
+ monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path))
103
+ monkeypatch.delenv("FAILPROOFAI_HOME", raising=False)
104
+ monkeypatch.setenv("AGENTEYE_HOME", str(tmp_path / ".agenteye"))
105
+ _resolver.set_base_dir(None)
106
+
107
+ assert _resolver.get_base_dir() == tmp_path / ".failproofai" / "custom-agents"
108
+
109
+
110
+ def test_agenteye_home_overrides_nothing_in_this_package(tmp_path, monkeypatch):
111
+ """It is still the collector's variable; it is no longer ours."""
112
+ monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path))
113
+ monkeypatch.delenv("FAILPROOFAI_HOME", raising=False)
114
+ monkeypatch.setenv("AGENTEYE_HOME", str(tmp_path / "shared"))
115
+ _resolver.set_base_dir(None)
116
+
117
+ assert _resolver.get_base_dir() == tmp_path / ".failproofai" / "custom-agents"
118
+
119
+
120
+ # ─────────────────────────────────────────────────────────────────────────────
121
+ # failproofaid — same repository, so these are hard assertions.
122
+ # ─────────────────────────────────────────────────────────────────────────────
123
+
124
+
125
+ def test_failproofaid_watches_the_legacy_agenteye_root():
126
+ """``crates/fpai-collect`` must keep reading ``~/.agenteye``.
127
+
128
+ This is the assertion that protects every already-deployed SDK. Dropping
129
+ this root from the daemon does not break a build or a test on the Rust side
130
+ — it just means an unupgraded SDK writes into a directory nothing reads.
131
+ """
132
+ src = _read_sibling(FPAI_COLLECT_CONFIG)
133
+
134
+ assert "fn agenteye_events_dir()" in src, (
135
+ "crates/fpai-collect no longer defines agenteye_events_dir(). Every SDK "
136
+ "release to date writes to ~/.agenteye/events by default; removing that "
137
+ "root silently strands all of them."
138
+ )
139
+ assert 'var_os("AGENTEYE_HOME")' in src
140
+ assert '".agenteye"' in src
141
+ assert 'join("events")' in src
142
+
143
+
144
+ def test_failproofaid_watches_both_roots_in_one_list():
145
+ """Both spool roots must be in the daemon's watch list, not just defined."""
146
+ src = _read_sibling(FPAI_COLLECT_CONFIG)
147
+
148
+ match = re.search(r"for sdk_spool in \[([^\]]*)\]", src)
149
+ assert match, (
150
+ "the spool_dirs loop in crates/fpai-collect/src/config.rs was "
151
+ "restructured. Find where the daemon now assembles the directories it "
152
+ "watches and re-point this assertion; a defined-but-unwatched root reads "
153
+ "as working from every side."
154
+ )
155
+ watched = match.group(1)
156
+ assert "custom_agents_events_dir" in watched
157
+ assert "agenteye_events_dir" in watched
158
+
159
+
160
+ def test_umbrella_path_agrees_with_rust_and_typescript():
161
+ """``~/.failproofai/custom-agents`` is spelled the same in all three languages."""
162
+ rust = _read_sibling(FPAI_COLLECT_CONFIG)
163
+ typescript = _read_sibling(FP_HOME_TS)
164
+
165
+ assert 'join("custom-agents")' in rust
166
+ assert '"custom-agents"' in typescript
167
+ assert 'atHome(home, "custom-agents")' in typescript, (
168
+ "src/hooks/fp-home.ts no longer derives customAgentsDir from atHome(). "
169
+ "atHome() is what applies FAILPROOFAI_HOME, which _resolver mirrors."
170
+ )
171
+ # The SDK's own spelling of the same path, under a fake home.
172
+ assert _resolver.failproofai_custom_agents_dir().name == "custom-agents"
173
+
174
+
175
+ def test_failproofai_home_is_honoured_the_same_way_on_both_sides(tmp_path, monkeypatch):
176
+ """``FAILPROOFAI_HOME`` moves the umbrella root for the SDK and the TS alike."""
177
+ typescript = _read_sibling(FP_HOME_TS)
178
+ assert "process.env.FAILPROOFAI_HOME" in typescript
179
+
180
+ monkeypatch.setenv("FAILPROOFAI_HOME", str(tmp_path / "elsewhere"))
181
+ assert _resolver.failproofai_custom_agents_dir() == (
182
+ tmp_path / "elsewhere" / "custom-agents"
183
+ )
184
+
185
+
186
+ # ─────────────────────────────────────────────────────────────────────────────
187
+ # agenteye-collector — private repository. Opt-in via FP_AGENTEYE_ROOT.
188
+ # ─────────────────────────────────────────────────────────────────────────────
189
+
190
+ _AGENTEYE_ROOT = os.environ.get("FP_AGENTEYE_ROOT")
191
+ _agenteye_collector_config = (
192
+ Path(_AGENTEYE_ROOT) / "collector" / "src" / "config.rs" if _AGENTEYE_ROOT else None
193
+ )
194
+
195
+ requires_agenteye_checkout = pytest.mark.skipif(
196
+ _agenteye_collector_config is None or not _agenteye_collector_config.is_file(),
197
+ reason="set FP_AGENTEYE_ROOT to an AgentEye checkout to verify the older collector",
198
+ )
199
+
200
+
201
+ @requires_agenteye_checkout
202
+ def test_agenteye_collector_still_reads_only_agenteye_home_and_dot_agenteye():
203
+ """Pins the premise the SDK default rests on, on the older collector."""
204
+ src = _agenteye_collector_config.read_text(encoding="utf-8")
205
+ start = src.find("pub fn base_dir()")
206
+ assert start != -1, (
207
+ "the AgentEye collector's `pub fn base_dir()` is gone. It is the other "
208
+ "half of this contract — find where it now resolves its spool root and "
209
+ "re-point this test at it rather than deleting the test."
210
+ )
211
+ end = src.find("\nfn ", start)
212
+ body = src[start : end if end != -1 else len(src)]
213
+
214
+ assert 'var("AGENTEYE_HOME")' in body
215
+ assert '".agenteye"' in body
216
+ # The load-bearing half. This collector NOT knowing the umbrella is the
217
+ # entire reason `AGENTEYE_HOME` is documented as the escape hatch for hosts
218
+ # running it. If it ever learns the umbrella root, that advice becomes
219
+ # unnecessary and the migration note should be retired — deliberately, not
220
+ # by someone noticing years later.
221
+ assert "failproofai" not in body.lower(), (
222
+ "the AgentEye collector now mentions failproofai in base_dir(). If it "
223
+ "genuinely watches ~/.failproofai/custom-agents, the AGENTEYE_HOME "
224
+ "escape hatch this SDK documents for legacy hosts is no longer needed "
225
+ "— retire it on purpose, not silently."
226
+ )
227
+
228
+
229
+ def test_this_file_can_be_imported_outside_the_repository():
230
+ """A collection error here aborts the whole suite, not just this file.
231
+
232
+ `_read_sibling` is written to skip when the repo is absent — "in a packaged
233
+ sdist that is expected" — but `parents[3]` raised IndexError at import
234
+ before any of that could run, so the graceful path was unreachable in the
235
+ one situation it exists for.
236
+ """
237
+ assert REPO_ROOT.is_absolute()
238
+ # The real repo has these; an sdist does not, and then the guards skip.
239
+ if FPAI_COLLECT_CONFIG.is_file():
240
+ assert FP_HOME_TS.is_file(), "half the contract's sources are missing"
241
+
242
+
243
+ # --- The README is part of the contract too -----------------------------------
244
+ #
245
+ # The three assertions above pin the spool root across Python, Rust and
246
+ # TypeScript, so the code cannot drift. Nothing pinned the README, and it drifted
247
+ # on its own: after the default moved to ~/.failproofai/custom-agents, the
248
+ # architecture diagram, the configure() comment and the JSONL example were all
249
+ # still showing ~/.agenteye/events — contradicted by correct prose two lines away.
250
+ #
251
+ # That is not a cosmetic miss. A reader follows the code sample, tails a
252
+ # directory nothing writes to, sees an empty spool, and concludes the SDK is
253
+ # broken — which is the same "an unread spool is indistinguishable from an idle
254
+ # one" failure this whole file exists to make loud, just relocated into the docs.
255
+
256
+ README = Path(__file__).resolve().parents[1] / "README.md"
257
+
258
+ # ~/.agenteye is still allowed to appear in the migration notes, which exist to
259
+ # tell legacy hosts what to set. What is NOT allowed is presenting it as where
260
+ # batches land today.
261
+ _SPOOL_FILE_LINE = re.compile(r"events/(\*|event-)[^\s`]*\.jsonl")
262
+
263
+
264
+ def test_readme_shows_the_current_spool_root_wherever_it_shows_a_batch_path():
265
+ offenders = [
266
+ f"README.md:{i}: {line.strip()}"
267
+ for i, line in enumerate(README.read_text(encoding="utf-8").split("\n"), 1)
268
+ if _SPOOL_FILE_LINE.search(line) and "custom-agents" not in line
269
+ ]
270
+ assert not offenders, (
271
+ "the README shows a spool batch path that is not the current default "
272
+ f"({_resolver.failproofai_custom_agents_dir()}); a reader will tail a "
273
+ "directory nothing writes to:\n " + "\n ".join(offenders)
274
+ )
275
+
276
+
277
+ def test_readme_documents_the_current_default_for_base_dir():
278
+ """The `configure(base_dir=...)` comment must not name the retired root as the default."""
279
+ text = README.read_text(encoding="utf-8")
280
+ assert "~/.failproofai/custom-agents" in text, (
281
+ "the README never spells the current default spool root"
282
+ )
283
+ for i, line in enumerate(text.split("\n"), 1):
284
+ if "Default:" in line and ".agenteye" in line:
285
+ assert "custom-agents" in line, (
286
+ f"README.md:{i} gives the retired root as the default: {line.strip()}"
287
+ )
@@ -0,0 +1,360 @@
1
+ """The SDK creates its spool path and NOTHING else, at every level.
2
+
3
+ This SDK installs into other people's agent processes and now writes inside
4
+ `~/.failproofai`, a directory the CLI and the daemon own. So the rule is not
5
+ merely "make the directory work" — it is that a machine which has only ever run
6
+ this SDK must be indistinguishable, to every other component, from a machine
7
+ that has run nothing at all.
8
+
9
+ `detectLayout()` in `src/hooks/fp-config.ts` is the reason that matters. It
10
+ decides whether a home is `absent`, `current`, `stale` or `future`, and a
11
+ `stale` verdict authorises `resetHome()`, which deletes files. Its landmarks are
12
+ `VERSION`, `config.json`, `config.toml`, and layout 1's seven markers. Creating
13
+ any of those from here would hand the CLI a half-built home it believes it wrote
14
+ — so the only safe thing to create is the spool path itself.
15
+
16
+ The three cases below are the three states a machine can be in, and each asserts
17
+ the EXACT set of paths that appear. An assertion on "the events dir exists" would
18
+ pass just as happily if a `VERSION` file appeared beside it.
19
+ """
20
+ import json
21
+ import os
22
+ import stat
23
+ import time
24
+
25
+ import pytest
26
+
27
+ from failproofai_sdk import _resolver, _runtime
28
+ from failproofai_sdk._events import EventNamespace
29
+ from failproofai_sdk._writer import EventWriter
30
+
31
+ #: The interval `_runtime.writer` runs on outside this suite — what a regression
32
+ #: would have to wait out. Read off the constructor default rather than restated.
33
+ _DEFAULT_FLUSH_INTERVAL = EventWriter.__init__.__defaults__[0]
34
+
35
+
36
+ @pytest.fixture
37
+ def home(tmp_path, monkeypatch):
38
+ """An isolated HOME with no failproofai directory at all."""
39
+ monkeypatch.setenv("HOME", str(tmp_path))
40
+ monkeypatch.setenv("FAILPROOFAI_HOME", str(tmp_path / ".failproofai"))
41
+ monkeypatch.delenv("AGENTEYE_HOME", raising=False)
42
+ _resolver.set_base_dir(None)
43
+ yield tmp_path
44
+ _resolver.set_base_dir(None)
45
+
46
+
47
+ def tree(root):
48
+ """Every path under `root`, relative and sorted. The whole filesystem effect."""
49
+ if not root.exists():
50
+ return []
51
+ return sorted(str(p.relative_to(root)) for p in root.rglob("*"))
52
+
53
+
54
+ def emit_one(goal="e"):
55
+ writer = EventWriter(flush_interval=3600)
56
+ EventNamespace(writer).agent_start(session_id="s", agent_id="a", goal=goal)
57
+ writer.flush_now()
58
+ return writer
59
+
60
+
61
+ #: What a single published batch must add, and the complete list of it.
62
+ def spool_paths(batch_names):
63
+ return sorted(["custom-agents", "custom-agents/events", *[f"custom-agents/events/{n}" for n in batch_names]])
64
+
65
+
66
+ # ─────────────────────────────────────────────────────────────────────────────
67
+ # Case 1 — the umbrella spool already exists: add a batch, create nothing else
68
+ # ─────────────────────────────────────────────────────────────────────────────
69
+
70
+
71
+ def test_when_the_spool_exists_only_a_batch_file_appears(home):
72
+ fp = home / ".failproofai"
73
+ (fp / "custom-agents" / "events").mkdir(parents=True)
74
+ before = tree(fp)
75
+
76
+ emit_one()
77
+
78
+ after = tree(fp)
79
+ added = [p for p in after if p not in before]
80
+ assert len(added) == 1, f"expected one new batch file, got {added}"
81
+ assert added[0].startswith("custom-agents/events/event-")
82
+ assert added[0].endswith(".jsonl")
83
+
84
+
85
+ def test_an_existing_failproofai_home_is_not_otherwise_disturbed(home):
86
+ """A configured machine must come through completely untouched.
87
+
88
+ Not just "the files are still there" — unchanged. A rewritten `config.json`
89
+ with identical content would still be a component writing into a file it
90
+ does not own.
91
+ """
92
+ fp = home / ".failproofai"
93
+ (fp / "custom-agents" / "events").mkdir(parents=True)
94
+ (fp / "VERSION").write_text('{"layout":4,"cli":"1.0.1"}')
95
+ (fp / "config.json").write_text('{"mode":{"kind":"oss"}}')
96
+ (fp / "credentials.json").write_text('{"token":"secret"}')
97
+ (fp / "policies").mkdir()
98
+ (fp / "policies" / "mine.mjs").write_text("// mine")
99
+ (fp / "hook-activity").mkdir()
100
+
101
+ owned = {
102
+ p: (p.read_bytes(), p.stat().st_mtime_ns)
103
+ for p in (fp / "VERSION", fp / "config.json", fp / "credentials.json", fp / "policies" / "mine.mjs")
104
+ }
105
+
106
+ emit_one()
107
+
108
+ for path, (content, mtime) in owned.items():
109
+ assert path.read_bytes() == content, f"{path.name} was rewritten"
110
+ assert path.stat().st_mtime_ns == mtime, f"{path.name} was touched"
111
+ assert (fp / "hook-activity").is_dir()
112
+ assert sorted(p.name for p in fp.iterdir()) == [
113
+ "VERSION", "config.json", "credentials.json", "custom-agents", "hook-activity", "policies",
114
+ ]
115
+
116
+
117
+ # ─────────────────────────────────────────────────────────────────────────────
118
+ # Case 2 — the home exists, the spool does not: create only the spool
119
+ # ─────────────────────────────────────────────────────────────────────────────
120
+
121
+
122
+ def test_when_the_home_exists_only_the_spool_path_is_created(home):
123
+ fp = home / ".failproofai"
124
+ fp.mkdir()
125
+ (fp / "VERSION").write_text('{"layout":4,"cli":"1.0.1"}')
126
+ before = tree(fp)
127
+
128
+ emit_one()
129
+
130
+ added = [p for p in tree(fp) if p not in before]
131
+ batch = [p for p in added if p.endswith(".jsonl")]
132
+ assert len(batch) == 1
133
+ assert sorted(added) == spool_paths([batch[0].rsplit("/", 1)[-1]]), (
134
+ f"created something beyond the spool path: {added}"
135
+ )
136
+
137
+
138
+ # ─────────────────────────────────────────────────────────────────────────────
139
+ # Case 3 — nothing exists: create the home and the spool, and stop there
140
+ # ─────────────────────────────────────────────────────────────────────────────
141
+
142
+
143
+ def test_when_nothing_exists_only_the_home_and_the_spool_are_created(home):
144
+ fp = home / ".failproofai"
145
+ assert not fp.exists()
146
+
147
+ emit_one()
148
+
149
+ assert fp.is_dir()
150
+ created = tree(fp)
151
+ batch = [p for p in created if p.endswith(".jsonl")]
152
+ assert len(batch) == 1
153
+ assert created == spool_paths([batch[0].rsplit("/", 1)[-1]]), (
154
+ f"a fresh home got more than the spool: {created}"
155
+ )
156
+
157
+
158
+ @pytest.mark.parametrize(
159
+ "landmark",
160
+ ["VERSION", "config.json", "config.toml", "policies-config.json", "last-version", "cloud.json"],
161
+ )
162
+ def test_no_layout_landmark_is_ever_created(home, landmark):
163
+ """Each of these tells `detectLayout()` a different story, none of them true.
164
+
165
+ `VERSION`/`config.json` would report a home this SDK never built as
166
+ `current`; `config.toml` and layout 1's markers report it `stale`, which is
167
+ what authorises `resetHome()` to start deleting. The SDK has no business
168
+ voting on any of it.
169
+ """
170
+ emit_one()
171
+ assert not (home / ".failproofai" / landmark).exists()
172
+
173
+
174
+ def test_a_home_built_only_by_the_sdk_looks_unconfigured(home):
175
+ """The cross-component property, asserted from this side.
176
+
177
+ Verified against the real `detectLayout()` too: a home holding only
178
+ `custom-agents/` returns `{kind: "absent"}` and `isConfigured()` is false.
179
+ Pinned here so a change on THIS side that starts writing a landmark fails in
180
+ the SDK's own suite rather than in the CLI's, months later.
181
+ """
182
+ emit_one()
183
+ fp = home / ".failproofai"
184
+ assert sorted(p.name for p in fp.iterdir()) == ["custom-agents"]
185
+
186
+
187
+ # ─────────────────────────────────────────────────────────────────────────────
188
+ # Repeat writes, permissions, and the failure paths
189
+ # ─────────────────────────────────────────────────────────────────────────────
190
+
191
+
192
+ def test_repeated_flushes_do_not_recreate_or_churn_the_directories(home):
193
+ writer = EventWriter(flush_interval=3600)
194
+ ns = EventNamespace(writer)
195
+ ns.agent_start(session_id="s", agent_id="a", goal="one")
196
+ writer.flush_now()
197
+
198
+ events = home / ".failproofai" / "custom-agents" / "events"
199
+ inode = events.stat().st_ino
200
+
201
+ for i in range(5):
202
+ ns.agent_start(session_id="s", agent_id="a", goal=f"more-{i}")
203
+ writer.flush_now()
204
+
205
+ assert events.stat().st_ino == inode, "the events directory was replaced"
206
+ assert len(list(events.glob("*.jsonl"))) == 6
207
+ assert list(events.glob("*.tmp")) == [], "a temp file was left behind"
208
+
209
+
210
+ def test_the_process_wide_writer_cannot_flush_into_the_running_test(home):
211
+ """The count above is only meaningful if nothing else writes here.
212
+
213
+ `_runtime.writer` starts at import on a 0.5s interval, and the spool path is
214
+ resolved when a batch is WRITTEN, not when the event is submitted. So an
215
+ event queued while one test's `FAILPROOFAI_HOME` was current gets written
216
+ into whichever test is running half a second later, with no error on either
217
+ side — the test above then counts seven batch files where it wrote six. It
218
+ reached CI exactly once, on one interpreter out of five.
219
+
220
+ `conftest._quiesce_the_process_wide_writer` closes it. This asserts the
221
+ EFFECT rather than the interval, so it still fails if the loop acquires
222
+ another way to wake — and it waits well past the 0.5s default, so a
223
+ regression cannot pass by being fast.
224
+ """
225
+ events = home / ".failproofai" / "custom-agents" / "events"
226
+
227
+ _runtime.writer.submit(
228
+ {"type": "agent_start", "session_id": "leak", "agent_id": "a", "goal": "must not land here"}
229
+ )
230
+ try:
231
+ time.sleep(_DEFAULT_FLUSH_INTERVAL * 3)
232
+ assert not events.exists() or list(events.glob("*.jsonl")) == [], (
233
+ "the process-wide writer flushed into a test's spool"
234
+ )
235
+ finally:
236
+ # Drop it rather than flush it: flushing would write the event into this
237
+ # test's directory, which is the thing being ruled out.
238
+ _runtime.writer._queue.clear()
239
+ _runtime.writer._queued_bytes = 0
240
+
241
+
242
+ def test_created_directories_are_owner_writable_and_not_world_writable(home):
243
+ emit_one()
244
+ for d in (
245
+ home / ".failproofai",
246
+ home / ".failproofai" / "custom-agents",
247
+ home / ".failproofai" / "custom-agents" / "events",
248
+ ):
249
+ mode = stat.S_IMODE(d.stat().st_mode)
250
+ assert mode & stat.S_IRWXU == stat.S_IRWXU, f"{d} is not owner-rwx"
251
+ assert not mode & stat.S_IWOTH, f"{d} is world-writable"
252
+
253
+
254
+ @pytest.mark.skipif(os.geteuid() == 0, reason="root ignores directory permissions")
255
+ def test_an_unwritable_home_raises_and_keeps_the_events_queued(home):
256
+ """A permission error must not be mistaken for a delivered batch.
257
+
258
+ `_flush` returns the drained entries to the queue and re-raises, so the next
259
+ interval retries them. Dropping here would lose live events to a condition
260
+ that is usually temporary — a home mounted read-only, a directory an
261
+ installer briefly re-owned.
262
+ """
263
+ fp = home / ".failproofai"
264
+ fp.mkdir(mode=0o500)
265
+ try:
266
+ writer = EventWriter(flush_interval=3600)
267
+ EventNamespace(writer).agent_start(session_id="s", agent_id="a", goal="held")
268
+ with pytest.raises(OSError):
269
+ writer.flush_now()
270
+ assert len(writer._queue) == 1, "events were dropped on a permission error"
271
+ finally:
272
+ fp.chmod(0o700)
273
+
274
+
275
+ def test_agenteye_home_no_longer_bypasses_the_umbrella(home, monkeypatch):
276
+ """Emitting with it exported must build the umbrella and NOT the legacy tree.
277
+
278
+ The observable half of the resolver change: not just "resolution returns a
279
+ different path", but "the batch is on disk under ~/.failproofai and nothing
280
+ was created under ~/.agenteye".
281
+ """
282
+ legacy = home / ".agenteye"
283
+ monkeypatch.setenv("AGENTEYE_HOME", str(legacy))
284
+ _resolver.set_base_dir(None)
285
+
286
+ emit_one()
287
+
288
+ assert not legacy.exists(), "the legacy root was created despite the change"
289
+ umbrella = home / ".failproofai" / "custom-agents"
290
+ assert umbrella.exists(), "the umbrella was not created"
291
+ assert list((umbrella / "events").glob("*.jsonl")), "no batch landed in the umbrella"
292
+
293
+
294
+ def test_the_batch_written_is_readable_and_carries_the_event(home):
295
+ """Creating directories is worthless if the payload does not survive it."""
296
+ emit_one(goal="round-trip")
297
+ events = home / ".failproofai" / "custom-agents" / "events"
298
+ (batch,) = list(events.glob("*.jsonl"))
299
+ rows = [json.loads(line) for line in batch.read_text(encoding="utf-8").splitlines()]
300
+ assert [r["goal"] for r in rows] == ["round-trip"]
301
+ assert rows[0]["type"] == "agent_start"
302
+
303
+
304
+ @pytest.mark.skipif(os.geteuid() == 0, reason="root ignores file permissions")
305
+ def test_batches_and_the_events_dir_are_not_readable_by_other_local_users(home):
306
+ """These files are transcripts, not metadata.
307
+
308
+ A batch carries `goal`, prompt text, tool arguments and tool output straight
309
+ from the host agent. Under the ordinary `umask 022` they landed `0644` inside
310
+ `0755` directories, so on any shared host — a build box, a bastion, a
311
+ container with several service accounts — every other local user could read
312
+ every agent transcript this SDK spools, for the whole flush+upload window and
313
+ indefinitely if no daemon is running.
314
+
315
+ The sibling `fp-cloud-cli` already writes its credential `0600` inside a `0700`
316
+ directory, so the asymmetry was an oversight rather than a house style. The
317
+ daemon reads these as the SAME user (its unit is `User=<user>` with `HOME`
318
+ set to that user's home), so tightening them costs no delivery.
319
+ """
320
+ prev = os.umask(0o022)
321
+ try:
322
+ emit_one(goal="SECRET-GOAL-TEXT")
323
+ finally:
324
+ os.umask(prev)
325
+
326
+ events = home / ".failproofai" / "custom-agents" / "events"
327
+ assert stat.S_IMODE(events.stat().st_mode) == 0o700, "events dir is enterable by others"
328
+
329
+ batches = list(events.glob("*.jsonl"))
330
+ assert batches, "nothing was published"
331
+ for batch in batches:
332
+ mode = stat.S_IMODE(batch.stat().st_mode)
333
+ assert mode == 0o600, f"{batch.name} is {oct(mode)}, not 0600"
334
+
335
+
336
+ def test_a_tilde_base_dir_is_expanded_rather_than_taken_literally(home, monkeypatch, tmp_path):
337
+ """`configure(base_dir="~/.agenteye")` is a recipe this package prescribes.
338
+
339
+ `_resolver.get_base_dir`'s own docstring lists it as one of three supported
340
+ bridges for a host still running the older `agenteye-collector`, and calls it
341
+ the explicit, visible-at-the-call-site one. `Path("~/.agenteye")` is a
342
+ RELATIVE path whose first segment is the literal character `~`, so without
343
+ `expanduser` the writer's `mkdir(parents=True)` created a `~` directory under
344
+ whatever the process's cwd happened to be and spooled into it. Nothing on the
345
+ machine watches that path: 100% of the telemetry is lost, silently, which is
346
+ the exact "an unread spool is indistinguishable from an idle one" failure the
347
+ prose around that docstring exists to prevent.
348
+ """
349
+ monkeypatch.chdir(tmp_path)
350
+ _resolver.set_base_dir("~/.agenteye")
351
+ try:
352
+ resolved = _resolver.get_base_dir()
353
+ assert resolved.is_absolute(), f"{resolved} is relative"
354
+ assert resolved == home / ".agenteye"
355
+
356
+ emit_one()
357
+ assert not (tmp_path / "~").exists(), "spooled into a literal '~' directory"
358
+ assert list((home / ".agenteye" / "events").glob("*.jsonl"))
359
+ finally:
360
+ _resolver.set_base_dir(None)