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,279 @@
1
+ """The bytes on disk are the product. This file freezes them.
2
+
3
+ Everything downstream — the daemon that uploads the batch, the ingest handler
4
+ that promotes fields into indexed columns, the dedup key computed over the
5
+ whole payload — consumes exactly the JSON this module produces, and none of it
6
+ is generated from a shared schema. There is no build step that would notice a
7
+ renamed key, no type that spans the boundary, and no non-2xx response when a
8
+ field goes missing: ingest returns 200 and the column is simply NULL.
9
+
10
+ So the assertions here are deliberately literal. A golden line per event type,
11
+ compared byte for byte, including key ORDER — because `dedup.rs` hashes the
12
+ canonical form of the payload, and a reordering that looks cosmetic here stops
13
+ retried batches collapsing against rows already stored, which shows up as
14
+ silent duplicate events rather than an error.
15
+
16
+ If one of these fails, the question is never "how do I update the golden". It
17
+ is "what did I just change about the wire format, and who else has to change
18
+ with me".
19
+ """
20
+ import json
21
+ from dataclasses import dataclass, fields as dataclass_fields
22
+ from datetime import date, datetime, timezone
23
+ from decimal import Decimal
24
+ from enum import Enum
25
+ from pathlib import Path
26
+ from uuid import UUID
27
+
28
+ import pytest
29
+
30
+ from failproofai_sdk import _schema
31
+
32
+ TS = "2026-01-02T03:04:05.678901Z"
33
+ BASE = dict(timestamp=TS, session_id="sess-1", agent_id="agent-1")
34
+
35
+ # Every event type, fully populated, serialized exactly as `_writer` serializes it.
36
+ GOLDEN = {
37
+ "tool_use": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "tool_use", "tool_name": "bash", "tool_call_id": "tc-1", "environment": "prod", "input": {"cmd": "ls"}}',
38
+ "tool_result": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "tool_result", "tool_name": "bash", "tool_call_id": "tc-1", "environment": "prod", "output": "ok", "duration_ms": 12}',
39
+ "model_request": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "model_request", "environment": "prod", "model": "claude-opus-5", "messages": [{"role": "user"}], "system": "sys", "tools": [{"name": "t"}], "request_id": "req-1"}',
40
+ "model_response": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "model_response", "environment": "prod", "model": "claude-opus-5", "stop_reason": "end_turn", "input_tokens": 10, "output_tokens": 20, "content": "hi", "role": "assistant", "request_id": "req-1"}',
41
+ "agent_start": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "agent_start", "environment": "prod", "goal": "do it", "parent_id": "p-1"}',
42
+ "agent_end": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "agent_end", "environment": "prod", "outcome": "success", "summary": "done"}',
43
+ "agent_pause": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "agent_pause", "pause_id": "p-1", "environment": "prod", "reason": "quota", "user_id": "u-1"}',
44
+ "agent_resume": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "agent_resume", "pause_id": "p-1", "environment": "prod", "duration_ms": 34, "reason": "ok", "user_id": "u-1"}',
45
+ "hook_triggered": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "hook_triggered", "hook_name": "pre", "hook_id": "h-1", "environment": "prod", "trigger_event": "PreToolUse", "input": {"a": 1}}',
46
+ "hook_completed": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "hook_completed", "hook_name": "pre", "hook_id": "h-1", "environment": "prod", "outcome": "allow", "output": "o", "duration_ms": 56}',
47
+ "error": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "error", "error_type": "ValueError", "message": "boom", "environment": "prod", "traceback": "tb"}',
48
+ "human_wait": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "human_wait", "input_id": "i-1", "environment": "prod", "prompt": "ok?", "options": ["y", "n"], "reason": "approval"}',
49
+ "human_input": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "human_input", "input_id": "i-1", "environment": "prod", "response": "y", "duration_ms": 78}',
50
+ "human_pause": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "human_pause", "environment": "prod", "reason": "lunch", "user_id": "u-1"}',
51
+ "human_interrupt": '{"timestamp": "2026-01-02T03:04:05.678901Z", "session_id": "sess-1", "agent_id": "agent-1", "type": "human_interrupt", "environment": "prod", "reason": "stop", "user_id": "u-1", "at_step": "3"}',
52
+ }
53
+
54
+
55
+ def _events():
56
+ """One fully-populated instance of every event dataclass."""
57
+ s = _schema
58
+ return {
59
+ "tool_use": s.ToolUseEvent(**BASE, tool_name="bash", tool_call_id="tc-1", input={"cmd": "ls"}),
60
+ "tool_result": s.ToolResultEvent(**BASE, tool_name="bash", tool_call_id="tc-1", output="ok", error=None, duration_ms=12),
61
+ "model_request": s.ModelRequestEvent(**BASE, model="claude-opus-5", messages=[{"role": "user"}], system="sys", tools=[{"name": "t"}], request_id="req-1"),
62
+ "model_response": s.ModelResponseEvent(**BASE, model="claude-opus-5", stop_reason="end_turn", input_tokens=10, output_tokens=20, content="hi", role="assistant", request_id="req-1"),
63
+ "agent_start": s.AgentStartEvent(**BASE, goal="do it", parent_id="p-1"),
64
+ "agent_end": s.AgentEndEvent(**BASE, outcome="success", summary="done"),
65
+ "agent_pause": s.AgentPauseEvent(**BASE, pause_id="p-1", reason="quota", user_id="u-1"),
66
+ "agent_resume": s.AgentResumeEvent(**BASE, pause_id="p-1", duration_ms=34, reason="ok", user_id="u-1"),
67
+ "hook_triggered": s.HookTriggeredEvent(**BASE, hook_name="pre", hook_id="h-1", trigger_event="PreToolUse", input={"a": 1}),
68
+ "hook_completed": s.HookCompletedEvent(**BASE, hook_name="pre", hook_id="h-1", outcome="allow", output="o", error=None, duration_ms=56),
69
+ "error": s.ErrorEvent(**BASE, error_type="ValueError", message="boom", traceback="tb"),
70
+ "human_wait": s.HumanWaitEvent(**BASE, input_id="i-1", prompt="ok?", options=["y", "n"], reason="approval"),
71
+ "human_input": s.HumanInputEvent(**BASE, input_id="i-1", response="y", duration_ms=78),
72
+ "human_pause": s.HumanPauseEvent(**BASE, reason="lunch", user_id="u-1"),
73
+ "human_interrupt": s.HumanInterruptEvent(**BASE, reason="stop", user_id="u-1", at_step="3"),
74
+ }
75
+
76
+
77
+ @pytest.fixture(autouse=True)
78
+ def _fixed_environment(monkeypatch):
79
+ """Pin `environment`, which every event stamps from module state."""
80
+ monkeypatch.setenv("AGENTEYE_ENVIRONMENT", "prod")
81
+ from failproofai_sdk import _environment
82
+
83
+ monkeypatch.setattr(_environment, "_environment", None)
84
+
85
+
86
+ @pytest.mark.parametrize("event_type", sorted(GOLDEN))
87
+ def test_serialized_line_is_byte_for_byte_frozen(event_type):
88
+ """Key names, key ORDER, and value encoding — all of it is the contract."""
89
+ event = _events()[event_type]
90
+ # Exactly how `_writer._write_batch` serializes: default=str, no sort_keys.
91
+ line = json.dumps(event.to_dict(), default=str)
92
+ assert line == GOLDEN[event_type]
93
+
94
+
95
+ def test_every_schema_dataclass_has_a_golden():
96
+ """A new event type must arrive with its wire format frozen, not after.
97
+
98
+ Without this, adding a dataclass and forgetting the golden leaves the new
99
+ type's bytes unpinned — and the parametrized test above passes, because it
100
+ iterates the goldens rather than the schema.
101
+ """
102
+ import dataclasses
103
+ import inspect
104
+
105
+ declared = {
106
+ name
107
+ for name, obj in inspect.getmembers(_schema, inspect.isclass)
108
+ if dataclasses.is_dataclass(obj) and name.endswith("Event")
109
+ }
110
+ covered = {type(e).__name__ for e in _events().values()}
111
+ assert declared == covered, (
112
+ f"schema dataclasses without a golden line: {sorted(declared - covered)}. "
113
+ "Add the event to _events() and GOLDEN before shipping it — the wire "
114
+ "format is the one thing nothing downstream will tell you about."
115
+ )
116
+ assert len(GOLDEN) == len(declared)
117
+
118
+
119
+ def test_type_string_matches_the_key_it_is_registered_under():
120
+ """`type` is a free string at ingest — no enum, no allowlist, anywhere.
121
+
122
+ A typo ingests with HTTP 200 and `accepted: 1`, then never matches a filter.
123
+ This is the only place that spelling is checked.
124
+ """
125
+ for event_type, event in _events().items():
126
+ assert event.to_dict()["type"] == event_type
127
+
128
+
129
+ def test_none_valued_fields_are_omitted_not_serialized_as_null():
130
+ """Absent means absent. `null` would overwrite a promoted column with NULL."""
131
+ event = _schema.ToolResultEvent(
132
+ **BASE, tool_name="bash", tool_call_id="tc-1", output=None, error=None, duration_ms=None
133
+ )
134
+ payload = event.to_dict()
135
+ for absent in ("output", "error", "duration_ms"):
136
+ assert absent not in payload
137
+ assert "null" not in json.dumps(payload)
138
+
139
+
140
+ def test_the_five_always_present_keys_are_always_present():
141
+ """ingest.rs rejects a line missing any of the first four; the fifth is filtered on."""
142
+ for event in _events().values():
143
+ payload = event.to_dict()
144
+ for required in ("timestamp", "session_id", "agent_id", "type", "environment"):
145
+ assert required in payload, f"{type(event).__name__} is missing {required}"
146
+
147
+
148
+ def test_every_declared_field_appears_in_its_golden():
149
+ """A golden that omits a field does not freeze that field.
150
+
151
+ `_events()` claims "one fully-populated instance of every event dataclass"
152
+ and left `request_id` unset on both model events — a real field, emitted by
153
+ all four framework adapters, and the one `_schema.py` singles out as
154
+ "appended LAST in `_build`'s ordered list … `test_wire_format.py` freezes
155
+ those bytes, and the dedup key hashes them". Its key name and its POSITION
156
+ were unpinned, so reordering it would have changed the content hash of every
157
+ `model_request` carrying one — retried batches stop collapsing against rows
158
+ already stored, and duplicates appear — with this whole file green.
159
+
160
+ `test_every_schema_dataclass_has_a_golden` compares dataclass NAMES only, so
161
+ it cannot see this.
162
+ """
163
+ missing = []
164
+ for name, event in _events().items():
165
+ payload = json.loads(GOLDEN[name])
166
+ for f in dataclass_fields(event):
167
+ if f.name in {"extra_fields", "timestamp", "session_id", "agent_id"}:
168
+ continue
169
+ if getattr(event, f.name) is None:
170
+ continue
171
+ if f.name not in payload:
172
+ missing.append(f"{name}.{f.name}")
173
+ assert not missing, (
174
+ "these fields are set on the fixture but absent from the frozen golden, "
175
+ f"so their name and position on the wire are unpinned: {sorted(missing)}"
176
+ )
177
+
178
+
179
+ def test_custom_fields_are_merged_last_and_overwrite_a_schema_key_in_place():
180
+ """Extras land last — and an extra that COLLIDES overwrites the declared value.
181
+
182
+ Named for what `_build` actually does. It used to be called
183
+ `..._cannot_shadow_a_schema_key` and only ever passed two novel names, so the
184
+ invariant its name promised was never exercised and is false: `_build` ends
185
+ with `result.update(extra)`, so an extra wins its slot outright.
186
+
187
+ `integrations/_core.guard_extras` (via `FORBIDDEN_EXTRAS`) is the sole
188
+ mitigation and covers only the ADAPTER path; a direct `event.*` call with a
189
+ colliding custom field reaches this. Frozen here so the behaviour is at least
190
+ known, and so tightening it later is a deliberate change with a test to
191
+ update rather than a surprise.
192
+ """
193
+ event = _schema.AgentStartEvent(**BASE, goal="g", extra_fields={"trace_id": "abc", "cost": 0.5})
194
+ payload = event.to_dict()
195
+ assert payload["trace_id"] == "abc"
196
+ assert payload["cost"] == 0.5
197
+ # Insertion order: schema keys first, extras appended.
198
+ assert list(payload)[-2:] == ["trace_id", "cost"]
199
+
200
+ # The collision case, which the old name denied could happen.
201
+ shadowed = _schema.ToolUseEvent(
202
+ **BASE, tool_name="bash", tool_call_id="tc-1", extra_fields={"tool_name": 12345}
203
+ ).to_dict()
204
+ assert shadowed["tool_name"] == 12345, (
205
+ "if this now keeps the declared value, `_build` was tightened — update "
206
+ "this test and say so; the change is a good one"
207
+ )
208
+
209
+
210
+ class _Colour(Enum):
211
+ RED = "red"
212
+
213
+
214
+ @dataclass
215
+ class _Unserializable:
216
+ x: int
217
+
218
+
219
+ @pytest.mark.parametrize(
220
+ "value",
221
+ [
222
+ datetime(2026, 1, 2, 3, 4, 5, tzinfo=timezone.utc),
223
+ date(2026, 1, 2),
224
+ UUID("12345678-1234-5678-1234-567812345678"),
225
+ Decimal("1.25"),
226
+ Path("/tmp/x"),
227
+ _Colour.RED,
228
+ _Unserializable(x=1),
229
+ {1, 2, 3},
230
+ b"bytes",
231
+ ],
232
+ )
233
+ def test_unserializable_payload_values_are_coerced_never_dropped(value):
234
+ """`default=str` exists so one exotic value cannot kill the whole batch.
235
+
236
+ The writer serializes a batch as a single string. Without the coercion, one
237
+ `Decimal` in one event raises inside `_write_batch`, the batch is requeued,
238
+ and the next flush raises on the same value — the queue never drains again
239
+ and every subsequent event in the process is lost too.
240
+ """
241
+ event = _schema.AgentStartEvent(**BASE, extra_fields={"v": value})
242
+ line = json.dumps(event.to_dict(), default=str)
243
+ assert json.loads(line)["v"] == str(value)
244
+
245
+
246
+ def test_duration_ms_is_serialized_as_an_integer():
247
+ """A float here silently NULLs the column: the server's `as_u64()` drops it."""
248
+ for name in ("tool_result", "agent_resume", "hook_completed", "human_input"):
249
+ payload = _events()[name].to_dict()
250
+ assert isinstance(payload["duration_ms"], int)
251
+ assert not isinstance(payload["duration_ms"], bool)
252
+ assert "." not in json.dumps(payload["duration_ms"])
253
+
254
+
255
+ def test_a_batch_is_newline_delimited_with_a_trailing_newline():
256
+ """The daemon splits on newlines and POSTs the bytes verbatim as x-ndjson."""
257
+ events = [e.to_dict() for e in list(_events().values())[:3]]
258
+ content = "\n".join(json.dumps(e, default=str) for e in events) + "\n"
259
+
260
+ assert content.endswith("\n")
261
+ lines = content.split("\n")[:-1]
262
+ assert len(lines) == 3
263
+ for line in lines:
264
+ assert "\n" not in line
265
+ json.loads(line)
266
+
267
+
268
+ def test_no_payload_value_can_smuggle_a_newline_into_the_batch():
269
+ """One raw newline in a value would split one event into two malformed lines.
270
+
271
+ `json.dumps` escapes it, and ingest counts unparseable lines as `skipped`
272
+ while still returning 200 — so a regression here is silent partial loss.
273
+ """
274
+ event = _schema.ErrorEvent(
275
+ **BASE, error_type="E", message="line one\nline two\r\nline three", traceback=None
276
+ )
277
+ line = json.dumps(event.to_dict(), default=str)
278
+ assert "\n" not in line
279
+ assert json.loads(line)["message"] == "line one\nline two\r\nline three"
@@ -0,0 +1,378 @@
1
+ """This SDK imports nothing outside the standard library, and must not start.
2
+
3
+ It is installed into other people's agent processes. Every dependency we declare
4
+ becomes a version constraint on their application, and a resolver conflict we
5
+ cause is one they have to solve — in the observability library, which is the last
6
+ place anyone wants to spend an afternoon. "Zero dependencies" is the reason it is
7
+ safe to add to an existing project without thinking, so it is a promise rather
8
+ than a coincidence, and a promise needs a test.
9
+
10
+ The framework adapters under `integrations/` are the one exception, and they do
11
+ not weaken the promise. Each one imports the framework it adapts — there is no
12
+ other way to subclass its callback base class — but they are reached ONLY through
13
+ `instrument()`, which resolves them by string through `importlib.import_module`
14
+ at call time. So `import failproofai_sdk` still touches nothing outside the
15
+ standard library, and an adapter's import can only run in a process where that
16
+ framework was already installed and imported. Both halves of that are asserted
17
+ below, the second by launching a fresh interpreter rather than by reading source.
18
+
19
+ The declaration and the code are checked separately because they fail
20
+ separately: a stray `import httpx` in a rarely-taken branch is an ImportError in
21
+ the user's process at exactly the wrong moment, and an unused `dependencies`
22
+ entry drags a package into every install for nothing. CI proves the third case
23
+ the source cannot — that the built wheel really installs with `--no-deps`.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import ast
29
+ import sys
30
+
31
+ import pytest
32
+ from pathlib import Path
33
+
34
+ try: # Python 3.11+
35
+ import tomllib
36
+ except ModuleNotFoundError: # 3.10 — `tomli` is the same parser, from the dev extra
37
+ # Deliberately not a skip. The manifest assertions below are the enforcement
38
+ # of the zero-dependency promise, and a promise that stops being checked on
39
+ # the oldest interpreter we advertise is checked where it matters least.
40
+ # `tomli` is a TEST dependency; `[project.dependencies]` stays empty, which
41
+ # is the thing actually being promised.
42
+ import tomli as tomllib
43
+
44
+ import failproofai_sdk
45
+
46
+ PKG = Path(failproofai_sdk.__file__).resolve().parent
47
+ ROOT = PKG.parent
48
+ PYPROJECT = ROOT / "pyproject.toml"
49
+
50
+ #: Everything the package is allowed to import: the standard library, itself, and
51
+ #: nothing else. `sys.stdlib_module_names` is the interpreter's own list, so this
52
+ #: stays correct across versions instead of being a hand-maintained allowlist.
53
+ ALLOWED = set(sys.stdlib_module_names) | {"failproofai_sdk"}
54
+
55
+
56
+ #: Adapters are allowed to import the framework they adapt. Nothing else is, and
57
+ #: this set is deliberately explicit rather than "anything under integrations/":
58
+ #: a new file added there gets scanned like core code until it is named here.
59
+ ADAPTER_IMPORTS = {
60
+ "langchain.py": {"langchain_core", "langgraph"},
61
+ "crewai.py": {"crewai"},
62
+ "llama_index.py": {"llama_index", "llama_index_instrumentation", "pydantic"},
63
+ "pydantic_ai.py": {"pydantic_ai"},
64
+ }
65
+
66
+ #: Framework top-level names that must never appear in `sys.modules` after a bare
67
+ #: `import failproofai_sdk`.
68
+ FRAMEWORK_ROOTS = {
69
+ "langchain", "langchain_core", "langgraph", "crewai",
70
+ "llama_index", "llama_index_instrumentation", "pydantic", "pydantic_ai",
71
+ }
72
+
73
+
74
+ def _module_sources() -> list[Path]:
75
+ return sorted(p for p in PKG.rglob("*.py") if "__pycache__" not in p.parts)
76
+
77
+
78
+ def _allowed_for(path: Path) -> set[str]:
79
+ """What this module may import beyond the standard library."""
80
+ if path.parent.name == "integrations":
81
+ return ALLOWED | ADAPTER_IMPORTS.get(path.name, set())
82
+ return ALLOWED
83
+
84
+
85
+ def _imported_top_level_modules(path: Path) -> set[str]:
86
+ tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
87
+ modules: set[str] = set()
88
+ for node in ast.walk(tree):
89
+ if isinstance(node, ast.Import):
90
+ for alias in node.names:
91
+ modules.add(alias.name.split(".")[0])
92
+ elif isinstance(node, ast.ImportFrom):
93
+ # A relative import (`from . import x`) has level > 0 and no external
94
+ # module to check.
95
+ if node.level == 0 and node.module:
96
+ modules.add(node.module.split(".")[0])
97
+ return modules
98
+
99
+
100
+ def test_the_package_imports_only_the_standard_library():
101
+ """Including imports inside functions, which `ast.walk` reaches too.
102
+
103
+ `_environment.get_environment` really does `import os` inside the function
104
+ body, so a check that only read module-level imports would miss a whole class
105
+ of dependency — the deferred one somebody adds to keep import time down.
106
+ """
107
+ offenders: dict[str, set[str]] = {}
108
+ for path in _module_sources():
109
+ external = _imported_top_level_modules(path) - _allowed_for(path)
110
+ if external:
111
+ offenders[path.name] = external
112
+
113
+ assert not offenders, (
114
+ f"the SDK imports non-stdlib modules: {offenders}. This package is "
115
+ "installed into other people's agent processes, where every dependency "
116
+ "we add is a constraint they have to satisfy. If one is genuinely "
117
+ "unavoidable, it needs a deliberate decision and a `dependencies` entry — "
118
+ "not an import that fails at runtime in a branch CI never took."
119
+ )
120
+
121
+
122
+ def test_the_scan_found_the_modules_it_was_meant_to_scan():
123
+ """Guards against the check above passing because it read nothing."""
124
+ names = {p.name for p in _module_sources()}
125
+ assert {"_writer.py", "_events.py", "_schema.py", "_resolver.py"} <= names, (
126
+ f"the package walk found only {sorted(names)}"
127
+ )
128
+
129
+
130
+ def test_no_runtime_dependencies_are_declared():
131
+ """The manifest half of the same promise."""
132
+ manifest = tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))
133
+ declared = manifest["project"].get("dependencies", [])
134
+ assert declared == [], (
135
+ f"pyproject declares runtime dependencies: {declared}. Installing this "
136
+ "SDK must not pull anything in."
137
+ )
138
+
139
+
140
+ #: The only distributions the dev extra may name, each with the reason it is there.
141
+ #: This is an allowlist rather than a count, because the failure it prevents is a
142
+ #: convenience library drifting in — anything here is one `--extra dev` away from a
143
+ #: user's environment, and none of it is covered by the zero-dependency promise.
144
+ ALLOWED_DEV_DEPENDENCIES = {
145
+ "pytest": "the test runner",
146
+ "pytest-asyncio": "the adapters are half-async; their scopes are exercised "
147
+ "under `async with`, which needs an async test runner",
148
+ "tomli": "tomllib's backport; Python 3.10 has no stdlib TOML parser, and "
149
+ "test_zero_dependencies.py must not stop checking the manifest there",
150
+ }
151
+
152
+ #: Extras that exist only to pull a FRAMEWORK in, mapped to the distribution each
153
+ #: is allowed to name. The adapter code always ships in the base wheel and lazy-
154
+ #: imports, so these gate nothing on our side — they are a convenience for users
155
+ #: who do not already have the framework.
156
+ FRAMEWORK_EXTRAS = {
157
+ "langchain": {"langchain-core"},
158
+ "langgraph": {"langgraph"},
159
+ "crewai": {"crewai", "onnxruntime"},
160
+ "llamaindex": {"llama-index-core"},
161
+ "pydantic-ai": {"pydantic-ai-slim"},
162
+ }
163
+
164
+
165
+ def _named(spec: str) -> str:
166
+ """"crewai>=1.13,<2; python_version < '3.11'" -> "crewai"."""
167
+ head = spec.split(";")[0].strip().split("[")[0]
168
+ for op in ("===", "~=", "!=", ">=", "<=", "==", ">", "<"):
169
+ head = head.split(op)[0]
170
+ return head.strip()
171
+
172
+
173
+ def test_dev_dependencies_are_test_only_and_stay_out_of_the_install():
174
+ """`[project.optional-dependencies].dev` never reaches a plain install."""
175
+ manifest = tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))
176
+ extras = manifest["project"].get("optional-dependencies", {})
177
+ assert set(extras) == {"dev"} | set(FRAMEWORK_EXTRAS), (
178
+ f"unexpected extras: {sorted(set(extras) - {'dev'} - set(FRAMEWORK_EXTRAS))}"
179
+ )
180
+
181
+ named = {_named(spec) for spec in extras["dev"]}
182
+ unexpected = named - set(ALLOWED_DEV_DEPENDENCIES)
183
+ assert not unexpected, (
184
+ f"the dev extra grew beyond the test tooling: {sorted(unexpected)}. Each "
185
+ "entry needs a reason in ALLOWED_DEV_DEPENDENCIES, because anything here "
186
+ "is one `--extra dev` away from a user's environment."
187
+ )
188
+
189
+
190
+ def test_the_package_declares_itself_typed():
191
+ """`py.typed` must ship, or the annotations are invisible to a type checker."""
192
+ assert (PKG / "py.typed").is_file()
193
+ manifest = tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))
194
+ package_data = manifest["tool"]["setuptools"]["package-data"]
195
+ assert "py.typed" in package_data["failproofai_sdk"], (
196
+ "py.typed exists on disk but is not in [tool.setuptools.package-data], so "
197
+ "it is absent from the built wheel — where it is the only thing that "
198
+ "makes the type hints count."
199
+ )
200
+
201
+
202
+ def test_importing_the_package_does_not_touch_the_network_or_the_filesystem_eagerly():
203
+ """Import must be cheap and side-effect-light beyond starting the flush thread.
204
+
205
+ Constructing `EventWriter` at module scope already starts a daemon thread,
206
+ which is a documented cost. Creating directories or reading config at import
207
+ time would be a further one, paid by every process that imports the package
208
+ whether or not it emits anything.
209
+ """
210
+ # EVERY module, not just `__init__.py`. This read one file and asserted on
211
+ # `ast.Assign` nodes only — and `__init__.py` has none: the writer and the
212
+ # namespace moved to `_runtime.py`, whose own docstring says so. So the set
213
+ # was always empty and `set() <= {...}` could not fail, while the two things
214
+ # it claimed to guard — import-time work added in `_runtime.py` or any other
215
+ # imported module, and a bare expression statement like `os.makedirs(...)`,
216
+ # which is an `ast.Expr` and not an `ast.Assign` — were both invisible to it.
217
+ # Negative-controlled: a planted `mkdir` + `open()` in `_runtime.py` used to
218
+ # leave the whole file green.
219
+ # Each of these is module-level work that IS intended, and each is here
220
+ # deliberately rather than by a pattern that would also admit a `mkdir`:
221
+ #
222
+ # EventWriter / EventNamespace the documented cost — starts the flush thread
223
+ # count `itertools.count()` for the batch sequence
224
+ # register `atexit.register(_flush_all_at_exit)`
225
+ # _Auto a sentinel object
226
+ # getLogger / frozenset / tuple pure, no I/O
227
+ #
228
+ # Adding to this list is the deliberate act. Anything NOT here — a `mkdir`,
229
+ # an `open`, a config read, a request — is paid by every process that
230
+ # imports this package, in someone else's agent, whether or not they ever
231
+ # emit an event.
232
+ allowed = {
233
+ "EventWriter",
234
+ "EventNamespace",
235
+ "count",
236
+ "register",
237
+ "_Auto",
238
+ "getLogger",
239
+ "frozenset",
240
+ "tuple",
241
+ }
242
+ offenders: list[str] = []
243
+ for module in sorted(PKG.glob("*.py")):
244
+ tree = ast.parse(module.read_text(encoding="utf-8"))
245
+ for node in tree.body:
246
+ if isinstance(node, ast.Assign) and isinstance(node.value, ast.Call):
247
+ call = node.value
248
+ elif isinstance(node, ast.Expr) and isinstance(node.value, ast.Call):
249
+ call = node.value
250
+ else:
251
+ continue
252
+ func = call.func
253
+ name = (
254
+ func.id
255
+ if isinstance(func, ast.Name)
256
+ else (func.attr if isinstance(func, ast.Attribute) else None)
257
+ )
258
+ if name is None or name in allowed:
259
+ continue
260
+ offenders.append(f"{module.name}:{node.lineno}: {name}()")
261
+
262
+ assert not offenders, (
263
+ "module-level work runs at `import failproofai_sdk`, in someone else's "
264
+ "agent process, whether or not they ever emit an event:\n "
265
+ + "\n ".join(offenders)
266
+ )
267
+
268
+
269
+ # ─────────────────────────────────────────────────────────────────────────────
270
+ # The adapters exist, so laziness is now the load-bearing half
271
+ # ─────────────────────────────────────────────────────────────────────────────
272
+
273
+
274
+ def test_importing_the_package_loads_no_framework():
275
+ """The promise, asserted at runtime instead of inferred from the source.
276
+
277
+ `integrations/` may import frameworks; what must stay true is that nothing
278
+ reaches it unless the caller asks. A fresh interpreter is used because this
279
+ test process has already imported half of everything — checking `sys.modules`
280
+ in-process would pass on the strength of nobody having imported LangChain yet.
281
+ """
282
+ import json
283
+ import subprocess
284
+
285
+ probe = (
286
+ "import json, sys; import failproofai_sdk; "
287
+ f"roots = {sorted(FRAMEWORK_ROOTS)!r}; "
288
+ "print(json.dumps(sorted({m.split('.')[0] for m in sys.modules} & set(roots))))"
289
+ )
290
+ result = subprocess.run(
291
+ [sys.executable, "-c", probe], capture_output=True, text=True, cwd=str(ROOT), timeout=60
292
+ )
293
+ assert result.returncode == 0, result.stderr
294
+ loaded = json.loads(result.stdout.strip())
295
+ assert loaded == [], (
296
+ f"`import failproofai_sdk` pulled in {loaded}. The adapters must stay behind "
297
+ "`instrument()`, which resolves them by string at call time — an eager "
298
+ "import here is an ImportError in every process that has not installed "
299
+ "that framework."
300
+ )
301
+
302
+
303
+ def test_the_adapter_registry_holds_strings_not_modules():
304
+ """`_REGISTRY` maps a name to a dotted path; importing it here would defeat it."""
305
+ from failproofai_sdk.integrations import _REGISTRY
306
+
307
+ assert _REGISTRY, "the adapter registry is empty"
308
+ for name, target in _REGISTRY.items():
309
+ assert isinstance(target, str), f"{name} maps to {type(target).__name__}, not a path"
310
+ assert target.startswith("failproofai_sdk.integrations."), target
311
+
312
+
313
+ def test_instrument_on_a_bare_interpreter_installs_nothing_and_does_not_raise():
314
+ """The realistic first call: no framework installed, `instrument()` anyway.
315
+
316
+ It must return an empty result rather than raising — an observability call
317
+ that explodes because the user has not installed LangChain is worse than one
318
+ that does nothing.
319
+ """
320
+ import subprocess
321
+
322
+ probe = (
323
+ "import failproofai_sdk as f; r = f.instrument(); "
324
+ "assert r == () or r == [], r; print('ok')"
325
+ )
326
+ result = subprocess.run(
327
+ [sys.executable, "-c", probe], capture_output=True, text=True, cwd=str(ROOT), timeout=60
328
+ )
329
+ assert result.returncode == 0, result.stderr
330
+ assert result.stdout.strip() == "ok"
331
+
332
+
333
+ @pytest.mark.parametrize("extra", sorted(FRAMEWORK_EXTRAS), ids=sorted(FRAMEWORK_EXTRAS))
334
+ def test_a_framework_extra_names_only_its_own_framework(extra):
335
+ """Each one pulls in a framework and nothing else.
336
+
337
+ An extra that quietly added a runtime library would put it in the
338
+ environment of everyone who typed `failproofai-sdk[langchain]`, which is the
339
+ zero-dependency promise leaking out through a side door.
340
+ """
341
+ manifest = tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))
342
+ specs = manifest["project"]["optional-dependencies"][extra]
343
+ assert specs, f"the {extra} extra is empty"
344
+ assert {_named(s) for s in specs} == FRAMEWORK_EXTRAS[extra]
345
+
346
+
347
+ def test_no_extra_refers_back_to_this_package():
348
+ """A self-referential extra is how a package installs something else over itself.
349
+
350
+ On public PyPI `agenteye` is the CLI, which is what made this a real hazard
351
+ in the SDK's previous home: `agenteye[langchain]` would have pulled the CLI
352
+ in on top of the SDK. The name is different here; the shape of the mistake is
353
+ not, and there is deliberately no `[all]` either — an extra that installs
354
+ four agent frameworks at once is a resolver problem handed to somebody who
355
+ wanted a telemetry library.
356
+ """
357
+ manifest = tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))
358
+ extras = manifest["project"]["optional-dependencies"]
359
+ assert "all" not in extras
360
+ for name, specs in extras.items():
361
+ for spec in specs:
362
+ assert _named(spec) not in {"failproofai-sdk", "failproofai_sdk", "agenteye"}, (
363
+ f"the {name} extra refers back to this package: {spec!r}"
364
+ )
365
+
366
+
367
+ def test_every_framework_extra_has_an_upper_bound():
368
+ """A ceiling on each, or a clean build a year from now silently stops working.
369
+
370
+ The adapters subclass framework callback bases. A new major shifts that API,
371
+ the adapter stops receiving events, and it raises nothing while doing so —
372
+ the failure is an empty dashboard, not a traceback.
373
+ """
374
+ manifest = tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))
375
+ extras = manifest["project"]["optional-dependencies"]
376
+ for name in FRAMEWORK_EXTRAS:
377
+ for spec in extras[name]:
378
+ assert "<" in spec, f"{name}: {spec!r} has no upper bound"