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
@@ -55,7 +55,6 @@ import {
55
55
  PERMISSION_POLICIES,
56
56
  } from "./cloud-introspect";
57
57
  import type { CloudJevConfigWrite } from "./jev-cloud-connection";
58
- import { jevChecksDeclared } from "./effective-reviewers";
59
58
  import {
60
59
  ingestPath,
61
60
  validateIngestKey,
@@ -63,7 +62,7 @@ import {
63
62
  type CollectorSettings,
64
63
  } from "./collector-config";
65
64
  import { recoverCredentialRefusals } from "./credential-recovery";
66
- import { clearActiveCloudManagedPolicies } from "./cloud-managed-policies";
65
+ import { clearActiveCloudManagedPolicies, readCloudJevState } from "./cloud-managed-policies";
67
66
  import { rmSync } from "node:fs";
68
67
  import { backfillRequestPath } from "./backfill-request";
69
68
 
@@ -141,6 +140,10 @@ export interface ConnectInput {
141
140
  */
142
141
  sessions?: boolean;
143
142
  environment?: string;
143
+ /** The org the key is already known to report into — a browser login's
144
+ * claim names it. Used only when introspect gives no answer (unreachable
145
+ * just then, or an older server), so the saved org is not left stale. */
146
+ org?: { id?: string; slug?: string; name?: string };
144
147
  /** Injected by tests so nothing reaches the network. */
145
148
  verifyPolicy?: typeof verifyCloudCredentials;
146
149
  verifyIngest?: typeof validateIngestKey;
@@ -162,7 +165,8 @@ export interface ConnectOutcome {
162
165
  * server has no introspect endpoint to say whether the key carries it.
163
166
  */
164
167
  jev?: JevConnectOutcome;
165
- /** Which organisation the key belongs to. Absent on a pre-introspect server. */
168
+ /** Which organisation the key belongs to. Absent when neither introspect nor
169
+ * the caller (`ConnectInput.org`) named one. */
166
170
  org?: { id?: string; slug?: string; name?: string };
167
171
  /** True when at least one capability was configured. */
168
172
  anyConfigured: boolean;
@@ -180,11 +184,18 @@ export interface JevConnectOutcome {
180
184
  * is stored; `jev setup --provider failproofai` switches Jev on.
181
185
  */
182
186
  optIn?: true;
187
+ /**
188
+ * A `--no-transcripts` connection removed this `jev.json`: it named the
189
+ * FailproofAI Cloud provider and was not switched off (the rule `config
190
+ * --disconnect` uses), so Jev through FailproofAI Cloud stays off here.
191
+ * Set with `optIn`.
192
+ */
193
+ removedCloudConfig?: string;
183
194
  /**
184
195
  * A `--no-transcripts` connection found a `jev.json` already in place that
185
- * keeps Jev running through FailproofAI Cloud, in this mode. It is left
186
- * alone like any existing file (never overwritten), but a connection that
187
- * asked for decisions only is told that Jev still sends more than that.
196
+ * keeps Jev running through FailproofAI Cloud, in this mode, and could not
197
+ * remove it (see `removedCloudConfig`). A connection that asked for
198
+ * decisions only is told that Jev still sends more than that.
188
199
  */
189
200
  stillOn?: "observe" | "enforce";
190
201
  /**
@@ -264,6 +275,7 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
264
275
  // CLI ships independently of whatever server a customer runs, so a good key
265
276
  // on an older deployment must still connect.
266
277
  const known = identity.kind === "ok" ? identity.identity : null;
278
+ const org = known ? { id: known.orgId, slug: known.orgSlug, name: known.orgName } : input.org;
267
279
 
268
280
  // Skip a probe the key provably cannot pass. This is not an optimisation: a
269
281
  // permission failure surfaces from `/events` as a 403 whose message reads
@@ -294,9 +306,7 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
294
306
  : { ok: false, reason: policyResult.reason },
295
307
  ingest: ingestResult.ok ? { ok: true } : { ok: false, reason: ingestResult.reason },
296
308
  anyConfigured: false,
297
- org: known
298
- ? { id: known.orgId, slug: known.orgSlug, name: known.orgName }
299
- : undefined,
309
+ org,
300
310
  };
301
311
 
302
312
  outcome.anyConfigured = policyResult.ok || ingestResult.ok;
@@ -316,9 +326,14 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
316
326
  prev.ingest ? { token: prev.ingest.key, url: prev.ingest.url } : undefined,
317
327
  prev.jev ? { token: prev.jev.key, url: prev.jev.url } : undefined,
318
328
  ].some((slot) => slot && (slot.token !== input.token || !sameOrigin(slot.url)));
319
- const orgSwitch = hadConnection && (
320
- (prev.org?.id && known?.orgId && prev.org.id !== known.orgId) ||
321
- ((!prev.org?.id || !known?.orgId) && keyChanged) ||
329
+ // Both sides name their organisation: that alone decides. The same org on
330
+ // a new URL (http → https, a new host for the same Cloud) is a reconnect,
331
+ // not a switch — clearing its deployment would drop enforcement until the
332
+ // next poll, and "Switched from X to X" says nothing true.
333
+ const sameKnownOrg = Boolean(prev.org?.id && org?.id && prev.org.id === org.id);
334
+ const orgSwitch = hadConnection && !sameKnownOrg && (
335
+ (prev.org?.id && org?.id && prev.org.id !== org.id) ||
336
+ ((!prev.org?.id || !org?.id) && keyChanged) ||
322
337
  (previousPolicy && !sameOrigin(previousPolicy.url)) ||
323
338
  (prev.ingest && !sameOrigin(prev.ingest.url))
324
339
  );
@@ -329,7 +344,7 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
329
344
  if (orgSwitch) {
330
345
  rmSync(backfillRequestPath(), { force: true });
331
346
  const oldOrg = prev.org?.name ?? prev.org?.slug ?? prev.org?.id ?? "the previous org";
332
- const newOrg = known?.orgName ?? known?.orgSlug ?? known?.orgId ?? "the new org";
347
+ const newOrg = org?.name ?? org?.slug ?? org?.id ?? "the new org";
333
348
  changes.push(`Switched from ${oldOrg} to ${newOrg}. Nothing recorded before now will be sent to ${newOrg}; ${oldOrg}'s cloud policies stop within ~30 s.`);
334
349
  // Remove local references, not the daemon's policy-floor state (#899).
335
350
  clearActiveCloudManagedPolicies();
@@ -362,8 +377,8 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
362
377
  if (prev.jev) changes.push(`Removed the previous Jev key ${maskToken(prev.jev.key)}: this key's ${PERMISSION_JEV} permission was not verified.`);
363
378
  }
364
379
  const since = boundary === undefined ? {} : { since: boundary };
365
- if (known) {
366
- next.org = { id: known.orgId, slug: known.orgSlug, name: known.orgName, ...since };
380
+ if (org) {
381
+ next.org = { ...org, ...since };
367
382
  } else {
368
383
  const { since: _drop, ...kept } = orgSwitch ? {} : (prev.org ?? {});
369
384
  next.org = { ...kept, ...since };
@@ -373,7 +388,7 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
373
388
  // a flush served before the daemon sees the new key retries these batches
374
389
  // with the key that was refused, re-parking them as definitive refusals.
375
390
  if (ingestResult.ok && prev.ingest && prev.ingest.key !== input.token) {
376
- recoverCredentialRefusals(Boolean(prev.org?.id && prev.org.id === known?.orgId && sameOrigin(prev.ingest.url)), prev.org?.id);
391
+ recoverCredentialRefusals(Boolean(prev.org?.id && prev.org.id === org?.id && sameOrigin(prev.ingest.url)), prev.org?.id);
377
392
  }
378
393
  // Legacy JSON override remains supported; the consolidated file above is
379
394
  // published once, never as a mixture of previous and replacement slots.
@@ -425,23 +440,37 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
425
440
  writeJevCloudCredential({ url: new URL(input.url).origin, key: input.token });
426
441
  // Loaded here and nowhere earlier: a connect whose key does not carry Jev
427
442
  // pulls in none of the Jev modules.
428
- const { cloudJevRunningMode, existingJevConfig, writeCloudJevConfigIfAbsent } = await import("./jev-cloud-connection");
443
+ const { cloudJevRunningMode, existingJevConfig, removeCloudJevConfig, writeCloudJevConfig } = await import(
444
+ "./jev-cloud-connection"
445
+ );
429
446
  if (input.sessions === true) {
430
- outcome.jev = { ok: true, config: writeCloudJevConfigIfAbsent(input.url) };
447
+ outcome.jev = { ok: true, config: writeCloudJevConfig(input.url) };
431
448
  } else {
432
449
  // `--no-transcripts` never switches Jev on (the user's decision). Jev
433
450
  // sends each checked tool call and the recent prompt to FailproofAI
434
451
  // Cloud, and someone who just asked for decisions only has not asked
435
452
  // for that. The key is stored all the same, so opting in later is one
436
- // command with no key to paste; jev.json is not written. One that is
437
- // already there is somebody's decision and is reported as ever — and
438
- // when it keeps Jev on through FailproofAI Cloud, that is said too, or
439
- // "Decisions only." would be the last word while Jev keeps sending.
440
- const existing = existingJevConfig(input.url);
441
- const stillOn = existing ? cloudJevRunningMode() : null;
442
- outcome.jev = existing
443
- ? { ok: true, config: existing, ...(stillOn ? { stillOn } : {}) }
444
- : { ok: true, optIn: true };
453
+ // command with no key to paste; jev.json is not written.
454
+ //
455
+ // And a jev.json naming the FailproofAI Cloud provider — left by an
456
+ // earlier connect with transcripts on — is removed, by exactly the rule
457
+ // `config --disconnect` uses (`removeCloudJevConfig`: the Cloud's file,
458
+ // not switched off). Kept, it went on sending this machine's tool calls
459
+ // to FailproofAI Cloud with no Cloud mode set (e2e-c10 O2, user
460
+ // decision). A BYOK file is its owner's and stays, reported as ever; so
461
+ // does a Cloud file switched off, the owner's opt-out.
462
+ const removal = removeCloudJevConfig();
463
+ if (removal.status === "removed") {
464
+ outcome.jev = { ok: true, optIn: true, removedCloudConfig: removal.path };
465
+ } else {
466
+ const existing = existingJevConfig(input.url);
467
+ // Still sending (the removal failed): said, or "Decisions only."
468
+ // would be the last word while Jev keeps sending.
469
+ const stillOn = existing ? cloudJevRunningMode() : null;
470
+ outcome.jev = existing
471
+ ? { ok: true, config: existing, ...(stillOn ? { stillOn } : {}) }
472
+ : { ok: true, optIn: true };
473
+ }
445
474
  }
446
475
  } else if (known) {
447
476
  // Introspect ANSWERED, and the key does not carry Jev. This connection
@@ -491,6 +520,14 @@ export async function connectToCloud(input: ConnectInput): Promise<ConnectOutcom
491
520
  * this machine now, and — the one case worth a second line — why an existing
492
521
  * `jev.json` was not touched.
493
522
  */
523
+ function safeCloudJev(): ReturnType<typeof readCloudJevState> {
524
+ try {
525
+ return readCloudJevState();
526
+ } catch {
527
+ return { jevMode: null, deployment: null, errors: [] };
528
+ }
529
+ }
530
+
494
531
  export function jevLines(outcome: ConnectOutcome): string[] {
495
532
  const jev = outcome.jev;
496
533
  if (!jev) return [];
@@ -503,6 +540,11 @@ export function jevLines(outcome: ConnectOutcome): string[] {
503
540
  if (!jev.ok) {
504
541
  return [` Jev not through FailproofAI Cloud: ${jev.reason ?? `that key does not carry \`${PERMISSION_JEV}\``}.`];
505
542
  }
543
+ if (jev.optIn && jev.removedCloudConfig) {
544
+ return [
545
+ ` Jev removed ${jev.removedCloudConfig}, which sent each checked tool call and the recent prompt to FailproofAI Cloud: Jev via FailproofAI Cloud stays off on this machine (--no-transcripts). To switch it on: \`failproofai jev setup --provider failproofai\`.`,
546
+ ];
547
+ }
506
548
  if (jev.optIn) {
507
549
  // One line, and nothing that reads as "on": this connection asked for
508
550
  // decisions only, and Jev would send more than that.
@@ -511,28 +553,32 @@ export function jevLines(outcome: ConnectOutcome): string[] {
511
553
  ];
512
554
  }
513
555
  const config = jev.config;
514
- if (!config || config.status === "written") {
515
- // Plain http (loopback only) is accepted in observe mode and nowhere else, so
516
- // the enforce command would be refused there, by the CLI and the dashboard.
517
- const plainHttp = config?.status === "written" && new URL(config.baseUrl).protocol === "http:";
518
- return [
519
- ` Jev on through FailproofAI Cloud, in observe mode: logged, not enforced (${config?.path ?? "jev.json"}).`,
520
- // The package ships no Jev checks, so "on" asks nothing until a pack
521
- // gives it some — said here, where the user just turned it on.
522
- ...(jevChecksDeclared()
523
- ? []
524
- : [" It has no checks installed yet, so it asks nothing. To enable it: `failproofai policies add FailproofAI/jev-policies`."]),
525
- plainHttp
526
- ? " Enforce needs an https FailproofAI Cloud URL: reconnect with `failproofai config --token <key> --url https://…`. Over plain http Jev stays in observe mode."
527
- : " Enforce it with `failproofai jev setup --mode enforce`, or from the dashboard.",
528
- ];
556
+ if (!config || config.status === "written" || config.status === "repointed") {
557
+ // What happened to jev.json, said first on a re-point; the mode lines
558
+ // below are the same for both (N1).
559
+ const lead =
560
+ config?.status === "repointed" ? [` Jev ${config.path} now points here instead of ${config.from}.`] : [];
561
+ const indent = lead.length > 0 ? " " : " Jev ";
562
+ // FailproofAI Cloud already set this machine's Jev mode (a reconnect, or a
563
+ // deployment that arrived first): that mode and Cloud's own checks decide,
564
+ // not the local jev.json or the packs installed here.
565
+ const cloudJev = safeCloudJev();
566
+ // One line: FailproofAI Cloud decides the mode, whatever jev.json says, so
567
+ // the local fallback and the packs installed here are not worth a line.
568
+ const state =
569
+ cloudJev.jevMode === null
570
+ ? "waiting for its first deployment"
571
+ : cloudJev.jevMode === "off"
572
+ ? "off — no Jev policy deployed"
573
+ : cloudJev.jevMode;
574
+ return [...lead, `${indent}managed by FailproofAI Cloud · ${state}`];
529
575
  }
530
576
  if (config.status === "kept") {
531
577
  const lines = [` Jev key stored; ${config.path} already exists and was left as configured.`];
532
578
  if (config.otherOrigin) {
533
579
  lines.push(
534
580
  config.jevOff?.why === "off"
535
- ? ` It points at ${config.otherOrigin} and has Jev switched off (mode off), so Jev is off. To point it here and turn it on: \`failproofai jev setup --provider failproofai --mode observe\`.`
581
+ ? ` It points at ${config.otherOrigin} and has Jev switched off (mode off), so Jev is off. To point it here: \`failproofai jev setup --provider failproofai\`.`
536
582
  : ` It points at ${config.otherOrigin}, so Jev stays off until you run \`failproofai jev setup --provider failproofai\`.`,
537
583
  );
538
584
  } else if (config.jevOff?.why === "refused") {
@@ -540,11 +586,11 @@ export function jevLines(outcome: ConnectOutcome): string[] {
540
586
  ` It was refused (${config.jevOff.problem}), so Jev is off. ${config.jevOff.fix ? `Fix: \`${config.jevOff.fix}\`.` : "Write a valid one: `failproofai jev setup --provider failproofai`."}`,
541
587
  );
542
588
  } else if (config.jevOff?.why === "off") {
543
- lines.push(" It has Jev switched off (mode off), so Jev is off. To turn it on: `failproofai jev setup --mode observe`.");
589
+ lines.push(" It has Jev switched off (mode off) until FailproofAI Cloud's Jev mode arrives with this machine's first deployment; from then on FailproofAI Cloud decides.");
544
590
  }
545
591
  if (jev.stillOn) {
546
592
  lines.push(
547
- ` Jev is still on through FailproofAI Cloud (${jev.stillOn} mode): it sends each checked tool call and the recent prompt to FailproofAI Cloud. To switch it off: \`failproofai jev setup --mode off\`.`,
593
+ ` Jev is still on through FailproofAI Cloud (${jev.stillOn} mode): it sends each checked tool call and the recent prompt to FailproofAI Cloud. FailproofAI Cloud sets its mode: off when no Jev policy is deployed here.`,
548
594
  );
549
595
  }
550
596
  return lines;
@@ -409,12 +409,13 @@ export function runDisconnectCommand(): CommandResult {
409
409
  // machine that had deliberately left its organisation went on being governed
410
410
  // by whatever deployment was current when it left, indefinitely, while
411
411
  // `--status` reported it as unconnected.
412
- const stoppedManaged = clearActiveCloudManagedPolicies();
413
412
  // Back to OSS. Leaving mode = "cloud" with no credentials would describe a
414
413
  // machine that does not exist, and every cloud code path keys off this flag
415
414
  // rather than off "is a token lying around" precisely so that a disconnected
416
415
  // machine is provably silent instead of silent-by-happenstance.
417
416
  updateConfig({ mode: "oss" });
417
+ const sharedPolicyRoot = process.env.FAILPROOFAI_CLOUD_POLICY_DIR !== undefined;
418
+ const stoppedManaged = clearActiveCloudManagedPolicies();
418
419
 
419
420
  const jevLines =
420
421
  jevConfig.status === "removed"
@@ -450,10 +451,12 @@ export function runDisconnectCommand(): CommandResult {
450
451
  const lines = [
451
452
  "Disconnected from FailproofAI Cloud.",
452
453
  "",
453
- stoppedManaged
454
- ? " Cloud-managed policies stop being enforced and stop being refreshed.\n" +
454
+ stoppedManaged || (sharedPolicyRoot && !!existing)
455
+ ? " Cloud-managed policies stop being enforced and stop being refreshed, and any Jev mode\n" +
456
+ " FailproofAI Cloud set goes with them (its Jev checks never ran on this machine).\n" +
455
457
  " Local builtin, custom and convention policies are unaffected."
456
458
  : " Local builtin, custom and convention policies are unaffected.",
459
+ ...(sharedPolicyRoot ? [" An overridden Cloud policy directory was left untouched; OSS mode ignores its old deployment."] : []),
457
460
  ...jevLines,
458
461
  ];
459
462
  if (removedIngest) {
@@ -589,4 +592,4 @@ export function connectionStatusLines(daemonStatus = daemonServiceStatus): strin
589
592
  kitRows(report.rows, opts),
590
593
  report.warnings.length > 0 ? warning(report.warnings, opts) : null,
591
594
  );
592
- }
595
+ }
@@ -162,9 +162,12 @@ export async function startLogin(
162
162
  fetchImpl,
163
163
  );
164
164
  if ("error" in r) return { kind: "unreachable", reason: r.error };
165
- // No such route, or the web app redirecting an unknown path: a cloud from
166
- // before this feature. Not an error — the key prompt still connects it.
167
- if (r.status === 404 || (r.status >= 300 && r.status < 400)) return { kind: "unsupported" };
165
+ // No such route, the web app redirecting an unknown path, or serving a page
166
+ // (200, not JSON) in its place: a cloud from before this feature. Not an
167
+ // error — the key prompt still connects it.
168
+ if (r.status === 404 || (r.status >= 300 && r.status < 400) || (r.status === 200 && !r.json)) {
169
+ return { kind: "unsupported" };
170
+ }
168
171
  if (r.status === 429) return { kind: "refused", reason: "too many login attempts from here — wait a minute" };
169
172
  if (r.status !== 201 || !r.json) {
170
173
  return r.status >= 500