failproofai 1.0.12-beta.1 → 1.0.12
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 +4 -4
- 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.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 +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
- 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.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 +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- 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.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.nft.json +1 -1
- 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.nft.json +1 -1
- 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.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 +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- 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 +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +2 -2
- 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.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.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.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 +8 -8
- 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/_0tovk6q0miud6._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0_em82s16zfym._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__167mipqmjprq5._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0b78b0j8iyv6l._.js → [root-of-the-server]__1vofkb2oq7p7e._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1a3tubds7s7ow._.js → _0ouu886szh6yj._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1d98fyfrcaqhy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1i8oid1gc7s1f.js → node_modules_html-to-image_es_index_0tqc05n832v7a.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589gp4p60j._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me658zqmh_._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- 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/preview-props.json +3 -3
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
- package/.next/standalone/.next/static/chunks/{1b32-afqacaen.js → 0el9n3h9_zp5m.js} +1 -1
- package/.next/standalone/.next/static/chunks/0k7g_8x3o6gd-.js +1 -0
- package/.next/standalone/.next/static/chunks/{42e8zab314654.js → 0umzct31duk5g.js} +1 -1
- package/.next/standalone/.next/static/chunks/{24w3x2da9sk49.js → 0wobqgosirs8p.js} +1 -1
- package/.next/standalone/.next/static/chunks/12rug3jilwqzs.js +1 -0
- package/.next/standalone/.next/static/chunks/{2-tf6_7u623kd.js → 1kduj74-715jh.js} +1 -1
- package/.next/standalone/.next/static/chunks/2701amhrbsbot.js +1 -0
- package/.next/standalone/.next/static/chunks/{18433qz-8njv3.js → 27cavoc8paelg.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0_zfad4co-t2z.js → 2ve23a7keupjw.js} +1 -1
- package/.next/standalone/.next/static/chunks/3bv6xaoitjpnh.js +6 -0
- package/.next/standalone/app/actions/update-jev-config.ts +5 -3
- package/.next/standalone/app/settings/jev-panel.tsx +1 -1
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +28 -18
- package/.next/standalone/fp-cloud-cli/README.md +127 -9
- package/.next/standalone/fp-cloud-cli/fp_cli/app.py +15 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +199 -34
- package/.next/standalone/fp-cloud-cli/fp_cli/cloud_output.py +523 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_cloud.py +45 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +3 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/account_cmds.py +171 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +80 -7
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +23 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +2 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/contracts_cmds.py +67 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/dashboards_cmds.py +159 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/evaluators_cmds.py +380 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +14 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +12 -216
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +82 -6
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +4 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +17 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +143 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +70 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sentiment_cmds.py +49 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +95 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +2 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +21 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +14 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +7 -53
- package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +3 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/evaluator_spec.py +266 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +189 -152
- package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +21 -24
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +124 -23
- package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +2 -1
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +173 -22
- package/.next/standalone/fp-cloud-cli/tests/fake_cloud_smoke.py +312 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_account.py +125 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_agent.py +253 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_human_views.py +287 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +32 -197
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_observability.py +116 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_transport.py +141 -0
- package/.next/standalone/fp-cloud-cli/tests/test_commands.py +3 -3
- package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +3 -3
- package/.next/standalone/fp-cloud-cli/tests/test_evaluator_authoring.py +449 -0
- package/.next/standalone/fp-cloud-cli/tests/test_facets.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +15 -0
- package/.next/standalone/fp-cloud-cli/tests/test_operator.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +37 -37
- package/.next/standalone/fp-cloud-cli/tests/test_round4_behaviour.py +144 -0
- package/.next/standalone/fp-cloud-cli/tests/test_screen_language.py +80 -0
- package/.next/standalone/fp-cloud-cli/tests/test_session_reevaluate.py +33 -0
- package/.next/standalone/fp-cloud-cli/tests/test_staging_parity.py +198 -0
- package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +1 -1
- package/.next/standalone/hermes-plugin/__init__.py +11 -0
- package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
- package/.next/standalone/package.json +11 -10
- package/.next/standalone/sdk/python/uv.lock +31 -8
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +68 -176
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +2 -2
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +98 -115
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +3 -3
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +149 -171
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +2 -2
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +25 -25
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +10 -10
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +1 -1
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +1760 -638
- package/dist/worker.mjs +426 -76
- package/hermes-plugin/__init__.py +11 -0
- package/hermes-plugin/plugin.yaml +1 -1
- package/package.json +11 -10
- package/src/audit/open-browser.ts +24 -7
- package/src/hooks/agent-roster.ts +9 -1
- package/src/hooks/agent-skill.ts +206 -0
- package/src/hooks/cloud-connection.ts +27 -51
- package/src/hooks/cloud-introspect.ts +5 -2
- package/src/hooks/cloud-login.ts +248 -0
- package/src/hooks/cloud-managed-policies.ts +54 -4
- package/src/hooks/codex-system-hooks.ts +10 -4
- package/src/hooks/config-status.ts +9 -3
- package/src/hooks/configure-wizard.ts +356 -32
- package/src/hooks/delivery-health.ts +47 -2
- package/src/hooks/effective-reviewers.ts +14 -0
- package/src/hooks/fp-config.ts +19 -0
- package/src/hooks/fp-home.ts +1 -1
- package/src/hooks/handler.ts +43 -5
- package/src/hooks/hook-activity-store.ts +7 -1
- package/src/hooks/integrations.ts +18 -3
- package/src/hooks/jev-activity.ts +117 -1
- package/src/hooks/jev-cli.ts +7 -9
- package/src/hooks/manager.ts +80 -46
- package/src/hooks/policy-evaluator.ts +17 -0
- package/src/hooks/policy-reviewability.ts +4 -2
- package/src/hooks/semantic/cloud-jev.ts +219 -28
- package/src/hooks/semantic/combine.ts +26 -1
- package/src/hooks/semantic/intent.ts +127 -24
- package/src/hooks/semantic/jev-client.ts +1 -1
- package/src/hooks/semantic/jev-config.ts +6 -27
- package/src/hooks/semantic/jev-review.ts +111 -4
- package/src/hooks/tui.ts +280 -0
- package/src/hooks/uninstall-cli.ts +12 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0588st-tpiuia._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_128ebxr83nk6g._.js +0 -3
- package/.next/standalone/.next/static/chunks/0jutamwqz8pg5.js +0 -1
- package/.next/standalone/.next/static/chunks/114hbolvrv94e.js +0 -1
- package/.next/standalone/.next/static/chunks/1p-hsi58i-6h0.js +0 -6
- package/.next/standalone/.next/static/chunks/3-pku9qwv1yfu.js +0 -1
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_ssgManifest.js +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""The fleet: fleet list / show / deploy /
|
|
1
|
+
"""The fleet: fleet list / show / deploy / diff / history / rollback / rename.
|
|
2
2
|
|
|
3
3
|
What each machine is TOLD to enforce. Authoring the policies is `fp policies`;
|
|
4
4
|
what they actually did is `fp guardrails`.
|
|
@@ -26,12 +26,10 @@ from .. import output
|
|
|
26
26
|
from .._context import GLOBALS_EPILOG, AppState, deny_in_key_mode, require_auth
|
|
27
27
|
from .. import _click_compat as click # the Click Typer is running; see _click_compat
|
|
28
28
|
from ..enforcement import (
|
|
29
|
-
JEV_MODES,
|
|
30
29
|
RefError,
|
|
31
30
|
RefUsageError,
|
|
32
31
|
check_race,
|
|
33
32
|
disabled_ids,
|
|
34
|
-
jev_mode_after,
|
|
35
33
|
latest_versions,
|
|
36
34
|
plan_deploy,
|
|
37
35
|
version_kinds,
|
|
@@ -157,7 +155,7 @@ def fleet_deploy(
|
|
|
157
155
|
add: Optional[List[str]] = typer.Option(
|
|
158
156
|
None, "--add",
|
|
159
157
|
help="Add or update a policy: `id`, `id@version`, `id:effect` or `id@version:effect`. "
|
|
160
|
-
"`observe`
|
|
158
|
+
"`observe` records what the policy (its JavaScript and its Jev checks) would decide, and decides nothing.",
|
|
161
159
|
),
|
|
162
160
|
remove: Optional[List[str]] = typer.Option(None, "--remove", help="Remove a policy by id."),
|
|
163
161
|
replace: Optional[List[str]] = typer.Option(
|
|
@@ -176,14 +174,6 @@ def fleet_deploy(
|
|
|
176
174
|
False, "--create",
|
|
177
175
|
help="Allow deploying to a machine id that has not checked in yet (pre-staging).",
|
|
178
176
|
),
|
|
179
|
-
jev_mode: Optional[str] = typer.Option(
|
|
180
|
-
None, "--jev-mode",
|
|
181
|
-
help="Set the machine's Jev mode from FailproofAI Cloud: off, observe, enforce (Jev's "
|
|
182
|
-
"checks BLOCK calls as well as clearing what they review) — or local to stop "
|
|
183
|
-
"overriding the machine's own. Jev checks run on FailproofAI Cloud; nothing is "
|
|
184
|
-
"installed on the machine. Machine-wide: installed packs' checks too. "
|
|
185
|
-
"Omitted: unchanged.",
|
|
186
|
-
),
|
|
187
177
|
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."),
|
|
188
178
|
) -> None:
|
|
189
179
|
"""Change what a machine enforces, showing the full resulting set first.
|
|
@@ -198,20 +188,10 @@ def fleet_deploy(
|
|
|
198
188
|
Any kind deploys the same way. Jev checks run on FailproofAI Cloud; nothing
|
|
199
189
|
is installed on the machine: a `both` policy sends the machine its
|
|
200
190
|
JavaScript, and the machine asks FailproofAI Cloud about each checked tool
|
|
201
|
-
call
|
|
202
|
-
|
|
203
|
-
Jev
|
|
204
|
-
|
|
205
|
-
regex policies decide; `enforce` lets Jev's checks BLOCK calls (every `jev`
|
|
206
|
-
policy's deny checks) as well as clear the regex verdicts they review; `off`
|
|
207
|
-
stops Jev on the machine; `local` hands the choice back to the machine. The
|
|
208
|
-
mode is the MACHINE's: it also governs the checks of any pack installed
|
|
209
|
-
there, and it overrides the machine's own mode and `jev.json`, a local
|
|
210
|
-
`off` included. Without `--jev-mode` it is left as it is.
|
|
211
|
-
|
|
212
|
-
`--jev-mode` with no change to the set changes ONLY the mode (the same as
|
|
213
|
-
`fp fleet jev-mode`): the policy set — disabled policies' assignments
|
|
214
|
-
included — is not read back and rewritten.
|
|
191
|
+
call. An `observe` effect applies to every kind: the policy's JavaScript
|
|
192
|
+
and its Jev checks are evaluated and recorded, and decide nothing. There is
|
|
193
|
+
no Jev mode to set: FailproofAI Cloud turns Jev on for a machine while any
|
|
194
|
+
`jev` or `both` policy is deployed to it, and off when none is.
|
|
215
195
|
|
|
216
196
|
**Concurrency.** The write is a full replace with no server-side lock, so the
|
|
217
197
|
CLI records the generation it read and refuses if the result is not exactly
|
|
@@ -224,19 +204,18 @@ def fleet_deploy(
|
|
|
224
204
|
* `fp fleet deploy ci-runner-01 --add no-force-push`
|
|
225
205
|
* `fp fleet deploy ci-runner-01 --add prod-guard@1:observe --remove old-rule`
|
|
226
206
|
* `fp fleet deploy ci-runner-01 --set no-force-push --set no-secret-echo`
|
|
227
|
-
* `fp fleet deploy ci-runner-01 --add prod-db-intent
|
|
228
|
-
* `fp fleet deploy ci-runner-01 --jev-mode local`
|
|
207
|
+
* `fp fleet deploy ci-runner-01 --add prod-db-intent:observe`
|
|
229
208
|
"""
|
|
230
209
|
state: AppState = ctx.obj
|
|
231
210
|
deny_in_key_mode(state, "fleet deploy", _KEY_MODE_REASON)
|
|
232
211
|
cctx = require_auth(state)
|
|
233
212
|
|
|
234
|
-
if not add and not remove and replace is None and
|
|
213
|
+
if not add and not remove and replace is None and not targets and not all_agents:
|
|
235
214
|
# Exit 2 for the same reason `--set` with `--add` is: no flag
|
|
236
215
|
# combination was given that this command can act on. Both are the
|
|
237
216
|
# caller's command line, not the server's answer.
|
|
238
217
|
raise click.UsageError(
|
|
239
|
-
"nothing to do — pass --add, --remove, --set, --target, --all-agents
|
|
218
|
+
"nothing to do — pass --add, --remove, --set, --target, or --all-agents. "
|
|
240
219
|
"`fp fleet show <machine>` prints the current set."
|
|
241
220
|
)
|
|
242
221
|
|
|
@@ -268,8 +247,6 @@ def fleet_deploy(
|
|
|
268
247
|
replace=replace,
|
|
269
248
|
latest=latest,
|
|
270
249
|
disabled=disabled_ids(published),
|
|
271
|
-
jev_mode=jev_mode,
|
|
272
|
-
current_jev_mode=current.jev_mode if current else None,
|
|
273
250
|
kinds=version_kinds(published),
|
|
274
251
|
targets=targets or (),
|
|
275
252
|
all_agents=all_agents or (),
|
|
@@ -298,16 +275,6 @@ def fleet_deploy(
|
|
|
298
275
|
output.deployment_unchanged(machine_id)
|
|
299
276
|
return
|
|
300
277
|
|
|
301
|
-
# The mode alone: its own route, which never touches the policy set. A
|
|
302
|
-
# deploy is a full replace of the set the CLI read, and that set leaves out
|
|
303
|
-
# disabled policies' assignments — so a mode change sent as a deploy deleted
|
|
304
|
-
# them, and reverted anything changed since the read (CONTRACT C9.1). A
|
|
305
|
-
# machine with no deployment yet has nothing to lose and no mode to change:
|
|
306
|
-
# its first deploy, carrying the mode, is how it gets one.
|
|
307
|
-
if plan.jev_mode is not None and not plan.set_changes and current is not None:
|
|
308
|
-
_apply_jev_modes(state, cctx, [(machine_id, current.jev_mode)], plan.jev_mode, yes=yes, plan=plan)
|
|
309
|
-
return
|
|
310
|
-
|
|
311
278
|
if not output.is_json():
|
|
312
279
|
output.render_deploy_plan(plan)
|
|
313
280
|
dropped = len(plan.removed)
|
|
@@ -323,7 +290,7 @@ def fleet_deploy(
|
|
|
323
290
|
output.print_cancelled()
|
|
324
291
|
return
|
|
325
292
|
|
|
326
|
-
result = api.deploy_policies(cctx, machine_id, plan.result
|
|
293
|
+
result = api.deploy_policies(cctx, machine_id, plan.result)
|
|
327
294
|
check_race(plan.base, result.deployment)
|
|
328
295
|
|
|
329
296
|
if output.is_json():
|
|
@@ -336,176 +303,6 @@ def fleet_deploy(
|
|
|
336
303
|
output.deployment_applied(machine_id, result.deployment, len(result.policies))
|
|
337
304
|
|
|
338
305
|
|
|
339
|
-
def _jev_mode_consequence(mode: str, count: int) -> str:
|
|
340
|
-
what = f"{count} machine{'s' if count != 1 else ''}"
|
|
341
|
-
tail = {
|
|
342
|
-
"enforce": "; in enforce Jev's deny checks BLOCK calls, and it covers installed packs' checks too",
|
|
343
|
-
"observe": "; in observe Jev only records what it would have decided",
|
|
344
|
-
"off": "; Jev stops on them, a local jev.json included",
|
|
345
|
-
"local": "; each machine's own jev.json decides again",
|
|
346
|
-
}.get(mode, "")
|
|
347
|
-
return f"this sets the Jev mode to {mode} on {what} and leaves their policy sets untouched{tail}"
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
def _apply_jev_modes(
|
|
351
|
-
state: AppState,
|
|
352
|
-
cctx,
|
|
353
|
-
targets: List[tuple],
|
|
354
|
-
mode: str,
|
|
355
|
-
*,
|
|
356
|
-
yes: bool,
|
|
357
|
-
plan=None,
|
|
358
|
-
) -> None:
|
|
359
|
-
"""Set `mode` on each `(machine_id, current_mode)`, through the mode-only route.
|
|
360
|
-
|
|
361
|
-
Shared by `fleet jev-mode` and `fleet deploy --jev-mode` with no set change.
|
|
362
|
-
A machine already on `mode` is left alone; one that fails is reported and
|
|
363
|
-
the rest still go ahead (exit 1 at the end, naming them).
|
|
364
|
-
"""
|
|
365
|
-
after = jev_mode_after(mode)
|
|
366
|
-
rows = [
|
|
367
|
-
{"machineId": mid, "from": cur or "local", "to": after or "local", "changed": (cur or None) != after}
|
|
368
|
-
for mid, cur in targets
|
|
369
|
-
]
|
|
370
|
-
todo = [r for r in rows if r["changed"]]
|
|
371
|
-
if not todo:
|
|
372
|
-
if output.is_json():
|
|
373
|
-
out = {"mode": mode, "machines": rows, "applied": False}
|
|
374
|
-
if plan is not None:
|
|
375
|
-
out = {"plan": plan.to_dict(), "deployment": None, "applied": False}
|
|
376
|
-
output.emit_json(out)
|
|
377
|
-
return
|
|
378
|
-
output.jev_mode_unchanged([r["machineId"] for r in rows], mode)
|
|
379
|
-
return
|
|
380
|
-
|
|
381
|
-
if not output.is_json():
|
|
382
|
-
output.render_jev_mode_plan(rows)
|
|
383
|
-
if not _write.confirm_destructive(
|
|
384
|
-
state, "change the Jev mode on",
|
|
385
|
-
todo[0]["machineId"] if len(todo) == 1 else f"{len(todo)} machines",
|
|
386
|
-
consequence=_jev_mode_consequence(mode, len(todo)),
|
|
387
|
-
assume_yes=yes,
|
|
388
|
-
):
|
|
389
|
-
if output.is_json():
|
|
390
|
-
output.emit_json({"cancelled": True, "applied": False, "machines": rows})
|
|
391
|
-
else:
|
|
392
|
-
output.print_cancelled()
|
|
393
|
-
return
|
|
394
|
-
|
|
395
|
-
failed = []
|
|
396
|
-
last = None
|
|
397
|
-
for row in todo:
|
|
398
|
-
try:
|
|
399
|
-
result, changed = api.set_jev_mode(cctx, row["machineId"], mode)
|
|
400
|
-
except AuthError:
|
|
401
|
-
raise
|
|
402
|
-
except FpCliError as exc:
|
|
403
|
-
# One machine's refusal (a 404, a 403, a 422) is that machine's;
|
|
404
|
-
# the rest still go ahead, and the exit code says it was not all.
|
|
405
|
-
row["error"] = exc.format_message() if hasattr(exc, "format_message") else str(exc)
|
|
406
|
-
failed.append(row["machineId"])
|
|
407
|
-
continue
|
|
408
|
-
last = result
|
|
409
|
-
row["deployment"] = result.deployment
|
|
410
|
-
row["changed"] = changed
|
|
411
|
-
|
|
412
|
-
if output.is_json():
|
|
413
|
-
if plan is not None:
|
|
414
|
-
output.emit_json({
|
|
415
|
-
"plan": plan.to_dict(),
|
|
416
|
-
"deployment": last.to_dict() if last is not None else None,
|
|
417
|
-
"applied": last is not None,
|
|
418
|
-
"jevModeOnly": True,
|
|
419
|
-
})
|
|
420
|
-
else:
|
|
421
|
-
output.emit_json({"mode": mode, "machines": rows, "applied": not failed})
|
|
422
|
-
else:
|
|
423
|
-
output.jev_mode_applied(rows)
|
|
424
|
-
if failed:
|
|
425
|
-
raise ApiError(
|
|
426
|
-
f"the Jev mode could not be changed on {', '.join(failed)}"
|
|
427
|
-
+ ("" if len(failed) == len(todo) else " — the other machines were changed"),
|
|
428
|
-
)
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
def fleet_jev_mode(
|
|
432
|
-
ctx: typer.Context,
|
|
433
|
-
args: Optional[List[str]] = typer.Argument(
|
|
434
|
-
None, metavar="[MACHINE]... MODE",
|
|
435
|
-
help="The machines, then the mode: off, observe, enforce or local. With --all, just the mode.",
|
|
436
|
-
),
|
|
437
|
-
all_machines: bool = typer.Option(
|
|
438
|
-
False, "--all", help="Every machine that has a deployment (machines with none are skipped).",
|
|
439
|
-
),
|
|
440
|
-
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."),
|
|
441
|
-
) -> None:
|
|
442
|
-
"""Set the Jev mode FailproofAI Cloud gives machines, and nothing else.
|
|
443
|
-
|
|
444
|
-
`off`, `observe` or `enforce` override each machine's own mode and
|
|
445
|
-
`jev.json`, a local `off` included; `local` hands the choice back to the
|
|
446
|
-
machine. Jev checks run on FailproofAI Cloud; nothing is installed on the
|
|
447
|
-
machine: under `observe` or `enforce` it sends each checked tool call to
|
|
448
|
-
FailproofAI Cloud. `observe` asks Jev and records what it says while the
|
|
449
|
-
regex policies decide. `enforce` lets Jev's checks BLOCK calls — every `jev`
|
|
450
|
-
policy's deny checks — as well as clear the regex verdicts they review. The
|
|
451
|
-
mode is the machine's: it also governs the checks of every pack installed
|
|
452
|
-
there. A machine connected with `--no-transcripts` never asks, and reports
|
|
453
|
-
`transcripts_disabled`.
|
|
454
|
-
|
|
455
|
-
Only the mode changes: each machine's policy set, disabled policies'
|
|
456
|
-
assignments included, is left exactly as it is, and a machine already on
|
|
457
|
-
the mode is skipped. Each change is a new generation in `fp fleet history`.
|
|
458
|
-
A machine with no deployment has no mode to change — `fp fleet deploy
|
|
459
|
-
<machine> --jev-mode <mode>` gives it its first deployment with one.
|
|
460
|
-
|
|
461
|
-
Needs `policies:write`. With `--json`: `{mode, applied, machines:[{machineId,
|
|
462
|
-
from, to, changed, deployment?, error?}]}`.
|
|
463
|
-
|
|
464
|
-
Examples:
|
|
465
|
-
|
|
466
|
-
* `fp fleet jev-mode ci-runner-01 observe`
|
|
467
|
-
* `fp fleet jev-mode ci-runner-01 ci-runner-02 enforce`
|
|
468
|
-
* `fp fleet jev-mode --all local`
|
|
469
|
-
"""
|
|
470
|
-
state: AppState = ctx.obj
|
|
471
|
-
deny_in_key_mode(state, "fleet jev-mode", _KEY_MODE_REASON)
|
|
472
|
-
words = list(args or [])
|
|
473
|
-
if not words:
|
|
474
|
-
raise click.UsageError("pass the mode: off, observe, enforce or local")
|
|
475
|
-
mode, machine_ids = words[-1], list(dict.fromkeys(words[:-1]))
|
|
476
|
-
if mode not in JEV_MODES:
|
|
477
|
-
raise click.UsageError(f"{mode!r} is not a Jev mode — one of {', '.join(JEV_MODES)}")
|
|
478
|
-
if all_machines and machine_ids:
|
|
479
|
-
raise click.UsageError("--all sets every machine; do not name machines as well")
|
|
480
|
-
if not all_machines and not machine_ids:
|
|
481
|
-
raise click.UsageError("name at least one machine, or pass --all")
|
|
482
|
-
cctx = require_auth(state)
|
|
483
|
-
|
|
484
|
-
deployments = {d.machine_id: d for d in api.list_deployments(cctx)}
|
|
485
|
-
if all_machines:
|
|
486
|
-
targets = [(mid, deployments[mid].jev_mode) for mid in sorted(deployments)]
|
|
487
|
-
else:
|
|
488
|
-
known = {m.machine_id for m in api.list_machines(cctx)} | set(deployments)
|
|
489
|
-
unknown = [mid for mid in machine_ids if mid not in known]
|
|
490
|
-
if unknown:
|
|
491
|
-
raise NotFoundError(f"no machine {', '.join(repr(u) for u in unknown)} has checked in")
|
|
492
|
-
bare = [mid for mid in machine_ids if mid not in deployments]
|
|
493
|
-
if bare:
|
|
494
|
-
raise ApiError(
|
|
495
|
-
f"{', '.join(bare)} {'has' if len(bare) == 1 else 'have'} no deployment, so there is no Jev "
|
|
496
|
-
"mode to change",
|
|
497
|
-
hint=f"`fp fleet deploy <machine> --jev-mode {mode}` gives a machine its first deployment with the mode",
|
|
498
|
-
)
|
|
499
|
-
targets = [(mid, deployments[mid].jev_mode) for mid in machine_ids]
|
|
500
|
-
if not targets:
|
|
501
|
-
if output.is_json():
|
|
502
|
-
output.emit_json({"mode": mode, "machines": [], "applied": False})
|
|
503
|
-
else:
|
|
504
|
-
output.info("no machine has a deployment yet — nothing to set")
|
|
505
|
-
return
|
|
506
|
-
_apply_jev_modes(state, cctx, targets, mode, yes=yes)
|
|
507
|
-
|
|
508
|
-
|
|
509
306
|
def fleet_diff(
|
|
510
307
|
ctx: typer.Context,
|
|
511
308
|
machine_id: Optional[str] = typer.Argument(None, help="Machine id. Omit for the whole fleet."),
|
|
@@ -606,7 +403,7 @@ def fleet_rollback(
|
|
|
606
403
|
_require_machine(cctx, machine_id)
|
|
607
404
|
current = api.get_deployment(cctx, machine_id)
|
|
608
405
|
# The mode that generation carried comes back with it, NULL (`local`)
|
|
609
|
-
# included
|
|
406
|
+
# included, as the server reports it.
|
|
610
407
|
# Read it first, so the prompt says so rather than the fleet finding out.
|
|
611
408
|
target = next(
|
|
612
409
|
(e for e in api.deployment_history(cctx, machine_id) if e.get("deployment") == deployment), None)
|
|
@@ -669,12 +466,11 @@ def register(app: typer.Typer) -> None:
|
|
|
669
466
|
no_args_is_help=True,
|
|
670
467
|
rich_markup_mode="markdown",
|
|
671
468
|
context_settings={"help_option_names": ["-h", "--help"]},
|
|
672
|
-
help="The fleet and what each machine enforces (list / show / deploy /
|
|
469
|
+
help="The fleet and what each machine enforces (list / show / deploy / diff / history / rollback / rename).",
|
|
673
470
|
)
|
|
674
471
|
fleet_app.command("list", epilog=GLOBALS_EPILOG)(fleet_list)
|
|
675
472
|
fleet_app.command("show", epilog=GLOBALS_EPILOG)(fleet_show)
|
|
676
473
|
fleet_app.command("deploy", epilog=GLOBALS_EPILOG)(fleet_deploy)
|
|
677
|
-
fleet_app.command("jev-mode", epilog=GLOBALS_EPILOG)(fleet_jev_mode)
|
|
678
474
|
fleet_app.command("diff", epilog=GLOBALS_EPILOG)(fleet_diff)
|
|
679
475
|
fleet_app.command("history", epilog=GLOBALS_EPILOG)(fleet_history)
|
|
680
476
|
fleet_app.command("rollback", epilog=GLOBALS_EPILOG)(fleet_rollback)
|
|
@@ -14,7 +14,8 @@ import typer
|
|
|
14
14
|
|
|
15
15
|
from .. import client as api
|
|
16
16
|
from .. import output
|
|
17
|
-
from ..
|
|
17
|
+
from .. import _click_compat as click
|
|
18
|
+
from .._context import GLOBALS_EPILOG, AppState, require_auth, resolve_dates, validate_limit
|
|
18
19
|
from ..errors import ApiError, ForbiddenError, NotFoundError
|
|
19
20
|
from . import _write
|
|
20
21
|
|
|
@@ -68,12 +69,40 @@ def _fail(state: AppState, exc: Exception, *, incident_id: str = "") -> None:
|
|
|
68
69
|
raise exc
|
|
69
70
|
|
|
70
71
|
|
|
72
|
+
def _issue_filters(
|
|
73
|
+
agent_id, env, machine, source, audit_id, assignee, archived, search, since,
|
|
74
|
+
) -> dict:
|
|
75
|
+
if archived is not None and archived not in ("false", "true", "only", "all"):
|
|
76
|
+
raise click.UsageError("--archived must be false, only or all")
|
|
77
|
+
if source is not None:
|
|
78
|
+
bad = [s for s in source.split(",") if s.strip() and s.strip() not in ("alert", "audit", "manual")]
|
|
79
|
+
if bad:
|
|
80
|
+
raise click.UsageError("--source must be alert, audit or manual (CSV)")
|
|
81
|
+
opened_from = None
|
|
82
|
+
if since is not None:
|
|
83
|
+
opened_from, _ = resolve_dates(since, None, None)
|
|
84
|
+
return {
|
|
85
|
+
"agent": agent_id, "environment": env, "machine": machine, "source": source,
|
|
86
|
+
"audit_id": audit_id, "assignee": assignee, "archived": archived, "q": search,
|
|
87
|
+
"opened_from": opened_from,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
71
91
|
def incidents_list(
|
|
72
92
|
ctx: typer.Context,
|
|
73
93
|
state_filter: Optional[str] = typer.Option(None, "--state", help="Filter by state(s): firing, acknowledged, resolved, closed (CSV)."),
|
|
74
94
|
alert_id: Optional[str] = typer.Option(None, "--alert-id", help="Only incidents for this alert."),
|
|
75
95
|
limit: int = typer.Option(50, "--limit", "-n", help="Max incidents to return."),
|
|
76
96
|
show_id: bool = typer.Option(False, "--show-id", help="Show the full incident id instead of the short form (always full in --json)."),
|
|
97
|
+
agent_id: Optional[str] = typer.Option(None, "--agent-id", help="Only issues touching these agent ids (CSV)."),
|
|
98
|
+
env: Optional[str] = typer.Option(None, "--env", help="Only these environments (CSV)."),
|
|
99
|
+
machine: Optional[str] = typer.Option(None, "--machine", help="Only these machine ids (CSV)."),
|
|
100
|
+
source: Optional[str] = typer.Option(None, "--source", help="alert, audit and/or manual (CSV)."),
|
|
101
|
+
audit_id: Optional[str] = typer.Option(None, "--audit-id", help="Only issues from these audits (CSV of ids)."),
|
|
102
|
+
assignee: Optional[str] = typer.Option(None, "--assignee", help="Emails, `me` or `unassigned` (CSV)."),
|
|
103
|
+
archived: Optional[str] = typer.Option(None, "--archived", help="false (default), only or all."),
|
|
104
|
+
search: Optional[str] = typer.Option(None, "--search", help="Case-insensitive text in the title or summary."),
|
|
105
|
+
since: Optional[str] = typer.Option(None, "--since", help="Opened within: 15m 1h 6h 24h 7d."),
|
|
77
106
|
) -> None:
|
|
78
107
|
"""List incidents in a boxed table (newest-opened first).
|
|
79
108
|
|
|
@@ -94,7 +123,8 @@ def incidents_list(
|
|
|
94
123
|
_validate_states(state_filter)
|
|
95
124
|
validate_limit(limit)
|
|
96
125
|
cctx = require_auth(state)
|
|
97
|
-
|
|
126
|
+
extra = _issue_filters(agent_id, env, machine, source, audit_id, assignee, archived, search, since)
|
|
127
|
+
incidents = api.list_incidents(cctx, state=state_filter, alert_id=alert_id, limit=limit, filters=extra)
|
|
98
128
|
if state.json:
|
|
99
129
|
output.emit_json({"issues": incidents})
|
|
100
130
|
return
|
|
@@ -105,6 +135,16 @@ def incidents_list(
|
|
|
105
135
|
def incidents_count(
|
|
106
136
|
ctx: typer.Context,
|
|
107
137
|
state_filter: Optional[str] = typer.Option(None, "--state", help="Filter by state(s) (CSV)."),
|
|
138
|
+
alert_id: Optional[str] = typer.Option(None, "--alert-id", help="Only issues from these alerts (CSV of ids)."),
|
|
139
|
+
agent_id: Optional[str] = typer.Option(None, "--agent-id", help="Only issues touching these agent ids (CSV)."),
|
|
140
|
+
env: Optional[str] = typer.Option(None, "--env", help="Only these environments (CSV)."),
|
|
141
|
+
machine: Optional[str] = typer.Option(None, "--machine", help="Only these machine ids (CSV)."),
|
|
142
|
+
source: Optional[str] = typer.Option(None, "--source", help="alert, audit and/or manual (CSV)."),
|
|
143
|
+
audit_id: Optional[str] = typer.Option(None, "--audit-id", help="Only issues from these audits (CSV of ids)."),
|
|
144
|
+
assignee: Optional[str] = typer.Option(None, "--assignee", help="Emails, `me` or `unassigned` (CSV)."),
|
|
145
|
+
archived: Optional[str] = typer.Option(None, "--archived", help="false (default), only or all."),
|
|
146
|
+
search: Optional[str] = typer.Option(None, "--search", help="Case-insensitive text in the title or summary."),
|
|
147
|
+
since: Optional[str] = typer.Option(None, "--since", help="Opened within: 15m 1h 6h 24h 7d."),
|
|
108
148
|
) -> None:
|
|
109
149
|
"""Count incidents (optionally by state) as a compact stat card.
|
|
110
150
|
|
|
@@ -118,7 +158,10 @@ def incidents_count(
|
|
|
118
158
|
state: AppState = ctx.obj
|
|
119
159
|
_validate_states(state_filter)
|
|
120
160
|
cctx = require_auth(state)
|
|
121
|
-
|
|
161
|
+
extra = _issue_filters(agent_id, env, machine, source, audit_id, assignee, archived, search, since)
|
|
162
|
+
if alert_id:
|
|
163
|
+
extra["alert_id"] = alert_id
|
|
164
|
+
count = api.count_incidents(cctx, state=state_filter, filters=extra)
|
|
122
165
|
if state.json:
|
|
123
166
|
output.emit_json({"count": count})
|
|
124
167
|
else:
|
|
@@ -261,10 +304,11 @@ def incidents_close(
|
|
|
261
304
|
|
|
262
305
|
For an issue that came from an audit, this also marks the finding `dismissed`. It does
|
|
263
306
|
NOT write the finding's org-wide suppression: closing one issue never hides that pattern
|
|
264
|
-
in your other audits. Use `fp audits finding-
|
|
307
|
+
in your other audits. Use `fp audits dismiss <finding-id>` for that.
|
|
265
308
|
|
|
266
|
-
Needs `issues:close`. Exits
|
|
267
|
-
and closing must not overwrite the record that someone
|
|
309
|
+
Needs `issues:close`. Exits 1 (the server answers 409 conflict) if the issue already
|
|
310
|
+
ended — an issue ends once, and closing must not overwrite the record that someone
|
|
311
|
+
believed they had fixed it.
|
|
268
312
|
|
|
269
313
|
With `--json`: `{"closed": true, "id": "<id>"}` (or `{cancelled: true}` on a declined
|
|
270
314
|
prompt).
|
|
@@ -666,6 +710,37 @@ def incidents_open(
|
|
|
666
710
|
output.render_incident_opened(summary=summary, severity=sev, state=st, title=hero)
|
|
667
711
|
|
|
668
712
|
|
|
713
|
+
def incidents_facets(
|
|
714
|
+
ctx: typer.Context,
|
|
715
|
+
state_filter: Optional[str] = typer.Option(None, "--state", help="Filter by state(s) (CSV)."),
|
|
716
|
+
alert_id: Optional[str] = typer.Option(None, "--alert-id", help="Only issues from these alerts (CSV of ids)."),
|
|
717
|
+
agent_id: Optional[str] = typer.Option(None, "--agent-id", help="Only issues touching these agent ids (CSV)."),
|
|
718
|
+
env: Optional[str] = typer.Option(None, "--env", help="Only these environments (CSV)."),
|
|
719
|
+
machine: Optional[str] = typer.Option(None, "--machine", help="Only these machine ids (CSV)."),
|
|
720
|
+
source: Optional[str] = typer.Option(None, "--source", help="alert, audit and/or manual (CSV)."),
|
|
721
|
+
audit_id: Optional[str] = typer.Option(None, "--audit-id", help="Only issues from these audits (CSV of ids)."),
|
|
722
|
+
assignee: Optional[str] = typer.Option(None, "--assignee", help="Emails, `me` or `unassigned` (CSV)."),
|
|
723
|
+
archived: Optional[str] = typer.Option(None, "--archived", help="false (default), only or all."),
|
|
724
|
+
search: Optional[str] = typer.Option(None, "--search", help="Case-insensitive text in the title or summary."),
|
|
725
|
+
since: Optional[str] = typer.Option(None, "--since", help="Opened within: 15m 1h 6h 24h 7d."),
|
|
726
|
+
) -> None:
|
|
727
|
+
"""The values the issue filters can take right now (agents, environments, machines,
|
|
728
|
+
sources, assignees), with counts — the dashboard inbox's filter bar. Needs `issues:read`.
|
|
729
|
+
|
|
730
|
+
Example:
|
|
731
|
+
|
|
732
|
+
* `fp issues facets --state firing,acknowledged`
|
|
733
|
+
"""
|
|
734
|
+
from . import _cloud
|
|
735
|
+
state: AppState = ctx.obj
|
|
736
|
+
_validate_states(state_filter)
|
|
737
|
+
extra = _issue_filters(agent_id, env, machine, source, audit_id, assignee, archived, search, since)
|
|
738
|
+
params = {"state": state_filter, "alert_id": alert_id, **extra}
|
|
739
|
+
data = api.cloud_request(require_auth(state), "GET", "/api/issues/facets",
|
|
740
|
+
params={k: v for k, v in params.items() if v is not None})
|
|
741
|
+
_cloud.emit(state, data, "Issue facets")
|
|
742
|
+
|
|
743
|
+
|
|
669
744
|
def register(app: typer.Typer) -> None:
|
|
670
745
|
inc = typer.Typer(
|
|
671
746
|
no_args_is_help=True,
|
|
@@ -675,6 +750,7 @@ def register(app: typer.Typer) -> None:
|
|
|
675
750
|
)
|
|
676
751
|
inc.command("list", epilog=GLOBALS_EPILOG)(incidents_list)
|
|
677
752
|
inc.command("count", epilog=GLOBALS_EPILOG)(incidents_count)
|
|
753
|
+
inc.command("facets", epilog=GLOBALS_EPILOG)(incidents_facets)
|
|
678
754
|
inc.command("show", epilog=GLOBALS_EPILOG)(incidents_show)
|
|
679
755
|
inc.command("ack", epilog=GLOBALS_EPILOG)(incidents_ack)
|
|
680
756
|
inc.command("assign", epilog=GLOBALS_EPILOG)(incidents_assign)
|
|
@@ -24,6 +24,9 @@ _LIST_KINDS = {
|
|
|
24
24
|
"hooks": ("hook_names", "events:read", "Hook names seen across events.", "seen across events"),
|
|
25
25
|
"tools": ("tool_names", "events:read", "Tool names seen across events.", "seen across events"),
|
|
26
26
|
"error_types": ("error_types", "events:read", "Error types seen across events.", "seen across events"),
|
|
27
|
+
"trigger_events": ("trigger_events", "events:read", "Hook trigger events seen across events.", "seen across events"),
|
|
28
|
+
"eval_envs": ("eval_environments", "evaluations:read", "Environments that have evaluation results.", "with evaluation results"),
|
|
29
|
+
"metric_keys": ("metric_keys", "evaluations:read", "Evaluation metric keys (metrics, not scores).", "evaluation metric keys"),
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
def _make_list_cmd(name: str, facet_key: str, perm: str, summary: str, description: str):
|
|
@@ -56,7 +59,7 @@ def register(app: typer.Typer) -> None:
|
|
|
56
59
|
context_settings={"help_option_names": ["-h", "--help"]},
|
|
57
60
|
help="List the distinct values behind the dashboard's filter dropdowns. Subcommands: "
|
|
58
61
|
"**envs**, **agents**, **event_types**, **score_filters**, **models**, **hooks**, "
|
|
59
|
-
"**tools**, **error_types**.",
|
|
62
|
+
"**tools**, **error_types**, **trigger_events**, **eval_envs**, **metric_keys**.",
|
|
60
63
|
)
|
|
61
64
|
for name, (facet_key, perm, summary, description) in _LIST_KINDS.items():
|
|
62
65
|
list_app.command(name, epilog=GLOBALS_EPILOG)(_make_list_cmd(name, facet_key, perm, summary, description))
|
|
@@ -316,4 +316,21 @@ def register(app: typer.Typer) -> None:
|
|
|
316
316
|
orgs_app.command("switch", epilog=GLOBALS_EPILOG)(org_switch)
|
|
317
317
|
orgs_app.command("current", epilog=GLOBALS_EPILOG)(org_current)
|
|
318
318
|
orgs_app.command("perms", epilog=GLOBALS_EPILOG)(org_perms)
|
|
319
|
+
orgs_app.command("create", epilog=GLOBALS_EPILOG)(org_create)
|
|
319
320
|
app.add_typer(orgs_app, name="orgs")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def org_create(ctx: typer.Context, name: str = typer.Option(..., "--name"),
|
|
324
|
+
slug: Optional[str] = typer.Option(None, "--slug")) -> None:
|
|
325
|
+
"""Create an organization (session only; does not switch the saved org)."""
|
|
326
|
+
from . import _cloud
|
|
327
|
+
from .account_cmds import request
|
|
328
|
+
from .. import _click_compat as click
|
|
329
|
+
_cloud.session_only(ctx.obj, "orgs create")
|
|
330
|
+
if not name.strip():
|
|
331
|
+
raise click.UsageError("--name cannot be empty")
|
|
332
|
+
import re
|
|
333
|
+
slug = slug or re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-")
|
|
334
|
+
if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", slug):
|
|
335
|
+
raise click.UsageError("--slug must be lowercase letters/digits separated by hyphens")
|
|
336
|
+
request(ctx, "POST", "/api/orgs", {"name": name, "slug": slug})
|