failproofai 1.0.6-beta.1 → 1.0.7-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 (258) 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 +3 -3
  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 +4 -4
  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 +4 -4
  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.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/run/route.js +8 -7
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  33. package/.next/standalone/.next/server/app/audit/page.js +7 -5
  34. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/index.html +1 -1
  37. package/.next/standalone/.next/server/app/index.rsc +15 -15
  38. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  39. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  40. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  41. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page.js +6 -6
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  46. package/.next/standalone/.next/server/app/policies/page.js +13 -11
  47. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/page.js +8 -7
  51. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +7 -7
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  59. package/.next/standalone/.next/server/app/projects/page.js +7 -6
  60. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +41 -8
  63. package/.next/standalone/.next/server/app/settings/page.js +12 -9
  64. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/chunks/{[externals]__1msfs-h._.js → [externals]__19_pzeq._.js} +1 -1
  67. package/.next/standalone/.next/server/chunks/{[externals]__1_bftcl._.js → [externals]__1j-zsg5._.js} +1 -1
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1_r2rbg._.js → [root-of-the-server]__1bf34x4._.js} +5 -7
  70. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
  71. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
  74. package/.next/standalone/.next/server/chunks/{_1c3k-8x._.js → _1q5i8mb._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
  76. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
  77. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +5 -0
  79. package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +4 -2
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +4 -0
  83. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__02npjtd._.js → [root-of-the-server]__056wjo4._.js} +3 -3
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0576iuy._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +22 -0
  87. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0p-5p8u._.js → [root-of-the-server]__0n0xg95._.js} +3 -3
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qxnccm._.js +5 -0
  90. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__013jr2b._.js → [root-of-the-server]__0rwtwpm._.js} +3 -3
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +4 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0da85px._.js → [root-of-the-server]__11mayhe._.js} +3 -3
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +3 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cxe_2_._.js → [root-of-the-server]__1mf3zp6._.js} +3 -3
  98. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1x3j9mj._.js → [root-of-the-server]__1p28m0u._.js} +2 -2
  99. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ftmoxc._.js → [root-of-the-server]__1pprgri._.js} +3 -3
  100. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1p2otjt._.js → [root-of-the-server]__1q4p5b8._.js} +3 -3
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/ssr/{_0qlygw6._.js → _0cf1olg._.js} +2 -2
  107. package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/ssr/{_1_ddpm3._.js → _135-c3y._.js} +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +5 -0
  110. package/.next/standalone/.next/server/chunks/ssr/{_1es2j7i._.js → _1gb0ifp._.js} +5 -5
  111. package/.next/standalone/.next/server/chunks/ssr/{_1_qswah._.js → _1q46vxx._.js} +2 -2
  112. package/.next/standalone/.next/server/chunks/ssr/{_0h_douw._.js → _1w_5l7t._.js} +1 -1
  113. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +4 -4
  115. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +3 -0
  116. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  118. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  119. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/ssr/lib_utils_ts_0az0sfq._.js +1 -1
  121. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
  122. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
  123. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
  124. package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +5 -0
  125. package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +12 -0
  127. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  132. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  133. package/.next/standalone/.next/server/pages/404.html +1 -1
  134. package/.next/standalone/.next/server/pages/500.html +1 -1
  135. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  136. package/.next/standalone/.next/server/server-reference-manifest.json +56 -23
  137. package/.next/standalone/.next/static/chunks/{06fjx8-0_qufs.js → 00i9thnpht4l1.js} +1 -1
  138. package/.next/standalone/.next/static/chunks/{3ht0xsco46pul.js → 0289oapk24zql.js} +1 -1
  139. package/.next/standalone/.next/static/chunks/{1qf2uiipatr80.js → 0pyy9m5cm8zku.js} +1 -1
  140. package/.next/standalone/.next/static/chunks/15heikmy3yjzc.js +1 -0
  141. package/.next/standalone/.next/static/chunks/285spx855h_3r.css +2 -0
  142. package/.next/standalone/.next/static/chunks/{02vwr5js1x28j.js → 2plg9qae1yejv.js} +1 -1
  143. package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
  144. package/.next/standalone/.next/static/chunks/{2x776s0x3n_tu.js → 2tqszh84mp_2l.js} +1 -1
  145. package/.next/standalone/.next/static/chunks/34jpdb2xjm-x7.js +1 -0
  146. package/.next/standalone/.next/static/chunks/3cdkxrukvlvul.js +1 -0
  147. package/.next/standalone/.next/static/chunks/3dnd-_29uiaj3.js +6 -0
  148. package/.next/standalone/.next/static/chunks/{0y227_ohdqwvn.js → 3vp7c3knkh8mr.js} +1 -1
  149. package/.next/standalone/.next/static/chunks/{27_bwbg8sv6vj.js → 3vwnzfx9q80yl.js} +1 -1
  150. package/.next/standalone/app/actions/get-jev-config.ts +409 -0
  151. package/.next/standalone/app/actions/pack-actions.ts +12 -0
  152. package/.next/standalone/app/actions/update-jev-config.ts +420 -0
  153. package/.next/standalone/app/components/jev-notices.tsx +96 -0
  154. package/.next/standalone/app/policies/hooks-client.tsx +9 -0
  155. package/.next/standalone/app/settings/jev-panel.tsx +469 -0
  156. package/.next/standalone/app/settings/page.tsx +20 -1
  157. package/.next/standalone/app/settings/settings-client.tsx +27 -1
  158. package/.next/standalone/app/settings/settings.css +79 -0
  159. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +51 -3
  160. package/.next/standalone/fp-cloud-cli/README.md +1 -1
  161. package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -1
  162. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +44 -0
  163. package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +226 -5
  164. package/.next/standalone/fp-cloud-cli/fp_cli/models.py +11 -0
  165. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +68 -1
  166. package/.next/standalone/fp-cloud-cli/pyproject.toml +1 -1
  167. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +17 -2
  168. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +7 -4
  169. package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +184 -0
  170. package/.next/standalone/fp-cloud-cli/uv.lock +3 -3
  171. package/.next/standalone/package.json +11 -11
  172. package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
  173. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
  174. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__init__.py +2 -0
  175. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/authoring.py +24 -1
  176. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/runtime.py +41 -6
  177. package/.next/standalone/sdk/python/tests/test_evaluator_runtime.py +88 -0
  178. package/.next/standalone/sdk/python/uv.lock +15 -15
  179. package/.next/standalone/server.js +1 -1
  180. package/bin/failproofai.mjs +115 -2
  181. package/dist/cli.mjs +10982 -3787
  182. package/dist/index.js +19 -1
  183. package/dist/worker.mjs +13761 -7834
  184. package/package.json +11 -11
  185. package/pi-extension/index.ts +11 -0
  186. package/scripts/build-policy-pack.mjs +39 -2
  187. package/src/hooks/builtin-policies.ts +21 -3
  188. package/src/hooks/cloud-enrollment-cli.ts +1 -1
  189. package/src/hooks/cloud-managed-policies.ts +22 -0
  190. package/src/hooks/custom-hooks-loader.ts +45 -7
  191. package/src/hooks/custom-hooks-registry.ts +45 -1
  192. package/src/hooks/daemon-client.ts +128 -50
  193. package/src/hooks/daemon-service.ts +30 -0
  194. package/src/hooks/effective-reviewers.ts +79 -0
  195. package/src/hooks/first-run-gate.ts +5 -0
  196. package/src/hooks/fp-home.ts +23 -0
  197. package/src/hooks/handler.ts +265 -6
  198. package/src/hooks/hook-activity-store.ts +105 -1
  199. package/src/hooks/hook-telemetry.ts +41 -0
  200. package/src/hooks/integrations.ts +3 -2
  201. package/src/hooks/jev-activity.ts +385 -0
  202. package/src/hooks/jev-cli.ts +1193 -0
  203. package/src/hooks/loader-utils.ts +6 -0
  204. package/src/hooks/manager.ts +4 -4
  205. package/src/hooks/pack-cli.ts +237 -11
  206. package/src/hooks/pack-manifest.ts +479 -7
  207. package/src/hooks/pack-store.ts +156 -10
  208. package/src/hooks/policy-authority.ts +333 -0
  209. package/src/hooks/policy-catalog.ts +65 -0
  210. package/src/hooks/policy-evaluator.ts +940 -796
  211. package/src/hooks/policy-registry.ts +25 -0
  212. package/src/hooks/policy-reviewability.ts +229 -0
  213. package/src/hooks/policy-types.ts +126 -0
  214. package/src/hooks/semantic/combine.ts +541 -0
  215. package/src/hooks/semantic/compile.ts +176 -0
  216. package/src/hooks/semantic/decide.ts +392 -0
  217. package/src/hooks/semantic/envelope.ts +1296 -0
  218. package/src/hooks/semantic/evaluator.ts +547 -0
  219. package/src/hooks/semantic/facts.ts +292 -0
  220. package/src/hooks/semantic/intent.ts +1190 -0
  221. package/src/hooks/semantic/jev-client.ts +643 -0
  222. package/src/hooks/semantic/jev-config.ts +594 -0
  223. package/src/hooks/semantic/jev-review.ts +374 -0
  224. package/src/hooks/semantic/jev-stats.ts +289 -0
  225. package/src/hooks/semantic/jev-throttle.ts +421 -0
  226. package/src/hooks/semantic/pack-policies.ts +251 -0
  227. package/src/hooks/semantic/policies.ts +596 -0
  228. package/src/hooks/semantic/precondition-names.ts +60 -0
  229. package/src/hooks/semantic/preconditions.ts +58 -0
  230. package/src/hooks/semantic/redact.ts +2910 -0
  231. package/src/hooks/semantic/types.ts +145 -0
  232. package/src/hooks/semver-precedence.ts +128 -0
  233. package/src/hooks/worker-server.ts +119 -26
  234. package/src/index.ts +6 -0
  235. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
  236. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +0 -4
  237. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +0 -4
  238. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +0 -5
  239. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +0 -3
  240. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +0 -22
  241. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
  242. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
  243. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
  244. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
  245. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
  246. package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
  247. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
  248. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
  249. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +0 -10
  250. package/.next/standalone/.next/static/chunks/0cbvytf8epn34.js +0 -1
  251. package/.next/standalone/.next/static/chunks/0qmsunv04x4na.css +0 -2
  252. package/.next/standalone/.next/static/chunks/1p4xauf-zu6zk.js +0 -1
  253. package/.next/standalone/.next/static/chunks/1uc_s_dftgxiq.js +0 -6
  254. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
  255. package/.next/standalone/.next/static/chunks/2lvr2pt70qn14.js +0 -1
  256. /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_buildManifest.js +0 -0
  257. /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_clientMiddlewareManifest.js +0 -0
  258. /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_ssgManifest.js +0 -0
@@ -642,3 +642,187 @@ def test_issues_show_malformed_id_is_not_found(logged_in, runner):
642
642
  result = runner.invoke(app, ["--json", "issues", "show", "not-a-uuid"])
643
643
  assert result.exit_code == 6
644
644
  assert "no issue not-a-uuid" in json.loads(result.stdout)["error"]
645
+
646
+ # --- close / archive / clear ------------------------------------------------
647
+ #
648
+ # The issue lifecycle grew a second terminal state (`closed`) and an orthogonal
649
+ # archived flag. The CLI restates the server's contract by hand — the state
650
+ # allowlist, the paths, the response keys — with nothing checking the two agree,
651
+ # so these pin the CLI half.
652
+
653
+
654
+ @respx.mock
655
+ def test_issues_close_posts_to_close_not_resolve(logged_in, runner):
656
+ # The whole point of `close` is that it is NOT `resolve`: a resolved issue
657
+ # reopens when its audit finding recurs and a closed one does not, so a
658
+ # close that quietly hit /resolve would silently give the opposite
659
+ # behaviour with an identical success line.
660
+ resolve = respx.post(f"{BASE}/api/issues/i1/resolve").mock(return_value=httpx.Response(200, json={}))
661
+ close = respx.post(f"{BASE}/api/issues/i1/close").mock(
662
+ return_value=httpx.Response(200, json={"id": "i1", "state": "closed", "closed_at": "t"}))
663
+ result = runner.invoke(app, ["--json", "issues", "close", "i1", "--yes"])
664
+ assert result.exit_code == 0, result.output
665
+ assert json.loads(result.stdout) == {"closed": True, "id": "i1"}
666
+ assert close.called
667
+ assert not resolve.called
668
+
669
+
670
+ @respx.mock
671
+ def test_issues_close_human_line(logged_in, runner):
672
+ respx.post(f"{BASE}/api/issues/i1/close").mock(return_value=httpx.Response(200, json={}))
673
+ result = runner.invoke(app, ["issues", "close", "i1", "--yes"])
674
+ assert result.exit_code == 0, result.output
675
+ assert "closed issue" in result.stderr
676
+
677
+
678
+ @respx.mock
679
+ def test_issues_close_declined_emits_the_json_cancel_envelope(logged_in, runner, monkeypatch):
680
+ monkeypatch.setattr(_write, "should_prompt", lambda *a, **k: True)
681
+ monkeypatch.setattr(output, "confirm_incident_close", lambda *a, **k: False)
682
+ respx.get(f"{BASE}/api/issues/i1").mock(
683
+ return_value=httpx.Response(200, json={"id": "i1", "alert_name": "p95"}))
684
+ close = respx.post(f"{BASE}/api/issues/i1/close").mock(return_value=httpx.Response(200, json={}))
685
+ result = runner.invoke(app, ["--json", "issues", "close", "i1"])
686
+ assert result.exit_code == 0, result.output
687
+ assert json.loads(result.stdout) == {"cancelled": True}
688
+ assert not close.called
689
+
690
+
691
+ @respx.mock
692
+ def test_issues_archive_and_unarchive_hit_their_own_verbs(logged_in, runner):
693
+ arch = respx.post(f"{BASE}/api/issues/i1/archive").mock(return_value=httpx.Response(200, json={}))
694
+ unarch = respx.post(f"{BASE}/api/issues/i1/unarchive").mock(return_value=httpx.Response(200, json={}))
695
+
696
+ result = runner.invoke(app, ["--json", "issues", "archive", "i1"])
697
+ assert result.exit_code == 0, result.output
698
+ assert json.loads(result.stdout) == {"archived": True, "id": "i1"}
699
+ assert arch.called and not unarch.called
700
+
701
+ result = runner.invoke(app, ["--json", "issues", "unarchive", "i1"])
702
+ assert result.exit_code == 0, result.output
703
+ assert json.loads(result.stdout) == {"archived": False, "id": "i1"}
704
+ assert unarch.called
705
+
706
+
707
+ @respx.mock
708
+ def test_issues_archive_does_not_prompt(logged_in, runner, monkeypatch):
709
+ # Archiving destroys nothing and `unarchive` undoes it, so it must not ask —
710
+ # a prompt here would be the only blocking one in a cleanup loop.
711
+ monkeypatch.setattr(_write, "should_prompt", lambda *a, **k: True)
712
+ route = respx.post(f"{BASE}/api/issues/i1/archive").mock(return_value=httpx.Response(200, json={}))
713
+ result = runner.invoke(app, ["--json", "issues", "archive", "i1"])
714
+ assert result.exit_code == 0, result.output
715
+ assert route.called
716
+
717
+
718
+ def test_issues_clear_requires_exactly_one_scope(logged_in, runner):
719
+ # The three scopes have very different blast radii, so there is deliberately
720
+ # no default and no way to pass two.
721
+ none_given = runner.invoke(app, ["--json", "issues", "clear"])
722
+ assert none_given.exit_code == 2, none_given.output
723
+
724
+ two_given = runner.invoke(app, ["--json", "issues", "clear", "--all-audits", "--everything"])
725
+ assert two_given.exit_code == 2, two_given.output
726
+
727
+
728
+ @respx.mock
729
+ def test_issues_clear_dry_run_writes_nothing(logged_in, runner):
730
+ calls = []
731
+
732
+ def record(request):
733
+ calls.append(json.loads(request.content))
734
+ return httpx.Response(200, json={"issues": 12, "findings": 9, "dry_run": True, "scope": "all_audits"})
735
+
736
+ respx.post(f"{BASE}/api/issues/bulk-clear").mock(side_effect=record)
737
+ result = runner.invoke(app, ["--json", "issues", "clear", "--all-audits", "--dry-run"])
738
+ assert result.exit_code == 0, result.output
739
+ assert json.loads(result.stdout)["issues"] == 12
740
+ # Exactly one request, and it carried dry_run — a second (writing) call here
741
+ # would mean --dry-run cleared the board.
742
+ assert len(calls) == 1
743
+ assert calls[0] == {"scope": "all_audits", "dry_run": True}
744
+
745
+
746
+ @respx.mock
747
+ def test_issues_clear_previews_then_writes(logged_in, runner):
748
+ calls = []
749
+
750
+ def record(request):
751
+ body = json.loads(request.content)
752
+ calls.append(body)
753
+ return httpx.Response(200, json={
754
+ "issues": 3, "findings": 2, "dry_run": body["dry_run"], "scope": "audit"})
755
+
756
+ respx.post(f"{BASE}/api/issues/bulk-clear").mock(side_effect=record)
757
+ result = runner.invoke(app, ["--json", "issues", "clear", "--audit", "a1", "--yes"])
758
+ assert result.exit_code == 0, result.output
759
+ # Preview first (so the confirm can name a number), then the real write.
760
+ assert [c["dry_run"] for c in calls] == [True, False]
761
+ assert all(c["audit_id"] == "a1" and c["scope"] == "audit" for c in calls)
762
+ assert json.loads(result.stdout)["issues"] == 3
763
+
764
+
765
+ @respx.mock
766
+ def test_issues_clear_declined_emits_the_json_cancel_envelope(logged_in, runner, monkeypatch):
767
+ monkeypatch.setattr(_write, "should_prompt", lambda *a, **k: True)
768
+ monkeypatch.setattr(output, "confirm_issues_clear", lambda *a, **k: False)
769
+ calls = []
770
+
771
+ def record(request):
772
+ calls.append(json.loads(request.content))
773
+ return httpx.Response(200, json={"issues": 5, "findings": 4, "dry_run": True, "scope": "everything"})
774
+
775
+ respx.post(f"{BASE}/api/issues/bulk-clear").mock(side_effect=record)
776
+ result = runner.invoke(app, ["--json", "issues", "clear", "--everything"])
777
+ assert result.exit_code == 0, result.output
778
+ assert json.loads(result.stdout) == {"cancelled": True}
779
+ # Only the preview ran. A declined confirm that had already written would be
780
+ # the worst possible version of this bug.
781
+ assert [c["dry_run"] for c in calls] == [True]
782
+
783
+
784
+ @respx.mock
785
+ def test_issues_clear_on_an_empty_scope_does_not_prompt_or_write(logged_in, runner, monkeypatch):
786
+ monkeypatch.setattr(_write, "should_prompt", lambda *a, **k: True)
787
+
788
+ def boom(*a, **k): # pragma: no cover - asserts it is never reached
789
+ raise AssertionError("prompted to confirm clearing zero issues")
790
+
791
+ monkeypatch.setattr(output, "confirm_issues_clear", boom)
792
+ calls = []
793
+
794
+ def record(request):
795
+ calls.append(json.loads(request.content))
796
+ return httpx.Response(200, json={"issues": 0, "findings": 0, "dry_run": True, "scope": "all_audits"})
797
+
798
+ respx.post(f"{BASE}/api/issues/bulk-clear").mock(side_effect=record)
799
+ result = runner.invoke(app, ["--json", "issues", "clear", "--all-audits"])
800
+ assert result.exit_code == 0, result.output
801
+ assert json.loads(result.stdout)["issues"] == 0
802
+ assert [c["dry_run"] for c in calls] == [True]
803
+
804
+
805
+ def test_issues_list_accepts_closed_as_a_state(logged_in, runner):
806
+ # `--state` is validated client-side (exit 2) against a hand-copied list. A
807
+ # state missing from it is rejected here for something the server accepts.
808
+ from fp_cli.commands import incidents_cmds
809
+
810
+ assert "closed" in incidents_cmds._STATES
811
+ bad = runner.invoke(app, ["--json", "issues", "list", "--state", "nope"])
812
+ assert bad.exit_code == 2, bad.output
813
+
814
+
815
+ @respx.mock
816
+ def test_issues_show_surfaces_closed_at_and_archived_at(logged_in, runner):
817
+ # `Incident.from_dict` is an ALLOWLIST — a key it does not name is dropped
818
+ # with no error, which renders as a permanently blank column.
819
+ respx.get(f"{BASE}/api/issues/i1").mock(return_value=httpx.Response(200, json={
820
+ "id": "i1", "state": "closed", "closed_at": "2026-09-22T10:00:00Z",
821
+ "archived_at": "2026-09-22T11:00:00Z", "alert_severity": "warning",
822
+ }))
823
+ from fp_cli import client as api
824
+
825
+ inc = api.get_incident(api.ClientContext(base_url=BASE, token="s"), "i1")
826
+ assert inc.closed_at == "2026-09-22T10:00:00Z"
827
+ assert inc.archived_at == "2026-09-22T11:00:00Z"
828
+ assert inc.state == "closed"
@@ -372,7 +372,7 @@ wheels = [
372
372
 
373
373
  [[package]]
374
374
  name = "posthog"
375
- version = "7.53.0"
375
+ version = "7.58.0"
376
376
  source = { registry = "https://pypi.org/simple" }
377
377
  dependencies = [
378
378
  { name = "backoff" },
@@ -380,9 +380,9 @@ dependencies = [
380
380
  { name = "requests" },
381
381
  { name = "typing-extensions" },
382
382
  ]
383
- sdist = { url = "https://files.pythonhosted.org/packages/f2/c4/17a2a764a63e061f768a56d50c245e3012da536b5a15ed2521616b8869b7/posthog-7.53.0.tar.gz", hash = "sha256:38223a8769b9db7ab906d32f5b87c4cde534cc91b30f1dcfa4443577819e068c", size = 565313, upload-time = "2026-09-11T17:51:16.199Z" }
383
+ sdist = { url = "https://files.pythonhosted.org/packages/ef/d9/6e351a6c739fec6e78c1be494f4d39512271167d3eb5905ccafa783c3234/posthog-7.58.0.tar.gz", hash = "sha256:43dcdeea3af4dbdece3322c5e37864cbdad6d0e507c20182bb33a960900bb077", size = 668207, upload-time = "2026-09-18T15:00:06.96Z" }
384
384
  wheels = [
385
- { url = "https://files.pythonhosted.org/packages/03/9d/4af58450b986666c810dc1fbae04e41cdec62ab31ae1e0e51bb5a4b75f71/posthog-7.53.0-py3-none-any.whl", hash = "sha256:ca9176334cf0c7799fe5518f7b9dc02d3f2f709a859da4332f14b3586b9d4554", size = 662783, upload-time = "2026-09-11T17:51:14.275Z" },
385
+ { url = "https://files.pythonhosted.org/packages/24/60/997629e929e96641d297f7bf67543026eeaf5f8a3de66363f1cbe366c0ee/posthog-7.58.0-py3-none-any.whl", hash = "sha256:2ac581ce52c63acb3a551c9066652c3d29e1de1ea06e70655ebe8b866e80f334", size = 781569, upload-time = "2026-09-18T15:00:04.587Z" },
386
386
  ]
387
387
 
388
388
  [[package]]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "1.0.6-beta.1",
3
+ "version": "1.0.7-beta.0",
4
4
  "description": "Observability and enforcement for AI agent harnesses. 39 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
5
5
  "bin": {
6
6
  "failproofai": "./dist/cli.mjs",
@@ -81,14 +81,14 @@
81
81
  "access": "public"
82
82
  },
83
83
  "devDependencies": {
84
- "@anthropic-ai/sdk": "^0.125.0",
84
+ "@anthropic-ai/sdk": "^0.127.0",
85
85
  "@mdx-js/mdx": "^3.1.1",
86
86
  "@tailwindcss/postcss": "^4.3.1",
87
87
  "@tanstack/react-virtual": "^3.14.10",
88
88
  "@testing-library/jest-dom": "^7.0.0",
89
89
  "@testing-library/react": "^16.3.2",
90
90
  "@testing-library/user-event": "^14.6.5",
91
- "@types/node": "26.5.1",
91
+ "@types/node": "26.6.1",
92
92
  "@types/react": "19.3.0",
93
93
  "@types/react-dom": "^19.2.3",
94
94
  "@vitejs/plugin-react": "^6.1.0",
@@ -122,15 +122,15 @@
122
122
  "browserslist": "4.28.8"
123
123
  },
124
124
  "optionalDependencies": {
125
- "@failproofai/failproofaid-linux-x64": "1.0.6-beta.1",
126
- "@failproofai/failproofaid-linux-arm64": "1.0.6-beta.1",
127
- "@failproofai/failproofaid-darwin-x64": "1.0.6-beta.1",
128
- "@failproofai/failproofaid-darwin-arm64": "1.0.6-beta.1"
125
+ "@failproofai/failproofaid-linux-x64": "1.0.7-beta.0",
126
+ "@failproofai/failproofaid-linux-arm64": "1.0.7-beta.0",
127
+ "@failproofai/failproofaid-darwin-x64": "1.0.7-beta.0",
128
+ "@failproofai/failproofaid-darwin-arm64": "1.0.7-beta.0"
129
129
  },
130
130
  "failproofaidBinaries": {
131
- "linux-x64": "b6f877cd407c3322ff8ad2f98a096b6ea15b0bd1d63de6276c689368345c1cd2",
132
- "linux-arm64": "0da90a816b9dc707efe9b82176923cbf0f00026179c816e34eaad362ce2c0fa4",
133
- "darwin-x64": "bcbec488353fa04a1953a87cad15054e4796117cac4fd60b17fb1c87d62b7b1e",
134
- "darwin-arm64": "6830989a91a27642cdcc63fcfad529c69c0deb3f3abcf5ad40fd3516289b451d"
131
+ "linux-x64": "3ac234868fe9149179c32981da9ae1209371e98df7ffb09c6a4947664a3d6857",
132
+ "linux-arm64": "3603faac5741c321a63ba5a963e0cb4bb38c84663a63b53f764faae020116f90",
133
+ "darwin-x64": "c8a5b32a1e97b5a7f76ce4e42c78f0d566d108be65fdca3c6cd59c405f39b946",
134
+ "darwin-arm64": "255ac741c3ee3069591b1eb8bbf397baa0722ef240b83a8540b73f3f1b35722c"
135
135
  }
136
136
  }
@@ -9,6 +9,13 @@ in `failproofai_sdk/_version.py` becomes that release's GitHub Release body. A
9
9
  release whose section is missing or empty is refused before anything is built —
10
10
  see `scripts/changelog-section.py`.
11
11
 
12
+ ## 0.0.1b5 — 2026-09-22
13
+
14
+ Open for the next release. `0.0.1b4` published on 2026-09-22 and the `bump` job
15
+ moved the version here automatically; nothing has landed against `0.0.1b5` yet.
16
+ Add entries as changes merge — this section becomes the GitHub Release body when
17
+ it ships.
18
+
12
19
  ## 0.0.1b4 — 2026-09-12
13
20
 
14
21
  Open for the next release. `0.0.1b3` published on 2026-09-12 and the `bump` job
@@ -1 +1 @@
1
- __version__ = "0.0.1b4"
1
+ __version__ = "0.0.1b5"
@@ -10,6 +10,7 @@ from failproofai_sdk.evaluator.authoring import (
10
10
  EvalDefinition,
11
11
  EvalResult,
12
12
  Evaluator,
13
+ ManagedCompiler,
13
14
  Metric,
14
15
  Score,
15
16
  )
@@ -67,6 +68,7 @@ __all__ = [
67
68
  "EvalSelection",
68
69
  "ExecutionMode",
69
70
  "Evaluator",
71
+ "ManagedCompiler",
70
72
  "EvaluatorAPIError",
71
73
  "EvaluatorClient",
72
74
  "EvaluatorKind",
@@ -36,6 +36,17 @@ ConditionFunction = Callable[
36
36
  [SessionTranscript], "bool | ConditionResult | Awaitable[bool | ConditionResult]"
37
37
  ]
38
38
  CancellationFunction = Callable[[SessionTranscript], "Any | Awaitable[Any]"]
39
+ # Compiles server-authored (managed) source into something callable with a
40
+ # session. Signature mirrors `source.compile_evaluator`, which is the default.
41
+ #
42
+ # A host that serves managed definitions whose source is NOT a restricted Python
43
+ # expression — FailproofAI's own managed worker, whose source may instead be a
44
+ # declarative judge document — installs its own compiler here. The SDK stays
45
+ # agnostic: it never inspects the source, it just hands it to whoever compiles.
46
+ # Returning an `async def` is supported and is the right shape for a compiler
47
+ # whose work is IO-bound (see `WorkerRuntime._invoke`, which awaits an awaitable
48
+ # result on the event loop rather than parking a thread on it).
49
+ ManagedCompiler = Callable[..., EvalFunction]
39
50
 
40
51
 
41
52
  def _bounded(value: str, *, field_name: str, maximum: int) -> str:
@@ -284,11 +295,23 @@ class EvalDefinition:
284
295
  class Evaluator:
285
296
  """A process-local collection of explicitly versioned evaluations."""
286
297
 
287
- def __init__(self, *, name: str, version: str) -> None:
298
+ def __init__(
299
+ self,
300
+ *,
301
+ name: str,
302
+ version: str,
303
+ managed_compiler: ManagedCompiler | None = None,
304
+ ) -> None:
288
305
  self.name = _bounded(name, field_name="name", maximum=MAX_DISPLAY_NAME_BYTES)
289
306
  self.version = _bounded(
290
307
  version, field_name="version", maximum=MAX_VERSION_BYTES
291
308
  )
309
+ # `None` keeps the default: managed source is compiled and sandboxed by
310
+ # `source.compile_evaluator`. Customer workers never set this — their
311
+ # definitions are LOCAL and never carry source at all.
312
+ if managed_compiler is not None and not callable(managed_compiler):
313
+ raise TypeError("managed_compiler must be callable")
314
+ self.managed_compiler = managed_compiler
292
315
  self._definitions: dict[str, EvalDefinition] = {}
293
316
 
294
317
  def eval(
@@ -88,7 +88,12 @@ def _utc_now() -> str:
88
88
  )
89
89
 
90
90
 
91
- def _deferred_managed_eval(source: str, timeout_seconds: int | None, eval_key: str):
91
+ def _deferred_managed_eval(
92
+ source: str,
93
+ timeout_seconds: int | None,
94
+ eval_key: str,
95
+ compiler: Any = None,
96
+ ):
92
97
  """Compile server-authored source lazily, at invocation time.
93
98
 
94
99
  Compilation can reject unsafe or malformed source (``UnsafeEvaluatorSource``).
@@ -98,10 +103,19 @@ def _deferred_managed_eval(source: str, timeout_seconds: int | None, eval_key: s
98
103
  dead-letters cleanly as one failed run instead of raising out of assignment
99
104
  setup, crashing the task, and forcing the whole assignment to be reclaimed
100
105
  and retried until its attempt budget is exhausted.
106
+
107
+ ``compiler`` defaults to :func:`compile_evaluator` — the restricted-expression
108
+ sandbox. A host serving managed definitions in another shape installs its own
109
+ via ``Evaluator(managed_compiler=...)``; the SDK never inspects the source.
110
+ The thunk stays SYNCHRONOUS either way, so a sandboxed compile still runs in
111
+ the runtime's sized executor exactly as before. A compiler that returns an
112
+ ``async def`` simply makes this thunk return a coroutine, which ``_invoke``
113
+ awaits on the event loop — the right place for IO-bound work.
101
114
  """
115
+ compile_source = compiler if compiler is not None else compile_evaluator
102
116
 
103
117
  def evaluate(session: Any) -> Any:
104
- return compile_evaluator(
118
+ return compile_source(
105
119
  source, timeout_seconds=timeout_seconds, eval_key=eval_key
106
120
  )(session)
107
121
 
@@ -551,6 +565,7 @@ class WorkerRuntime:
551
565
  run.evaluator_source,
552
566
  run.timeout_seconds or descriptor.timeout_seconds,
553
567
  descriptor.eval_key,
568
+ self.evaluator.managed_compiler,
554
569
  ),
555
570
  condition=None,
556
571
  on_cancel=None,
@@ -597,9 +612,15 @@ class WorkerRuntime:
597
612
  started = time.monotonic()
598
613
  # A synchronous evaluator runs in the executor thread; if it overruns the
599
614
  # wall-clock timeout below, the thread cannot be cancelled and is orphaned.
600
- sync_function = not inspect.iscoroutinefunction(definition.function)
615
+ #
616
+ # Decided AFTER the fact, not from the function object: a deferred managed
617
+ # thunk is a sync callable that may return a coroutine, which `_invoke`
618
+ # awaits on the loop. That case is cancellable and leaves no orphan, so
619
+ # counting it as one would send an operator hunting a hung thread that
620
+ # does not exist.
621
+ awaited_on_loop: list[bool] = []
601
622
  try:
602
- invocation = self._invoke(definition.function, session)
623
+ invocation = self._invoke(definition.function, session, awaited_on_loop)
603
624
  # Always bound the evaluation. A definition with no declared
604
625
  # timeout_seconds falls back to DEFAULT_EVAL_TIMEOUT_SECONDS rather
605
626
  # than awaiting unbounded — an unbounded local eval that hangs would
@@ -630,7 +651,7 @@ class WorkerRuntime:
630
651
  # the forked managed sandbox was killed by its CPU/memory/time budget —
631
652
  # the real, thread-uncancellable case. Both are a timed-out run.
632
653
  await self._cancel_hook(definition, session)
633
- if isinstance(timeout_error, asyncio.TimeoutError) and sync_function:
654
+ if isinstance(timeout_error, asyncio.TimeoutError) and not awaited_on_loop:
634
655
  # The awaiter gave up while a SYNCHRONOUS evaluator was still
635
656
  # running in the executor. CPython cannot interrupt that thread,
636
657
  # so it is now orphaned — it runs to completion (or forever)
@@ -824,12 +845,26 @@ class WorkerRuntime:
824
845
  remaining = min(remaining, float(timeout_seconds))
825
846
  return remaining
826
847
 
827
- async def _invoke(self, function, session):
848
+ async def _invoke(self, function, session, awaited: list[bool] | None = None):
849
+ """Run an evaluation or condition, on the loop or in the executor.
850
+
851
+ ``awaited`` is an out-parameter: append-only, and appended to exactly when
852
+ the work ends up being awaited on the EVENT LOOP rather than occupying an
853
+ executor thread. The timeout handler needs that distinction — a coroutine
854
+ is cancelled cleanly, a running thread is not — and it cannot be known
855
+ from the function object alone, because a deferred managed thunk is a
856
+ plain sync callable that may still RETURN a coroutine.
857
+ """
828
858
  if inspect.iscoroutinefunction(function):
859
+ if awaited is not None:
860
+ awaited.append(True)
829
861
  return await function(session)
830
862
  loop = asyncio.get_running_loop()
831
863
  result = await loop.run_in_executor(self._eval_executor, function, session)
832
864
  if inspect.isawaitable(result):
865
+ # The thread has already returned; only the coroutine is outstanding.
866
+ if awaited is not None:
867
+ awaited.append(True)
833
868
  return await result
834
869
  return result
835
870
 
@@ -1101,3 +1101,91 @@ def test_condition_phase_deadline_and_budget_are_lease_bounded():
1101
1101
  assert runtime._condition_budget(deadline, None) == pytest.approx(95, abs=2)
1102
1102
  assert runtime._condition_budget(deadline, 10) == pytest.approx(10, abs=0.05)
1103
1103
  assert runtime._condition_budget(time.monotonic(), None) < 0
1104
+
1105
+
1106
+ def test_managed_compiler_hook_receives_source_verbatim_and_may_be_async():
1107
+ # The SDK must never inspect managed source. FailproofAI's hosted worker
1108
+ # serves definitions whose source is a declarative judge document rather than
1109
+ # a restricted Python expression; it installs its own compiler, and the SDK's
1110
+ # only job is to hand the string over unchanged. The compiler returns an
1111
+ # `async def` here because judge work is IO-bound — `_invoke` must await that
1112
+ # on the event loop rather than parking an executor thread on it.
1113
+ source = '{"kind": "judge", "criteria": "was it helpful", "threshold": 0.5}'
1114
+ seen: dict[str, object] = {}
1115
+
1116
+ def compiler(raw, *, timeout_seconds, eval_key):
1117
+ seen["source"] = raw
1118
+ seen["timeout_seconds"] = timeout_seconds
1119
+ seen["eval_key"] = eval_key
1120
+
1121
+ async def judge(session):
1122
+ seen["session_id"] = session.session_id
1123
+ return EvalResult(score=Score(0.9, passed=True), summary="judged")
1124
+
1125
+ return judge
1126
+
1127
+ class HostedClient(FakeClient):
1128
+ def __init__(self):
1129
+ super().__init__()
1130
+ self.assignment = replace(
1131
+ self.assignment,
1132
+ definitions_url=f"/v1/evaluator/assignments/{self.assignment.assignment_id}/definitions",
1133
+ )
1134
+
1135
+ def definitions(self, assignment, *, worker_id):
1136
+ return DefinitionsResponse(
1137
+ assignment_id=assignment.assignment_id,
1138
+ catalog_revision="sha256:hosted",
1139
+ definitions=(
1140
+ AssignmentDefinition(
1141
+ eval_key="answer_quality",
1142
+ display_name="Answer quality",
1143
+ eval_version="1",
1144
+ result_kind=ResultKind.SCORE,
1145
+ execution_mode=ExecutionMode.PYTHON,
1146
+ source_checksum=source_checksum(None, source),
1147
+ ),
1148
+ ),
1149
+ )
1150
+
1151
+ def plan(self, assignment_id, request):
1152
+ self.plans.append(request)
1153
+ return PlanResponse(
1154
+ assignment_id=assignment_id,
1155
+ assignment_status="planned",
1156
+ runs=(
1157
+ PlannedRun(
1158
+ "run-judge",
1159
+ "answer_quality",
1160
+ "1",
1161
+ execution_mode=ExecutionMode.PYTHON,
1162
+ evaluator_source=source,
1163
+ source_checksum=source_checksum(None, source),
1164
+ timeout_seconds=30,
1165
+ ),
1166
+ ),
1167
+ )
1168
+
1169
+ client = HostedClient()
1170
+ evaluator = Evaluator(name="managed", version="1", managed_compiler=compiler)
1171
+ asyncio.run(_runtime(evaluator, client).process_assignment(client.assignment))
1172
+
1173
+ # Verbatim: not parsed, not normalised, not validated by the SDK.
1174
+ assert seen["source"] == source
1175
+ assert seen["eval_key"] == "answer_quality"
1176
+ assert seen["timeout_seconds"] == 30
1177
+ assert seen["session_id"] == client.assignment.session_id
1178
+
1179
+ assert len(client.submissions) == 1
1180
+ run_id, result = client.submissions[0]
1181
+ assert run_id == "run-judge"
1182
+ assert result.status.value == "succeeded"
1183
+ assert result.results[0].numeric_value == 0.9
1184
+
1185
+
1186
+ def test_managed_compiler_defaults_to_the_sandbox_and_rejects_non_callables():
1187
+ # No compiler installed => the restricted-expression sandbox, unchanged. This
1188
+ # is what every customer worker and every existing deployment gets.
1189
+ assert Evaluator(name="default", version="1").managed_compiler is None
1190
+ with pytest.raises(TypeError):
1191
+ Evaluator(name="bad", version="1", managed_compiler="not-callable")
@@ -751,7 +751,7 @@ wheels = [
751
751
 
752
752
  [[package]]
753
753
  name = "crewai"
754
- version = "1.15.21"
754
+ version = "1.15.22"
755
755
  source = { registry = "https://pypi.org/simple" }
756
756
  dependencies = [
757
757
  { name = "aiofiles" },
@@ -786,14 +786,14 @@ dependencies = [
786
786
  { name = "tomli" },
787
787
  { name = "tomli-w" },
788
788
  ]
789
- sdist = { url = "https://files.pythonhosted.org/packages/ab/d4/58a44b3450f95c0cb2654bccc01bc5cd65bff768b57189f11cecf641d18d/crewai-1.15.21.tar.gz", hash = "sha256:87eb5c7ad772db5b21cfd4c219800c1886a6a5076d02617db16824f944264345", size = 7975540, upload-time = "2026-09-09T22:54:56.424Z" }
789
+ sdist = { url = "https://files.pythonhosted.org/packages/38/3e/e79f59badc4194f5d80e9ae5b07d35d96206d705270a2b2a2b5508b0a54f/crewai-1.15.22.tar.gz", hash = "sha256:0b1c654f35e07dfe41720337c56542b334644a32c5e4498a6e38e5e7da7dd82e", size = 8057957, upload-time = "2026-09-16T22:10:43.476Z" }
790
790
  wheels = [
791
- { url = "https://files.pythonhosted.org/packages/c6/2b/38d8b378ec1f55d893f2d52dfb6349b282f464f40535ff53b047916f21d4/crewai-1.15.21-py3-none-any.whl", hash = "sha256:08ef2605260b0ffa7c54218fbcab8d3c4dc8d6307bd3b43513985529f6f666ab", size = 1144433, upload-time = "2026-09-09T22:54:54.299Z" },
791
+ { url = "https://files.pythonhosted.org/packages/f0/92/b19a70b6109ce2c2bd24dc63f8b4022a7097816ef236a1d9de1733738db8/crewai-1.15.22-py3-none-any.whl", hash = "sha256:7675c2bb1166933c60d67596936cd8b4ce63d77361dd3aeb925b210180298e5a", size = 1189089, upload-time = "2026-09-16T22:10:41.471Z" },
792
792
  ]
793
793
 
794
794
  [[package]]
795
795
  name = "crewai-cli"
796
- version = "1.15.21"
796
+ version = "1.15.22"
797
797
  source = { registry = "https://pypi.org/simple" }
798
798
  dependencies = [
799
799
  { name = "appdirs" },
@@ -813,14 +813,14 @@ dependencies = [
813
813
  { name = "tomli-w" },
814
814
  { name = "uv" },
815
815
  ]
816
- sdist = { url = "https://files.pythonhosted.org/packages/c7/22/4747b13eeedcc073d6b5c5af1738491d321b26846ab9d979def67fbecbff/crewai_cli-1.15.21.tar.gz", hash = "sha256:9b949c50d20daef8b51a0c54e7726a0570b55233eef8ffd47febfe329137b3da", size = 235951, upload-time = "2026-09-09T22:54:59.153Z" }
816
+ sdist = { url = "https://files.pythonhosted.org/packages/3f/ed/f143440caca0fd6c5cb985710f79f0a6ebffbbb5468ccf842d55b5500ce6/crewai_cli-1.15.22.tar.gz", hash = "sha256:b7e00daedfbee9102ce4fcaffa10224b27c9c02e2b976c98a25acf214730acae", size = 244293, upload-time = "2026-09-16T22:10:46.178Z" }
817
817
  wheels = [
818
- { url = "https://files.pythonhosted.org/packages/ad/b4/3352d0f1bb431bc2d8f4aaabd045a30bd374a50c682e15c80fc1290b8d52/crewai_cli-1.15.21-py3-none-any.whl", hash = "sha256:f65b4d0f55816a1a2d3b86c9c087527ddb1c85d868a4d2a75931edc04482cbd9", size = 198948, upload-time = "2026-09-09T22:54:57.921Z" },
818
+ { url = "https://files.pythonhosted.org/packages/d6/af/5e06129c84ba60a7f48ee181d6b0c3f270f00e2dcebaf48556325cc1a59b/crewai_cli-1.15.22-py3-none-any.whl", hash = "sha256:b44abcc69b90f30c4d3b93657542879f5e2ac83506757a38a1e2e404a9d84300", size = 202381, upload-time = "2026-09-16T22:10:44.901Z" },
819
819
  ]
820
820
 
821
821
  [[package]]
822
822
  name = "crewai-core"
823
- version = "1.15.21"
823
+ version = "1.15.22"
824
824
  source = { registry = "https://pypi.org/simple" }
825
825
  dependencies = [
826
826
  { name = "appdirs" },
@@ -836,9 +836,9 @@ dependencies = [
836
836
  { name = "rich" },
837
837
  { name = "tomli" },
838
838
  ]
839
- sdist = { url = "https://files.pythonhosted.org/packages/b3/4c/28f0bcf8b77bcb5adc782b6403001fb23e83ec26e6a6c7a5aee97b480450/crewai_core-1.15.21.tar.gz", hash = "sha256:8ca73bd33e0fe1de1a25193bd3c468e9e23aee55f6b4001c4f43e65a3fdbde81", size = 39213, upload-time = "2026-09-09T22:55:01.395Z" }
839
+ sdist = { url = "https://files.pythonhosted.org/packages/95/27/1ed033b746506039e21291eb64fd4624e9de0b0489dfd5128e7efb936e92/crewai_core-1.15.22.tar.gz", hash = "sha256:6b85c609536bdca62e93eca59933f3609f79b8e207e8f6d3c59a8365a5ed5d78", size = 43090, upload-time = "2026-09-16T22:10:48.274Z" }
840
840
  wheels = [
841
- { url = "https://files.pythonhosted.org/packages/a1/36/9a415b0420ff9dff74d9413fb48a438754ebaa102fc73b85f7923d5819a1/crewai_core-1.15.21-py3-none-any.whl", hash = "sha256:26143369fb3278c41ea843f2bab29437c78d962738b98e8f846aac3225c65bf5", size = 42480, upload-time = "2026-09-09T22:55:00.218Z" },
841
+ { url = "https://files.pythonhosted.org/packages/02/45/f2eb1866dc98d75617ebe942e9575b6fa1f2f2b7c6352ea22cb2faf75931/crewai_core-1.15.22-py3-none-any.whl", hash = "sha256:8b97a45a35e663f17af65ed2a318bf428d5806ce20a54b0fce726ebe53eedb75", size = 44375, upload-time = "2026-09-16T22:10:47.06Z" },
842
842
  ]
843
843
 
844
844
  [[package]]
@@ -3906,7 +3906,7 @@ wheels = [
3906
3906
 
3907
3907
  [[package]]
3908
3908
  name = "pydantic-ai-slim"
3909
- version = "2.42.0"
3909
+ version = "2.45.0"
3910
3910
  source = { registry = "https://pypi.org/simple" }
3911
3911
  dependencies = [
3912
3912
  { name = "anyio" },
@@ -3919,9 +3919,9 @@ dependencies = [
3919
3919
  { name = "pydantic-graph" },
3920
3920
  { name = "typing-inspection" },
3921
3921
  ]
3922
- sdist = { url = "https://files.pythonhosted.org/packages/47/ec/59161f7aa44251775afec888615b84905634252b7dac6f0acf2f430ffadf/pydantic_ai_slim-2.42.0.tar.gz", hash = "sha256:045c788b1636e0ed79542f2b7562d2688a3e3701744ed7379a928fceec446336", size = 1453904, upload-time = "2026-09-09T03:48:51.06Z" }
3922
+ sdist = { url = "https://files.pythonhosted.org/packages/70/ad/d39e46c192e04df2e620871592054185aed4e212558d90bf89436aa7628d/pydantic_ai_slim-2.45.0.tar.gz", hash = "sha256:34cf5216ebe05cb3d343129bb24a1bd011c66e89dd620f00afa982bd2484e3bf", size = 1562650, upload-time = "2026-09-18T04:50:15.315Z" }
3923
3923
  wheels = [
3924
- { url = "https://files.pythonhosted.org/packages/80/d5/5f9489b6d8583366c32c76671f4d5c3a91cf7e0e0a7073269d9fee756f28/pydantic_ai_slim-2.42.0-py3-none-any.whl", hash = "sha256:a042b73551470c40a2ab7cd532e26c20f592b5731ae544ff3eecbcdf6ceebcc4", size = 1714452, upload-time = "2026-09-09T03:48:43.81Z" },
3924
+ { url = "https://files.pythonhosted.org/packages/de/5b/1397c8c65eaeae781ec489db648ab24125268b749d1f256ba7c13b07891e/pydantic_ai_slim-2.45.0-py3-none-any.whl", hash = "sha256:0914f4e307c64401b676bbba2d613da69f74dbeaab4e2e8516d0d9b53f4ab483", size = 1857668, upload-time = "2026-09-18T04:50:07.395Z" },
3925
3925
  ]
3926
3926
 
3927
3927
  [[package]]
@@ -4044,7 +4044,7 @@ wheels = [
4044
4044
 
4045
4045
  [[package]]
4046
4046
  name = "pydantic-graph"
4047
- version = "2.42.0"
4047
+ version = "2.45.0"
4048
4048
  source = { registry = "https://pypi.org/simple" }
4049
4049
  dependencies = [
4050
4050
  { name = "anyio" },
@@ -4052,9 +4052,9 @@ dependencies = [
4052
4052
  { name = "pydantic" },
4053
4053
  { name = "typing-inspection" },
4054
4054
  ]
4055
- sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/f14d82d10c7f4e4e4d20c880593745e05686b176ac397fe52a5ec40959a9/pydantic_graph-2.42.0.tar.gz", hash = "sha256:eb6c416dee9e3415195194c44ca3abeeae8853bff095d38ac798c7c3395f815d", size = 45189, upload-time = "2026-09-09T03:48:53.398Z" }
4055
+ sdist = { url = "https://files.pythonhosted.org/packages/56/ee/d4f4c73eb91241af7a06e4c36ef58c18d3622dc03d8343825464ec06cb2e/pydantic_graph-2.45.0.tar.gz", hash = "sha256:c07e8cee55f25d79ea2ea55de67fc67d0353ec2807e3a1328f42995edf022c23", size = 45192, upload-time = "2026-09-18T04:50:17.706Z" }
4056
4056
  wheels = [
4057
- { url = "https://files.pythonhosted.org/packages/7a/c7/eafabe22e04e4e8dfb8e01725e051feed75d3cda937054a727cffc525497/pydantic_graph-2.42.0-py3-none-any.whl", hash = "sha256:25c47047ac1bbad2fdc6dace23958c6ec700a6366abc18d67dccd41411185df7", size = 52700, upload-time = "2026-09-09T03:48:46.931Z" },
4057
+ { url = "https://files.pythonhosted.org/packages/27/56/d040149787ae9ade628f05c1e7f30657437731b0e2bb3a2cdb6cb979b7af/pydantic_graph-2.45.0-py3-none-any.whl", hash = "sha256:60fef7eab40a103510f0baf6d17cdd11418672cb64a7524089fbaf845f45aa1a", size = 52701, upload-time = "2026-09-18T04:50:10.72Z" },
4058
4058
  ]
4059
4059
 
4060
4060
  [[package]]