failproofai 1.0.12-beta.1 → 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 (168) 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.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  30. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/index.html +1 -1
  33. package/.next/standalone/.next/server/app/index.rsc +3 -3
  34. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  35. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  36. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  37. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  38. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  41. package/.next/standalone/.next/server/app/policies/page.js +2 -2
  42. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  55. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
  59. package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +2 -2
  60. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/src_hooks_0_em82s16zfym._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00l2u6j86cb8y._.js +3 -0
  63. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pwj2h2efe5f-._.js +1 -1
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0b78b0j8iyv6l._.js → [root-of-the-server]__1vofkb2oq7p7e._.js} +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/_0bseae9u7g98r._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/_0mouhaflye1ro._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/ssr/{_1a3tubds7s7ow._.js → _0ouu886szh6yj._.js} +4 -4
  78. package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
  79. package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/_1fw0_nzkae5ao._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/ssr/_20hn4gn87nsjc._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
  83. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr0vt64h._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +2 -2
  88. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1i8oid1gc7s1f.js → node_modules_html-to-image_es_index_1fv03uigni8s5.js} +1 -1
  90. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0janj0i6dqko7._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589gp4p60j._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me658zqmh_._.js +1 -1
  96. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  97. package/.next/standalone/.next/server/pages/404.html +1 -1
  98. package/.next/standalone/.next/server/pages/500.html +1 -1
  99. package/.next/standalone/.next/server/preview-props.json +3 -3
  100. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  101. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  102. package/.next/standalone/.next/static/chunks/{2-tf6_7u623kd.js → 0ay__-d2h1xb5.js} +1 -1
  103. package/.next/standalone/.next/static/chunks/{18433qz-8njv3.js → 0tbm3vjm62h-2.js} +1 -1
  104. package/.next/standalone/.next/static/chunks/{1b32-afqacaen.js → 1c_jf6g5wnslk.js} +1 -1
  105. package/.next/standalone/.next/static/chunks/{0_zfad4co-t2z.js → 1my3-5p47t7r-.js} +1 -1
  106. package/.next/standalone/.next/static/chunks/{0jutamwqz8pg5.js → 20py8k1gd_i1m.js} +1 -1
  107. package/.next/standalone/.next/static/chunks/21hckpmsc4g70.js +6 -0
  108. package/.next/standalone/.next/static/chunks/2hmex93ppj1ke.js +1 -0
  109. package/.next/standalone/.next/static/chunks/{42e8zab314654.js → 2o6d37j425uci.js} +1 -1
  110. package/.next/standalone/.next/static/chunks/40oworn_-x12p.js +1 -0
  111. package/.next/standalone/.next/static/chunks/{24w3x2da9sk49.js → 42xptz88mtq3x.js} +1 -1
  112. package/.next/standalone/app/actions/update-jev-config.ts +5 -3
  113. package/.next/standalone/app/settings/jev-panel.tsx +1 -1
  114. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +9 -18
  115. package/.next/standalone/fp-cloud-cli/README.md +7 -9
  116. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +1 -27
  117. package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +12 -216
  118. package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +7 -53
  119. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +2 -71
  120. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +1 -1
  121. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +6 -7
  122. package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +32 -197
  123. package/.next/standalone/hermes-plugin/__init__.py +11 -0
  124. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  125. package/.next/standalone/package.json +11 -10
  126. package/.next/standalone/sdk/python/uv.lock +31 -8
  127. package/.next/standalone/server.js +1 -1
  128. package/dist/cli.mjs +1686 -612
  129. package/dist/worker.mjs +354 -49
  130. package/hermes-plugin/__init__.py +11 -0
  131. package/hermes-plugin/plugin.yaml +1 -1
  132. package/package.json +11 -10
  133. package/src/audit/open-browser.ts +24 -7
  134. package/src/hooks/agent-roster.ts +9 -1
  135. package/src/hooks/agent-skill.ts +203 -0
  136. package/src/hooks/cloud-connection.ts +27 -51
  137. package/src/hooks/cloud-introspect.ts +5 -2
  138. package/src/hooks/cloud-login.ts +248 -0
  139. package/src/hooks/cloud-managed-policies.ts +54 -4
  140. package/src/hooks/codex-system-hooks.ts +10 -4
  141. package/src/hooks/config-status.ts +9 -3
  142. package/src/hooks/configure-wizard.ts +356 -32
  143. package/src/hooks/delivery-health.ts +47 -2
  144. package/src/hooks/effective-reviewers.ts +14 -0
  145. package/src/hooks/fp-config.ts +19 -0
  146. package/src/hooks/handler.ts +21 -2
  147. package/src/hooks/hook-activity-store.ts +7 -1
  148. package/src/hooks/integrations.ts +18 -3
  149. package/src/hooks/jev-activity.ts +117 -1
  150. package/src/hooks/jev-cli.ts +7 -9
  151. package/src/hooks/manager.ts +80 -46
  152. package/src/hooks/policy-evaluator.ts +17 -0
  153. package/src/hooks/policy-reviewability.ts +4 -2
  154. package/src/hooks/semantic/cloud-jev.ts +219 -28
  155. package/src/hooks/semantic/combine.ts +26 -1
  156. package/src/hooks/semantic/jev-client.ts +1 -1
  157. package/src/hooks/semantic/jev-config.ts +6 -27
  158. package/src/hooks/semantic/jev-review.ts +90 -2
  159. package/src/hooks/tui.ts +280 -0
  160. package/src/hooks/uninstall-cli.ts +12 -1
  161. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0588st-tpiuia._.js +0 -3
  162. package/.next/standalone/.next/server/chunks/ssr/_128ebxr83nk6g._.js +0 -3
  163. package/.next/standalone/.next/static/chunks/114hbolvrv94e.js +0 -1
  164. package/.next/standalone/.next/static/chunks/1p-hsi58i-6h0.js +0 -6
  165. package/.next/standalone/.next/static/chunks/3-pku9qwv1yfu.js +0 -1
  166. /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → LHCtFxVG0mdvd4lsXAOFC}/_buildManifest.js +0 -0
  167. /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → LHCtFxVG0mdvd4lsXAOFC}/_clientMiddlewareManifest.js +0 -0
  168. /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → LHCtFxVG0mdvd4lsXAOFC}/_ssgManifest.js +0 -0
@@ -10,7 +10,7 @@
10
10
  * `writeJsonAtomically` the CLI's `jev setup` calls, with the same
11
11
  * `{ mode: 0o600, dirMode: 0o700 }`, after the same `validateJevConfig` the
12
12
  * loader itself runs. Nothing here re-states a rule that lives in
13
- * `jev-config.ts`: not the URL scheme, not "plain http only in observe mode",
13
+ * `jev-config.ts`: not the URL scheme, not "plain http to loopback only",
14
14
  * not "cloudflare needs an account id". A second copy of those rules is how the
15
15
  * dashboard ends up writing a file the hooks then refuse — the exact failure
16
16
  * mode this module exists to avoid.
@@ -101,6 +101,7 @@ import { chmodSync, existsSync, statSync, unlinkSync } from "node:fs";
101
101
  import { dirname } from "node:path";
102
102
  import { headers } from "next/headers";
103
103
  import { writeJsonAtomically } from "@/lib/atomic-write";
104
+ import { CLOUD_MANAGED_JEV_MODE, cloudManagesJevMode } from "@/src/hooks/fp-config";
104
105
  import {
105
106
  hostnameFromHostHeader,
106
107
  isLoopbackHostname,
@@ -475,8 +476,8 @@ export async function saveJevConfigAction(input: JevConfigInput): Promise<JevWri
475
476
  * endpoint and the provider are exactly what they were. It is written the way
476
477
  * every other write here is (`writeJsonAtomically`, 0600, owner-only
477
478
  * directory), after the loader's own validator has checked the result with
478
- * stand-ins in the key slots, so `enforce` is refused where the loader would
479
- * refuse it (plain http to localhost) rather than written and then ignored.
479
+ * stand-ins in the key slots, so a mode the loader would refuse is refused
480
+ * here rather than written and then ignored.
480
481
  *
481
482
  * Refused for a file the loader calls too open: someone else may have written
482
483
  * the endpoint it names, and re-saving it at 0600 would start trusting that
@@ -489,6 +490,7 @@ export async function setJevModeAction(requested: string): Promise<JevWriteResul
489
490
  if (mode === null) {
490
491
  return { ok: false, problem: 'mode must be "off", "observe" or "enforce".' };
491
492
  }
493
+ if (cloudManagesJevMode()) return { ok: false, problem: CLOUD_MANAGED_JEV_MODE };
492
494
 
493
495
  const existingFile = readJevConfigFileForUpdate();
494
496
  if (existingFile === null) {
@@ -45,7 +45,7 @@
45
45
  * and a save leaves whatever is stored alone.
46
46
  *
47
47
  * Client-side validation here is a convenience only. Every rule — the URL
48
- * scheme, plain http being refused outside observe mode, cloudflare's account id
48
+ * scheme (plain http to loopback only), cloudflare's account id
49
49
  * — is enforced server-side by the same `validateJevConfig` the loader runs.
50
50
  *
51
51
  * ## FailproofAI Cloud
@@ -10,24 +10,15 @@
10
10
  `both` (JavaScript reviewable by exactly its own checks; the server derives the authority).
11
11
  The kind follows from what is given when `--kind` is omitted, and a kind that contradicts its
12
12
  inputs is exit 2 before anything is sent. The positional source still works.
13
- - `fp fleet deploy … --jev-mode off|observe|enforce|local` sets the machine's Jev mode from
14
- FailproofAI Cloud (overriding the machine's own; `local` hands it back). Omitted, the request
15
- carries no `jevMode` and the mode is unchanged; `--jev-mode` alone is a valid deploy. An
16
- `observe` effect on a `jev` policy is refused (exit 2) — Jev is observed through the mode.
17
- - `fp fleet jev-mode <machine…|--all> <mode>` changes only the Jev mode, through its own
18
- route (`PUT /api/enforcement/deployments/{id}/jev-mode`), never the policy set — and
19
- `fp fleet deploy --jev-mode` with no change to the set now uses it too. A deploy is a full
20
- replace of the set the CLI read, which leaves out disabled policies' assignments, so a
21
- mode change sent as a deploy deleted them and reverted concurrent edits.
22
- - `fp fleet rollback` says which Jev mode it restores (`jev mode <now> → <then>`, and
23
- `jevModeBefore` in `--json`); `fp fleet history` shows each generation's Jev mode and marks a
24
- mode-only generation as a change.
25
- - The `--jev-mode` and `fleet jev-mode` help says what `enforce` does: Jev's checks block calls
26
- as well as clearing what they review, machine-wide (installed packs' checks included); an
27
- `observe` effect on a `both` policy withholds its Jev checks.
28
- - The `fleet deploy`, `fleet jev-mode` and `policies publish` help says Jev checks run on
29
- FailproofAI Cloud and nothing is installed on the machine, and `fleet show`/`fleet jev-mode`
30
- name the reasons a machine with a mode cannot ask (`jev_unconfigured`, `transcripts_disabled`)
13
+ - No Jev mode is set by hand: FailproofAI Cloud derives a machine's Jev mode from what is
14
+ deployed to it (on while a `jev`/`both` policy is, off otherwise), so `fp fleet deploy` has
15
+ no `--jev-mode` and there is no `fp fleet jev-mode`. An `observe` effect applies to `jev`
16
+ and `both` policies like any other: their checks are recorded and decide nothing.
17
+ - `fp fleet rollback` says when the Jev mode differs (`jev mode <now> → <then>`, and
18
+ `jevModeBefore` in `--json`); `fp fleet history` shows each generation's Jev mode.
19
+ - The `fleet deploy` and `policies publish` help says Jev checks run on
20
+ FailproofAI Cloud and nothing is installed on the machine, and `fleet show`
21
+ names the reasons a machine with a mode cannot ask (`jev_unconfigured`, `transcripts_disabled`)
31
22
  and the `jev_budget` report.
32
23
  - `fp policies list/show` print each version's kind and Jev check names (`show` also the
33
24
  declarations); `fp fleet list/show` print the machine's Jev mode and the policy errors it
@@ -144,19 +144,17 @@ earlier version is), `jev` (Jev checks only, from a JSON file of declarations
144
144
  each exactly a failproofai pack manifest's semantic entry), or `both`
145
145
  (JavaScript whose verdict its own Jev checks may clear, deployed as one).
146
146
  Jev checks run on FailproofAI Cloud; nothing is installed on the machine.
147
- `--jev-mode` sets a machine's Jev mode from FailproofAI Cloud, overriding the
148
- machine's own; omit it and the mode is left alone. Under `observe` or `enforce`
149
- the machine sends each checked tool call to FailproofAI Cloud, which asks the
150
- checks deployed to it. The mode is the machine's, so it covers installed packs'
151
- checks too, and in `enforce` Jev's checks **block** calls as well as clearing
152
- the regex verdicts they review. `fp fleet jev-mode` changes only the mode, and
153
- never rewrites a machine's policy set.
147
+ While a `jev` or `both` policy is deployed to a machine, FailproofAI Cloud
148
+ turns Jev on there and the machine sends each checked tool call to it; with
149
+ none, Jev is off. There is no Jev mode to set: trial a policy with `:observe`
150
+ (its checks are recorded, never applied), then deploy it as `enforce`, where
151
+ Jev's checks **block** calls as well as clearing the regex verdicts they
152
+ review.
154
153
 
155
154
  ```bash
156
155
  fp policies publish prod-db-intent --kind jev --semantic ./checks.json
157
156
  fp policies publish no-prod-db --kind both --source ./rule.mjs --semantic ./checks.json
158
- fp fleet deploy ci-runner-01 --add prod-db-intent --jev-mode observe
159
- fp fleet jev-mode --all enforce # the mode alone, every machine with a deployment
157
+ fp fleet deploy ci-runner-01 --add prod-db-intent:observe
160
158
  fp fleet show ci-runner-01 # its Jev mode, and any policy errors it reported
161
159
  ```
162
160
 
@@ -1785,42 +1785,16 @@ def deploy_policies(
1785
1785
  ctx: ClientContext,
1786
1786
  machine_id: str,
1787
1787
  policies: Sequence[PolicyRef],
1788
- jev_mode: Optional[str] = None,
1789
1788
  ) -> Deployment:
1790
1789
  """PUT /api/enforcement/deployments/{id} — REPLACES the machine's whole set.
1791
1790
 
1792
- ``jev_mode`` (``off|observe|enforce|local``) is sent only when given: the
1793
- server reads an ABSENT ``jevMode`` as "keep the machine's current one", so a
1794
- deploy that says nothing about Jev never changes it. ``local`` stops
1795
- FailproofAI Cloud overriding the machine's own mode.
1791
+ Never carries a Jev mode: FailproofAI Cloud derives it from the set.
1796
1792
  """
1797
1793
  path = f"/api/enforcement/deployments/{machine_id}"
1798
1794
  body: Dict[str, Any] = {"policies": [p.to_dict() for p in policies]}
1799
- if jev_mode is not None:
1800
- body["jevMode"] = jev_mode
1801
1795
  return Deployment.from_dict(_request_json(ctx, "PUT", path, json_body=body) or {})
1802
1796
 
1803
1797
 
1804
- def set_jev_mode(ctx: ClientContext, machine_id: str, jev_mode: str) -> Tuple[Deployment, bool]:
1805
- """PUT /api/enforcement/deployments/{id}/jev-mode — change ONLY the Jev mode.
1806
-
1807
- ``jev_mode`` is ``off|observe|enforce|local`` (``local`` stops FailproofAI
1808
- Cloud overriding the machine's own mode). Unlike a deploy, this never
1809
- touches the machine's policy set: a deploy is a full replace of the set, and
1810
- the set a client can read leaves out disabled policies' assignments, so a
1811
- mode change sent as a deploy deleted them — and reverted any edit made since
1812
- the set was read. It still mints a new generation, which is how the machine
1813
- picks the mode up.
1814
-
1815
- Returns the deployment and whether anything changed: setting the mode the
1816
- machine already has changes nothing and mints nothing (``changed: false``).
1817
- A machine with no deployment is a 404 — a deploy is how it gets its first.
1818
- """
1819
- path = f"/api/enforcement/deployments/{machine_id}/jev-mode"
1820
- data = _request_json(ctx, "PUT", path, json_body={"jevMode": jev_mode}) or {}
1821
- return Deployment.from_dict(data), bool(data.get("changed", True))
1822
-
1823
-
1824
1798
  def deployment_history(ctx: ClientContext, machine_id: str) -> List[Dict[str, Any]]:
1825
1799
  """GET /api/enforcement/deployments/{id}/history — every generation, newest first."""
1826
1800
  path = f"/api/enforcement/deployments/{machine_id}/history"
@@ -1,4 +1,4 @@
1
- """The fleet: fleet list / show / deploy / jev-mode / diff / history / rollback / rename.
1
+ """The fleet: fleet list / show / deploy / diff / history / rollback / rename.
2
2
 
3
3
  What each machine is TOLD to enforce. Authoring the policies is `fp policies`;
4
4
  what they actually did is `fp guardrails`.
@@ -26,12 +26,10 @@ from .. import output
26
26
  from .._context import GLOBALS_EPILOG, AppState, deny_in_key_mode, require_auth
27
27
  from .. import _click_compat as click # the Click Typer is running; see _click_compat
28
28
  from ..enforcement import (
29
- JEV_MODES,
30
29
  RefError,
31
30
  RefUsageError,
32
31
  check_race,
33
32
  disabled_ids,
34
- jev_mode_after,
35
33
  latest_versions,
36
34
  plan_deploy,
37
35
  version_kinds,
@@ -157,7 +155,7 @@ def fleet_deploy(
157
155
  add: Optional[List[str]] = typer.Option(
158
156
  None, "--add",
159
157
  help="Add or update a policy: `id`, `id@version`, `id:effect` or `id@version:effect`. "
160
- "`observe` on a `both` policy observes its JavaScript and withholds its Jev checks.",
158
+ "`observe` records what the policy (its JavaScript and its Jev checks) would decide, and decides nothing.",
161
159
  ),
162
160
  remove: Optional[List[str]] = typer.Option(None, "--remove", help="Remove a policy by id."),
163
161
  replace: Optional[List[str]] = typer.Option(
@@ -176,14 +174,6 @@ def fleet_deploy(
176
174
  False, "--create",
177
175
  help="Allow deploying to a machine id that has not checked in yet (pre-staging).",
178
176
  ),
179
- jev_mode: Optional[str] = typer.Option(
180
- None, "--jev-mode",
181
- help="Set the machine's Jev mode from FailproofAI Cloud: off, observe, enforce (Jev's "
182
- "checks BLOCK calls as well as clearing what they review) — or local to stop "
183
- "overriding the machine's own. Jev checks run on FailproofAI Cloud; nothing is "
184
- "installed on the machine. Machine-wide: installed packs' checks too. "
185
- "Omitted: unchanged.",
186
- ),
187
177
  yes: bool = typer.Option(False, "--yes", "-y", help="Skip the confirmation prompt. The prompt only appears on an interactive terminal: under --json, or with stdin redirected, this command proceeds without asking."),
188
178
  ) -> None:
189
179
  """Change what a machine enforces, showing the full resulting set first.
@@ -198,20 +188,10 @@ def fleet_deploy(
198
188
  Any kind deploys the same way. Jev checks run on FailproofAI Cloud; nothing
199
189
  is installed on the machine: a `both` policy sends the machine its
200
190
  JavaScript, and the machine asks FailproofAI Cloud about each checked tool
201
- call while its Jev mode is `observe` or `enforce`. An effect applies to
202
- JavaScript only — an `observe` `both` policy observes its JavaScript and its
203
- Jev checks are not asked — and a `jev` policy is watched through the Jev
204
- MODE instead: `--jev-mode observe` asks Jev and logs what it says while the
205
- regex policies decide; `enforce` lets Jev's checks BLOCK calls (every `jev`
206
- policy's deny checks) as well as clear the regex verdicts they review; `off`
207
- stops Jev on the machine; `local` hands the choice back to the machine. The
208
- mode is the MACHINE's: it also governs the checks of any pack installed
209
- there, and it overrides the machine's own mode and `jev.json`, a local
210
- `off` included. Without `--jev-mode` it is left as it is.
211
-
212
- `--jev-mode` with no change to the set changes ONLY the mode (the same as
213
- `fp fleet jev-mode`): the policy set — disabled policies' assignments
214
- included — is not read back and rewritten.
191
+ call. An `observe` effect applies to every kind: the policy's JavaScript
192
+ and its Jev checks are evaluated and recorded, and decide nothing. There is
193
+ no Jev mode to set: FailproofAI Cloud turns Jev on for a machine while any
194
+ `jev` or `both` policy is deployed to it, and off when none is.
215
195
 
216
196
  **Concurrency.** The write is a full replace with no server-side lock, so the
217
197
  CLI records the generation it read and refuses if the result is not exactly
@@ -224,19 +204,18 @@ def fleet_deploy(
224
204
  * `fp fleet deploy ci-runner-01 --add no-force-push`
225
205
  * `fp fleet deploy ci-runner-01 --add prod-guard@1:observe --remove old-rule`
226
206
  * `fp fleet deploy ci-runner-01 --set no-force-push --set no-secret-echo`
227
- * `fp fleet deploy ci-runner-01 --add prod-db-intent --jev-mode observe`
228
- * `fp fleet deploy ci-runner-01 --jev-mode local`
207
+ * `fp fleet deploy ci-runner-01 --add prod-db-intent:observe`
229
208
  """
230
209
  state: AppState = ctx.obj
231
210
  deny_in_key_mode(state, "fleet deploy", _KEY_MODE_REASON)
232
211
  cctx = require_auth(state)
233
212
 
234
- if not add and not remove and replace is None and jev_mode is None and not targets and not all_agents:
213
+ if not add and not remove and replace is None and not targets and not all_agents:
235
214
  # Exit 2 for the same reason `--set` with `--add` is: no flag
236
215
  # combination was given that this command can act on. Both are the
237
216
  # caller's command line, not the server's answer.
238
217
  raise click.UsageError(
239
- "nothing to do — pass --add, --remove, --set, --target, --all-agents, or --jev-mode. "
218
+ "nothing to do — pass --add, --remove, --set, --target, or --all-agents. "
240
219
  "`fp fleet show <machine>` prints the current set."
241
220
  )
242
221
 
@@ -268,8 +247,6 @@ def fleet_deploy(
268
247
  replace=replace,
269
248
  latest=latest,
270
249
  disabled=disabled_ids(published),
271
- jev_mode=jev_mode,
272
- current_jev_mode=current.jev_mode if current else None,
273
250
  kinds=version_kinds(published),
274
251
  targets=targets or (),
275
252
  all_agents=all_agents or (),
@@ -298,16 +275,6 @@ def fleet_deploy(
298
275
  output.deployment_unchanged(machine_id)
299
276
  return
300
277
 
301
- # The mode alone: its own route, which never touches the policy set. A
302
- # deploy is a full replace of the set the CLI read, and that set leaves out
303
- # disabled policies' assignments — so a mode change sent as a deploy deleted
304
- # them, and reverted anything changed since the read (CONTRACT C9.1). A
305
- # machine with no deployment yet has nothing to lose and no mode to change:
306
- # its first deploy, carrying the mode, is how it gets one.
307
- if plan.jev_mode is not None and not plan.set_changes and current is not None:
308
- _apply_jev_modes(state, cctx, [(machine_id, current.jev_mode)], plan.jev_mode, yes=yes, plan=plan)
309
- return
310
-
311
278
  if not output.is_json():
312
279
  output.render_deploy_plan(plan)
313
280
  dropped = len(plan.removed)
@@ -323,7 +290,7 @@ def fleet_deploy(
323
290
  output.print_cancelled()
324
291
  return
325
292
 
326
- result = api.deploy_policies(cctx, machine_id, plan.result, jev_mode=jev_mode)
293
+ result = api.deploy_policies(cctx, machine_id, plan.result)
327
294
  check_race(plan.base, result.deployment)
328
295
 
329
296
  if output.is_json():
@@ -336,176 +303,6 @@ def fleet_deploy(
336
303
  output.deployment_applied(machine_id, result.deployment, len(result.policies))
337
304
 
338
305
 
339
- def _jev_mode_consequence(mode: str, count: int) -> str:
340
- what = f"{count} machine{'s' if count != 1 else ''}"
341
- tail = {
342
- "enforce": "; in enforce Jev's deny checks BLOCK calls, and it covers installed packs' checks too",
343
- "observe": "; in observe Jev only records what it would have decided",
344
- "off": "; Jev stops on them, a local jev.json included",
345
- "local": "; each machine's own jev.json decides again",
346
- }.get(mode, "")
347
- return f"this sets the Jev mode to {mode} on {what} and leaves their policy sets untouched{tail}"
348
-
349
-
350
- def _apply_jev_modes(
351
- state: AppState,
352
- cctx,
353
- targets: List[tuple],
354
- mode: str,
355
- *,
356
- yes: bool,
357
- plan=None,
358
- ) -> None:
359
- """Set `mode` on each `(machine_id, current_mode)`, through the mode-only route.
360
-
361
- Shared by `fleet jev-mode` and `fleet deploy --jev-mode` with no set change.
362
- A machine already on `mode` is left alone; one that fails is reported and
363
- the rest still go ahead (exit 1 at the end, naming them).
364
- """
365
- after = jev_mode_after(mode)
366
- rows = [
367
- {"machineId": mid, "from": cur or "local", "to": after or "local", "changed": (cur or None) != after}
368
- for mid, cur in targets
369
- ]
370
- todo = [r for r in rows if r["changed"]]
371
- if not todo:
372
- if output.is_json():
373
- out = {"mode": mode, "machines": rows, "applied": False}
374
- if plan is not None:
375
- out = {"plan": plan.to_dict(), "deployment": None, "applied": False}
376
- output.emit_json(out)
377
- return
378
- output.jev_mode_unchanged([r["machineId"] for r in rows], mode)
379
- return
380
-
381
- if not output.is_json():
382
- output.render_jev_mode_plan(rows)
383
- if not _write.confirm_destructive(
384
- state, "change the Jev mode on",
385
- todo[0]["machineId"] if len(todo) == 1 else f"{len(todo)} machines",
386
- consequence=_jev_mode_consequence(mode, len(todo)),
387
- assume_yes=yes,
388
- ):
389
- if output.is_json():
390
- output.emit_json({"cancelled": True, "applied": False, "machines": rows})
391
- else:
392
- output.print_cancelled()
393
- return
394
-
395
- failed = []
396
- last = None
397
- for row in todo:
398
- try:
399
- result, changed = api.set_jev_mode(cctx, row["machineId"], mode)
400
- except AuthError:
401
- raise
402
- except FpCliError as exc:
403
- # One machine's refusal (a 404, a 403, a 422) is that machine's;
404
- # the rest still go ahead, and the exit code says it was not all.
405
- row["error"] = exc.format_message() if hasattr(exc, "format_message") else str(exc)
406
- failed.append(row["machineId"])
407
- continue
408
- last = result
409
- row["deployment"] = result.deployment
410
- row["changed"] = changed
411
-
412
- if output.is_json():
413
- if plan is not None:
414
- output.emit_json({
415
- "plan": plan.to_dict(),
416
- "deployment": last.to_dict() if last is not None else None,
417
- "applied": last is not None,
418
- "jevModeOnly": True,
419
- })
420
- else:
421
- output.emit_json({"mode": mode, "machines": rows, "applied": not failed})
422
- else:
423
- output.jev_mode_applied(rows)
424
- if failed:
425
- raise ApiError(
426
- f"the Jev mode could not be changed on {', '.join(failed)}"
427
- + ("" if len(failed) == len(todo) else " — the other machines were changed"),
428
- )
429
-
430
-
431
- def fleet_jev_mode(
432
- ctx: typer.Context,
433
- args: Optional[List[str]] = typer.Argument(
434
- None, metavar="[MACHINE]... MODE",
435
- help="The machines, then the mode: off, observe, enforce or local. With --all, just the mode.",
436
- ),
437
- all_machines: bool = typer.Option(
438
- False, "--all", help="Every machine that has a deployment (machines with none are skipped).",
439
- ),
440
- yes: bool = typer.Option(False, "--yes", "-y", help="Skip the confirmation prompt. The prompt only appears on an interactive terminal: under --json, or with stdin redirected, this command proceeds without asking."),
441
- ) -> None:
442
- """Set the Jev mode FailproofAI Cloud gives machines, and nothing else.
443
-
444
- `off`, `observe` or `enforce` override each machine's own mode and
445
- `jev.json`, a local `off` included; `local` hands the choice back to the
446
- machine. Jev checks run on FailproofAI Cloud; nothing is installed on the
447
- machine: under `observe` or `enforce` it sends each checked tool call to
448
- FailproofAI Cloud. `observe` asks Jev and records what it says while the
449
- regex policies decide. `enforce` lets Jev's checks BLOCK calls — every `jev`
450
- policy's deny checks — as well as clear the regex verdicts they review. The
451
- mode is the machine's: it also governs the checks of every pack installed
452
- there. A machine connected with `--no-transcripts` never asks, and reports
453
- `transcripts_disabled`.
454
-
455
- Only the mode changes: each machine's policy set, disabled policies'
456
- assignments included, is left exactly as it is, and a machine already on
457
- the mode is skipped. Each change is a new generation in `fp fleet history`.
458
- A machine with no deployment has no mode to change — `fp fleet deploy
459
- <machine> --jev-mode <mode>` gives it its first deployment with one.
460
-
461
- Needs `policies:write`. With `--json`: `{mode, applied, machines:[{machineId,
462
- from, to, changed, deployment?, error?}]}`.
463
-
464
- Examples:
465
-
466
- * `fp fleet jev-mode ci-runner-01 observe`
467
- * `fp fleet jev-mode ci-runner-01 ci-runner-02 enforce`
468
- * `fp fleet jev-mode --all local`
469
- """
470
- state: AppState = ctx.obj
471
- deny_in_key_mode(state, "fleet jev-mode", _KEY_MODE_REASON)
472
- words = list(args or [])
473
- if not words:
474
- raise click.UsageError("pass the mode: off, observe, enforce or local")
475
- mode, machine_ids = words[-1], list(dict.fromkeys(words[:-1]))
476
- if mode not in JEV_MODES:
477
- raise click.UsageError(f"{mode!r} is not a Jev mode — one of {', '.join(JEV_MODES)}")
478
- if all_machines and machine_ids:
479
- raise click.UsageError("--all sets every machine; do not name machines as well")
480
- if not all_machines and not machine_ids:
481
- raise click.UsageError("name at least one machine, or pass --all")
482
- cctx = require_auth(state)
483
-
484
- deployments = {d.machine_id: d for d in api.list_deployments(cctx)}
485
- if all_machines:
486
- targets = [(mid, deployments[mid].jev_mode) for mid in sorted(deployments)]
487
- else:
488
- known = {m.machine_id for m in api.list_machines(cctx)} | set(deployments)
489
- unknown = [mid for mid in machine_ids if mid not in known]
490
- if unknown:
491
- raise NotFoundError(f"no machine {', '.join(repr(u) for u in unknown)} has checked in")
492
- bare = [mid for mid in machine_ids if mid not in deployments]
493
- if bare:
494
- raise ApiError(
495
- f"{', '.join(bare)} {'has' if len(bare) == 1 else 'have'} no deployment, so there is no Jev "
496
- "mode to change",
497
- hint=f"`fp fleet deploy <machine> --jev-mode {mode}` gives a machine its first deployment with the mode",
498
- )
499
- targets = [(mid, deployments[mid].jev_mode) for mid in machine_ids]
500
- if not targets:
501
- if output.is_json():
502
- output.emit_json({"mode": mode, "machines": [], "applied": False})
503
- else:
504
- output.info("no machine has a deployment yet — nothing to set")
505
- return
506
- _apply_jev_modes(state, cctx, targets, mode, yes=yes)
507
-
508
-
509
306
  def fleet_diff(
510
307
  ctx: typer.Context,
511
308
  machine_id: Optional[str] = typer.Argument(None, help="Machine id. Omit for the whole fleet."),
@@ -606,7 +403,7 @@ def fleet_rollback(
606
403
  _require_machine(cctx, machine_id)
607
404
  current = api.get_deployment(cctx, machine_id)
608
405
  # The mode that generation carried comes back with it, NULL (`local`)
609
- # included — a rollback past an `--jev-mode off` turns enforcement back on.
406
+ # included, as the server reports it.
610
407
  # Read it first, so the prompt says so rather than the fleet finding out.
611
408
  target = next(
612
409
  (e for e in api.deployment_history(cctx, machine_id) if e.get("deployment") == deployment), None)
@@ -669,12 +466,11 @@ def register(app: typer.Typer) -> None:
669
466
  no_args_is_help=True,
670
467
  rich_markup_mode="markdown",
671
468
  context_settings={"help_option_names": ["-h", "--help"]},
672
- help="The fleet and what each machine enforces (list / show / deploy / jev-mode / diff / history / rollback / rename).",
469
+ help="The fleet and what each machine enforces (list / show / deploy / diff / history / rollback / rename).",
673
470
  )
674
471
  fleet_app.command("list", epilog=GLOBALS_EPILOG)(fleet_list)
675
472
  fleet_app.command("show", epilog=GLOBALS_EPILOG)(fleet_show)
676
473
  fleet_app.command("deploy", epilog=GLOBALS_EPILOG)(fleet_deploy)
677
- fleet_app.command("jev-mode", epilog=GLOBALS_EPILOG)(fleet_jev_mode)
678
474
  fleet_app.command("diff", epilog=GLOBALS_EPILOG)(fleet_diff)
679
475
  fleet_app.command("history", epilog=GLOBALS_EPILOG)(fleet_history)
680
476
  fleet_app.command("rollback", epilog=GLOBALS_EPILOG)(fleet_rollback)
@@ -43,10 +43,6 @@ VALID_EFFECTS = ("enforce", "observe")
43
43
  #: JavaScript reviewable by its own Jev checks (`both`).
44
44
  VALID_KINDS = ("regex", "jev", "both")
45
45
 
46
- #: The Jev modes a deployment may set. `local` stops FailproofAI Cloud
47
- #: overriding the machine's own mode (the server stores it as no value).
48
- JEV_MODES = ("off", "observe", "enforce", "local")
49
-
50
46
  #: The server's (and the machine's) cap on Jev declarations per policy version.
51
47
  MAX_JEV_DECLARATIONS = 24
52
48
  AGENT_INTEGRATIONS = frozenset({
@@ -120,22 +116,6 @@ class DeployPlan:
120
116
  removed: List[PolicyRef]
121
117
  changed: List[Tuple[PolicyRef, PolicyRef]]
122
118
  unchanged: List[PolicyRef]
123
- #: `--jev-mode` as given (`off|observe|enforce|local`), or None when the
124
- #: command said nothing about Jev — which the server reads as "keep it".
125
- jev_mode: Optional[str] = None
126
- #: The mode FailproofAI Cloud sets on the machine now (None = local).
127
- jev_mode_before: Optional[str] = None
128
-
129
- @property
130
- def jev_mode_after(self) -> Optional[str]:
131
- """What the machine's Cloud-set mode will be (None = local)."""
132
- if self.jev_mode is None:
133
- return self.jev_mode_before
134
- return None if self.jev_mode == "local" else self.jev_mode
135
-
136
- @property
137
- def jev_mode_changes(self) -> bool:
138
- return self.jev_mode is not None and self.jev_mode_after != self.jev_mode_before
139
119
 
140
120
  @property
141
121
  def set_changes(self) -> bool:
@@ -144,7 +124,7 @@ class DeployPlan:
144
124
 
145
125
  @property
146
126
  def is_noop(self) -> bool:
147
- return not (self.set_changes or self.jev_mode_changes)
127
+ return not self.set_changes
148
128
 
149
129
  def to_dict(self) -> Dict[str, object]:
150
130
  out: Dict[str, object] = {
@@ -157,13 +137,6 @@ class DeployPlan:
157
137
  "unchanged": [p.to_dict() for p in self.unchanged],
158
138
  "noop": self.is_noop,
159
139
  }
160
- if self.jev_mode is not None:
161
- # `local` on both sides: the wire word, not the stored NULL.
162
- out["jevMode"] = {
163
- "from": self.jev_mode_before or "local",
164
- "to": self.jev_mode_after or "local",
165
- "changed": self.jev_mode_changes,
166
- }
167
140
  return out
168
141
 
169
142
 
@@ -237,8 +210,6 @@ def plan_deploy(
237
210
  replace: Optional[Sequence[str]] = None,
238
211
  latest: Optional[Dict[str, int]] = None,
239
212
  disabled: Optional[set] = None,
240
- jev_mode: Optional[str] = None,
241
- current_jev_mode: Optional[str] = None,
242
213
  kinds: Optional[Dict[Tuple[str, int], str]] = None,
243
214
  targets: Sequence[str] = (),
244
215
  all_agents: Sequence[str] = (),
@@ -249,16 +220,13 @@ def plan_deploy(
249
220
  with "these as well" has no single obvious reading, and guessing one would
250
221
  be guessing about somebody's fleet.
251
222
 
252
- `jev_mode` is `--jev-mode`; `current_jev_mode` what Cloud sets now (None =
253
- local). `kinds` maps `(id, version)` to the version's kind, so an `observe`
254
- effect on a Jev-only version — which the server refuses, because a Jev
255
- rollout is observed through the Jev MODE — is refused here, before a plan is
256
- drawn and confirmed for a write that cannot happen.
223
+ There is no Jev mode to set: FailproofAI Cloud derives a machine's Jev mode
224
+ from what is deployed to it (enforce while any `jev`/`both` policy is,
225
+ off otherwise), and a Jev rollout is observed per policy with an `observe`
226
+ effect like any other. `kinds` (`(id, version)` → kind) is accepted for
227
+ callers that already have it.
257
228
  """
258
- if jev_mode is not None and jev_mode not in JEV_MODES:
259
- raise RefUsageError(
260
- f"--jev-mode {jev_mode!r} is not one of {', '.join(JEV_MODES)}"
261
- )
229
+ del kinds
262
230
  latest = latest or {}
263
231
  current_list = list(current or [])
264
232
  current_map = {p.id: p for p in current_list}
@@ -314,13 +282,6 @@ def plan_deploy(
314
282
  result_map[pid] = dataclass_replace(result_map[pid], agent_targets=grouped.get(pid))
315
283
 
316
284
  result = sorted(result_map.values(), key=lambda p: p.id)
317
- for ref in result:
318
- if ref.effect == "observe" and (kinds or {}).get((ref.id, ref.version)) == "jev":
319
- raise RefUsageError(
320
- f"{ref.id}@{ref.version} is a Jev policy, and an effect does not apply to Jev "
321
- "checks — deploy it as enforce and use --jev-mode observe to watch Jev without "
322
- "it deciding anything"
323
- )
324
285
  added, removed, changed, unchanged = [], [], [], []
325
286
  for pid, ref in sorted(result_map.items()):
326
287
  was = current_map.get(pid)
@@ -342,16 +303,9 @@ def plan_deploy(
342
303
  removed=removed,
343
304
  changed=changed,
344
305
  unchanged=unchanged,
345
- jev_mode=jev_mode,
346
- jev_mode_before=current_jev_mode,
347
306
  )
348
307
 
349
308
 
350
- def jev_mode_after(mode: str) -> Optional[str]:
351
- """What a `--jev-mode`/`jev-mode` word leaves stored: `local` is None (Cloud stops overriding)."""
352
- return None if mode == "local" else mode
353
-
354
-
355
309
  def version_kinds(policies: Iterable[PolicyVersion]) -> Dict[Tuple[str, int], str]:
356
310
  """`{(id, version): kind}` for every published version."""
357
311
  return {(p.id, p.version): p.kind for p in policies}