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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +6 -6
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +5 -4
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -3
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js +4 -4
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +4 -4
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +66 -22
- package/.next/standalone/.next/server/app/policies/page.js +5 -4
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +2 -2
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +2 -2
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__1qzqcio._.js → [externals]__0e32-7c._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__1s462yl._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p8poxd._.js → [root-of-the-server]__0igxt32._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0neqlu-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t9oqnn._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +3 -0
- package/.next/standalone/.next/server/chunks/_185gyk9._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +5 -6
- package/.next/standalone/.next/server/chunks/lib_telemetry-id_ts_1k8gj9o._.js +3 -0
- 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
- 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
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0xbw3bu._.js +12 -0
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0spkm68._.js → [root-of-the-server]__07gm3zl._.js} +21 -20
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07tbbf3._.js → [root-of-the-server]__0zig5sv._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__192nhvh._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0lvyuv4._.js → _00e9tnf._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0u5zauv._.js → _12l4a_k._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +13 -13
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_08o7bqo._.js +10 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware.js +1 -1
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +71 -27
- package/.next/standalone/.next/static/chunks/{37fcpc36x9gpe.js → 0eqky-7n7dgdm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1ide1wl2f7zi1.js → 0sa9v5mhc8861.js} +13 -13
- package/.next/standalone/.next/static/chunks/1mfzsja6ftim5.css +2 -0
- package/.next/standalone/.next/static/chunks/{3k7mxbp9hiom0.js → 1nuqytbta44af.js} +1 -1
- package/.next/standalone/.next/static/chunks/1sl2xmn9inx3y.js +6 -0
- package/.next/standalone/.next/static/chunks/{2_2lhuojq2xop.js → 2aawsjld9ied_.js} +1 -1
- package/.next/standalone/.next/static/chunks/2gn7ut4svofm2.js +1 -0
- package/.next/standalone/.next/static/chunks/{31s1smp-g7gks.js → 2hs7ycvsav7v2.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0k5050ezjvv49.js → 2i2yt3hzjredm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3c808qrfcq0im.js → 35xg6retpe6rp.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2zpp1we2jv6tb.js → 3y54nw0i7dsjl.js} +1 -1
- package/.next/standalone/SECURITY.md +5 -4
- package/.next/standalone/app/actions/get-hooks-config.ts +108 -17
- package/.next/standalone/app/actions/pack-actions.ts +153 -0
- package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
- package/.next/standalone/app/audit/_components/share-templates.ts +13 -13
- package/.next/standalone/app/policies/hooks-client.tsx +320 -14
- package/.next/standalone/components/navbar.tsx +2 -28
- package/.next/standalone/components/reach-developers.tsx +8 -1
- package/.next/standalone/docs-old/agenteye/python-sdk-skill.mdx +1 -1
- package/.next/standalone/docs-old/agenteye/python-sdk.mdx +59 -35
- package/.next/standalone/docs-old/i18n/README.ar.md +1 -0
- package/.next/standalone/docs-old/i18n/README.de.md +1 -0
- package/.next/standalone/docs-old/i18n/README.es.md +1 -0
- package/.next/standalone/docs-old/i18n/README.fr.md +1 -0
- package/.next/standalone/docs-old/i18n/README.he.md +1 -0
- package/.next/standalone/docs-old/i18n/README.hi.md +1 -0
- package/.next/standalone/docs-old/i18n/README.it.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ja.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ko.md +1 -0
- package/.next/standalone/docs-old/i18n/README.pt-br.md +1 -0
- package/.next/standalone/docs-old/i18n/README.ru.md +1 -0
- package/.next/standalone/docs-old/i18n/README.tr.md +1 -0
- package/.next/standalone/docs-old/i18n/README.vi.md +1 -0
- package/.next/standalone/docs-old/i18n/README.zh.md +1 -0
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +431 -0
- package/.next/standalone/fp-cloud-cli/LICENSE +42 -0
- package/.next/standalone/fp-cloud-cli/MANIFEST.in +10 -0
- package/.next/standalone/fp-cloud-cli/README.md +259 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/__init__.py +10 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/__main__.py +4 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +64 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_context.py +332 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics.py +432 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics_config.py +77 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/analytics_registry.py +83 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/app.py +492 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/auth.py +160 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +1694 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/__init__.py +0 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +214 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +407 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +445 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +1054 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/auth_cmds.py +512 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/errors_cmds.py +190 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/evals_cmds.py +161 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +159 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +416 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/guardrails_cmds.py +148 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +472 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +407 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +63 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +319 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +499 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +378 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +151 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +150 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +35 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +404 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/config.py +330 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/dates.py +78 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +345 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +98 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/models.py +891 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/orgs.py +30 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +6593 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +208 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +290 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/py.typed +0 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/select.py +322 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +53 -0
- package/.next/standalone/fp-cloud-cli/pyproject.toml +106 -0
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +302 -0
- package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +8 -0
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +254 -0
- package/.next/standalone/fp-cloud-cli/tests/__init__.py +0 -0
- package/.next/standalone/fp-cloud-cli/tests/conftest.py +92 -0
- package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +644 -0
- package/.next/standalone/fp-cloud-cli/tests/test_analytics.py +385 -0
- package/.next/standalone/fp-cloud-cli/tests/test_audits.py +950 -0
- package/.next/standalone/fp-cloud-cli/tests/test_auth.py +98 -0
- package/.next/standalone/fp-cloud-cli/tests/test_auth_mode.py +272 -0
- package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +118 -0
- package/.next/standalone/fp-cloud-cli/tests/test_client.py +299 -0
- package/.next/standalone/fp-cloud-cli/tests/test_commands.py +814 -0
- package/.next/standalone/fp-cloud-cli/tests/test_config.py +99 -0
- package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +340 -0
- package/.next/standalone/fp-cloud-cli/tests/test_dates.py +76 -0
- package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +481 -0
- package/.next/standalone/fp-cloud-cli/tests/test_facets.py +170 -0
- package/.next/standalone/fp-cloud-cli/tests/test_failproofai_home.py +722 -0
- package/.next/standalone/fp-cloud-cli/tests/test_fp_home_contract.py +198 -0
- package/.next/standalone/fp-cloud-cli/tests/test_hardening.py +186 -0
- package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +127 -0
- package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +496 -0
- package/.next/standalone/fp-cloud-cli/tests/test_list.py +59 -0
- package/.next/standalone/fp-cloud-cli/tests/test_multivalue.py +183 -0
- package/.next/standalone/fp-cloud-cli/tests/test_no_customer_identifiers.py +168 -0
- package/.next/standalone/fp-cloud-cli/tests/test_operator.py +492 -0
- package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +692 -0
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +1732 -0
- package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +191 -0
- package/.next/standalone/fp-cloud-cli/tests/test_readme_matches_reality.py +125 -0
- package/.next/standalone/fp-cloud-cli/tests/test_review_fixes.py +161 -0
- package/.next/standalone/fp-cloud-cli/tests/test_telemetry_completeness.py +90 -0
- package/.next/standalone/fp-cloud-cli/tests/test_usage.py +115 -0
- package/.next/standalone/fp-cloud-cli/tests/test_v1_origin_diagnostic.py +70 -0
- package/.next/standalone/fp-cloud-cli/tests/test_v1_routing.py +214 -0
- package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +104 -0
- package/.next/standalone/fp-cloud-cli/uv.lock +552 -0
- package/.next/standalone/package.json +29 -22
- package/.next/standalone/sdk/python/CHANGELOG.md +70 -0
- package/.next/standalone/sdk/python/LICENSE +42 -0
- package/.next/standalone/sdk/python/MANIFEST.in +21 -0
- package/.next/standalone/sdk/python/README.md +466 -0
- package/.next/standalone/sdk/python/docs/README.md +249 -0
- package/.next/standalone/sdk/python/docs/_shared/README.md +43 -0
- package/.next/standalone/sdk/python/docs/_shared/__init__.py +195 -0
- package/.next/standalone/sdk/python/docs/crewai/README.md +328 -0
- package/.next/standalone/sdk/python/docs/crewai/examples/quickstart.py +61 -0
- package/.next/standalone/sdk/python/docs/crewai/examples/research_crew.py +92 -0
- package/.next/standalone/sdk/python/docs/langgraph/README.md +481 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/quickstart.py +51 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/research_agent.py +86 -0
- package/.next/standalone/sdk/python/docs/langgraph/examples/supervisor_handoff.py +83 -0
- package/.next/standalone/sdk/python/docs/llama_index/README.md +332 -0
- package/.next/standalone/sdk/python/docs/llama_index/examples/quickstart.py +62 -0
- package/.next/standalone/sdk/python/docs/llama_index/examples/research_agent.py +93 -0
- package/.next/standalone/sdk/python/docs/manual/README.md +491 -0
- package/.next/standalone/sdk/python/docs/manual/examples/quickstart.py +57 -0
- package/.next/standalone/sdk/python/docs/manual/examples/research_agent.py +133 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/README.md +323 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/examples/quickstart.py +48 -0
- package/.next/standalone/sdk/python/docs/pydantic_ai/examples/research_agent.py +94 -0
- package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +131 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_context.py +218 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_environment.py +78 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_events.py +752 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_resolver.py +118 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_runtime.py +21 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_schema.py +317 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_scopes.py +422 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_writer.py +750 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +274 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/_compat.py +247 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/_core.py +1076 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/crewai.py +1669 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/langchain.py +2151 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/llama_index.py +1588 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/pydantic_ai.py +977 -0
- package/.next/standalone/sdk/python/failproofai_sdk/py.typed +0 -0
- package/.next/standalone/sdk/python/pyproject.toml +92 -0
- package/.next/standalone/sdk/python/skill/SKILL.md +399 -0
- package/.next/standalone/sdk/python/skill/agents/openai.yaml +8 -0
- package/.next/standalone/sdk/python/skill/references/events.md +234 -0
- package/.next/standalone/sdk/python/skill/references/frameworks.md +314 -0
- package/.next/standalone/sdk/python/skill/references/install.md +100 -0
- package/.next/standalone/sdk/python/skill/references/integration.md +352 -0
- package/.next/standalone/sdk/python/tests/__init__.py +0 -0
- package/.next/standalone/sdk/python/tests/conftest.py +169 -0
- package/.next/standalone/sdk/python/tests/integrations/__init__.py +6 -0
- package/.next/standalone/sdk/python/tests/integrations/test_crewai.py +1299 -0
- package/.next/standalone/sdk/python/tests/integrations/test_langchain.py +2338 -0
- package/.next/standalone/sdk/python/tests/integrations/test_llama_index.py +1576 -0
- package/.next/standalone/sdk/python/tests/integrations/test_pydantic_ai.py +1102 -0
- package/.next/standalone/sdk/python/tests/test_context.py +410 -0
- package/.next/standalone/sdk/python/tests/test_docs.py +411 -0
- package/.next/standalone/sdk/python/tests/test_durability.py +1352 -0
- package/.next/standalone/sdk/python/tests/test_encoding.py +571 -0
- package/.next/standalone/sdk/python/tests/test_integrations.py +1127 -0
- package/.next/standalone/sdk/python/tests/test_no_customer_identifiers.py +214 -0
- package/.next/standalone/sdk/python/tests/test_packaging.py +185 -0
- package/.next/standalone/sdk/python/tests/test_resolver_umbrella.py +178 -0
- package/.next/standalone/sdk/python/tests/test_scopes.py +487 -0
- package/.next/standalone/sdk/python/tests/test_sdk.py +923 -0
- package/.next/standalone/sdk/python/tests/test_server_contract.py +650 -0
- package/.next/standalone/sdk/python/tests/test_site_docs.py +371 -0
- package/.next/standalone/sdk/python/tests/test_skill_snippets.py +165 -0
- package/.next/standalone/sdk/python/tests/test_spool_contract.py +287 -0
- package/.next/standalone/sdk/python/tests/test_spool_creation.py +360 -0
- package/.next/standalone/sdk/python/tests/test_wire_format.py +279 -0
- package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +378 -0
- package/.next/standalone/sdk/python/uv.lock +6005 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +77 -21
- package/bin/failproofai.mjs +145 -11
- package/dist/cli.mjs +4482 -2205
- package/dist/index.js +5 -5
- package/dist/worker.mjs +2399 -1978
- package/package.json +29 -22
- package/policy-pack/SHA256SUMS +2 -0
- package/policy-pack/failproofai-pack.json +732 -0
- package/policy-pack/failproofai-pack.mjs +2017 -0
- package/scripts/build-policy-pack.mjs +121 -0
- package/scripts/changelog-section.py +77 -0
- package/scripts/prune-standalone.mjs +3 -0
- package/scripts/python-version.py +232 -0
- package/scripts/translate-docs/cli.ts +21 -0
- package/scripts/translate-docs/mintlify-nav.ts +85 -13
- package/scripts/translate-docs/translator.ts +66 -11
- package/src/audit/cache.ts +40 -3
- package/src/audit/cli.ts +1 -1
- package/src/audit/index.ts +1 -1
- package/src/audit/replay.ts +89 -3
- package/src/audit/report.ts +5 -5
- package/src/audit/schedule-cli.ts +49 -53
- package/src/hooks/builtin-policies.ts +217 -579
- package/src/hooks/cloud-enrollment-cli.ts +66 -27
- package/src/hooks/cloud-managed-policies.ts +6 -1
- package/src/hooks/configure-wizard.ts +1 -1
- package/src/hooks/custom-hooks-loader.ts +89 -6
- package/src/hooks/fp-home.ts +92 -4
- package/src/hooks/fp-reset.ts +26 -2
- package/src/hooks/handler.ts +181 -11
- package/src/hooks/harness-cli.ts +79 -25
- package/src/hooks/hook-activity-store.ts +13 -3
- package/src/hooks/install-prompt.ts +25 -3
- package/src/hooks/manager.ts +418 -172
- package/src/hooks/migrations.ts +2 -2
- package/src/hooks/pack-cli.ts +505 -0
- package/src/hooks/pack-failclosed.ts +189 -0
- package/src/hooks/pack-manifest.ts +344 -0
- package/src/hooks/pack-store.ts +930 -0
- package/src/hooks/policy-catalog.ts +535 -0
- package/src/hooks/policy-evaluator.ts +24 -16
- package/src/hooks/policy-presets.ts +4 -2
- package/src/hooks/policy-registry.ts +8 -2
- package/src/hooks/policy-types.ts +26 -0
- package/src/hooks/session-pause-cli.ts +33 -9
- package/src/hooks/tui.ts +570 -0
- package/src/hooks/types.ts +15 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0agdw2a._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d1u_na._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +0 -3
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1ger0a2._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_1iiwm5e._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kc8_v5._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kgy9da._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mhqk5a._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pltpn8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qkcyf-._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w0x_ok._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_dfocn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bfex62._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kzapg5._.js +0 -6
- package/.next/standalone/.next/static/chunks/36fw6yk1s6x_f.js +0 -1
- package/.next/standalone/.next/static/chunks/3tzq0260n17ae.js +0 -6
- package/.next/standalone/.next/static/chunks/3wq9x0c-7ld5d.css +0 -2
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZklXxyA5ygsWSQz2u_zii → nwbyWT06Ud0XdXOrVJyvS}/_clientMiddlewareManifest.js +0 -0
- /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
|