failproofai 1.0.7-beta.2 → 1.0.8-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) 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 +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  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 +15 -15
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  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 +5 -5
  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 +4 -4
  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 +15 -15
  36. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  37. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  38. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  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 +6 -7
  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 +2 -2
  52. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  55. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +18 -7
  58. package/.next/standalone/.next/server/app/settings/page.js +9 -7
  59. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/chunks/{[externals]__1j-zsg5._.js → [externals]__20kzpkf._.js} +1 -1
  62. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
  63. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +22 -0
  65. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +20 -20
  66. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  68. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +9 -9
  69. package/.next/standalone/.next/server/chunks/_1q5i8mb._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/lib_16xa545._.js +3 -0
  71. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +5 -0
  73. package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7._.js +3 -0
  74. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/[externals]__0ohnuzs._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  77. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
  78. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qxnccm._.js → [root-of-the-server]__06pflha._.js} +2 -4
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
  82. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0soxz2z._.js → [root-of-the-server]__12e7nhs._.js} +2 -2
  83. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l44ual._.js → [root-of-the-server]__14o3ek1._.js} +2 -2
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +4 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19evfi8._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +4 -0
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
  89. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/{_1v-jvrv._.js → _0o4xkpl._.js} +1 -1
  93. package/.next/standalone/.next/server/chunks/ssr/{_1mel6y1._.js → _1-7sqrb._.js} +2 -2
  94. package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  96. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +13 -13
  97. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0aiy-os._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
  105. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +12 -0
  107. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
  109. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
  113. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  114. package/.next/standalone/.next/server/pages/404.html +1 -1
  115. package/.next/standalone/.next/server/pages/500.html +1 -1
  116. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  117. package/.next/standalone/.next/server/server-reference-manifest.json +33 -22
  118. package/.next/standalone/.next/static/chunks/078gnymqoh3r4.js +1 -0
  119. package/.next/standalone/.next/static/chunks/{3brze37td_wnc.js → 0ahfwmbkpgfw4.js} +1 -1
  120. package/.next/standalone/.next/static/chunks/{043j99m8ykg__.css → 0bhidk90e-07f.css} +1 -1
  121. package/.next/standalone/.next/static/chunks/1bu2-nv59ed6i.js +1 -0
  122. package/.next/standalone/.next/static/chunks/{3ugmd_7dyn0id.js → 1v_tp3hm8wyhe.js} +1 -1
  123. package/.next/standalone/.next/static/chunks/2a405_e1o26ol.js +1 -0
  124. package/.next/standalone/.next/static/chunks/{3yxro_r2_o9ad.js → 2vo7qbdbvnc0o.js} +1 -1
  125. package/.next/standalone/.next/static/chunks/{0fqd7m_u81mi5.js → 2z7i-yg59w4pn.js} +1 -1
  126. package/.next/standalone/.next/static/chunks/36uhh9el_oz8e.js +1 -0
  127. package/.next/standalone/.next/static/chunks/{129ag2bw93bdh.js → 3bgot5v6f6s3c.js} +1 -1
  128. package/.next/standalone/.next/static/chunks/{3otmypm6j_xfo.js → 3o3f1ibfci0p7.js} +2 -2
  129. package/.next/standalone/SECURITY.md +53 -0
  130. package/.next/standalone/app/actions/get-jev-config.ts +194 -14
  131. package/.next/standalone/app/actions/update-jev-config.ts +160 -11
  132. package/.next/standalone/app/policies/hooks-client.tsx +5 -3
  133. package/.next/standalone/app/settings/jev-panel.tsx +169 -12
  134. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
  135. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
  136. package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
  137. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
  138. package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
  139. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
  140. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
  141. package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
  142. package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
  143. package/.next/standalone/package.json +9 -9
  144. package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
  145. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
  146. package/.next/standalone/sdk/typescript/CHANGELOG.md +15 -1
  147. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +10 -30
  148. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +3 -0
  149. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -16
  150. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +3 -0
  151. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +13 -132
  152. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +4 -0
  153. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -142
  154. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +4 -0
  155. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +1396 -1016
  156. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +9 -0
  157. package/.next/standalone/sdk/typescript/scripts/release.mjs +30 -0
  158. package/.next/standalone/server.js +1 -1
  159. package/README.md +1 -0
  160. package/bin/failproofai.mjs +37 -6
  161. package/dist/cli.mjs +2861 -1145
  162. package/dist/worker.mjs +815 -197
  163. package/package.json +9 -9
  164. package/src/hooks/builtin-policies.ts +24 -6
  165. package/src/hooks/cloud-connection.ts +190 -1
  166. package/src/hooks/cloud-enrollment-cli.ts +56 -7
  167. package/src/hooks/cloud-introspect.ts +6 -0
  168. package/src/hooks/configure-wizard.ts +26 -6
  169. package/src/hooks/custom-hooks-loader.ts +58 -8
  170. package/src/hooks/effective-reviewers.ts +172 -8
  171. package/src/hooks/flush-cli.ts +35 -8
  172. package/src/hooks/fp-config.ts +266 -1
  173. package/src/hooks/fp-reset.ts +22 -4
  174. package/src/hooks/handler.ts +44 -7
  175. package/src/hooks/hook-activity-store.ts +13 -5
  176. package/src/hooks/jev-cli.ts +1009 -86
  177. package/src/hooks/jev-cloud-connection.ts +392 -0
  178. package/src/hooks/manager.ts +44 -6
  179. package/src/hooks/pack-cli.ts +175 -37
  180. package/src/hooks/pack-failclosed.ts +3 -0
  181. package/src/hooks/pack-manifest.ts +11 -1
  182. package/src/hooks/pack-store.ts +47 -10
  183. package/src/hooks/policy-authority.ts +73 -20
  184. package/src/hooks/policy-catalog.ts +10 -8
  185. package/src/hooks/policy-evaluator.ts +31 -8
  186. package/src/hooks/policy-registry.ts +3 -2
  187. package/src/hooks/policy-reviewability.ts +41 -12
  188. package/src/hooks/policy-types.ts +5 -3
  189. package/src/hooks/semantic/combine.ts +124 -84
  190. package/src/hooks/semantic/decide.ts +16 -0
  191. package/src/hooks/semantic/envelope.ts +18 -59
  192. package/src/hooks/semantic/evaluator.ts +11 -2
  193. package/src/hooks/semantic/facts.ts +7 -1
  194. package/src/hooks/semantic/intent.ts +8 -8
  195. package/src/hooks/semantic/jev-client.ts +540 -28
  196. package/src/hooks/semantic/jev-config.ts +586 -38
  197. package/src/hooks/semantic/jev-review.ts +26 -11
  198. package/src/hooks/semantic/jev-throttle.ts +8 -2
  199. package/src/hooks/semantic/pack-policies.ts +58 -15
  200. package/src/hooks/semantic/policies.ts +4 -3
  201. package/src/hooks/semantic/redact.ts +28 -1
  202. package/src/hooks/semantic/session-root.ts +116 -0
  203. package/src/hooks/semantic/types.ts +11 -0
  204. package/src/hooks/tui.ts +4 -0
  205. package/src/hooks/worker-server.ts +2 -1
  206. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
  207. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bf34x4._.js +0 -22
  208. package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +0 -5
  209. package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +0 -3
  210. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +0 -4
  211. package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +0 -5
  212. package/.next/standalone/.next/server/chunks/ssr/_1w_5l7t._.js +0 -3
  213. package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +0 -5
  214. package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +0 -3
  215. package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +0 -12
  216. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +0 -3
  217. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +0 -3
  218. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
  219. package/.next/standalone/.next/static/chunks/1qd741hzlmjbo.js +0 -1
  220. package/.next/standalone/.next/static/chunks/2_pltstd8-xgs.js +0 -1
  221. package/.next/standalone/.next/static/chunks/2c8j9l6j_b1ci.js +0 -1
  222. package/.next/standalone/.next/static/chunks/3-k569wzcli8q.js +0 -1
  223. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_buildManifest.js +0 -0
  224. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_clientMiddlewareManifest.js +0 -0
  225. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "1.0.7-beta.2",
3
+ "version": "1.0.8-beta.0",
4
4
  "description": "Observability and enforcement for AI agent harnesses. 40 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
5
5
  "bin": {
6
6
  "failproofai": "./dist/cli.mjs",
@@ -122,15 +122,15 @@
122
122
  "browserslist": "4.28.8"
123
123
  },
124
124
  "optionalDependencies": {
125
- "@failproofai/failproofaid-linux-x64": "1.0.7-beta.2",
126
- "@failproofai/failproofaid-linux-arm64": "1.0.7-beta.2",
127
- "@failproofai/failproofaid-darwin-x64": "1.0.7-beta.2",
128
- "@failproofai/failproofaid-darwin-arm64": "1.0.7-beta.2"
125
+ "@failproofai/failproofaid-linux-x64": "1.0.8-beta.0",
126
+ "@failproofai/failproofaid-linux-arm64": "1.0.8-beta.0",
127
+ "@failproofai/failproofaid-darwin-x64": "1.0.8-beta.0",
128
+ "@failproofai/failproofaid-darwin-arm64": "1.0.8-beta.0"
129
129
  },
130
130
  "failproofaidBinaries": {
131
- "linux-x64": "5362ab6a4cfb23d1eee598aac67aedd9e43351e62724ee1c6b40c4fc8021c08f",
132
- "linux-arm64": "f14a2548b41e14ad32b7fdd03a27ffe7160da1046e422e1f651099371b0d27e5",
133
- "darwin-x64": "14189cade04f17cf5278bd8061a5ec0c2a2381aa21944ae0d05e8c35b98c2816",
134
- "darwin-arm64": "4ba9f5c321894d953407efa3404f0deec07dccfd7db7bc2511bb35226816b583"
131
+ "linux-x64": "35b641f60e1724513fbb760db382123dad2b5fcdd67dda6373bda80651bd36bb",
132
+ "linux-arm64": "a058288f5d08f99612f053793e5c7f59264d3da97f3dbe769e401b0f8536702d",
133
+ "darwin-x64": "b4f3dbe4aea44e77cc10839647c0dc03a1b8b797dfb27e1d89a6a96554ef27b8",
134
+ "darwin-arm64": "fd42f28bf92001d39117b488a215578b966f07d316214744aca64a1888f2aed2"
135
135
  }
136
136
  }
@@ -186,6 +186,15 @@ const PUBLISH_CMD_RE = /(?:npm\s+publish|bun\s+publish|pnpm\s+publish|yarn\s+npm
186
186
  const ENV_PRINTENV_RE = /(?:^|\s|;|&&|\|\|)(?:env|printenv)(?:\s|$|;|&&|\|)/;
187
187
  const ECHO_ENV_RE = /echo\s+.*\$\{?[A-Za-z_]/;
188
188
  const EXPORT_RE = /(?:^|\s|;|&&|\|\|)export\s+\w+/;
189
+ // The other whole-environment dumps: bare `set` / `export` / `export -p` in
190
+ // command position (`set -e`, `export FOO=bar` are not), `declare|typeset` with
191
+ // -x or -p (not `declare -a`), `compgen -v|-e`, and /proc/<pid>/environ.
192
+ const ENV_DUMP_RE =
193
+ /(?:^|[;&|({\n`'"]|\$\()\s*(?:set|export(?:\s+-p)?)\s*(?:$|[|;&>)\n])|\b(?:declare|typeset)\s+-[A-Za-z]*[xp]|\bcompgen\s+-[A-Za-z]*[ev]|\/proc\/(?:self|thread-self|\d+|\$\$|\$BASHPID)\/environ/;
194
+ // A script dumping the environment OBJECT, not one variable of it. Two tests,
195
+ // both linear: an interpreter in the command, and the object used whole.
196
+ const SCRIPT_INTERP_RE = /\b(?:python[\d.]*|node|bun|deno)\b/;
197
+ const ENV_OBJECT_RE = /\bos\.environ(?!\s*\[|\.(?:get|setdefault|pop|update)\b)|\bprocess\.env(?![.\[\w])/;
189
198
  const PS_ENV_VAR_RE = /\$env:[A-Za-z_]/i;
190
199
  const PS_CHILDITEM_ENV_RE = /(?:Get-ChildItem|dir|gci|ls)\s+Env:/i;
191
200
  const DOTNET_GETENV_RE = /\[Environment\]::GetEnvironment/i;
@@ -1765,6 +1774,9 @@ function protectEnvVars(ctx: PolicyContext): PolicyResult {
1765
1774
  if (EXPORT_RE.test(cmd)) {
1766
1775
  return deny("Command exports environment variable");
1767
1776
  }
1777
+ if (ENV_DUMP_RE.test(cmd) || (SCRIPT_INTERP_RE.test(cmd) && ENV_OBJECT_RE.test(cmd))) {
1778
+ return deny("Command dumps environment variables");
1779
+ }
1768
1780
  // PowerShell: $env:VAR
1769
1781
  if (PS_ENV_VAR_RE.test(cmd)) {
1770
1782
  return deny("Command reads environment variable via PowerShell");
@@ -2277,11 +2289,11 @@ const READ_LIKE_CMDS =
2277
2289
  * a real, working spelling of /etc/passwd — is still extracted and still
2278
2290
  * resolves to /etc/passwd.
2279
2291
  *
2280
- * Runs of slashes with nothing else ('//', '///') are dropped: those are a
2281
- * line-comment marker, not a directory. They used to resolve to the filesystem
2282
- * root, which denied any read-like command carrying a '// …' comment — a
2283
- * heredoc writing a TypeScript file into the project, say. A lone '/' is the
2284
- * root and is kept.
2292
+ * A run of slashes with nothing else ('//', '///') that opens a line after
2293
+ * the first is dropped: that is a line-comment marker in a heredoc body, not a
2294
+ * directory. It used to resolve to the filesystem root, which denied a heredoc
2295
+ * writing a TypeScript file into the project. Anywhere else a slash-run is
2296
+ * the root, as a lone '/' is: `cd // && cat etc/shadow` reads /etc/shadow.
2285
2297
  */
2286
2298
  function extractAbsolutePaths(command: string): string[] {
2287
2299
  const paths: string[] = [];
@@ -2292,7 +2304,13 @@ function extractAbsolutePaths(command: string): string[] {
2292
2304
  let m: RegExpExecArray | null;
2293
2305
  while ((m = pathRe.exec(s)) !== null) {
2294
2306
  let p = m[0];
2295
- if (/^\/{2,}$/.test(p)) continue; // a `//` comment marker, not the root
2307
+ // A `//` that opens a line after the first (a heredoc body) is a comment
2308
+ // marker. Anywhere else a slash-run is the root: `cd // && cat etc/shadow`,
2309
+ // and `cd \<newline>//`, which bash joins into one line.
2310
+ if (/^\/{2,}$/.test(p)) {
2311
+ const nl = s.lastIndexOf("\n", m.index - 1);
2312
+ if (nl !== -1 && s[nl - 1] !== "\\" && /^[ \t]*$/.test(s.slice(nl + 1, m.index))) continue;
2313
+ }
2296
2314
  if (p === "~") p = homedir();
2297
2315
  else if (p.startsWith("~/")) p = join(homedir(), p.slice(2));
2298
2316
  paths.push(p);
@@ -35,14 +35,23 @@ import {
35
35
  writeCloudCredentials,
36
36
  type CloudCredentials,
37
37
  } from "./cloud-enrollment";
38
- import { updateConfig, readCredentials, writeCredentials } from "./fp-config";
38
+ import { credentialsFile } from "./fp-home";
39
+ import {
40
+ clearJevCloudCredential,
41
+ updateConfig,
42
+ readCredentials,
43
+ writeCredentials,
44
+ writeJevCloudCredential,
45
+ } from "./fp-config";
39
46
  import {
40
47
  introspectKey,
41
48
  hasPermission,
42
49
  describeOrg,
43
50
  PERMISSION_EVENTS,
51
+ PERMISSION_JEV,
44
52
  PERMISSION_POLICIES,
45
53
  } from "./cloud-introspect";
54
+ import type { CloudJevConfigWrite } from "./jev-cloud-connection";
46
55
  import {
47
56
  ingestPath,
48
57
  validateIngestKey,
@@ -139,12 +148,50 @@ export interface CapabilityOutcome {
139
148
  export interface ConnectOutcome {
140
149
  policy: CapabilityOutcome & { policyCount?: number; deployment?: number };
141
150
  ingest: CapabilityOutcome;
151
+ /**
152
+ * Jev through FailproofAI Cloud. Absent when nothing connected, and when the
153
+ * server has no introspect endpoint to say whether the key carries it.
154
+ */
155
+ jev?: JevConnectOutcome;
142
156
  /** Which organisation the key belongs to. Absent on a pre-introspect server. */
143
157
  org?: { id?: string; slug?: string; name?: string };
144
158
  /** True when at least one capability was configured. */
145
159
  anyConfigured: boolean;
146
160
  }
147
161
 
162
+ export interface JevConnectOutcome {
163
+ /** The key carries `jev:evaluate`, and it was stored for Jev. */
164
+ ok: boolean;
165
+ reason?: string;
166
+ /** What became of `jev.json`. Present when `ok`, unless `optIn`. */
167
+ config?: CloudJevConfigWrite;
168
+ /**
169
+ * `ok`, but Jev was deliberately NOT switched on: the connection opted out
170
+ * of transcripts (`sessions !== true`), and there is no `jev.json`. The key
171
+ * is stored; `jev setup --provider failproofai` switches Jev on.
172
+ */
173
+ optIn?: true;
174
+ /**
175
+ * A `--no-transcripts` connection found a `jev.json` already in place that
176
+ * keeps Jev running through FailproofAI Cloud, in this mode. It is left
177
+ * alone like any existing file (never overwritten), but a connection that
178
+ * asked for decisions only is told that Jev still sends more than that.
179
+ */
180
+ stillOn?: "shadow" | "enforce";
181
+ /**
182
+ * Introspect gave no answer about this key's permissions — the server could
183
+ * not be asked (`unreachable`) or predates the endpoint (`unsupported`) — so
184
+ * whether it carries `jev:evaluate` is unknown, and the Jev slot was:
185
+ *
186
+ * - `kept`: it already held THIS key for THIS origin, and was left as it
187
+ * was — the machine's connection is the same one it belongs to;
188
+ * - `cleared`: it held another key or origin — the previous connection's,
189
+ * possibly another org's budget — and was removed;
190
+ * - `none`: there was no slot, and there still is none.
191
+ */
192
+ unconfirmed?: "kept" | "cleared" | "none";
193
+ }
194
+
148
195
  /**
149
196
  * Verify both capabilities, then write only what actually works.
150
197
  *
@@ -282,9 +329,146 @@ writeCloudCredentials(creds);
282
329
  });
283
330
  }
284
331
 
332
+ // ── Jev, last ─────────────────────────────────────────────────────────────
333
+ //
334
+ // Only from introspect's answer. `jev:evaluate` spends the org's budget on
335
+ // every gated tool call, so it is never inferred from a probe succeeding the
336
+ // way the other two capabilities are on a server without introspect — and a
337
+ // machine that connected to nothing gets nothing.
338
+ //
339
+ // There is no Jev-only path: the server refuses a key that carries
340
+ // `jev:evaluate` without `events:add` and `policies:pull`, so a key that gets
341
+ // here with Jev has already configured the other two.
342
+ if (outcome.anyConfigured) {
343
+ if (known && hasPermission(known, PERMISSION_JEV)) {
344
+ // The ORIGIN the key was verified against. `jev-config.ts` sends the key
345
+ // only to a base URL on this origin, so a jev.json pointing anywhere else
346
+ // cannot borrow it.
347
+ writeJevCloudCredential({ url: new URL(input.url).origin, key: input.token });
348
+ // Loaded here and nowhere earlier: a connect whose key does not carry Jev
349
+ // pulls in none of the Jev modules.
350
+ const { cloudJevRunningMode, existingJevConfig, writeCloudJevConfigIfAbsent } = await import("./jev-cloud-connection");
351
+ if (input.sessions === true) {
352
+ outcome.jev = { ok: true, config: writeCloudJevConfigIfAbsent(input.url) };
353
+ } else {
354
+ // `--no-transcripts` never switches Jev on (the user's decision). Jev
355
+ // sends each checked tool call and the recent prompt to FailproofAI
356
+ // Cloud, and someone who just asked for decisions only has not asked
357
+ // for that. The key is stored all the same, so opting in later is one
358
+ // command with no key to paste; jev.json is not written. One that is
359
+ // already there is somebody's decision and is reported as ever — and
360
+ // when it keeps Jev on through FailproofAI Cloud, that is said too, or
361
+ // "Decisions only." would be the last word while Jev keeps sending.
362
+ const existing = existingJevConfig(input.url);
363
+ const stillOn = existing ? cloudJevRunningMode() : null;
364
+ outcome.jev = existing
365
+ ? { ok: true, config: existing, ...(stillOn ? { stillOn } : {}) }
366
+ : { ok: true, optIn: true };
367
+ }
368
+ } else if (known) {
369
+ // Introspect ANSWERED, and the key does not carry Jev. This connection
370
+ // replaces the last one, and the last key's Jev slot describes a
371
+ // connection this machine no longer has — possibly another org's budget.
372
+ // Dropped, so Jev never spends on a key it was not just handed. (A Cloud
373
+ // jev.json then reads `key-lacks-jev`: off, and said.)
374
+ clearJevCloudCredential();
375
+ outcome.jev = { ok: false, reason: missing(PERMISSION_JEV) };
376
+ } else {
377
+ // No answer about this key: a 502 or a network blip on introspect
378
+ // (`unreachable`), or a server with no introspect at all (`unsupported`).
379
+ // Neither says the key LACKS Jev, so neither is a reason to switch Jev
380
+ // off — a reconnect during a transient outage used to do exactly that,
381
+ // silently.
382
+ //
383
+ // But neither says it HAS Jev, and the slot is only ever spent against.
384
+ // So it is kept only when it already holds this very key for this very
385
+ // origin: then the connection just verified IS the one it belongs to,
386
+ // and keeping it changes nothing about whose budget Jev spends. A slot
387
+ // holding any other key or origin is the previous connection's, and the
388
+ // machine is not connected with that key any more — the rule above,
389
+ // which wins whenever the answer is not "yes".
390
+ //
391
+ // `unsupported` is decided the same way, and conservatively: a server
392
+ // that predates introspect predates the Jev route too, so a kept slot
393
+ // there can only fall back with `http-404` — never spend — and clearing
394
+ // it would turn Jev off on no evidence. It gets no line when there was
395
+ // no slot, because on such a server there is nothing to say about Jev.
396
+ const previous = readCredentials().jev;
397
+ if (!previous) {
398
+ if (identity.kind === "unreachable") outcome.jev = { ok: false, unconfirmed: "none" };
399
+ } else if (previous.key === input.token && previous.url === new URL(input.url).origin) {
400
+ outcome.jev = { ok: false, unconfirmed: "kept" };
401
+ } else {
402
+ clearJevCloudCredential();
403
+ outcome.jev = { ok: false, unconfirmed: "cleared" };
404
+ }
405
+ }
406
+ }
407
+
285
408
  return outcome;
286
409
  }
287
410
 
411
+ /**
412
+ * The Jev line(s) for `describeOutcome`: whether FailproofAI Cloud runs Jev for
413
+ * this machine now, and — the one case worth a second line — why an existing
414
+ * `jev.json` was not touched.
415
+ */
416
+ function jevLines(outcome: ConnectOutcome): string[] {
417
+ const jev = outcome.jev;
418
+ if (!jev) return [];
419
+ if (jev.unconfirmed === "cleared") {
420
+ return [
421
+ " Jev could not confirm this key's Jev permission, and the Jev key stored before was a different one, so it was removed: Jev through FailproofAI Cloud is off. Run `failproofai config --token <key>` again to retry.",
422
+ ];
423
+ }
424
+ if (jev.unconfirmed) return [" Jev could not confirm the key's Jev permission; left as it was."];
425
+ if (!jev.ok) {
426
+ return [` Jev not through FailproofAI Cloud: ${jev.reason ?? `that key does not carry \`${PERMISSION_JEV}\``}.`];
427
+ }
428
+ if (jev.optIn) {
429
+ // One line, and nothing that reads as "on": this connection asked for
430
+ // decisions only, and Jev would send more than that.
431
+ return [
432
+ " Jev available on this key, not switched on (--no-transcripts): it sends each checked tool call and the recent prompt to FailproofAI Cloud for evaluation. To switch it on: `failproofai jev setup --provider failproofai`.",
433
+ ];
434
+ }
435
+ const config = jev.config;
436
+ if (!config || config.status === "written") {
437
+ // Plain http (loopback only) is accepted in shadow mode and nowhere else, so
438
+ // the enforce command would be refused there, by the CLI and the dashboard.
439
+ const plainHttp = config?.status === "written" && new URL(config.baseUrl).protocol === "http:";
440
+ return [
441
+ ` Jev on through FailproofAI Cloud, in shadow mode: logged, not enforced (${config?.path ?? "jev.json"}).`,
442
+ plainHttp
443
+ ? " Enforce needs an https FailproofAI Cloud URL: reconnect with `failproofai config --token <key> --url https://…`. Over plain http Jev stays in shadow mode."
444
+ : " Enforce it with `failproofai jev setup --mode enforce`, or from the dashboard.",
445
+ ];
446
+ }
447
+ if (config.status === "kept") {
448
+ const lines = [` Jev key stored; ${config.path} already exists and was left as configured.`];
449
+ if (config.otherOrigin) {
450
+ lines.push(
451
+ config.jevOff?.why === "off"
452
+ ? ` 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 shadow\`.`
453
+ : ` It points at ${config.otherOrigin}, so Jev stays off until you run \`failproofai jev setup --provider failproofai\`.`,
454
+ );
455
+ } else if (config.jevOff?.why === "refused") {
456
+ lines.push(
457
+ ` 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`."}`,
458
+ );
459
+ } else if (config.jevOff?.why === "off") {
460
+ lines.push(" It has Jev switched off (mode off), so Jev is off. To turn it on: `failproofai jev setup --mode shadow`.");
461
+ }
462
+ if (jev.stillOn) {
463
+ lines.push(
464
+ ` 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\`.`,
465
+ );
466
+ }
467
+ return lines;
468
+ }
469
+ return [` Jev key stored, but Jev was not turned on: ${config.problem}.`];
470
+ }
471
+
288
472
  /**
289
473
  * How to describe the outcome to someone at a terminal.
290
474
  *
@@ -311,6 +495,7 @@ export function describeOutcome(outcome: ConnectOutcome, machineId: string, url:
311
495
  const n = outcome.policy.policyCount ?? 0;
312
496
  lines.push(` Policy ${n} polic${n === 1 ? "y" : "ies"} assigned (deployment ${outcome.policy.deployment ?? 0}).`);
313
497
  lines.push(` Dashboard hook activity will be sent to ${ingestUrlFor(url)}.`);
498
+ lines.push(...jevLines(outcome));
314
499
  return lines;
315
500
  }
316
501
 
@@ -322,6 +507,7 @@ export function describeOutcome(outcome: ConnectOutcome, machineId: string, url:
322
507
  lines.push("");
323
508
  lines.push(" Enforcement works. Nothing will appear in the dashboard until this");
324
509
  lines.push(" key also carries `events:add`, or you re-run --connect with one that does.");
510
+ lines.push(...jevLines(outcome));
325
511
  return lines;
326
512
  }
327
513
 
@@ -332,6 +518,7 @@ export function describeOutcome(outcome: ConnectOutcome, machineId: string, url:
332
518
  lines.push("");
333
519
  lines.push(" This machine keeps enforcing its LOCAL policies and will report what");
334
520
  lines.push(" they decide, but will not receive centrally-managed ones.");
521
+ lines.push(...jevLines(outcome));
335
522
  return lines;
336
523
  }
337
524
 
@@ -346,6 +533,8 @@ export function configuredPaths(outcome: ConnectOutcome): string[] {
346
533
  const paths: string[] = [];
347
534
  if (outcome.policy.ok) paths.push(cloudCredentialPath());
348
535
  if (outcome.ingest.ok) paths.push(ingestPath());
536
+ // The Jev slot is always in credentials.json, never behind the JSON override.
537
+ if (outcome.jev?.ok) paths.push(credentialsFile());
349
538
  // Deduplicated because layout 2 consolidated both credentials into
350
539
  // `credentials.toml`, so the two capabilities now name the SAME file and the
351
540
  // closing note read "stored in <path> and <path>" — the same path, twice.
@@ -20,9 +20,13 @@ import {
20
20
  daemonVersionSkew,
21
21
  } from "./daemon-service";
22
22
  import { clearActiveCloudManagedPolicies } from "./cloud-managed-policies";
23
- import { optsFor, rows as kitRows, stack, warning } from "./tui";
23
+ import { TOKEN_ON_ARGV, optsFor, rows as kitRows, stack, warning } from "./tui";
24
24
  import { deliveryHealth, deliveryHealthLine } from "./delivery-health";
25
- import { readVersionFile, readCredentials } from "./fp-config";
25
+ import { clearJevCloudCredential, readVersionFile, readCredentials } from "./fp-config";
26
+ // A CLI command's import, never the hook path's: `jev-cloud-connection` reaches
27
+ // `semantic/jev-config`, which a machine without jev.json must not load while
28
+ // evaluating a tool call. `config --disconnect` is not that.
29
+ import { removeCloudJevConfig } from "./jev-cloud-connection";
26
30
  import { version as cliVersion } from "../../package.json";
27
31
  import { daemonSocketPresent } from "./daemon-client";
28
32
  import {
@@ -174,16 +178,26 @@ function daemonWarning(status: ReturnType<typeof daemonServiceStatus>): string[]
174
178
  ];
175
179
  }
176
180
 
181
+ /**
182
+ * What `config --token` (and `config --connect … --token`) ends with, connected
183
+ * or not: every Jev remedy sends people to that spelling, and the key is in
184
+ * history either way. `jev setup --token` says the same first line.
185
+ */
186
+ export const CONFIG_TOKEN_HISTORY_WARNING = [
187
+ TOKEN_ON_ARGV,
188
+ "Next time, run `failproofai config` with the key in FAILPROOFAI_CLOUD_TOKEN (from a secret store, or `read -rs`) instead of --token — and rotate this one if it matters.",
189
+ ];
190
+
177
191
  export async function runConnectCommand(opts: ConnectOptions): Promise<CommandResult> {
178
192
  if (!opts.url) return { exitCode: 1, lines: ["--connect needs a URL, e.g. --connect https://be.failproof.ai"] };
179
193
  if (!opts.token) {
180
194
  return {
181
195
  exitCode: 1,
182
196
  lines: [
183
- "--connect needs a machine token: --token <key>",
197
+ "--connect needs this machine's key: set FAILPROOFAI_CLOUD_TOKEN (preferred), or pass --token <key>.",
184
198
  "",
185
- "Create an API key carrying only the `policies:pull` permission for this",
186
- "machine. Do not use an admin key.",
199
+ "Create it with the \"machine\" preset on the dashboard's Keys page",
200
+ "(events:add, policies:pull, jev:evaluate). Do not use an admin key.",
187
201
  ],
188
202
  };
189
203
  }
@@ -324,6 +338,13 @@ export function runDisconnectCommand(): CommandResult {
324
338
  // leave the machine still shipping activity to a cloud the user believes
325
339
  // they have left — the one outcome nobody expects from this command.
326
340
  const removedIngest = clearIngestCredential();
341
+ // And spending: the Jev key goes with the rest, so no tool call after this
342
+ // one is charged to an org this machine has left. The Cloud's own jev.json
343
+ // goes too — it names a route that now has no key — unless it is switched
344
+ // off, the owner's opt-out that must outlive a reconnect; a BYOK one stays,
345
+ // because it never depended on the Cloud and is not the Cloud's to delete.
346
+ const removedJevKey = clearJevCloudCredential();
347
+ const jevConfig = removeCloudJevConfig();
327
348
  // Stop ENFORCING them too, not merely refreshing them. Clearing the
328
349
  // credential ends the daemon's polling; every artifact already on disk stayed
329
350
  // referenced by `active.json` and kept being loaded on every tool call — so a
@@ -337,8 +358,35 @@ export function runDisconnectCommand(): CommandResult {
337
358
  // machine is provably silent instead of silent-by-happenstance.
338
359
  updateConfig({ mode: "oss" });
339
360
 
340
- if (!removed && !existing && !hadIngest && !stoppedManaged) {
341
- return { exitCode: 0, lines: ["This machine is not connected to FailproofAI Cloud."] };
361
+ const jevLines =
362
+ jevConfig.status === "removed"
363
+ ? [` Jev through FailproofAI Cloud is off: removed ${jevConfig.path}. Hooks run the regex policies.`]
364
+ : jevConfig.status === "error"
365
+ ? [
366
+ removedJevKey
367
+ ? ` ! The FailproofAI Cloud key for Jev is gone, so Jev is off — but ${jevConfig.problem}.`
368
+ : ` ! ${jevConfig.problem}.`,
369
+ ...(jevConfig.setAside ? [` To put it back: mv ${jevConfig.setAside} ${jevConfig.path}`] : []),
370
+ ]
371
+ : jevConfig.status === "kept-off"
372
+ ? [` Jev stays switched off: ${jevConfig.path} (mode off) was kept, so connecting again leaves it off.`]
373
+ : jevConfig.status === "kept" && jevConfig.provider !== null
374
+ ? [` ${jevConfig.path} (provider ${jevConfig.provider}) is your own Jev setup and was left in place.`]
375
+ : jevConfig.status === "set-aside"
376
+ ? [
377
+ ` ! ${jevConfig.path} was being checked when another jev.json was written in its place. Nothing was deleted: the new one is at ${jevConfig.path},`,
378
+ ` and the one that was there (provider ${jevConfig.provider ?? "unreadable"}, not FailproofAI Cloud's) is kept at ${jevConfig.setAside}.`,
379
+ ]
380
+ : removedJevKey
381
+ ? [" Jev through FailproofAI Cloud is off: its key was removed."]
382
+ : [];
383
+
384
+ if (!removed && !existing && !hadIngest && !stoppedManaged && !removedJevKey && jevConfig.status !== "removed") {
385
+ // Nothing was connected — but the jev.json check ran all the same, and a
386
+ // file it could not put back where it was (a bring-your-own-key setup,
387
+ // moved aside) is not nothing: left unsaid, that Jev is simply gone.
388
+ const unsettled = jevConfig.status === "error" || jevConfig.status === "set-aside";
389
+ return { exitCode: 0, lines: ["This machine is not connected to FailproofAI Cloud.", ...(unsettled ? jevLines : [])] };
342
390
  }
343
391
 
344
392
  const lines = [
@@ -348,6 +396,7 @@ export function runDisconnectCommand(): CommandResult {
348
396
  ? " Cloud-managed policies stop being enforced and stop being refreshed.\n" +
349
397
  " Local builtin, custom and convention policies are unaffected."
350
398
  : " Local builtin, custom and convention policies are unaffected.",
399
+ ...jevLines,
351
400
  ];
352
401
  if (removedIngest) {
353
402
  // Named honestly. The collector manager starts once for the daemon's
@@ -135,6 +135,12 @@ export async function introspectKey(
135
135
  /** The permission each capability needs. Named so the messages can quote them. */
136
136
  export const PERMISSION_EVENTS = "events:add";
137
137
  export const PERMISSION_POLICIES = "policies:pull";
138
+ /**
139
+ * Jev through FailproofAI Cloud, charged to the key's org. The server refuses a
140
+ * key that carries it without both of the above, so a key with it always
141
+ * connects for policy and reporting too — there is no Jev-only connection.
142
+ */
143
+ export const PERMISSION_JEV = "jev:evaluate";
138
144
 
139
145
  export function hasPermission(identity: KeyIdentity, permission: string): boolean {
140
146
  return identity.permissions.includes(permission);
@@ -1178,7 +1178,18 @@ export async function runConfigureWizard(
1178
1178
  // did — it is not a trusted back door. http stays loopback-only, so a
1179
1179
  // bearer token still cannot be exported onto the wire in clear by
1180
1180
  // setting a variable.
1181
- const override = process.env.FAILPROOFAI_CLOUD_URL?.trim();
1181
+ //
1182
+ // `--url` comes first. It was read into `answers.url` and then only
1183
+ // ever used as a CONDITION above, never as the URL, so
1184
+ // `config --token <key> --url <X>` connected to the env value or the
1185
+ // hosted default and ignored X — reporting a machine somewhere its
1186
+ // operator had explicitly said not to. The CLI fills `answers.url` from
1187
+ // `--url`, falling back to FAILPROOFAI_CLOUD_URL, so the source named
1188
+ // on screen is whichever of the two the value actually is.
1189
+ const envOverride = process.env.FAILPROOFAI_CLOUD_URL?.trim();
1190
+ const flagOverride = answers.url?.trim();
1191
+ const override = flagOverride || envOverride;
1192
+ const source = flagOverride && flagOverride !== envOverride ? "--url" : "FAILPROOFAI_CLOUD_URL";
1182
1193
  if (override) {
1183
1194
  const validated = validateCloudUrl(cloudBaseFor(override));
1184
1195
  if (!validated.ok) {
@@ -1186,7 +1197,7 @@ export async function runConfigureWizard(
1186
1197
  // wants THAT endpoint, and quietly reporting a machine to the
1187
1198
  // hosted service instead is the one outcome they did not ask for.
1188
1199
  stdout.write(
1189
- `\nFAILPROOFAI_CLOUD_URL is set to "${override}", which cannot be used: ` +
1200
+ `\n${source === "--url" ? "--url is" : "FAILPROOFAI_CLOUD_URL is set to"} "${override}", which cannot be used: ` +
1190
1201
  `${validated.reason}\n`,
1191
1202
  );
1192
1203
  return cancel();
@@ -1196,7 +1207,7 @@ export async function runConfigureWizard(
1196
1207
  // matters and a machine reporting somewhere unexpected is exactly the
1197
1208
  // thing nobody notices until they go looking for data that is not
1198
1209
  // there.
1199
- stdout.write(`\nUsing ${url} (from FAILPROOFAI_CLOUD_URL).\n`);
1210
+ stdout.write(`\nUsing ${url} (from ${source}).\n`);
1200
1211
  } else {
1201
1212
  url = cloudBaseFor(DEFAULT_INGEST_URL);
1202
1213
  }
@@ -1507,9 +1518,13 @@ export async function runConfigureWizard(
1507
1518
  token: connect.token,
1508
1519
  machineId: connect.machineId,
1509
1520
  machineLabel: connect.machineLabel,
1510
- // Both streams, as disclosed at the connect question. This is the one
1511
- // place that decision becomes a written setting.
1512
- sessions: true,
1521
+ // Both streams, as disclosed at the connect question, unless the run
1522
+ // said `--no-transcripts`. This is the one place that decision becomes
1523
+ // a written setting — and it used to be a literal `true`, so the flag
1524
+ // was parsed, carried here in `answers`, and silently ignored: a fleet
1525
+ // that asked for decisions only shipped every prompt, file and command
1526
+ // output. The separate `--connect` path always honoured it.
1527
+ sessions: answers.noTranscripts !== true,
1513
1528
  });
1514
1529
  connected = outcome.anyConfigured;
1515
1530
  // Show the human label with the id in parentheses when they differ.
@@ -1520,6 +1535,11 @@ export async function runConfigureWizard(
1520
1535
  for (const line of describeOutcome(outcome, shownAs, connect.url)) {
1521
1536
  stdout.write(`${line}\n`);
1522
1537
  }
1538
+ // Said when it took effect, as `--connect` says it: the default carries
1539
+ // prompts and file contents, so the opt-out is worth confirming.
1540
+ if (outcome.ingest.ok && answers.noTranscripts === true) {
1541
+ stdout.write(" Session transcripts are NOT being sent (--no-transcripts). Decisions only.\n");
1542
+ }
1523
1543
  // Never the key, the URL, or the count — only that it happened and which
1524
1544
  // capabilities the server actually granted.
1525
1545
  void emit("configure_connect", {