failproofai 1.0.7 → 1.0.8
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 +4 -4
- 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 +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- 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 +4 -4
- 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 +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- 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 +8 -7
- 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 +9 -7
- 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 +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +6 -6
- 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 +11 -10
- 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 +8 -7
- 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 +6 -6
- 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 +7 -6
- 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 +52 -8
- package/.next/standalone/.next/server/app/settings/page.js +12 -7
- 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/{[externals]__1msfs-h._.js → [externals]__19_pzeq._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__1_bftcl._.js → [externals]__20kzpkf._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +22 -0
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
- package/.next/standalone/.next/server/chunks/{_1c3k-8x._.js → _1q5i8mb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/lib_16xa545._.js +3 -0
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +5 -0
- package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +4 -2
- package/.next/standalone/.next/server/chunks/ssr/[externals]__0ohnuzs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__02npjtd._.js → [root-of-the-server]__056wjo4._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06pflha._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +22 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0p-5p8u._.js → [root-of-the-server]__0n0xg95._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__013jr2b._.js → [root-of-the-server]__0rwtwpm._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0da85px._.js → [root-of-the-server]__11mayhe._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12e7nhs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13t1zkw._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19evfi8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cxe_2_._.js → [root-of-the-server]__1mf3zp6._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ftmoxc._.js → [root-of-the-server]__1pprgri._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1p2otjt._.js → [root-of-the-server]__1q4p5b8._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_166t73i._.js → _0-vcssj._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_13kfn90._.js +23 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1es2j7i._.js → _1gb0ifp._.js} +5 -5
- package/.next/standalone/.next/server/chunks/ssr/{_1_qswah._.js → _1q46vxx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +13 -13
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +4 -2
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
- 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/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +67 -23
- package/.next/standalone/.next/static/chunks/06dnzbolj00mc.js +6 -0
- package/.next/standalone/.next/static/chunks/0bhidk90e-07f.css +2 -0
- package/.next/standalone/.next/static/chunks/{2mdh397ghgnvv.js → 0m-9d6yn9hx4j.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0__8a7m868fvf.js → 0wbjy0zo-m7is.js} +1 -1
- package/.next/standalone/.next/static/chunks/16f3fa-lx38hk.js +1 -0
- package/.next/standalone/.next/static/chunks/{0o6qlkgubtoex.js → 21uv-uusw329x.js} +1 -1
- package/.next/standalone/.next/static/chunks/2g2tki08kdhie.js +1 -0
- package/.next/standalone/.next/static/chunks/2qdpj67x6ifk_.js +69 -0
- package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
- package/.next/standalone/.next/static/chunks/3-nbtkhg9y-1j.js +1 -0
- package/.next/standalone/.next/static/chunks/355km0ihuqo1p.js +1 -0
- package/.next/standalone/.next/static/chunks/{13i7-9is-vhys.js → 3c3qbmosdjl6w.js} +1 -1
- package/.next/standalone/PROBE-FOLLOWUP.md +186 -0
- package/.next/standalone/app/actions/get-jev-config.ts +604 -0
- package/.next/standalone/app/actions/pack-actions.ts +12 -0
- package/.next/standalone/app/actions/update-jev-config.ts +569 -0
- package/.next/standalone/app/components/jev-notices.tsx +96 -0
- package/.next/standalone/app/policies/hooks-client.tsx +14 -3
- package/.next/standalone/app/settings/jev-panel.tsx +630 -0
- package/.next/standalone/app/settings/page.tsx +20 -1
- package/.next/standalone/app/settings/settings-client.tsx +27 -1
- package/.next/standalone/app/settings/settings.css +79 -0
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
- package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
- package/.next/standalone/package.json +10 -10
- package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- package/.next/standalone/sdk/typescript/CHANGELOG.md +18 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +0 -274
- package/.next/standalone/sdk/typescript/scripts/release.mjs +30 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +3 -2
- package/bin/failproofai.mjs +148 -4
- package/dist/cli.mjs +18599 -9478
- package/dist/index.js +19 -1
- package/dist/worker.mjs +10500 -3773
- package/package.json +10 -10
- package/pi-extension/index.ts +11 -0
- package/scripts/build-policy-pack.mjs +53 -2
- package/src/audit/features.ts +3 -2
- package/src/hooks/builtin-policies.ts +195 -8
- package/src/hooks/cloud-connection.ts +190 -1
- package/src/hooks/cloud-enrollment-cli.ts +57 -8
- package/src/hooks/cloud-introspect.ts +6 -0
- package/src/hooks/cloud-managed-policies.ts +22 -0
- package/src/hooks/configure-wizard.ts +26 -6
- package/src/hooks/custom-hooks-loader.ts +98 -10
- package/src/hooks/custom-hooks-registry.ts +45 -1
- package/src/hooks/effective-reviewers.ts +243 -0
- package/src/hooks/first-run-gate.ts +5 -0
- package/src/hooks/flush-cli.ts +35 -8
- package/src/hooks/fp-config.ts +266 -1
- package/src/hooks/fp-home.ts +23 -0
- package/src/hooks/fp-reset.ts +22 -4
- package/src/hooks/handler.ts +303 -7
- package/src/hooks/hook-activity-store.ts +118 -6
- package/src/hooks/hook-telemetry.ts +41 -0
- package/src/hooks/jev-activity.ts +385 -0
- package/src/hooks/jev-cli.ts +2172 -0
- package/src/hooks/jev-cloud-connection.ts +392 -0
- package/src/hooks/loader-utils.ts +6 -0
- package/src/hooks/manager.ts +45 -7
- package/src/hooks/pack-cli.ts +568 -37
- package/src/hooks/pack-failclosed.ts +3 -0
- package/src/hooks/pack-manifest.ts +489 -7
- package/src/hooks/pack-store.ts +194 -11
- package/src/hooks/policy-authority.ts +386 -0
- package/src/hooks/policy-catalog.ts +206 -0
- package/src/hooks/policy-evaluator.ts +963 -796
- package/src/hooks/policy-registry.ts +27 -1
- package/src/hooks/policy-reviewability.ts +258 -0
- package/src/hooks/policy-types.ts +128 -0
- package/src/hooks/semantic/combine.ts +581 -0
- package/src/hooks/semantic/compile.ts +176 -0
- package/src/hooks/semantic/decide.ts +513 -0
- package/src/hooks/semantic/envelope.ts +1255 -0
- package/src/hooks/semantic/evaluator.ts +556 -0
- package/src/hooks/semantic/facts.ts +387 -0
- package/src/hooks/semantic/intent.ts +1190 -0
- package/src/hooks/semantic/jev-client.ts +1155 -0
- package/src/hooks/semantic/jev-config.ts +1142 -0
- package/src/hooks/semantic/jev-review.ts +389 -0
- package/src/hooks/semantic/jev-stats.ts +289 -0
- package/src/hooks/semantic/jev-throttle.ts +427 -0
- package/src/hooks/semantic/pack-policies.ts +294 -0
- package/src/hooks/semantic/policies.ts +597 -0
- package/src/hooks/semantic/precondition-names.ts +60 -0
- package/src/hooks/semantic/preconditions.ts +58 -0
- package/src/hooks/semantic/redact.ts +2937 -0
- package/src/hooks/semantic/session-root.ts +116 -0
- package/src/hooks/semantic/types.ts +162 -0
- package/src/hooks/semver-precedence.ts +128 -0
- package/src/hooks/tui.ts +4 -0
- package/src/hooks/types.ts +1 -1
- package/src/hooks/worker-server.ts +121 -27
- package/src/index.ts +6 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_r2rbg._.js +0 -24
- package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bd3mje._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +0 -22
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/_0h_douw._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1-i_gzc._.js +0 -23
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
- package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +0 -1
- package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +0 -6
- package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
- package/.next/standalone/.next/static/chunks/2klitrtzpaoe0.js +0 -1
- package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +0 -2
- package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +0 -1
- package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +0 -69
- package/.next/standalone/.next/static/chunks/43ufqrz8qo3h-.js +0 -1
- /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → 5yKM8NoTOpUI0MoQl8eo5}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → 5yKM8NoTOpUI0MoQl8eo5}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → 5yKM8NoTOpUI0MoQl8eo5}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Jev panel on /settings — the dashboard's half of `failproofai jev setup`.
|
|
5
|
+
*
|
|
6
|
+
* ## What it is for
|
|
7
|
+
*
|
|
8
|
+
* Jev is the optional second evaluation tier. It is on when, and only when, a
|
|
9
|
+
* valid `~/.failproofai/jev.json` exists; without it the hook path is byte for
|
|
10
|
+
* byte what it was before. So this panel has two jobs, in this order:
|
|
11
|
+
*
|
|
12
|
+
* 1. say whether it is on, in which mode, against which provider and model,
|
|
13
|
+
* how much of the enabled policy set it is allowed to clear, and — once it
|
|
14
|
+
* has run — how often it fell back to the regex engine. A panel that only
|
|
15
|
+
* took input would leave "is this thing working" unanswerable from the
|
|
16
|
+
* dashboard.
|
|
17
|
+
* 2. take the endpoint and the token.
|
|
18
|
+
*
|
|
19
|
+
* Each fact is stated ONCE. The status block says nothing the form below it
|
|
20
|
+
* already holds: the endpoint and the account id are form values, and printing
|
|
21
|
+
* them above the fields put a per-account URL — and the id inside it — twice on
|
|
22
|
+
* one screen. The config file's path and permission bits are gone for a
|
|
23
|
+
* different reason: nobody repairs a 0644 from a browser, and the loader's own
|
|
24
|
+
* refusal already names the file when it matters.
|
|
25
|
+
*
|
|
26
|
+
* It is a full-width cell in the same hairline console as the scheduled-audit
|
|
27
|
+
* panel, using the same tokens, the same `.btn-press` action and the same
|
|
28
|
+
* `toast()` on success. No new colour and no new control that did not already
|
|
29
|
+
* exist on this page, except a `<select>` — five providers do not fit a switch.
|
|
30
|
+
*
|
|
31
|
+
* ## The token is write-only
|
|
32
|
+
*
|
|
33
|
+
* The field is always blank on load, whatever is stored. `JevSettingsView`
|
|
34
|
+
* carries presence and nothing else, so the panel can say "configured" — or
|
|
35
|
+
* that the key is read from the environment — and no more; the value itself
|
|
36
|
+
* never leaves the machine's filesystem, and neither does a fragment of it.
|
|
37
|
+
* Leaving the field empty on save KEEPS the key where it is — the stored one, or
|
|
38
|
+
* the environment's for a config that takes it from there — which the server
|
|
39
|
+
* decides, not this component.
|
|
40
|
+
*
|
|
41
|
+
* The stored MODEL is shown the same way when it does not look like a model id,
|
|
42
|
+
* for the same reason: it is the one routing field someone can paste a key into.
|
|
43
|
+
* The panel gets a `JevModelView`, never the string, so there is nothing here to
|
|
44
|
+
* leak even by accident. It is display-only — the form does not offer the field,
|
|
45
|
+
* and a save leaves whatever is stored alone.
|
|
46
|
+
*
|
|
47
|
+
* Client-side validation here is a convenience only. Every rule — the URL
|
|
48
|
+
* scheme, plain http being refused outside shadow mode, cloudflare's account id
|
|
49
|
+
* — is enforced server-side by the same `validateJevConfig` the loader runs.
|
|
50
|
+
*
|
|
51
|
+
* ## FailproofAI Cloud
|
|
52
|
+
*
|
|
53
|
+
* When `jev.json` names the FailproofAI Cloud provider, the endpoint and the
|
|
54
|
+
* key are not this page's to edit: both come from the machine's connection
|
|
55
|
+
* (`config --token`). So the form is replaced by the two controls that are
|
|
56
|
+
* the owner's — an on/off switch and shadow/enforce — both through
|
|
57
|
+
* `setJevModeAction`, which rewrites `mode` and nothing else. "Off" there keeps
|
|
58
|
+
* the file (`mode: "off"`): deleting it would leave nothing on this page to
|
|
59
|
+
* switch back on — the only way to get the file back would be re-running
|
|
60
|
+
* `config --token` with the key.
|
|
61
|
+
*
|
|
62
|
+
* The connection itself — org, and whether the key carries Jev — is one row,
|
|
63
|
+
* read from `credentials.json` by the server with no network call; the key is
|
|
64
|
+
* never part of what this component receives.
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
|
|
68
|
+
import {
|
|
69
|
+
getJevSettingsAction,
|
|
70
|
+
type JevModelView,
|
|
71
|
+
type JevSettingsView,
|
|
72
|
+
} from "@/app/actions/get-jev-config";
|
|
73
|
+
import {
|
|
74
|
+
removeJevConfigAction,
|
|
75
|
+
saveJevConfigAction,
|
|
76
|
+
setJevModeAction,
|
|
77
|
+
} from "@/app/actions/update-jev-config";
|
|
78
|
+
import { toast } from "@/app/components/toast";
|
|
79
|
+
|
|
80
|
+
const PROVIDERS = [
|
|
81
|
+
{ value: "typesafe", label: "typesafe" },
|
|
82
|
+
{ value: "openrouter", label: "openrouter" },
|
|
83
|
+
{ value: "vercel", label: "vercel ai gateway" },
|
|
84
|
+
{ value: "cloudflare", label: "cloudflare workers ai" },
|
|
85
|
+
{ value: "custom", label: "custom endpoint" },
|
|
86
|
+
] as const;
|
|
87
|
+
|
|
88
|
+
const MODES = [
|
|
89
|
+
{ value: "enforce", label: "enforce — jev's verdict counts" },
|
|
90
|
+
{ value: "shadow", label: "shadow — log only, regex decides" },
|
|
91
|
+
{ value: "off", label: "off — keep this config, don't ask jev" },
|
|
92
|
+
] as const;
|
|
93
|
+
|
|
94
|
+
/** The provider whose endpoint and key come from the FailproofAI Cloud connection. */
|
|
95
|
+
const CLOUD_PROVIDER = "failproofai";
|
|
96
|
+
|
|
97
|
+
/** What a provider is called on this page. */
|
|
98
|
+
function providerName(provider: string | null): string {
|
|
99
|
+
return provider === CLOUD_PROVIDER ? "FailproofAI Cloud" : (provider ?? "");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The connection row: org, and whether the key carries Jev. No key, ever. */
|
|
103
|
+
function fmtConnection(cloud: JevSettingsView["cloud"] | undefined): string {
|
|
104
|
+
if (!cloud || !cloud.connected) {
|
|
105
|
+
return cloud?.jev === "yes" ? "a jev key is stored, but no connection is" : "not connected";
|
|
106
|
+
}
|
|
107
|
+
const where = cloud.org ?? cloud.host ?? "connected";
|
|
108
|
+
const jev =
|
|
109
|
+
cloud.jev === "yes"
|
|
110
|
+
? "key carries jev"
|
|
111
|
+
: cloud.jev === "refused"
|
|
112
|
+
? "jev key refused — credentials.json is not owner-only"
|
|
113
|
+
: "no jev key stored";
|
|
114
|
+
return `connected to ${where} · ${jev}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** "24h" / "7d" / "30m", for the stats window. */
|
|
118
|
+
function fmtWindow(ms: number): string {
|
|
119
|
+
const h = ms / 3_600_000;
|
|
120
|
+
if (Number.isInteger(h) && h >= 1) return h % 24 === 0 ? `${h / 24}d` : `${h}h`;
|
|
121
|
+
return `${Math.max(1, Math.round(ms / 60_000))}m`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** A rate as a whole percent. 0.0417 → "4%". */
|
|
125
|
+
function fmtRate(rate: number): string {
|
|
126
|
+
return `${Math.round(rate * 100)}%`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The model row. `withheld` is the case worth having a row for at all: the file
|
|
131
|
+
* holds something in that slot that does not look like a model id, which is
|
|
132
|
+
* what a key pasted one field off looks like, so it is described and not
|
|
133
|
+
* printed — and the server never sent it here to print. The loader's own reason
|
|
134
|
+
* and its fix are already on the page above, in `view.problem`.
|
|
135
|
+
*/
|
|
136
|
+
function fmtModel(model: JevModelView): string {
|
|
137
|
+
switch (model.kind) {
|
|
138
|
+
case "id":
|
|
139
|
+
return model.id;
|
|
140
|
+
case "withheld":
|
|
141
|
+
return "set to something that is not a model id — not shown here, in case it is a key";
|
|
142
|
+
default:
|
|
143
|
+
return "the provider's default";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
interface FormState {
|
|
148
|
+
provider: string;
|
|
149
|
+
baseUrl: string;
|
|
150
|
+
accountId: string;
|
|
151
|
+
mode: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function formFrom(view: JevSettingsView): FormState {
|
|
155
|
+
return {
|
|
156
|
+
// The form is the BYOK form: the Cloud provider is never one of its values.
|
|
157
|
+
provider: view.provider && view.provider !== CLOUD_PROVIDER ? view.provider : "typesafe",
|
|
158
|
+
baseUrl: view.baseUrl,
|
|
159
|
+
accountId: view.accountId,
|
|
160
|
+
mode: view.mode,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** One labelled control, laid out like the interval row above it. */
|
|
165
|
+
function Field({
|
|
166
|
+
id,
|
|
167
|
+
label,
|
|
168
|
+
hint,
|
|
169
|
+
children,
|
|
170
|
+
}: {
|
|
171
|
+
id: string;
|
|
172
|
+
label: string;
|
|
173
|
+
hint?: string;
|
|
174
|
+
children: ReactNode;
|
|
175
|
+
}) {
|
|
176
|
+
return (
|
|
177
|
+
<div className="set-field">
|
|
178
|
+
<label className="set-field-label" htmlFor={id}>
|
|
179
|
+
{label}
|
|
180
|
+
</label>
|
|
181
|
+
{children}
|
|
182
|
+
{hint ? <span className="set-hint">{hint}</span> : null}
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export default function JevPanel({ initial }: { initial: JevSettingsView | null }) {
|
|
188
|
+
const [view, setView] = useState<JevSettingsView | null>(initial);
|
|
189
|
+
const [form, setForm] = useState<FormState>(
|
|
190
|
+
initial ? formFrom(initial) : { provider: "typesafe", baseUrl: "", accountId: "", mode: "enforce" },
|
|
191
|
+
);
|
|
192
|
+
const [token, setToken] = useState("");
|
|
193
|
+
const [busy, setBusy] = useState(false);
|
|
194
|
+
const [problem, setProblem] = useState<string | null>(null);
|
|
195
|
+
/**
|
|
196
|
+
* The server's `needsToken`: the one refusal whose remedy is this field, so it
|
|
197
|
+
* is marked rather than leaving the person to match the message to a control.
|
|
198
|
+
* The refusals that no token can settle deliberately do not set it.
|
|
199
|
+
*/
|
|
200
|
+
const [needsToken, setNeedsToken] = useState(false);
|
|
201
|
+
const mounted = useRef(true);
|
|
202
|
+
/**
|
|
203
|
+
* Whether the user has touched the form since the last read. A focus refresh
|
|
204
|
+
* must not overwrite a half-typed endpoint with what is still on disk — the
|
|
205
|
+
* page re-reads on every `visibilitychange`, which includes the tab hide that
|
|
206
|
+
* happens when somebody alt-tabs to their password manager mid-edit.
|
|
207
|
+
*/
|
|
208
|
+
const dirty = useRef(false);
|
|
209
|
+
|
|
210
|
+
const reload = useCallback(async () => {
|
|
211
|
+
try {
|
|
212
|
+
const next = await getJevSettingsAction();
|
|
213
|
+
if (!mounted.current) return;
|
|
214
|
+
setView(next);
|
|
215
|
+
if (!dirty.current) setForm(formFrom(next));
|
|
216
|
+
} catch {
|
|
217
|
+
// Leave what is on screen: it describes real machine state, and blanking
|
|
218
|
+
// it would report something less true than what is already there.
|
|
219
|
+
}
|
|
220
|
+
}, []);
|
|
221
|
+
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
mounted.current = true;
|
|
224
|
+
void reload();
|
|
225
|
+
return () => {
|
|
226
|
+
mounted.current = false;
|
|
227
|
+
};
|
|
228
|
+
}, [reload]);
|
|
229
|
+
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
// Same reason as the scheduled-audit panel: `failproofai jev setup` in a
|
|
232
|
+
// terminal changes the same file, and a page left open would otherwise keep
|
|
233
|
+
// showing the old state until it was reloaded by hand.
|
|
234
|
+
const onFocus = () => void reload();
|
|
235
|
+
window.addEventListener("focus", onFocus);
|
|
236
|
+
document.addEventListener("visibilitychange", onFocus);
|
|
237
|
+
return () => {
|
|
238
|
+
window.removeEventListener("focus", onFocus);
|
|
239
|
+
document.removeEventListener("visibilitychange", onFocus);
|
|
240
|
+
};
|
|
241
|
+
}, [reload]);
|
|
242
|
+
|
|
243
|
+
const set = useCallback(<K extends keyof FormState>(key: K, value: FormState[K]) => {
|
|
244
|
+
dirty.current = true;
|
|
245
|
+
setForm((f) => ({ ...f, [key]: value }));
|
|
246
|
+
}, []);
|
|
247
|
+
|
|
248
|
+
const onSave = useCallback(async () => {
|
|
249
|
+
setBusy(true);
|
|
250
|
+
setProblem(null);
|
|
251
|
+
try {
|
|
252
|
+
// The form does not offer a model, so it says nothing about one: the
|
|
253
|
+
// server keeps a stored model when the provider is unchanged, and drops it
|
|
254
|
+
// for the new provider's default when it is not — the same rule `jev setup`
|
|
255
|
+
// applies. `JevConfigInput` has no model field for this to get wrong.
|
|
256
|
+
const res = await saveJevConfigAction({
|
|
257
|
+
provider: form.provider,
|
|
258
|
+
baseUrl: form.baseUrl,
|
|
259
|
+
accountId: form.accountId,
|
|
260
|
+
mode: form.mode,
|
|
261
|
+
token,
|
|
262
|
+
});
|
|
263
|
+
if (!res.ok) {
|
|
264
|
+
setProblem(res.problem);
|
|
265
|
+
setNeedsToken(res.needsToken === true);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
dirty.current = false;
|
|
269
|
+
setNeedsToken(false);
|
|
270
|
+
setToken("");
|
|
271
|
+
setView(res.view);
|
|
272
|
+
setForm(formFrom(res.view));
|
|
273
|
+
toast(res.view.on ? "jev is on." : "saved.");
|
|
274
|
+
} catch {
|
|
275
|
+
setProblem("could not save that.");
|
|
276
|
+
} finally {
|
|
277
|
+
setBusy(false);
|
|
278
|
+
}
|
|
279
|
+
}, [form, token]);
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* The FailproofAI Cloud switch: `off`, `shadow` or `enforce`, and nothing
|
|
283
|
+
* else about the file changes (see `setJevModeAction`).
|
|
284
|
+
*/
|
|
285
|
+
const onMode = useCallback(async (mode: "off" | "shadow" | "enforce") => {
|
|
286
|
+
setBusy(true);
|
|
287
|
+
setProblem(null);
|
|
288
|
+
try {
|
|
289
|
+
const res = await setJevModeAction(mode);
|
|
290
|
+
if (!res.ok) {
|
|
291
|
+
setProblem(res.problem);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
dirty.current = false;
|
|
295
|
+
setView(res.view);
|
|
296
|
+
setForm(formFrom(res.view));
|
|
297
|
+
toast(mode === "off" ? "jev is off. hooks run the regex policies." : `jev is on, in ${mode} mode.`);
|
|
298
|
+
} catch {
|
|
299
|
+
setProblem("could not switch that.");
|
|
300
|
+
} finally {
|
|
301
|
+
setBusy(false);
|
|
302
|
+
}
|
|
303
|
+
}, []);
|
|
304
|
+
|
|
305
|
+
const onRemove = useCallback(async () => {
|
|
306
|
+
setBusy(true);
|
|
307
|
+
setProblem(null);
|
|
308
|
+
setNeedsToken(false);
|
|
309
|
+
try {
|
|
310
|
+
const res = await removeJevConfigAction();
|
|
311
|
+
if (!res.ok) {
|
|
312
|
+
setProblem(res.problem);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
dirty.current = false;
|
|
316
|
+
setToken("");
|
|
317
|
+
setView(res.view);
|
|
318
|
+
setForm(formFrom(res.view));
|
|
319
|
+
toast("jev is off. hooks run the regex policies.");
|
|
320
|
+
} catch {
|
|
321
|
+
setProblem("could not turn that off.");
|
|
322
|
+
} finally {
|
|
323
|
+
setBusy(false);
|
|
324
|
+
}
|
|
325
|
+
}, []);
|
|
326
|
+
|
|
327
|
+
const configured = view !== null && view.status !== "absent";
|
|
328
|
+
const cloudRoute = view?.provider === CLOUD_PROVIDER;
|
|
329
|
+
const stored = view?.token;
|
|
330
|
+
const tokenHint = needsToken
|
|
331
|
+
? "needed for this save — type the token for this endpoint"
|
|
332
|
+
: stored
|
|
333
|
+
? stored.source === "cloud"
|
|
334
|
+
? "from this machine's FailproofAI Cloud connection"
|
|
335
|
+
: stored.source === "env"
|
|
336
|
+
? "configured, read from the environment — leave blank to keep it"
|
|
337
|
+
: // Presence and the keep rule, which is everything this field's reader
|
|
338
|
+
// has to decide. Naming the last four characters of the stored key
|
|
339
|
+
// here put that fragment on the page a second time, next to the input
|
|
340
|
+
// it would be re-typed into.
|
|
341
|
+
"configured — leave blank to keep it"
|
|
342
|
+
: // The file names the environment as the key's source and the variable is
|
|
343
|
+
// unset in this process. Leaving the field blank keeps that arrangement,
|
|
344
|
+
// which is not the same thing as "there is no key" — the endpoint and the
|
|
345
|
+
// mode are editable from here without one.
|
|
346
|
+
view?.status === "key-missing"
|
|
347
|
+
? "read from the environment, which is not set in this shell — leave blank to keep it that way"
|
|
348
|
+
: "sent as a bearer token; stored owner-only at 0600";
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<div className="set-cell set-jev">
|
|
352
|
+
<h2 className="set-cell-title">jev · semantic evaluation</h2>
|
|
353
|
+
|
|
354
|
+
<div className="set-row set-row-main">
|
|
355
|
+
<div className="set-row-copy">
|
|
356
|
+
<span className={view?.on ? "set-strong" : "set-dim"}>
|
|
357
|
+
{view === null
|
|
358
|
+
? "reading…"
|
|
359
|
+
: view.on
|
|
360
|
+
? `on. every judged command is also asked of ${providerName(view.provider)}.`
|
|
361
|
+
: view.status === "off"
|
|
362
|
+
? "off — switched off. hooks run the regex policies exactly as before."
|
|
363
|
+
: view.status === "not-connected"
|
|
364
|
+
? "off — this machine is not connected to FailproofAI Cloud. hooks run the regex policies."
|
|
365
|
+
: view.status === "key-lacks-jev"
|
|
366
|
+
? "off — no jev key is stored for this machine's FailproofAI Cloud connection. hooks run the regex policies."
|
|
367
|
+
: view.status === "key-missing"
|
|
368
|
+
? // The CLI's "off in this shell": the key is read by whatever
|
|
369
|
+
// runs the hook, and this server's environment is not that.
|
|
370
|
+
"off for this dashboard — FAILPROOFAI_JEV_API_KEY is not set in its environment. hooks run the regex policies wherever it is unset, including under the daemon; a session that sets it still consults jev."
|
|
371
|
+
: "off. hooks run the regex policies exactly as before."}
|
|
372
|
+
</span>
|
|
373
|
+
<span className="set-dim">
|
|
374
|
+
{view?.on
|
|
375
|
+
? view.mode === "shadow"
|
|
376
|
+
? "shadow: jev's answers are logged, the regex result is what gets enforced."
|
|
377
|
+
: "enforce: jev's answers can clear a reviewable deny."
|
|
378
|
+
: cloudRoute
|
|
379
|
+
? "through FailproofAI Cloud, on your org's plan — no endpoint or token of your own."
|
|
380
|
+
: "a second opinion on the commands the regex policies flag. needs your own endpoint and token."}
|
|
381
|
+
</span>
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
{/* Where requests GO is not in this block, deliberately. The editable
|
|
386
|
+
field below holds it, and for Cloudflare that URL is
|
|
387
|
+
`/accounts/<id>/ai/run` — so a read-only row above the form printed the
|
|
388
|
+
same per-account address, and the account id inside it, a second time
|
|
389
|
+
on one screen. The path of the config file and its mode were here too;
|
|
390
|
+
nobody acts on either from a browser. */}
|
|
391
|
+
{/* The FailproofAI Cloud connection, on every state: whether this
|
|
392
|
+
machine could run Jev through the Cloud is worth knowing before it
|
|
393
|
+
does. Org and a yes/no, never a key. */}
|
|
394
|
+
{view && (
|
|
395
|
+
<dl className="set-how-list set-jev-cloud">
|
|
396
|
+
<div className="set-how-row">
|
|
397
|
+
<dt className="set-how-label">FailproofAI Cloud connection</dt>
|
|
398
|
+
<dd className="set-how-body">{fmtConnection(view.cloud)}</dd>
|
|
399
|
+
</div>
|
|
400
|
+
</dl>
|
|
401
|
+
)}
|
|
402
|
+
|
|
403
|
+
{view && configured && (
|
|
404
|
+
<dl className="set-how-list">
|
|
405
|
+
{cloudRoute && (
|
|
406
|
+
<div className="set-how-row">
|
|
407
|
+
<dt className="set-how-label">provider</dt>
|
|
408
|
+
<dd className="set-how-body">
|
|
409
|
+
{view.cloud?.host ? `FailproofAI Cloud · ${view.cloud.host}` : "FailproofAI Cloud"}
|
|
410
|
+
</dd>
|
|
411
|
+
</div>
|
|
412
|
+
)}
|
|
413
|
+
<div className="set-how-row">
|
|
414
|
+
<dt className="set-how-label">model</dt>
|
|
415
|
+
<dd className="set-how-body">{fmtModel(view.model)}</dd>
|
|
416
|
+
</div>
|
|
417
|
+
{/* Presence, and where it came from. Not four characters of it: that
|
|
418
|
+
is a recognisable fragment of a live credential on a page with no
|
|
419
|
+
authentication, and it tells the reader nothing they cannot get by
|
|
420
|
+
re-pasting the key. The server no longer sends it either. */}
|
|
421
|
+
<div className="set-how-row">
|
|
422
|
+
<dt className="set-how-label">token</dt>
|
|
423
|
+
<dd className="set-how-body">
|
|
424
|
+
{stored
|
|
425
|
+
? stored.source === "cloud"
|
|
426
|
+
? "FailproofAI Cloud connection"
|
|
427
|
+
: stored.source === "env"
|
|
428
|
+
? "from FAILPROOFAI_JEV_API_KEY"
|
|
429
|
+
: "configured"
|
|
430
|
+
: cloudRoute
|
|
431
|
+
? view.cloud?.connected
|
|
432
|
+
? "none stored for this connection"
|
|
433
|
+
: "none — this machine is not connected"
|
|
434
|
+
: "none stored"}
|
|
435
|
+
</dd>
|
|
436
|
+
</div>
|
|
437
|
+
{view.reviewable && (
|
|
438
|
+
<div className="set-how-row">
|
|
439
|
+
<dt className="set-how-label">reviewable</dt>
|
|
440
|
+
{/* The count, in the server's words — the same sentence
|
|
441
|
+
`failproofai jev status` prints, because the two surfaces must
|
|
442
|
+
not disagree about what Jev is allowed to clear. */}
|
|
443
|
+
<dd className="set-how-body">{view.reviewable.summary}</dd>
|
|
444
|
+
</div>
|
|
445
|
+
)}
|
|
446
|
+
{view.stats && view.stats.total > 0 && (
|
|
447
|
+
<div className="set-how-row">
|
|
448
|
+
<dt className="set-how-label">fallbacks</dt>
|
|
449
|
+
{/* One string, not an interpolated fragment: a sentence split
|
|
450
|
+
across text nodes is a sentence a screen reader and a test both
|
|
451
|
+
have to reassemble. */}
|
|
452
|
+
<dd className="set-how-body">
|
|
453
|
+
{`${fmtRate(view.stats.fallbackRate)} of ${view.stats.total} calls in the last ${fmtWindow(view.stats.windowMs)} fell back to the regex engine.`}
|
|
454
|
+
</dd>
|
|
455
|
+
</div>
|
|
456
|
+
)}
|
|
457
|
+
</dl>
|
|
458
|
+
)}
|
|
459
|
+
|
|
460
|
+
{view?.problem && (
|
|
461
|
+
<p className="set-warn">
|
|
462
|
+
{view.problem}
|
|
463
|
+
{view.fix ? ` — ${view.fix}` : ""}
|
|
464
|
+
</p>
|
|
465
|
+
)}
|
|
466
|
+
|
|
467
|
+
{/* Jev is on, answering, and cannot clear a single verdict. Nothing else
|
|
468
|
+
on this page shows it: a policy set with no authority marks behaves
|
|
469
|
+
exactly like a healthy one until you notice that nothing is ever
|
|
470
|
+
cleared. The server sends the cause and the remedy together. */}
|
|
471
|
+
{view?.reviewable?.problem && <p className="set-warn">{view.reviewable.problem}</p>}
|
|
472
|
+
|
|
473
|
+
<div className="set-rule" />
|
|
474
|
+
|
|
475
|
+
{cloudRoute ? (
|
|
476
|
+
<>
|
|
477
|
+
<div className="set-jev-fields">
|
|
478
|
+
<Field
|
|
479
|
+
id="jev-cloud-mode"
|
|
480
|
+
label="mode"
|
|
481
|
+
hint="the endpoint and key come from this machine's FailproofAI Cloud connection"
|
|
482
|
+
>
|
|
483
|
+
<select
|
|
484
|
+
id="jev-cloud-mode"
|
|
485
|
+
className="set-select"
|
|
486
|
+
value={view?.mode === "enforce" ? "enforce" : "shadow"}
|
|
487
|
+
disabled={busy || view?.mode === "off"}
|
|
488
|
+
onChange={(e) => void onMode(e.target.value === "enforce" ? "enforce" : "shadow")}
|
|
489
|
+
>
|
|
490
|
+
{MODES.filter((m) => m.value !== "off").map((m) => (
|
|
491
|
+
<option key={m.value} value={m.value}>
|
|
492
|
+
{m.label}
|
|
493
|
+
</option>
|
|
494
|
+
))}
|
|
495
|
+
</select>
|
|
496
|
+
</Field>
|
|
497
|
+
</div>
|
|
498
|
+
|
|
499
|
+
{problem && <p className="set-warn">{problem}</p>}
|
|
500
|
+
|
|
501
|
+
<div className="set-actions set-jev-actions">
|
|
502
|
+
<button
|
|
503
|
+
type="button"
|
|
504
|
+
className="btn btn-press"
|
|
505
|
+
disabled={busy}
|
|
506
|
+
onClick={() => void onMode(view?.mode === "off" ? "shadow" : "off")}
|
|
507
|
+
>
|
|
508
|
+
{busy ? "[ switching… ]" : view?.mode === "off" ? "[ turn jev on ]" : "[ turn jev off ]"}
|
|
509
|
+
</button>
|
|
510
|
+
</div>
|
|
511
|
+
</>
|
|
512
|
+
) : (
|
|
513
|
+
<>
|
|
514
|
+
<div className="set-jev-fields">
|
|
515
|
+
<Field id="jev-provider" label="provider">
|
|
516
|
+
<select
|
|
517
|
+
id="jev-provider"
|
|
518
|
+
className="set-select"
|
|
519
|
+
value={form.provider}
|
|
520
|
+
disabled={busy}
|
|
521
|
+
onChange={(e) => set("provider", e.target.value)}
|
|
522
|
+
>
|
|
523
|
+
{PROVIDERS.map((p) => (
|
|
524
|
+
<option key={p.value} value={p.value}>
|
|
525
|
+
{p.label}
|
|
526
|
+
</option>
|
|
527
|
+
))}
|
|
528
|
+
</select>
|
|
529
|
+
</Field>
|
|
530
|
+
|
|
531
|
+
<Field
|
|
532
|
+
id="jev-url"
|
|
533
|
+
label="endpoint url"
|
|
534
|
+
hint={[
|
|
535
|
+
form.provider === "custom"
|
|
536
|
+
? "required — https, or http to localhost in shadow mode"
|
|
537
|
+
: "leave blank for the provider's own api",
|
|
538
|
+
// The stored query string is not sent to this page (see
|
|
539
|
+
// `baseUrlView` in `get-jev-config.ts`), so the field holds the URL
|
|
540
|
+
// without it. Said out loud, because the alternative is a field that
|
|
541
|
+
// silently disagrees with the file and a save that looks like it
|
|
542
|
+
// kept something the person was never shown.
|
|
543
|
+
view?.baseUrlQueryWithheld
|
|
544
|
+
? "the stored url's query string is not shown here — leave the url as it is to keep it"
|
|
545
|
+
: null,
|
|
546
|
+
]
|
|
547
|
+
.filter(Boolean)
|
|
548
|
+
.join(" · ")}
|
|
549
|
+
>
|
|
550
|
+
<input
|
|
551
|
+
id="jev-url"
|
|
552
|
+
type="text"
|
|
553
|
+
className="set-text"
|
|
554
|
+
inputMode="url"
|
|
555
|
+
autoComplete="off"
|
|
556
|
+
spellCheck={false}
|
|
557
|
+
placeholder={form.provider === "custom" ? "https://…" : "the provider's own api"}
|
|
558
|
+
value={form.baseUrl}
|
|
559
|
+
disabled={busy}
|
|
560
|
+
onChange={(e) => set("baseUrl", e.target.value)}
|
|
561
|
+
/>
|
|
562
|
+
</Field>
|
|
563
|
+
|
|
564
|
+
{form.provider === "cloudflare" && (
|
|
565
|
+
<Field id="jev-account" label="account id" hint="32 lowercase hex characters">
|
|
566
|
+
<input
|
|
567
|
+
id="jev-account"
|
|
568
|
+
type="text"
|
|
569
|
+
className="set-text"
|
|
570
|
+
autoComplete="off"
|
|
571
|
+
spellCheck={false}
|
|
572
|
+
value={form.accountId}
|
|
573
|
+
disabled={busy}
|
|
574
|
+
onChange={(e) => set("accountId", e.target.value)}
|
|
575
|
+
/>
|
|
576
|
+
</Field>
|
|
577
|
+
)}
|
|
578
|
+
|
|
579
|
+
<Field id="jev-token" label="token" hint={tokenHint}>
|
|
580
|
+
<input
|
|
581
|
+
id="jev-token"
|
|
582
|
+
type="password"
|
|
583
|
+
className="set-text"
|
|
584
|
+
autoComplete="off"
|
|
585
|
+
spellCheck={false}
|
|
586
|
+
placeholder={stored ? "•••• kept" : ""}
|
|
587
|
+
value={token}
|
|
588
|
+
disabled={busy}
|
|
589
|
+
aria-invalid={needsToken || undefined}
|
|
590
|
+
onChange={(e) => {
|
|
591
|
+
dirty.current = true;
|
|
592
|
+
setToken(e.target.value);
|
|
593
|
+
}}
|
|
594
|
+
/>
|
|
595
|
+
</Field>
|
|
596
|
+
|
|
597
|
+
<Field id="jev-mode" label="mode">
|
|
598
|
+
<select
|
|
599
|
+
id="jev-mode"
|
|
600
|
+
className="set-select"
|
|
601
|
+
value={form.mode}
|
|
602
|
+
disabled={busy}
|
|
603
|
+
onChange={(e) => set("mode", e.target.value)}
|
|
604
|
+
>
|
|
605
|
+
{MODES.map((m) => (
|
|
606
|
+
<option key={m.value} value={m.value}>
|
|
607
|
+
{m.label}
|
|
608
|
+
</option>
|
|
609
|
+
))}
|
|
610
|
+
</select>
|
|
611
|
+
</Field>
|
|
612
|
+
</div>
|
|
613
|
+
|
|
614
|
+
{problem && <p className="set-warn">{problem}</p>}
|
|
615
|
+
|
|
616
|
+
<div className="set-actions set-jev-actions">
|
|
617
|
+
<button type="button" className="btn btn-press" disabled={busy} onClick={() => void onSave()}>
|
|
618
|
+
{busy ? "[ saving… ]" : configured ? "[ save changes ]" : "[ turn jev on ]"}
|
|
619
|
+
</button>
|
|
620
|
+
{configured && (
|
|
621
|
+
<button type="button" className="set-link" disabled={busy} onClick={() => void onRemove()}>
|
|
622
|
+
turn jev off
|
|
623
|
+
</button>
|
|
624
|
+
)}
|
|
625
|
+
</div>
|
|
626
|
+
</>
|
|
627
|
+
)}
|
|
628
|
+
</div>
|
|
629
|
+
);
|
|
630
|
+
}
|
|
@@ -4,6 +4,11 @@ import {
|
|
|
4
4
|
getScheduledAuditAction,
|
|
5
5
|
type ScheduledAuditView,
|
|
6
6
|
} from "@/app/actions/get-scheduled-audit";
|
|
7
|
+
import {
|
|
8
|
+
getJevSettingsAction,
|
|
9
|
+
type JevSettingsView,
|
|
10
|
+
} from "@/app/actions/get-jev-config";
|
|
11
|
+
import JevPanel from "./jev-panel";
|
|
7
12
|
import SettingsClient from "./settings-client";
|
|
8
13
|
|
|
9
14
|
export const metadata: Metadata = {
|
|
@@ -44,5 +49,19 @@ export default async function SettingsPage() {
|
|
|
44
49
|
// here would replace a page that can explain itself with an error boundary
|
|
45
50
|
// that cannot.
|
|
46
51
|
}
|
|
47
|
-
|
|
52
|
+
// Same reasoning as above, for the same reason it matters more here: this
|
|
53
|
+
// panel's headline fact is whether a second evaluator is switched on, and a
|
|
54
|
+
// client-side load would paint "off" first and correct itself after.
|
|
55
|
+
// `getJevSettingsAction` does not throw, but a failed read must not take the
|
|
56
|
+
// scheduled-audit panel down with it either.
|
|
57
|
+
let jev: JevSettingsView | null = null;
|
|
58
|
+
try {
|
|
59
|
+
jev = await getJevSettingsAction();
|
|
60
|
+
} catch {
|
|
61
|
+
// Left null; the panel reads as "reading…" until the client load lands.
|
|
62
|
+
}
|
|
63
|
+
// The panel is composed in rather than imported by `SettingsClient`, so the
|
|
64
|
+
// scheduled-audit console and the Jev console share a layout without sharing
|
|
65
|
+
// a dependency — see the `jevPanel` prop.
|
|
66
|
+
return <SettingsClient initial={initial} jevPanel={<JevPanel initial={jev} />} />;
|
|
48
67
|
}
|