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
@@ -236,7 +236,7 @@ import type { PolicyAuthority } from "../policy-types";
236
236
  // shape check the row's own `jevModel` does. Importing them makes a rename a
237
237
  // compile error here. `jev-activity.ts` is pure (no node imports, no semantic
238
238
  // modules), so this costs the hook path nothing.
239
- import { JEV_MODEL_RE, JEV_REASON_REQUEST_CUT } from "../jev-activity";
239
+ import { JEV_MODEL_RE, JEV_REASON_REQUEST_CUT, type JevCheckRecord } from "../jev-activity";
240
240
 
241
241
  export type Decision = "allow" | "deny" | "instruct";
242
242
  export type JevMode = "observe" | "enforce";
@@ -287,6 +287,11 @@ export type JevReview =
287
287
  origin?: { packId: string; packVersion?: string };
288
288
  /** Semantic policies whose questions were in the request that was answered. */
289
289
  asked: readonly string[];
290
+ /**
291
+ * What each asked check concluded (`jevChecks` on the activity row).
292
+ * Recorded only; nothing here decides from it. Absent: not recorded.
293
+ */
294
+ checks?: readonly JevCheckRecord[];
290
295
  /**
291
296
  * Of `asked`, the ones whose answer lets a reviewable regex verdict be
292
297
  * cleared: `none`, `overridden`, or a deny the human's task softened to
@@ -330,6 +335,14 @@ export type JevReview =
330
335
  * / `request-cut`.
331
336
  */
332
337
  requestCut: boolean;
338
+ /**
339
+ * What the checks of an `observe` FailproofAI Cloud assignment would have
340
+ * done. Recorded on the row's `observed` list, never applied: they are in
341
+ * neither `asked` nor `notDenied`, so they clear nothing either.
342
+ */
343
+ observedCloud?: ReadonlyArray<ObservedCloudVerdict>;
344
+ /** The `observe` Cloud assignments' checks that were asked (names); never in `asked`. */
345
+ observedAsked?: readonly string[];
333
346
  /**
334
347
  * Null when nothing had to be sent (no semantic policy applied), or when
335
348
  * the answer came from the throttle's cache: a hit's ~0 ms is not a
@@ -358,6 +371,8 @@ export interface TwoTierReview {
358
371
  * network instead of holding every hook on the machine behind it.
359
372
  */
360
373
  releaseRegistry?: () => void;
374
+ /** The review goes to FailproofAI Cloud (its Jev mode asked), not a local `jev.json`. */
375
+ cloud?: boolean;
361
376
  /** Effective authority + the reviewing semantic policies (empty unless reviewable). */
362
377
  authorityOf(p: { name: string; authority?: PolicyAuthority; reviewedBy?: string[] }): {
363
378
  authority: PolicyAuthority;
@@ -380,6 +395,7 @@ export interface JevActivityFields {
380
395
  jevLatencyMs?: number;
381
396
  jevModel?: string;
382
397
  jevMode: JevMode;
398
+ jevChecks?: JevCheckRecord[];
383
399
  }
384
400
 
385
401
  /**
@@ -389,6 +405,14 @@ export interface JevActivityFields {
389
405
  * and pack policies already use, so FailproofAI Cloud's policy page counts it
390
406
  * with no change on its side.
391
407
  */
408
+ /** One observe Cloud assignment's would-have (see `JevReview.observedCloud`). */
409
+ export interface ObservedCloudVerdict {
410
+ cloudPolicyId: string;
411
+ cloudVersion: number;
412
+ decision: "deny" | "instruct";
413
+ reason: string;
414
+ }
415
+
392
416
  export interface ObserveVerdict {
393
417
  /** `semantic/<check>` — the name enforce mode would have attributed it to. */
394
418
  policyName: string;
@@ -518,6 +542,7 @@ export function combineTwoTier(
518
542
  ...(review.latencyMs !== null ? { jevLatencyMs: review.latencyMs } : {}),
519
543
  ...(review.model ? { jevModel: review.model } : {}),
520
544
  jevMode: mode,
545
+ ...(review.checks ? { jevChecks: [...review.checks] } : {}),
521
546
  };
522
547
 
523
548
  if (mode === "observe") {
@@ -469,7 +469,7 @@ async function postJson(url: string, bearer: string, body: unknown, signal: Abor
469
469
  body: JSON.stringify(body),
470
470
  signal,
471
471
  // Never followed. The configured URL is the one `validateBaseUrl` checked
472
- // (https, or loopback http in observe mode only); a redirect would hand the
472
+ // (https, or loopback http); a redirect would hand the
473
473
  // answer — the thing that can clear a deny — to an origin nobody checked,
474
474
  // plain http included. No provider redirects this POST.
475
475
  redirect: "manual",
@@ -536,6 +536,14 @@ export interface NativeTransportOptions {
536
536
  allowUnreported?: boolean;
537
537
  /** The base URL came from a connection, not from the person (FailproofAI Cloud). */
538
538
  derivedBase?: boolean;
539
+ /**
540
+ * FailproofAI Cloud: the reply's `cloud` block (its verdict for the Cloud
541
+ * checks deployed to this machine, CONTRACT C10.4) is carried through as
542
+ * `JevResponse.cloud`, unvalidated — `parseCloudReply` in `cloud-jev.ts`
543
+ * validates it field by field. A reply that carries one may come without
544
+ * `answers` (Cloud's short-circuit asked nothing), which reads as none.
545
+ */
546
+ cloudReply?: boolean;
539
547
  }
540
548
 
541
549
  /**
@@ -545,7 +553,12 @@ export interface NativeTransportOptions {
545
553
  */
546
554
  function normalizeNative(body: unknown, sentModel: string, opts: NativeTransportOptions): JevResponse {
547
555
  if (!body || typeof body !== "object" || Array.isArray(body)) throw new JevError("malformed", "Jev returned no object");
548
- const b = body as { model?: unknown; answers?: unknown; usage?: unknown; error?: unknown };
556
+ const b = body as { model?: unknown; answers?: unknown; usage?: unknown; error?: unknown; cloud?: unknown };
557
+ const cloud = opts.cloudReply && b.cloud !== undefined ? { cloud: b.cloud } : {};
558
+ if ((typeof b.answers !== "object" || b.answers === null) && "cloud" in cloud && b.error === undefined) {
559
+ // Cloud's short-circuit: nothing was asked, so there is nothing to answer.
560
+ return { model: typeof b.model === "string" ? scrubSecret(b.model, opts.apiKey) : "", answers: {}, ...cloud };
561
+ }
549
562
  if (typeof b.answers !== "object" || b.answers === null) {
550
563
  // Gateways sometimes report an upstream failure inside a 200.
551
564
  if (b.error !== undefined) {
@@ -576,6 +589,7 @@ function normalizeNative(body: unknown, sentModel: string, opts: NativeTransport
576
589
  answers: b.answers as JevResponse["answers"],
577
590
  ...(usage ? { usage } : {}),
578
591
  ...(unverified ? { modelUnverified: true } : {}),
592
+ ...cloud,
579
593
  };
580
594
  }
581
595
 
@@ -1146,6 +1160,9 @@ export function transportForConfig(input: JevConfig): { transport: JevTransport;
1146
1160
  // `readAnswers` then holds the reported id to the 1.13 family.
1147
1161
  allowUnreported: false,
1148
1162
  derivedBase: true,
1163
+ // Cloud's verdict for its own checks, when the request asked for
1164
+ // it (a `cloud` block, CONTRACT C10.3).
1165
+ cloudReply: true,
1149
1166
  }),
1150
1167
  ),
1151
1168
  via: "failproofai",
@@ -89,8 +89,8 @@
89
89
  */
90
90
  import { closeSync, constants as fsConstants, fstatSync, openSync, readSync, statSync } from "node:fs";
91
91
  import { dirname } from "node:path";
92
- import { readJevCloudCredential, type JevCloudCredential } from "../fp-config";
93
- import { jevConfigFile } from "../fp-home";
92
+ import { readConfig, readCredentials, readJevCloudCredential, type JevCloudCredential } from "../fp-config";
93
+ import { configFile, credentialsFile, jevConfigFile } from "../fp-home";
94
94
 
95
95
  export type { JevCloudCredential } from "../fp-config";
96
96
 
@@ -179,6 +179,15 @@ export const JEV_API_KEY_ENV = "FAILPROOFAI_JEV_API_KEY";
179
179
  * the evaluator; the two are pinned equal by a test.
180
180
  */
181
181
  export const JEV_CONFIG_DEFAULT_TIMEOUT_MS = 3_000;
182
+ /**
183
+ * How long one call waits for FailproofAI Cloud when Cloud's Jev mode asks
184
+ * (CONTRACT C10.2). Longer than the local default above: the call makes one
185
+ * more hop (Cloud selects this machine's checks, forwards to Jev, and decides
186
+ * them) before it answers. A local BYOK `jev.json` keeps its own `timeoutMs`
187
+ * or the default; this one is fixed, because under a Cloud mode `jev.json` is
188
+ * not read at all. Past it, the regex decides alone, as for any fallback.
189
+ */
190
+ export const CLOUD_JEV_TIMEOUT_MS = 5_000;
182
191
  /** Bounds on `timeoutMs`. Every millisecond of it can be added to a tool call. */
183
192
  export const MIN_JEV_TIMEOUT_MS = 100;
184
193
  export const MAX_JEV_TIMEOUT_MS = 10_000;
@@ -318,8 +327,7 @@ function credentialQueryProblem(url: URL): string | null {
318
327
  * An endpoint base URL: https, or http to a loopback host only (a local proxy).
319
328
  * No credentials in the URL — not as userinfo, not as a query parameter — and no
320
329
  * fragment, because a key belongs in the key field, where it is sent as a bearer
321
- * and never printed. `validateJevConfig` further accepts the loopback http form
322
- * only in observe mode.
330
+ * and never printed. Loopback http is accepted in every mode.
323
331
  */
324
332
  export function validateBaseUrl(raw: unknown): ValidationResult<string> {
325
333
  if (typeof raw !== "string" || raw.trim() === "") return { ok: false, problem: "baseUrl must be a non-empty string" };
@@ -474,14 +482,6 @@ export function providerHostConflict(provider: string, url: string): string | nu
474
482
  : null;
475
483
  }
476
484
 
477
- function isPlainHttp(url: string): boolean {
478
- try {
479
- return new URL(url).protocol === "http:";
480
- } catch {
481
- return false;
482
- }
483
- }
484
-
485
485
  /**
486
486
  * The origin a bearer token would be sent to: https, or http to a loopback host
487
487
  * — the same schemes `validateBaseUrl` admits. Null for anything else, so a
@@ -618,7 +618,15 @@ export function validateJevConfig(
618
618
  };
619
619
  }
620
620
 
621
- const cfg: JevConfig = { provider: kind, apiKey, mode: DEFAULT_JEV_MODE, timeoutMs: JEV_CONFIG_DEFAULT_TIMEOUT_MS };
621
+ // The FailproofAI Cloud provider waits as long as a Cloud Jev mode does (B9):
622
+ // the same extra hop, and `jev setup --provider failproofai` printing 3000 ms
623
+ // misstated what FailproofAI Cloud is given.
624
+ const cfg: JevConfig = {
625
+ provider: kind,
626
+ apiKey,
627
+ mode: DEFAULT_JEV_MODE,
628
+ timeoutMs: kind === JEV_CLOUD_PROVIDER ? CLOUD_JEV_TIMEOUT_MS : JEV_CONFIG_DEFAULT_TIMEOUT_MS,
629
+ };
622
630
 
623
631
  if (o.baseUrl !== undefined) {
624
632
  const r = validateBaseUrl(o.baseUrl);
@@ -680,23 +688,11 @@ export function validateJevConfig(
680
688
  cfg.mode = mode;
681
689
  }
682
690
 
683
- // Plain http reaches only a loopback host (`validateBaseUrl`), and nothing
684
- // authenticates the server there: while the local proxy is down, any process
685
- // of this user — the agent being judged included — can bind its port and
686
- // answer "none" to every question. In enforce mode that answer clears
687
- // reviewable denies; in observe mode it changes nothing, and `off` sends
688
- // nothing at all, so enforce is the one mode it is refused in.
689
- if (cfg.baseUrl !== undefined && isPlainHttp(cfg.baseUrl) && cfg.mode === "enforce") {
690
- return {
691
- ok: false,
692
- problem:
693
- "plain http (to localhost) is accepted only with mode observe: in enforce mode Jev's answers can clear a deny, " +
694
- "and while the local proxy is down any process on this machine could take its port and answer. " +
695
- (cfg.provider === JEV_CLOUD_PROVIDER
696
- ? "Reconnect to an https FailproofAI Cloud URL (failproofai config --token <key> --url https://…), or keep mode observe"
697
- : "Use https, or mode observe"),
698
- };
699
- }
691
+ // Plain http reaches only a loopback host (`validateBaseUrl`) and is
692
+ // accepted in every mode, enforce included, so a local stack can be tested
693
+ // as it runs in production. Risk: nothing authenticates a loopback server,
694
+ // so while it is down any process of this user could bind its port and
695
+ // answer; use https wherever that matters.
700
696
 
701
697
  return { ok: true, value: cfg };
702
698
  }
@@ -1090,6 +1086,159 @@ export function loadJevConfig(): JevConfig | null {
1090
1086
  }
1091
1087
  }
1092
1088
 
1089
+ // ── FailproofAI Cloud sets the mode ──────────────────────────────────────────
1090
+
1091
+ export interface CloudModeJevConfig {
1092
+ /**
1093
+ * The config to run with: provider `failproofai` on the Cloud Jev
1094
+ * credential, with Cloud's mode — or null: Jev does not run.
1095
+ */
1096
+ config: JevConfig | null;
1097
+ /**
1098
+ * This machine's FailproofAI Cloud machine id (`credentials.json`
1099
+ * `cloud.machine_id`), sent in the request's `cloud` block so Cloud can find
1100
+ * the checks deployed to this machine. Null when there is none.
1101
+ */
1102
+ machineId: string | null;
1103
+ /**
1104
+ * Why Jev cannot run in Cloud's mode, for `errors.json` — null when it runs,
1105
+ * and null for `off`, which is not a problem:
1106
+ * - `transcripts_disabled`: connected for decisions only (`--no-transcripts`);
1107
+ * - `jev_unconfigured`: no Cloud Jev credential at all;
1108
+ * - `jev_unconfigured: <detail>`: one exists and cannot be used.
1109
+ */
1110
+ problem: string | null;
1111
+ }
1112
+
1113
+ /** The contract's message for "Cloud set a Jev mode and this machine has no Cloud Jev credential". */
1114
+ export const JEV_UNCONFIGURED_PROBLEM = "jev_unconfigured";
1115
+ /** The contract's message for "Cloud set a Jev mode on a decisions-only connection" (CONTRACT C10.2). */
1116
+ export const TRANSCRIPTS_DISABLED_PROBLEM = "transcripts_disabled";
1117
+
1118
+ /**
1119
+ * The config to run when FailproofAI Cloud sets this machine's Jev mode
1120
+ * (`active.json` `jevMode`) to `observe` or `enforce` (CONTRACT C10.2).
1121
+ *
1122
+ * The provider is ALWAYS FailproofAI Cloud, on the Cloud Jev credential
1123
+ * (`credentials.json` `jev {url,key}`, only while its connection is still on
1124
+ * this machine), at `<cloud base>/enforcement/v1/jev` — the checks Cloud
1125
+ * deployed to this machine live there, and the verdict for them is computed
1126
+ * there. A local `jev.json` is NOT used while Cloud sets a mode, BYOK provider
1127
+ * or not: Cloud wins, a local `off` included.
1128
+ *
1129
+ * Jev does not run, and `problem` says why, when:
1130
+ * 1. the machine was connected for decisions only (`collector.sessions` is not
1131
+ * `true` in `config.json` — what `--no-transcripts` writes): the standing
1132
+ * rule is that such a connection never switches Jev on, because a Jev call
1133
+ * sends the tool call and the recent prompt;
1134
+ * 2. there is no Cloud Jev credential, or it cannot be used;
1135
+ * 3. there is no FailproofAI Cloud machine id to name.
1136
+ *
1137
+ * Asked twice per gate event on a machine whose Cloud mode asks — once for the
1138
+ * review, once for `errors.json` — and on every event in the warm worker, so
1139
+ * the answer is kept until an input changes (see `jevInputsVersion`).
1140
+ *
1141
+ * Never throws.
1142
+ */
1143
+ export function loadJevConfigForCloudMode(mode: JevConfigMode): CloudModeJevConfig {
1144
+ if (mode === "off") return { config: null, machineId: null, problem: null };
1145
+ const key = `${mode}\u0000${jevInputsVersion()}`;
1146
+ if (cloudModeMemo?.key === key) return cloudModeMemo.value;
1147
+ const value = computeCloudModeConfig(mode);
1148
+ cloudModeMemo = { key, value };
1149
+ return value;
1150
+ }
1151
+
1152
+ let cloudModeMemo: { key: string; value: CloudModeJevConfig } | null = null;
1153
+
1154
+ /** Forget the memoised answer. Tests only. */
1155
+ export function _resetJevConfigMemoForTest(): void {
1156
+ cloudModeMemo = null;
1157
+ }
1158
+
1159
+ /**
1160
+ * Everything a Cloud-mode config load reads, as one version string: the
1161
+ * credentials file and its directory (whose modes the loader checks), and
1162
+ * `config.json` (the decisions-only switch), plus where those paths point. A
1163
+ * file's version is its device, inode, mode, size and modification and change
1164
+ * times to the nanosecond — an edit, a chmod and a replace-by-rename all move
1165
+ * it, and the change time cannot be set back by the user.
1166
+ */
1167
+ function jevInputsVersion(): string {
1168
+ const credentials = credentialsFile();
1169
+ const config = configFile();
1170
+ return [
1171
+ process.platform,
1172
+ credentials,
1173
+ statVersion(credentials),
1174
+ statVersion(dirname(credentials)),
1175
+ config,
1176
+ statVersion(config),
1177
+ ].join("\u0000");
1178
+ }
1179
+
1180
+ function statVersion(path: string): string {
1181
+ try {
1182
+ const st = statSync(path, { bigint: true });
1183
+ return `${st.dev}:${st.ino}:${st.mode}:${st.size}:${st.mtimeNs}:${st.ctimeNs}`;
1184
+ } catch (err) {
1185
+ return `-${(err as NodeJS.ErrnoException).code ?? "error"}`;
1186
+ }
1187
+ }
1188
+
1189
+ function computeCloudModeConfig(mode: JevConfigMode): CloudModeJevConfig {
1190
+ try {
1191
+ // Decisions only: checked first, because no credential would change it.
1192
+ if (readConfig().collector.sessions !== true) {
1193
+ return { config: null, machineId: null, problem: TRANSCRIPTS_DISABLED_PROBLEM };
1194
+ }
1195
+ const credential = readJevCloudCredential();
1196
+ if (credential.status === "absent") return { config: null, machineId: null, problem: JEV_UNCONFIGURED_PROBLEM };
1197
+ if (credential.status === "refused") {
1198
+ return {
1199
+ config: null,
1200
+ machineId: null,
1201
+ problem: `${JEV_UNCONFIGURED_PROBLEM}: the FailproofAI Cloud Jev credential is refused — ${credential.problem}`,
1202
+ };
1203
+ }
1204
+ const machineId = readCredentials().cloud?.machineId || null;
1205
+ if (!machineId) {
1206
+ return {
1207
+ config: null,
1208
+ machineId: null,
1209
+ problem: `${JEV_UNCONFIGURED_PROBLEM}: this machine has no FailproofAI Cloud machine id (no policy connection in credentials.json)`,
1210
+ };
1211
+ }
1212
+ const raw = {
1213
+ provider: JEV_CLOUD_PROVIDER,
1214
+ baseUrl: jevCloudBaseUrl(cloudBaseFor(credential.credential)),
1215
+ mode,
1216
+ timeoutMs: CLOUD_JEV_TIMEOUT_MS,
1217
+ };
1218
+ const r = validateJevConfig(raw, null, credential.credential);
1219
+ return r.ok
1220
+ ? { config: r.value, machineId, problem: null }
1221
+ : { config: null, machineId, problem: `${JEV_UNCONFIGURED_PROBLEM}: the FailproofAI Cloud Jev credential — ${r.problem}` };
1222
+ } catch (err) {
1223
+ return { config: null, machineId: null, problem: `${JEV_UNCONFIGURED_PROBLEM}: ${err instanceof Error ? err.message : String(err)}` };
1224
+ }
1225
+ }
1226
+
1227
+ /**
1228
+ * The Cloud base URL the Jev route hangs off: the connection's own URL when it
1229
+ * is on the credential's origin (so a Cloud served under a path prefix keeps
1230
+ * it, as `config --token` writes it), else the credential's origin.
1231
+ */
1232
+ function cloudBaseFor(credential: JevCloudCredential): string {
1233
+ try {
1234
+ const cloud = readCredentials().cloud;
1235
+ if (cloud?.url && new URL(cloud.url).origin === new URL(credential.url).origin) return cloud.url;
1236
+ } catch {
1237
+ // Fall through to the origin.
1238
+ }
1239
+ return credential.url;
1240
+ }
1241
+
1093
1242
  export interface JevConfigFileForUpdate {
1094
1243
  raw: Record<string, unknown>;
1095
1244
  /** The file's permission bits (null where they mean nothing). */