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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +2 -2
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0_em82s16zfym._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00l2u6j86cb8y._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pwj2h2efe5f-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0b78b0j8iyv6l._.js → [root-of-the-server]__1vofkb2oq7p7e._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bseae9u7g98r._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0mouhaflye1ro._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1a3tubds7s7ow._.js → _0ouu886szh6yj._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1fw0_nzkae5ao._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_20hn4gn87nsjc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr0vt64h._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
- 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
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0janj0i6dqko7._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589gp4p60j._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me658zqmh_._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/preview-props.json +3 -3
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
- package/.next/standalone/.next/static/chunks/{2-tf6_7u623kd.js → 0ay__-d2h1xb5.js} +1 -1
- package/.next/standalone/.next/static/chunks/{18433qz-8njv3.js → 0tbm3vjm62h-2.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1b32-afqacaen.js → 1c_jf6g5wnslk.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0_zfad4co-t2z.js → 1my3-5p47t7r-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0jutamwqz8pg5.js → 20py8k1gd_i1m.js} +1 -1
- package/.next/standalone/.next/static/chunks/21hckpmsc4g70.js +6 -0
- package/.next/standalone/.next/static/chunks/2hmex93ppj1ke.js +1 -0
- package/.next/standalone/.next/static/chunks/{42e8zab314654.js → 2o6d37j425uci.js} +1 -1
- package/.next/standalone/.next/static/chunks/40oworn_-x12p.js +1 -0
- package/.next/standalone/.next/static/chunks/{24w3x2da9sk49.js → 42xptz88mtq3x.js} +1 -1
- package/.next/standalone/app/actions/update-jev-config.ts +5 -3
- package/.next/standalone/app/settings/jev-panel.tsx +1 -1
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +9 -18
- package/.next/standalone/fp-cloud-cli/README.md +7 -9
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +1 -27
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +12 -216
- package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +7 -53
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +2 -71
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +1 -1
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +6 -7
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +32 -197
- package/.next/standalone/hermes-plugin/__init__.py +11 -0
- package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
- package/.next/standalone/package.json +11 -10
- package/.next/standalone/sdk/python/uv.lock +31 -8
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +1686 -612
- package/dist/worker.mjs +354 -49
- package/hermes-plugin/__init__.py +11 -0
- package/hermes-plugin/plugin.yaml +1 -1
- package/package.json +11 -10
- package/src/audit/open-browser.ts +24 -7
- package/src/hooks/agent-roster.ts +9 -1
- package/src/hooks/agent-skill.ts +203 -0
- package/src/hooks/cloud-connection.ts +27 -51
- package/src/hooks/cloud-introspect.ts +5 -2
- package/src/hooks/cloud-login.ts +248 -0
- package/src/hooks/cloud-managed-policies.ts +54 -4
- package/src/hooks/codex-system-hooks.ts +10 -4
- package/src/hooks/config-status.ts +9 -3
- package/src/hooks/configure-wizard.ts +356 -32
- package/src/hooks/delivery-health.ts +47 -2
- package/src/hooks/effective-reviewers.ts +14 -0
- package/src/hooks/fp-config.ts +19 -0
- package/src/hooks/handler.ts +21 -2
- package/src/hooks/hook-activity-store.ts +7 -1
- package/src/hooks/integrations.ts +18 -3
- package/src/hooks/jev-activity.ts +117 -1
- package/src/hooks/jev-cli.ts +7 -9
- package/src/hooks/manager.ts +80 -46
- package/src/hooks/policy-evaluator.ts +17 -0
- package/src/hooks/policy-reviewability.ts +4 -2
- package/src/hooks/semantic/cloud-jev.ts +219 -28
- package/src/hooks/semantic/combine.ts +26 -1
- package/src/hooks/semantic/jev-client.ts +1 -1
- package/src/hooks/semantic/jev-config.ts +6 -27
- package/src/hooks/semantic/jev-review.ts +90 -2
- package/src/hooks/tui.ts +280 -0
- package/src/hooks/uninstall-cli.ts +12 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0588st-tpiuia._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_128ebxr83nk6g._.js +0 -3
- package/.next/standalone/.next/static/chunks/114hbolvrv94e.js +0 -1
- package/.next/standalone/.next/static/chunks/1p-hsi58i-6h0.js +0 -6
- package/.next/standalone/.next/static/chunks/3-pku9qwv1yfu.js +0 -1
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → LHCtFxVG0mdvd4lsXAOFC}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → LHCtFxVG0mdvd4lsXAOFC}/_clientMiddlewareManifest.js +0 -0
- /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
|
|
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
|
|
479
|
-
*
|
|
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
|
|
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
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- `fp fleet
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
checks
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
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
|
-
|
|
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 /
|
|
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`
|
|
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
|
|
202
|
-
|
|
203
|
-
Jev
|
|
204
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 /
|
|
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
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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}
|