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,345 @@
|
|
|
1
|
+
"""The logic behind `fp policies` and `fp fleet`, with no HTTP in it.
|
|
2
|
+
|
|
3
|
+
Everything here is pure so it can be tested without a server, because the two
|
|
4
|
+
things most likely to lose someone's work are decided here rather than in a
|
|
5
|
+
handler: what a deploy's resulting policy set is, and whether somebody else
|
|
6
|
+
wrote while we were deciding.
|
|
7
|
+
|
|
8
|
+
## Why a diff at all
|
|
9
|
+
|
|
10
|
+
`PUT /enforcement/deployments/{id}` is a FULL REPLACE. Send `{"policies": [a]}`
|
|
11
|
+
to a machine running `[a, b, c]` and it now runs `[a]` — permanently, with a
|
|
12
|
+
200 and no warning. The dashboard never exposes that as a form for exactly this
|
|
13
|
+
reason (`app/(dashboard)/[org]/enforcement/page.tsx`: "a form that asks you to
|
|
14
|
+
re-pick a machine and re-tick its policies silently drops whatever you forget
|
|
15
|
+
to tick"). It edits the machine's own current set instead.
|
|
16
|
+
|
|
17
|
+
So `--add`/`--remove` are the CLI's equivalent: read the current set, apply the
|
|
18
|
+
delta, write the whole thing back. `--set` remains for the declarative case,
|
|
19
|
+
and is the only way to express "exactly these, drop the rest".
|
|
20
|
+
|
|
21
|
+
## Why the race check
|
|
22
|
+
|
|
23
|
+
There is no optimistic locking on that endpoint. The dashboard detects a
|
|
24
|
+
collision AFTER the fact by checking the returned generation is exactly
|
|
25
|
+
`base + 1` (`lib/enforcementFleet.ts`, `staleness()`). The same check here is
|
|
26
|
+
what stops two operators silently overwriting each other — the CLI refuses and
|
|
27
|
+
re-reads rather than reporting a success that erased somebody.
|
|
28
|
+
"""
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import re
|
|
32
|
+
import sys
|
|
33
|
+
from dataclasses import dataclass
|
|
34
|
+
from typing import Dict, Iterable, List, Optional, Sequence, Tuple
|
|
35
|
+
|
|
36
|
+
from .errors import ApiError
|
|
37
|
+
from .models import PolicyRef, PolicyVersion
|
|
38
|
+
|
|
39
|
+
VALID_EFFECTS = ("enforce", "observe")
|
|
40
|
+
|
|
41
|
+
#: `id`, `id@3`, `id:observe`, `id@3:observe`. The id charset mirrors the
|
|
42
|
+
#: server's `safe_identifier`, so a ref this accepts is one the server will too
|
|
43
|
+
#: — a rejection should come from the policy not existing, not from parsing.
|
|
44
|
+
_REF = re.compile(r"^(?P<id>[A-Za-z0-9._-]{1,128})(?:@(?P<version>\d+))?(?:[:](?P<effect>[a-z]+))?$")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RefError(ValueError):
|
|
48
|
+
"""A malformed `--add` / `--remove` / `--set` token, with the reason."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RefUsageError(RefError):
|
|
52
|
+
"""A RefError the caller can fix by retyping the command.
|
|
53
|
+
|
|
54
|
+
Split out so the command layer can exit 2 (usage) rather than 1 (API error)
|
|
55
|
+
for these, which is what the documented exit-code table promises and what
|
|
56
|
+
`--since` and `--expect` in these same commands already do. A malformed
|
|
57
|
+
token, two flags that contradict each other, or a path that is not readable
|
|
58
|
+
text are all "you typed it wrong" — not "the server said no".
|
|
59
|
+
|
|
60
|
+
Subclasses ``RefError`` so every existing caller and test that catches the
|
|
61
|
+
base class keeps working unchanged.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def parse_ref(token: str) -> Tuple[str, Optional[int], Optional[str]]:
|
|
66
|
+
"""``"id@2:observe"`` → ``("id", 2, "observe")``; omitted parts are None.
|
|
67
|
+
|
|
68
|
+
Version and effect are resolved later — ``None`` means "whatever is current",
|
|
69
|
+
which is not the same as a default, because for an existing deployment the
|
|
70
|
+
current value is the deployed one rather than the newest one.
|
|
71
|
+
"""
|
|
72
|
+
token = token.strip()
|
|
73
|
+
if not token:
|
|
74
|
+
raise RefUsageError("empty policy reference")
|
|
75
|
+
m = _REF.match(token)
|
|
76
|
+
if not m:
|
|
77
|
+
raise RefUsageError(
|
|
78
|
+
f"{token!r} is not a policy reference — expected id, id@version, "
|
|
79
|
+
"id:effect or id@version:effect"
|
|
80
|
+
)
|
|
81
|
+
effect = m.group("effect")
|
|
82
|
+
if effect is not None and effect not in VALID_EFFECTS:
|
|
83
|
+
raise RefUsageError(
|
|
84
|
+
f"{token!r} has effect {effect!r}; expected one of {', '.join(VALID_EFFECTS)}"
|
|
85
|
+
)
|
|
86
|
+
version = m.group("version")
|
|
87
|
+
return m.group("id"), (int(version) if version is not None else None), effect
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class DeployPlan:
|
|
92
|
+
"""The resulting set, and how it differs from what the machine runs now.
|
|
93
|
+
|
|
94
|
+
`result` is what will be PUT — the whole set, because that is what the
|
|
95
|
+
endpoint takes. The three lists exist to be shown to a human before it is.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
machine_id: str
|
|
99
|
+
base: Optional[int]
|
|
100
|
+
result: List[PolicyRef]
|
|
101
|
+
added: List[PolicyRef]
|
|
102
|
+
removed: List[PolicyRef]
|
|
103
|
+
changed: List[Tuple[PolicyRef, PolicyRef]]
|
|
104
|
+
unchanged: List[PolicyRef]
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def is_noop(self) -> bool:
|
|
108
|
+
return not (self.added or self.removed or self.changed)
|
|
109
|
+
|
|
110
|
+
def to_dict(self) -> Dict[str, object]:
|
|
111
|
+
return {
|
|
112
|
+
"machineId": self.machine_id,
|
|
113
|
+
"base": self.base,
|
|
114
|
+
"result": [p.to_dict() for p in self.result],
|
|
115
|
+
"added": [p.to_dict() for p in self.added],
|
|
116
|
+
"removed": [p.to_dict() for p in self.removed],
|
|
117
|
+
"changed": [{"from": a.to_dict(), "to": b.to_dict()} for a, b in self.changed],
|
|
118
|
+
"unchanged": [p.to_dict() for p in self.unchanged],
|
|
119
|
+
"noop": self.is_noop,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def latest_versions(policies: Iterable[PolicyVersion]) -> Dict[str, int]:
|
|
124
|
+
"""`{policy_id: newest published version}`, ignoring archived policies."""
|
|
125
|
+
out: Dict[str, int] = {}
|
|
126
|
+
for p in policies:
|
|
127
|
+
if p.archived:
|
|
128
|
+
continue
|
|
129
|
+
if p.version > out.get(p.id, 0):
|
|
130
|
+
out[p.id] = p.version
|
|
131
|
+
return out
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def disabled_ids(policies: Iterable[PolicyVersion]) -> set:
|
|
135
|
+
"""Policies the server will refuse to deploy.
|
|
136
|
+
|
|
137
|
+
The server rejects these anyway, but only after the CLI has drawn a plan and
|
|
138
|
+
asked the operator to confirm it — so the last thing on screen is a change
|
|
139
|
+
that cannot happen, under a prompt that implied it could. Everything else
|
|
140
|
+
the plan depends on (the machine exists, the policy exists) is already
|
|
141
|
+
checked before the plan is built; this was the one gap.
|
|
142
|
+
"""
|
|
143
|
+
return {p.id for p in policies if p.disabled and not p.archived}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def resolve_ref(
|
|
147
|
+
token: str,
|
|
148
|
+
*,
|
|
149
|
+
latest: Dict[str, int],
|
|
150
|
+
current: Dict[str, PolicyRef],
|
|
151
|
+
disabled: Optional[set] = None,
|
|
152
|
+
) -> PolicyRef:
|
|
153
|
+
"""Turn one `--add`/`--set` token into a concrete `PolicyRef`.
|
|
154
|
+
|
|
155
|
+
Version: explicit wins; else the version already deployed (so `--add` on a
|
|
156
|
+
policy the machine already runs is a no-op rather than a silent upgrade);
|
|
157
|
+
else the newest published.
|
|
158
|
+
|
|
159
|
+
Effect: explicit wins; else the deployed effect; else `enforce`, matching
|
|
160
|
+
the server's own default for an omitted effect.
|
|
161
|
+
"""
|
|
162
|
+
pid, version, effect = parse_ref(token)
|
|
163
|
+
if disabled and pid in disabled and pid not in current:
|
|
164
|
+
raise RefError(
|
|
165
|
+
f"{pid!r} is disabled — `fp policies enable {pid}` first, or the machine "
|
|
166
|
+
"would be sent a deployment the server refuses"
|
|
167
|
+
)
|
|
168
|
+
existing = current.get(pid)
|
|
169
|
+
if version is None:
|
|
170
|
+
version = existing.version if existing else latest.get(pid)
|
|
171
|
+
if version is None:
|
|
172
|
+
raise RefError(
|
|
173
|
+
f"no published policy named {pid!r} — run `fp policies list` to see what exists"
|
|
174
|
+
)
|
|
175
|
+
if effect is None:
|
|
176
|
+
effect = existing.effect if existing else "enforce"
|
|
177
|
+
return PolicyRef(id=pid, version=version, effect=effect)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def plan_deploy(
|
|
181
|
+
machine_id: str,
|
|
182
|
+
*,
|
|
183
|
+
current: Optional[Sequence[PolicyRef]],
|
|
184
|
+
base: Optional[int],
|
|
185
|
+
add: Sequence[str] = (),
|
|
186
|
+
remove: Sequence[str] = (),
|
|
187
|
+
replace: Optional[Sequence[str]] = None,
|
|
188
|
+
latest: Optional[Dict[str, int]] = None,
|
|
189
|
+
disabled: Optional[set] = None,
|
|
190
|
+
) -> DeployPlan:
|
|
191
|
+
"""Compute the full resulting set, plus the diff to show before writing.
|
|
192
|
+
|
|
193
|
+
`replace` (`--set`) is exclusive with `add`/`remove`: mixing "these exactly"
|
|
194
|
+
with "these as well" has no single obvious reading, and guessing one would
|
|
195
|
+
be guessing about somebody's fleet.
|
|
196
|
+
"""
|
|
197
|
+
latest = latest or {}
|
|
198
|
+
current_list = list(current or [])
|
|
199
|
+
current_map = {p.id: p for p in current_list}
|
|
200
|
+
|
|
201
|
+
if replace is not None:
|
|
202
|
+
if add or remove:
|
|
203
|
+
raise RefUsageError(
|
|
204
|
+
"--set replaces the whole set; it cannot be combined with --add/--remove"
|
|
205
|
+
)
|
|
206
|
+
result_map = {}
|
|
207
|
+
for token in replace:
|
|
208
|
+
ref = resolve_ref(token, latest=latest, current=current_map, disabled=disabled)
|
|
209
|
+
result_map[ref.id] = ref
|
|
210
|
+
else:
|
|
211
|
+
result_map = dict(current_map)
|
|
212
|
+
for token in remove:
|
|
213
|
+
pid, _, _ = parse_ref(token)
|
|
214
|
+
if pid not in result_map:
|
|
215
|
+
raise RefError(
|
|
216
|
+
f"{pid!r} is not deployed to {machine_id} — nothing to remove"
|
|
217
|
+
)
|
|
218
|
+
del result_map[pid]
|
|
219
|
+
for token in add:
|
|
220
|
+
ref = resolve_ref(token, latest=latest, current=current_map, disabled=disabled)
|
|
221
|
+
result_map[ref.id] = ref
|
|
222
|
+
|
|
223
|
+
result = sorted(result_map.values(), key=lambda p: p.id)
|
|
224
|
+
added, removed, changed, unchanged = [], [], [], []
|
|
225
|
+
for pid, ref in sorted(result_map.items()):
|
|
226
|
+
was = current_map.get(pid)
|
|
227
|
+
if was is None:
|
|
228
|
+
added.append(ref)
|
|
229
|
+
elif (was.version, was.effect) != (ref.version, ref.effect):
|
|
230
|
+
changed.append((was, ref))
|
|
231
|
+
else:
|
|
232
|
+
unchanged.append(ref)
|
|
233
|
+
for pid, was in sorted(current_map.items()):
|
|
234
|
+
if pid not in result_map:
|
|
235
|
+
removed.append(was)
|
|
236
|
+
|
|
237
|
+
return DeployPlan(
|
|
238
|
+
machine_id=machine_id,
|
|
239
|
+
base=base,
|
|
240
|
+
result=result,
|
|
241
|
+
added=added,
|
|
242
|
+
removed=removed,
|
|
243
|
+
changed=changed,
|
|
244
|
+
unchanged=unchanged,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def check_race(base: Optional[int], returned: int) -> None:
|
|
249
|
+
"""Raise when a deploy landed on top of somebody else's.
|
|
250
|
+
|
|
251
|
+
`base` is the generation read before the write. A clean write is exactly
|
|
252
|
+
`base + 1`; anything else means another writer got in between, and their
|
|
253
|
+
change is already gone — a full replace does not merge. Reporting success
|
|
254
|
+
here is how the CLI would become the easiest way to silently overwrite a
|
|
255
|
+
colleague.
|
|
256
|
+
"""
|
|
257
|
+
if base is None:
|
|
258
|
+
return
|
|
259
|
+
if returned != base + 1:
|
|
260
|
+
raise ApiError(
|
|
261
|
+
f"deployment {returned} landed where {base + 1} was expected — someone "
|
|
262
|
+
"else deployed to this machine while this command was deciding, and a "
|
|
263
|
+
"deploy REPLACES the whole set rather than merging.",
|
|
264
|
+
hint="re-run `fp fleet show <machine>` to see the current set, then deploy again",
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def read_source(
|
|
269
|
+
value: Optional[str],
|
|
270
|
+
*,
|
|
271
|
+
stdin=None,
|
|
272
|
+
isatty: Optional[bool] = None,
|
|
273
|
+
prompt=None,
|
|
274
|
+
) -> str:
|
|
275
|
+
"""Resolve policy source from a path, `@path`, `-`, a pipe, or a paste.
|
|
276
|
+
|
|
277
|
+
The five shapes exist because the thing being supplied is a file that people
|
|
278
|
+
have in five different places: on disk, in a pipeline, in a heredoc, or on
|
|
279
|
+
the clipboard. Refusing the clipboard would mean "save it to a file first"
|
|
280
|
+
for the most common one-off case.
|
|
281
|
+
|
|
282
|
+
A bare `-` and a piped stdin are the same read; the difference is only
|
|
283
|
+
whether the user said so. On a TTY with nothing given we prompt, because
|
|
284
|
+
silently blocking on stdin is indistinguishable from a hang.
|
|
285
|
+
"""
|
|
286
|
+
stream = sys.stdin if stdin is None else stdin
|
|
287
|
+
tty = stream.isatty() if isatty is None else isatty
|
|
288
|
+
|
|
289
|
+
if value == "-":
|
|
290
|
+
return _checked(_read_stream(stream))
|
|
291
|
+
if value:
|
|
292
|
+
path = value[1:] if value.startswith("@") else value
|
|
293
|
+
try:
|
|
294
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
295
|
+
return _checked(fh.read())
|
|
296
|
+
except FileNotFoundError:
|
|
297
|
+
raise RefUsageError(f"no such file: {path}")
|
|
298
|
+
except UnicodeDecodeError:
|
|
299
|
+
# NOT an OSError, so the handler below never saw it and the
|
|
300
|
+
# decode error escaped as a raw traceback. `_checked` cannot
|
|
301
|
+
# catch this either: it inspects text, and there is no text yet.
|
|
302
|
+
raise RefUsageError(_NOT_TEXT.format(what=path))
|
|
303
|
+
except OSError as exc:
|
|
304
|
+
raise RefUsageError(f"cannot read {path}: {exc}")
|
|
305
|
+
if not tty:
|
|
306
|
+
return _checked(_read_stream(stream))
|
|
307
|
+
if prompt is not None:
|
|
308
|
+
prompt()
|
|
309
|
+
return _checked(_read_stream(stream))
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
#: Said the same way whether the bytes arrived by path or down a pipe.
|
|
313
|
+
_NOT_TEXT = (
|
|
314
|
+
"{what} is not UTF-8 text — this looks like a binary file rather than a policy"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _read_stream(stream) -> str:
|
|
319
|
+
"""Read stdin, turning undecodable bytes into a sentence.
|
|
320
|
+
|
|
321
|
+
``sys.stdin`` decodes as it reads, so piping a binary file raises
|
|
322
|
+
``UnicodeDecodeError`` here rather than returning bytes ``_checked`` could
|
|
323
|
+
inspect — which is how `cat rule.png | fp policies publish x` printed a
|
|
324
|
+
traceback instead of the NUL-byte message written for exactly that mistake.
|
|
325
|
+
"""
|
|
326
|
+
try:
|
|
327
|
+
return stream.read()
|
|
328
|
+
except UnicodeDecodeError:
|
|
329
|
+
raise RefUsageError(_NOT_TEXT.format(what="the input"))
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _checked(text: str) -> str:
|
|
333
|
+
"""Reject bytes the store cannot hold, with a message that says what happened.
|
|
334
|
+
|
|
335
|
+
A NUL byte in policy source reaches Postgres and comes back as a bare
|
|
336
|
+
"database error" — a raw internal failure shown to somebody who most likely
|
|
337
|
+
pointed the command at a binary file by mistake. The server ought to refuse
|
|
338
|
+
it; until it does, refusing here turns an unexplained 500 into a sentence.
|
|
339
|
+
"""
|
|
340
|
+
if "\x00" in text:
|
|
341
|
+
raise RefUsageError(
|
|
342
|
+
"policy source contains a NUL byte — this looks like a binary file "
|
|
343
|
+
"rather than a policy"
|
|
344
|
+
)
|
|
345
|
+
return text
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""CLI exception hierarchy.
|
|
2
|
+
|
|
3
|
+
Errors subclass ``ClickException``, so Typer/Click catch them automatically, print the
|
|
4
|
+
message to stderr, and exit with ``exit_code``. The base class comes from
|
|
5
|
+
:mod:`._click_compat`, **not** from a plain ``import click``: Typer 0.26+ vendors its
|
|
6
|
+
own Click and catches only that one, so subclassing pip Click's ``ClickException``
|
|
7
|
+
makes every typed error escape uncaught as exit 1 with an empty stderr. See
|
|
8
|
+
``_click_compat`` for the full failure mode.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from . import _click_compat as click # the Click Typer is running; see _click_compat
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FpCliError(click.ClickException):
|
|
19
|
+
"""Base class for all CLI errors.
|
|
20
|
+
|
|
21
|
+
Carries an optional ``hint`` (a short "what to do next" line). The single error
|
|
22
|
+
chokepoint in ``app.py`` renders it — under ``--json`` as a ``"hint"`` field, else
|
|
23
|
+
as the dim second line of the red error box — so commands just ``raise`` a typed
|
|
24
|
+
error with a hint instead of hand-rolling a JSON-vs-box branch per call site.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
exit_code = 1
|
|
28
|
+
|
|
29
|
+
def __init__(self, message: str, *, hint: Optional[str] = None) -> None:
|
|
30
|
+
super().__init__(message)
|
|
31
|
+
self.hint = hint
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class KeyModeUnsupportedError(FpCliError):
|
|
35
|
+
"""This command cannot work with an API key (`--api-key` / ``FP_API_KEY``).
|
|
36
|
+
|
|
37
|
+
Reuses **exit 2 (usage error)**, deliberately: the exit-code table is a scripted
|
|
38
|
+
contract restated in ``app.py``'s help, ``cli/skill/SKILL.md`` and
|
|
39
|
+
``enterprise-docs/cli.md``, so a seventh code would have to be added to all three
|
|
40
|
+
at once — and "you asked for something this credential can never do" IS a usage
|
|
41
|
+
error. Every raise site fires BEFORE any HTTP call, so an unsupported command
|
|
42
|
+
never half-runs.
|
|
43
|
+
|
|
44
|
+
Like every error here it subclasses through ``_click_compat``; bind it to pip
|
|
45
|
+
Click and Typer 0.26+ lets it escape as exit 1 with an empty stderr.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
exit_code = 2
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class NetworkError(FpCliError):
|
|
52
|
+
"""The dashboard could not be reached."""
|
|
53
|
+
|
|
54
|
+
exit_code = 3
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class AuthError(FpCliError):
|
|
58
|
+
"""Not logged in, or the stored session has expired."""
|
|
59
|
+
|
|
60
|
+
exit_code = 4
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ForbiddenError(FpCliError):
|
|
64
|
+
"""Authenticated, but the account lacks the required permission."""
|
|
65
|
+
|
|
66
|
+
exit_code = 5
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class NotFoundError(FpCliError):
|
|
70
|
+
"""The requested resource does not exist."""
|
|
71
|
+
|
|
72
|
+
exit_code = 6
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ApiError(FpCliError):
|
|
76
|
+
"""The dashboard returned an unexpected error status."""
|
|
77
|
+
|
|
78
|
+
exit_code = 1
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
message: str,
|
|
83
|
+
*,
|
|
84
|
+
status: Optional[int] = None,
|
|
85
|
+
request_id: Optional[str] = None,
|
|
86
|
+
hint: Optional[str] = None,
|
|
87
|
+
) -> None:
|
|
88
|
+
super().__init__(message, hint=hint)
|
|
89
|
+
self.status = status
|
|
90
|
+
self.request_id = request_id
|
|
91
|
+
|
|
92
|
+
def format_message(self) -> str:
|
|
93
|
+
parts = [self.message]
|
|
94
|
+
if self.status is not None:
|
|
95
|
+
parts.append(f"(HTTP {self.status})")
|
|
96
|
+
if self.request_id:
|
|
97
|
+
parts.append(f"[request-id: {self.request_id}]")
|
|
98
|
+
return " ".join(parts)
|