failproofai 1.0.12-beta.0 → 1.0.12-beta.2
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.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 +22 -22
- 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 +1 -1
- 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 +4 -4
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.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 +7 -7
- 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]__0-ha5ydqq0mws._.js → [externals]__0vt9i6fqinjne._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__20xe05hm5u8_1._.js → [root-of-the-server]__0f_4g-vazrvxv._.js} +1 -1
- package/.next/standalone/.next/server/chunks/_0hf5q4xzyg40j._.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/_19011-u44143j._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +2 -2
- package/.next/standalone/.next/server/chunks/{_0kc-piux3hady._.js → _1y590mjzpkbos._.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 +5 -0
- package/.next/standalone/.next/server/chunks/src_hooks_builtin-policies_ts_1yq5ybgx1v98e._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3h94wnn._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_daemon-client_ts_0il_yu1zc0291._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn_4twyh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ts0fg5yih1c1._.js → [root-of-the-server]__00l2u6j86cb8y._.js} +2 -2
- 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]__1_xbg2kx30wp-._.js → [root-of-the-server]__0naevg4bcoh54._.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]__0pwj2h2efe5f-._.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]__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]__0vcyntk4snf0f._.js → [root-of-the-server]__1vofkb2oq7p7e._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bseae9u7g98r._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0mouhaflye1ro._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1gb0ifpkowwub._.js → _0ouu886szh6yj._.js} +10 -9
- package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1ve8y151ua7qx._.js → _1fw0_nzkae5ao._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_20hn4gn87nsjc._.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_components_project-list_tsx_1ke6anr0vt64h._.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_00jxj8foxqz5t.js → node_modules_html-to-image_es_index_1fv03uigni8s5.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0janj0i6dqko7._.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_06kzv9ddabn3_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4l1tzrm._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_daemon-client_ts_0k41_i7jg6ndv._.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/{0pjxnp927y7_j.js → 0ay__-d2h1xb5.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2znt1784j9gd9.js → 0tbm3vjm62h-2.js} +1 -1
- package/.next/standalone/.next/static/chunks/1c_jf6g5wnslk.js +1 -0
- package/.next/standalone/.next/static/chunks/{3tn1bzk094e8e.js → 1my3-5p47t7r-.js} +1 -1
- package/.next/standalone/.next/static/chunks/20py8k1gd_i1m.js +1 -0
- package/.next/standalone/.next/static/chunks/21hckpmsc4g70.js +6 -0
- package/.next/standalone/.next/static/chunks/2hmex93ppj1ke.js +1 -0
- package/.next/standalone/.next/static/chunks/2o6d37j425uci.js +1 -0
- package/.next/standalone/.next/static/chunks/40oworn_-x12p.js +1 -0
- package/.next/standalone/.next/static/chunks/{2qz5piiir-qqm.js → 42xptz88mtq3x.js} +1 -1
- 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 +32 -0
- package/.next/standalone/fp-cloud-cli/README.md +40 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +91 -12
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +78 -16
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +84 -18
- package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +124 -6
- package/.next/standalone/fp-cloud-cli/fp_cli/models.py +89 -6
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +189 -20
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +8 -5
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +2 -2
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +14 -7
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +527 -0
- package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +46 -0
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +23 -0
- package/.next/standalone/hermes-plugin/README.md +6 -4
- package/.next/standalone/hermes-plugin/__init__.py +27 -0
- package/.next/standalone/hermes-plugin/client.py +5 -1
- 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/server.js +1 -1
- package/bin/failproofai.mjs +17 -1
- package/dist/cli.mjs +3507 -1250
- package/dist/worker.mjs +1779 -169
- package/hermes-plugin/README.md +6 -4
- package/hermes-plugin/__init__.py +27 -0
- package/hermes-plugin/client.py +5 -1
- package/hermes-plugin/plugin.yaml +1 -1
- package/package.json +11 -10
- package/src/audit/open-browser.ts +6 -7
- package/src/hooks/agent-roster.ts +180 -0
- package/src/hooks/agent-skill.ts +203 -0
- package/src/hooks/agent-targets.ts +60 -0
- package/src/hooks/cloud-connection.ts +91 -45
- package/src/hooks/cloud-enrollment-cli.ts +7 -4
- package/src/hooks/cloud-login.ts +6 -3
- package/src/hooks/cloud-managed-policies.ts +430 -20
- package/src/hooks/cloud-policy-errors.ts +481 -0
- package/src/hooks/codex-system-hooks.ts +10 -4
- package/src/hooks/config-status.ts +10 -4
- package/src/hooks/configure-wizard.ts +54 -26
- package/src/hooks/custom-hooks-loader.ts +26 -3
- package/src/hooks/daemon-client.ts +9 -4
- package/src/hooks/daemon-service.ts +2 -1
- package/src/hooks/delivery-health.ts +47 -2
- package/src/hooks/effective-reviewers.ts +36 -1
- package/src/hooks/fp-config.ts +19 -0
- package/src/hooks/fp-home.ts +6 -0
- package/src/hooks/handler.ts +255 -19
- package/src/hooks/health-probe.ts +15 -0
- package/src/hooks/hook-activity-store.ts +7 -1
- package/src/hooks/integrations.ts +36 -14
- package/src/hooks/jev-activity.ts +117 -1
- package/src/hooks/jev-cli.ts +196 -1
- package/src/hooks/jev-cloud-connection.ts +92 -0
- package/src/hooks/manager.ts +174 -34
- package/src/hooks/pack-failclosed.ts +2 -0
- package/src/hooks/pack-manifest.ts +8 -1
- package/src/hooks/policy-evaluator.ts +22 -1
- package/src/hooks/policy-registry.ts +3 -2
- package/src/hooks/policy-reviewability.ts +23 -5
- package/src/hooks/semantic/cloud-jev-health.ts +227 -0
- package/src/hooks/semantic/cloud-jev.ts +868 -0
- package/src/hooks/semantic/combine.ts +26 -1
- package/src/hooks/semantic/jev-client.ts +19 -2
- package/src/hooks/semantic/jev-config.ts +179 -30
- package/src/hooks/semantic/jev-review.ts +217 -10
- package/src/hooks/semantic/pack-policies.ts +2 -1
- package/src/hooks/semantic/types.ts +8 -0
- package/src/hooks/session-pause-cli.ts +1 -1
- package/src/hooks/tui.ts +13 -4
- package/src/hooks/uninstall-cli.ts +12 -1
- package/src/hooks/worker-server.ts +5 -1
- package/.next/standalone/.next/server/chunks/src_hooks_1aveq0uhs2t1b._.js +0 -5
- package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7cyqlb2._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo55j16x._.js +0 -5
- package/.next/standalone/.next/static/chunks/0a-ccsp3xmypz.js +0 -1
- package/.next/standalone/.next/static/chunks/13jv-xvabeb62.js +0 -6
- package/.next/standalone/.next/static/chunks/2ank2u3apy8m6.js +0 -1
- package/.next/standalone/.next/static/chunks/2meqb9kygrncb.js +0 -1
- package/.next/standalone/.next/static/chunks/2ohktp_-lkv3x.js +0 -1
- package/.next/standalone/.next/static/chunks/2py0eldhwz8ay.js +0 -1
- /package/.next/standalone/.next/static/{SNr7o_-G4kNBQTR-iLZMM → LHCtFxVG0mdvd4lsXAOFC}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{SNr7o_-G4kNBQTR-iLZMM → LHCtFxVG0mdvd4lsXAOFC}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{SNr7o_-G4kNBQTR-iLZMM → LHCtFxVG0mdvd4lsXAOFC}/_ssgManifest.js +0 -0
|
@@ -13,7 +13,7 @@ import json as _json
|
|
|
13
13
|
import math
|
|
14
14
|
import re
|
|
15
15
|
from datetime import datetime, timezone
|
|
16
|
-
from typing import Any, List, Optional, Sequence
|
|
16
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
17
17
|
|
|
18
18
|
import typer
|
|
19
19
|
from rich.box import ROUNDED, SIMPLE_HEAD
|
|
@@ -5918,6 +5918,10 @@ def _policy_cell(ref: Any) -> Text:
|
|
|
5918
5918
|
return t
|
|
5919
5919
|
|
|
5920
5920
|
|
|
5921
|
+
#: A policy version's kind, as `fp policies list` colours it.
|
|
5922
|
+
_KIND_STYLE = {"regex": theme.TEXT_DIM, "jev": theme.ACCENT, "both": theme.AMBER}
|
|
5923
|
+
|
|
5924
|
+
|
|
5921
5925
|
def render_policies(items: Sequence[Any]) -> None:
|
|
5922
5926
|
"""``fp policies`` — every published VERSION, newest of each policy first.
|
|
5923
5927
|
|
|
@@ -5938,11 +5942,25 @@ def render_policies(items: Sequence[Any]) -> None:
|
|
|
5938
5942
|
state = Text("archived", style=theme.FAINT)
|
|
5939
5943
|
elif p.disabled:
|
|
5940
5944
|
state = Text("disabled", style=theme.AMBER)
|
|
5945
|
+
kind = getattr(p, "kind", None) or "regex"
|
|
5946
|
+
names = list(getattr(p, "jev_names", None) or [])
|
|
5947
|
+
desc = Text()
|
|
5948
|
+
if names:
|
|
5949
|
+
# The checks first: for a `jev` policy they ARE the policy, and a
|
|
5950
|
+
# long description would otherwise push them off the ellipsis.
|
|
5951
|
+
desc.append(", ".join(names), style=theme.ACCENT)
|
|
5952
|
+
chars = getattr(p, "jev_chars", None)
|
|
5953
|
+
if chars is not None:
|
|
5954
|
+
desc.append(f" {chars:,} chars", style=theme.LABEL)
|
|
5955
|
+
if p.description:
|
|
5956
|
+
desc.append(" · ", style=theme.FAINT)
|
|
5957
|
+
desc.append(p.description or "", style=theme.TEXT_DIM)
|
|
5941
5958
|
rows.append([
|
|
5942
5959
|
Text(p.id, style=theme.TEXT),
|
|
5943
5960
|
Text(f"v{p.version}", style=theme.TEXT_DIM),
|
|
5961
|
+
Text(kind, style=_KIND_STYLE.get(kind, theme.TEXT_DIM)),
|
|
5944
5962
|
state,
|
|
5945
|
-
|
|
5963
|
+
desc,
|
|
5946
5964
|
])
|
|
5947
5965
|
distinct = len({p.id for p in items})
|
|
5948
5966
|
title = Text()
|
|
@@ -5952,7 +5970,7 @@ def render_policies(items: Sequence[Any]) -> None:
|
|
|
5952
5970
|
if len(rows) != distinct:
|
|
5953
5971
|
title.append(" · ", style=theme.FAINT)
|
|
5954
5972
|
title.append(f"{len(rows)} versions", style=theme.LABEL)
|
|
5955
|
-
render_list_panel("policies", header=["policy", "version", "state", "description"],
|
|
5973
|
+
render_list_panel("policies", header=["policy", "version", "kind", "state", "description"],
|
|
5956
5974
|
rows=rows, days=set(), order=None,
|
|
5957
5975
|
empty_message="no policies published — `fp policies publish <id> <file>`",
|
|
5958
5976
|
last_col="ellipsis", title=title)
|
|
@@ -5974,17 +5992,41 @@ def render_policy_published(p: Any, *, carriers: Optional[dict] = None,
|
|
|
5974
5992
|
"""
|
|
5975
5993
|
line1 = Text(p.id, style=f"bold {theme.TEXT}")
|
|
5976
5994
|
line1.append(f" v{p.version}", style=f"bold {theme.ACCENT}")
|
|
5995
|
+
kind = getattr(p, "kind", None) or "regex"
|
|
5996
|
+
line1.append(f" {kind}", style=_KIND_STYLE.get(kind, theme.TEXT_DIM))
|
|
5977
5997
|
body = [line1]
|
|
5978
5998
|
if p.description:
|
|
5979
5999
|
body.append(Text(p.description, style=theme.TEXT))
|
|
6000
|
+
names = list(getattr(p, "jev_names", None) or [])
|
|
6001
|
+
if names:
|
|
6002
|
+
checks = Text("jev checks ", style=theme.LABEL)
|
|
6003
|
+
checks.append(", ".join(names), style=theme.ACCENT)
|
|
6004
|
+
chars = getattr(p, "jev_chars", None)
|
|
6005
|
+
if chars is not None:
|
|
6006
|
+
# What these checks take of the ONE Jev question budget a machine
|
|
6007
|
+
# has for every check it asks — Cloud's and its installed packs'.
|
|
6008
|
+
checks.append(" · ", style=theme.FAINT)
|
|
6009
|
+
checks.append(f"{chars:,} question chars", style=theme.LABEL)
|
|
6010
|
+
body.append(checks)
|
|
6011
|
+
if kind == "both":
|
|
6012
|
+
# Derived by the server from the kind, never sent: said so the author
|
|
6013
|
+
# knows what the JavaScript's verdict now is.
|
|
6014
|
+
body.append(Text("its JavaScript is reviewable by exactly these checks", style=theme.LABEL))
|
|
5980
6015
|
body.append(Text())
|
|
5981
6016
|
|
|
5982
6017
|
meta = Text()
|
|
5983
6018
|
if source_bytes:
|
|
5984
6019
|
meta.append(f"{source_bytes:,} bytes", style=theme.LABEL)
|
|
5985
6020
|
meta.append(" · ", style=theme.FAINT)
|
|
5986
|
-
|
|
5987
|
-
|
|
6021
|
+
if p.sha256:
|
|
6022
|
+
meta.append("sha256 ", style=theme.LABEL)
|
|
6023
|
+
meta.append((p.sha256 or "")[:12] + "…", style=theme.TEXT_DIM)
|
|
6024
|
+
semantic_sha = getattr(p, "semantic_sha256", None)
|
|
6025
|
+
if semantic_sha:
|
|
6026
|
+
if p.sha256:
|
|
6027
|
+
meta.append(" · ", style=theme.FAINT)
|
|
6028
|
+
meta.append("jev sha256 ", style=theme.LABEL)
|
|
6029
|
+
meta.append(semantic_sha[:12] + "…", style=theme.TEXT_DIM)
|
|
5988
6030
|
body.append(meta)
|
|
5989
6031
|
body.append(Text())
|
|
5990
6032
|
|
|
@@ -6053,8 +6095,7 @@ def render_fleet(machines: Sequence[Any]) -> None:
|
|
|
6053
6095
|
Text(seen or "never", style=theme.FAINT if stale else theme.TEXT_DIM),
|
|
6054
6096
|
Text(f"{m.event_count:,}" if m.event_count else "—",
|
|
6055
6097
|
style=theme.TEXT_DIM if m.event_count else theme.FAINT),
|
|
6056
|
-
|
|
6057
|
-
style=theme.AMBER if m.drifted else (theme.SUCCESS if m.deployed else theme.FAINT)),
|
|
6098
|
+
_machine_state(m),
|
|
6058
6099
|
])
|
|
6059
6100
|
title = Text()
|
|
6060
6101
|
title.append("fleet", style=f"bold {theme.ACCENT}")
|
|
@@ -6067,6 +6108,18 @@ def render_fleet(machines: Sequence[Any]) -> None:
|
|
|
6067
6108
|
empty_message="no machines have checked in yet", title=title)
|
|
6068
6109
|
|
|
6069
6110
|
|
|
6111
|
+
def _machine_state(m: Any) -> Text:
|
|
6112
|
+
"""`fp fleet list`'s state cell. Reported policy errors outrank drift: a
|
|
6113
|
+
machine that says it cannot apply part of its set is not enforcing it,
|
|
6114
|
+
whichever generation it is on."""
|
|
6115
|
+
errors = getattr(m, "policy_errors", None) or []
|
|
6116
|
+
if errors:
|
|
6117
|
+
n = len(errors)
|
|
6118
|
+
return Text(f"{n} error{'s' if n != 1 else ''}", style=theme.ERROR)
|
|
6119
|
+
return Text("drifted" if m.drifted else ("ok" if m.deployed else "—"),
|
|
6120
|
+
style=theme.AMBER if m.drifted else (theme.SUCCESS if m.deployed else theme.FAINT))
|
|
6121
|
+
|
|
6122
|
+
|
|
6070
6123
|
def _compact_age(ms: Optional[int]) -> str:
|
|
6071
6124
|
"""`5h`, `7d`, `just now` — the column form of `_epoch_age`.
|
|
6072
6125
|
|
|
@@ -6097,7 +6150,33 @@ def _epoch_age(ms: Optional[int]) -> str:
|
|
|
6097
6150
|
return _relative_age(datetime.fromtimestamp(ms / 1000, tz=timezone.utc).isoformat())
|
|
6098
6151
|
|
|
6099
6152
|
|
|
6100
|
-
def
|
|
6153
|
+
def _agent_target_label(targets: Any, labels: Optional[Dict[str, str]] = None,
|
|
6154
|
+
*, with_id: bool = False) -> str:
|
|
6155
|
+
"""One assignment's scope, unscoped only when the field is absent.
|
|
6156
|
+
|
|
6157
|
+
``labels`` maps an exact profile's ``agt_…`` id to the profile label the
|
|
6158
|
+
machine reported (`.claude-work`), so the target reads as the profile a
|
|
6159
|
+
person recognises. ``with_id`` keeps the id beside it, for the views a
|
|
6160
|
+
selector is copied from. An id with no label stays the bare id.
|
|
6161
|
+
"""
|
|
6162
|
+
if not targets:
|
|
6163
|
+
return "all agents"
|
|
6164
|
+
labels = labels or {}
|
|
6165
|
+
|
|
6166
|
+
def one(target: Dict[str, Any]) -> str:
|
|
6167
|
+
instance = target.get("instanceId")
|
|
6168
|
+
if not instance:
|
|
6169
|
+
return f"all {target['integration']}"
|
|
6170
|
+
name = labels.get(instance)
|
|
6171
|
+
if not name:
|
|
6172
|
+
return f"{target['integration']}/{instance}"
|
|
6173
|
+
return f"{target['integration']}/{name}" + (f" ({instance})" if with_id else "")
|
|
6174
|
+
|
|
6175
|
+
return ", ".join(one(target) for target in targets)
|
|
6176
|
+
|
|
6177
|
+
|
|
6178
|
+
def render_machine_policies(machine_id: str, dep: Any, machine: Any = None,
|
|
6179
|
+
agent_labels: Optional[Dict[str, str]] = None) -> None:
|
|
6101
6180
|
"""``fp fleet show`` — what a machine is told to run, and whether it has it.
|
|
6102
6181
|
|
|
6103
6182
|
The first version printed the id, the generation and the policy list, which
|
|
@@ -6148,6 +6227,27 @@ def render_machine_policies(machine_id: str, dep: Any, machine: Any = None) -> N
|
|
|
6148
6227
|
act.append(f" · {machine.event_count} events", style=theme.LABEL)
|
|
6149
6228
|
field("last seen", act)
|
|
6150
6229
|
|
|
6230
|
+
mode = (getattr(dep, "jev_mode", None) if dep is not None else None) or (
|
|
6231
|
+
getattr(machine, "jev_mode", None) if machine is not None else None
|
|
6232
|
+
)
|
|
6233
|
+
jev = Text(mode or "local", style=theme.ACCENT if mode else theme.TEXT_DIM)
|
|
6234
|
+
jev.append(" · set by FailproofAI Cloud" if mode else " · the machine's own", style=theme.LABEL)
|
|
6235
|
+
field("jev mode", jev)
|
|
6236
|
+
|
|
6237
|
+
errors = (getattr(machine, "policy_errors", None) or []) if machine is not None else []
|
|
6238
|
+
if errors:
|
|
6239
|
+
body.append(Text())
|
|
6240
|
+
head = Text(f"{len(errors)} policy error{'s' if len(errors) != 1 else ''}", style=f"bold {theme.ERROR}")
|
|
6241
|
+
head.append(" · reported by the machine — these are not enforcing", style=theme.LABEL)
|
|
6242
|
+
body.append(head)
|
|
6243
|
+
for e in errors:
|
|
6244
|
+
line = Text(" ")
|
|
6245
|
+
ver = e.get("version")
|
|
6246
|
+
line.append(f"{e.get('id', '?')}{f' v{ver}' if ver is not None else ''}", style=theme.TEXT)
|
|
6247
|
+
line.append(f" {e.get('kind', '')}", style=theme.FAINT)
|
|
6248
|
+
line.append(f" {e.get('message', '')}", style=theme.TEXT_DIM)
|
|
6249
|
+
body.append(line)
|
|
6250
|
+
|
|
6151
6251
|
body.append(Text())
|
|
6152
6252
|
pols = sorted(dep.policies, key=lambda x: x.id) if dep is not None else []
|
|
6153
6253
|
if pols:
|
|
@@ -6156,13 +6256,15 @@ def render_machine_policies(machine_id: str, dep: Any, machine: Any = None) -> N
|
|
|
6156
6256
|
# padded to the header's width — otherwise the effect column steps left
|
|
6157
6257
|
# by one on every row and the table reads as misaligned.
|
|
6158
6258
|
vwidth = max(3, max(len(f"v{p.version}") for p in pols))
|
|
6159
|
-
head = Text(f" {'policy'.ljust(width)} {'ver'.ljust(vwidth)} effect", style=theme.LABEL)
|
|
6259
|
+
head = Text(f" {'policy'.ljust(width)} {'ver'.ljust(vwidth)} effect agents", style=theme.LABEL)
|
|
6160
6260
|
body.append(head)
|
|
6161
6261
|
for p in pols:
|
|
6162
6262
|
row = Text(" ")
|
|
6163
6263
|
row.append(p.id.ljust(width), style=theme.TEXT)
|
|
6164
6264
|
row.append(f" {f'v{p.version}'.ljust(vwidth)} ", style=theme.TEXT_DIM)
|
|
6165
6265
|
row.append_text(_effect(p.effect))
|
|
6266
|
+
row.append(" " + _agent_target_label(getattr(p, "agent_targets", None), agent_labels,
|
|
6267
|
+
with_id=True), style=theme.TEXT_DIM)
|
|
6166
6268
|
body.append(row)
|
|
6167
6269
|
else:
|
|
6168
6270
|
body.append(Text(" no policies deployed", style=theme.FAINT))
|
|
@@ -6183,18 +6285,24 @@ def render_deploy_plan(plan: Any, *, applied: bool = False) -> None:
|
|
|
6183
6285
|
lines = []
|
|
6184
6286
|
for p in plan.added:
|
|
6185
6287
|
t = Text(" + ", style=theme.SUCCESS); t.append_text(_policy_cell(p))
|
|
6186
|
-
t.append(" "); t.append_text(_effect(p.effect))
|
|
6288
|
+
t.append(" "); t.append_text(_effect(p.effect))
|
|
6289
|
+
t.append(" " + _agent_target_label(getattr(p, "agent_targets", None)), style=theme.TEXT_DIM)
|
|
6290
|
+
lines.append(t)
|
|
6187
6291
|
for was, now in plan.changed:
|
|
6188
6292
|
t = Text(" ~ ", style=theme.AMBER); t.append_text(_policy_cell(now))
|
|
6189
6293
|
t.append(" "); t.append_text(_effect(now.effect))
|
|
6190
|
-
t.append(
|
|
6294
|
+
t.append(" " + _agent_target_label(getattr(now, "agent_targets", None)), style=theme.TEXT_DIM)
|
|
6295
|
+
t.append(f" (was v{was.version} {was.effect}; {_agent_target_label(getattr(was, 'agent_targets', None))})", style=theme.FAINT)
|
|
6296
|
+
lines.append(t)
|
|
6191
6297
|
for p in plan.removed:
|
|
6192
6298
|
t = Text(" - ", style=theme.ERROR)
|
|
6193
6299
|
t.append(p.id, style=theme.TEXT_DIM); t.append(f" v{p.version}", style=theme.FAINT)
|
|
6194
6300
|
lines.append(t)
|
|
6195
6301
|
for p in plan.unchanged:
|
|
6196
6302
|
t = Text(" = ", style=theme.FAINT); t.append_text(_policy_cell(p))
|
|
6197
|
-
t.append(" "); t.append_text(_effect(p.effect))
|
|
6303
|
+
t.append(" "); t.append_text(_effect(p.effect))
|
|
6304
|
+
t.append(" " + _agent_target_label(getattr(p, "agent_targets", None)), style=theme.TEXT_DIM)
|
|
6305
|
+
lines.append(t)
|
|
6198
6306
|
if not lines:
|
|
6199
6307
|
lines = [Text(" (no policies)", style=theme.FAINT)]
|
|
6200
6308
|
|
|
@@ -6333,7 +6441,18 @@ def deployment_applied(machine_id: str, generation: int, count: int) -> None:
|
|
|
6333
6441
|
_notice_box(body, color=theme.SUCCESS, title="deployed")
|
|
6334
6442
|
|
|
6335
6443
|
|
|
6336
|
-
def deployment_rolled_back(
|
|
6444
|
+
def deployment_rolled_back(
|
|
6445
|
+
machine_id: str,
|
|
6446
|
+
restored: int,
|
|
6447
|
+
generation: int,
|
|
6448
|
+
*,
|
|
6449
|
+
jev_from: Optional[str] = None,
|
|
6450
|
+
jev_to: Optional[str] = None,
|
|
6451
|
+
) -> None:
|
|
6452
|
+
"""``✓ restored the set from #R on <machine> · minted as #G``, plus the Jev
|
|
6453
|
+
mode the rollback brought back when it changed — a rollback restores the
|
|
6454
|
+
generation's mode too, and saying nothing is how `off` silently became
|
|
6455
|
+
`enforce` again."""
|
|
6337
6456
|
body = Text()
|
|
6338
6457
|
body.append("✓ ", style=theme.SUCCESS)
|
|
6339
6458
|
body.append("restored the set from ", style=theme.TEXT)
|
|
@@ -6342,6 +6461,14 @@ def deployment_rolled_back(machine_id: str, restored: int, generation: int) -> N
|
|
|
6342
6461
|
body.append(machine_id, style=theme.ACCENT)
|
|
6343
6462
|
body.append(" · ", style=theme.FAINT)
|
|
6344
6463
|
body.append(f"minted as deployment #{generation}", style=theme.LABEL)
|
|
6464
|
+
if jev_from is not None and jev_to is not None:
|
|
6465
|
+
body.append(" · ", style=theme.FAINT)
|
|
6466
|
+
if jev_from != jev_to:
|
|
6467
|
+
body.append("jev mode ", style=theme.LABEL)
|
|
6468
|
+
body.append(f"{jev_from} → ", style=theme.FAINT)
|
|
6469
|
+
body.append(jev_to, style=f"bold {theme.AMBER}")
|
|
6470
|
+
else:
|
|
6471
|
+
body.append(f"jev mode {jev_to} (unchanged)", style=theme.LABEL)
|
|
6345
6472
|
_notice_box(body, color=theme.SUCCESS, title="rolled back")
|
|
6346
6473
|
|
|
6347
6474
|
|
|
@@ -6581,7 +6708,8 @@ def policy_published_brief(p: Any) -> None:
|
|
|
6581
6708
|
_notice_box(body, color=theme.SUCCESS, title="published")
|
|
6582
6709
|
|
|
6583
6710
|
|
|
6584
|
-
def render_deployment_history(machine_id: str, entries: Sequence[dict]
|
|
6711
|
+
def render_deployment_history(machine_id: str, entries: Sequence[dict],
|
|
6712
|
+
agent_labels: Optional[Dict[str, str]] = None) -> None:
|
|
6585
6713
|
"""``fp fleet history`` — one row per generation, newest first.
|
|
6586
6714
|
|
|
6587
6715
|
Was a bare `print` per line, which put an unaligned wall of timestamps and
|
|
@@ -6595,6 +6723,7 @@ def render_deployment_history(machine_id: str, entries: Sequence[dict]) -> None:
|
|
|
6595
6723
|
"""
|
|
6596
6724
|
rows = []
|
|
6597
6725
|
prev = None
|
|
6726
|
+
prev_mode = "local"
|
|
6598
6727
|
# oldest first so each row can be diffed against the one before it, then
|
|
6599
6728
|
# reversed for display — newest first is how you read a history.
|
|
6600
6729
|
ordered = sorted(entries, key=lambda e: e.get("deployment") or 0)
|
|
@@ -6605,17 +6734,28 @@ def render_deployment_history(machine_id: str, entries: Sequence[dict]) -> None:
|
|
|
6605
6734
|
# that STOPPED BLOCKING, and it rendered as "no change". It also split a
|
|
6606
6735
|
# version bump into a "+x" and a "-x" for the same policy, which reads
|
|
6607
6736
|
# as removed-and-re-added rather than moved.
|
|
6608
|
-
cur = {p.get("id"): (p.get("version"), p.get("effect")
|
|
6737
|
+
cur = {p.get("id"): (p.get("version"), p.get("effect"),
|
|
6738
|
+
tuple(sorted(
|
|
6739
|
+
(t.get("integration") or "", t.get("instanceId") or "")
|
|
6740
|
+
for t in (p.get("agentTargets") or [])
|
|
6741
|
+
)))
|
|
6609
6742
|
for p in (e.get("policies") or [])}
|
|
6743
|
+
scope = {p.get("id"): _agent_target_label(p.get("agentTargets"), agent_labels)
|
|
6744
|
+
for p in (e.get("policies") or [])}
|
|
6745
|
+
mode = e.get("jevMode") or "local"
|
|
6610
6746
|
if prev is None:
|
|
6611
|
-
diffs[e.get("deployment")] = [("+", i) for i in sorted(cur)]
|
|
6747
|
+
diffs[e.get("deployment")] = [("+", f"{i} → {scope[i]}") for i in sorted(cur)]
|
|
6612
6748
|
else:
|
|
6613
6749
|
diffs[e.get("deployment")] = (
|
|
6614
|
-
[("+", i) for i in sorted(set(cur) - set(prev))]
|
|
6750
|
+
[("+", f"{i} → {scope[i]}") for i in sorted(set(cur) - set(prev))]
|
|
6615
6751
|
+ [("-", i) for i in sorted(set(prev) - set(cur))]
|
|
6616
|
-
+ [("~", i) for i in sorted(set(cur) & set(prev)) if cur[i] != prev[i]]
|
|
6752
|
+
+ [("~", f"{i} → {scope[i]}") for i in sorted(set(cur) & set(prev)) if cur[i] != prev[i]]
|
|
6753
|
+
# A mode-only generation (a legacy jev-mode change) read as "no
|
|
6754
|
+
# change", which is exactly the change a rollback brings back.
|
|
6755
|
+
+ ([("~", f"jev {prev_mode}→{mode}")] if mode != prev_mode else [])
|
|
6617
6756
|
)
|
|
6618
6757
|
prev = cur
|
|
6758
|
+
prev_mode = mode
|
|
6619
6759
|
|
|
6620
6760
|
newest_first = sorted(entries, key=lambda e: e.get("deployment") or 0, reverse=True)
|
|
6621
6761
|
# The shared time column: clock time, with the date folded in only when the
|
|
@@ -6636,10 +6776,14 @@ def render_deployment_history(machine_id: str, entries: Sequence[dict]) -> None:
|
|
|
6636
6776
|
change.append(ref, style=theme.TEXT_DIM)
|
|
6637
6777
|
if not change.plain:
|
|
6638
6778
|
change = Text("no change", style=theme.FAINT)
|
|
6779
|
+
mode = e.get("jevMode") or "local"
|
|
6639
6780
|
rows.append([
|
|
6640
6781
|
Text(f"#{gen}", style=theme.TEXT),
|
|
6641
6782
|
Text(tcell or (e.get("updatedAt", "") or "-"), style=theme.TEXT_DIM),
|
|
6642
6783
|
Text(str(len(pols)), style=theme.TEXT_DIM if pols else theme.FAINT),
|
|
6784
|
+
# The mode FailproofAI Cloud set with this generation — what a
|
|
6785
|
+
# rollback to it restores.
|
|
6786
|
+
Text(mode, style=theme.FAINT if mode == "local" else theme.TEXT),
|
|
6643
6787
|
change,
|
|
6644
6788
|
Text(", ".join(pols) or "(none)", style=theme.TEXT_DIM if pols else theme.FAINT),
|
|
6645
6789
|
])
|
|
@@ -6647,9 +6791,34 @@ def render_deployment_history(machine_id: str, entries: Sequence[dict]) -> None:
|
|
|
6647
6791
|
title.append(machine_id, style=f"bold {theme.ACCENT}")
|
|
6648
6792
|
title.append(" · ", style=theme.FAINT)
|
|
6649
6793
|
title.append(f"{len(rows)} generations", style=theme.LABEL)
|
|
6650
|
-
|
|
6794
|
+
header = ["gen", "when", "n", "jev", "change", "policies"]
|
|
6795
|
+
# Fit the panel to the terminal by giving up the expendable columns first.
|
|
6796
|
+
# Every cell is no_wrap, so a table wider than the terminal had Rich shave
|
|
6797
|
+
# ALL columns at once — and at 80 columns `gen`, `when` and `jev` were the
|
|
6798
|
+
# ones that vanished, though `gen` is the argument `fp fleet rollback` takes.
|
|
6799
|
+
# `policies` goes first (`n` still counts it, `change` says what moved),
|
|
6800
|
+
# then `change` wraps rather than squeezing the four short columns.
|
|
6801
|
+
last_col, last_col_max = "ellipsis", None
|
|
6802
|
+
if rows:
|
|
6803
|
+
def natural(i: int) -> int:
|
|
6804
|
+
return max([len(header[i])] + [r[i].cell_len for r in rows])
|
|
6805
|
+
# indent 2 + border 2 + panel padding 2, two spaces between columns,
|
|
6806
|
+
# and a little slack for Rich's rounding.
|
|
6807
|
+
chrome = 6 + 2
|
|
6808
|
+
fixed = sum(natural(i) for i in range(4)) + 2 * 4 + chrome
|
|
6809
|
+
room = _stdout.width - fixed
|
|
6810
|
+
change_w, policies_w = natural(4), natural(5)
|
|
6811
|
+
if change_w + 2 + policies_w > room:
|
|
6812
|
+
if room - change_w - 2 >= 12:
|
|
6813
|
+
last_col_max = room - change_w - 2
|
|
6814
|
+
else:
|
|
6815
|
+
header = header[:5]
|
|
6816
|
+
rows = [r[:5] for r in rows]
|
|
6817
|
+
last_col = "wrap"
|
|
6818
|
+
render_list_panel("history", header=header,
|
|
6651
6819
|
rows=rows, days=days, order=None,
|
|
6652
|
-
empty_message="no deployment history", last_col=
|
|
6820
|
+
empty_message="no deployment history", last_col=last_col,
|
|
6821
|
+
last_col_max=last_col_max, title=title)
|
|
6653
6822
|
|
|
6654
6823
|
|
|
6655
6824
|
def render_fleet_diff(rows: Sequence[dict]) -> None:
|
|
@@ -446,14 +446,17 @@ def cloud_publish_problem(source: str) -> Optional[str]:
|
|
|
446
446
|
if not source or len(source) > _MAX_SCAN:
|
|
447
447
|
return None
|
|
448
448
|
if _find_call(source, _SEMANTIC_CALL) != -1:
|
|
449
|
-
return ("semanticPolicies.add does nothing in a cloud policy: Jev reads semantic
|
|
450
|
-
"from a failproofai pack
|
|
451
|
-
"`
|
|
449
|
+
return ("semanticPolicies.add does nothing in a cloud policy's JavaScript: Jev reads semantic "
|
|
450
|
+
"checks only from a failproofai pack or from the policy's own Jev declarations. Put the "
|
|
451
|
+
"check in a JSON file and publish with `--kind both --semantic checks.json` (or `--kind "
|
|
452
|
+
"jev`), or ship the file with `failproofai publish`.")
|
|
452
453
|
# Every registration: any of them can carry a reviewable declaration.
|
|
453
454
|
at = _find_call(source, _ADD_CALL)
|
|
454
455
|
while at != -1:
|
|
455
456
|
if _direct_props(source, at).get("authority") == "reviewable":
|
|
456
|
-
return ("a cloud policy is always hard
|
|
457
|
-
"
|
|
457
|
+
return ("a cloud policy's JavaScript is always hard as written: its authority comes from "
|
|
458
|
+
"its kind, never from the source, so authority/reviewedBy here are ignored. Remove "
|
|
459
|
+
"them, and publish with `--kind both --semantic checks.json` to make it reviewable "
|
|
460
|
+
"by its own Jev checks.")
|
|
458
461
|
at = _find_call(source, _ADD_CALL, at + len(_ADD))
|
|
459
462
|
return None
|
|
@@ -196,8 +196,8 @@ you need a flag you don't already know.
|
|
|
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):**
|
|
199
|
-
- `policies list|show|publish|enable|disable|delete|test|compose` — policy versions. `publish` mints a version
|
|
200
|
-
- `fleet list|show|deploy|diff|history|rollback|rename` — which machines run which policies. **`deploy` REPLACES a machine's whole set** (`--add`/`--remove` amend it, `--set` replaces, `--create` mints a deployment)
|
|
199
|
+
- `policies list|show|publish|enable|disable|delete|test|compose` — policy versions. `publish` mints a version of a kind — `regex` (a local `.mjs`), `jev` (`--semantic checks.json`: Jev declarations, no JavaScript) or `both` (the two, the JavaScript reviewable by its own checks); list/show print the kind and the Jev check names; `test` runs one against a synthetic context locally (it applies each policy's `match` filter, so a policy that does not cover the `--event`/`--tool` you pass is reported `skipped`, not run). `enable`/`disable`/`delete` take `--yes`.
|
|
200
|
+
- `fleet list|show|deploy|diff|history|rollback|rename` — which machines run which policies, and the policy errors each machine reports. **`deploy` REPLACES a machine's whole set** (`--add`/`--remove` amend it, `--set` replaces, `--create` mints a deployment). There is no Jev mode to set: FailproofAI Cloud turns Jev on for a machine while a `jev`/`both` policy is deployed to it (off otherwise); trial one with `:observe`. `deploy` prints the plan and asks **only on an interactive terminal without `--json`** — under `--json` or with stdin redirected it applies immediately, so read `fleet show` first if you want review.
|
|
201
201
|
- `guardrails summary|timeline` — what enforcement actually did; `--since 1h|6h|24h|7d`, `--machine`.
|
|
202
202
|
|
|
203
203
|
**Analytics & assistant:**
|
|
@@ -220,22 +220,29 @@ Built-in assistant. Chats referenced by a **short chat-id** (first 8 hex; prefix
|
|
|
220
220
|
Cloud-managed enforcement, split the way the dashboard splits it: `policies` writes a version, `fleet` decides which machines run it, `guardrails` reports what it blocked. Needs `policies:read` to read, `policies:write` to change anything.
|
|
221
221
|
|
|
222
222
|
### policies
|
|
223
|
-
- `policies list` — one row per published VERSION (versions are immutable and all stay addressable), newest of each policy first; the title counts distinct policies and captions the version total. `state` is active / disabled / archived. JSON `{policies:[…]}` — also every version, so deduplicate on `id` for one row per policy
|
|
224
|
-
- `policies show <id>` — the NEWEST version,
|
|
225
|
-
- `policies publish <id> [SOURCE] [--description "…"] [--no-verify]` — mints a **new version**; never edits one.
|
|
223
|
+
- `policies list` — one row per published VERSION (versions are immutable and all stay addressable), newest of each policy first; the title counts distinct policies and captions the version total. `kind` is `regex` / `jev` / `both`, and a `jev`/`both` row names its Jev checks before the description. `state` is active / disabled / archived. JSON `{policies:[…]}` — also every version, so deduplicate on `id` for one row per policy; each carries `kind`, and for `jev`/`both` `semantic`, `semanticSha256`, and the server-derived `authority`/`reviewedBy`.
|
|
224
|
+
- `policies show <id>` — the NEWEST version: its kind, its Jev check names, the full `source`, and the Jev declarations.
|
|
225
|
+
- `policies publish <id> [SOURCE] [--kind regex|jev|both] [--source FILE] [--semantic FILE] [--description "…"] [--no-verify]` — mints a **new version**; never edits one. A policy's **kind** says what it carries:
|
|
226
|
+
- `regex` — JavaScript only (SOURCE or `--source`): what publish has always done, and the default.
|
|
227
|
+
- `jev` — Jev checks only (`--semantic checks.json`, no JavaScript): a JSON array (or `{"semantic": [...]}`) of 1–24 Jev declarations, each exactly a failproofai pack manifest's semantic entry — `{name, title, appliesTo:[shell|write|read|network|other], mode:deny|instruct, userCanOverride, probes:[{id, instructions, criteria:{true,false}}] (≤6), guidance, precondition?, exempt?}`.
|
|
228
|
+
- `both` — JavaScript plus its own Jev checks, deployed and toggled as one. FailproofAI Cloud makes the JavaScript's verdict `reviewable` by exactly its own checks (`authority`/`reviewedBy` are derived by the server, never sent).
|
|
229
|
+
|
|
230
|
+
Without `--kind` it follows from what you give (`--semantic` alone is `jev`, with source `both`); a kind that contradicts its inputs is **exit 2**, before anything is sent. `fp` checks only the shape of the file (a non-empty list of named objects, ≤24); the server checks the rest — the limits, FailproofAI's 16 reserved check names (**422 `reserved_jev_name`**), and names unique across the org — and names the declaration it refused. SOURCE is a path, `@path`, `-`, a pipe, or omitted to paste on a TTY (Ctrl-D ends); `--semantic` takes a path, `@path` or `-` (only one of the two may be stdin). The JavaScript is **parse-checked with node** before it is sent; nothing downstream does this, and a broken policy otherwise fails on the machine at enforcement time. `--no-verify` skips it, and a host without node publishes with a warning rather than a block. JavaScript carrying Jev fields — `semanticPolicies.add(...)`, or `authority: "reviewable"` on a registration — is refused before sending (**exit 1**, not skipped by `--no-verify`; commented-out code does not count): put Jev checks in `--semantic` (`--kind both`), where they take effect. **Publishing deploys nothing** — the version is unused until `fleet deploy` puts it on a machine.
|
|
226
231
|
- `policies enable <id>` · `policies disable <id> [-y]` — **disable REMOVES the policy from every deployment carrying it**, reissuing each affected machine at a new generation (visible in `fleet history`). `enable` is the exact inverse — it puts the policy back into every deployment it was removed from, reissuing those machines again. Nothing needs redeploying by hand, and `machinesUpdated` in the JSON reports the count for both directions.
|
|
227
232
|
- `policies test [SOURCE] [--tool Bash] [--command "…"] [--file PATH] [--event PreToolUse] [--expect allow|deny|instruct]` — run a policy LOCALLY and print what it decides. Executes the real file (bare `import { deny } from "failproofai"` and all) against a synthetic context; nothing is published, nothing installed. Needs `node`. `--expect` asserts the decision and exits 1 when it differs — a correct `deny` is a PASSING test, so the decision alone never sets the exit code. JSON `{ok, decision, policies:[{name,decision,reason}], expected, met}`; `decision` is the strictest any registered policy returned.
|
|
228
233
|
- `policies compose "<description>" [--out FILE] [--publish ID]` — the assistant drafts policy source from plain English. Prints it and stops by default: a generated policy that deploys itself is one nobody read. `--publish` still syntax-checks first. Needs `policies:write` for BOTH the draft and the publish — `POST /api/agent/compose-policy` is `withAuth("policies:write")`; `agent:use` gates the assistant's other routes and is not checked on this one. The composer has a **30s server-side limit** — a long or vague description simply does not finish, and raising `--timeout` does not help because the cut is not client-side. Retry with something shorter and more specific.
|
|
229
234
|
- `policies delete <id> [-y]` — archives. **A machine already carrying the policy keeps enforcing it** until redeployed; `disable` is what stops enforcement everywhere.
|
|
230
235
|
|
|
231
236
|
### fleet
|
|
232
|
-
- `fleet list` — `machine · label · pol · intended · applied · seen · events · state`. `intended` is the generation deployed, `applied` what the machine last collected (they differ until it polls), and `seen` how long since it last reported anything — a machine can be in sync and dead, or alive and behind, which are different problems. JSON `{machines, deployments}` with raw epoch-ms timestamps plus the computed `drifted
|
|
233
|
-
- `fleet show <machine>` — the set the machine is told to run, **and whether it has collected it**. Reads both the deployment and the machine record, so it reports `not yet collected` / `machine is on #N` / `collected` alongside who deployed it, when, and last-seen. A machine can be told to run a policy it has never picked up; the policy list alone cannot tell you which. JSON `{machine, deployment}` with raw timestamps and both label fields; `deployment: null` when nothing is deployed.
|
|
237
|
+
- `fleet list` — `machine · label · pol · intended · applied · seen · events · state`. `intended` is the generation deployed, `applied` what the machine last collected (they differ until it polls), and `seen` how long since it last reported anything — a machine can be in sync and dead, or alive and behind, which are different problems. `state` reads `N errors` when the machine reported policies it could not apply. JSON `{machines, deployments}` with raw epoch-ms timestamps plus the computed `drifted`; a machine carries `policyErrors`/`policyErrorsAt` and `jevMode` when the server has them.
|
|
238
|
+
- `fleet show <machine>` — the set the machine is told to run, **and whether it has collected it**. Reads both the deployment and the machine record, so it reports `not yet collected` / `machine is on #N` / `collected` alongside who deployed it, when, and last-seen. A machine can be told to run a policy it has never picked up; the policy list alone cannot tell you which. Also the **Jev mode** FailproofAI Cloud derived for it from its policies (`local` when none) and the **policy errors** the machine last reported — `[{id, version, kind: regex|jev|both|daemon, message}]`: a policy that did not load, `jev_unconfigured` (a Jev mode, and the machine's key does not carry Jev), `transcripts_disabled` (a Jev mode on a machine connected with `--no-transcripts`, which never asks), `jev_budget: dropped <name>` (an installed pack's check FailproofAI Cloud left out of a request for the question budget). Whatever is listed there is not enforcing. JSON `{machine, deployment}` with raw timestamps and both label fields; `deployment: null` when nothing is deployed.
|
|
234
239
|
- `fleet deploy <machine> [--add REF]… [--remove ID]… [--set REF]… [--create] [-y]`
|
|
235
240
|
|
|
236
241
|
**A deploy REPLACES the whole set.** The endpoint takes the full list and does not merge. `--add`/`--remove` are a read-modify-write: the CLI reads the current set, applies the delta, prints the complete result, writes that. `--set` replaces everything and is refused alongside `--add`/`--remove`.
|
|
237
242
|
|
|
238
|
-
REF is `id`, `id@version`, `id:effect`, or `id@version:effect`. Effect is `enforce` (default) or `observe`. A bare `--add` of an already-deployed policy **keeps its pinned version** — pass `id@version` to move it.
|
|
243
|
+
REF is `id`, `id@version`, `id:effect`, or `id@version:effect`. Effect is `enforce` (default) or `observe`. A bare `--add` of an already-deployed policy **keeps its pinned version** — pass `id@version` to move it. Every kind deploys the same way (a `both` policy's JavaScript and its checks go together), and `observe` applies to every kind: a `jev` policy's checks, or both halves of a `both` policy, are asked and recorded and decide nothing.
|
|
244
|
+
|
|
245
|
+
There is no Jev mode to set: FailproofAI Cloud turns Jev on for a machine while a `jev` or `both` policy is deployed to it, and off when none is. Jev checks run on FailproofAI Cloud; nothing is installed on the machine. The machine sends each checked tool call of an agent such a policy targets to FailproofAI Cloud on its Cloud Jev credential; without one it reports `jev_unconfigured`, and connected with `--no-transcripts` it never asks and reports `transcripts_disabled`.
|
|
239
246
|
|
|
240
247
|
Deploying to an id that has never checked in is refused (a typo would mint a machine); `--create` allows it for pre-staging.
|
|
241
248
|
|
|
@@ -247,7 +254,7 @@ Cloud-managed enforcement, split the way the dashboard splits it: `policies` wri
|
|
|
247
254
|
|
|
248
255
|
JSON `{plan:{result,added,removed,changed,unchanged,noop}, deployment, applied}` — the plan is included so a harness does not recompute the diff.
|
|
249
256
|
- `fleet diff [machine]` — intent vs delivery per machine, with a `drifted` flag. A machine id nobody has reported under is refused (exit 6), not rendered as an empty fleet.
|
|
250
|
-
- `fleet history <machine>` · `fleet rollback <machine> <generation> [-y]` — rollback mints a NEW generation carrying the old set; history stays append-only. The `change` column uses the deploy plan's vocabulary: `+` added, `-` removed, `~` same policy at a different version or effect (an enforce → observe flip is a policy that stopped blocking, so it is never "no change").
|
|
257
|
+
- `fleet history <machine>` · `fleet rollback <machine> <generation> [-y]` — rollback mints a NEW generation carrying the old set; history stays append-only. The `change` column uses the deploy plan's vocabulary: `+` added, `-` removed, `~` same policy at a different version or effect (an enforce → observe flip is a policy that stopped blocking, so it is never "no change"), and `~ jev <from>→<to>` for a Jev mode change. The `jev` column is the mode FailproofAI Cloud had with each generation (`local` = none); a rollback's prompt and result say `jev mode <now> → <then>` when it differs, and the JSON adds `jevModeBefore`.
|
|
251
258
|
- `fleet rename <machine> "<label>"` — a human label; the id never changes. The server stores it as an override beside the machine's self-asserted label. An empty label **clears** the override (the machine falls back to its own label, else its id) and the CLI says so rather than reporting a rename to nothing. A machine that has never checked in cannot be renamed → exit 6.
|
|
252
259
|
|
|
253
260
|
### guardrails
|