failproofai 1.0.12-beta.1 → 1.0.12
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 +4 -4
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +2 -2
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0_em82s16zfym._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__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]__167mipqmjprq5._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0b78b0j8iyv6l._.js → [root-of-the-server]__1vofkb2oq7p7e._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1a3tubds7s7ow._.js → _0ouu886szh6yj._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1d98fyfrcaqhy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.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_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1i8oid1gc7s1f.js → node_modules_html-to-image_es_index_0tqc05n832v7a.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589gp4p60j._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me658zqmh_._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/preview-props.json +3 -3
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
- package/.next/standalone/.next/static/chunks/{1b32-afqacaen.js → 0el9n3h9_zp5m.js} +1 -1
- package/.next/standalone/.next/static/chunks/0k7g_8x3o6gd-.js +1 -0
- package/.next/standalone/.next/static/chunks/{42e8zab314654.js → 0umzct31duk5g.js} +1 -1
- package/.next/standalone/.next/static/chunks/{24w3x2da9sk49.js → 0wobqgosirs8p.js} +1 -1
- package/.next/standalone/.next/static/chunks/12rug3jilwqzs.js +1 -0
- package/.next/standalone/.next/static/chunks/{2-tf6_7u623kd.js → 1kduj74-715jh.js} +1 -1
- package/.next/standalone/.next/static/chunks/2701amhrbsbot.js +1 -0
- package/.next/standalone/.next/static/chunks/{18433qz-8njv3.js → 27cavoc8paelg.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0_zfad4co-t2z.js → 2ve23a7keupjw.js} +1 -1
- package/.next/standalone/.next/static/chunks/3bv6xaoitjpnh.js +6 -0
- 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 +28 -18
- package/.next/standalone/fp-cloud-cli/README.md +127 -9
- package/.next/standalone/fp-cloud-cli/fp_cli/app.py +15 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +199 -34
- package/.next/standalone/fp-cloud-cli/fp_cli/cloud_output.py +523 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_cloud.py +45 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +3 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/account_cmds.py +171 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +80 -7
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +23 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +2 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/contracts_cmds.py +67 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/dashboards_cmds.py +159 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/evaluators_cmds.py +380 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +14 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +12 -216
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +82 -6
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +4 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +17 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +143 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +70 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sentiment_cmds.py +49 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +95 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +2 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +21 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +14 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +7 -53
- package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +3 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/evaluator_spec.py +266 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +189 -152
- package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +21 -24
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +124 -23
- package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +2 -1
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +173 -22
- package/.next/standalone/fp-cloud-cli/tests/fake_cloud_smoke.py +312 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_account.py +125 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_agent.py +253 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_human_views.py +287 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +32 -197
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_observability.py +116 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_transport.py +141 -0
- package/.next/standalone/fp-cloud-cli/tests/test_commands.py +3 -3
- package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +3 -3
- package/.next/standalone/fp-cloud-cli/tests/test_evaluator_authoring.py +449 -0
- package/.next/standalone/fp-cloud-cli/tests/test_facets.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +15 -0
- package/.next/standalone/fp-cloud-cli/tests/test_operator.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +37 -37
- package/.next/standalone/fp-cloud-cli/tests/test_round4_behaviour.py +144 -0
- package/.next/standalone/fp-cloud-cli/tests/test_screen_language.py +80 -0
- package/.next/standalone/fp-cloud-cli/tests/test_session_reevaluate.py +33 -0
- package/.next/standalone/fp-cloud-cli/tests/test_staging_parity.py +198 -0
- package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +1 -1
- package/.next/standalone/hermes-plugin/__init__.py +11 -0
- package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
- package/.next/standalone/package.json +11 -10
- package/.next/standalone/sdk/python/uv.lock +31 -8
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +68 -176
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +2 -2
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +98 -115
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +3 -3
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +149 -171
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +2 -2
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +25 -25
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +10 -10
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +1 -1
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +1760 -638
- package/dist/worker.mjs +426 -76
- package/hermes-plugin/__init__.py +11 -0
- package/hermes-plugin/plugin.yaml +1 -1
- package/package.json +11 -10
- package/src/audit/open-browser.ts +24 -7
- package/src/hooks/agent-roster.ts +9 -1
- package/src/hooks/agent-skill.ts +206 -0
- package/src/hooks/cloud-connection.ts +27 -51
- package/src/hooks/cloud-introspect.ts +5 -2
- package/src/hooks/cloud-login.ts +248 -0
- package/src/hooks/cloud-managed-policies.ts +54 -4
- package/src/hooks/codex-system-hooks.ts +10 -4
- package/src/hooks/config-status.ts +9 -3
- package/src/hooks/configure-wizard.ts +356 -32
- package/src/hooks/delivery-health.ts +47 -2
- package/src/hooks/effective-reviewers.ts +14 -0
- package/src/hooks/fp-config.ts +19 -0
- package/src/hooks/fp-home.ts +1 -1
- package/src/hooks/handler.ts +43 -5
- package/src/hooks/hook-activity-store.ts +7 -1
- package/src/hooks/integrations.ts +18 -3
- package/src/hooks/jev-activity.ts +117 -1
- package/src/hooks/jev-cli.ts +7 -9
- package/src/hooks/manager.ts +80 -46
- package/src/hooks/policy-evaluator.ts +17 -0
- package/src/hooks/policy-reviewability.ts +4 -2
- package/src/hooks/semantic/cloud-jev.ts +219 -28
- package/src/hooks/semantic/combine.ts +26 -1
- package/src/hooks/semantic/intent.ts +127 -24
- package/src/hooks/semantic/jev-client.ts +1 -1
- package/src/hooks/semantic/jev-config.ts +6 -27
- package/src/hooks/semantic/jev-review.ts +111 -4
- package/src/hooks/tui.ts +280 -0
- package/src/hooks/uninstall-cli.ts +12 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0588st-tpiuia._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_128ebxr83nk6g._.js +0 -3
- package/.next/standalone/.next/static/chunks/0jutamwqz8pg5.js +0 -1
- package/.next/standalone/.next/static/chunks/114hbolvrv94e.js +0 -1
- package/.next/standalone/.next/static/chunks/1p-hsi58i-6h0.js +0 -6
- package/.next/standalone/.next/static/chunks/3-pku9qwv1yfu.js +0 -1
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_ssgManifest.js +0 -0
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type JevReview,
|
|
26
26
|
type RegexVerdict,
|
|
27
27
|
type ObserveVerdict,
|
|
28
|
+
type ObservedCloudVerdict,
|
|
28
29
|
type TwoTierReview,
|
|
29
30
|
} from "./semantic/combine";
|
|
30
31
|
// The code, not a copy of the string: the log line below turns on it, and a
|
|
@@ -64,6 +65,8 @@ export interface EvaluationResult {
|
|
|
64
65
|
jevOrigin?: { packId: string; packVersion?: string };
|
|
65
66
|
/** Observe mode: Jev's own deny/instruct, recorded as a "would have" (see `ObserveVerdict`). */
|
|
66
67
|
observeVerdict?: ObserveVerdict;
|
|
68
|
+
/** An `observe` Cloud Jev assignment's would-haves (see `JevReview.observedCloud`). */
|
|
69
|
+
observedCloud?: ReadonlyArray<ObservedCloudVerdict>;
|
|
67
70
|
};
|
|
68
71
|
}
|
|
69
72
|
|
|
@@ -217,6 +220,19 @@ export async function evaluatePolicies(
|
|
|
217
220
|
twoTier.releaseRegistry?.();
|
|
218
221
|
review = await twoTier.review;
|
|
219
222
|
}
|
|
223
|
+
// Jev asked no check about this call (Cloud's short-circuit, or nothing
|
|
224
|
+
// selected): it took no part, so the row carries no Jev fields at all — the
|
|
225
|
+
// regex result, exactly as without a review.
|
|
226
|
+
if (review.kind === "answered" && review.asked.length === 0 && review.decision === "allow" && !review.observedAsked?.length) {
|
|
227
|
+
return formatVerdict(eventType, session, toolName, regexOnly(collected.verdicts));
|
|
228
|
+
}
|
|
229
|
+
// A hard regex deny decided before FailproofAI Cloud's Jev answered, and the
|
|
230
|
+
// request was dropped: Jev took no part, so the row is the regex (Cloud or
|
|
231
|
+
// local) policy's, with no Jev fields. A local `jev.json` keeps its
|
|
232
|
+
// `not-consulted` row, which `jev status` counts.
|
|
233
|
+
if (review.kind === "not-consulted" && twoTier.cloud) {
|
|
234
|
+
return formatVerdict(eventType, session, toolName, regexOnly(collected.verdicts));
|
|
235
|
+
}
|
|
220
236
|
const combined = combineTwoTier(collected.verdicts, review, twoTier.mode);
|
|
221
237
|
if (combined.cleared.length > 0) {
|
|
222
238
|
hookLogInfo(`jev ${twoTier.mode === "observe" ? "would clear" : "cleared"}: ${combined.cleared.join(", ")}`);
|
|
@@ -256,6 +272,7 @@ export async function evaluatePolicies(
|
|
|
256
272
|
? { jevOrigin: review.origin }
|
|
257
273
|
: {}),
|
|
258
274
|
...(combined.observeVerdict ? { observeVerdict: combined.observeVerdict } : {}),
|
|
275
|
+
...(review.kind === "answered" && review.observedCloud?.length ? { observedCloud: review.observedCloud } : {}),
|
|
259
276
|
},
|
|
260
277
|
};
|
|
261
278
|
}
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
import {
|
|
59
59
|
cloudAuthorityDeclaration,
|
|
60
60
|
cloudReviewerNames,
|
|
61
|
-
|
|
61
|
+
listActiveCloudManagedPolicies,
|
|
62
62
|
readCloudJevMode,
|
|
63
63
|
} from "./cloud-managed-policies";
|
|
64
64
|
import { jevPacks } from "./effective-reviewers";
|
|
@@ -212,7 +212,9 @@ export function surveyReviewableCoverage(cwd?: string): ReviewableCoverage {
|
|
|
212
212
|
// Cloud checks (`cloud:<id>/<name>`), which this machine cannot list but
|
|
213
213
|
// Cloud answers for. No mode, `off`, or an `observe` assignment: hard.
|
|
214
214
|
const mode = readCloudJevMode();
|
|
215
|
-
|
|
215
|
+
// Listed for every agent: this survey has no agent, and the hook-scoped
|
|
216
|
+
// read with none drops each agent-targeted assignment.
|
|
217
|
+
const assignments = listActiveCloudManagedPolicies();
|
|
216
218
|
const cloudReviewers = cloudReviewerNames(assignments, mode);
|
|
217
219
|
if (cloudReviewers.length > 0) reviewers = new Set([...(reviewers ?? []), ...cloudReviewers]);
|
|
218
220
|
for (const assignment of assignments) records.push(cloudAuthorityDeclaration(assignment, mode));
|
|
@@ -90,10 +90,28 @@ export interface CloudJevBlock {
|
|
|
90
90
|
|
|
91
91
|
/** FailproofAI Cloud's reply block, validated. Its outcomes carry a pack-shaped `cloud:<id>` origin. */
|
|
92
92
|
export interface CloudJevReply {
|
|
93
|
+
/** The ENFORCED checks' verdict: an observe assignment's outcomes are in `observed`, not here. */
|
|
93
94
|
verdict: SemanticVerdict;
|
|
94
95
|
asked: string[];
|
|
95
96
|
droppedLocal: string[];
|
|
96
97
|
droppedCloud: string[];
|
|
98
|
+
/** What the checks of an `observe` assignment would have done (deny / instruct only). */
|
|
99
|
+
observed: ObservedCloudCheck[];
|
|
100
|
+
/** Every check of an `observe` assignment Cloud asked (its name). */
|
|
101
|
+
observedAsked: string[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* One `observe` Cloud assignment's would-have: recorded on the activity row's
|
|
106
|
+
* `observed` list like an observe JS policy's, never applied, never a clear.
|
|
107
|
+
* One per policy, its most severe check.
|
|
108
|
+
*/
|
|
109
|
+
export interface ObservedCloudCheck {
|
|
110
|
+
cloudPolicyId: string;
|
|
111
|
+
cloudVersion: number;
|
|
112
|
+
check: string;
|
|
113
|
+
decision: "deny" | "instruct";
|
|
114
|
+
reason: string;
|
|
97
115
|
}
|
|
98
116
|
|
|
99
117
|
/** What the Cloud review adds to a `SemanticOutcome`, for the verdict log and the budget report. */
|
|
@@ -102,6 +120,20 @@ export interface CloudReplySummary {
|
|
|
102
120
|
/** The machine's own groups Cloud dropped, with the pack that declared each (null when none did). */
|
|
103
121
|
droppedLocal: Array<{ name: string; packId: string | null }>;
|
|
104
122
|
droppedCloud: string[];
|
|
123
|
+
/**
|
|
124
|
+
* TypeSafe's answers to the Cloud checks' own questions (`<check>.<probe>`,
|
|
125
|
+
* `<check>.exempt`), for the activity row's per-check record. Ids and
|
|
126
|
+
* numbers only; the machine never decides from these (Cloud did).
|
|
127
|
+
*/
|
|
128
|
+
answers: Record<string, number>;
|
|
129
|
+
/**
|
|
130
|
+
* Checks of WATCHED (observe-assigned) Cloud policies, decided apart into
|
|
131
|
+
* the reply's `observed` block: recorded on the activity row only. They
|
|
132
|
+
* never reach the verdict, any clear, or the merge.
|
|
133
|
+
*/
|
|
134
|
+
observedOutcomes: PolicyOutcome[];
|
|
135
|
+
observed: ObservedCloudCheck[];
|
|
136
|
+
observedAsked: string[];
|
|
105
137
|
}
|
|
106
138
|
|
|
107
139
|
export type CloudSemanticOutcome =
|
|
@@ -113,6 +145,8 @@ export interface CloudSemanticOptions extends SemanticOptions {
|
|
|
113
145
|
machineId: string;
|
|
114
146
|
/** Explicit null = v2 request with unresolved identity (no scoped checks). */
|
|
115
147
|
agent?: AgentIdentity | null;
|
|
148
|
+
/** Cloud policy ids whose Jev assignment on this machine is `observe` (`active.json` `jevPolicies`). */
|
|
149
|
+
observedPolicies?: ReadonlyArray<string>;
|
|
116
150
|
}
|
|
117
151
|
|
|
118
152
|
// ── The request ──────────────────────────────────────────────────────────────
|
|
@@ -366,8 +400,19 @@ const DECISIONS = new Set(["allow", "deny", "instruct"]);
|
|
|
366
400
|
const VERDICTS = new Set(["deny", "instruct", "overridden", "none"]);
|
|
367
401
|
const INTENTS = new Set(["op-requested", "task-step", "downgraded-task-step"]);
|
|
368
402
|
|
|
403
|
+
/** One Cloud outcome as parsed: the outcome, and what only the reply can say about it. */
|
|
404
|
+
interface ParsedCloudOutcome {
|
|
405
|
+
outcome: PolicyOutcome;
|
|
406
|
+
cloudPolicyId: string;
|
|
407
|
+
cloudVersion: number;
|
|
408
|
+
/** The assignment's effect, when Cloud says (`effect`); absent reads as `enforce`. */
|
|
409
|
+
observe: boolean;
|
|
410
|
+
/** Cloud's own line for this check, when it sends one. */
|
|
411
|
+
reason: string | null;
|
|
412
|
+
}
|
|
413
|
+
|
|
369
414
|
/** One Cloud outcome, rebuilt field by field so nothing but these fields crosses over. */
|
|
370
|
-
function parseOutcome(raw: unknown, i: number):
|
|
415
|
+
function parseOutcome(raw: unknown, i: number): ParsedCloudOutcome {
|
|
371
416
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) malformed(`outcome ${i} is not an object`);
|
|
372
417
|
const o = raw as Record<string, unknown>;
|
|
373
418
|
if (!isName(o.policy)) malformed(`outcome ${i} has no policy name`);
|
|
@@ -391,7 +436,9 @@ function parseOutcome(raw: unknown, i: number): PolicyOutcome {
|
|
|
391
436
|
) {
|
|
392
437
|
malformed(`outcome ${i} has no Cloud origin`);
|
|
393
438
|
}
|
|
394
|
-
|
|
439
|
+
if (o.effect !== undefined && o.effect !== "enforce" && o.effect !== "observe") malformed(`outcome ${i} has an unknown effect`);
|
|
440
|
+
if (o.reason !== undefined && o.reason !== null && typeof o.reason !== "string") malformed(`outcome ${i} reason is not text`);
|
|
441
|
+
const outcome: PolicyOutcome = {
|
|
395
442
|
policy: o.policy,
|
|
396
443
|
mode: o.mode,
|
|
397
444
|
...(typeof o.userCanOverride === "boolean" ? { userCanOverride: o.userCanOverride } : {}),
|
|
@@ -408,6 +455,48 @@ function parseOutcome(raw: unknown, i: number): PolicyOutcome {
|
|
|
408
455
|
...(o.intent !== undefined ? { intent: o.intent as PolicyOutcome["intent"] } : {}),
|
|
409
456
|
...(o.targetScanIncomplete === true ? { targetScanIncomplete: true as const } : {}),
|
|
410
457
|
};
|
|
458
|
+
return {
|
|
459
|
+
outcome,
|
|
460
|
+
cloudPolicyId: origin.cloudPolicyId as string,
|
|
461
|
+
cloudVersion: origin.cloudVersion as number,
|
|
462
|
+
observe: o.effect === "observe",
|
|
463
|
+
reason: typeof o.reason === "string" ? capReason(o.reason) : null,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function capReason(reason: string): string {
|
|
468
|
+
return reason.length > MAX_CLOUD_REASON_CHARS ? `${reason.slice(0, MAX_CLOUD_REASON_CHARS - 1)}…` : reason;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const SEVERITY: Record<"deny" | "instruct", number> = { instruct: 1, deny: 2 };
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* The would-haves of the observed outcomes: per Cloud policy, its most severe
|
|
475
|
+
* check that fired (`deny`, or a warning). A check that found nothing, or that
|
|
476
|
+
* the human asked for, would have done nothing.
|
|
477
|
+
*/
|
|
478
|
+
function observedChecks(
|
|
479
|
+
parsed: ReadonlyArray<ParsedCloudOutcome>,
|
|
480
|
+
watched: { v: Record<string, unknown>; parsed: ParsedCloudOutcome[] } | null = null,
|
|
481
|
+
): ObservedCloudCheck[] {
|
|
482
|
+
// The observed verdict's own reason, when exactly one of its checks fired.
|
|
483
|
+
const firing = watched?.parsed.filter((p) => p.outcome.verdict === "deny" || p.outcome.verdict === "instruct") ?? [];
|
|
484
|
+
const watchedReason = firing.length === 1 && typeof watched?.v.reason === "string" ? capReason(watched.v.reason) : null;
|
|
485
|
+
const byPolicy = new Map<string, ObservedCloudCheck>();
|
|
486
|
+
for (const p of parsed) {
|
|
487
|
+
const verdict = p.outcome.verdict;
|
|
488
|
+
if (verdict !== "deny" && verdict !== "instruct") continue;
|
|
489
|
+
const prev = byPolicy.get(p.cloudPolicyId);
|
|
490
|
+
if (prev && SEVERITY[prev.decision] >= SEVERITY[verdict]) continue;
|
|
491
|
+
byPolicy.set(p.cloudPolicyId, {
|
|
492
|
+
cloudPolicyId: p.cloudPolicyId,
|
|
493
|
+
cloudVersion: p.cloudVersion,
|
|
494
|
+
check: p.outcome.policy,
|
|
495
|
+
decision: verdict,
|
|
496
|
+
reason: p.reason ?? (firing[0] === p ? watchedReason : null) ?? `Flagged by semantic review (semantic/${p.outcome.policy})`,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return [...byPolicy.values()];
|
|
411
500
|
}
|
|
412
501
|
|
|
413
502
|
/**
|
|
@@ -415,17 +504,20 @@ function parseOutcome(raw: unknown, i: number): PolicyOutcome {
|
|
|
415
504
|
* A reply without one (a server that does not implement C10) is malformed:
|
|
416
505
|
* nothing it says can be told apart from "Cloud has no checks here".
|
|
417
506
|
*/
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (!
|
|
426
|
-
if (v.
|
|
427
|
-
|
|
428
|
-
|
|
507
|
+
/** One verdict block (`verdict` + `asked`) validated, with every rule a reply's verdict is held to. */
|
|
508
|
+
function parseVerdictBlock(
|
|
509
|
+
rawVerdict: unknown,
|
|
510
|
+
rawAsked: unknown,
|
|
511
|
+
what: string,
|
|
512
|
+
): { v: Record<string, unknown>; parsed: ParsedCloudOutcome[]; asked: string[] } {
|
|
513
|
+
const v = rawVerdict as Record<string, unknown> | undefined;
|
|
514
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) malformed(`no ${what}`);
|
|
515
|
+
if (typeof v.decision !== "string" || !DECISIONS.has(v.decision)) malformed(`${what} has no decision`);
|
|
516
|
+
if (v.reason !== null && v.reason !== undefined && typeof v.reason !== "string") malformed(`${what} reason is not text`);
|
|
517
|
+
if (!Array.isArray(v.outcomes) || v.outcomes.length > MAX_LIST) malformed(`${what} has no outcomes list`);
|
|
518
|
+
if (v.beyondTask !== undefined && typeof v.beyondTask !== "boolean") malformed(`${what} beyondTask is not a flag`);
|
|
519
|
+
const parsed = v.outcomes.map(parseOutcome);
|
|
520
|
+
const asked = names(rawAsked, `${what} asked`);
|
|
429
521
|
// Each outcome must answer a check Cloud says it ASKED, once. The review
|
|
430
522
|
// clears a `both` policy's regex half from these outcomes, so one that
|
|
431
523
|
// `asked` does not vouch for — `asked: []` beside a fabricated `none` — is a
|
|
@@ -433,25 +525,65 @@ export function parseCloudReply(raw: unknown): CloudJevReply {
|
|
|
433
525
|
// decided by the regex alone (security review of #873).
|
|
434
526
|
const askedSet = new Set(asked);
|
|
435
527
|
const seen = new Set<string>();
|
|
436
|
-
|
|
437
|
-
if (!askedSet.has(o.policy)) malformed(
|
|
528
|
+
parsed.forEach(({ outcome: o }, i) => {
|
|
529
|
+
if (!askedSet.has(o.policy)) malformed(`${what} outcome ${i} answers ${JSON.stringify(o.policy)}, which Cloud did not ask`);
|
|
438
530
|
const key = `${o.origin?.packId ?? ""}\u0000${o.policy}`;
|
|
439
|
-
if (seen.has(key)) malformed(
|
|
531
|
+
if (seen.has(key)) malformed(`${what} outcome ${i} answers ${JSON.stringify(o.policy)} twice`);
|
|
440
532
|
seen.add(key);
|
|
441
533
|
});
|
|
534
|
+
return { v, parsed, asked };
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export function parseCloudReply(raw: unknown, observedPolicies: ReadonlyArray<string> = []): CloudJevReply {
|
|
538
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) malformed("no cloud block");
|
|
539
|
+
const block = raw as Record<string, unknown>;
|
|
540
|
+
const { v, parsed, asked } = parseVerdictBlock(block.verdict, block.asked, "verdict");
|
|
541
|
+
// `observed` (CONTRACT, fix round): the checks of watched (`observe`)
|
|
542
|
+
// assignments, decided apart, present only when one was asked. Held to the
|
|
543
|
+
// same rules as the verdict; recorded, never applied.
|
|
544
|
+
let watched: { v: Record<string, unknown>; parsed: ParsedCloudOutcome[]; asked: string[] } | null = null;
|
|
545
|
+
if (block.observed !== undefined && block.observed !== null) {
|
|
546
|
+
const o = block.observed as Record<string, unknown>;
|
|
547
|
+
if (typeof o !== "object" || Array.isArray(o)) malformed("observed is not an object");
|
|
548
|
+
watched = parseVerdictBlock(o.verdict, o.asked, "observed verdict");
|
|
549
|
+
if (watched.parsed.some((p) => p.outcome.verdict === "deny") && watched.v.decision !== "deny") {
|
|
550
|
+
malformed("an observed check denied but its verdict does not");
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
// An `observe` assignment's checks (Cloud says so per outcome, or this
|
|
554
|
+
// machine's deployment does) are recorded, never applied: they leave the
|
|
555
|
+
// verdict, so they can neither deny nor clear anything.
|
|
556
|
+
const observeIds = new Set(observedPolicies);
|
|
557
|
+
const isObserved = (p: ParsedCloudOutcome) => p.observe || observeIds.has(p.cloudPolicyId);
|
|
558
|
+
const enforced = parsed.filter((p) => !isObserved(p)).map((p) => p.outcome);
|
|
559
|
+
const observedHere = parsed.filter(isObserved);
|
|
560
|
+
const observed = [...observedHere, ...(watched?.parsed ?? [])];
|
|
442
561
|
// A check that fired deny makes the verdict a deny; a reply that says
|
|
443
562
|
// otherwise contradicts itself.
|
|
444
|
-
if (
|
|
563
|
+
if (enforced.some((o) => o.verdict === "deny") && v.decision !== "deny") malformed("a check denied but the verdict does not");
|
|
564
|
+
const cloudReason = typeof v.reason === "string" ? capReason(v.reason) : null;
|
|
565
|
+
let decision = v.decision as SemanticVerdict["decision"];
|
|
566
|
+
let reason = cloudReason;
|
|
567
|
+
if (observedHere.length > 0) {
|
|
568
|
+
// Decided again from the enforced checks alone, never above what Cloud
|
|
569
|
+
// said. Cloud's reason stands when the decision does; otherwise the
|
|
570
|
+
// combine's generic line names the deciding check.
|
|
571
|
+
const own: SemanticVerdict["decision"] = enforced.some((o) => o.verdict === "deny")
|
|
572
|
+
? "deny"
|
|
573
|
+
: enforced.some((o) => o.verdict === "instruct") || v.beyondTask === true
|
|
574
|
+
? "instruct"
|
|
575
|
+
: "allow";
|
|
576
|
+
const capped = RANK[own] <= RANK[decision] ? own : decision;
|
|
577
|
+
if (capped !== decision) reason = null;
|
|
578
|
+
decision = capped;
|
|
579
|
+
}
|
|
445
580
|
return {
|
|
581
|
+
observed: observedChecks(observed, watched),
|
|
582
|
+
observedAsked: [...observedHere.map((p) => p.outcome.policy), ...(watched?.asked ?? [])],
|
|
446
583
|
verdict: {
|
|
447
|
-
decision
|
|
448
|
-
reason
|
|
449
|
-
|
|
450
|
-
? v.reason.length > MAX_CLOUD_REASON_CHARS
|
|
451
|
-
? `${v.reason.slice(0, MAX_CLOUD_REASON_CHARS - 1)}…`
|
|
452
|
-
: v.reason
|
|
453
|
-
: null,
|
|
454
|
-
outcomes,
|
|
584
|
+
decision,
|
|
585
|
+
reason,
|
|
586
|
+
outcomes: enforced,
|
|
455
587
|
injectionSuspected: nullableProbability(v.injectionSuspected, "injectionSuspected"),
|
|
456
588
|
scopeWithinRequest: nullableProbability(v.scopeWithinRequest, "scopeWithinRequest"),
|
|
457
589
|
beyondTask: v.beyondTask === true,
|
|
@@ -462,6 +594,34 @@ export function parseCloudReply(raw: unknown): CloudJevReply {
|
|
|
462
594
|
};
|
|
463
595
|
}
|
|
464
596
|
|
|
597
|
+
/**
|
|
598
|
+
* The reply's optional `observed: {verdict, asked}` — the outcomes of checks
|
|
599
|
+
* from watched Cloud policies, which never block. Read leniently and apart
|
|
600
|
+
* from {@link parseCloudReply}: they are only recorded, so a malformed block
|
|
601
|
+
* costs the record, never the call's verdict. Only outcomes `observed.asked`
|
|
602
|
+
* names, each once.
|
|
603
|
+
*/
|
|
604
|
+
export function parseObservedOutcomes(raw: unknown): PolicyOutcome[] {
|
|
605
|
+
try {
|
|
606
|
+
const block = (raw as Record<string, unknown> | null)?.observed as Record<string, unknown> | undefined;
|
|
607
|
+
if (!block || typeof block !== "object" || Array.isArray(block)) return [];
|
|
608
|
+
const asked = new Set(names(block.asked, "observed.asked"));
|
|
609
|
+
const outcomes = ((block.verdict as Record<string, unknown> | undefined)?.outcomes ?? []) as unknown[];
|
|
610
|
+
if (!Array.isArray(outcomes) || outcomes.length > MAX_LIST) return [];
|
|
611
|
+
const seen = new Set<string>();
|
|
612
|
+
const out: PolicyOutcome[] = [];
|
|
613
|
+
outcomes.forEach((o, i) => {
|
|
614
|
+
const parsed = parseOutcome(o, i).outcome;
|
|
615
|
+
if (!asked.has(parsed.policy) || seen.has(parsed.policy)) return;
|
|
616
|
+
seen.add(parsed.policy);
|
|
617
|
+
out.push(parsed);
|
|
618
|
+
});
|
|
619
|
+
return out;
|
|
620
|
+
} catch {
|
|
621
|
+
return [];
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
465
625
|
// ── The merge (CONTRACT C10.5) ───────────────────────────────────────────────
|
|
466
626
|
|
|
467
627
|
const RANK: Record<SemanticVerdict["decision"], number> = { allow: 0, instruct: 1, deny: 2 };
|
|
@@ -539,6 +699,28 @@ function expectedQuestions(request: JevRequest, prepared: PreparedCall, dropped:
|
|
|
539
699
|
return { ...request, questions };
|
|
540
700
|
}
|
|
541
701
|
|
|
702
|
+
/** A question id's own part, as declarations allow it (`force_push`, `exempt`). */
|
|
703
|
+
const QUESTION_PART_RE = /^[a-z][a-z0-9_]{0,31}$/;
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* The answers to the questions Cloud added for the checks it asked: every
|
|
707
|
+
* `<check>.<part>` id whose check is in `asked`, read leniently (a missing or
|
|
708
|
+
* malformed one is simply not recorded — Cloud has already decided).
|
|
709
|
+
*/
|
|
710
|
+
export function cloudCheckAnswers(response: JevResponse, asked: ReadonlyArray<string>): Record<string, number> {
|
|
711
|
+
const out: Record<string, number> = {};
|
|
712
|
+
if (asked.length === 0) return out;
|
|
713
|
+
const names = new Set(asked);
|
|
714
|
+
const answers = response.answers && typeof response.answers === "object" ? response.answers : {};
|
|
715
|
+
for (const [id, a] of Object.entries(answers)) {
|
|
716
|
+
const dot = id.lastIndexOf(".");
|
|
717
|
+
if (dot <= 0 || !names.has(id.slice(0, dot)) || !QUESTION_PART_RE.test(id.slice(dot + 1))) continue;
|
|
718
|
+
const p = (a as { noul?: unknown } | null)?.noul;
|
|
719
|
+
if (isProbability(p)) out[id] = p;
|
|
720
|
+
}
|
|
721
|
+
return out;
|
|
722
|
+
}
|
|
723
|
+
|
|
542
724
|
/** Whatever answers a short-circuit reply carries, read leniently: none is required. */
|
|
543
725
|
function presentAnswers(response: JevResponse): Record<string, number> {
|
|
544
726
|
const out: Record<string, number> = {};
|
|
@@ -579,7 +761,7 @@ export async function evaluateCloudSemantic(input: SemanticInput, opts: CloudSem
|
|
|
579
761
|
model: opts.model ?? DEFAULT_JEV_MODEL,
|
|
580
762
|
modelVerified: true,
|
|
581
763
|
via: "none",
|
|
582
|
-
cloud: { asked: [], droppedLocal: [], droppedCloud: [] },
|
|
764
|
+
cloud: { asked: [], droppedLocal: [], droppedCloud: [], answers: {}, observed: [], observedAsked: [], observedOutcomes: [] },
|
|
583
765
|
};
|
|
584
766
|
}
|
|
585
767
|
let prepared: PreparedCall;
|
|
@@ -623,7 +805,11 @@ export async function evaluateCloudSemantic(input: SemanticInput, opts: CloudSem
|
|
|
623
805
|
const timeout = AbortSignal.timeout(opts.timeoutMs ?? envNumber("FAILPROOFAI_JEV_TIMEOUT_MS", DEFAULT_JEV_TIMEOUT_MS));
|
|
624
806
|
const signal = opts.signal ? AbortSignal.any([timeout, opts.signal]) : timeout;
|
|
625
807
|
const response = await transport(request, signal);
|
|
626
|
-
const reply = parseCloudReply(response.cloud);
|
|
808
|
+
const reply = parseCloudReply(response.cloud, opts.observedPolicies);
|
|
809
|
+
// The full outcomes of the watched checks the reply was vetted to have asked
|
|
810
|
+
// (`parseCloudReply` refuses the whole reply otherwise), for the per-check record.
|
|
811
|
+
const observedAsked = new Set(reply.observedAsked);
|
|
812
|
+
const observedOutcomes = parseObservedOutcomes(response.cloud).filter((o) => observedAsked.has(o.policy));
|
|
627
813
|
|
|
628
814
|
// Only the machine's own groups can be dropped from its side; a name it
|
|
629
815
|
// did not send is not its to act on.
|
|
@@ -636,7 +822,8 @@ export async function evaluateCloudSemantic(input: SemanticInput, opts: CloudSem
|
|
|
636
822
|
// answers to demand. The local verdict is then an empty allow whatever the
|
|
637
823
|
// answers — `decide` with nothing selected reads no per-policy answer, and
|
|
638
824
|
// no warning can fire without one.
|
|
639
|
-
const shortCircuit =
|
|
825
|
+
const shortCircuit =
|
|
826
|
+
kept.length === 0 && reply.asked.length === 0 && reply.verdict.outcomes.length === 0 && reply.observedAsked.length === 0;
|
|
640
827
|
const answers = shortCircuit ? presentAnswers(response) : readAnswers(expectedQuestions(request, prepared, dropped), response);
|
|
641
828
|
|
|
642
829
|
const local =
|
|
@@ -664,6 +851,10 @@ export async function evaluateCloudSemantic(input: SemanticInput, opts: CloudSem
|
|
|
664
851
|
asked: reply.asked,
|
|
665
852
|
droppedLocal: [...dropped].map((name) => ({ name, packId: own.get(name)?.origin?.packId ?? null })),
|
|
666
853
|
droppedCloud: reply.droppedCloud,
|
|
854
|
+
answers: cloudCheckAnswers(response, [...reply.asked, ...reply.observedAsked]),
|
|
855
|
+
observed: reply.observed,
|
|
856
|
+
observedAsked: reply.observedAsked,
|
|
857
|
+
observedOutcomes,
|
|
667
858
|
},
|
|
668
859
|
};
|
|
669
860
|
} catch (err) {
|
|
@@ -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") {
|