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,416 @@
|
|
|
1
|
+
"""The fleet: fleet list / show / deploy / diff / history / rollback / rename.
|
|
2
|
+
|
|
3
|
+
What each machine is TOLD to enforce. Authoring the policies is `fp policies`;
|
|
4
|
+
what they actually did is `fp guardrails`.
|
|
5
|
+
|
|
6
|
+
## The one thing to understand before reading `deploy`
|
|
7
|
+
|
|
8
|
+
`PUT /enforcement/deployments/{id}` REPLACES a machine's whole policy set. There
|
|
9
|
+
is no merge and no server-side lock. The dashboard deliberately has no deploy
|
|
10
|
+
form for this reason — it edits the machine's own current set instead, because a
|
|
11
|
+
form that asks you to re-tick policies silently drops whatever you forget.
|
|
12
|
+
|
|
13
|
+
So `deploy` here defaults to a read-modify-write: it reads what the machine runs,
|
|
14
|
+
applies `--add`/`--remove`, shows the resulting FULL set, and writes that.
|
|
15
|
+
`--set` is the escape hatch for the declarative case and is the only way to say
|
|
16
|
+
"exactly these, drop the rest".
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import List, Optional
|
|
21
|
+
|
|
22
|
+
import typer
|
|
23
|
+
|
|
24
|
+
from .. import client as api
|
|
25
|
+
from .. import output
|
|
26
|
+
from .._context import GLOBALS_EPILOG, AppState, deny_in_key_mode, require_auth
|
|
27
|
+
from .. import _click_compat as click # the Click Typer is running; see _click_compat
|
|
28
|
+
from ..enforcement import (
|
|
29
|
+
RefError,
|
|
30
|
+
RefUsageError,
|
|
31
|
+
check_race,
|
|
32
|
+
disabled_ids,
|
|
33
|
+
latest_versions,
|
|
34
|
+
plan_deploy,
|
|
35
|
+
)
|
|
36
|
+
from ..errors import ApiError, NotFoundError
|
|
37
|
+
from . import _write
|
|
38
|
+
|
|
39
|
+
_KEY_MODE_REASON = (
|
|
40
|
+
"the fleet is an operator surface and is not exposed on the versioned API that "
|
|
41
|
+
"an API key authenticates against"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _require_machine(cctx, machine_id: str) -> None:
|
|
46
|
+
"""Refuse an id no machine has ever reported under.
|
|
47
|
+
|
|
48
|
+
Without this, a typo is indistinguishable from a real machine that simply
|
|
49
|
+
has nothing deployed: both render an empty set and exit 0. The id is also
|
|
50
|
+
interpolated into a URL path further down, so an id containing `/` would
|
|
51
|
+
address a different route entirely — the server rejects those, but a clear
|
|
52
|
+
"no machine" beats someone else's 404.
|
|
53
|
+
"""
|
|
54
|
+
if machine_id not in {m.machine_id for m in api.list_machines(cctx)}:
|
|
55
|
+
raise NotFoundError(f"no machine {machine_id!r} has checked in")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def fleet_list(ctx: typer.Context) -> None:
|
|
59
|
+
"""List machines and how many policies each is told to run.
|
|
60
|
+
|
|
61
|
+
Shows `machine · label · pol · intended · applied · seen · events · state`.
|
|
62
|
+
`intended` is the generation deployed, `applied` is the one the machine last
|
|
63
|
+
collected, and `seen` is when it last reported anything — a machine can be
|
|
64
|
+
in sync and dead, or alive and behind, and those are different problems.
|
|
65
|
+
|
|
66
|
+
A machine appears from its very first check-in, including the poll that
|
|
67
|
+
finds nothing deployed — that is exactly the machine you are usually looking
|
|
68
|
+
for. Needs `policies:read`. With `--json`: `{machines, deployments}`, where
|
|
69
|
+
each machine carries raw timestamps plus the computed `drifted`.
|
|
70
|
+
|
|
71
|
+
Example:
|
|
72
|
+
|
|
73
|
+
* `fp fleet list`
|
|
74
|
+
"""
|
|
75
|
+
state: AppState = ctx.obj
|
|
76
|
+
deny_in_key_mode(state, "fleet", _KEY_MODE_REASON)
|
|
77
|
+
cctx = require_auth(state)
|
|
78
|
+
machines = api.list_machines(cctx)
|
|
79
|
+
if output.is_json():
|
|
80
|
+
# Only `--json` emits the deployments, and only `--json` pays for them.
|
|
81
|
+
# The table is built entirely from the machine records; fetching them
|
|
82
|
+
# for a human render was a second request whose result was discarded.
|
|
83
|
+
output.emit_json({
|
|
84
|
+
"machines": [m.to_dict() for m in machines],
|
|
85
|
+
"deployments": [d.to_dict() for d in api.list_deployments(cctx)],
|
|
86
|
+
})
|
|
87
|
+
return
|
|
88
|
+
output.render_fleet(machines)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def fleet_show(
|
|
92
|
+
ctx: typer.Context,
|
|
93
|
+
machine_id: str = typer.Argument(..., help="Machine id."),
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Show exactly what one machine is told to enforce.
|
|
96
|
+
|
|
97
|
+
The set shown is the set that exists — read this before a `--set`, because
|
|
98
|
+
that flag replaces all of it.
|
|
99
|
+
|
|
100
|
+
Also reports whether the machine has actually COLLECTED that deployment. A
|
|
101
|
+
machine can be told to run a policy and not yet have it; the policy list
|
|
102
|
+
alone cannot tell you which, and that is usually the question.
|
|
103
|
+
|
|
104
|
+
Needs `policies:read`. With `--json`: `{machine, deployment}` — the machine
|
|
105
|
+
record (including `appliedDeployment`, `drifted`, `lastSeen` and both label
|
|
106
|
+
fields, with raw timestamps) and the deployment, or `deployment: null` when
|
|
107
|
+
nothing is deployed.
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
|
|
111
|
+
* `fp fleet show ci-runner-01`
|
|
112
|
+
"""
|
|
113
|
+
state: AppState = ctx.obj
|
|
114
|
+
deny_in_key_mode(state, "fleet show", _KEY_MODE_REASON)
|
|
115
|
+
cctx = require_auth(state)
|
|
116
|
+
# Two reads on purpose. The deployment says what the machine was TOLD to
|
|
117
|
+
# run; only the machine record says whether it has collected it. Showing the
|
|
118
|
+
# first without the second is how this view came to imply a policy was in
|
|
119
|
+
# force when the host had never picked it up.
|
|
120
|
+
machines = api.list_machines(cctx)
|
|
121
|
+
machine = next((m for m in machines if m.machine_id == machine_id), None)
|
|
122
|
+
if machine is None:
|
|
123
|
+
raise NotFoundError(f"no machine {machine_id!r} has checked in")
|
|
124
|
+
dep = api.get_deployment(cctx, machine_id)
|
|
125
|
+
|
|
126
|
+
if output.is_json():
|
|
127
|
+
output.emit_json({
|
|
128
|
+
"machine": machine.to_dict(),
|
|
129
|
+
"deployment": dep.to_dict() if dep else None,
|
|
130
|
+
})
|
|
131
|
+
return
|
|
132
|
+
output.render_machine_policies(machine_id, dep, machine)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def fleet_deploy(
|
|
136
|
+
ctx: typer.Context,
|
|
137
|
+
machine_id: str = typer.Argument(..., help="Machine id."),
|
|
138
|
+
add: Optional[List[str]] = typer.Option(
|
|
139
|
+
None, "--add",
|
|
140
|
+
help="Add or update a policy: `id`, `id@version`, `id:effect` or `id@version:effect`.",
|
|
141
|
+
),
|
|
142
|
+
remove: Optional[List[str]] = typer.Option(None, "--remove", help="Remove a policy by id."),
|
|
143
|
+
replace: Optional[List[str]] = typer.Option(
|
|
144
|
+
None, "--set",
|
|
145
|
+
help="REPLACE the whole set with exactly these. Cannot be combined with --add/--remove.",
|
|
146
|
+
),
|
|
147
|
+
create: bool = typer.Option(
|
|
148
|
+
False, "--create",
|
|
149
|
+
help="Allow deploying to a machine id that has not checked in yet (pre-staging).",
|
|
150
|
+
),
|
|
151
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip the confirmation prompt. The prompt only appears on an interactive terminal: under --json, or with stdin redirected, this command proceeds without asking."),
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Change what a machine enforces, showing the full resulting set first.
|
|
154
|
+
|
|
155
|
+
`--add`/`--remove` read the machine's current set and apply a delta, so
|
|
156
|
+
nothing you did not mention is disturbed. A bare `--add` on a policy the
|
|
157
|
+
machine already runs keeps its pinned version rather than silently
|
|
158
|
+
upgrading; pass `id@version` to move it.
|
|
159
|
+
|
|
160
|
+
`--set` replaces everything — the only way to drop policies you do not name.
|
|
161
|
+
|
|
162
|
+
**Concurrency.** The write is a full replace with no server-side lock, so the
|
|
163
|
+
CLI records the generation it read and refuses if the result is not exactly
|
|
164
|
+
one higher: that means somebody else deployed in between and a replace does
|
|
165
|
+
not merge. Needs `policies:write`. With `--json`: the plan plus the resulting
|
|
166
|
+
deployment.
|
|
167
|
+
|
|
168
|
+
Examples:
|
|
169
|
+
|
|
170
|
+
* `fp fleet deploy ci-runner-01 --add no-force-push`
|
|
171
|
+
* `fp fleet deploy ci-runner-01 --add prod-guard@1:observe --remove old-rule`
|
|
172
|
+
* `fp fleet deploy ci-runner-01 --set no-force-push --set no-secret-echo`
|
|
173
|
+
"""
|
|
174
|
+
state: AppState = ctx.obj
|
|
175
|
+
deny_in_key_mode(state, "fleet deploy", _KEY_MODE_REASON)
|
|
176
|
+
cctx = require_auth(state)
|
|
177
|
+
|
|
178
|
+
if not add and not remove and replace is None:
|
|
179
|
+
# Exit 2 for the same reason `--set` with `--add` is: no flag
|
|
180
|
+
# combination was given that this command can act on. Both are the
|
|
181
|
+
# caller's command line, not the server's answer.
|
|
182
|
+
raise click.UsageError(
|
|
183
|
+
"nothing to do — pass --add, --remove, or --set. "
|
|
184
|
+
"`fp fleet show <machine>` prints the current set."
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# The server accepts a deploy to ANY id — that is how a machine can be
|
|
188
|
+
# pre-staged before it ever polls. It also means a typo does not fail: it
|
|
189
|
+
# mints a machine nobody owns, carrying policies nobody will collect, and
|
|
190
|
+
# the only sign is an extra row in `fleet list`. The dashboard cannot hit
|
|
191
|
+
# this because it deploys to a machine picked from a list; a CLI takes free
|
|
192
|
+
# text, so the check has to be here.
|
|
193
|
+
if not create:
|
|
194
|
+
try:
|
|
195
|
+
_require_machine(cctx, machine_id)
|
|
196
|
+
except NotFoundError:
|
|
197
|
+
raise NotFoundError(
|
|
198
|
+
f"no machine {machine_id!r} has checked in — deploying would create "
|
|
199
|
+
"it as a new machine id. Pass --create if that is deliberate."
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
current = api.get_deployment(cctx, machine_id)
|
|
203
|
+
published = api.list_policies(cctx)
|
|
204
|
+
latest = latest_versions(published)
|
|
205
|
+
try:
|
|
206
|
+
plan = plan_deploy(
|
|
207
|
+
machine_id,
|
|
208
|
+
current=current.policies if current else None,
|
|
209
|
+
base=current.deployment if current else None,
|
|
210
|
+
add=add or (),
|
|
211
|
+
remove=remove or (),
|
|
212
|
+
replace=replace,
|
|
213
|
+
latest=latest,
|
|
214
|
+
disabled=disabled_ids(published),
|
|
215
|
+
)
|
|
216
|
+
except RefUsageError as exc:
|
|
217
|
+
# Exit 2, like every other bad flag value in this CLI (`--since`,
|
|
218
|
+
# `--expect`, `--file`). These are retype-the-command mistakes; exit 1
|
|
219
|
+
# says "the server refused", which is a different thing to script on.
|
|
220
|
+
raise click.UsageError(str(exc))
|
|
221
|
+
except RefError as exc:
|
|
222
|
+
raise ApiError(str(exc))
|
|
223
|
+
|
|
224
|
+
# A no-op exits 0 WITHOUT writing, which is desired-state semantics: a
|
|
225
|
+
# retrying harness re-running the same deploy should succeed, not error.
|
|
226
|
+
# Two consequences worth knowing rather than discovering:
|
|
227
|
+
# * `applied: false` in --json is the only way to tell "I changed it" from
|
|
228
|
+
# "it already matched" — the exit code is 0 either way, on purpose.
|
|
229
|
+
# * the short-circuit happens BEFORE the write, so a reader without
|
|
230
|
+
# `policies:write` also gets 0 here. They have not gained anything (the
|
|
231
|
+
# state already held and nothing was written), but the exit code alone
|
|
232
|
+
# is not proof of write access.
|
|
233
|
+
if plan.is_noop:
|
|
234
|
+
if output.is_json():
|
|
235
|
+
output.emit_json({"plan": plan.to_dict(), "deployment": None, "applied": False})
|
|
236
|
+
return
|
|
237
|
+
output.deployment_unchanged(machine_id)
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
if not output.is_json():
|
|
241
|
+
output.render_deploy_plan(plan)
|
|
242
|
+
dropped = len(plan.removed)
|
|
243
|
+
if not _write.confirm_destructive(
|
|
244
|
+
state, "replace the policy set on", machine_id,
|
|
245
|
+
consequence=(f"this REPLACES the whole set with the {len(plan.result)} shown above"
|
|
246
|
+
+ (f"; {dropped} would be removed" if dropped else "")),
|
|
247
|
+
assume_yes=yes,
|
|
248
|
+
):
|
|
249
|
+
if output.is_json():
|
|
250
|
+
output.emit_json({"plan": plan.to_dict(), "cancelled": True, "applied": False})
|
|
251
|
+
else:
|
|
252
|
+
output.print_cancelled()
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
result = api.deploy_policies(cctx, machine_id, plan.result)
|
|
256
|
+
check_race(plan.base, result.deployment)
|
|
257
|
+
|
|
258
|
+
if output.is_json():
|
|
259
|
+
output.emit_json({
|
|
260
|
+
"plan": plan.to_dict(),
|
|
261
|
+
"deployment": result.to_dict(),
|
|
262
|
+
"applied": True,
|
|
263
|
+
})
|
|
264
|
+
return
|
|
265
|
+
output.deployment_applied(machine_id, result.deployment, len(result.policies))
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def fleet_diff(
|
|
269
|
+
ctx: typer.Context,
|
|
270
|
+
machine_id: Optional[str] = typer.Argument(None, help="Machine id. Omit for the whole fleet."),
|
|
271
|
+
) -> None:
|
|
272
|
+
"""Show intent vs delivery — what a machine is told to run vs what it last pulled.
|
|
273
|
+
|
|
274
|
+
The gap is the interesting part: a machine that has not collected its latest
|
|
275
|
+
deployment is not enforcing what the dashboard says it is, and nothing else
|
|
276
|
+
surfaces that as a single number. Needs `policies:read`. With `--json`:
|
|
277
|
+
`{machines:[{machineId, intended, delivered, drifted}]}` — `drifted` is the
|
|
278
|
+
field the CLI computes, so a harness need not derive it.
|
|
279
|
+
|
|
280
|
+
Example:
|
|
281
|
+
|
|
282
|
+
* `fp fleet diff`
|
|
283
|
+
"""
|
|
284
|
+
state: AppState = ctx.obj
|
|
285
|
+
deny_in_key_mode(state, "fleet diff", _KEY_MODE_REASON)
|
|
286
|
+
cctx = require_auth(state)
|
|
287
|
+
machines = api.list_machines(cctx)
|
|
288
|
+
# Every other machine-scoped command refuses an id nobody has reported
|
|
289
|
+
# under; this one filtered to nothing and exited 0 saying "no machines have
|
|
290
|
+
# checked in yet" — false, and indistinguishable from a healthy fleet. The
|
|
291
|
+
# list is already in hand, so the check costs no extra request.
|
|
292
|
+
if machine_id and machine_id not in {m.machine_id for m in machines}:
|
|
293
|
+
raise NotFoundError(f"no machine {machine_id!r} has checked in")
|
|
294
|
+
rows = []
|
|
295
|
+
for m in sorted(machines, key=lambda x: x.machine_id):
|
|
296
|
+
if machine_id and m.machine_id != machine_id:
|
|
297
|
+
continue
|
|
298
|
+
rows.append({
|
|
299
|
+
"machineId": m.machine_id,
|
|
300
|
+
"intended": m.deployment,
|
|
301
|
+
"delivered": m.applied_deployment,
|
|
302
|
+
"drifted": m.drifted,
|
|
303
|
+
})
|
|
304
|
+
if output.is_json():
|
|
305
|
+
output.emit_json({"machines": rows})
|
|
306
|
+
return
|
|
307
|
+
output.render_fleet_diff(rows)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def fleet_history(
|
|
311
|
+
ctx: typer.Context,
|
|
312
|
+
machine_id: str = typer.Argument(..., help="Machine id."),
|
|
313
|
+
) -> None:
|
|
314
|
+
"""List a machine's deployment generations, newest first.
|
|
315
|
+
|
|
316
|
+
A reissue — the server rewriting a deployment because a policy was disabled
|
|
317
|
+
— appears as an ordinary entry. Needs `policies:read`. With `--json`:
|
|
318
|
+
`{machineId, history:[{deployment, policies, updatedAt}]}`.
|
|
319
|
+
|
|
320
|
+
Example:
|
|
321
|
+
|
|
322
|
+
* `fp fleet history ci-runner-01`
|
|
323
|
+
"""
|
|
324
|
+
state: AppState = ctx.obj
|
|
325
|
+
deny_in_key_mode(state, "fleet history", _KEY_MODE_REASON)
|
|
326
|
+
cctx = require_auth(state)
|
|
327
|
+
_require_machine(cctx, machine_id)
|
|
328
|
+
entries = api.deployment_history(cctx, machine_id)
|
|
329
|
+
if output.is_json():
|
|
330
|
+
output.emit_json({"machineId": machine_id, "history": entries})
|
|
331
|
+
return
|
|
332
|
+
output.render_deployment_history(machine_id, entries)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def fleet_rollback(
|
|
336
|
+
ctx: typer.Context,
|
|
337
|
+
machine_id: str = typer.Argument(..., help="Machine id."),
|
|
338
|
+
deployment: int = typer.Argument(..., help="The generation to reinstate."),
|
|
339
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip the confirmation prompt. The prompt only appears on an interactive terminal: under --json, or with stdin redirected, this command proceeds without asking."),
|
|
340
|
+
) -> None:
|
|
341
|
+
"""Reinstate a past generation's policy set.
|
|
342
|
+
|
|
343
|
+
This mints a NEW generation carrying the old set rather than rewinding the
|
|
344
|
+
counter, so history stays append-only. A generation containing a policy that
|
|
345
|
+
has since been disabled or deleted cannot be reinstated; the server says so.
|
|
346
|
+
|
|
347
|
+
Needs `policies:write`. With `--json`: the resulting deployment, or
|
|
348
|
+
`{"cancelled": true}` if you decline.
|
|
349
|
+
|
|
350
|
+
Example:
|
|
351
|
+
|
|
352
|
+
* `fp fleet rollback ci-runner-01 3`
|
|
353
|
+
"""
|
|
354
|
+
state: AppState = ctx.obj
|
|
355
|
+
deny_in_key_mode(state, "fleet rollback", _KEY_MODE_REASON)
|
|
356
|
+
cctx = require_auth(state)
|
|
357
|
+
_require_machine(cctx, machine_id)
|
|
358
|
+
current = api.get_deployment(cctx, machine_id)
|
|
359
|
+
if not _write.confirm_destructive(
|
|
360
|
+
state, f"reinstate deployment #{deployment} on", machine_id,
|
|
361
|
+
consequence="this REPLACES the machine's current set with the one from that generation",
|
|
362
|
+
assume_yes=yes,
|
|
363
|
+
):
|
|
364
|
+
if output.is_json():
|
|
365
|
+
output.emit_json({"cancelled": True})
|
|
366
|
+
else:
|
|
367
|
+
output.print_cancelled()
|
|
368
|
+
return
|
|
369
|
+
result = api.rollback_deployment(cctx, machine_id, deployment)
|
|
370
|
+
check_race(current.deployment if current else None, result.deployment)
|
|
371
|
+
if output.is_json():
|
|
372
|
+
output.emit_json(result.to_dict())
|
|
373
|
+
return
|
|
374
|
+
output.deployment_rolled_back(machine_id, deployment, result.deployment)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def fleet_rename(
|
|
378
|
+
ctx: typer.Context,
|
|
379
|
+
machine_id: str = typer.Argument(..., help="Machine id."),
|
|
380
|
+
label: str = typer.Argument(..., help="Human-readable label."),
|
|
381
|
+
) -> None:
|
|
382
|
+
"""Give a machine a human label. The id itself never changes.
|
|
383
|
+
|
|
384
|
+
Needs `policies:write`. With `--json`: `{machineId, labelOverride}` — the
|
|
385
|
+
server stores the label as an override beside the machine's self-asserted
|
|
386
|
+
one rather than replacing it.
|
|
387
|
+
|
|
388
|
+
Example:
|
|
389
|
+
|
|
390
|
+
* `fp fleet rename ci-runner-01 "CI runner (eu-west)"`
|
|
391
|
+
"""
|
|
392
|
+
state: AppState = ctx.obj
|
|
393
|
+
deny_in_key_mode(state, "fleet rename", _KEY_MODE_REASON)
|
|
394
|
+
cctx = require_auth(state)
|
|
395
|
+
res = api.rename_machine(cctx, machine_id, label)
|
|
396
|
+
if output.is_json():
|
|
397
|
+
output.emit_json(res)
|
|
398
|
+
return
|
|
399
|
+
output.machine_renamed(machine_id, label)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def register(app: typer.Typer) -> None:
|
|
403
|
+
fleet_app = typer.Typer(
|
|
404
|
+
no_args_is_help=True,
|
|
405
|
+
rich_markup_mode="markdown",
|
|
406
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
407
|
+
help="The fleet and what each machine enforces (list / show / deploy / diff / history / rollback / rename).",
|
|
408
|
+
)
|
|
409
|
+
fleet_app.command("list", epilog=GLOBALS_EPILOG)(fleet_list)
|
|
410
|
+
fleet_app.command("show", epilog=GLOBALS_EPILOG)(fleet_show)
|
|
411
|
+
fleet_app.command("deploy", epilog=GLOBALS_EPILOG)(fleet_deploy)
|
|
412
|
+
fleet_app.command("diff", epilog=GLOBALS_EPILOG)(fleet_diff)
|
|
413
|
+
fleet_app.command("history", epilog=GLOBALS_EPILOG)(fleet_history)
|
|
414
|
+
fleet_app.command("rollback", epilog=GLOBALS_EPILOG)(fleet_rollback)
|
|
415
|
+
fleet_app.command("rename", epilog=GLOBALS_EPILOG)(fleet_rename)
|
|
416
|
+
app.add_typer(fleet_app, name="fleet")
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Guardrails: what enforcement actually did — `summary` and `timeline`.
|
|
2
|
+
|
|
3
|
+
The counterpart to `fp fleet`. That command says what the control plane
|
|
4
|
+
INTENDED; this says what happened — whether the fleet is really covered, what
|
|
5
|
+
got blocked, and which policies earn their place.
|
|
6
|
+
|
|
7
|
+
The two halves come from different stores and that is worth knowing when a
|
|
8
|
+
number looks wrong: coverage is Postgres (the deployments), while the decision
|
|
9
|
+
counts are ClickHouse (hook telemetry the machines reported). A machine can be
|
|
10
|
+
deployed-to and silent, or reporting and undeployed, and only the first half
|
|
11
|
+
moves when you run `fp fleet deploy`.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
import typer
|
|
18
|
+
|
|
19
|
+
from .. import client as api
|
|
20
|
+
from .. import output
|
|
21
|
+
from .._context import GLOBALS_EPILOG, AppState, deny_in_key_mode, require_auth
|
|
22
|
+
from ..errors import NotFoundError
|
|
23
|
+
|
|
24
|
+
_KEY_MODE_REASON = (
|
|
25
|
+
"guardrails reads an operator surface that is not exposed on the versioned "
|
|
26
|
+
"API that an API key authenticates against"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _require_machine(cctx, machine_id: Optional[str]) -> None:
|
|
31
|
+
"""Refuse a `--machine` id nobody has ever reported under.
|
|
32
|
+
|
|
33
|
+
Both views answer "what happened here", and both answer an unknown machine
|
|
34
|
+
with an empty window — which reads as "this machine was quiet", not as "you
|
|
35
|
+
typed the id wrong". `fp fleet` refuses the same mistake everywhere else;
|
|
36
|
+
the check costs one request, and only when the flag is actually used.
|
|
37
|
+
"""
|
|
38
|
+
if machine_id is None:
|
|
39
|
+
return
|
|
40
|
+
if machine_id not in {m.machine_id for m in api.list_machines(cctx)}:
|
|
41
|
+
raise NotFoundError(f"no machine {machine_id!r} has checked in")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _hours(since: str) -> int:
|
|
45
|
+
"""`1h`/`6h`/`24h`/`7d` → hours. The CLI's `--since` vocabulary, one window only.
|
|
46
|
+
|
|
47
|
+
`15m` is deliberately NOT here. These two endpoints take a whole-hour window
|
|
48
|
+
and nothing finer, so `15m` mapped to 1 and produced a request byte-identical
|
|
49
|
+
to `--since 1h`: the window was silently widened 4x with no tell anywhere —
|
|
50
|
+
`timeline` renders no window label at all, and `summary`'s title comes from
|
|
51
|
+
the server's echoed hours, so it read `guardrails · 1h` while contradicting
|
|
52
|
+
the flag the operator typed. An operator watching a rollout would read
|
|
53
|
+
denials from before their change as caused by it. Refusing it is honest;
|
|
54
|
+
accepting it and rounding is not.
|
|
55
|
+
"""
|
|
56
|
+
table = {"1h": 1, "6h": 6, "24h": 24, "7d": 168}
|
|
57
|
+
if since in table:
|
|
58
|
+
return table[since]
|
|
59
|
+
# A usage error, not a runtime one: exit 2 like every other bad flag value,
|
|
60
|
+
# rather than the exit 1 an uncaught ValueError would produce.
|
|
61
|
+
raise typer.BadParameter(
|
|
62
|
+
f"invalid --since value {since!r}; choose one of {', '.join(table)}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def guardrails_summary(
|
|
67
|
+
ctx: typer.Context,
|
|
68
|
+
since: str = typer.Option("24h", "--since", help="Window: 1h, 6h, 24h, 7d."),
|
|
69
|
+
machine: Optional[str] = typer.Option(None, "--machine", help="Scope to one machine id."),
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Coverage, blocks, and the per-policy table for a window.
|
|
72
|
+
|
|
73
|
+
Shows evaluated/blocked totals, how many machines are enforcing versus
|
|
74
|
+
merely reporting, a 24-bin sparkline of denies, and each policy's
|
|
75
|
+
fired/blocked/instructed/p95.
|
|
76
|
+
|
|
77
|
+
A `(no policy)` row is normal, not a gap: most evaluations are allows that
|
|
78
|
+
no policy objected to, and the row keeps the denominator on screen — "14
|
|
79
|
+
blocked" means little without the 933 it came from.
|
|
80
|
+
|
|
81
|
+
Needs `policies:read`. With `--json`: the server summary plus the timeline.
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
|
|
85
|
+
* `fp guardrails`
|
|
86
|
+
* `fp guardrails --since 7d --machine ci-runner-01`
|
|
87
|
+
"""
|
|
88
|
+
state: AppState = ctx.obj
|
|
89
|
+
deny_in_key_mode(state, "guardrails", _KEY_MODE_REASON)
|
|
90
|
+
cctx = require_auth(state)
|
|
91
|
+
hours = _hours(since)
|
|
92
|
+
_require_machine(cctx, machine)
|
|
93
|
+
summary = api.enforcement_summary(cctx, hours=hours, machine_id=machine)
|
|
94
|
+
timeline = api.decision_timeline(cctx, hours=hours, machine_id=machine)
|
|
95
|
+
if output.is_json():
|
|
96
|
+
output.emit_json({"summary": summary, "timeline": timeline})
|
|
97
|
+
return
|
|
98
|
+
output.render_guardrails(summary, timeline)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def guardrails_timeline(
|
|
102
|
+
ctx: typer.Context,
|
|
103
|
+
since: str = typer.Option("24h", "--since", help="Window: 1h, 6h, 24h, 7d."),
|
|
104
|
+
machine: Optional[str] = typer.Option(None, "--machine", help="Scope to one machine id."),
|
|
105
|
+
) -> None:
|
|
106
|
+
"""When enforcement bit, and how hard — one row per time bucket.
|
|
107
|
+
|
|
108
|
+
Shows `time · activity · total · denied · instructed`. The bar is scaled to
|
|
109
|
+
the busiest bucket in the window, with the blocked share drawn in red inside
|
|
110
|
+
it, so a quiet hour and a heavily-blocked hour are distinguishable at a
|
|
111
|
+
glance rather than by reading numbers.
|
|
112
|
+
|
|
113
|
+
Times are UTC, and the label follows the bucket size the server chose — a
|
|
114
|
+
clock for hourly buckets, a date for daily ones.
|
|
115
|
+
|
|
116
|
+
Needs `policies:read`. With `--json`: the server's timeline verbatim.
|
|
117
|
+
|
|
118
|
+
Example:
|
|
119
|
+
|
|
120
|
+
* `fp guardrails timeline --since 24h`
|
|
121
|
+
"""
|
|
122
|
+
state: AppState = ctx.obj
|
|
123
|
+
deny_in_key_mode(state, "guardrails timeline", _KEY_MODE_REASON)
|
|
124
|
+
cctx = require_auth(state)
|
|
125
|
+
hours = _hours(since)
|
|
126
|
+
_require_machine(cctx, machine)
|
|
127
|
+
data = api.decision_timeline(cctx, hours=hours, machine_id=machine)
|
|
128
|
+
if output.is_json():
|
|
129
|
+
output.emit_json(data)
|
|
130
|
+
return
|
|
131
|
+
output.render_decision_timeline(data)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def register(app: typer.Typer) -> None:
|
|
135
|
+
# A pure container, like every other group in this CLI: bare `fp guardrails`
|
|
136
|
+
# prints its help rather than running something. It used to run the summary
|
|
137
|
+
# from a callback, which made it the only group that did — and put `--since`
|
|
138
|
+
# in two places, where the group-level copy silently shadowed nothing and
|
|
139
|
+
# taught the wrong shape.
|
|
140
|
+
guardrails_app = typer.Typer(
|
|
141
|
+
no_args_is_help=True,
|
|
142
|
+
rich_markup_mode="markdown",
|
|
143
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
144
|
+
help="What enforcement actually did (summary / timeline).",
|
|
145
|
+
)
|
|
146
|
+
guardrails_app.command("summary", epilog=GLOBALS_EPILOG)(guardrails_summary)
|
|
147
|
+
guardrails_app.command("timeline", epilog=GLOBALS_EPILOG)(guardrails_timeline)
|
|
148
|
+
app.add_typer(guardrails_app, name="guardrails")
|