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,208 @@
1
+ """Permission catalogue (mirrors the server ``Permission`` enum + dashboard presets).
2
+
3
+ Used to validate ``--permission`` / ``--add`` / ``--remove`` / ``--preset`` values
4
+ client-side so a typo is a clean usage error instead of a server 422. Kept in sync
5
+ with ``server/src/auth.rs`` (``Permission::all``) and
6
+ ``dashboard/lib/permissionGroups.ts`` (presets).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from typing import List
13
+
14
+ # The full assignable permission set, in the server's declared order.
15
+ ALL_PERMISSIONS: List[str] = [
16
+ "events:add",
17
+ "events:read",
18
+ "keys:create",
19
+ "keys:read",
20
+ "keys:disable",
21
+ "keys:regenerate",
22
+ "keys:update",
23
+ "users:create",
24
+ "users:read",
25
+ "users:update",
26
+ "users:delete",
27
+ "evaluations:read",
28
+ "evaluations:trigger",
29
+ "dashboards:read",
30
+ "dashboards:write",
31
+ "dashboards:delete",
32
+ "queries:read",
33
+ "queries:write",
34
+ "queries:delete",
35
+ "queries:run",
36
+ "agent:use",
37
+ "settings:read",
38
+ "settings:write",
39
+ "alerts:read",
40
+ "alerts:write",
41
+ "issues:read",
42
+ "issues:create",
43
+ "issues:close",
44
+ "audits:read",
45
+ "audits:write",
46
+ "policies:read",
47
+ "policies:write",
48
+ "policies:pull",
49
+ "usage:read",
50
+ "orgs:admin",
51
+ ]
52
+
53
+ # Retired spellings. `incidents:*` was renamed to `issues:*`; the server still
54
+ # PARSES the old tokens forever (see Permission::from_str) so keys minted before
55
+ # the rename keep working. Accept them here too and normalize, or a script that
56
+ # has passed `--add incidents:read` for a year would start exiting 2 against a
57
+ # server that would happily have honoured it.
58
+ #
59
+ # `incidents:ack` gated ack + comment + assign + subscribe + resolve, which now
60
+ # span all three issues permissions — so it expands to all three rather than
61
+ # mapping to any one, matching expand_implied() on the server.
62
+ RETIRED_PERMISSION_ALIASES = {
63
+ "incidents:read": ["issues:read"],
64
+ "incidents:write": ["issues:create"],
65
+ "incidents:ack": ["issues:read", "issues:create", "issues:close"],
66
+ "alerts:ack": ["issues:read", "issues:create", "issues:close"],
67
+ }
68
+
69
+
70
+ def normalize_permissions(perms: List[str]) -> List[str]:
71
+ """Expand retired spellings to their current equivalents, order-preserving."""
72
+ out: List[str] = []
73
+ for p in perms:
74
+ for mapped in RETIRED_PERMISSION_ALIASES.get(p, [p]):
75
+ if mapped not in out:
76
+ out.append(mapped)
77
+ return out
78
+
79
+ # orgs:admin is an instance-level grant, not assignable to an org API key or member
80
+ # through these commands; exclude it from what the CLI lets you grant.
81
+ ASSIGNABLE_PERMISSIONS: List[str] = [p for p in ALL_PERMISSIONS if p != "orgs:admin"]
82
+
83
+ _READ_ONLY = [p for p in ALL_PERMISSIONS if p.endswith(":read")]
84
+ # Must match BUILTIN_PERMISSION_SETS["standard"] in server/src/ch_tenancy.rs and
85
+ # STANDARD_PERMS in dashboard/lib/permissionGroups.ts. `issues:read` arrives via
86
+ # _READ_ONLY (it ends in ":read"); create + close are added explicitly. Close is
87
+ # included because the retired `incidents:ack` this replaces already granted
88
+ # resolve — see migration 20260721000000.
89
+ _STANDARD = _READ_ONLY + [
90
+ "evaluations:trigger",
91
+ "queries:run",
92
+ "issues:create",
93
+ "issues:close",
94
+ "agent:use",
95
+ ]
96
+
97
+ # Builtin permission-set presets (dashboard/lib/permissionGroups.ts).
98
+ PRESETS = {
99
+ "read-only": list(_READ_ONLY),
100
+ "standard": list(_STANDARD),
101
+ "admin": list(ASSIGNABLE_PERMISSIONS),
102
+ "clear": [],
103
+ }
104
+
105
+ _ASSIGNABLE_SET = frozenset(ASSIGNABLE_PERMISSIONS)
106
+
107
+ # Permissions that parse + are user-assignable but must NEVER sit on an API key — the server's
108
+ # `Permission::key_assignable` rejects them (422). `orgs:admin` is already excluded above;
109
+ # `keys:update` is human-only (a bearer key can create keys but never edit them). The key flows
110
+ # strip these from a permission-set seed (silently, like the dashboard's keyAssignableOnly-strip)
111
+ # and reject them from an explicit `--add` (so the user gets a clean message, not a server 422).
112
+ KEY_NON_ASSIGNABLE = frozenset({"orgs:admin", "keys:update"})
113
+ KEY_ASSIGNABLE_PERMISSIONS: List[str] = [p for p in ASSIGNABLE_PERMISSIONS if p not in KEY_NON_ASSIGNABLE]
114
+
115
+
116
+ def unknown_permissions(perms: List[str]) -> List[str]:
117
+ """Return any tokens that are not assignable permissions (empty if all valid).
118
+
119
+ Retired spellings count as VALID. The server parses them (aliasing to the
120
+ current names), so rejecting them here would make the CLI stricter than the
121
+ API it fronts — a script that has passed `--add incidents:read` for a year
122
+ would start failing with exit 2 against a server that would have accepted it.
123
+ The returned values are already normalized — `parse_permission_tokens` and
124
+ `parse_key_permission_tokens` apply `normalize_permissions` themselves.
125
+ """
126
+ return [
127
+ p
128
+ for p in perms
129
+ if p not in _ASSIGNABLE_SET and p not in RETIRED_PERMISSION_ALIASES
130
+ ]
131
+
132
+
133
+ def key_assignable_only(perms) -> List[str]:
134
+ """Drop the human-only permissions (`keys:update` / `orgs:admin`) from a permission list —
135
+ used when SEEDING a key from a permission set, mirroring the dashboard's keyAssignableOnly
136
+ strip. Order-preserving."""
137
+ return [p for p in perms if p not in KEY_NON_ASSIGNABLE]
138
+
139
+
140
+ def parse_key_permission_tokens(tokens, *, require_nonempty: bool = True) -> List[str]:
141
+ """Like :func:`parse_permission_tokens`, but for an API KEY — additionally rejects the
142
+ human-only permissions (`keys:update` / `orgs:admin`) with a clear message, so an explicit
143
+ `--add keys:update` fails client-side (exit 2) instead of hitting a server 422."""
144
+ flat = parse_permission_tokens(tokens, require_nonempty=require_nonempty)
145
+ forbidden = [p for p in flat if p in KEY_NON_ASSIGNABLE]
146
+ if forbidden:
147
+ raise PermissionTokenError(
148
+ f'{", ".join(sorted(set(forbidden)))} can\'t be granted to an API key — it\'s human-only'
149
+ )
150
+ return flat
151
+
152
+
153
+ def expand_preset(name: str) -> List[str]:
154
+ """Return the permissions for a builtin preset, or raise KeyError."""
155
+ return list(PRESETS[name])
156
+
157
+
158
+ class PermissionTokenError(Exception):
159
+ """A malformed or unknown compact ``slug:act.act`` permission token (carries a
160
+ user-facing message). Shared by ``keys`` and ``users`` so both surfaces parse the
161
+ compact token format identically."""
162
+
163
+
164
+ def parse_permission_tokens(tokens, *, require_nonempty: bool = True) -> List[str]:
165
+ """Expand the compact ``slug:act1.act2`` permission tokens into a de-duplicated flat
166
+ assignable permission list (``events:read.add`` → ``events:read``, ``events:add``). Tokens
167
+ separated by **whitespace or commas** within one value AND repeated flags all compose — so
168
+ ``-p "a b"``, ``--add a,b``, and ``--add a --add b`` are equivalent. Raises
169
+ :class:`PermissionTokenError` on a malformed token or an unknown permission, and — when
170
+ ``require_nonempty`` — on an empty set (``users`` ``--add``/``--remove`` may be empty, so
171
+ they call the helper only when a value is supplied)."""
172
+ flat: List[str] = []
173
+ seen: set = set()
174
+ for raw in tokens or []:
175
+ for tok in re.split(r"[\s,]+", str(raw).strip()): # whitespace- or comma-separated
176
+ if not tok:
177
+ continue # stray empties from `a,,b` / whitespace-only input
178
+ if ":" not in tok:
179
+ raise PermissionTokenError(f'bad permission "{tok}" — expected slug:action (e.g. events:read.add)')
180
+ slug, _, actions_str = tok.partition(":")
181
+ slug = slug.strip()
182
+ actions = [a.strip() for a in actions_str.split(".")]
183
+ if not slug or not actions_str.strip() or any(not a for a in actions):
184
+ raise PermissionTokenError(f'bad permission "{tok}" — no action after \':\' (e.g. {slug or "events"}:read.add)')
185
+ for a in actions:
186
+ perm = f"{slug}:{a}"
187
+ if perm not in seen:
188
+ seen.add(perm)
189
+ flat.append(perm)
190
+ if not flat:
191
+ if require_nonempty:
192
+ raise PermissionTokenError("at least one permission is required (e.g. events:read.add)")
193
+ return flat
194
+ unknown = unknown_permissions(flat)
195
+ if unknown:
196
+ raise PermissionTokenError(f'unknown permission(s): {", ".join(unknown)}')
197
+ # Expand the retired spellings HERE, at the one boundary every caller goes
198
+ # through. `unknown_permissions` deliberately accepts `incidents:*` and
199
+ # `alerts:ack` so a script that has passed them for a year keeps working —
200
+ # but `normalize_permissions`, the function that turns them into current
201
+ # names, had ZERO call sites in the package. So the raw token was carried
202
+ # all the way into `users update`'s diff, where `predicted_after =
203
+ # (before | add) - remove` subtracted a string that is not in the server's
204
+ # set: `--remove incidents:ack` computed no change at all, took the no-op
205
+ # branch, and exited 0 reporting `{"removed": []}` without ever calling the
206
+ # server — while the member kept every issues permission. The command's own
207
+ # help gives `--remove alerts:read,incidents:ack` as a worked example.
208
+ return normalize_permissions(flat)
@@ -0,0 +1,290 @@
1
+ """Check a policy before it reaches a fleet.
2
+
3
+ Nothing between an author and a machine validates policy source today. The CLI
4
+ rejects a NUL byte, the server checks the id charset and a 1 MiB ceiling — and
5
+ neither looks at whether the file is parseable JavaScript at all. So this
6
+ publishes, deploys, and reaches every machine in the fleet:
7
+
8
+ echo 'this is not javascript {{{' | fp policies publish broken
9
+
10
+ It then fails at enforcement time, on the machine, where nobody is watching.
11
+ That is the worst available place for a syntax error to surface, which is what
12
+ these two checks exist to move.
13
+
14
+ ``check_syntax`` is the cheap one and runs before every publish. ``run_policy``
15
+ is the deliberate one behind ``fp policies test``: it actually executes the
16
+ policy against a context you describe, so an author — human or agent — can see
17
+ allow/deny/instruct before anyone's machine does.
18
+
19
+ Both shell out to ``node``. Neither makes it a hard dependency: a machine
20
+ without node still publishes, with a stated reason rather than a silent skip.
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import json
25
+ import os
26
+ import shutil
27
+ import subprocess
28
+ import re
29
+ import tempfile
30
+ from dataclasses import dataclass
31
+ from typing import Any, Dict, List, Optional
32
+
33
+ #: Budget for EXECUTING a policy. Short on purpose: a policy with an accidental
34
+ #: infinite loop must fail the command instead of hanging it, and a policy that
35
+ #: cannot decide in five seconds cannot sit on a hook either. This is a product
36
+ #: statement about hook latency, so it stays tight.
37
+ _TIMEOUT_SECS = 5
38
+
39
+ #: Budget for PARSING one — `node --check`, which runs no user code at all.
40
+ #:
41
+ #: Separate from the number above because the reasoning above does not apply to
42
+ #: it: parsing is bounded work whose only real variable is how long a cold node
43
+ #: process takes to start, and that is a property of the machine, not of the
44
+ #: policy. Sharing the execution budget made the syntax check fail on a busy
45
+ #: runner and report `the syntax check timed out` — which reads as "your policy
46
+ #: is bad" for what is actually "this box was loaded". Seen on CI once the
47
+ #: fp-cloud-cli matrix widened to four concurrent interpreters: three legs passed and
48
+ #: the fourth timed out on the same source.
49
+ #:
50
+ #: Generous rather than tight, because nothing here can loop: if `node --check`
51
+ #: has not answered in thirty seconds, node is wedged and saying so is right.
52
+ _SYNTAX_TIMEOUT_SECS = 30
53
+
54
+ #: SGR escapes node emits around its stack frames.
55
+ _ANSI = re.compile(r"\x1b\[[0-9;]*m")
56
+
57
+
58
+ def node_available() -> bool:
59
+ return shutil.which("node") is not None
60
+
61
+
62
+ @dataclass
63
+ class SyntaxResult:
64
+ ok: bool
65
+ #: None when the check could not run at all (no node). Distinct from `ok`,
66
+ #: because "we did not look" must never render as "we looked and it passed".
67
+ checked: bool
68
+ message: str = ""
69
+
70
+ def to_dict(self) -> Dict[str, Any]:
71
+ return {"ok": self.ok, "checked": self.checked, "message": self.message}
72
+
73
+
74
+ def check_syntax(source: str) -> SyntaxResult:
75
+ """Parse-check policy source with ``node --check``.
76
+
77
+ Written to a ``.mjs`` file so node parses it as a module: policies are ESM
78
+ (`import { deny } from "failproofai"`), and checking that as a script would
79
+ reject every real policy for using `import`.
80
+ """
81
+ if not node_available():
82
+ return SyntaxResult(
83
+ ok=True, checked=False,
84
+ message="node was not found on PATH, so the policy was not syntax-checked",
85
+ )
86
+ with tempfile.TemporaryDirectory() as tmp:
87
+ path = os.path.join(tmp, "policy.mjs")
88
+ with open(path, "w", encoding="utf-8") as fh:
89
+ fh.write(source)
90
+ try:
91
+ proc = subprocess.run(
92
+ ["node", "--check", path],
93
+ capture_output=True, text=True, timeout=_SYNTAX_TIMEOUT_SECS,
94
+ )
95
+ except subprocess.TimeoutExpired:
96
+ # `checked=False`, not `ok=False`. A timeout here says nothing about
97
+ # the SOURCE — `node --check` runs none of it — so reporting a syntax
98
+ # failure blamed the user's policy for the machine being busy, and
99
+ # `fp policies publish` refused a perfectly good file. "We could not
100
+ # look" is the honest verdict, and it is the one `SyntaxResult`
101
+ # already models: the same shape as node being absent entirely.
102
+ return SyntaxResult(
103
+ ok=True, checked=False,
104
+ message=(
105
+ f"the syntax check did not finish within {_SYNTAX_TIMEOUT_SECS}s, "
106
+ "so the source was not checked"
107
+ ),
108
+ )
109
+ except OSError as exc:
110
+ return SyntaxResult(ok=True, checked=False,
111
+ message=f"could not run node ({exc}); source not checked")
112
+ if proc.returncode == 0:
113
+ return SyntaxResult(ok=True, checked=True)
114
+ # node prints the offending line, a caret, the SyntaxError — and then its own
115
+ # internal stack and version banner. The first three are the whole value of
116
+ # the check; the rest is node talking about itself inside an error box about
117
+ # the user's policy.
118
+ raw = (proc.stderr or proc.stdout or "").strip().replace(path, "<policy>")
119
+ # node colourises its stack frames, so the marker lines arrive with ANSI
120
+ # prefixes and a plain startswith() sails straight past them. Strip escapes
121
+ # before matching, and from the kept text too — this is going inside a box
122
+ # the CLI is already styling.
123
+ raw = _ANSI.sub("", raw)
124
+ keep = []
125
+ for line in raw.splitlines():
126
+ stripped = line.strip()
127
+ if stripped.startswith("at ") or stripped.startswith("Node.js v"):
128
+ break
129
+ keep.append(line)
130
+ detail = "\n".join(keep).strip() or raw
131
+ return SyntaxResult(ok=False, checked=True, message=detail)
132
+
133
+
134
+ #: A stand-in for the `failproofai` package a policy imports. Policies are
135
+ #: authored against the real one; this provides the same three helpers and
136
+ #: collects what `customPolicies.add` registers, so a policy can be executed
137
+ #: without installing anything.
138
+ _SHIM = """\
139
+ export const allow = (reason) => reason ? { decision: "allow", reason } : { decision: "allow" };
140
+ export const deny = (reason) => ({ decision: "deny", reason });
141
+ export const instruct = (reason) => ({ decision: "instruct", reason });
142
+ export const registered = [];
143
+ export const customPolicies = { add: (p) => { registered.push(p); } };
144
+ export default { allow, deny, instruct, customPolicies };
145
+ """
146
+
147
+ _RUNNER = """\
148
+ import { registered } from "failproofai";
149
+ import "./policy.mjs";
150
+
151
+ const ctx = JSON.parse(process.argv[2]);
152
+ const out = [];
153
+ for (const p of registered) {
154
+ // The SAME filter the engine applies before it ever calls `fn`
155
+ // (`src/hooks/policy-registry.ts`): a policy whose `match` does not cover this
156
+ // event or this tool is never RUN on a real machine, so running it here and
157
+ // folding its verdict into the overall decision reported a DENY the daemon
158
+ // would never have produced. `--event`/`--tool` only shaped the synthetic ctx;
159
+ // they did not gate registration. A `match: { events: ["PostToolUse"],
160
+ // toolNames: ["Write"] }` policy printed a red DENY under the default
161
+ // `--event PreToolUse`, and `--expect deny` passed in CI, while the machine
162
+ // allowed the command.
163
+ const m = p.match ?? {};
164
+ const evs = m.events ?? [];
165
+ const tools = m.toolNames ?? [];
166
+ if (evs.length > 0 && !evs.includes(ctx.eventType)) {
167
+ out.push({ name: p.name ?? "(unnamed)", description: p.description ?? null,
168
+ decision: "skipped", reason: `match.events does not include ${ctx.eventType}` });
169
+ continue;
170
+ }
171
+ if (tools.length > 0 && (!ctx.toolName || !tools.includes(ctx.toolName))) {
172
+ out.push({ name: p.name ?? "(unnamed)", description: p.description ?? null,
173
+ decision: "skipped", reason: `match.toolNames does not include ${ctx.toolName ?? "(none)"}` });
174
+ continue;
175
+ }
176
+ try {
177
+ const r = await p.fn(ctx);
178
+ out.push({ name: p.name ?? "(unnamed)", description: p.description ?? null,
179
+ decision: r?.decision ?? "allow", reason: r?.reason ?? null });
180
+ } catch (e) {
181
+ out.push({ name: p.name ?? "(unnamed)", error: String(e && e.message || e) });
182
+ }
183
+ }
184
+ process.stdout.write(JSON.stringify({ policies: out }));
185
+ """
186
+
187
+
188
+ @dataclass
189
+ class PolicyRun:
190
+ ok: bool
191
+ results: List[Dict[str, Any]]
192
+ error: str = ""
193
+
194
+ @property
195
+ def decision(self) -> str:
196
+ """The strictest decision any policy returned.
197
+
198
+ deny beats instruct beats allow, because that is how a fleet of policies
199
+ composes: one refusal is a refusal regardless of what the others said.
200
+ """
201
+ decisions = [r.get("decision") for r in self.results if "decision" in r]
202
+ for level in ("deny", "instruct"):
203
+ if level in decisions:
204
+ return level
205
+ return "allow"
206
+
207
+ def to_dict(self) -> Dict[str, Any]:
208
+ return {"ok": self.ok, "decision": self.decision,
209
+ "policies": self.results, "error": self.error}
210
+
211
+
212
+ def run_policy(
213
+ source: str,
214
+ *,
215
+ tool: str = "Bash",
216
+ command: Optional[str] = None,
217
+ file_path: Optional[str] = None,
218
+ event: str = "PreToolUse",
219
+ tool_input: Optional[Dict[str, Any]] = None,
220
+ ) -> PolicyRun:
221
+ """Execute a policy against one synthetic context and report each verdict.
222
+
223
+ Runs in a temp directory with the shim beside it, so the policy's
224
+ `import ... from "failproofai"` resolves without a node_modules anywhere.
225
+ Nothing is installed and nothing outside the temp directory is written.
226
+
227
+ This is a DRY RUN, not the enforcement path: it proves the policy parses,
228
+ registers, and returns a decision for the input described. It cannot prove
229
+ the daemon will feed it the same context.
230
+ """
231
+ if not node_available():
232
+ return PolicyRun(ok=False, results=[],
233
+ error="node was not found on PATH, so the policy could not be run")
234
+
235
+ payload: Dict[str, Any] = dict(tool_input or {})
236
+ if command is not None:
237
+ payload.setdefault("command", command)
238
+ if file_path is not None:
239
+ payload.setdefault("file_path", file_path)
240
+ ctx = {"eventType": event, "toolName": tool, "toolInput": payload, "payload": payload}
241
+
242
+ with tempfile.TemporaryDirectory() as tmp:
243
+ # The shim goes in `node_modules/failproofai/` rather than beside the
244
+ # policy, so the bare specifier a real policy writes —
245
+ # `import { deny } from "failproofai"` — resolves by node's ordinary
246
+ # lookup. The policy under test is then byte-identical to the one that
247
+ # gets published; an import map would have meant testing a rewritten
248
+ # file, and import-map support also varies by node version.
249
+ pkg = os.path.join(tmp, "node_modules", "failproofai")
250
+ os.makedirs(pkg)
251
+ with open(os.path.join(pkg, "package.json"), "w", encoding="utf-8") as fh:
252
+ fh.write(json.dumps({"name": "failproofai", "version": "0.0.0",
253
+ "type": "module", "main": "index.mjs",
254
+ "exports": "./index.mjs"}))
255
+ with open(os.path.join(pkg, "index.mjs"), "w", encoding="utf-8") as fh:
256
+ fh.write(_SHIM)
257
+ for name, body in (("policy.mjs", source), ("run.mjs", _RUNNER)):
258
+ with open(os.path.join(tmp, name), "w", encoding="utf-8") as fh:
259
+ fh.write(body)
260
+ with open(os.path.join(tmp, "package.json"), "w", encoding="utf-8") as fh:
261
+ fh.write(json.dumps({"type": "module"}))
262
+ try:
263
+ proc = subprocess.run(
264
+ ["node", "run.mjs", json.dumps(ctx)],
265
+ cwd=tmp, capture_output=True, text=True, timeout=_TIMEOUT_SECS,
266
+ )
267
+ except subprocess.TimeoutExpired:
268
+ return PolicyRun(ok=False, results=[],
269
+ error=f"the policy did not finish within {_TIMEOUT_SECS}s")
270
+ except OSError as exc:
271
+ return PolicyRun(ok=False, results=[], error=f"could not run node: {exc}")
272
+
273
+ if proc.returncode != 0:
274
+ detail = (proc.stderr or proc.stdout or "").strip().splitlines()
275
+ return PolicyRun(ok=False, results=[],
276
+ error="\n".join(detail[:6]) or "the policy failed to run")
277
+ try:
278
+ data = json.loads(proc.stdout or "{}")
279
+ except json.JSONDecodeError:
280
+ return PolicyRun(ok=False, results=[], error="the policy produced no readable result")
281
+
282
+ results = data.get("policies") or []
283
+ if not results:
284
+ return PolicyRun(
285
+ ok=False, results=[],
286
+ error=("the file registered no policies — a policy calls "
287
+ "`customPolicies.add({...})`; check it does, and that the call runs "
288
+ "at import time"),
289
+ )
290
+ return PolicyRun(ok=True, results=results)
File without changes