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.
Files changed (214) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/audit/run/route.js +22 -22
  23. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  31. package/.next/standalone/.next/server/app/audit/page.js +1 -1
  32. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  34. package/.next/standalone/.next/server/app/index.html +1 -1
  35. package/.next/standalone/.next/server/app/index.rsc +3 -3
  36. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  37. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  38. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  39. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  40. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  43. package/.next/standalone/.next/server/app/policies/page.js +4 -4
  44. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  51. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  54. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  57. package/.next/standalone/.next/server/app/settings/page.js +7 -7
  58. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  60. package/.next/standalone/.next/server/chunks/{[externals]__0-ha5ydqq0mws._.js → [externals]__0vt9i6fqinjne._.js} +1 -1
  61. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__20xe05hm5u8_1._.js → [root-of-the-server]__0f_4g-vazrvxv._.js} +1 -1
  62. package/.next/standalone/.next/server/chunks/_0hf5q4xzyg40j._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/_19011-u44143j._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +2 -2
  67. package/.next/standalone/.next/server/chunks/{_0kc-piux3hady._.js → _1y590mjzpkbos._.js} +2 -2
  68. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/src_hooks_0_em82s16zfym._.js +5 -0
  70. package/.next/standalone/.next/server/chunks/src_hooks_builtin-policies_ts_1yq5ybgx1v98e._.js +3 -0
  71. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3h94wnn._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/src_hooks_daemon-client_ts_0il_yu1zc0291._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn_4twyh._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ts0fg5yih1c1._.js → [root-of-the-server]__00l2u6j86cb8y._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1_xbg2kx30wp-._.js → [root-of-the-server]__0naevg4bcoh54._.js} +1 -1
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pwj2h2efe5f-._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0vcyntk4snf0f._.js → [root-of-the-server]__1vofkb2oq7p7e._.js} +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/_0bseae9u7g98r._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/ssr/_0mouhaflye1ro._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/ssr/{_1gb0ifpkowwub._.js → _0ouu886szh6yj._.js} +10 -9
  91. package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/ssr/{_1ve8y151ua7qx._.js → _1fw0_nzkae5ao._.js} +2 -2
  94. package/.next/standalone/.next/server/chunks/ssr/_20hn4gn87nsjc._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
  96. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr0vt64h._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +2 -2
  101. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
  102. 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
  103. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0janj0i6dqko7._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9ddabn3_._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +5 -0
  107. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +5 -0
  108. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4l1tzrm._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/src_hooks_daemon-client_ts_0k41_i7jg6ndv._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589gp4p60j._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me658zqmh_._.js +1 -1
  112. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  113. package/.next/standalone/.next/server/pages/404.html +1 -1
  114. package/.next/standalone/.next/server/pages/500.html +1 -1
  115. package/.next/standalone/.next/server/preview-props.json +3 -3
  116. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  117. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  118. package/.next/standalone/.next/static/chunks/{0pjxnp927y7_j.js → 0ay__-d2h1xb5.js} +1 -1
  119. package/.next/standalone/.next/static/chunks/{2znt1784j9gd9.js → 0tbm3vjm62h-2.js} +1 -1
  120. package/.next/standalone/.next/static/chunks/1c_jf6g5wnslk.js +1 -0
  121. package/.next/standalone/.next/static/chunks/{3tn1bzk094e8e.js → 1my3-5p47t7r-.js} +1 -1
  122. package/.next/standalone/.next/static/chunks/20py8k1gd_i1m.js +1 -0
  123. package/.next/standalone/.next/static/chunks/21hckpmsc4g70.js +6 -0
  124. package/.next/standalone/.next/static/chunks/2hmex93ppj1ke.js +1 -0
  125. package/.next/standalone/.next/static/chunks/2o6d37j425uci.js +1 -0
  126. package/.next/standalone/.next/static/chunks/40oworn_-x12p.js +1 -0
  127. package/.next/standalone/.next/static/chunks/{2qz5piiir-qqm.js → 42xptz88mtq3x.js} +1 -1
  128. package/.next/standalone/app/actions/update-jev-config.ts +5 -3
  129. package/.next/standalone/app/settings/jev-panel.tsx +1 -1
  130. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +32 -0
  131. package/.next/standalone/fp-cloud-cli/README.md +40 -1
  132. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +91 -12
  133. package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +78 -16
  134. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +84 -18
  135. package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +124 -6
  136. package/.next/standalone/fp-cloud-cli/fp_cli/models.py +89 -6
  137. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +189 -20
  138. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +8 -5
  139. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +2 -2
  140. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +14 -7
  141. package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +527 -0
  142. package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +46 -0
  143. package/.next/standalone/fp-cloud-cli/tests/test_output.py +23 -0
  144. package/.next/standalone/hermes-plugin/README.md +6 -4
  145. package/.next/standalone/hermes-plugin/__init__.py +27 -0
  146. package/.next/standalone/hermes-plugin/client.py +5 -1
  147. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  148. package/.next/standalone/package.json +11 -10
  149. package/.next/standalone/sdk/python/uv.lock +31 -8
  150. package/.next/standalone/server.js +1 -1
  151. package/bin/failproofai.mjs +17 -1
  152. package/dist/cli.mjs +3507 -1250
  153. package/dist/worker.mjs +1779 -169
  154. package/hermes-plugin/README.md +6 -4
  155. package/hermes-plugin/__init__.py +27 -0
  156. package/hermes-plugin/client.py +5 -1
  157. package/hermes-plugin/plugin.yaml +1 -1
  158. package/package.json +11 -10
  159. package/src/audit/open-browser.ts +6 -7
  160. package/src/hooks/agent-roster.ts +180 -0
  161. package/src/hooks/agent-skill.ts +203 -0
  162. package/src/hooks/agent-targets.ts +60 -0
  163. package/src/hooks/cloud-connection.ts +91 -45
  164. package/src/hooks/cloud-enrollment-cli.ts +7 -4
  165. package/src/hooks/cloud-login.ts +6 -3
  166. package/src/hooks/cloud-managed-policies.ts +430 -20
  167. package/src/hooks/cloud-policy-errors.ts +481 -0
  168. package/src/hooks/codex-system-hooks.ts +10 -4
  169. package/src/hooks/config-status.ts +10 -4
  170. package/src/hooks/configure-wizard.ts +54 -26
  171. package/src/hooks/custom-hooks-loader.ts +26 -3
  172. package/src/hooks/daemon-client.ts +9 -4
  173. package/src/hooks/daemon-service.ts +2 -1
  174. package/src/hooks/delivery-health.ts +47 -2
  175. package/src/hooks/effective-reviewers.ts +36 -1
  176. package/src/hooks/fp-config.ts +19 -0
  177. package/src/hooks/fp-home.ts +6 -0
  178. package/src/hooks/handler.ts +255 -19
  179. package/src/hooks/health-probe.ts +15 -0
  180. package/src/hooks/hook-activity-store.ts +7 -1
  181. package/src/hooks/integrations.ts +36 -14
  182. package/src/hooks/jev-activity.ts +117 -1
  183. package/src/hooks/jev-cli.ts +196 -1
  184. package/src/hooks/jev-cloud-connection.ts +92 -0
  185. package/src/hooks/manager.ts +174 -34
  186. package/src/hooks/pack-failclosed.ts +2 -0
  187. package/src/hooks/pack-manifest.ts +8 -1
  188. package/src/hooks/policy-evaluator.ts +22 -1
  189. package/src/hooks/policy-registry.ts +3 -2
  190. package/src/hooks/policy-reviewability.ts +23 -5
  191. package/src/hooks/semantic/cloud-jev-health.ts +227 -0
  192. package/src/hooks/semantic/cloud-jev.ts +868 -0
  193. package/src/hooks/semantic/combine.ts +26 -1
  194. package/src/hooks/semantic/jev-client.ts +19 -2
  195. package/src/hooks/semantic/jev-config.ts +179 -30
  196. package/src/hooks/semantic/jev-review.ts +217 -10
  197. package/src/hooks/semantic/pack-policies.ts +2 -1
  198. package/src/hooks/semantic/types.ts +8 -0
  199. package/src/hooks/session-pause-cli.ts +1 -1
  200. package/src/hooks/tui.ts +13 -4
  201. package/src/hooks/uninstall-cli.ts +12 -1
  202. package/src/hooks/worker-server.ts +5 -1
  203. package/.next/standalone/.next/server/chunks/src_hooks_1aveq0uhs2t1b._.js +0 -5
  204. package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7cyqlb2._.js +0 -3
  205. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo55j16x._.js +0 -5
  206. package/.next/standalone/.next/static/chunks/0a-ccsp3xmypz.js +0 -1
  207. package/.next/standalone/.next/static/chunks/13jv-xvabeb62.js +0 -6
  208. package/.next/standalone/.next/static/chunks/2ank2u3apy8m6.js +0 -1
  209. package/.next/standalone/.next/static/chunks/2meqb9kygrncb.js +0 -1
  210. package/.next/standalone/.next/static/chunks/2ohktp_-lkv3x.js +0 -1
  211. package/.next/standalone/.next/static/chunks/2py0eldhwz8ay.js +0 -1
  212. /package/.next/standalone/.next/static/{SNr7o_-G4kNBQTR-iLZMM → LHCtFxVG0mdvd4lsXAOFC}/_buildManifest.js +0 -0
  213. /package/.next/standalone/.next/static/{SNr7o_-G4kNBQTR-iLZMM → LHCtFxVG0mdvd4lsXAOFC}/_clientMiddlewareManifest.js +0 -0
  214. /package/.next/standalone/.next/static/{SNr7o_-G4kNBQTR-iLZMM → LHCtFxVG0mdvd4lsXAOFC}/_ssgManifest.js +0 -0
@@ -41,7 +41,7 @@ import type { HookActivityEntry } from "./hook-activity-store";
41
41
 
42
42
  export type JevActivityFields = Pick<
43
43
  HookActivityEntry,
44
- "evaluator" | "jevDecision" | "jevCleared" | "jevFallbackReason" | "jevLatencyMs" | "jevModel" | "jevMode"
44
+ "evaluator" | "jevDecision" | "jevCleared" | "jevFallbackReason" | "jevLatencyMs" | "jevModel" | "jevMode" | "jevChecks"
45
45
  >;
46
46
 
47
47
  /** Every Jev key on an activity row, in the order they are documented. */
@@ -53,6 +53,7 @@ export const JEV_ACTIVITY_KEYS = [
53
53
  "jevLatencyMs",
54
54
  "jevModel",
55
55
  "jevMode",
56
+ "jevChecks",
56
57
  ] as const satisfies ReadonlyArray<keyof JevActivityFields>;
57
58
 
58
59
  const EVALUATORS = new Set(["jev", "jev-fallback"]);
@@ -219,6 +220,113 @@ export function normalizeJevFallbackReason(raw: unknown): string | undefined {
219
220
  return JEV_REASON_CODES.has(code) ? code : JEV_REASON_OTHER;
220
221
  }
221
222
 
223
+ // ── Per-check answers ────────────────────────────────────────────────────────
224
+
225
+ /** What one asked Jev check concluded. */
226
+ export const JEV_CHECK_VERDICTS = ["none", "deny", "instruct", "overridden"] as const;
227
+
228
+ /**
229
+ * Why a check ended where it did, as a closed code (read off its outcome,
230
+ * never text from the call):
231
+ * - `not-detected`: its probes did not hold (evidence under the fire threshold);
232
+ * - `exempt`: it fired, and its `exempt` question held;
233
+ * - `fired`: it fired and nothing softened it;
234
+ * - `injection`: it fired and the request also argued for its own approval;
235
+ * - `user-asked` (v0), `op-requested`, `task-step` (v1): the human's consent cleared it;
236
+ * - `downgraded`: a deny the human's task softened to a warning;
237
+ * - `scan-incomplete`: a clear withheld because the command scan may have missed a target.
238
+ */
239
+ export const JEV_CHECK_WHY = [
240
+ "not-detected",
241
+ "exempt",
242
+ "fired",
243
+ "injection",
244
+ "user-asked",
245
+ "op-requested",
246
+ "task-step",
247
+ "downgraded",
248
+ "scan-incomplete",
249
+ ] as const;
250
+
251
+ /**
252
+ * One Jev check that was asked on this call, and its answer: names, closed
253
+ * codes and probabilities only — never question text, the command or the
254
+ * human's words. Jev answers yes/no probabilities, not reasons, so `why` is
255
+ * the decider's own reading of them.
256
+ */
257
+ export interface JevCheckRecord {
258
+ /** The check's name (`semantic/<check>` without the prefix). */
259
+ check: string;
260
+ /** Who declared it: `cloud/<policyId>@<version>` or `pack/<packId>@<version>`. Absent for a caller-supplied set. */
261
+ policy?: string;
262
+ verdict: (typeof JEV_CHECK_VERDICTS)[number];
263
+ why: (typeof JEV_CHECK_WHY)[number];
264
+ /** Evidence: the minimum over its probes, 2 decimals. */
265
+ p: number;
266
+ /** Its `exempt` answer, when it declares one. */
267
+ exempt?: number;
268
+ /** Each probe's answer by probe id, when the answers came back. */
269
+ probes?: Record<string, number>;
270
+ /** A check of a watched (observe) Cloud policy: recorded, never enforced. */
271
+ watched?: true;
272
+ }
273
+
274
+ export const JEV_CHECKS_MAX = 32;
275
+ export const JEV_CHECK_PROBES_MAX = 8;
276
+ /** A check name: a slug, `.` allowed (a Cloud check name may carry one). */
277
+ export const JEV_CHECK_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
278
+ /** A probe id, as declarations allow it. */
279
+ export const JEV_PROBE_ID_RE = /^[a-z][a-z0-9_]{0,31}$/;
280
+ const CHECK_VERDICT_SET: ReadonlySet<string> = new Set(JEV_CHECK_VERDICTS);
281
+ const CHECK_WHY_SET: ReadonlySet<string> = new Set(JEV_CHECK_WHY);
282
+
283
+ const isP = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 1;
284
+ /** A probability at 2 decimals — what the reason strings print. */
285
+ export const round2 = (v: number): number => Math.round(v * 100) / 100;
286
+
287
+ /** One record, rebuilt field by field, or null when it is not one. */
288
+ function sanitizeCheck(raw: unknown): JevCheckRecord | null {
289
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
290
+ const r = raw as Record<string, unknown>;
291
+ if (typeof r.check !== "string" || !JEV_CHECK_NAME_RE.test(r.check)) return null;
292
+ if (typeof r.verdict !== "string" || !CHECK_VERDICT_SET.has(r.verdict)) return null;
293
+ if (typeof r.why !== "string" || !CHECK_WHY_SET.has(r.why)) return null;
294
+ if (!isP(r.p)) return null;
295
+ const out: JevCheckRecord = {
296
+ check: r.check,
297
+ verdict: r.verdict as JevCheckRecord["verdict"],
298
+ why: r.why as JevCheckRecord["why"],
299
+ p: round2(r.p),
300
+ };
301
+ if (typeof r.policy === "string" && isJevPolicyName(r.policy) && !/\s/.test(r.policy) && /^(?:cloud|pack)\//.test(r.policy)) {
302
+ out.policy = r.policy;
303
+ }
304
+ if (isP(r.exempt)) out.exempt = round2(r.exempt);
305
+ if (r.watched === true) out.watched = true;
306
+ if (r.probes && typeof r.probes === "object" && !Array.isArray(r.probes)) {
307
+ const probes: Record<string, number> = {};
308
+ for (const [id, v] of Object.entries(r.probes as Record<string, unknown>)) {
309
+ if (Object.keys(probes).length >= JEV_CHECK_PROBES_MAX) break;
310
+ if (JEV_PROBE_ID_RE.test(id) && isP(v)) probes[id] = round2(v);
311
+ }
312
+ if (Object.keys(probes).length > 0) out.probes = probes;
313
+ }
314
+ return out;
315
+ }
316
+
317
+ /** A row's `jevChecks`, valid records only (at most {@link JEV_CHECKS_MAX}), or undefined when it is not a list. */
318
+ export function sanitizeJevChecks(raw: unknown): JevCheckRecord[] | undefined {
319
+ if (!Array.isArray(raw)) return undefined;
320
+ const out: JevCheckRecord[] = [];
321
+ for (const item of raw) {
322
+ if (out.length >= JEV_CHECKS_MAX) break;
323
+ const rec = sanitizeCheck(item);
324
+ if (rec) out.push(rec);
325
+ }
326
+ // Every entry invalid: not "nothing asked" — the list is unreadable, so it goes.
327
+ return raw.length > 0 && out.length === 0 ? undefined : out;
328
+ }
329
+
222
330
  /**
223
331
  * The Jev fields of `entry`, each one valid or absent. Non-Jev keys pass
224
332
  * through untouched, and an entry with no Jev keys comes back as the same
@@ -265,6 +373,11 @@ export function sanitizeJevActivity<T extends JevActivityFields>(entry: T): T {
265
373
  if (typeof out.jevModel === "string" && JEV_MODEL_RE.test(out.jevModel.trim())) out.jevModel = out.jevModel.trim();
266
374
  else drop("jevModel");
267
375
 
376
+ // Only on an answered call: a fallback or an unconsulted call asked nothing.
377
+ const checks = out.evaluator === "jev" ? sanitizeJevChecks(out.jevChecks) : undefined;
378
+ if (checks === undefined) drop("jevChecks");
379
+ else out.jevChecks = checks;
380
+
268
381
  return out;
269
382
  }
270
383
 
@@ -307,6 +420,9 @@ export function jevOutcome(raw: JevActivityFields): JevOutcome | null {
307
420
  function outcomeOf(e: JevActivityFields): JevOutcome | null {
308
421
  if (e.evaluator === "jev-fallback") return "fallback";
309
422
  if (e.evaluator !== "jev") return null;
423
+ // A request that carried no check (only the global intent questions, or a
424
+ // FailproofAI Cloud short-circuit) decided nothing: Jev had nothing to ask.
425
+ if (Array.isArray(e.jevChecks) && e.jevChecks.length === 0) return "no-request";
310
426
  if (e.jevCleared !== undefined || e.jevLatencyMs !== undefined || e.jevModel !== undefined) return "answered";
311
427
  if (e.jevDecision === "deny" || e.jevDecision === "instruct") return "answered";
312
428
  if (e.jevDecision === "allow") return "no-request";
@@ -108,6 +108,8 @@ import {
108
108
  inspectJevConfig,
109
109
  jevCloudBaseUrl,
110
110
  jevConfigPath,
111
+ loadJevConfigForCloudMode,
112
+ TRANSCRIPTS_DISABLED_PROBLEM,
111
113
  providerForUrl,
112
114
  providerHostConflict,
113
115
  readJevConfigFileForUpdate,
@@ -144,7 +146,8 @@ import {
144
146
  type ReviewableCoverage,
145
147
  } from "./policy-reviewability";
146
148
  import { jevStats, type JevStats } from "./semantic/jev-stats";
147
- import { readCredentials, readJevCloudCredential, type JevCloudCredential } from "./fp-config";
149
+ import { listActiveCloudManagedPolicies, readCloudJevState, type CloudJevState } from "./cloud-managed-policies";
150
+ import { CLOUD_MANAGED_JEV_MODE, cloudManagesJevMode, readCredentials, readJevCloudCredential, type JevCloudCredential } from "./fp-config";
148
151
  import type { JevRequest } from "./semantic/types";
149
152
  import { TOKEN_ON_ARGV, optsFor, paint, screenKit, stack, wrap, type RenderOpts } from "./tui";
150
153
 
@@ -364,6 +367,9 @@ function cloudConnection(): { cloudConnected: boolean; keyCarriesJev: boolean }
364
367
  }
365
368
  }
366
369
 
370
+ /** What `jev setup --mode` says on a machine whose Jev mode FailproofAI Cloud sets. */
371
+ const CLOUD_MANAGED_MODE_LINES = [CLOUD_MANAGED_JEV_MODE, "Deploy or remove Jev policies in FailproofAI Cloud to change it. Nothing was written."];
372
+
367
373
  /** The one line `status` titles a connected machine whose key has no Jev with. */
368
374
  const KEY_LACKS_JEV_TITLE = "off — no Jev key is stored for this machine's FailproofAI Cloud connection";
369
375
 
@@ -686,6 +692,9 @@ async function setupRun(argv: string[], deps: JevCliDeps, opts: RenderOpts): Pro
686
692
  if (typeof parsed === "string") return fail([parsed, "", ...JEV_USAGE]);
687
693
  if (parsed.positionals.length > 0) return fail([STRAY_ARGUMENT, "", ...JEV_USAGE]);
688
694
  const { values, bools } = parsed;
695
+ // A machine connected for policies has its Jev mode set by FailproofAI Cloud
696
+ // alone: enforce while a Jev policy is deployed to it, off when none is.
697
+ if (values.has("--mode") && cloudManagesJevMode()) return fail(CLOUD_MANAGED_MODE_LINES);
689
698
  if (bools.has("--key-stdin") && bools.has("--key-from-env")) return fail(["Use one of --key-stdin and --key-from-env, not both."]);
690
699
  // One source for the key, so no run has to decide which of two the person
691
700
  // meant. The message names the recommended one first.
@@ -1418,7 +1427,193 @@ function safeCoverage(): ReviewableCoverage | null {
1418
1427
  }
1419
1428
  }
1420
1429
 
1430
+ // ── status: FailproofAI Cloud ────────────────────────────────────────────────
1431
+ //
1432
+ // FailproofAI Cloud can SET this machine's Jev mode (`active.json` `jevMode`).
1433
+ // With `observe`/`enforce` the Jev checks Cloud deployed here run ON Cloud —
1434
+ // nothing of them is on this machine (CONTRACT C10) — every gated tool call
1435
+ // goes there on the Cloud Jev credential, and `jev.json` is not used. With
1436
+ // `off`, Jev is off whatever `jev.json` says. So `status` answers from Cloud
1437
+ // first; without a Cloud mode the local answer stands.
1438
+ //
1439
+ // What it can show about Cloud's checks is only what the machine knows: the
1440
+ // mode, and the `reviewedBy` names of the deployment's `both` policies — the
1441
+ // Cloud checks that may clear their regex half. Never a check's contents: the
1442
+ // machine does not have them.
1443
+
1444
+ /** Label for everything Cloud decided here. Customer-facing: never "AgentEye". */
1445
+ const MODE_SET_BY_CLOUD = "mode set by FailproofAI Cloud";
1446
+
1447
+ /** The one sentence the contract fixes for `jev status` under a Cloud mode (C10.5). */
1448
+ export function jevRunsOnCloudLine(mode: string): string {
1449
+ return `Jev checks run on FailproofAI Cloud (mode: ${mode})`;
1450
+ }
1451
+
1452
+ function safeCloudJev(): CloudJevState {
1453
+ try {
1454
+ return readCloudJevState();
1455
+ } catch {
1456
+ return { jevMode: null, deployment: null, errors: [] };
1457
+ }
1458
+ }
1459
+
1460
+ /** A `both` policy's Cloud reviewers, as the deployment names them. */
1461
+ interface CloudReviewers {
1462
+ policy: string;
1463
+ version: number;
1464
+ effect: "enforce" | "observe";
1465
+ reviewedBy: string[];
1466
+ }
1467
+
1468
+ /** Every Cloud JS policy that names Cloud checks in `reviewedBy` — a `both` policy. Never throws. */
1469
+ function cloudReviewers(): CloudReviewers[] {
1470
+ try {
1471
+ // Every assignment, agent-scoped ones included: this describes the machine.
1472
+ return listActiveCloudManagedPolicies()
1473
+ .filter((p) => p.authority === "reviewable" && (p.reviewedBy?.length ?? 0) > 0)
1474
+ .map((p) => ({ policy: p.id, version: p.version, effect: p.effect, reviewedBy: [...(p.reviewedBy ?? [])] }));
1475
+ } catch {
1476
+ return [];
1477
+ }
1478
+ }
1479
+
1480
+ /** What to do about a Cloud mode this machine cannot act on. */
1481
+ function cloudProblemAdvice(problem: string): string[] {
1482
+ if (problem === TRANSCRIPTS_DISABLED_PROBLEM) {
1483
+ return [
1484
+ "Jev does not run here: this machine was connected for decisions only (--no-transcripts), and a Jev call sends each checked tool call and the recent prompt to FailproofAI Cloud.",
1485
+ "To use FailproofAI Cloud's Jev checks, reconnect without --no-transcripts (failproofai config --token <key>).",
1486
+ ];
1487
+ }
1488
+ return [
1489
+ `Jev cannot run here: ${problem}.`,
1490
+ "Connect with a key that carries jev:evaluate (failproofai config --token <key>). A local jev.json is not used while FailproofAI Cloud sets the mode.",
1491
+ ];
1492
+ }
1493
+
1421
1494
  async function status(argv: string[], opts: RenderOpts): Promise<JevCliResult> {
1495
+ const cloud = safeCloudJev();
1496
+ if (cloud.jevMode === null && cloud.errors.length === 0) return localStatus(argv, opts);
1497
+ const parsed = parseFlags(argv, new Set(["--json"]));
1498
+ if (typeof parsed === "string" || parsed.positionals.length > 0) return localStatus(argv, opts);
1499
+ const asJson = parsed.bools.has("--json");
1500
+
1501
+ if (cloud.jevMode === null) {
1502
+ // A mode this build cannot read is no mode: jev.json decides. Said beside it.
1503
+ const local = await localStatus(argv, opts);
1504
+ if (asJson && local.json !== undefined) {
1505
+ const body = JSON.parse(local.json) as Record<string, unknown>;
1506
+ body.cloud = { jevMode: null, modeSetBy: null, errors: cloud.errors };
1507
+ return { ...local, json: JSON.stringify(body, null, 2) };
1508
+ }
1509
+ return { ...local, lines: [...local.lines, ...jevWarn(cloud.errors.map((e) => `${e.id}: ${e.message}`), opts)] };
1510
+ }
1511
+
1512
+ const mode = cloud.jevMode;
1513
+ const resolved = loadJevConfigForCloudMode(mode);
1514
+ const reviewers = cloudReviewers();
1515
+ let stats: JevStats | null = null;
1516
+ try {
1517
+ stats = await jevStats();
1518
+ } catch {
1519
+ stats = null;
1520
+ }
1521
+ const cfg = resolved.config;
1522
+ const coverage = cfg ? safeCoverage() : null;
1523
+ const route = cfg ? jevRoute(cfg) : null;
1524
+ const running = cfg !== null;
1525
+
1526
+ if (asJson) {
1527
+ const body: Record<string, unknown> = {
1528
+ path: jevConfigPath(),
1529
+ status: running ? "ok" : mode === "off" ? "off" : "unconfigured",
1530
+ mode,
1531
+ modeSetBy: "FailproofAI Cloud",
1532
+ ...(mode !== "off" ? { runsOn: "FailproofAI Cloud" } : {}),
1533
+ ...(cfg && route
1534
+ ? {
1535
+ provider: cfg.provider,
1536
+ providerLabel: providerLabel(cfg.provider),
1537
+ endpoint: shownEndpoint(cfg.provider, route.endpoint),
1538
+ model: route.model,
1539
+ modelIsDefault: route.modelIsDefault,
1540
+ timeoutMs: cfg.timeoutMs,
1541
+ }
1542
+ : {}),
1543
+ ...(resolved.problem ? { problem: resolved.problem } : {}),
1544
+ cloud: { jevMode: mode, modeSetBy: "FailproofAI Cloud", reviewers, errors: cloud.errors },
1545
+ ...(coverage
1546
+ ? {
1547
+ reviewablePolicies: {
1548
+ enabled: coverage.enabled,
1549
+ reviewable: coverage.reviewable,
1550
+ customPolicyFiles: coverage.customFiles,
1551
+ // The checks THIS machine's packs give; Cloud's are asked on Cloud.
1552
+ jevChecks: coverage.jevChecks,
1553
+ // "No checks installed" is not a problem here: FailproofAI Cloud's
1554
+ // checks are asked whatever this machine has installed.
1555
+ problem: coverage.jevChecks === 0 ? null : reviewableProblem(coverage),
1556
+ },
1557
+ }
1558
+ : {}),
1559
+ stats,
1560
+ };
1561
+ return ok([], JSON.stringify(body, null, 2));
1562
+ }
1563
+
1564
+ const head =
1565
+ mode === "off"
1566
+ ? jevHeader("Jev status", `off — ${MODE_SET_BY_CLOUD}`, opts)
1567
+ : running
1568
+ ? jevHeader("Jev status", `on · ${mode} — ${jevRunsOnCloudLine(mode)}`, opts)
1569
+ : jevHeader("Jev status", `off — FailproofAI Cloud set ${mode}, and this machine cannot run it`, opts);
1570
+ const reviewerRows = reviewers.map((r): [string, string] => [
1571
+ `${r.policy} v${r.version}`,
1572
+ `${r.reviewedBy.join(", ")}${r.effect === "observe" ? " (not asked while observed)" : ""}`,
1573
+ ]);
1574
+ return ok(
1575
+ stack(
1576
+ head,
1577
+ mode === "off"
1578
+ ? jevNote(
1579
+ `FailproofAI Cloud switched Jev off on this machine. That overrides ${jevConfigPath()} for as long as this machine is connected.`,
1580
+ opts,
1581
+ )
1582
+ : jevNote(
1583
+ `${jevRunsOnCloudLine(mode)}. Every checked tool call goes to FailproofAI Cloud, which asks the Jev checks it ` +
1584
+ "deployed to this machine and this machine's installed packs' checks in one request; nothing of Cloud's checks is " +
1585
+ `installed here. ${jevConfigPath()} is not used while FailproofAI Cloud sets the mode. In enforce, Jev's own deny ` +
1586
+ "checks block calls as well as clearing the regex policies they review. Jev-only Cloud policies are not on this machine, so they are " +
1587
+ "not listed here; fp fleet show and the dashboard list them.",
1588
+ opts,
1589
+ ),
1590
+ cfg && route
1591
+ ? jevRows(
1592
+ [
1593
+ ["runs on", "FailproofAI Cloud"],
1594
+ ["endpoint", shownEndpoint(cfg.provider, route.endpoint)],
1595
+ ["model", route.modelIsDefault ? `${route.model} (provider default)` : route.model],
1596
+ ["mode", `${modeLine(mode)} (${MODE_SET_BY_CLOUD})`],
1597
+ ["timeout", `${cfg.timeoutMs} ms`],
1598
+ ],
1599
+ opts,
1600
+ )
1601
+ : jevRows([["mode", mode === "off" ? `${modeLine("off")} (${MODE_SET_BY_CLOUD})` : `${mode} (${MODE_SET_BY_CLOUD}) — not running`]], opts),
1602
+ resolved.problem ? jevWarn(cloudProblemAdvice(resolved.problem), opts) : null,
1603
+ reviewerRows.length > 0
1604
+ ? stack(
1605
+ [screenKit(opts).head("Cloud reviewers", "FailproofAI Cloud policies reviewed by their Cloud Jev checks")],
1606
+ jevRows(reviewerRows, opts),
1607
+ )
1608
+ : [],
1609
+ cloud.errors.length > 0 ? jevWarn(cloud.errors.map((e) => `${e.id}: ${e.message}`), opts) : null,
1610
+ coverage ? jevNote(reviewableSummary(coverage), opts) : null,
1611
+ jevStatsLines(stats, opts),
1612
+ ),
1613
+ );
1614
+ }
1615
+
1616
+ async function localStatus(argv: string[], opts: RenderOpts): Promise<JevCliResult> {
1422
1617
  const parsed = parseFlags(argv, new Set(["--json"]));
1423
1618
  if (typeof parsed === "string") return fail([parsed, "", ...JEV_USAGE]);
1424
1619
  if (parsed.positionals.length > 0) return fail([STRAY_ARGUMENT, "", ...JEV_USAGE]);
@@ -86,8 +86,100 @@ export type CloudJevConfigWrite =
86
86
  /** Set when the kept file leaves Jev off here: refused, or switched off. */
87
87
  jevOff?: { why: "refused"; problem: string; fix?: string } | { why: "off" };
88
88
  }
89
+ /**
90
+ * The Cloud's own `jev.json` (provider `failproofai`, as `config` writes it)
91
+ * named another FailproofAI Cloud origin, and was pointed at this one: same
92
+ * mode, same everything else. A BYOK file is never touched.
93
+ */
94
+ | { status: "repointed"; path: string; from: string; baseUrl: string; mode: string }
89
95
  | { status: "error"; path: string; problem: string };
90
96
 
97
+ /**
98
+ * Create `jev.json` for the Cloud provider unless one exists. Never throws and
99
+ * never replaces a file — except the Cloud's own file pointing at another
100
+ * FailproofAI Cloud origin, which is re-pointed here (`repointCloudJevConfig`):
101
+ * a reconnect on a new URL (http → https) otherwise left Jev off, refused for
102
+ * naming an origin the key was not issued for.
103
+ */
104
+ export function writeCloudJevConfig(cloudBase: string): CloudJevConfigWrite {
105
+ const first = writeCloudJevConfigIfAbsent(cloudBase);
106
+ if (first.status === "kept" && first.provider === JEV_CLOUD_PROVIDER && first.otherOrigin) {
107
+ return repointCloudJevConfig(cloudBase, first);
108
+ }
109
+ return first;
110
+ }
111
+
112
+ /**
113
+ * Point the Cloud's `jev.json` at `cloudBase`, keeping every other field. The
114
+ * file is moved aside first and judged there (as `removeCloudJevConfig` does),
115
+ * so a BYOK file renamed into place meanwhile is never the one rewritten, and
116
+ * the new file is linked in no-clobber.
117
+ */
118
+ function repointCloudJevConfig(cloudBase: string, found: Extract<CloudJevConfigWrite, { status: "kept" }>): CloudJevConfigWrite {
119
+ const path = jevConfigPath();
120
+ let baseUrl: string;
121
+ try {
122
+ baseUrl = jevCloudBaseUrl(cloudBase);
123
+ } catch {
124
+ return found;
125
+ }
126
+ let st: Stats;
127
+ try {
128
+ st = lstatSync(path);
129
+ } catch {
130
+ return found;
131
+ }
132
+ if (!st.isFile()) return found;
133
+ const aside = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.repointing`;
134
+ try {
135
+ renameSync(path, aside);
136
+ } catch {
137
+ return found;
138
+ }
139
+ let raw: Record<string, unknown> | null = null;
140
+ try {
141
+ const parsed: unknown = JSON.parse(readFileSync(aside, "utf8"));
142
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) raw = parsed as Record<string, unknown>;
143
+ } catch {
144
+ raw = null;
145
+ }
146
+ if (!raw || raw.provider !== JEV_CLOUD_PROVIDER) {
147
+ // Not the Cloud's after all: back exactly as it was.
148
+ putBack(aside, path, st.mode & 0o777);
149
+ return found;
150
+ }
151
+ const body = `${JSON.stringify({ ...raw, baseUrl }, null, 2)}\n`;
152
+ const tmp = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
153
+ try {
154
+ writeFileSync(tmp, body, { mode: 0o600, flag: "wx" });
155
+ chmodSync(tmp, 0o600);
156
+ linkSync(tmp, path);
157
+ } catch {
158
+ // Something landed at the path meanwhile, or the write failed: the old
159
+ // file goes back (never over the new one) and nothing was re-pointed.
160
+ putBack(aside, path, st.mode & 0o777);
161
+ return found;
162
+ } finally {
163
+ try {
164
+ rmSync(tmp, { force: true });
165
+ } catch {
166
+ // A stray temp file holds no key.
167
+ }
168
+ }
169
+ try {
170
+ unlinkSync(aside);
171
+ } catch {
172
+ // The aside copy is the old URL only; harmless if it lingers.
173
+ }
174
+ return {
175
+ status: "repointed",
176
+ path,
177
+ from: found.otherOrigin ?? "",
178
+ baseUrl,
179
+ mode: typeof raw.mode === "string" ? raw.mode : DEFAULT_JEV_MODE,
180
+ };
181
+ }
182
+
91
183
  /**
92
184
  * Create `jev.json` for the Cloud provider unless one exists. Never throws and
93
185
  * never replaces a file.