failproofai 1.0.6-beta.1 → 1.0.7-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 +3 -3
- 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 +4 -4
- 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 +4 -4
- 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.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +8 -7
- 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 +7 -5
- 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 +6 -6
- 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 +13 -11
- 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 +8 -7
- 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 +7 -7
- 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 +7 -6
- 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 +41 -8
- package/.next/standalone/.next/server/app/settings/page.js +12 -9
- 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]__1msfs-h._.js → [externals]__19_pzeq._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__1_bftcl._.js → [externals]__1j-zsg5._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1_r2rbg._.js → [root-of-the-server]__1bf34x4._.js} +5 -7
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
- package/.next/standalone/.next/server/chunks/{_1c3k-8x._.js → _1q5i8mb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +5 -0
- package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +4 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__02npjtd._.js → [root-of-the-server]__056wjo4._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0576iuy._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +22 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0p-5p8u._.js → [root-of-the-server]__0n0xg95._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qxnccm._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__013jr2b._.js → [root-of-the-server]__0rwtwpm._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0da85px._.js → [root-of-the-server]__11mayhe._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cxe_2_._.js → [root-of-the-server]__1mf3zp6._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1x3j9mj._.js → [root-of-the-server]__1p28m0u._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ftmoxc._.js → [root-of-the-server]__1pprgri._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1p2otjt._.js → [root-of-the-server]__1q4p5b8._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0qlygw6._.js → _0cf1olg._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1_ddpm3._.js → _135-c3y._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1es2j7i._.js → _1gb0ifp._.js} +5 -5
- package/.next/standalone/.next/server/chunks/ssr/{_1_qswah._.js → _1q46vxx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_0h_douw._.js → _1w_5l7t._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +4 -4
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- 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_02tf1h4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_utils_ts_0az0sfq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +3 -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/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.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/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +56 -23
- package/.next/standalone/.next/static/chunks/{06fjx8-0_qufs.js → 00i9thnpht4l1.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3ht0xsco46pul.js → 0289oapk24zql.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1qf2uiipatr80.js → 0pyy9m5cm8zku.js} +1 -1
- package/.next/standalone/.next/static/chunks/15heikmy3yjzc.js +1 -0
- package/.next/standalone/.next/static/chunks/285spx855h_3r.css +2 -0
- package/.next/standalone/.next/static/chunks/{02vwr5js1x28j.js → 2plg9qae1yejv.js} +1 -1
- package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
- package/.next/standalone/.next/static/chunks/{2x776s0x3n_tu.js → 2tqszh84mp_2l.js} +1 -1
- package/.next/standalone/.next/static/chunks/34jpdb2xjm-x7.js +1 -0
- package/.next/standalone/.next/static/chunks/3cdkxrukvlvul.js +1 -0
- package/.next/standalone/.next/static/chunks/3dnd-_29uiaj3.js +6 -0
- package/.next/standalone/.next/static/chunks/{0y227_ohdqwvn.js → 3vp7c3knkh8mr.js} +1 -1
- package/.next/standalone/.next/static/chunks/{27_bwbg8sv6vj.js → 3vwnzfx9q80yl.js} +1 -1
- package/.next/standalone/app/actions/get-jev-config.ts +409 -0
- package/.next/standalone/app/actions/pack-actions.ts +12 -0
- package/.next/standalone/app/actions/update-jev-config.ts +420 -0
- package/.next/standalone/app/components/jev-notices.tsx +96 -0
- package/.next/standalone/app/policies/hooks-client.tsx +9 -0
- package/.next/standalone/app/settings/jev-panel.tsx +469 -0
- package/.next/standalone/app/settings/page.tsx +20 -1
- package/.next/standalone/app/settings/settings-client.tsx +27 -1
- package/.next/standalone/app/settings/settings.css +79 -0
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +51 -3
- package/.next/standalone/fp-cloud-cli/README.md +1 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +44 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +226 -5
- package/.next/standalone/fp-cloud-cli/fp_cli/models.py +11 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +68 -1
- package/.next/standalone/fp-cloud-cli/pyproject.toml +1 -1
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +17 -2
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +7 -4
- package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +184 -0
- package/.next/standalone/fp-cloud-cli/uv.lock +3 -3
- package/.next/standalone/package.json +11 -11
- package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__init__.py +2 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/authoring.py +24 -1
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/runtime.py +41 -6
- package/.next/standalone/sdk/python/tests/test_evaluator_runtime.py +88 -0
- package/.next/standalone/sdk/python/uv.lock +15 -15
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +115 -2
- package/dist/cli.mjs +10982 -3787
- package/dist/index.js +19 -1
- package/dist/worker.mjs +13761 -7834
- package/package.json +11 -11
- package/pi-extension/index.ts +11 -0
- package/scripts/build-policy-pack.mjs +39 -2
- package/src/hooks/builtin-policies.ts +21 -3
- package/src/hooks/cloud-enrollment-cli.ts +1 -1
- package/src/hooks/cloud-managed-policies.ts +22 -0
- package/src/hooks/custom-hooks-loader.ts +45 -7
- package/src/hooks/custom-hooks-registry.ts +45 -1
- package/src/hooks/daemon-client.ts +128 -50
- package/src/hooks/daemon-service.ts +30 -0
- package/src/hooks/effective-reviewers.ts +79 -0
- package/src/hooks/first-run-gate.ts +5 -0
- package/src/hooks/fp-home.ts +23 -0
- package/src/hooks/handler.ts +265 -6
- package/src/hooks/hook-activity-store.ts +105 -1
- package/src/hooks/hook-telemetry.ts +41 -0
- package/src/hooks/integrations.ts +3 -2
- package/src/hooks/jev-activity.ts +385 -0
- package/src/hooks/jev-cli.ts +1193 -0
- package/src/hooks/loader-utils.ts +6 -0
- package/src/hooks/manager.ts +4 -4
- package/src/hooks/pack-cli.ts +237 -11
- package/src/hooks/pack-manifest.ts +479 -7
- package/src/hooks/pack-store.ts +156 -10
- package/src/hooks/policy-authority.ts +333 -0
- package/src/hooks/policy-catalog.ts +65 -0
- package/src/hooks/policy-evaluator.ts +940 -796
- package/src/hooks/policy-registry.ts +25 -0
- package/src/hooks/policy-reviewability.ts +229 -0
- package/src/hooks/policy-types.ts +126 -0
- package/src/hooks/semantic/combine.ts +541 -0
- package/src/hooks/semantic/compile.ts +176 -0
- package/src/hooks/semantic/decide.ts +392 -0
- package/src/hooks/semantic/envelope.ts +1296 -0
- package/src/hooks/semantic/evaluator.ts +547 -0
- package/src/hooks/semantic/facts.ts +292 -0
- package/src/hooks/semantic/intent.ts +1190 -0
- package/src/hooks/semantic/jev-client.ts +643 -0
- package/src/hooks/semantic/jev-config.ts +594 -0
- package/src/hooks/semantic/jev-review.ts +374 -0
- package/src/hooks/semantic/jev-stats.ts +289 -0
- package/src/hooks/semantic/jev-throttle.ts +421 -0
- package/src/hooks/semantic/pack-policies.ts +251 -0
- package/src/hooks/semantic/policies.ts +596 -0
- package/src/hooks/semantic/precondition-names.ts +60 -0
- package/src/hooks/semantic/preconditions.ts +58 -0
- package/src/hooks/semantic/redact.ts +2910 -0
- package/src/hooks/semantic/types.ts +145 -0
- package/src/hooks/semver-precedence.ts +128 -0
- package/src/hooks/worker-server.ts +119 -26
- package/src/index.ts +6 -0
- package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +0 -22
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +0 -10
- package/.next/standalone/.next/static/chunks/0cbvytf8epn34.js +0 -1
- package/.next/standalone/.next/static/chunks/0qmsunv04x4na.css +0 -2
- package/.next/standalone/.next/static/chunks/1p4xauf-zu6zk.js +0 -1
- package/.next/standalone/.next/static/chunks/1uc_s_dftgxiq.js +0 -6
- package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
- package/.next/standalone/.next/static/chunks/2lvr2pt70qn14.js +0 -1
- /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_ssgManifest.js +0 -0
|
@@ -1,12 +1,60 @@
|
|
|
1
1
|
# Changelog — `fp` CLI
|
|
2
2
|
|
|
3
|
-
## 0.0.
|
|
3
|
+
## 0.0.2b0 — 2026-09-22
|
|
4
4
|
|
|
5
|
-
Open for the next release. `0.0.
|
|
6
|
-
moved the version here automatically; nothing has landed against `0.0.
|
|
5
|
+
Open for the next release. `0.0.1` published on 2026-09-22 and the `bump` job
|
|
6
|
+
moved the version here automatically; nothing has landed against `0.0.2b0` yet.
|
|
7
7
|
Add entries as changes merge — this section becomes the GitHub Release body when
|
|
8
8
|
it ships.
|
|
9
9
|
|
|
10
|
+
## 0.0.1 — 2026-09-22
|
|
11
|
+
|
|
12
|
+
**First stable release.** `0.0.1b1` and `0.0.1b2` are the two betas behind it; the
|
|
13
|
+
`0.0.1b3` line the `bump` job opened is cut stable here rather than published, because
|
|
14
|
+
the server endpoints these commands drive are deployed to FailproofAI Cloud and the
|
|
15
|
+
CLI half should not stay a pre-release behind them. Until now `pipx install
|
|
16
|
+
fp-cloud-cli` resolved a beta only because no stable existed — a default that would
|
|
17
|
+
have silently changed the day one did. `Development Status` moves to
|
|
18
|
+
`5 - Production/Stable` with the version, so the classifier and the one string pip
|
|
19
|
+
reads say the same thing.
|
|
20
|
+
|
|
21
|
+
Nothing about the command surface changes at the cut: the entries below are the
|
|
22
|
+
entire diff from `0.0.1b2`, and `0.0.2b0` opens the next beta line.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- `fp issues close <id>` — end an issue as won't-fix. Distinct from `resolve`: a
|
|
27
|
+
recurring audit finding reopens a **resolved** issue and leaves a **closed** one
|
|
28
|
+
alone. On an audit issue it marks the finding `dismissed`, without writing the
|
|
29
|
+
org-wide fingerprint suppression that `fp audits dismiss` writes. Exit 9 if the
|
|
30
|
+
issue already ended. (#815)
|
|
31
|
+
- `fp issues archive <id>` / `unarchive <id>` — take an issue off the board and put
|
|
32
|
+
it back. Orthogonal to state, allowed in any state, no confirmation prompt. (#815)
|
|
33
|
+
- `fp issues clear (--audit <id> | --all-audits | --everything) [--dry-run]` — resolve
|
|
34
|
+
every open issue in a scope plus the audit findings behind them, in one server-side
|
|
35
|
+
transaction. Exactly one scope flag is required. Needs `issues:close` **and**
|
|
36
|
+
`audits:write`. Writes no suppression, so anything still broken reopens its issue
|
|
37
|
+
on the next run. The confirm names the count from a dry run that shares its
|
|
38
|
+
scope predicate with the write. (#815)
|
|
39
|
+
- `issues list --state closed` is accepted, and `Incident` carries `closed_at` and
|
|
40
|
+
`archived_at`. (#815)
|
|
41
|
+
|
|
42
|
+
### Fixes
|
|
43
|
+
|
|
44
|
+
- The recurrence sentence said two different things in one release. `audits resolve`'s
|
|
45
|
+
confirm line (`a genuine recurrence re-opens as new`), the skill and the command
|
|
46
|
+
reference implied a recurrence opens a **different** issue; `issues close --help`, the
|
|
47
|
+
audits guide and the CLI reference said the resolved issue **reopens**. The second is
|
|
48
|
+
the one the rest of the model needs — a `closed` issue has to have something to stay
|
|
49
|
+
closed through — so `reopens` is now what every surface says. (#815)
|
|
50
|
+
- `issues clear --help` claimed the `--dry-run` count "cannot disagree" with the write.
|
|
51
|
+
The two requests share a scope predicate, not a row set, so an issue entering the scope
|
|
52
|
+
between them is cleared without being in the confirmed number — correct for a scoped
|
|
53
|
+
clear, but not the guarantee that was written. The help now states the real one, and
|
|
54
|
+
names the closing line as the count of what changed. (#815)
|
|
55
|
+
- The skill answered "clear all our issues" with `--all-audits`, which leaves alert-born
|
|
56
|
+
and hand-opened issues untouched. Settling the scope is now the first step. (#815)
|
|
57
|
+
|
|
10
58
|
## 0.0.1b2 — 2026-08-25
|
|
11
59
|
|
|
12
60
|
Open for the next release. `0.0.1b1` published on 2026-08-24 and the `bump` job
|
|
@@ -90,7 +90,7 @@ fp keys list|show|create|update|disable|regenerate # API keys (secret shown
|
|
|
90
90
|
fp users list|show|create|update|disable|enable
|
|
91
91
|
fp settings list|schema|set
|
|
92
92
|
fp alerts list|show|create|update|delete|test
|
|
93
|
-
fp issues list|count|show|ack|assign|resolve|comment-add|comment-list|comment-delete|subscribe|subscribers|unsubscribe|open
|
|
93
|
+
fp issues list|count|show|ack|assign|resolve|close|archive|unarchive|clear|comment-add|comment-list|comment-delete|subscribe|subscribers|unsubscribe|open
|
|
94
94
|
fp audits list|show|create|edit|delete|run|runs|findings|finding # scheduled audits
|
|
95
95
|
fp audits ack|assign|resolve|dismiss|mute|reopen # triage a finding
|
|
96
96
|
fp audits context-show|context-set|context-refresh # reference context
|
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.0.
|
|
1
|
+
__version__ = "0.0.2b0"
|
|
@@ -1161,6 +1161,50 @@ def resolve_incident(ctx: ClientContext, incident_id: str) -> None:
|
|
|
1161
1161
|
_post_json(ctx, f"/api/issues/{incident_id}/resolve")
|
|
1162
1162
|
|
|
1163
1163
|
|
|
1164
|
+
def close_incident(ctx: ClientContext, incident_id: str) -> None:
|
|
1165
|
+
"""POST /api/issues/{id}/close — end the issue as won't-fix.
|
|
1166
|
+
|
|
1167
|
+
The sibling of ``resolve_incident``, and the difference is what happens next:
|
|
1168
|
+
a resolved issue REOPENS if its audit finding recurs, a closed one does not.
|
|
1169
|
+
409 if the issue already ended.
|
|
1170
|
+
"""
|
|
1171
|
+
_post_json(ctx, f"/api/issues/{incident_id}/close")
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
def set_incident_archived(ctx: ClientContext, incident_id: str, archived: bool) -> None:
|
|
1175
|
+
"""POST /api/issues/{id}/archive or /unarchive — hide from the board, or restore.
|
|
1176
|
+
|
|
1177
|
+
Does not change ``state``: an issue ends once, and archiving says nothing
|
|
1178
|
+
about how. Idempotent on both sides, so a caller never has to read the
|
|
1179
|
+
current value first.
|
|
1180
|
+
"""
|
|
1181
|
+
verb = "archive" if archived else "unarchive"
|
|
1182
|
+
_post_json(ctx, f"/api/issues/{incident_id}/{verb}")
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
def clear_issues(
|
|
1186
|
+
ctx: ClientContext,
|
|
1187
|
+
*,
|
|
1188
|
+
scope: str,
|
|
1189
|
+
audit_id: Optional[str] = None,
|
|
1190
|
+
dry_run: bool = False,
|
|
1191
|
+
) -> Dict[str, Any]:
|
|
1192
|
+
"""POST /api/issues/bulk-clear — resolve every live issue in a scope.
|
|
1193
|
+
|
|
1194
|
+
``scope`` is ``audit`` (with ``audit_id``), ``all_audits`` or ``everything``.
|
|
1195
|
+
Returns ``{issues, findings, dry_run, scope}``.
|
|
1196
|
+
|
|
1197
|
+
Needs ``issues:close`` AND ``audits:write`` — clearing resolves the audit
|
|
1198
|
+
findings behind the issues, and the second grant is what stops a key that
|
|
1199
|
+
cannot touch one finding from resolving all of them at once.
|
|
1200
|
+
"""
|
|
1201
|
+
body: Dict[str, Any] = {"scope": scope, "dry_run": dry_run}
|
|
1202
|
+
if audit_id:
|
|
1203
|
+
body["audit_id"] = audit_id
|
|
1204
|
+
data = _post_json(ctx, "/api/issues/bulk-clear", body)
|
|
1205
|
+
return data if isinstance(data, dict) else {}
|
|
1206
|
+
|
|
1207
|
+
|
|
1164
1208
|
def list_incident_comments(ctx: ClientContext, incident_id: str) -> List[IncidentComment]:
|
|
1165
1209
|
data = _get_json(ctx, f"/api/issues/{incident_id}/comments")
|
|
1166
1210
|
return [IncidentComment.from_dict(c) for c in (data if isinstance(data, list) else [])]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Incident triage: incidents list/count/show/ack/assign/resolve/comment*/subscribe*/open.
|
|
1
|
+
"""Incident triage: incidents list/count/show/ack/assign/resolve/close/archive/clear/comment*/subscribe*/open.
|
|
2
2
|
|
|
3
3
|
Incidents live under /api/issues but the triage workflow is distinct, so it
|
|
4
4
|
gets its own top-level group. The id IS the handle (incidents have no human name), so the
|
|
@@ -19,13 +19,16 @@ from ..errors import ApiError, ForbiddenError, NotFoundError
|
|
|
19
19
|
from . import _write
|
|
20
20
|
|
|
21
21
|
_SEVERITIES = ("info", "warning", "critical")
|
|
22
|
-
|
|
22
|
+
# Mirrors `issue_sync::ISSUE_STATES` on the server. `closed` is the second
|
|
23
|
+
# TERMINAL state (won't-fix); missing it here would reject `--state closed`
|
|
24
|
+
# client-side with exit 2 for a state the server accepts.
|
|
25
|
+
_STATES = ("firing", "acknowledged", "resolved", "closed")
|
|
23
26
|
_UUID_RE = re.compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
def _validate_states(value: Optional[str]) -> None:
|
|
27
30
|
"""Reject an unknown ``--state`` value up front (exit 2) rather than letting the server
|
|
28
|
-
silently drop it and return a confusing set. Accepts a CSV of firing/acknowledged/resolved."""
|
|
31
|
+
silently drop it and return a confusing set. Accepts a CSV of firing/acknowledged/resolved/closed."""
|
|
29
32
|
if value is None:
|
|
30
33
|
return
|
|
31
34
|
for s in value.split(","):
|
|
@@ -67,7 +70,7 @@ def _fail(state: AppState, exc: Exception, *, incident_id: str = "") -> None:
|
|
|
67
70
|
|
|
68
71
|
def incidents_list(
|
|
69
72
|
ctx: typer.Context,
|
|
70
|
-
state_filter: Optional[str] = typer.Option(None, "--state", help="Filter by state(s): firing, acknowledged, resolved (CSV)."),
|
|
73
|
+
state_filter: Optional[str] = typer.Option(None, "--state", help="Filter by state(s): firing, acknowledged, resolved, closed (CSV)."),
|
|
71
74
|
alert_id: Optional[str] = typer.Option(None, "--alert-id", help="Only incidents for this alert."),
|
|
72
75
|
limit: int = typer.Option(50, "--limit", "-n", help="Max incidents to return."),
|
|
73
76
|
show_id: bool = typer.Option(False, "--show-id", help="Show the full incident id instead of the short form (always full in --json)."),
|
|
@@ -244,6 +247,220 @@ def incidents_resolve(
|
|
|
244
247
|
output.incident_resolved(incident_id)
|
|
245
248
|
|
|
246
249
|
|
|
250
|
+
def incidents_close(
|
|
251
|
+
ctx: typer.Context,
|
|
252
|
+
incident_id: str = typer.Argument(..., help="Issue id."),
|
|
253
|
+
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."),
|
|
254
|
+
) -> None:
|
|
255
|
+
"""Close an issue — "we're done with it", not "we fixed it".
|
|
256
|
+
|
|
257
|
+
The difference from `resolve` is what happens next. A **resolved** issue REOPENS if its
|
|
258
|
+
audit finding recurs: someone claimed a fix and the fix did not hold, which is worth
|
|
259
|
+
knowing. A **closed** issue does not — closing records a decision (won't fix, not a
|
|
260
|
+
problem, stale), and a recurrence is not news about a decision.
|
|
261
|
+
|
|
262
|
+
For an issue that came from an audit, this also marks the finding `dismissed`. It does
|
|
263
|
+
NOT write the finding's org-wide suppression: closing one issue never hides that pattern
|
|
264
|
+
in your other audits. Use `fp audits finding-status <id> --action dismiss` for that.
|
|
265
|
+
|
|
266
|
+
Needs `issues:close`. Exits 9 (conflict) if the issue already ended — an issue ends once,
|
|
267
|
+
and closing must not overwrite the record that someone believed they had fixed it.
|
|
268
|
+
|
|
269
|
+
With `--json`: `{"closed": true, "id": "<id>"}` (or `{cancelled: true}` on a declined
|
|
270
|
+
prompt).
|
|
271
|
+
|
|
272
|
+
Example:
|
|
273
|
+
|
|
274
|
+
* `fp issues close <id> --yes`
|
|
275
|
+
"""
|
|
276
|
+
state: AppState = ctx.obj
|
|
277
|
+
cctx = require_auth(state)
|
|
278
|
+
if _write.should_prompt(state, yes):
|
|
279
|
+
alert_name = None
|
|
280
|
+
try:
|
|
281
|
+
alert_name = api.get_incident(cctx, incident_id).alert_name
|
|
282
|
+
except NotFoundError as exc:
|
|
283
|
+
_fail(state, exc, incident_id=incident_id)
|
|
284
|
+
except (ApiError, ForbiddenError):
|
|
285
|
+
alert_name = None
|
|
286
|
+
if not output.confirm_incident_close(incident_id, alert_name):
|
|
287
|
+
if state.json:
|
|
288
|
+
output.emit_json({"cancelled": True})
|
|
289
|
+
else:
|
|
290
|
+
output.cancelled_plain("nothing changed")
|
|
291
|
+
return
|
|
292
|
+
try:
|
|
293
|
+
api.close_incident(cctx, incident_id)
|
|
294
|
+
except (ApiError, ForbiddenError, NotFoundError) as exc:
|
|
295
|
+
_fail(state, exc, incident_id=incident_id)
|
|
296
|
+
_write.record_action("incident_closed", resource="incident", success=True)
|
|
297
|
+
if state.json:
|
|
298
|
+
output.emit_json({"closed": True, "id": incident_id})
|
|
299
|
+
else:
|
|
300
|
+
output.incident_closed(incident_id)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def incidents_archive(
|
|
304
|
+
ctx: typer.Context,
|
|
305
|
+
incident_id: str = typer.Argument(..., help="Issue id."),
|
|
306
|
+
) -> None:
|
|
307
|
+
"""Take an issue off the issues board, keeping its history.
|
|
308
|
+
|
|
309
|
+
Archiving is separate from state: it does not resolve or close anything, and an issue
|
|
310
|
+
that has already ended keeps the record of HOW it ended. It works on a live issue too —
|
|
311
|
+
you should not have to triage something in order to stop looking at it — because the
|
|
312
|
+
safety net is on the other side: a fresh alert breach or a recurring audit finding puts
|
|
313
|
+
a live issue back on the board automatically. Archive can hide a problem; it cannot keep
|
|
314
|
+
hiding one that is still happening. A closed (won't-fix) issue is the exception and stays
|
|
315
|
+
hidden, since nobody asked for it back.
|
|
316
|
+
|
|
317
|
+
No confirmation: archiving destroys nothing and `fp issues unarchive` undoes it.
|
|
318
|
+
|
|
319
|
+
Needs `issues:close`. With `--json`: `{"archived": true, "id": "<id>"}`.
|
|
320
|
+
|
|
321
|
+
Example:
|
|
322
|
+
|
|
323
|
+
* `fp issues archive <id>`
|
|
324
|
+
"""
|
|
325
|
+
_set_archived(ctx, incident_id, True)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def incidents_unarchive(
|
|
329
|
+
ctx: typer.Context,
|
|
330
|
+
incident_id: str = typer.Argument(..., help="Issue id."),
|
|
331
|
+
) -> None:
|
|
332
|
+
"""Put an archived issue back on the issues board. See `fp issues archive`.
|
|
333
|
+
|
|
334
|
+
Needs `issues:close`. With `--json`: `{"archived": false, "id": "<id>"}`.
|
|
335
|
+
|
|
336
|
+
Example:
|
|
337
|
+
|
|
338
|
+
* `fp issues unarchive <id>`
|
|
339
|
+
"""
|
|
340
|
+
_set_archived(ctx, incident_id, False)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _set_archived(ctx: typer.Context, incident_id: str, archived: bool) -> None:
|
|
344
|
+
"""Shared body of archive/unarchive. Two commands rather than one with a flag, so each
|
|
345
|
+
reads as the verb it is and neither can be invoked meaning the opposite."""
|
|
346
|
+
state: AppState = ctx.obj
|
|
347
|
+
cctx = require_auth(state)
|
|
348
|
+
try:
|
|
349
|
+
api.set_incident_archived(cctx, incident_id, archived)
|
|
350
|
+
except (ApiError, ForbiddenError, NotFoundError) as exc:
|
|
351
|
+
_fail(state, exc, incident_id=incident_id)
|
|
352
|
+
_write.record_action(
|
|
353
|
+
"incident_archived" if archived else "incident_unarchived",
|
|
354
|
+
resource="incident", success=True,
|
|
355
|
+
)
|
|
356
|
+
if state.json:
|
|
357
|
+
output.emit_json({"archived": archived, "id": incident_id})
|
|
358
|
+
else:
|
|
359
|
+
output.incident_archived(incident_id, archived)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def incidents_clear(
|
|
363
|
+
ctx: typer.Context,
|
|
364
|
+
audit_id: Optional[str] = typer.Option(None, "--audit", help="Clear only the issues this audit raised."),
|
|
365
|
+
all_audits: bool = typer.Option(False, "--all-audits", help="Clear every issue any audit raised. Alert and hand-opened issues are left alone."),
|
|
366
|
+
everything: bool = typer.Option(False, "--everything", help="Clear every open issue in the workspace, whatever opened it."),
|
|
367
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Print what would be cleared and change nothing."),
|
|
368
|
+
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."),
|
|
369
|
+
) -> None:
|
|
370
|
+
"""Resolve every open issue in a scope — a fresh start after changing your agents.
|
|
371
|
+
|
|
372
|
+
Exactly one of `--audit <id>`, `--all-audits` or `--everything` is required: the three
|
|
373
|
+
have very different blast radii, so there is deliberately no default.
|
|
374
|
+
|
|
375
|
+
This is the same thing as resolving each issue by hand, including what it does NOT do.
|
|
376
|
+
**Nothing is deleted, and nothing is suppressed.** A pattern your agent changes genuinely
|
|
377
|
+
fixed stays gone; one they did not comes back on the next audit run and REOPENS the issue
|
|
378
|
+
it was raised under — a cleared board is not a quiet one. If you want a pattern silenced
|
|
379
|
+
for good, that is `fp audits finding-status <id> --action mute`, which is a different and
|
|
380
|
+
much bigger hammer.
|
|
381
|
+
|
|
382
|
+
Run it with `--dry-run` first: the count comes from the server, taken with the same scope
|
|
383
|
+
predicate the write uses, so it is the real size of the scope rather than a client-side
|
|
384
|
+
guess. It is a count, not a lease — the confirmed write re-runs that predicate, so an
|
|
385
|
+
issue that entered the scope since the preview is cleared along with the rest, which is
|
|
386
|
+
what clearing a SCOPE means. The line printed at the end is what actually changed.
|
|
387
|
+
|
|
388
|
+
Needs `issues:close` AND `audits:write` — clearing resolves the audit findings behind the
|
|
389
|
+
issues, so a key that cannot touch one finding cannot resolve all of them at once.
|
|
390
|
+
|
|
391
|
+
With `--json`: `{"issues": n, "findings": n, "dry_run": bool, "scope": "..."}` (or
|
|
392
|
+
`{cancelled: true}` on a declined prompt).
|
|
393
|
+
|
|
394
|
+
Examples:
|
|
395
|
+
|
|
396
|
+
* `fp issues clear --all-audits --dry-run`
|
|
397
|
+
* `fp issues clear --audit <id> --yes`
|
|
398
|
+
"""
|
|
399
|
+
state: AppState = ctx.obj
|
|
400
|
+
picked = [bool(audit_id), all_audits, everything]
|
|
401
|
+
if sum(picked) != 1:
|
|
402
|
+
raise typer.BadParameter(
|
|
403
|
+
"choose exactly one of --audit <id>, --all-audits, or --everything.",
|
|
404
|
+
param_hint="--all-audits",
|
|
405
|
+
)
|
|
406
|
+
if audit_id:
|
|
407
|
+
scope, label = "audit", "this audit"
|
|
408
|
+
elif all_audits:
|
|
409
|
+
scope, label = "all_audits", "every audit"
|
|
410
|
+
else:
|
|
411
|
+
scope, label = "everything", "the whole workspace"
|
|
412
|
+
|
|
413
|
+
cctx = require_auth(state)
|
|
414
|
+
|
|
415
|
+
# The dry run is also the preview behind the prompt, so a `--dry-run` call and a
|
|
416
|
+
# confirmed clear count the same rows the same way.
|
|
417
|
+
try:
|
|
418
|
+
preview = api.clear_issues(cctx, scope=scope, audit_id=audit_id, dry_run=True)
|
|
419
|
+
except (ApiError, ForbiddenError, NotFoundError) as exc:
|
|
420
|
+
_fail(state, exc)
|
|
421
|
+
return
|
|
422
|
+
issues = int(preview.get("issues") or 0)
|
|
423
|
+
findings = int(preview.get("findings") or 0)
|
|
424
|
+
|
|
425
|
+
if dry_run:
|
|
426
|
+
if state.json:
|
|
427
|
+
output.emit_json(preview)
|
|
428
|
+
else:
|
|
429
|
+
output.issues_cleared(issues, findings, dry_run=True)
|
|
430
|
+
return
|
|
431
|
+
|
|
432
|
+
if issues == 0:
|
|
433
|
+
# Nothing to do is not a failure, and prompting to confirm zero rows is noise.
|
|
434
|
+
if state.json:
|
|
435
|
+
output.emit_json({"issues": 0, "findings": 0, "dry_run": False, "scope": scope})
|
|
436
|
+
else:
|
|
437
|
+
output.issues_cleared(0, 0)
|
|
438
|
+
return
|
|
439
|
+
|
|
440
|
+
if _write.should_prompt(state, yes) and not output.confirm_issues_clear(label, issues, findings):
|
|
441
|
+
if state.json:
|
|
442
|
+
output.emit_json({"cancelled": True})
|
|
443
|
+
else:
|
|
444
|
+
output.cancelled_plain("nothing changed")
|
|
445
|
+
return
|
|
446
|
+
|
|
447
|
+
try:
|
|
448
|
+
result = api.clear_issues(cctx, scope=scope, audit_id=audit_id)
|
|
449
|
+
except (ApiError, ForbiddenError, NotFoundError) as exc:
|
|
450
|
+
_fail(state, exc)
|
|
451
|
+
return
|
|
452
|
+
# `mode` and `count` are both on _SAFE_PROP_KEYS, and both are shape: a closed enum we
|
|
453
|
+
# authored and an integer. Never an audit id or an issue title.
|
|
454
|
+
_write.record_action(
|
|
455
|
+
"issues_cleared", resource="incident", success=True, destructive=True,
|
|
456
|
+
mode=scope, count=int(result.get("issues") or 0),
|
|
457
|
+
)
|
|
458
|
+
if state.json:
|
|
459
|
+
output.emit_json(result)
|
|
460
|
+
else:
|
|
461
|
+
output.issues_cleared(int(result.get("issues") or 0), int(result.get("findings") or 0))
|
|
462
|
+
|
|
463
|
+
|
|
247
464
|
def incidents_comment_list(
|
|
248
465
|
ctx: typer.Context,
|
|
249
466
|
incident_id: str = typer.Argument(..., help="Incident id."),
|
|
@@ -454,7 +671,7 @@ def register(app: typer.Typer) -> None:
|
|
|
454
671
|
no_args_is_help=True,
|
|
455
672
|
rich_markup_mode="markdown",
|
|
456
673
|
context_settings={"help_option_names": ["-h", "--help"]},
|
|
457
|
-
help="Triage issues (list / count / show / ack / assign / resolve / comment-* / subscribe* / open).",
|
|
674
|
+
help="Triage issues (list / count / show / ack / assign / resolve / close / archive / unarchive / clear / comment-* / subscribe* / open).",
|
|
458
675
|
)
|
|
459
676
|
inc.command("list", epilog=GLOBALS_EPILOG)(incidents_list)
|
|
460
677
|
inc.command("count", epilog=GLOBALS_EPILOG)(incidents_count)
|
|
@@ -462,6 +679,10 @@ def register(app: typer.Typer) -> None:
|
|
|
462
679
|
inc.command("ack", epilog=GLOBALS_EPILOG)(incidents_ack)
|
|
463
680
|
inc.command("assign", epilog=GLOBALS_EPILOG)(incidents_assign)
|
|
464
681
|
inc.command("resolve", epilog=GLOBALS_EPILOG)(incidents_resolve)
|
|
682
|
+
inc.command("close", epilog=GLOBALS_EPILOG)(incidents_close)
|
|
683
|
+
inc.command("archive", epilog=GLOBALS_EPILOG)(incidents_archive)
|
|
684
|
+
inc.command("unarchive", epilog=GLOBALS_EPILOG)(incidents_unarchive)
|
|
685
|
+
inc.command("clear", epilog=GLOBALS_EPILOG)(incidents_clear)
|
|
465
686
|
inc.command("comment-list", epilog=GLOBALS_EPILOG)(incidents_comment_list)
|
|
466
687
|
inc.command("comment-add", epilog=GLOBALS_EPILOG)(incidents_comment_add)
|
|
467
688
|
inc.command("comment-delete", epilog=GLOBALS_EPILOG)(incidents_comment_delete)
|
|
@@ -417,6 +417,12 @@ class Incident:
|
|
|
417
417
|
acknowledged_by: Optional[str] = None
|
|
418
418
|
assignees: List[str] = field(default_factory=list)
|
|
419
419
|
resolved_at: Optional[str] = None
|
|
420
|
+
#: When the issue was CLOSED (won't-fix) rather than resolved (fixed). At most
|
|
421
|
+
#: one of the two is ever set. Both absent on a live issue.
|
|
422
|
+
closed_at: Optional[str] = None
|
|
423
|
+
#: When the issue was hidden from the board. Orthogonal to ``state`` — an
|
|
424
|
+
#: archived issue keeps whatever state it ended in.
|
|
425
|
+
archived_at: Optional[str] = None
|
|
420
426
|
breach_value: Optional[float] = None
|
|
421
427
|
breach_summary: Optional[str] = None
|
|
422
428
|
evidence: Optional[Dict[str, Any]] = None
|
|
@@ -443,6 +449,11 @@ class Incident:
|
|
|
443
449
|
acknowledged_by=d.get("acknowledged_by"),
|
|
444
450
|
assignees=list(d.get("assignees") or []),
|
|
445
451
|
resolved_at=d.get("resolved_at"),
|
|
452
|
+
# `from_dict` is an ALLOWLIST: a key missing here is dropped with no
|
|
453
|
+
# error, so a column added server-side renders as a blank column here
|
|
454
|
+
# and nowhere complains. That is why these two lines exist at all.
|
|
455
|
+
closed_at=d.get("closed_at"),
|
|
456
|
+
archived_at=d.get("archived_at"),
|
|
446
457
|
breach_value=d.get("breach_value"),
|
|
447
458
|
breach_summary=d.get("breach_summary"),
|
|
448
459
|
evidence=d.get("evidence"),
|
|
@@ -4563,6 +4563,73 @@ def incident_resolved(incident_id: str) -> None:
|
|
|
4563
4563
|
(_short_id(incident_id or "-"), theme.ACCENT))
|
|
4564
4564
|
|
|
4565
4565
|
|
|
4566
|
+
def confirm_incident_close(incident_id: str, alert_name: Optional[str] = None) -> bool:
|
|
4567
|
+
"""Plain close confirm (stderr). Says what separates close from resolve — a closed issue
|
|
4568
|
+
does NOT come back when the pattern recurs — because that is the whole distinction and
|
|
4569
|
+
nobody can infer it from the word."""
|
|
4570
|
+
h = Text()
|
|
4571
|
+
h.append("⚠ ", style=f"bold {theme.AMBER}")
|
|
4572
|
+
h.append("close issue ", style=theme.TEXT)
|
|
4573
|
+
h.append(_short_id(incident_id or "-"), style=f"bold {theme.ACCENT}")
|
|
4574
|
+
if alert_name:
|
|
4575
|
+
h.append(f" ({alert_name})", style=theme.LABEL)
|
|
4576
|
+
h.append("?", style=theme.TEXT)
|
|
4577
|
+
return confirm_line(
|
|
4578
|
+
h, Text("won't fix — it stays closed even if the pattern comes back", style=theme.LABEL)
|
|
4579
|
+
)
|
|
4580
|
+
|
|
4581
|
+
|
|
4582
|
+
def incident_closed(incident_id: str) -> None:
|
|
4583
|
+
"""Plain green line (stderr): ``✓ closed issue {short id}``."""
|
|
4584
|
+
if _quiet:
|
|
4585
|
+
return
|
|
4586
|
+
_stderr.print()
|
|
4587
|
+
_plain(("✓ ", theme.SUCCESS), ("closed issue ", theme.TEXT),
|
|
4588
|
+
(_short_id(incident_id or "-"), theme.ACCENT))
|
|
4589
|
+
|
|
4590
|
+
|
|
4591
|
+
def incident_archived(incident_id: str, archived: bool) -> None:
|
|
4592
|
+
"""Plain green line (stderr): ``✓ archived issue {short id}`` / ``✓ unarchived …``."""
|
|
4593
|
+
if _quiet:
|
|
4594
|
+
return
|
|
4595
|
+
_stderr.print()
|
|
4596
|
+
_plain(("✓ ", theme.SUCCESS),
|
|
4597
|
+
("archived issue " if archived else "unarchived issue ", theme.TEXT),
|
|
4598
|
+
(_short_id(incident_id or "-"), theme.ACCENT))
|
|
4599
|
+
|
|
4600
|
+
|
|
4601
|
+
def confirm_issues_clear(scope_label: str, issues: int, findings: int) -> bool:
|
|
4602
|
+
"""The bulk-clear confirm (stderr). Names the COUNT from the server's dry run rather than
|
|
4603
|
+
the scope alone — a confirmation that cannot say how many rows it will change is a
|
|
4604
|
+
confirmation in name only — and states the part operators actually need: nothing is
|
|
4605
|
+
suppressed, so a pattern that survived their agent changes comes back and reopens its issue."""
|
|
4606
|
+
h = Text()
|
|
4607
|
+
h.append("⚠ ", style=f"bold {theme.AMBER}")
|
|
4608
|
+
h.append("clear ", style=theme.TEXT)
|
|
4609
|
+
h.append(f"{issues} issue{'' if issues == 1 else 's'}", style=f"bold {theme.ACCENT}")
|
|
4610
|
+
h.append(f" ({scope_label})", style=theme.LABEL)
|
|
4611
|
+
h.append("?", style=theme.TEXT)
|
|
4612
|
+
tail = f"resolves them"
|
|
4613
|
+
if findings:
|
|
4614
|
+
tail += f" and {findings} audit finding{'' if findings == 1 else 's'}"
|
|
4615
|
+
tail += " — nothing is deleted or suppressed"
|
|
4616
|
+
return confirm_line(h, Text(tail, style=theme.LABEL))
|
|
4617
|
+
|
|
4618
|
+
|
|
4619
|
+
def issues_cleared(issues: int, findings: int, dry_run: bool = False) -> None:
|
|
4620
|
+
"""Plain green line (stderr) for a completed bulk clear, or the dry-run preview."""
|
|
4621
|
+
if _quiet:
|
|
4622
|
+
return
|
|
4623
|
+
_stderr.print()
|
|
4624
|
+
noun = f"{issues} issue{'' if issues == 1 else 's'}"
|
|
4625
|
+
if dry_run:
|
|
4626
|
+
_plain(("○ ", theme.FAINT), ("would clear ", theme.LABEL), (noun, theme.ACCENT),
|
|
4627
|
+
(f" · {findings} finding{'' if findings == 1 else 's'}", theme.FAINT))
|
|
4628
|
+
return
|
|
4629
|
+
_plain(("✓ ", theme.SUCCESS), ("cleared ", theme.TEXT), (noun, theme.ACCENT),
|
|
4630
|
+
(f" · {findings} finding{'' if findings == 1 else 's'} resolved", theme.FAINT))
|
|
4631
|
+
|
|
4632
|
+
|
|
4566
4633
|
def incident_assigned(incident_id: str, assignees: Sequence[str]) -> None:
|
|
4567
4634
|
"""Plain green line (stderr): ``✓ assigned {short id} · a@x, b@x`` — or ``✓ cleared assignees
|
|
4568
4635
|
on {short id}`` when the list is empty."""
|
|
@@ -5754,7 +5821,7 @@ def confirm_finding_action(action: str, finding_id: str, *, title: Optional[str]
|
|
|
5754
5821
|
consequence = {
|
|
5755
5822
|
"mute": "future runs stop surfacing this pattern",
|
|
5756
5823
|
"dismiss": "it's suppressed as not worth acting on",
|
|
5757
|
-
"resolve": "it closes; a genuine recurrence re-opens
|
|
5824
|
+
"resolve": "it closes; a genuine recurrence re-opens it",
|
|
5758
5825
|
}.get(action, "this changes the finding's status")
|
|
5759
5826
|
return confirm_line(h, Text(consequence, style=theme.LABEL))
|
|
5760
5827
|
|
|
@@ -12,7 +12,7 @@ license = { file = "LICENSE" }
|
|
|
12
12
|
authors = [{ name = "Failproof AI", email = "failproofai@exosphere.host" }]
|
|
13
13
|
keywords = ["agents", "observability", "ai", "monitoring", "failproofai", "cli"]
|
|
14
14
|
classifiers = [
|
|
15
|
-
"Development Status ::
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
16
|
"Environment :: Console",
|
|
17
17
|
"Intended Audience :: Developers",
|
|
18
18
|
"Programming Language :: Python :: 3",
|
|
@@ -192,7 +192,7 @@ you need a flag you don't already know.
|
|
|
192
192
|
- `users list|show|create|update|disable|enable` — referenced by **email**.
|
|
193
193
|
- `settings list|schema|set` — fixed registry; `schema` shows what each key accepts.
|
|
194
194
|
- `alerts list|show|create|update|delete|test` — referenced by **name**.
|
|
195
|
-
- `issues list|count|show|ack|assign|resolve|comment-add|comment-list|comment-delete|subscribe|subscribers|unsubscribe|open` — by id (short ids accepted). **One board for everything needing attention**: alert breaches, hand-raised issues, and audit findings, told apart by a `source` of `alert` / `manual` / `audit`. (This group was called `incidents` before; the old name is gone.)
|
|
195
|
+
- `issues list|count|show|ack|assign|resolve|close|archive|unarchive|clear|comment-add|comment-list|comment-delete|subscribe|subscribers|unsubscribe|open` — by id (short ids accepted). **One board for everything needing attention**: alert breaches, hand-raised issues, and audit findings, told apart by a `source` of `alert` / `manual` / `audit`. (This group was called `incidents` before; the old name is gone.)
|
|
196
196
|
- `audits list|show|create|edit|delete|run|runs` — scheduled sweeps, referenced by **name**; `audits findings|finding` + the triage verbs `ack|mute|dismiss|resolve|reopen|assign` act on a finding **id**. `audits run <name>` only *queues* a run (poll `audits runs <name>` for completion). See §8.
|
|
197
197
|
|
|
198
198
|
**Enforce (cloud-managed policy, session-only — see §2):**
|
|
@@ -225,6 +225,9 @@ command (`list <kind>`, `whoami`, a `list` subcommand) before committing.
|
|
|
225
225
|
| "what has this org used this metering window?" | `usage` (or `--json usage` for the complete response) |
|
|
226
226
|
| "is anything on fire?", "any alerts firing / open issues?" | `alerts list` + `issues list` (and `issues count`) |
|
|
227
227
|
| "ack / look at / resolve that issue" | `issues list` → `issues show <id>` → **confirm** → `issues ack`/`resolve <id>` |
|
|
228
|
+
| "clear all our issues", "fresh start", "we changed the agents" | **settle the scope first** (`--all-audits` leaves alert and hand-opened issues alone; `--everything` does not; `--audit <id>` is one audit) → `issues clear <scope> --dry-run` → show the count → **confirm** → `issues clear <scope>` |
|
|
229
|
+
| "we're not going to fix that one" | `issues close <id>` (NOT `resolve` — closed survives a recurrence) |
|
|
230
|
+
| "get that off my board" | `issues archive <id>` |
|
|
228
231
|
| "run an audit", "what did the audit find?", "any findings to triage?" | `audits list` → `audits run <name>` (queues) → `audits runs <name>` (wait for `succeeded`) → `audits findings --audit <name>`; triage with `audits resolve/mute/dismiss <id>` — **confirm first** |
|
|
229
232
|
| "give CI / this service an API key" | `keys create <name> --add events:add` (scope to what they describe) — **state it, then create**; capture the one-time secret |
|
|
230
233
|
| "who has access?", "add / remove a teammate", "make them read-only" | `users list` / `users show <email>` / `users create`/`update`/`disable` |
|
|
@@ -267,6 +270,18 @@ fp --json events --full --session-id run-001 --all | jq '.events[].payload' #
|
|
|
267
270
|
reliable cross-check that you pulled everything.
|
|
268
271
|
- **Triage flow:** `issues list` → `issues show <id>` (read the activity
|
|
269
272
|
log) → confirm with the user → `issues ack <id>` or `resolve <id>`.
|
|
273
|
+
- **`resolve` vs `close`:** `resolve` claims a fix, so a recurring audit finding
|
|
274
|
+
REOPENS it — that is the signal that the fix did not hold. `close` records a
|
|
275
|
+
decision (won't fix / not a problem / stale) and survives the recurrence. Pick
|
|
276
|
+
the one that matches what the user actually said; they are not synonyms.
|
|
277
|
+
- **"clear all our issues" / "fresh start":** that is `issues clear`, not a loop
|
|
278
|
+
of `resolve`. **Settle the scope before you run anything** — "all our issues"
|
|
279
|
+
does not pick one: `--all-audits` leaves alert and hand-opened issues on the
|
|
280
|
+
board, `--everything` takes them too, and `--audit <id>` is one audit's work.
|
|
281
|
+
Ask which they mean, then `--dry-run` first, tell the user the number it
|
|
282
|
+
returns, and only then run it for real. It resolves the audit findings too and
|
|
283
|
+
writes **no** suppression, so anything still broken comes back and reopens its
|
|
284
|
+
issue — say that, because users often expect "clear" to mean "silence".
|
|
270
285
|
- **Investigate a regression:** `evals --aggregate` to see which score dropped →
|
|
271
286
|
`evals --score helpfulness:..0.5` to list the bad runs → `events --session-id <id>`
|
|
272
287
|
to see what happened inside one.
|
|
@@ -293,7 +308,7 @@ Two things about the flow matter when driving it from the CLI:
|
|
|
293
308
|
either surface works, they never disagree. Triage a finding with
|
|
294
309
|
`audits ack|mute|dismiss|resolve|reopen <id>` (durable **mute/dismiss** suppress
|
|
295
310
|
the pattern org-wide by fingerprint; **resolve** leaves no suppression, so a true
|
|
296
|
-
recurrence reopens
|
|
311
|
+
recurrence reopens the issue). Reads need `audits:read`, every mutation
|
|
297
312
|
`audits:write` (note: triaging a finding needs `audits:write`, not an `issues:*`
|
|
298
313
|
permission — the audit is the system of record and the issue follows it).
|
|
299
314
|
|
|
@@ -169,7 +169,7 @@ Context is a **separate sub-resource on purpose**: `audits edit` read-merges the
|
|
|
169
169
|
- `audits ack <id> [--reason ...]` — seen, stays visible, ranked lower. No confirm.
|
|
170
170
|
- `audits mute <id> [--reason ...] [--yes]` — stop surfacing this pattern in future runs (durable). Confirms first.
|
|
171
171
|
- `audits dismiss <id> [--reason ...] [--yes]` — judged not worth acting on; suppressed like mute. Confirms first.
|
|
172
|
-
- `audits resolve <id> [--yes]` — you fixed it. Leaves **no** suppression, so a genuine recurrence
|
|
172
|
+
- `audits resolve <id> [--yes]` — you fixed it. Leaves **no** suppression, so a genuine recurrence reopens the issue it graduated to. Confirms first.
|
|
173
173
|
- `audits reopen <id>` — back to `open` **and** clears any mute/dismiss suppression. The undo for the three above.
|
|
174
174
|
- `audits assign <id> --to <email>` — set the owner; the status is untouched. `--to` is required (exit 2 without it).
|
|
175
175
|
|
|
@@ -177,14 +177,17 @@ The title column truncates to whatever width is left so the fixed columns always
|
|
|
177
177
|
|
|
178
178
|
**`audits run` is async — it only queues.** Success is `{"queued": true}`, not a finished run; the analysis can take minutes. Poll `audits runs <name>` until the newest row is `succeeded`/`failed` before reading `audits findings`, rather than assuming results exist on the call that queued them.
|
|
179
179
|
|
|
180
|
-
**Findings and issues are one bucket.** Every finding graduates to an issue (`source: audit`) that stores the finding's full content, so the same problem appears under both `audits findings` and `issues list`. Triage is consistent in **both** directions and needs `audits:write` either way: `audits resolve|mute|dismiss|ack|reopen <finding-id>` mirrors onto the linked issue, and `issues resolve <issue-id>` on an audit issue mirrors back onto the finding — the two never disagree. **resolve** leaves no suppression (a genuine recurrence reopens
|
|
180
|
+
**Findings and issues are one bucket.** Every finding graduates to an issue (`source: audit`) that stores the finding's full content, so the same problem appears under both `audits findings` and `issues list`. Triage is consistent in **both** directions and needs `audits:write` either way: `audits resolve|mute|dismiss|ack|reopen <finding-id>` mirrors onto the linked issue, and `issues resolve <issue-id>` on an audit issue mirrors back onto the finding — the two never disagree. **resolve** leaves no suppression (a genuine recurrence reopens the issue); **mute/dismiss** suppress the pattern org-wide by fingerprint. `issues close <issue-id>` sits between the two: it marks the finding `dismissed` and the issue stays closed through a recurrence, but it writes no org-wide suppression. `issues clear` is bulk `resolve`, with the same absence of suppression.
|
|
181
181
|
|
|
182
182
|
## issues
|
|
183
|
-
The single board for everything needing human attention — alert breaches (`source: alert`), hand-raised issues (`manual`), and audit findings (`audit`). Referenced by id (short ids accepted, `--show-id` shows them). This group was **renamed from `incidents`**; the old name no longer exists. Reads and ack/comment need `issues:read`; opening, assigning, and subscribing others need `issues:create`; resolving
|
|
184
|
-
- `issues list [--state firing|acknowledged|resolved] [--alert-id <id>] [--limit N] [--show-id]` — there is **no** `--severity` filter on this command.
|
|
183
|
+
The single board for everything needing human attention — alert breaches (`source: alert`), hand-raised issues (`manual`), and audit findings (`audit`). Referenced by id (short ids accepted, `--show-id` shows them). This group was **renamed from `incidents`**; the old name no longer exists. Reads and ack/comment need `issues:read`; opening, assigning, and subscribing others need `issues:create`; resolving, closing, archiving and clearing need `issues:close` (and `clear` also needs `audits:write`, because it resolves the findings behind the issues).
|
|
184
|
+
- `issues list [--state firing|acknowledged|resolved|closed] [--alert-id <id>] [--limit N] [--show-id]` — there is **no** `--severity` filter on this command. Archived issues are hidden.
|
|
185
185
|
- `issues count`
|
|
186
186
|
- `issues show <id>` — identity + comments + subscribers + **activity log** (read this before acting). An audit-born issue (`source: audit`) carries the full finding it graduated from and back-links to the audit/run.
|
|
187
187
|
- `issues ack <id>` · `issues assign <id> --assignee <member>` (repeatable; omit to clear all assignees; each must be an operator) · `issues resolve <id>` (calm confirm). **On an audit issue these stay in sync with the finding** — resolving the issue resolves the underlying audit finding, so it can't reappear on the next run (equivalently, triage it with `audits resolve <finding-id>`; both surfaces agree).
|
|
188
|
+
- `issues close <id>` (calm confirm) — the second terminal state: **"we're done with it", not "we fixed it"** (won't fix / not a problem / stale). The difference from `resolve` is what happens next: a **resolved** issue REOPENS when its audit finding recurs, a **closed** one does not. On an audit issue this marks the finding `dismissed`; it does **not** write the org-wide fingerprint suppression that `audits dismiss` writes, so closing one issue never hides that pattern in your other audits. Exit 9 if the issue already ended — an issue ends once, and neither terminal state may overwrite the other.
|
|
189
|
+
- `issues archive <id>` · `issues unarchive <id>` — take an issue off the board / put it back. **Orthogonal to state**: archiving resolves nothing and never overwrites how an issue ended, and it works on a live issue too. No confirm (it destroys nothing). A fresh alert breach or a recurring audit finding un-archives a live issue automatically, so archive can hide a problem but cannot keep hiding one that is still happening; a `closed` issue is the exception and stays hidden.
|
|
190
|
+
- `issues clear (--audit <id> | --all-audits | --everything) [--dry-run] [--yes]` — **the "we changed our agents, give us a fresh board" command.** Resolves every open issue in the scope in one server-side transaction, plus the audit findings behind them. Exactly one scope flag is required. Needs `issues:close` **and** `audits:write`. **Nothing is deleted and nothing is suppressed** — a pattern the agent changes genuinely fixed stays gone, one they did not comes back on the next run and reopens its issue; for permanent silence use `audits mute`/`dismiss` instead. Always `--dry-run` first: the count comes from the server using the same scope predicate the write uses, so it is the real size of the scope — but it is a count, not a lease: the write re-runs the predicate, so anything that entered the scope since the preview is cleared too, and the closing line reports what actually changed.
|
|
188
191
|
- `issues open --summary <text> (--title <text> | --alert-id <id>) [--title ...] [--severity ...]` — `--title` is **required** for a standalone issue (nothing to borrow a name from); with `--alert-id` it is optional and defaults to the alert's name. Missing `--title` on the standalone path → exit 2.
|
|
189
192
|
- `issues comment-add <id> (--body <text> | --file <path>)` — `--file -` reads stdin; exactly one of the two · `comment-list <id>` · `comment-delete <id> <comment-id>`
|
|
190
193
|
- `issues subscribe <id> [--email <addr>]` · `unsubscribe <id> [--email <addr>]` · `subscribers <id>` — `--email` defaults to you; naming someone else needs `issues:create`
|