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,371 @@
1
+ """The published docs site makes claims about this package, and nothing checked them.
2
+
3
+ `docs/start/integrations/` on the site is a second copy of the integration guide,
4
+ maintained by hand alongside `sdk/python/docs/`. Two copies of the same claims is
5
+ exactly the shape that drifts, and it already had: the site named
6
+ `capture_content` for CrewAI and `session_id` for LlamaIndex, neither of which
7
+ those adapters read, and told readers to verify a Pydantic AI install by printing
8
+ `agent.capabilities`, which raises `AttributeError`.
9
+
10
+ None of that produced an error for a reader — `instrument()` drops unknown option
11
+ keys by design — so only a test can catch it.
12
+
13
+ Same shape as `test_spool_contract.py` and `fp-cloud-cli/tests/test_fp_home_contract.py`:
14
+ read the other side's source, skip when it is genuinely absent (an installed
15
+ sdist has no docs site), and fail when `FAILPROOFAI_SDK_REQUIRE_CONTRACT` says the
16
+ repository should be there.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import ast
21
+ import os
22
+ import re
23
+ import textwrap
24
+ from pathlib import Path
25
+
26
+ import pytest
27
+
28
+ import failproofai_sdk
29
+ from failproofai_sdk import _events as _events_module
30
+
31
+ # sdk/python -> sdk -> repo root -> docs/
32
+ _HERE = Path(failproofai_sdk.__file__).resolve().parent.parent
33
+ SITE = _HERE.parent.parent / "docs" / "start" / "integrations"
34
+
35
+ REQUIRE = os.environ.get("FAILPROOFAI_SDK_REQUIRE_CONTRACT", "").strip().lower() in {
36
+ "1",
37
+ "true",
38
+ "yes",
39
+ "on",
40
+ }
41
+
42
+ #: site page -> the adapter module it documents.
43
+ PAGE_ADAPTER = {
44
+ "langchain.mdx": "langchain",
45
+ "crewai.mdx": "crewai",
46
+ "llamaindex.mdx": "llama_index",
47
+ "pydantic-ai.mdx": "pydantic_ai",
48
+ }
49
+
50
+
51
+ def _page(name: str) -> str:
52
+ path = SITE / name
53
+ if path.is_file():
54
+ return path.read_text(encoding="utf-8")
55
+ message = (
56
+ f"{path} is missing. In an installed sdist that is expected. In the "
57
+ f"repository it means the docs site moved and these claims are now "
58
+ f"unguarded — re-point this test rather than deleting it."
59
+ )
60
+ if REQUIRE:
61
+ pytest.fail(message)
62
+ pytest.skip(message)
63
+
64
+
65
+ def _adapter_options(module_name: str) -> set[str]:
66
+ source = (
67
+ Path(failproofai_sdk.__file__).resolve().parent
68
+ / "integrations"
69
+ / f"{module_name}.py"
70
+ ).read_text(encoding="utf-8")
71
+ names = set(re.findall(r'options\.get\(\s*["\'](\w+)["\']', source))
72
+ for node in ast.walk(ast.parse(source)):
73
+ if isinstance(node, ast.ClassDef) and node.name == "_Options":
74
+ names |= {
75
+ stmt.target.id
76
+ for stmt in node.body
77
+ if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name)
78
+ }
79
+ return names
80
+
81
+
82
+ def _documented_options(text: str, adapter: str) -> set[str]:
83
+ out: set[str] = set()
84
+ for block in re.findall(r"```python[^\n]*\n(.*?)```", text, re.S):
85
+ try:
86
+ tree = ast.parse(textwrap.dedent(block))
87
+ except SyntaxError:
88
+ continue
89
+ for node in ast.walk(tree):
90
+ if not (isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute)):
91
+ continue
92
+ if node.func.attr != "instrument" or not node.args:
93
+ continue
94
+ first = node.args[0]
95
+ if isinstance(first, ast.Constant) and first.value == adapter:
96
+ out |= {kw.arg for kw in node.keywords if kw.arg}
97
+ return out
98
+
99
+
100
+ @pytest.mark.parametrize("page,adapter", sorted(PAGE_ADAPTER.items()))
101
+ def test_a_site_page_only_documents_options_the_adapter_reads(page, adapter):
102
+ documented = _documented_options(_page(page), adapter)
103
+ if not documented:
104
+ pytest.skip(f"{page} documents no instrument() options")
105
+ invented = documented - _adapter_options(adapter)
106
+ assert not invented, (
107
+ f"docs/start/integrations/{page} documents instrument({adapter!r}, ...) "
108
+ f"options {sorted(invented)} that the adapter never reads. Unknown keys "
109
+ f"are dropped silently, so a reader gets no error and no effect."
110
+ )
111
+
112
+
113
+ @pytest.mark.parametrize("page", sorted(PAGE_ADAPTER))
114
+ def test_a_site_page_only_names_event_methods_that_exist(page):
115
+ named = set(re.findall(r"failproofai_sdk\.event\.([a-z_]+)\s*\(", _page(page)))
116
+ missing = {n for n in named if not hasattr(failproofai_sdk.event, n)}
117
+ assert not missing, f"{page} names event.{sorted(missing)}, which does not exist"
118
+
119
+
120
+ @pytest.mark.parametrize("page", sorted(PAGE_ADAPTER))
121
+ def test_a_site_page_only_names_api_this_package_exports(page):
122
+ named = set(re.findall(r"failproofai_sdk\.([a-z_]+)\s*\(", _page(page))) - {"event"}
123
+ missing = {n for n in named if not hasattr(failproofai_sdk, n)}
124
+ assert not missing, f"{page} names failproofai_sdk.{sorted(missing)}, which does not exist"
125
+
126
+
127
+ def test_the_pydantic_page_verifies_the_capability_the_way_that_works():
128
+ """`agent.capabilities` raises AttributeError.
129
+
130
+ Pydantic AI merges the list you pass into a single `root_capability`, so the
131
+ verification snippet the page tells a reader to run has to go through it.
132
+ """
133
+ text = _page("pydantic-ai.mdx")
134
+ assert "root_capability.capabilities" in text, (
135
+ "the pydantic page must verify through `agent.root_capability.capabilities`"
136
+ )
137
+ # Scoped to CODE, not prose: the page explains in words that
138
+ # `agent.capabilities` does not exist, and that sentence should stay.
139
+ code = "\n".join(re.findall(r"```python[^\n]*\n(.*?)```", text, re.S))
140
+ assert not re.search(r"(?<!root_capability\.)\bagent\.capabilities\b", code), (
141
+ "a code sample on the pydantic page reads `agent.capabilities`, "
142
+ "which raises AttributeError"
143
+ )
144
+
145
+
146
+ # ─────────────────────────────────────────────────────────────────────────────
147
+ # Structure
148
+ # ─────────────────────────────────────────────────────────────────────────────
149
+ #
150
+ # Four framework pages written one at a time drifted into four different shapes:
151
+ # LangGraph explained session control under "Control the session", CrewAI buried
152
+ # the same thing as an H3 inside "Options", LlamaIndex had it in a callout, and
153
+ # Pydantic AI did not mention it at all. A reader who learns one page should be
154
+ # able to skim the next.
155
+
156
+ #: The order every framework page follows. Pages may insert their own sections
157
+ #: between these, but these must appear, in this order.
158
+ SPINE = [
159
+ "Install",
160
+ "Instrument",
161
+ "What gets recorded",
162
+ "Example",
163
+ "Name your spans",
164
+ "Control the session",
165
+ "Options",
166
+ "Common problems",
167
+ "Next",
168
+ ]
169
+
170
+
171
+ def _headings(page: str) -> list[str]:
172
+ return [h.strip() for h in re.findall(r"^## (.+)$", _page(page), re.M)]
173
+
174
+
175
+ @pytest.mark.parametrize("page", sorted(PAGE_ADAPTER))
176
+ def test_a_framework_page_follows_the_shared_spine(page):
177
+ headings = _headings(page)
178
+ missing = [h for h in SPINE if h not in headings]
179
+ assert not missing, f"{page} is missing sections {missing}; has {headings}"
180
+
181
+ positions = [headings.index(h) for h in SPINE]
182
+ assert positions == sorted(positions), (
183
+ f"{page} orders the shared sections differently: "
184
+ f"{[headings[i] for i in positions]}"
185
+ )
186
+
187
+
188
+ @pytest.mark.parametrize("page", sorted(PAGE_ADAPTER))
189
+ def test_a_framework_page_is_explicit_about_human_in_the_loop(page):
190
+ """Either it documents the pairs, or it says why there are none.
191
+
192
+ Silence reads as an oversight, and the reader cannot tell whether the
193
+ framework has no HITL surface or we simply did not map it.
194
+ """
195
+ text = _page(page)
196
+ assert "## Human in the loop" in text or "human-in-the-loop pair" in text, (
197
+ f"{page} neither documents human-in-the-loop nor states that the "
198
+ f"framework has none"
199
+ )
200
+
201
+
202
+ # --- Keyword arguments, on every page in the directory ----------------------
203
+ #
204
+ # The tests above check that a method NAME exists. Nothing checked the keywords,
205
+ # and `event.*` ends in `**fields` — so a wrong one is accepted, stored as a
206
+ # custom field, and never populates the column the reader wanted. The docs told
207
+ # people to call `model_response(response=...)` for a long time; the parameter is
208
+ # `content`, so every reader who copied it got an event whose `content` column
209
+ # was empty and a stray `response` field they never asked for. Nothing raised.
210
+ #
211
+ # These also widen the net: PAGE_ADAPTER covers only the four framework pages, so
212
+ # `custom-agents.mdx` — which carries the most hand-written event calls on the
213
+ # site — was scanned by nothing at all. That is where the bug survived.
214
+
215
+ ALL_PAGES = sorted(p.name for p in SITE.glob("*.mdx")) if SITE.is_dir() else []
216
+
217
+ # Custom fields are legal, and the adapters namespace theirs. A bare unknown
218
+ # keyword on a documented call is the typo case.
219
+ #
220
+ # Two exemptions, both deliberate. `fw_*` is the documented namespace for a
221
+ # framework's own metadata. And `_PROMOTED_NUMERIC` names the keys ingest lifts
222
+ # into real columns — `duration_ms` on `model_response` is the one the docs
223
+ # actively tell you to pass, and it travels through `**fields` by design, so the
224
+ # SDK validates it there rather than declaring it a parameter. Reading the set
225
+ # from the SDK keeps this from drifting the moment a fourth key is promoted.
226
+ _CUSTOM_FIELD_PREFIX = "fw_"
227
+ _PROMOTED = set(_events_module._PROMOTED_NUMERIC)
228
+
229
+
230
+ def _event_calls(text: str):
231
+ """(method, {keywords}) for every failproofai_sdk.event.X(...) in a python block."""
232
+ for block in re.findall(r"```python[^\n]*\n(.*?)```", text, re.S):
233
+ try:
234
+ tree = ast.parse(textwrap.dedent(block))
235
+ except SyntaxError:
236
+ continue
237
+ for node in ast.walk(tree):
238
+ if not (isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute)):
239
+ continue
240
+ value = node.func.value
241
+ if not (isinstance(value, ast.Attribute) and value.attr == "event"):
242
+ continue
243
+ yield node.func.attr, {kw.arg for kw in node.keywords if kw.arg}
244
+
245
+
246
+ @pytest.mark.parametrize("page", ALL_PAGES)
247
+ def test_every_documented_event_call_uses_real_keywords(page):
248
+ import inspect
249
+
250
+ bad = []
251
+ for method, keywords in _event_calls(_page(page)):
252
+ fn = getattr(failproofai_sdk.event, method, None)
253
+ if fn is None:
254
+ continue # the name test above owns this failure
255
+ real = set(inspect.signature(fn).parameters) - {"fields"}
256
+ for kw in sorted(keywords - real):
257
+ if kw.startswith(_CUSTOM_FIELD_PREFIX) or kw in _PROMOTED:
258
+ continue
259
+ bad.append(f"event.{method}({kw}=...)")
260
+ assert not bad, (
261
+ f"docs/start/integrations/{page} passes keywords that are not parameters: "
262
+ f"{bad}. `event.*` ends in **fields, so these are accepted silently and "
263
+ f"stored as custom fields instead of filling the column the reader wanted."
264
+ )
265
+
266
+
267
+ def test_the_keyword_scan_actually_finds_calls():
268
+ """Otherwise a change to the block format makes every page above pass vacuously."""
269
+ found = sum(len(list(_event_calls(_page(p)))) for p in ALL_PAGES)
270
+ assert found >= 10, f"only {found} event calls found across {len(ALL_PAGES)} pages"
271
+
272
+
273
+ # ─────────────────────────────────────────────────────────────────────────────
274
+ # Claims that are true of ONE adapter and were written as if universal
275
+ # ─────────────────────────────────────────────────────────────────────────────
276
+
277
+ #: The content-capture option each adapter actually reads. `None` means the
278
+ #: adapter has no content switch at all, which is a fact worth stating rather
279
+ #: than an omission — CrewAI's only option is `session_id`, so
280
+ #: `instrument("crewai", capture_content=False)` raises nothing and records
281
+ #: everything.
282
+ CONTENT_OPTION = {
283
+ "langchain": "capture_content",
284
+ "pydantic_ai": "capture_content",
285
+ "llama_index": "capture_messages",
286
+ "crewai": None,
287
+ }
288
+
289
+
290
+ def _adapter_source(name: str) -> str:
291
+ return (
292
+ Path(failproofai_sdk.__file__).resolve().parent / "integrations" / f"{name}.py"
293
+ ).read_text(encoding="utf-8")
294
+
295
+
296
+ @pytest.mark.parametrize("adapter,option", sorted(CONTENT_OPTION.items(), key=lambda kv: kv[0]))
297
+ def test_the_content_option_map_matches_what_each_adapter_reads(adapter, option):
298
+ """The map above is what the cross-adapter pages are checked against.
299
+
300
+ Pinned to the source so the map cannot quietly become the stale thing.
301
+ """
302
+ source = _adapter_source(adapter)
303
+ for candidate in ("capture_content", "capture_messages"):
304
+ reads_it = f'options.get("{candidate}"' in source
305
+ assert reads_it == (candidate == option), (
306
+ f"{adapter}.py {'reads' if reads_it else 'does not read'} {candidate!r}, "
307
+ f"but CONTENT_OPTION says its switch is {option!r}"
308
+ )
309
+
310
+
311
+ def test_no_cross_adapter_page_presents_one_adapters_option_as_universal():
312
+ """`how-it-works.mdx` told every reader `capture_content=False` was THE switch.
313
+
314
+ It is read by two adapters of four. LlamaIndex spells it `capture_messages`
315
+ and CrewAI has none — and `instrument()` drops options an adapter does not
316
+ read, so `instrument("crewai", capture_content=False)` raised nothing and
317
+ changed nothing. A reader on regulated data shipped believing prompts and
318
+ completions had stopped being recorded, and `collector.redact` explicitly
319
+ does not apply to SDK events, so nothing was behind it.
320
+
321
+ The four per-framework pages are checked elsewhere; these are the pages that
322
+ speak about all of them at once and so must name the difference.
323
+ """
324
+ root = SITE.parent.parent # docs/
325
+ pages = [SITE / "how-it-works.mdx", SITE / "custom-agents.mdx"]
326
+ checked = 0
327
+ for page in pages:
328
+ if not page.is_file():
329
+ continue
330
+ checked += 1
331
+ text = page.read_text(encoding="utf-8")
332
+ if "capture_content" not in text:
333
+ continue
334
+ # If it names one adapter's option it must name the others, or a reader
335
+ # applies it to a framework that ignores it.
336
+ assert "capture_messages" in text, (
337
+ f"{page.name} names `capture_content` without `capture_messages`, so a "
338
+ "LlamaIndex reader is told to set an option that adapter does not read"
339
+ )
340
+ assert "crewai" in text.lower(), (
341
+ f"{page.name} presents a content switch without saying CrewAI has none"
342
+ )
343
+ if REQUIRE:
344
+ assert checked, "neither cross-adapter page was found; the guard checked nothing"
345
+
346
+
347
+ def test_no_guide_still_says_a_float_duration_is_silently_dropped():
348
+ """It raises `ValueError`, and has since `_validate_promoted_numeric` landed.
349
+
350
+ The skill files were corrected when that changed and the site reference and
351
+ the manual guide were not, so a reader was told the worst case was an empty
352
+ column and got an exception out of `event.model_response()` on their first
353
+ instrumented model call — outside the SDK's own try/except, so it takes down
354
+ the agent turn.
355
+ """
356
+ bad = re.compile(r"(stored as null|silently nulls|silently drop\w*)", re.I)
357
+ roots = [
358
+ SITE.parent.parent / "reference" / "python-sdk.mdx",
359
+ Path(failproofai_sdk.__file__).resolve().parent.parent / "docs",
360
+ ]
361
+ hits = []
362
+ for root in roots:
363
+ files = [root] if root.is_file() else sorted(root.rglob("*.md*")) if root.is_dir() else []
364
+ for path in files:
365
+ for i, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
366
+ if "duration_ms" in line and bad.search(line):
367
+ hits.append(f"{path.name}:{i}: {line.strip()[:110]}")
368
+ assert not hits, (
369
+ "these say a float `duration_ms` is dropped or nulled; it raises ValueError "
370
+ "at the call site:\n " + "\n ".join(hits)
371
+ )
@@ -0,0 +1,165 @@
1
+ """The skill's code is instructions an agent executes, so it is code under test.
2
+
3
+ An agent reading `SKILL.md` copies these blocks into someone's real agent loop.
4
+ A bug in a documented snippet ships to every reader, and unlike the package it
5
+ is never exercised by anything — which is why it gets a guard rather than a
6
+ proofread.
7
+ """
8
+ import ast
9
+ import re
10
+ import textwrap
11
+ from pathlib import Path
12
+
13
+ import pytest
14
+
15
+ SKILL = Path(__file__).resolve().parents[1] / "skill"
16
+ SNIPPET_FILES = sorted(SKILL.rglob("*.md"))
17
+
18
+
19
+ def python_blocks(path):
20
+ """Every ```python fenced block in a markdown file, dedented.
21
+
22
+ Dedented because a fenced block nested inside a list item is indented in the
23
+ source and is still valid Python once that common prefix is removed. Without
24
+ this the only way to keep a snippet testable was to hoist it out of the list
25
+ it belongs to — so the guard was quietly shaping the prose. `test_site_docs.py`
26
+ already dedents; these two scans should not disagree about what a block is.
27
+ """
28
+ text = path.read_text(encoding="utf-8")
29
+ blocks = re.findall(r"```python[^\n]*\n(.*?)```", text, re.DOTALL)
30
+ return [textwrap.dedent(b) for b in blocks]
31
+
32
+
33
+ def test_there_are_snippets_to_check():
34
+ """A path typo would otherwise make every test below pass vacuously."""
35
+ blocks = [b for p in SNIPPET_FILES for b in python_blocks(p)]
36
+ assert len(blocks) >= 5, f"found {len(blocks)} python blocks under {SKILL}"
37
+
38
+
39
+ @pytest.mark.parametrize("path", SNIPPET_FILES, ids=lambda p: p.name)
40
+ def test_every_documented_snippet_parses(path):
41
+ """A snippet that does not compile is worse than no snippet."""
42
+ for i, block in enumerate(python_blocks(path)):
43
+ try:
44
+ ast.parse(block)
45
+ except SyntaxError as exc: # pragma: no cover - failure path
46
+ pytest.fail(f"{path.name} block {i} does not parse: {exc}")
47
+
48
+
49
+ @pytest.mark.parametrize("path", SNIPPET_FILES, ids=lambda p: p.name)
50
+ def test_lifecycle_brackets_catch_baseexception_not_exception(path):
51
+ """A cancelled tool or run must still emit its closing event.
52
+
53
+ `asyncio.CancelledError` inherits straight from `BaseException`, so
54
+ `except Exception` does not see it — and cancellation is the ordinary way an
55
+ async tool ends when a timeout fires or a caller gives up, not an exotic
56
+ one. The result is a `tool_use` with no `tool_result`, or an `agent_start`
57
+ with no `agent_end`: an orphaned event that also holds its correlation slot
58
+ until the cap evicts it.
59
+
60
+ Only handlers that EMIT are checked. A bare `except Exception` around the
61
+ emit call itself is correct and must stay — swallowing `KeyboardInterrupt`
62
+ there would make telemetry able to block a Ctrl-C.
63
+ """
64
+ for i, block in enumerate(python_blocks(path)):
65
+ try:
66
+ tree = ast.parse(block)
67
+ except SyntaxError:
68
+ continue
69
+ for node in ast.walk(tree):
70
+ if not isinstance(node, ast.ExceptHandler):
71
+ continue
72
+ body = ast.unparse(ast.Module(body=node.body, type_ignores=[]))
73
+ emits = re.search(r"\b(_emit|event\.\w+|failproofai_sdk\.event\.\w+)\s*\(", body)
74
+ if not emits:
75
+ continue
76
+ caught = ast.unparse(node.type) if node.type else "<bare>"
77
+ assert "BaseException" in caught, (
78
+ f"{path.name} block {i}: `except {caught}` wraps an emit — a cancelled "
79
+ "tool or run would skip its closing event. Use BaseException."
80
+ )
81
+
82
+
83
+ # --- The prose is a contract too -------------------------------------------
84
+ #
85
+ # Everything above checks that snippets PARSE and handle cancellation. Nothing
86
+ # checked that what the skill SAYS is true, and three claims had gone stale
87
+ # undetected: a correlation bug described as current after it was fixed, a float
88
+ # `duration_ms` described as silently dropped after it started raising, and a
89
+ # verify step pointing at the pre-migration spool root. An agent following that
90
+ # last one looks in an empty directory and reports the integration broken.
91
+ #
92
+ # These are the checks that would have caught each class.
93
+
94
+ import failproofai_sdk
95
+ from failproofai_sdk import _resolver
96
+
97
+ SKILL_TEXT = {p.name: p.read_text(encoding="utf-8") for p in SNIPPET_FILES}
98
+
99
+
100
+ def test_the_skill_only_names_event_methods_that_exist():
101
+ """A method renamed in the SDK leaves the skill teaching a call that raises."""
102
+ named = set()
103
+ for text in SKILL_TEXT.values():
104
+ named |= set(re.findall(r"(?:failproofai_sdk\.)?event\.([a-z_]+)\s*\(", text))
105
+ assert named, "no event.* calls found — the scan stopped working"
106
+ missing = sorted(n for n in named if not hasattr(failproofai_sdk.event, n))
107
+ assert not missing, f"the skill names event methods that do not exist: {missing}"
108
+
109
+
110
+ def test_the_skill_only_names_public_api_that_exists():
111
+ """Same, for the top-level surface an agent is told to import and call."""
112
+ named = set(re.findall(r"failproofai_sdk\.([a-z_]+[a-z_0-9]*)\s*\(", "\n".join(SKILL_TEXT.values())))
113
+ named -= {"event"} # a namespace, reached as failproofai_sdk.event.<method>
114
+ assert named, "no failproofai_sdk.* calls found — the scan stopped working"
115
+ missing = sorted(n for n in named if not hasattr(failproofai_sdk, n))
116
+ assert not missing, f"the skill names public API that does not exist: {missing}"
117
+
118
+
119
+ def test_the_skill_verifies_against_the_current_spool_root(monkeypatch):
120
+ """The verify step must send a reader to the directory the SDK actually writes.
121
+
122
+ `~/.agenteye` is still allowed in the migration notes — that is what the
123
+ older `agenteye-collector` reads, and the skill has to say so. What is not
124
+ allowed is presenting it as the path to CHECK, which is what it did: a fresh
125
+ integration with no env vars writes to `~/.failproofai/custom-agents/events`,
126
+ so the documented `ls` found nothing and read as total failure.
127
+ """
128
+ # The suite sets FAILPROOFAI_HOME to a sandbox (conftest), and this
129
+ # assertion is about the SHAPE of the default rather than where this run
130
+ # happens to spool — so it resolves with that pointer removed.
131
+ monkeypatch.delenv("FAILPROOFAI_HOME", raising=False)
132
+ default = str(_resolver.failproofai_custom_agents_dir())
133
+ assert default.endswith("/.failproofai/custom-agents"), default
134
+
135
+ skill = SKILL_TEXT["SKILL.md"]
136
+ assert "~/.failproofai/custom-agents/events" in skill, (
137
+ "SKILL.md never names the current default spool directory"
138
+ )
139
+ offenders = [
140
+ line.strip()
141
+ for line in skill.splitlines()
142
+ if re.search(r"(ls|cat)\b[^\n]*~/\.agenteye/events", line)
143
+ ]
144
+ assert not offenders, (
145
+ "SKILL.md tells the reader to inspect the pre-migration spool root; "
146
+ f"that directory is empty on a default install: {offenders}"
147
+ )
148
+
149
+
150
+ def test_the_skill_does_not_brand_the_product_with_the_retired_name():
151
+ """The H1 and the description are what an agent reads before anything else.
152
+
153
+ `agenteye` stays legal in migration notes and in the collector's own name;
154
+ naming the PRODUCT that is what this catches.
155
+ """
156
+ skill = SKILL_TEXT["SKILL.md"]
157
+ heading = next(line for line in skill.splitlines() if line.startswith("# "))
158
+ assert "agenteye" not in heading.lower(), f"retired product name in the H1: {heading}"
159
+
160
+ front = skill.split("---")[1] if skill.startswith("---") else ""
161
+ description = re.search(r"description:.*?(?=\n[a-z_]+:|\Z)", front, re.S)
162
+ assert description, "SKILL.md has no frontmatter description"
163
+ assert "to agenteye" not in description.group(0).lower(), (
164
+ "the frontmatter description still says events are reported to AgentEye"
165
+ )