failproofai 1.0.12-beta.0 → 1.0.12-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +22 -22
- 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 +1 -1
- 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 +4 -4
- 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 +7 -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]__0-ha5ydqq0mws._.js → [externals]__0vt9i6fqinjne._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__20xe05hm5u8_1._.js → [root-of-the-server]__0f_4g-vazrvxv._.js} +1 -1
- package/.next/standalone/.next/server/chunks/_0hf5q4xzyg40j._.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/_19011-u44143j._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +2 -2
- package/.next/standalone/.next/server/chunks/{_0kc-piux3hady._.js → _1y590mjzpkbos._.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 +5 -0
- package/.next/standalone/.next/server/chunks/src_hooks_builtin-policies_ts_1yq5ybgx1v98e._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3h94wnn._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_daemon-client_ts_0il_yu1zc0291._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn_4twyh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ts0fg5yih1c1._.js → [root-of-the-server]__00l2u6j86cb8y._.js} +2 -2
- 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]__1_xbg2kx30wp-._.js → [root-of-the-server]__0naevg4bcoh54._.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]__0vcyntk4snf0f._.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/{_1gb0ifpkowwub._.js → _0ouu886szh6yj._.js} +10 -9
- 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/{_1ve8y151ua7qx._.js → _1fw0_nzkae5ao._.js} +2 -2
- 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_00jxj8foxqz5t.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_06kzv9ddabn3_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4l1tzrm._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_daemon-client_ts_0k41_i7jg6ndv._.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/{0pjxnp927y7_j.js → 0ay__-d2h1xb5.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2znt1784j9gd9.js → 0tbm3vjm62h-2.js} +1 -1
- package/.next/standalone/.next/static/chunks/1c_jf6g5wnslk.js +1 -0
- package/.next/standalone/.next/static/chunks/{3tn1bzk094e8e.js → 1my3-5p47t7r-.js} +1 -1
- package/.next/standalone/.next/static/chunks/20py8k1gd_i1m.js +1 -0
- 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/2o6d37j425uci.js +1 -0
- package/.next/standalone/.next/static/chunks/40oworn_-x12p.js +1 -0
- package/.next/standalone/.next/static/chunks/{2qz5piiir-qqm.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 +32 -0
- package/.next/standalone/fp-cloud-cli/README.md +40 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +91 -12
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +78 -16
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +84 -18
- package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +124 -6
- package/.next/standalone/fp-cloud-cli/fp_cli/models.py +89 -6
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +189 -20
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +8 -5
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +2 -2
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +14 -7
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +527 -0
- package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +46 -0
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +23 -0
- package/.next/standalone/hermes-plugin/README.md +6 -4
- package/.next/standalone/hermes-plugin/__init__.py +27 -0
- package/.next/standalone/hermes-plugin/client.py +5 -1
- 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/bin/failproofai.mjs +17 -1
- package/dist/cli.mjs +3507 -1250
- package/dist/worker.mjs +1779 -169
- package/hermes-plugin/README.md +6 -4
- package/hermes-plugin/__init__.py +27 -0
- package/hermes-plugin/client.py +5 -1
- package/hermes-plugin/plugin.yaml +1 -1
- package/package.json +11 -10
- package/src/audit/open-browser.ts +6 -7
- package/src/hooks/agent-roster.ts +180 -0
- package/src/hooks/agent-skill.ts +203 -0
- package/src/hooks/agent-targets.ts +60 -0
- package/src/hooks/cloud-connection.ts +91 -45
- package/src/hooks/cloud-enrollment-cli.ts +7 -4
- package/src/hooks/cloud-login.ts +6 -3
- package/src/hooks/cloud-managed-policies.ts +430 -20
- package/src/hooks/cloud-policy-errors.ts +481 -0
- package/src/hooks/codex-system-hooks.ts +10 -4
- package/src/hooks/config-status.ts +10 -4
- package/src/hooks/configure-wizard.ts +54 -26
- package/src/hooks/custom-hooks-loader.ts +26 -3
- package/src/hooks/daemon-client.ts +9 -4
- package/src/hooks/daemon-service.ts +2 -1
- package/src/hooks/delivery-health.ts +47 -2
- package/src/hooks/effective-reviewers.ts +36 -1
- package/src/hooks/fp-config.ts +19 -0
- package/src/hooks/fp-home.ts +6 -0
- package/src/hooks/handler.ts +255 -19
- package/src/hooks/health-probe.ts +15 -0
- package/src/hooks/hook-activity-store.ts +7 -1
- package/src/hooks/integrations.ts +36 -14
- package/src/hooks/jev-activity.ts +117 -1
- package/src/hooks/jev-cli.ts +196 -1
- package/src/hooks/jev-cloud-connection.ts +92 -0
- package/src/hooks/manager.ts +174 -34
- package/src/hooks/pack-failclosed.ts +2 -0
- package/src/hooks/pack-manifest.ts +8 -1
- package/src/hooks/policy-evaluator.ts +22 -1
- package/src/hooks/policy-registry.ts +3 -2
- package/src/hooks/policy-reviewability.ts +23 -5
- package/src/hooks/semantic/cloud-jev-health.ts +227 -0
- package/src/hooks/semantic/cloud-jev.ts +868 -0
- package/src/hooks/semantic/combine.ts +26 -1
- package/src/hooks/semantic/jev-client.ts +19 -2
- package/src/hooks/semantic/jev-config.ts +179 -30
- package/src/hooks/semantic/jev-review.ts +217 -10
- package/src/hooks/semantic/pack-policies.ts +2 -1
- package/src/hooks/semantic/types.ts +8 -0
- package/src/hooks/session-pause-cli.ts +1 -1
- package/src/hooks/tui.ts +13 -4
- package/src/hooks/uninstall-cli.ts +12 -1
- package/src/hooks/worker-server.ts +5 -1
- package/.next/standalone/.next/server/chunks/src_hooks_1aveq0uhs2t1b._.js +0 -5
- package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7cyqlb2._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo55j16x._.js +0 -5
- package/.next/standalone/.next/static/chunks/0a-ccsp3xmypz.js +0 -1
- package/.next/standalone/.next/static/chunks/13jv-xvabeb62.js +0 -6
- package/.next/standalone/.next/static/chunks/2ank2u3apy8m6.js +0 -1
- package/.next/standalone/.next/static/chunks/2meqb9kygrncb.js +0 -1
- package/.next/standalone/.next/static/chunks/2ohktp_-lkv3x.js +0 -1
- package/.next/standalone/.next/static/chunks/2py0eldhwz8ay.js +0 -1
- /package/.next/standalone/.next/static/{SNr7o_-G4kNBQTR-iLZMM → LHCtFxVG0mdvd4lsXAOFC}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{SNr7o_-G4kNBQTR-iLZMM → LHCtFxVG0mdvd4lsXAOFC}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{SNr7o_-G4kNBQTR-iLZMM → LHCtFxVG0mdvd4lsXAOFC}/_ssgManifest.js +0 -0
|
@@ -236,7 +236,7 @@ import type { PolicyAuthority } from "../policy-types";
|
|
|
236
236
|
// shape check the row's own `jevModel` does. Importing them makes a rename a
|
|
237
237
|
// compile error here. `jev-activity.ts` is pure (no node imports, no semantic
|
|
238
238
|
// modules), so this costs the hook path nothing.
|
|
239
|
-
import { JEV_MODEL_RE, JEV_REASON_REQUEST_CUT } from "../jev-activity";
|
|
239
|
+
import { JEV_MODEL_RE, JEV_REASON_REQUEST_CUT, type JevCheckRecord } from "../jev-activity";
|
|
240
240
|
|
|
241
241
|
export type Decision = "allow" | "deny" | "instruct";
|
|
242
242
|
export type JevMode = "observe" | "enforce";
|
|
@@ -287,6 +287,11 @@ export type JevReview =
|
|
|
287
287
|
origin?: { packId: string; packVersion?: string };
|
|
288
288
|
/** Semantic policies whose questions were in the request that was answered. */
|
|
289
289
|
asked: readonly string[];
|
|
290
|
+
/**
|
|
291
|
+
* What each asked check concluded (`jevChecks` on the activity row).
|
|
292
|
+
* Recorded only; nothing here decides from it. Absent: not recorded.
|
|
293
|
+
*/
|
|
294
|
+
checks?: readonly JevCheckRecord[];
|
|
290
295
|
/**
|
|
291
296
|
* Of `asked`, the ones whose answer lets a reviewable regex verdict be
|
|
292
297
|
* cleared: `none`, `overridden`, or a deny the human's task softened to
|
|
@@ -330,6 +335,14 @@ export type JevReview =
|
|
|
330
335
|
* / `request-cut`.
|
|
331
336
|
*/
|
|
332
337
|
requestCut: boolean;
|
|
338
|
+
/**
|
|
339
|
+
* What the checks of an `observe` FailproofAI Cloud assignment would have
|
|
340
|
+
* done. Recorded on the row's `observed` list, never applied: they are in
|
|
341
|
+
* neither `asked` nor `notDenied`, so they clear nothing either.
|
|
342
|
+
*/
|
|
343
|
+
observedCloud?: ReadonlyArray<ObservedCloudVerdict>;
|
|
344
|
+
/** The `observe` Cloud assignments' checks that were asked (names); never in `asked`. */
|
|
345
|
+
observedAsked?: readonly string[];
|
|
333
346
|
/**
|
|
334
347
|
* Null when nothing had to be sent (no semantic policy applied), or when
|
|
335
348
|
* the answer came from the throttle's cache: a hit's ~0 ms is not a
|
|
@@ -358,6 +371,8 @@ export interface TwoTierReview {
|
|
|
358
371
|
* network instead of holding every hook on the machine behind it.
|
|
359
372
|
*/
|
|
360
373
|
releaseRegistry?: () => void;
|
|
374
|
+
/** The review goes to FailproofAI Cloud (its Jev mode asked), not a local `jev.json`. */
|
|
375
|
+
cloud?: boolean;
|
|
361
376
|
/** Effective authority + the reviewing semantic policies (empty unless reviewable). */
|
|
362
377
|
authorityOf(p: { name: string; authority?: PolicyAuthority; reviewedBy?: string[] }): {
|
|
363
378
|
authority: PolicyAuthority;
|
|
@@ -380,6 +395,7 @@ export interface JevActivityFields {
|
|
|
380
395
|
jevLatencyMs?: number;
|
|
381
396
|
jevModel?: string;
|
|
382
397
|
jevMode: JevMode;
|
|
398
|
+
jevChecks?: JevCheckRecord[];
|
|
383
399
|
}
|
|
384
400
|
|
|
385
401
|
/**
|
|
@@ -389,6 +405,14 @@ export interface JevActivityFields {
|
|
|
389
405
|
* and pack policies already use, so FailproofAI Cloud's policy page counts it
|
|
390
406
|
* with no change on its side.
|
|
391
407
|
*/
|
|
408
|
+
/** One observe Cloud assignment's would-have (see `JevReview.observedCloud`). */
|
|
409
|
+
export interface ObservedCloudVerdict {
|
|
410
|
+
cloudPolicyId: string;
|
|
411
|
+
cloudVersion: number;
|
|
412
|
+
decision: "deny" | "instruct";
|
|
413
|
+
reason: string;
|
|
414
|
+
}
|
|
415
|
+
|
|
392
416
|
export interface ObserveVerdict {
|
|
393
417
|
/** `semantic/<check>` — the name enforce mode would have attributed it to. */
|
|
394
418
|
policyName: string;
|
|
@@ -518,6 +542,7 @@ export function combineTwoTier(
|
|
|
518
542
|
...(review.latencyMs !== null ? { jevLatencyMs: review.latencyMs } : {}),
|
|
519
543
|
...(review.model ? { jevModel: review.model } : {}),
|
|
520
544
|
jevMode: mode,
|
|
545
|
+
...(review.checks ? { jevChecks: [...review.checks] } : {}),
|
|
521
546
|
};
|
|
522
547
|
|
|
523
548
|
if (mode === "observe") {
|
|
@@ -469,7 +469,7 @@ async function postJson(url: string, bearer: string, body: unknown, signal: Abor
|
|
|
469
469
|
body: JSON.stringify(body),
|
|
470
470
|
signal,
|
|
471
471
|
// Never followed. The configured URL is the one `validateBaseUrl` checked
|
|
472
|
-
// (https, or loopback http
|
|
472
|
+
// (https, or loopback http); a redirect would hand the
|
|
473
473
|
// answer — the thing that can clear a deny — to an origin nobody checked,
|
|
474
474
|
// plain http included. No provider redirects this POST.
|
|
475
475
|
redirect: "manual",
|
|
@@ -536,6 +536,14 @@ export interface NativeTransportOptions {
|
|
|
536
536
|
allowUnreported?: boolean;
|
|
537
537
|
/** The base URL came from a connection, not from the person (FailproofAI Cloud). */
|
|
538
538
|
derivedBase?: boolean;
|
|
539
|
+
/**
|
|
540
|
+
* FailproofAI Cloud: the reply's `cloud` block (its verdict for the Cloud
|
|
541
|
+
* checks deployed to this machine, CONTRACT C10.4) is carried through as
|
|
542
|
+
* `JevResponse.cloud`, unvalidated — `parseCloudReply` in `cloud-jev.ts`
|
|
543
|
+
* validates it field by field. A reply that carries one may come without
|
|
544
|
+
* `answers` (Cloud's short-circuit asked nothing), which reads as none.
|
|
545
|
+
*/
|
|
546
|
+
cloudReply?: boolean;
|
|
539
547
|
}
|
|
540
548
|
|
|
541
549
|
/**
|
|
@@ -545,7 +553,12 @@ export interface NativeTransportOptions {
|
|
|
545
553
|
*/
|
|
546
554
|
function normalizeNative(body: unknown, sentModel: string, opts: NativeTransportOptions): JevResponse {
|
|
547
555
|
if (!body || typeof body !== "object" || Array.isArray(body)) throw new JevError("malformed", "Jev returned no object");
|
|
548
|
-
const b = body as { model?: unknown; answers?: unknown; usage?: unknown; error?: unknown };
|
|
556
|
+
const b = body as { model?: unknown; answers?: unknown; usage?: unknown; error?: unknown; cloud?: unknown };
|
|
557
|
+
const cloud = opts.cloudReply && b.cloud !== undefined ? { cloud: b.cloud } : {};
|
|
558
|
+
if ((typeof b.answers !== "object" || b.answers === null) && "cloud" in cloud && b.error === undefined) {
|
|
559
|
+
// Cloud's short-circuit: nothing was asked, so there is nothing to answer.
|
|
560
|
+
return { model: typeof b.model === "string" ? scrubSecret(b.model, opts.apiKey) : "", answers: {}, ...cloud };
|
|
561
|
+
}
|
|
549
562
|
if (typeof b.answers !== "object" || b.answers === null) {
|
|
550
563
|
// Gateways sometimes report an upstream failure inside a 200.
|
|
551
564
|
if (b.error !== undefined) {
|
|
@@ -576,6 +589,7 @@ function normalizeNative(body: unknown, sentModel: string, opts: NativeTransport
|
|
|
576
589
|
answers: b.answers as JevResponse["answers"],
|
|
577
590
|
...(usage ? { usage } : {}),
|
|
578
591
|
...(unverified ? { modelUnverified: true } : {}),
|
|
592
|
+
...cloud,
|
|
579
593
|
};
|
|
580
594
|
}
|
|
581
595
|
|
|
@@ -1146,6 +1160,9 @@ export function transportForConfig(input: JevConfig): { transport: JevTransport;
|
|
|
1146
1160
|
// `readAnswers` then holds the reported id to the 1.13 family.
|
|
1147
1161
|
allowUnreported: false,
|
|
1148
1162
|
derivedBase: true,
|
|
1163
|
+
// Cloud's verdict for its own checks, when the request asked for
|
|
1164
|
+
// it (a `cloud` block, CONTRACT C10.3).
|
|
1165
|
+
cloudReply: true,
|
|
1149
1166
|
}),
|
|
1150
1167
|
),
|
|
1151
1168
|
via: "failproofai",
|
|
@@ -89,8 +89,8 @@
|
|
|
89
89
|
*/
|
|
90
90
|
import { closeSync, constants as fsConstants, fstatSync, openSync, readSync, statSync } from "node:fs";
|
|
91
91
|
import { dirname } from "node:path";
|
|
92
|
-
import { readJevCloudCredential, type JevCloudCredential } from "../fp-config";
|
|
93
|
-
import { jevConfigFile } from "../fp-home";
|
|
92
|
+
import { readConfig, readCredentials, readJevCloudCredential, type JevCloudCredential } from "../fp-config";
|
|
93
|
+
import { configFile, credentialsFile, jevConfigFile } from "../fp-home";
|
|
94
94
|
|
|
95
95
|
export type { JevCloudCredential } from "../fp-config";
|
|
96
96
|
|
|
@@ -179,6 +179,15 @@ export const JEV_API_KEY_ENV = "FAILPROOFAI_JEV_API_KEY";
|
|
|
179
179
|
* the evaluator; the two are pinned equal by a test.
|
|
180
180
|
*/
|
|
181
181
|
export const JEV_CONFIG_DEFAULT_TIMEOUT_MS = 3_000;
|
|
182
|
+
/**
|
|
183
|
+
* How long one call waits for FailproofAI Cloud when Cloud's Jev mode asks
|
|
184
|
+
* (CONTRACT C10.2). Longer than the local default above: the call makes one
|
|
185
|
+
* more hop (Cloud selects this machine's checks, forwards to Jev, and decides
|
|
186
|
+
* them) before it answers. A local BYOK `jev.json` keeps its own `timeoutMs`
|
|
187
|
+
* or the default; this one is fixed, because under a Cloud mode `jev.json` is
|
|
188
|
+
* not read at all. Past it, the regex decides alone, as for any fallback.
|
|
189
|
+
*/
|
|
190
|
+
export const CLOUD_JEV_TIMEOUT_MS = 5_000;
|
|
182
191
|
/** Bounds on `timeoutMs`. Every millisecond of it can be added to a tool call. */
|
|
183
192
|
export const MIN_JEV_TIMEOUT_MS = 100;
|
|
184
193
|
export const MAX_JEV_TIMEOUT_MS = 10_000;
|
|
@@ -318,8 +327,7 @@ function credentialQueryProblem(url: URL): string | null {
|
|
|
318
327
|
* An endpoint base URL: https, or http to a loopback host only (a local proxy).
|
|
319
328
|
* No credentials in the URL — not as userinfo, not as a query parameter — and no
|
|
320
329
|
* fragment, because a key belongs in the key field, where it is sent as a bearer
|
|
321
|
-
* and never printed.
|
|
322
|
-
* only in observe mode.
|
|
330
|
+
* and never printed. Loopback http is accepted in every mode.
|
|
323
331
|
*/
|
|
324
332
|
export function validateBaseUrl(raw: unknown): ValidationResult<string> {
|
|
325
333
|
if (typeof raw !== "string" || raw.trim() === "") return { ok: false, problem: "baseUrl must be a non-empty string" };
|
|
@@ -474,14 +482,6 @@ export function providerHostConflict(provider: string, url: string): string | nu
|
|
|
474
482
|
: null;
|
|
475
483
|
}
|
|
476
484
|
|
|
477
|
-
function isPlainHttp(url: string): boolean {
|
|
478
|
-
try {
|
|
479
|
-
return new URL(url).protocol === "http:";
|
|
480
|
-
} catch {
|
|
481
|
-
return false;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
485
|
/**
|
|
486
486
|
* The origin a bearer token would be sent to: https, or http to a loopback host
|
|
487
487
|
* — the same schemes `validateBaseUrl` admits. Null for anything else, so a
|
|
@@ -618,7 +618,15 @@ export function validateJevConfig(
|
|
|
618
618
|
};
|
|
619
619
|
}
|
|
620
620
|
|
|
621
|
-
|
|
621
|
+
// The FailproofAI Cloud provider waits as long as a Cloud Jev mode does (B9):
|
|
622
|
+
// the same extra hop, and `jev setup --provider failproofai` printing 3000 ms
|
|
623
|
+
// misstated what FailproofAI Cloud is given.
|
|
624
|
+
const cfg: JevConfig = {
|
|
625
|
+
provider: kind,
|
|
626
|
+
apiKey,
|
|
627
|
+
mode: DEFAULT_JEV_MODE,
|
|
628
|
+
timeoutMs: kind === JEV_CLOUD_PROVIDER ? CLOUD_JEV_TIMEOUT_MS : JEV_CONFIG_DEFAULT_TIMEOUT_MS,
|
|
629
|
+
};
|
|
622
630
|
|
|
623
631
|
if (o.baseUrl !== undefined) {
|
|
624
632
|
const r = validateBaseUrl(o.baseUrl);
|
|
@@ -680,23 +688,11 @@ export function validateJevConfig(
|
|
|
680
688
|
cfg.mode = mode;
|
|
681
689
|
}
|
|
682
690
|
|
|
683
|
-
// Plain http reaches only a loopback host (`validateBaseUrl`)
|
|
684
|
-
//
|
|
685
|
-
//
|
|
686
|
-
//
|
|
687
|
-
//
|
|
688
|
-
// nothing at all, so enforce is the one mode it is refused in.
|
|
689
|
-
if (cfg.baseUrl !== undefined && isPlainHttp(cfg.baseUrl) && cfg.mode === "enforce") {
|
|
690
|
-
return {
|
|
691
|
-
ok: false,
|
|
692
|
-
problem:
|
|
693
|
-
"plain http (to localhost) is accepted only with mode observe: in enforce mode Jev's answers can clear a deny, " +
|
|
694
|
-
"and while the local proxy is down any process on this machine could take its port and answer. " +
|
|
695
|
-
(cfg.provider === JEV_CLOUD_PROVIDER
|
|
696
|
-
? "Reconnect to an https FailproofAI Cloud URL (failproofai config --token <key> --url https://…), or keep mode observe"
|
|
697
|
-
: "Use https, or mode observe"),
|
|
698
|
-
};
|
|
699
|
-
}
|
|
691
|
+
// Plain http reaches only a loopback host (`validateBaseUrl`) and is
|
|
692
|
+
// accepted in every mode, enforce included, so a local stack can be tested
|
|
693
|
+
// as it runs in production. Risk: nothing authenticates a loopback server,
|
|
694
|
+
// so while it is down any process of this user could bind its port and
|
|
695
|
+
// answer; use https wherever that matters.
|
|
700
696
|
|
|
701
697
|
return { ok: true, value: cfg };
|
|
702
698
|
}
|
|
@@ -1090,6 +1086,159 @@ export function loadJevConfig(): JevConfig | null {
|
|
|
1090
1086
|
}
|
|
1091
1087
|
}
|
|
1092
1088
|
|
|
1089
|
+
// ── FailproofAI Cloud sets the mode ──────────────────────────────────────────
|
|
1090
|
+
|
|
1091
|
+
export interface CloudModeJevConfig {
|
|
1092
|
+
/**
|
|
1093
|
+
* The config to run with: provider `failproofai` on the Cloud Jev
|
|
1094
|
+
* credential, with Cloud's mode — or null: Jev does not run.
|
|
1095
|
+
*/
|
|
1096
|
+
config: JevConfig | null;
|
|
1097
|
+
/**
|
|
1098
|
+
* This machine's FailproofAI Cloud machine id (`credentials.json`
|
|
1099
|
+
* `cloud.machine_id`), sent in the request's `cloud` block so Cloud can find
|
|
1100
|
+
* the checks deployed to this machine. Null when there is none.
|
|
1101
|
+
*/
|
|
1102
|
+
machineId: string | null;
|
|
1103
|
+
/**
|
|
1104
|
+
* Why Jev cannot run in Cloud's mode, for `errors.json` — null when it runs,
|
|
1105
|
+
* and null for `off`, which is not a problem:
|
|
1106
|
+
* - `transcripts_disabled`: connected for decisions only (`--no-transcripts`);
|
|
1107
|
+
* - `jev_unconfigured`: no Cloud Jev credential at all;
|
|
1108
|
+
* - `jev_unconfigured: <detail>`: one exists and cannot be used.
|
|
1109
|
+
*/
|
|
1110
|
+
problem: string | null;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/** The contract's message for "Cloud set a Jev mode and this machine has no Cloud Jev credential". */
|
|
1114
|
+
export const JEV_UNCONFIGURED_PROBLEM = "jev_unconfigured";
|
|
1115
|
+
/** The contract's message for "Cloud set a Jev mode on a decisions-only connection" (CONTRACT C10.2). */
|
|
1116
|
+
export const TRANSCRIPTS_DISABLED_PROBLEM = "transcripts_disabled";
|
|
1117
|
+
|
|
1118
|
+
/**
|
|
1119
|
+
* The config to run when FailproofAI Cloud sets this machine's Jev mode
|
|
1120
|
+
* (`active.json` `jevMode`) to `observe` or `enforce` (CONTRACT C10.2).
|
|
1121
|
+
*
|
|
1122
|
+
* The provider is ALWAYS FailproofAI Cloud, on the Cloud Jev credential
|
|
1123
|
+
* (`credentials.json` `jev {url,key}`, only while its connection is still on
|
|
1124
|
+
* this machine), at `<cloud base>/enforcement/v1/jev` — the checks Cloud
|
|
1125
|
+
* deployed to this machine live there, and the verdict for them is computed
|
|
1126
|
+
* there. A local `jev.json` is NOT used while Cloud sets a mode, BYOK provider
|
|
1127
|
+
* or not: Cloud wins, a local `off` included.
|
|
1128
|
+
*
|
|
1129
|
+
* Jev does not run, and `problem` says why, when:
|
|
1130
|
+
* 1. the machine was connected for decisions only (`collector.sessions` is not
|
|
1131
|
+
* `true` in `config.json` — what `--no-transcripts` writes): the standing
|
|
1132
|
+
* rule is that such a connection never switches Jev on, because a Jev call
|
|
1133
|
+
* sends the tool call and the recent prompt;
|
|
1134
|
+
* 2. there is no Cloud Jev credential, or it cannot be used;
|
|
1135
|
+
* 3. there is no FailproofAI Cloud machine id to name.
|
|
1136
|
+
*
|
|
1137
|
+
* Asked twice per gate event on a machine whose Cloud mode asks — once for the
|
|
1138
|
+
* review, once for `errors.json` — and on every event in the warm worker, so
|
|
1139
|
+
* the answer is kept until an input changes (see `jevInputsVersion`).
|
|
1140
|
+
*
|
|
1141
|
+
* Never throws.
|
|
1142
|
+
*/
|
|
1143
|
+
export function loadJevConfigForCloudMode(mode: JevConfigMode): CloudModeJevConfig {
|
|
1144
|
+
if (mode === "off") return { config: null, machineId: null, problem: null };
|
|
1145
|
+
const key = `${mode}\u0000${jevInputsVersion()}`;
|
|
1146
|
+
if (cloudModeMemo?.key === key) return cloudModeMemo.value;
|
|
1147
|
+
const value = computeCloudModeConfig(mode);
|
|
1148
|
+
cloudModeMemo = { key, value };
|
|
1149
|
+
return value;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
let cloudModeMemo: { key: string; value: CloudModeJevConfig } | null = null;
|
|
1153
|
+
|
|
1154
|
+
/** Forget the memoised answer. Tests only. */
|
|
1155
|
+
export function _resetJevConfigMemoForTest(): void {
|
|
1156
|
+
cloudModeMemo = null;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Everything a Cloud-mode config load reads, as one version string: the
|
|
1161
|
+
* credentials file and its directory (whose modes the loader checks), and
|
|
1162
|
+
* `config.json` (the decisions-only switch), plus where those paths point. A
|
|
1163
|
+
* file's version is its device, inode, mode, size and modification and change
|
|
1164
|
+
* times to the nanosecond — an edit, a chmod and a replace-by-rename all move
|
|
1165
|
+
* it, and the change time cannot be set back by the user.
|
|
1166
|
+
*/
|
|
1167
|
+
function jevInputsVersion(): string {
|
|
1168
|
+
const credentials = credentialsFile();
|
|
1169
|
+
const config = configFile();
|
|
1170
|
+
return [
|
|
1171
|
+
process.platform,
|
|
1172
|
+
credentials,
|
|
1173
|
+
statVersion(credentials),
|
|
1174
|
+
statVersion(dirname(credentials)),
|
|
1175
|
+
config,
|
|
1176
|
+
statVersion(config),
|
|
1177
|
+
].join("\u0000");
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function statVersion(path: string): string {
|
|
1181
|
+
try {
|
|
1182
|
+
const st = statSync(path, { bigint: true });
|
|
1183
|
+
return `${st.dev}:${st.ino}:${st.mode}:${st.size}:${st.mtimeNs}:${st.ctimeNs}`;
|
|
1184
|
+
} catch (err) {
|
|
1185
|
+
return `-${(err as NodeJS.ErrnoException).code ?? "error"}`;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function computeCloudModeConfig(mode: JevConfigMode): CloudModeJevConfig {
|
|
1190
|
+
try {
|
|
1191
|
+
// Decisions only: checked first, because no credential would change it.
|
|
1192
|
+
if (readConfig().collector.sessions !== true) {
|
|
1193
|
+
return { config: null, machineId: null, problem: TRANSCRIPTS_DISABLED_PROBLEM };
|
|
1194
|
+
}
|
|
1195
|
+
const credential = readJevCloudCredential();
|
|
1196
|
+
if (credential.status === "absent") return { config: null, machineId: null, problem: JEV_UNCONFIGURED_PROBLEM };
|
|
1197
|
+
if (credential.status === "refused") {
|
|
1198
|
+
return {
|
|
1199
|
+
config: null,
|
|
1200
|
+
machineId: null,
|
|
1201
|
+
problem: `${JEV_UNCONFIGURED_PROBLEM}: the FailproofAI Cloud Jev credential is refused — ${credential.problem}`,
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
const machineId = readCredentials().cloud?.machineId || null;
|
|
1205
|
+
if (!machineId) {
|
|
1206
|
+
return {
|
|
1207
|
+
config: null,
|
|
1208
|
+
machineId: null,
|
|
1209
|
+
problem: `${JEV_UNCONFIGURED_PROBLEM}: this machine has no FailproofAI Cloud machine id (no policy connection in credentials.json)`,
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
const raw = {
|
|
1213
|
+
provider: JEV_CLOUD_PROVIDER,
|
|
1214
|
+
baseUrl: jevCloudBaseUrl(cloudBaseFor(credential.credential)),
|
|
1215
|
+
mode,
|
|
1216
|
+
timeoutMs: CLOUD_JEV_TIMEOUT_MS,
|
|
1217
|
+
};
|
|
1218
|
+
const r = validateJevConfig(raw, null, credential.credential);
|
|
1219
|
+
return r.ok
|
|
1220
|
+
? { config: r.value, machineId, problem: null }
|
|
1221
|
+
: { config: null, machineId, problem: `${JEV_UNCONFIGURED_PROBLEM}: the FailproofAI Cloud Jev credential — ${r.problem}` };
|
|
1222
|
+
} catch (err) {
|
|
1223
|
+
return { config: null, machineId: null, problem: `${JEV_UNCONFIGURED_PROBLEM}: ${err instanceof Error ? err.message : String(err)}` };
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* The Cloud base URL the Jev route hangs off: the connection's own URL when it
|
|
1229
|
+
* is on the credential's origin (so a Cloud served under a path prefix keeps
|
|
1230
|
+
* it, as `config --token` writes it), else the credential's origin.
|
|
1231
|
+
*/
|
|
1232
|
+
function cloudBaseFor(credential: JevCloudCredential): string {
|
|
1233
|
+
try {
|
|
1234
|
+
const cloud = readCredentials().cloud;
|
|
1235
|
+
if (cloud?.url && new URL(cloud.url).origin === new URL(credential.url).origin) return cloud.url;
|
|
1236
|
+
} catch {
|
|
1237
|
+
// Fall through to the origin.
|
|
1238
|
+
}
|
|
1239
|
+
return credential.url;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1093
1242
|
export interface JevConfigFileForUpdate {
|
|
1094
1243
|
raw: Record<string, unknown>;
|
|
1095
1244
|
/** The file's permission bits (null where they mean nothing). */
|