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,571 @@
1
+ """One event the encoder cannot handle must not take the others with it.
2
+
3
+ Serialisation used to be a single `json.dumps` over the whole drained batch, so
4
+ an unencodable payload was not a lost event — it was a lost SPOOL. `_flush`
5
+ returned the batch to the queue and re-raised, `_flush_loop` logged and retried
6
+ the identical batch on the next interval, and that repeated for the life of the
7
+ process. Every event emitted afterwards queued up behind the one that could not
8
+ be written, and the only outward sign was `Exception ignored in atexit callback`
9
+ on the way out, which reads as a crash in the host application.
10
+
11
+ `default=str` is not a defence. It is consulted for unsupported *values*, so it
12
+ rescues datetime, UUID and Decimal, and does nothing at all for a non-str dict
13
+ key or a reference cycle — the two shapes an ordinary agent payload actually
14
+ arrives in. A tuple-keyed cache and an ORM row holding a back-reference are both
15
+ perfectly reasonable things to hand a telemetry call.
16
+
17
+ Each test here is written so that it FAILS against the batch-wide encoder: they
18
+ assert on the events published beside the bad one, not on the bad one itself.
19
+ """
20
+ import json
21
+ import logging
22
+
23
+ import pytest
24
+
25
+ from failproofai_sdk import _resolver
26
+ from failproofai_sdk._events import EventNamespace
27
+ from failproofai_sdk._writer import (
28
+ _CYCLE_MARKER,
29
+ _DEPTH_MARKER,
30
+ _MAX_SANITIZE_DEPTH,
31
+ EventWriter,
32
+ _encode_entry,
33
+ _sanitize,
34
+ )
35
+
36
+
37
+ @pytest.fixture
38
+ def spool(tmp_path):
39
+ _resolver.set_base_dir(tmp_path)
40
+ yield tmp_path
41
+ _resolver.set_base_dir(None)
42
+
43
+
44
+ def read_all(spool_dir):
45
+ events = []
46
+ for path in sorted((spool_dir / "events").glob("*.jsonl")):
47
+ for line in path.read_text(encoding="utf-8").splitlines():
48
+ events.append(json.loads(line))
49
+ return events
50
+
51
+
52
+ # A payload key that cannot be coerced even by the fallback, so `_encode_entry`
53
+ # has to give up on the event rather than find a way through.
54
+ class _Unstringable:
55
+ def __repr__(self):
56
+ raise RuntimeError("nope")
57
+
58
+ def __str__(self):
59
+ raise RuntimeError("nope")
60
+
61
+ def __hash__(self):
62
+ return 0
63
+
64
+
65
+ # ─────────────────────────────────────────────────────────────────────────────
66
+ # The wedge itself
67
+ # ─────────────────────────────────────────────────────────────────────────────
68
+
69
+
70
+ def test_a_poison_payload_does_not_stop_the_events_around_it(spool):
71
+ """The regression. One bad event, six good ones, and the six must land."""
72
+ writer = EventWriter(flush_interval=3600)
73
+ namespace = EventNamespace(writer)
74
+
75
+ namespace.agent_start(session_id="s", agent_id="a", goal="before")
76
+ # A cache keyed by tuple — `json.dumps` refuses this no matter what
77
+ # `default=` is set to, because `default=` is never consulted for keys.
78
+ namespace.tool_use(
79
+ session_id="s", agent_id="a", tool_name="t", tool_call_id="c1",
80
+ input={"cache": {(1, 2): "hit"}},
81
+ )
82
+ for i in range(5):
83
+ namespace.agent_start(session_id="s", agent_id="a", goal=f"after-{i}")
84
+
85
+ writer.flush_now()
86
+
87
+ goals = {e.get("goal") for e in read_all(spool)}
88
+ assert "before" in goals, "the event emitted BEFORE the bad one was lost with it"
89
+ assert {f"after-{i}" for i in range(5)} <= goals, "events queued behind the bad one never published"
90
+
91
+
92
+ def test_the_spool_keeps_advancing_after_a_poison_payload(spool):
93
+ """Not just this batch — every batch after it.
94
+
95
+ The old failure was permanent: the same batch was retried forever, so no
96
+ event emitted at any later point ever reached disk.
97
+ """
98
+ writer = EventWriter(flush_interval=3600)
99
+ namespace = EventNamespace(writer)
100
+
101
+ circular: dict = {}
102
+ circular["self"] = circular
103
+ namespace.tool_use(
104
+ session_id="s", agent_id="a", tool_name="t", tool_call_id="c1", input=circular
105
+ )
106
+ writer.flush_now()
107
+
108
+ namespace.agent_start(session_id="s", agent_id="a", goal="much-later")
109
+ writer.flush_now()
110
+
111
+ assert "much-later" in {e.get("goal") for e in read_all(spool)}
112
+
113
+
114
+ def test_the_queue_does_not_grow_behind_a_poison_payload(spool):
115
+ """The wedge was also a leak: nothing drained, so everything accumulated."""
116
+ writer = EventWriter(flush_interval=3600)
117
+ namespace = EventNamespace(writer)
118
+
119
+ namespace.tool_use(
120
+ session_id="s", agent_id="a", tool_name="t", tool_call_id="c1",
121
+ input={"bad": {(1, 2): "x"}},
122
+ )
123
+ for i in range(20):
124
+ namespace.agent_start(session_id="s", agent_id="a", goal=f"g{i}")
125
+ writer.flush_now()
126
+
127
+ assert len(writer._queue) == 0, "entries were returned to the queue and will be retried forever"
128
+
129
+
130
+ # ─────────────────────────────────────────────────────────────────────────────
131
+ # What the fallback preserves, and what it gives up on
132
+ # ─────────────────────────────────────────────────────────────────────────────
133
+
134
+
135
+ def test_a_non_str_key_is_coerced_rather_than_dropped(spool):
136
+ """Recovering the event beats discarding it; the payload is still readable."""
137
+ writer = EventWriter(flush_interval=3600)
138
+ EventNamespace(writer).tool_use(
139
+ session_id="s", agent_id="a", tool_name="t", tool_call_id="c1",
140
+ input={"counts": {1: "one", (2, 3): "two-three"}},
141
+ )
142
+ writer.flush_now()
143
+
144
+ published = read_all(spool)
145
+ assert len(published) == 1
146
+ assert published[0]["input"]["counts"] == {"1": "one", "(2, 3)": "two-three"}
147
+
148
+
149
+ def test_a_cycle_becomes_a_marker_and_the_rest_of_the_event_survives(spool):
150
+ writer = EventWriter(flush_interval=3600)
151
+ payload: dict = {"name": "node", "size": 3}
152
+ payload["self"] = payload
153
+ EventNamespace(writer).tool_use(
154
+ session_id="s", agent_id="a", tool_name="t", tool_call_id="c1", input=payload
155
+ )
156
+ writer.flush_now()
157
+
158
+ published = read_all(spool)[0]
159
+ assert published["input"]["self"] == _CYCLE_MARKER
160
+ assert published["input"]["name"] == "node", "unrelated keys were collateral damage"
161
+ assert published["input"]["size"] == 3
162
+ assert published["tool_call_id"] == "c1", "the envelope was rewritten too"
163
+
164
+
165
+ def test_a_shared_reference_is_not_mistaken_for_a_cycle():
166
+ """A DAG is not a cycle. Flagging one would corrupt a perfectly good event.
167
+
168
+ This is why `_sanitize` tracks the ids on the current PATH rather than every
169
+ id it has ever seen: the same dict appearing twice as siblings encodes fine.
170
+ """
171
+ shared = {"shared": True}
172
+ encoded = _encode_entry({"a": shared, "b": shared, "c": [shared, shared]})
173
+ assert encoded is not None
174
+ assert _CYCLE_MARKER not in encoded
175
+ assert json.loads(encoded)["b"] == {"shared": True}
176
+
177
+
178
+ def test_depth_is_bounded_so_the_fallback_cannot_blow_the_stack():
179
+ """The fallback must not fail the way it exists to prevent."""
180
+ deep: dict = {}
181
+ node = deep
182
+ for _ in range(_MAX_SANITIZE_DEPTH + 50):
183
+ node["next"] = {}
184
+ node = node["next"]
185
+ node["bad"] = {(1, 2): "forces the fallback"}
186
+
187
+ encoded = _encode_entry(deep)
188
+ assert encoded is not None
189
+ assert _DEPTH_MARKER in encoded
190
+
191
+
192
+ def test_an_event_that_cannot_be_encoded_at_all_is_dropped_alone(spool, caplog):
193
+ """Give up on the one event. Never on the batch."""
194
+ writer = EventWriter(flush_interval=3600)
195
+ writer.submit({"type": "good-before", "n": 1})
196
+ writer.submit({"type": "hopeless", "payload": {_Unstringable(): "x"}})
197
+ writer.submit({"type": "good-after", "n": 2})
198
+
199
+ with caplog.at_level(logging.ERROR, logger="failproofai_sdk._writer"):
200
+ writer.flush_now()
201
+
202
+ types = [e["type"] for e in read_all(spool)]
203
+ assert types == ["good-before", "good-after"]
204
+ assert any("dropped 1 unserializable" in r.getMessage() for r in caplog.records), caplog.text
205
+
206
+
207
+ def test_a_batch_of_nothing_but_poison_publishes_no_file_and_does_not_raise(spool):
208
+ writer = EventWriter(flush_interval=3600)
209
+ writer.submit({"type": "hopeless", "payload": {_Unstringable(): "x"}})
210
+ writer.flush_now()
211
+
212
+ assert list((spool / "events").glob("*")) == [] or read_all(spool) == []
213
+ assert len(writer._queue) == 0
214
+
215
+
216
+ # ─────────────────────────────────────────────────────────────────────────────
217
+ # The two failure classes must stay distinguishable
218
+ # ─────────────────────────────────────────────────────────────────────────────
219
+
220
+
221
+ def test_a_filesystem_error_still_retries_the_whole_batch(spool, monkeypatch):
222
+ """Encoding failures drop; IO failures retry. Collapsing the two loses data.
223
+
224
+ An unencodable event is permanent — retrying produces the identical failure —
225
+ while a full disk or a momentarily read-only mount is not. If the IO path
226
+ started dropping too, a transient error would silently discard live events.
227
+ """
228
+ writer = EventWriter(flush_interval=3600)
229
+ writer.submit({"type": "keep-me", "n": 1})
230
+
231
+ import sys
232
+
233
+ writer_module = sys.modules["failproofai_sdk._writer"]
234
+ real_replace = writer_module.os.replace
235
+ calls = {"n": 0}
236
+
237
+ def flaky(src, dst):
238
+ calls["n"] += 1
239
+ if calls["n"] == 1:
240
+ raise OSError(28, "No space left on device")
241
+ return real_replace(src, dst)
242
+
243
+ monkeypatch.setattr(writer_module.os, "replace", flaky)
244
+
245
+ with pytest.raises(OSError):
246
+ writer.flush_now()
247
+ assert len(writer._queue) == 1, "a transient IO error discarded the batch"
248
+
249
+ monkeypatch.undo()
250
+ writer.flush_now()
251
+ assert [e["type"] for e in read_all(spool)] == ["keep-me"]
252
+
253
+
254
+ # ─────────────────────────────────────────────────────────────────────────────
255
+ # The fast path must not have moved
256
+ # ─────────────────────────────────────────────────────────────────────────────
257
+
258
+
259
+ def test_an_ordinary_event_takes_the_strict_path_byte_for_byte():
260
+ """`test_wire_format.py` pins the bytes; this pins that nothing re-encodes them.
261
+
262
+ The fallback rebuilds dicts, and a rebuild is exactly the kind of change that
263
+ reorders keys. Key order is load-bearing here — ingest's dedup key hashes the
264
+ canonical payload, so a reordered event stops retried batches collapsing and
265
+ surfaces as duplicate rows rather than as an error.
266
+ """
267
+ entry = {
268
+ "timestamp": "2026-01-02T03:04:05.678901Z",
269
+ "session_id": "sess-1",
270
+ "agent_id": "agent-1",
271
+ "type": "tool_use",
272
+ "tool_name": "bash",
273
+ "tool_call_id": "tc-1",
274
+ "environment": "prod",
275
+ "input": {"command": "ls"},
276
+ }
277
+ assert _encode_entry(entry) == json.dumps(entry, default=str)
278
+
279
+
280
+ def test_default_str_still_rescues_the_values_it_always_did():
281
+ """The fallback is an addition, not a replacement."""
282
+ from datetime import datetime, timezone
283
+ from decimal import Decimal
284
+ from uuid import UUID
285
+
286
+ encoded = _encode_entry(
287
+ {
288
+ "type": "tool_result",
289
+ "when": datetime(2026, 1, 2, tzinfo=timezone.utc),
290
+ "id": UUID("00000000-0000-0000-0000-00000000beef"),
291
+ "cost": Decimal("0.25"),
292
+ }
293
+ )
294
+ decoded = json.loads(encoded)
295
+ assert decoded["cost"] == "0.25"
296
+ assert decoded["id"].endswith("beef")
297
+ assert _CYCLE_MARKER not in encoded, "an ordinary event took the fallback path"
298
+
299
+
300
+ def test_sanitize_leaves_an_already_clean_payload_alone():
301
+ payload = {"a": 1, "b": [1, 2, {"c": "d"}], "e": None, "f": True}
302
+ assert _sanitize(payload, frozenset()) == payload
303
+
304
+
305
+ # ─────────────────────────────────────────────────────────────────────────────
306
+ # Non-finite floats — valid Python, invalid JSON
307
+ # ─────────────────────────────────────────────────────────────────────────────
308
+
309
+
310
+ def _strict_loads(line):
311
+ """Parse the way a conforming JSON reader does: NaN/Infinity are not values."""
312
+ def reject(token):
313
+ raise ValueError(f"non-standard JSON constant: {token}")
314
+ return json.loads(line, parse_constant=reject)
315
+
316
+
317
+ @pytest.mark.parametrize("bad", [float("nan"), float("inf"), float("-inf")])
318
+ def test_a_non_finite_float_still_produces_standard_json(spool, bad):
319
+ """`json.dumps` writes NaN / Infinity / -Infinity by default.
320
+
321
+ Those are a Python extension, not JSON, and — this is the part that made it
322
+ dangerous — `json.dumps` does NOT raise on them. The encoder's fallback was
323
+ never reached, so a malformed line went out looking like a clean success and
324
+ a strict NDJSON reader on the far side drops it.
325
+ """
326
+ writer = EventWriter(flush_interval=3600)
327
+ EventNamespace(writer).tool_use(
328
+ session_id="s", agent_id="a", tool_name="t", tool_call_id="c1",
329
+ input={"score": bad, "label": "kept"},
330
+ )
331
+ writer.flush_now()
332
+
333
+ (batch,) = list((spool / "events").glob("*.jsonl"))
334
+ for line in batch.read_text(encoding="utf-8").splitlines():
335
+ _strict_loads(line) # raises if NaN/Infinity survived
336
+
337
+ published = read_all(spool)[0]
338
+ assert published["input"]["score"] is None, "the non-finite value was not neutralised"
339
+ assert published["input"]["label"] == "kept", "the sibling field was collateral damage"
340
+
341
+
342
+ def test_the_raw_bytes_contain_no_nan_or_infinity_tokens(spool):
343
+ writer = EventWriter(flush_interval=3600)
344
+ EventNamespace(writer).tool_use(
345
+ session_id="s", agent_id="a", tool_name="t", tool_call_id="c1",
346
+ input={"a": float("nan"), "b": float("inf"), "c": float("-inf")},
347
+ )
348
+ writer.flush_now()
349
+ raw = next((spool / "events").glob("*.jsonl")).read_text(encoding="utf-8")
350
+ assert "NaN" not in raw and "Infinity" not in raw, raw
351
+
352
+
353
+ def test_non_finite_floats_nested_in_lists_are_neutralised_too(spool):
354
+ writer = EventWriter(flush_interval=3600)
355
+ EventNamespace(writer).tool_use(
356
+ session_id="s", agent_id="a", tool_name="t", tool_call_id="c1",
357
+ input={"series": [1.0, float("nan"), 3.0]},
358
+ )
359
+ writer.flush_now()
360
+ assert read_all(spool)[0]["input"]["series"] == [1.0, None, 3.0]
361
+
362
+
363
+ def test_finite_floats_are_left_exactly_alone():
364
+ """The fix must not round, reformat or otherwise touch ordinary numbers."""
365
+ entry = {"type": "e", "a": 0.1, "b": -2.5, "c": 1e300, "d": 0.0}
366
+ assert _encode_entry(entry) == json.dumps(entry, default=str, allow_nan=False)
367
+ assert json.loads(_encode_entry(entry))["a"] == 0.1
368
+
369
+
370
+ def test_a_non_finite_float_does_not_push_the_event_onto_the_drop_path(spool, caplog):
371
+ """It is recoverable, so it must be recovered — not logged and discarded."""
372
+ writer = EventWriter(flush_interval=3600)
373
+ writer.submit({"type": "recoverable", "x": float("nan")})
374
+ with caplog.at_level(logging.ERROR, logger="failproofai_sdk._writer"):
375
+ writer.flush_now()
376
+ assert [e["type"] for e in read_all(spool)] == ["recoverable"]
377
+ assert not any("dropped" in r.getMessage() for r in caplog.records), caplog.text
378
+
379
+
380
+ # ─────────────────────────────────────────────────────────────────────────────
381
+ # The encoder runs the CALLER'S code, which can raise anything
382
+ # ─────────────────────────────────────────────────────────────────────────────
383
+
384
+
385
+ class _ReprRaises:
386
+ """`default=str` calls this, and it does not raise TypeError or ValueError."""
387
+
388
+ def __init__(self, exc):
389
+ self._exc = exc
390
+
391
+ def __repr__(self):
392
+ raise self._exc
393
+
394
+
395
+ @pytest.mark.parametrize(
396
+ "exc",
397
+ [RuntimeError("boom"), OSError(5, "io in __repr__"), KeyError("k"), AttributeError("a")],
398
+ ids=lambda e: type(e).__name__,
399
+ )
400
+ def test_an_exploding_repr_drops_one_event_and_never_wedges_the_batch(spool, exc):
401
+ """Regression: the narrow `except (TypeError, ValueError, RecursionError)`.
402
+
403
+ `default=str` runs the caller's `__repr__`, which can raise anything — a
404
+ RuntimeError from a lazy ORM attribute, an OSError from a property that
405
+ touches the network. Anything outside those three escaped `_encode_entry`,
406
+ propagated out of `_write_batch`, and put the whole batch back on the queue
407
+ to be retried identically forever. That is the same permanent wedge this
408
+ module exists to prevent, reached through a different exception type.
409
+ """
410
+ writer = EventWriter(flush_interval=3600)
411
+ writer.submit({"type": "before", "n": 1})
412
+ writer.submit({"type": "poison", "v": _ReprRaises(exc)})
413
+ writer.submit({"type": "after", "n": 2})
414
+
415
+ writer.flush_now() # must not raise
416
+
417
+ assert [e["type"] for e in read_all(spool)] == ["before", "after"]
418
+ assert len(writer._queue) == 0, "the batch was re-queued and will retry forever"
419
+
420
+
421
+ def test_a_keyboard_interrupt_during_encoding_is_not_swallowed(spool):
422
+ """`except Exception`, never `BaseException`.
423
+
424
+ Telemetry must not be able to eat a Ctrl-C — an SDK that makes a process
425
+ unkillable during a flush is worse than one that loses an event.
426
+ """
427
+ writer = EventWriter(flush_interval=3600)
428
+ writer.submit({"type": "e", "v": _ReprRaises(KeyboardInterrupt())})
429
+ with pytest.raises(KeyboardInterrupt):
430
+ writer.flush_now()
431
+
432
+
433
+ @pytest.mark.parametrize(
434
+ "name,payload",
435
+ [
436
+ ("lone surrogate", {"s": "bad \ud800 pair"}),
437
+ ("unpaired low surrogate", {"s": "\udfff"}),
438
+ ("null byte", {"s": "a\x00b"}),
439
+ ("int beyond any integer type", {"n": 10**400}),
440
+ ("bytes", {"b": b"\xff\xfe"}),
441
+ ("set", {"v": {1, 2, 3}}),
442
+ ("300-deep nesting", None),
443
+ ("200KB string", {"s": "x" * 200_000}),
444
+ ],
445
+ )
446
+ def test_hostile_but_legitimate_payloads_still_publish(spool, name, payload):
447
+ """None of these are mistakes; agents really hand these to a telemetry call.
448
+
449
+ A lone surrogate comes back from a truncated model response, a null byte
450
+ from a binary tool output, a 10**400 from a numeric library.
451
+ """
452
+ if payload is None: # built here so the parametrize id stays readable
453
+ payload = cur = {}
454
+ for _ in range(300):
455
+ cur["n"] = {}
456
+ cur = cur["n"]
457
+
458
+ writer = EventWriter(flush_interval=3600)
459
+ EventNamespace(writer).tool_use(
460
+ session_id="s", agent_id="a", tool_name="t", tool_call_id="c1", input=payload
461
+ )
462
+ writer.flush_now()
463
+
464
+ published = read_all(spool)
465
+ assert len(published) == 1, f"{name} was dropped"
466
+ assert published[0]["tool_call_id"] == "c1"
467
+
468
+
469
+ # ─────────────────────────────────────────────────────────────────────────────
470
+ # Lone surrogates — encode cleanly here, get skipped by ingest
471
+ # ─────────────────────────────────────────────────────────────────────────────
472
+
473
+
474
+ def test_a_lone_surrogate_is_scrubbed_so_ingest_accepts_the_event(spool):
475
+ """The whole event was silently skipped, and nothing failed locally.
476
+
477
+ `os.fsdecode` and `bytes.decode(errors="surrogateescape")` — how Python
478
+ carries bytes that are not valid UTF-8, and what a filesystem path or a
479
+ truncated tool output arrives as — produce lone surrogates. `json.dumps`
480
+ escapes them to the literal text \\udcff without complaint, so the SDK saw a
481
+ clean success. The server then skipped the row: verified live, before the
482
+ fix `{"accepted":0,"skipped":1}` at 200 OK, after it `{"accepted":1}`.
483
+ """
484
+ writer = EventWriter(flush_interval=3600)
485
+ EventNamespace(writer).tool_use(
486
+ session_id="s", agent_id="a", tool_name="t", tool_call_id="c1",
487
+ input={"path": "file-\udcff-name", "ok": "kept"},
488
+ )
489
+ writer.flush_now()
490
+
491
+ (batch,) = list((spool / "events").glob("*.jsonl"))
492
+ decoded = read_all(spool)[0]
493
+ assert decoded["input"]["ok"] == "kept", "the sibling field was lost"
494
+ assert "\udcff" not in json.dumps(decoded), "a lone surrogate survived into the payload"
495
+ assert "udcff" in decoded["input"]["path"], "the offending byte was silently discarded"
496
+
497
+
498
+ def test_the_surrogate_scan_does_not_touch_ordinary_events():
499
+ """One substring scan per line; a clean event must take the fast path."""
500
+ entry = {"type": "tool_use", "input": {"cmd": "ls -la", "emoji": "🚀", "n": 1}}
501
+ assert _encode_entry(entry) == json.dumps(entry, default=str, allow_nan=False)
502
+
503
+
504
+ def test_a_payload_whose_text_merely_looks_like_an_escape_still_round_trips():
505
+ """A false positive on the scan must cost correctness nothing."""
506
+ entry = {"type": "e", "s": r"literally \ud800 as text"}
507
+ decoded = json.loads(_encode_entry(entry))
508
+ assert decoded["s"] == r"literally \ud800 as text"
509
+
510
+
511
+ def test_surrogates_nested_anywhere_are_reached(spool):
512
+ writer = EventWriter(flush_interval=3600)
513
+ EventNamespace(writer).tool_use(
514
+ session_id="s", agent_id="a", tool_name="t", tool_call_id="c1",
515
+ input={"deep": {"list": ["ok", "bad-\udcff"]}},
516
+ )
517
+ writer.flush_now()
518
+ assert "\udcff" not in json.dumps(read_all(spool)[0])
519
+
520
+
521
+ def test_a_surrogate_in_a_dict_KEY_is_scrubbed_like_one_in_a_value(spool):
522
+ """The scrub was applied to every value and to no key.
523
+
524
+ `_sanitize`'s dict branch passed `str` keys straight through, so the rebuild
525
+ that exists to make a lone surrogate inert did nothing for the half of the
526
+ payload most likely to carry one: this module's own docstring names
527
+ `os.fsdecode` and a filesystem path as the realistic source, and a path is
528
+ most naturally a KEY (`{path: contents}`), not a value.
529
+
530
+ An unscrubbed key reaches the wire as a JSON lone-surrogate escape, ingest
531
+ answers 200 `{"accepted":0,"skipped":1}`, and the uploader now PARKS that
532
+ batch and poisons it after three retries — so one bad key loses every event
533
+ batched alongside it, not just its own.
534
+ """
535
+ writer = EventWriter(flush_interval=3600)
536
+ EventNamespace(writer).tool_use(
537
+ session_id="s", agent_id="a", tool_name="t", tool_call_id="c1",
538
+ input={"file-\udcff-name": "contents"},
539
+ )
540
+ writer.flush_now()
541
+ raw = (spool / "events").glob("*.jsonl")
542
+ text = "\n".join(p.read_text(encoding="utf-8") for p in raw)
543
+ # No lone-surrogate ESCAPE on the wire: the byte survives as visible text,
544
+ # exactly as it already did on the value side.
545
+ assert "\\udcff" not in text.replace("\\\\udcff", "")
546
+ assert "\\\\udcff" in text
547
+ assert "\udcff" not in json.dumps(read_all(spool)[0])
548
+
549
+
550
+ def test_ordinary_korean_text_does_not_trip_the_surrogate_rebuild(spool):
551
+ """`\\ud` also matches U+D000–U+D7FF, i.e. most of the Hangul block.
552
+
553
+ `json.dumps("한")` is `"\\ud55c"`, so every event carrying ordinary Korean
554
+ text took the rebuild path. That was documented as costing nothing — "merely
555
+ re-encoded to the same bytes" — and it is not, because `_sanitize` replaces
556
+ anything past `_MAX_SANITIZE_DEPTH` with a marker. A payload the strict
557
+ encoder had handled perfectly was silently truncated the moment it also
558
+ contained a Korean character, and the identical payload in ASCII was not.
559
+ """
560
+ deep = {"leaf": "keep-me"}
561
+ for _ in range(60):
562
+ deep = {"n": deep}
563
+
564
+ korean = _encode_entry({"type": "tool_result", "output": "한국어", "input": deep})
565
+ ascii_ = _encode_entry({"type": "tool_result", "output": "hangul", "input": deep})
566
+
567
+ assert "<max depth exceeded>" not in korean
568
+ assert korean.replace("\\ud55c\\uad6d\\uc5b4", "REDACTED") == ascii_.replace(
569
+ "hangul", "REDACTED"
570
+ ), "the Korean payload must be encoded exactly like its ASCII twin"
571
+ assert json.loads(korean)["output"] == "한국어"