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
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
} from "./integrations";
|
|
63
63
|
import { INTEGRATION_TYPES, type IntegrationType, type HookScope } from "./types";
|
|
64
64
|
import { enforcingPolicyCount, installHooks, notEnforcingReason } from "./manager";
|
|
65
|
+
import { installAgentSkill } from "./agent-skill";
|
|
65
66
|
import {
|
|
66
67
|
CODEX_UNTRUSTED_NOTICE,
|
|
67
68
|
codexHookTrust,
|
|
@@ -1098,6 +1099,10 @@ export async function runConfigureWizard(
|
|
|
1098
1099
|
});
|
|
1099
1100
|
};
|
|
1100
1101
|
|
|
1102
|
+
// Whether the saved key was refused when the CONNECT step checked it — still
|
|
1103
|
+
// true at the end of the run if a login there created no key.
|
|
1104
|
+
let savedRefused = false;
|
|
1105
|
+
|
|
1101
1106
|
// The interactive CONNECT step. A machine with no connection, or whose saved
|
|
1102
1107
|
// key is refused, starts the browser login straight away — Tab or Esc there
|
|
1103
1108
|
// opens the other options. A machine that is connected with a working key gets
|
|
@@ -1108,6 +1113,7 @@ export async function runConfigureWizard(
|
|
|
1108
1113
|
? await transient("Checking your saved key…", () => checkCloudKey(saved.url, saved.token))
|
|
1109
1114
|
: null;
|
|
1110
1115
|
const savedBroken = verdict !== null && !verdict.ok && verdict.refused;
|
|
1116
|
+
savedRefused = savedBroken;
|
|
1111
1117
|
const keptPlan: CloudPlan =
|
|
1112
1118
|
verdict && verdict.ok
|
|
1113
1119
|
? { kind: "kept", how: "saved", verified: true }
|
|
@@ -1438,24 +1444,6 @@ export async function runConfigureWizard(
|
|
|
1438
1444
|
pruneOldDaemonBinaries();
|
|
1439
1445
|
}
|
|
1440
1446
|
|
|
1441
|
-
const connectedAfterPlan = connecting || cloud.kind === "kept";
|
|
1442
|
-
// Telemetry runs concurrently with the rest of apply (never rejects,
|
|
1443
|
-
// 5s-bounded) and is awaited before returning. `cli` carries the selected
|
|
1444
|
-
// agent ids, as it always has (and as hooks_installed does): a fixed list of
|
|
1445
|
-
// product ids, never user content. The agents_* fields add counts only.
|
|
1446
|
-
const applied = emit("configure_applied", {
|
|
1447
|
-
target: "user",
|
|
1448
|
-
scopes: ["user"],
|
|
1449
|
-
cli: selected,
|
|
1450
|
-
cli_count: selected.length,
|
|
1451
|
-
policy_count: (readScopedHooksConfig("user", cwd).enabledPolicies ?? []).length,
|
|
1452
|
-
connected: connecting,
|
|
1453
|
-
mode: connectedAfterPlan ? "cloud" : "oss",
|
|
1454
|
-
agents_selected: selected.length,
|
|
1455
|
-
agents_detected: plan.detected.size,
|
|
1456
|
-
agents_changed: added.length + removed.length > 0,
|
|
1457
|
-
});
|
|
1458
|
-
|
|
1459
1447
|
// Hooks, at user scope, for the chosen agents. Whatever policies are enabled
|
|
1460
1448
|
// there are carried through untouched: `replace: true` would otherwise switch
|
|
1461
1449
|
// off policies the user had turned on, and setup must never reduce
|
|
@@ -1483,6 +1471,11 @@ export async function runConfigureWizard(
|
|
|
1483
1471
|
cwd,
|
|
1484
1472
|
);
|
|
1485
1473
|
|
|
1474
|
+
// The failproofai skill, in every agent this machine has and any ticked
|
|
1475
|
+
// without being detected — traced or not, an agent asked about FailproofAI
|
|
1476
|
+
// should know it. A pointer to the skill online (see agent-skill.ts).
|
|
1477
|
+
installAgentSkill(INTEGRATION_TYPES.filter((id) => plan.detected.has(id) || selected.includes(id)));
|
|
1478
|
+
|
|
1486
1479
|
// Agents traced again start fresh (see `requestFreshStart`). Only on a
|
|
1487
1480
|
// rerun: its baseline is the selection the daemon actually filtered on. With
|
|
1488
1481
|
// no saved list (upgrade, first) the daemon was collecting every agent, so an
|
|
@@ -1503,6 +1496,7 @@ export async function runConfigureWizard(
|
|
|
1503
1496
|
let token: string | null = cloud.kind === "connect" ? cloud.token : null;
|
|
1504
1497
|
let orgLabel: string | undefined = cloud.kind === "connect" ? cloud.org : undefined;
|
|
1505
1498
|
let mintedName: string | undefined;
|
|
1499
|
+
let claimedOrg: LoginOrg | undefined;
|
|
1506
1500
|
if (cloud.kind === "login" && url) {
|
|
1507
1501
|
const claimed = await transient("Creating this machine's key…", () => claimLogin(url, cloud.pollSecret));
|
|
1508
1502
|
void emit("configure_cloud_login", { step: "claimed", ok: claimed.kind === "ok", reason: claimed.kind });
|
|
@@ -1510,6 +1504,7 @@ export async function runConfigureWizard(
|
|
|
1510
1504
|
token = claimed.key;
|
|
1511
1505
|
orgLabel = loginOrgLabel(claimed.org);
|
|
1512
1506
|
mintedName = claimed.keyName;
|
|
1507
|
+
claimedOrg = claimed.org;
|
|
1513
1508
|
} else {
|
|
1514
1509
|
hookLogWarn(`cloud login could not be completed: ${claimed.kind}`);
|
|
1515
1510
|
done.push(
|
|
@@ -1536,6 +1531,8 @@ export async function runConfigureWizard(
|
|
|
1536
1531
|
machineLabel,
|
|
1537
1532
|
// Both streams unless the run said `--no-transcripts`.
|
|
1538
1533
|
sessions: answers.noTranscripts !== true,
|
|
1534
|
+
// The org the login was approved into, for when introspect can't say.
|
|
1535
|
+
org: claimedOrg,
|
|
1539
1536
|
}),
|
|
1540
1537
|
);
|
|
1541
1538
|
connected = outcome.anyConfigured;
|
|
@@ -1566,12 +1563,38 @@ export async function runConfigureWizard(
|
|
|
1566
1563
|
updateConfig({ mode: "cloud" });
|
|
1567
1564
|
}
|
|
1568
1565
|
|
|
1569
|
-
// Logging in again on a connected machine, with
|
|
1570
|
-
//
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1566
|
+
// Logging in again on a connected machine, with nothing connected at the end
|
|
1567
|
+
// of it — no key, or a key `connectToCloud` could not use, which writes
|
|
1568
|
+
// nothing — changed nothing about the connection: the previous one is still
|
|
1569
|
+
// there (never removed from this step, decision D4) — but not as if it worked
|
|
1570
|
+
// when its key was refused.
|
|
1571
|
+
const keptOld = cloud.kind === "login" && !connected && Boolean(saved);
|
|
1572
|
+
if (keptOld) {
|
|
1573
|
+
done.push(
|
|
1574
|
+
savedRefused
|
|
1575
|
+
? " This machine keeps its previous connection, but its key no longer works. Run failproofai config again to log in."
|
|
1576
|
+
: " This machine keeps its previous connection.",
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
const cloudAfter = connected || cloud.kind === "kept" || keptOld;
|
|
1580
|
+
|
|
1581
|
+
// Sent once the connection's outcome is known — a login whose claim then
|
|
1582
|
+
// failed must not count as connected. Never rejects, 5s-bounded. `cli`
|
|
1583
|
+
// carries the selected agent ids, as it always has (and as hooks_installed
|
|
1584
|
+
// does): a fixed list of product ids, never user content. The agents_*
|
|
1585
|
+
// fields add counts only.
|
|
1586
|
+
await emit("configure_applied", {
|
|
1587
|
+
target: "user",
|
|
1588
|
+
scopes: ["user"],
|
|
1589
|
+
cli: selected,
|
|
1590
|
+
cli_count: selected.length,
|
|
1591
|
+
policy_count: policies.length,
|
|
1592
|
+
connected,
|
|
1593
|
+
mode: cloudAfter ? "cloud" : "oss",
|
|
1594
|
+
agents_selected: selected.length,
|
|
1595
|
+
agents_detected: plan.detected.size,
|
|
1596
|
+
agents_changed: added.length + removed.length > 0,
|
|
1597
|
+
});
|
|
1575
1598
|
// Only now — a completed apply — is the launcher considered "seen", so
|
|
1576
1599
|
// first-run onboarding stops offering itself on every command.
|
|
1577
1600
|
markLauncherSeen();
|
|
@@ -1588,7 +1611,7 @@ export async function runConfigureWizard(
|
|
|
1588
1611
|
done,
|
|
1589
1612
|
cwd,
|
|
1590
1613
|
agents: selected.length,
|
|
1591
|
-
connected:
|
|
1614
|
+
connected: cloudAfter,
|
|
1592
1615
|
codexTrust,
|
|
1593
1616
|
}),
|
|
1594
1617
|
);
|
|
@@ -1601,7 +1624,7 @@ export async function runConfigureWizard(
|
|
|
1601
1624
|
policies,
|
|
1602
1625
|
daemonInstalled,
|
|
1603
1626
|
connected,
|
|
1604
|
-
mode:
|
|
1627
|
+
mode: cloudAfter ? "cloud" : "oss",
|
|
1605
1628
|
agents: { selected, added, removed },
|
|
1606
1629
|
};
|
|
1607
1630
|
}
|
|
@@ -1717,6 +1740,11 @@ export async function checkCloudKey(
|
|
|
1717
1740
|
url: string,
|
|
1718
1741
|
key: string,
|
|
1719
1742
|
): Promise<{ ok: true; org?: string; note?: string } | { ok: false; refused: boolean; reason: string }> {
|
|
1743
|
+
// Checked here rather than trusted: a saved address comes from a file that
|
|
1744
|
+
// can be edited, and an http one to another host would carry the key in
|
|
1745
|
+
// the clear. Not a verdict on the key, so not `refused`.
|
|
1746
|
+
const target = validateCloudUrl(url);
|
|
1747
|
+
if (!target.ok) return { ok: false, refused: false, reason: `the address ${url} can't be used: ${target.reason}` };
|
|
1720
1748
|
const identity = await introspectKey(url, key);
|
|
1721
1749
|
if (identity.kind === "rejected") return { ok: false, refused: true, reason: "the server did not accept it" };
|
|
1722
1750
|
if (identity.kind === "unreachable") return { ok: false, refused: false, reason: identity.reason };
|
|
@@ -176,7 +176,7 @@ async function importWithDeadline(fileUrl: string): Promise<void> {
|
|
|
176
176
|
* Does NOT clear the registry — caller is responsible for that.
|
|
177
177
|
*/
|
|
178
178
|
export interface PolicyLoadFailure {
|
|
179
|
-
type: "module_not_found" | "syntax_error" | "load_timeout" | "runtime_error" | "path_missing";
|
|
179
|
+
type: "module_not_found" | "file_not_found" | "syntax_error" | "load_timeout" | "runtime_error" | "path_missing";
|
|
180
180
|
reason: string;
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -250,8 +250,13 @@ async function loadSingleFile(
|
|
|
250
250
|
return null;
|
|
251
251
|
} catch (err) {
|
|
252
252
|
const msg = err instanceof Error ? err.message : String(err);
|
|
253
|
-
|
|
253
|
+
// An import that names no module, apart from a file the policy itself
|
|
254
|
+
// reads at load (a `readFileSync` of its data): both are ENOENT underneath,
|
|
255
|
+
// but "module not found" sent people looking for a missing package (B13).
|
|
256
|
+
const errorType = /Cannot find module|MODULE_NOT_FOUND|Cannot find package/i.test(msg)
|
|
254
257
|
? "module_not_found"
|
|
258
|
+
: /ENOENT/i.test(msg)
|
|
259
|
+
? "file_not_found"
|
|
255
260
|
: /SyntaxError|Unexpected token/i.test(msg)
|
|
256
261
|
? "syntax_error"
|
|
257
262
|
: /timed out loading module/i.test(msg)
|
|
@@ -334,6 +339,13 @@ export interface LoadAllResult {
|
|
|
334
339
|
* Only present for a path more than one pack resolved to.
|
|
335
340
|
*/
|
|
336
341
|
packAliases: Map<string, string[]>;
|
|
342
|
+
/**
|
|
343
|
+
* Import failures of FailproofAI Cloud JS policies, keyed by Cloud policy id
|
|
344
|
+
* (every id behind a shared artifact). Fail-open like the rest of the Cloud
|
|
345
|
+
* read — nothing denies over them — but reported to Cloud in `errors.json`,
|
|
346
|
+
* so a deployment that is not actually enforcing says so.
|
|
347
|
+
*/
|
|
348
|
+
cloudFailures?: Map<string, PolicyLoadFailure>;
|
|
337
349
|
}
|
|
338
350
|
|
|
339
351
|
export function customPolicyId(file: string, name: string): string {
|
|
@@ -456,8 +468,12 @@ export async function loadAllCustomHooks(
|
|
|
456
468
|
// codebase exists to remove. It is also announced, so an operator can give the
|
|
457
469
|
// two policies distinguishable source instead of living with the merge.
|
|
458
470
|
const cloudManagedByPath = new Map<string, CloudManagedPolicyArtifact>();
|
|
471
|
+
/** Every Cloud policy id behind each artifact path, for `cloudFailures`. */
|
|
472
|
+
const cloudIdsByPath = new Map<string, string[]>();
|
|
473
|
+
const cloudFailures = new Map<string, PolicyLoadFailure>();
|
|
459
474
|
for (const policy of opts?.cloudManagedPolicies ?? []) {
|
|
460
475
|
const key = resolve(policy.path);
|
|
476
|
+
cloudIdsByPath.set(key, [...(cloudIdsByPath.get(key) ?? []), policy.id]);
|
|
461
477
|
const existing = cloudManagedByPath.get(key);
|
|
462
478
|
if (!existing) {
|
|
463
479
|
cloudManagedByPath.set(key, policy);
|
|
@@ -660,6 +676,9 @@ export async function loadAllCustomHooks(
|
|
|
660
676
|
if (failure && pack) {
|
|
661
677
|
for (const id of packIdsByPath.get(absPath) ?? [pack.id]) packFailures.set(id, failure);
|
|
662
678
|
}
|
|
679
|
+
if (failure && cloudManaged) {
|
|
680
|
+
for (const id of cloudIdsByPath.get(absPath) ?? [cloudManaged.id]) cloudFailures.set(id, failure);
|
|
681
|
+
}
|
|
663
682
|
for (const hook of getCustomHooks().slice(hooksBefore)) {
|
|
664
683
|
const tagged = hook as CustomHook & {
|
|
665
684
|
__policyId?: string;
|
|
@@ -680,6 +699,10 @@ export async function loadAllCustomHooks(
|
|
|
680
699
|
}
|
|
681
700
|
} else {
|
|
682
701
|
hookLogWarn(`custom policy path not found: ${absPath}`);
|
|
702
|
+
if (cloudManaged) {
|
|
703
|
+
const missing = { type: "path_missing", reason: `path missing: ${absPath}` } as const;
|
|
704
|
+
for (const id of cloudIdsByPath.get(absPath) ?? [cloudManaged.id]) cloudFailures.set(id, missing);
|
|
705
|
+
}
|
|
683
706
|
if (pack) {
|
|
684
707
|
const missing = { type: "path_missing", reason: `path missing: ${absPath}` } as const;
|
|
685
708
|
for (const id of packIdsByPath.get(absPath) ?? [pack.id]) packFailures.set(id, missing);
|
|
@@ -830,5 +853,5 @@ export async function loadAllCustomHooks(
|
|
|
830
853
|
const winner = packByPath.get(path);
|
|
831
854
|
if (winner && ids.length > 1) packAliases.set(winner.id, ids);
|
|
832
855
|
}
|
|
833
|
-
return { hooks: allHooks, conventionSources, packFailures, packAliases };
|
|
856
|
+
return { hooks: allHooks, conventionSources, packFailures, packAliases, cloudFailures };
|
|
834
857
|
}
|
|
@@ -16,7 +16,7 @@ import { existsSync } from "node:fs";
|
|
|
16
16
|
import { daemonSocket as daemonSocketPath } from "./fp-home";
|
|
17
17
|
import { readConfig } from "./fp-config";
|
|
18
18
|
|
|
19
|
-
const PROTOCOL_VERSION =
|
|
19
|
+
const PROTOCOL_VERSION = 2;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Reaching the daemon and getting an answer out of it are two different
|
|
@@ -56,6 +56,8 @@ export interface DaemonHookRequest {
|
|
|
56
56
|
* hazard.
|
|
57
57
|
*/
|
|
58
58
|
cwd?: string;
|
|
59
|
+
/** Settings path of the originating agent profile, not the daemon's. */
|
|
60
|
+
agentSettingsPath?: string;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
export interface DaemonHookResponse {
|
|
@@ -98,6 +100,7 @@ export interface DaemonPolicyEvaluationRequest {
|
|
|
98
100
|
event: string;
|
|
99
101
|
payload: Record<string, unknown>;
|
|
100
102
|
cwd?: string;
|
|
103
|
+
agentSettingsPath?: string;
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
export type DaemonPolicyEvaluationAttempt =
|
|
@@ -304,6 +307,7 @@ export async function attemptDaemonHook(
|
|
|
304
307
|
cli: req.cli,
|
|
305
308
|
stdin: req.stdin,
|
|
306
309
|
cwd: req.cwd,
|
|
310
|
+
agentSettingsPath: req.agentSettingsPath,
|
|
307
311
|
},
|
|
308
312
|
opts,
|
|
309
313
|
);
|
|
@@ -338,9 +342,9 @@ function isStringArray(value: unknown): value is string[] {
|
|
|
338
342
|
|
|
339
343
|
/**
|
|
340
344
|
* Attempts the structured request used by native in-process integrations.
|
|
341
|
-
* A matching protocol version is not sufficient:
|
|
342
|
-
* `hook` but
|
|
343
|
-
*
|
|
345
|
+
* A matching protocol version is not sufficient: a broken or partial daemon
|
|
346
|
+
* may speak `hook` but not `policyEvaluation`, so only a complete, well-shaped
|
|
347
|
+
* `policyResult` proves this capability exists.
|
|
344
348
|
*/
|
|
345
349
|
export async function attemptDaemonPolicyEvaluation(
|
|
346
350
|
req: DaemonPolicyEvaluationRequest,
|
|
@@ -354,6 +358,7 @@ export async function attemptDaemonPolicyEvaluation(
|
|
|
354
358
|
event: req.event,
|
|
355
359
|
payload: req.payload,
|
|
356
360
|
cwd: req.cwd,
|
|
361
|
+
agentSettingsPath: req.agentSettingsPath,
|
|
357
362
|
},
|
|
358
363
|
opts,
|
|
359
364
|
);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* `configure-wizard.ts` calls the functions here directly, the same
|
|
8
8
|
* relationship it already has with `manager.ts`'s `installHooks()`.
|
|
9
9
|
*/
|
|
10
|
+
import { HEALTH_PROBE_SOURCE } from "./health-probe";
|
|
10
11
|
import {
|
|
11
12
|
existsSync,
|
|
12
13
|
mkdirSync,
|
|
@@ -1030,7 +1031,7 @@ export async function probeDaemon(): Promise<DaemonProbe> {
|
|
|
1030
1031
|
cli: "claude",
|
|
1031
1032
|
stdin: JSON.stringify({
|
|
1032
1033
|
hook_event_name: "SessionStart",
|
|
1033
|
-
source:
|
|
1034
|
+
source: HEALTH_PROBE_SOURCE,
|
|
1034
1035
|
}),
|
|
1035
1036
|
},
|
|
1036
1037
|
{ responseTimeoutMs: DAEMON_PROBE_TIMEOUT_MS },
|
|
@@ -23,11 +23,20 @@
|
|
|
23
23
|
* `is_auto_retryable()` excludes it from the retry pass — that is correct, and
|
|
24
24
|
* re-litigating it here would burn the budget of batches that could succeed.
|
|
25
25
|
* The gap was never the detection. It was that the detection was mute.
|
|
26
|
+
*
|
|
27
|
+
* A credential refusal is only evidence about the CURRENT key while nothing
|
|
28
|
+
* has been accepted since. A key regenerated on the dashboard leaves the
|
|
29
|
+
* running collector on the old secret until the daemon's next config tick, so
|
|
30
|
+
* a batch or two is refused while the new key already works. Those parked
|
|
31
|
+
* before the collector's last accepted delivery (`delivery.last_ok_ts`) are
|
|
32
|
+
* counted as `resending`, not refused: the daemon re-sends them with the key
|
|
33
|
+
* that works (`Delivery::redrive_refused`) and drops one only if that key
|
|
34
|
+
* refuses it too.
|
|
26
35
|
*/
|
|
27
|
-
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
36
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
28
37
|
import { join } from "node:path";
|
|
29
38
|
|
|
30
|
-
import { failedDir } from "./fp-home";
|
|
39
|
+
import { collectorHealthFile, failedDir } from "./fp-home";
|
|
31
40
|
import { readCredentials } from "./fp-config";
|
|
32
41
|
|
|
33
42
|
/**
|
|
@@ -106,6 +115,13 @@ export interface DeliveryHealth {
|
|
|
106
115
|
credentialRejected: number;
|
|
107
116
|
/** Count per client status, e.g. `{ 401: 26 }`. */
|
|
108
117
|
byStatus: Record<number, number>;
|
|
118
|
+
/**
|
|
119
|
+
* Credential refusals parked BEFORE the last delivery the server accepted.
|
|
120
|
+
* The key in use now works, so these are not counted as refused: the daemon
|
|
121
|
+
* re-sends them with that key (`Delivery::redrive_refused`, fpai-collect)
|
|
122
|
+
* and drops one only if the current key refuses it too.
|
|
123
|
+
*/
|
|
124
|
+
resending: number;
|
|
109
125
|
/** Age of the oldest parked batch, in ms. Undefined when none are parked. */
|
|
110
126
|
oldestAgeMs?: number;
|
|
111
127
|
}
|
|
@@ -115,8 +131,23 @@ const EMPTY: DeliveryHealth = {
|
|
|
115
131
|
rejected: 0,
|
|
116
132
|
credentialRejected: 0,
|
|
117
133
|
byStatus: {},
|
|
134
|
+
resending: 0,
|
|
118
135
|
};
|
|
119
136
|
|
|
137
|
+
/**
|
|
138
|
+
* `delivery.last_ok_ts` from the collector's health file, in ms, or undefined.
|
|
139
|
+
* The last upload the server ACCEPTED (`UploadMetrics::last_ok_ts`).
|
|
140
|
+
*/
|
|
141
|
+
export function readLastDeliveredAt(home?: string): number | undefined {
|
|
142
|
+
try {
|
|
143
|
+
const raw = JSON.parse(readFileSync(collectorHealthFile(home), "utf8")) as { delivery?: { last_ok_ts?: unknown } };
|
|
144
|
+
const ts = raw?.delivery?.last_ok_ts;
|
|
145
|
+
return typeof ts === "number" && Number.isFinite(ts) && ts > 0 ? ts * 1000 : undefined;
|
|
146
|
+
} catch {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
120
151
|
/**
|
|
121
152
|
* Summarise `failed/` without touching it.
|
|
122
153
|
*
|
|
@@ -127,6 +158,7 @@ export function deliveryHealth(
|
|
|
127
158
|
home?: string,
|
|
128
159
|
now: number = Date.now(),
|
|
129
160
|
connectedAt: number | undefined = home ? undefined : readCredentials().connectedAt,
|
|
161
|
+
lastDeliveredAt: number | undefined = home ? undefined : readLastDeliveredAt(),
|
|
130
162
|
): DeliveryHealth {
|
|
131
163
|
const dir = failedDir(home);
|
|
132
164
|
if (!existsSync(dir)) return EMPTY;
|
|
@@ -144,6 +176,7 @@ export function deliveryHealth(
|
|
|
144
176
|
let total = 0;
|
|
145
177
|
let rejected = 0;
|
|
146
178
|
let credentialRejected = 0;
|
|
179
|
+
let resending = 0;
|
|
147
180
|
let oldestMtime: number | undefined;
|
|
148
181
|
|
|
149
182
|
for (const name of entries) {
|
|
@@ -162,6 +195,17 @@ export function deliveryHealth(
|
|
|
162
195
|
CREDENTIAL_STATUSES.has(parsed.clientStatus) &&
|
|
163
196
|
mtime < connectedAt
|
|
164
197
|
) continue;
|
|
198
|
+
// Refused with a key that has worked since: the latest delivery attempt
|
|
199
|
+
// was accepted, so the CURRENT key is not the one being refused.
|
|
200
|
+
if (
|
|
201
|
+
lastDeliveredAt !== undefined &&
|
|
202
|
+
parsed.clientStatus !== undefined &&
|
|
203
|
+
CREDENTIAL_STATUSES.has(parsed.clientStatus) &&
|
|
204
|
+
mtime <= lastDeliveredAt
|
|
205
|
+
) {
|
|
206
|
+
resending += 1;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
165
209
|
total += 1;
|
|
166
210
|
if (parsed.clientStatus !== undefined) {
|
|
167
211
|
rejected += 1;
|
|
@@ -177,6 +221,7 @@ export function deliveryHealth(
|
|
|
177
221
|
rejected,
|
|
178
222
|
credentialRejected,
|
|
179
223
|
byStatus,
|
|
224
|
+
resending,
|
|
180
225
|
...(oldestMtime !== undefined ? { oldestAgeMs: Math.max(0, now - oldestMtime) } : {}),
|
|
181
226
|
};
|
|
182
227
|
}
|
|
@@ -12,6 +12,15 @@
|
|
|
12
12
|
* never installed its checks: `jevChecksInstalled` below is the hook path's
|
|
13
13
|
* gate for starting a review at all.
|
|
14
14
|
*
|
|
15
|
+
* ## …and a FailproofAI Cloud `both` policy's own Cloud checks
|
|
16
|
+
*
|
|
17
|
+
* A `both` policy's JS half names its own Jev checks, which run on FailproofAI
|
|
18
|
+
* Cloud and never reach this machine (CONTRACT C10). While Cloud's Jev mode
|
|
19
|
+
* asks (`observe`/`enforce`), those names are reviewers too — but only as
|
|
20
|
+
* `cloud:<policyId>/<name>` (`cloudReviewerName`), which the Cloud review files
|
|
21
|
+
* that policy's own Cloud outcomes under. An installed pack's check of the same
|
|
22
|
+
* name is a different reviewer and can never clear it.
|
|
23
|
+
*
|
|
15
24
|
* The names come from the MANIFEST, which is already where `authority` and
|
|
16
25
|
* `reviewedBy` themselves are read from, so this adds no new trust and imports
|
|
17
26
|
* no semantic module.
|
|
@@ -36,11 +45,16 @@
|
|
|
36
45
|
*/
|
|
37
46
|
import { readInstalledPacks, type ResolvedPack } from "./pack-manifest";
|
|
38
47
|
import { NO_REVIEWERS, SEMANTIC_REVIEWER_NAMES } from "./policy-authority";
|
|
48
|
+
import { cloudReviewerNames, readCloudAuthorityInputs, readCloudJevMode } from "./cloud-managed-policies";
|
|
49
|
+
import type { AgentIdentity } from "./agent-targets";
|
|
39
50
|
|
|
40
51
|
let cached: ReadonlySet<string> | null = null;
|
|
52
|
+
/** Whether the installed packs alone (not Cloud) declared any check, for the cached answer. */
|
|
53
|
+
let cachedPackChecks = false;
|
|
41
54
|
let cachedContested: ReadonlyMap<string, string[]> = new Map();
|
|
42
55
|
/** The agent the current registration pass is for; see {@link forgetEffectiveReviewerNames}. */
|
|
43
56
|
let reviewerCli: string | undefined;
|
|
57
|
+
let reviewerAgent: AgentIdentity | null = null;
|
|
44
58
|
|
|
45
59
|
/**
|
|
46
60
|
* The packs whose Jev checks take part for `cli`, filtered the way the regex
|
|
@@ -173,13 +187,23 @@ export function effectiveReviewerNames(): ReadonlySet<string> {
|
|
|
173
187
|
if (cached) return cached;
|
|
174
188
|
let names: ReadonlySet<string> = NO_REVIEWERS;
|
|
175
189
|
cachedContested = new Map();
|
|
190
|
+
cachedPackChecks = false;
|
|
176
191
|
try {
|
|
177
192
|
const packs = jevPacks(readInstalledPacks().packs, reviewerCli);
|
|
178
193
|
names = reviewerNamesFor(packs);
|
|
194
|
+
cachedPackChecks = names.size > 0;
|
|
179
195
|
cachedContested = contestedSemanticNames(packs);
|
|
180
196
|
} catch {
|
|
181
197
|
// See above: an unreadable manifest declares no checks.
|
|
182
198
|
}
|
|
199
|
+
try {
|
|
200
|
+
// Both reads answer "nothing" for a file they cannot use; guarded anyway,
|
|
201
|
+
// because a throw here would cost the registration pass — every policy.
|
|
202
|
+
const cloud = cloudReviewerNames(readCloudAuthorityInputs(reviewerAgent), readCloudJevMode());
|
|
203
|
+
if (cloud.length > 0) names = new Set([...names, ...cloud]);
|
|
204
|
+
} catch {
|
|
205
|
+
// No Cloud reviewers: every `both` policy stays hard.
|
|
206
|
+
}
|
|
183
207
|
cached = names;
|
|
184
208
|
return names;
|
|
185
209
|
}
|
|
@@ -204,6 +228,16 @@ export function jevChecksInstalled(): boolean {
|
|
|
204
228
|
return effectiveReviewerNames().size > 0;
|
|
205
229
|
}
|
|
206
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Whether an installed pack (not FailproofAI Cloud) gives Jev a check for this
|
|
233
|
+
* agent: the part of {@link jevChecksInstalled} a Cloud review needs to know
|
|
234
|
+
* when deciding whether a call has anything of the machine's own to ask.
|
|
235
|
+
*/
|
|
236
|
+
export function packJevChecksInstalled(): boolean {
|
|
237
|
+
effectiveReviewerNames();
|
|
238
|
+
return cachedPackChecks;
|
|
239
|
+
}
|
|
240
|
+
|
|
207
241
|
/**
|
|
208
242
|
* The same question for a CLI command, read fresh rather than from the
|
|
209
243
|
* registration cache (which only the hook path fills): any agent, packs as
|
|
@@ -244,7 +278,8 @@ export function reviewerNamesFor(
|
|
|
244
278
|
* runs before it registers anything — so a pack installed under a long-lived
|
|
245
279
|
* warm worker is picked up on the next event rather than at the next restart.
|
|
246
280
|
*/
|
|
247
|
-
export function forgetEffectiveReviewerNames(cli?: string): void {
|
|
281
|
+
export function forgetEffectiveReviewerNames(cli?: string, agent: AgentIdentity | null = null): void {
|
|
248
282
|
cached = null;
|
|
249
283
|
reviewerCli = cli;
|
|
284
|
+
reviewerAgent = agent;
|
|
250
285
|
}
|
package/src/hooks/fp-config.ts
CHANGED
|
@@ -940,6 +940,25 @@ export function readCredentials(): FpCredentials {
|
|
|
940
940
|
}
|
|
941
941
|
}
|
|
942
942
|
|
|
943
|
+
/**
|
|
944
|
+
* Whether FailproofAI Cloud sets this machine's Jev mode: it is connected for
|
|
945
|
+
* policies (a machine id in `credentials.json`). Cloud then runs Jev in enforce
|
|
946
|
+
* while a Jev policy is deployed here and switches it off when none is; local
|
|
947
|
+
* mode settings do not apply. Never throws.
|
|
948
|
+
*/
|
|
949
|
+
export function cloudManagesJevMode(): boolean {
|
|
950
|
+
try {
|
|
951
|
+
const cloud = readCredentials().cloud;
|
|
952
|
+
return Boolean(cloud?.url && cloud.machineId);
|
|
953
|
+
} catch {
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/** The one line a local mode switch answers with on such a machine. */
|
|
959
|
+
export const CLOUD_MANAGED_JEV_MODE =
|
|
960
|
+
"Jev mode is managed by FailproofAI Cloud on this machine: enforce while a Jev policy is deployed here, off when none is.";
|
|
961
|
+
|
|
943
962
|
/**
|
|
944
963
|
* Project a parsed credentials object onto {@link FpCredentials}.
|
|
945
964
|
*
|
package/src/hooks/fp-home.ts
CHANGED
|
@@ -165,6 +165,9 @@ export const credentialsFile = (home?: string) => atHome(home, "credentials.json
|
|
|
165
165
|
*/
|
|
166
166
|
export const jevConfigFile = (home?: string) => atHome(home, "jev.json");
|
|
167
167
|
|
|
168
|
+
/** Owner-only inventory maintained by failproofaid. Paths stay on this machine. */
|
|
169
|
+
export const agentRosterFile = (home?: string) => atHome(home, "agents", "roster.json");
|
|
170
|
+
|
|
168
171
|
// ── Daemon binaries ──────────────────────────────────────────────────────────
|
|
169
172
|
|
|
170
173
|
export const binDir = (home?: string) => atHome(home, "bin");
|
|
@@ -656,6 +659,9 @@ export const HOME_CLASSES: readonly { path: (home?: string) => string; class: Da
|
|
|
656
659
|
// history the user was already told about. Kept OUT of `auditSessionFile`
|
|
657
660
|
// precisely so both survive a sign-out.
|
|
658
661
|
{ path: auditMachineFile, class: "identity" },
|
|
662
|
+
// Profile IDs are stable across restarts and renames. A reset that deleted
|
|
663
|
+
// this file would make Cloud's exact-profile assignments match nobody.
|
|
664
|
+
{ path: agentRosterFile, class: "identity" },
|
|
659
665
|
|
|
660
666
|
// ── May be dropped: rebuilt on demand ──
|
|
661
667
|
// NOTE: `auditDir` itself is deliberately absent. Layout 4 made it MIXED — it
|