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
|
@@ -91,7 +91,7 @@ export const SEMANTIC_POLICY_NAMES = [
|
|
|
91
91
|
* A pack that declares its own `semantic` entries replaces
|
|
92
92
|
* `SEMANTIC_POLICIES` wholesale on the machine that installed it, so the names
|
|
93
93
|
* a `reviewedBy` may use there are the pack's. Callers that know which set is
|
|
94
|
-
* live pass it (`effectiveReviewerNames()` in `
|
|
94
|
+
* live pass it (`effectiveReviewerNames()` in `effective-reviewers.ts`, which reads
|
|
95
95
|
* the manifest and imports nothing from `semantic/`); everyone else gets this
|
|
96
96
|
* one, which is what a machine with no pack runs.
|
|
97
97
|
*/
|
|
@@ -124,6 +124,8 @@ const isName = (n: unknown): n is string => typeof n === "string" && n.length >
|
|
|
124
124
|
export function resolvePolicyAuthority(
|
|
125
125
|
decl: AuthorityDeclaration | undefined,
|
|
126
126
|
knownReviewers: ReadonlySet<string> = SEMANTIC_REVIEWER_NAMES,
|
|
127
|
+
/** Names left out of `knownReviewers` because packs declare them differently, with those packs. */
|
|
128
|
+
contested?: ReadonlyMap<string, string[]>,
|
|
127
129
|
): ResolvedAuthority {
|
|
128
130
|
const declaredReviewable = decl?.authority === "reviewable";
|
|
129
131
|
if (!decl || effectiveAuthority(decl) === "hard") {
|
|
@@ -136,16 +138,28 @@ export function resolvePolicyAuthority(
|
|
|
136
138
|
}
|
|
137
139
|
const unknown = names.filter((n) => !knownReviewers.has(n));
|
|
138
140
|
if (unknown.length > 0) {
|
|
139
|
-
return {
|
|
140
|
-
authority: "hard",
|
|
141
|
-
downgraded:
|
|
142
|
-
`reviewedBy names ${unknown.map((n) => JSON.stringify(n)).join(", ")}, ` +
|
|
143
|
-
`which ${unknown.length === 1 ? "is not a semantic policy" : "are not semantic policies"} in this build`,
|
|
144
|
-
};
|
|
141
|
+
return { authority: "hard", downgraded: `reviewedBy names ${unknown.map((n) => whyUnknown(n, knownReviewers, contested)).join("; ")}` };
|
|
145
142
|
}
|
|
146
143
|
return { authority: "reviewable", reviewedBy: [...new Set(names)] };
|
|
147
144
|
}
|
|
148
145
|
|
|
146
|
+
/**
|
|
147
|
+
* The rule that kept one name out. "Not in this build" is only true of the
|
|
148
|
+
* compiled-in set: a pack's own checks, or a name two packs disagree on, is a
|
|
149
|
+
* different reason, and the author reading it has a different fix.
|
|
150
|
+
*/
|
|
151
|
+
function whyUnknown(name: string, known: ReadonlySet<string>, contested?: ReadonlyMap<string, string[]>): string {
|
|
152
|
+
// Quoted with JSON.stringify: a pack's name reaches the hook's stderr, so a
|
|
153
|
+
// control character in it must not reach the terminal raw.
|
|
154
|
+
const quoted = JSON.stringify(name);
|
|
155
|
+
const claimants = contested?.get(name);
|
|
156
|
+
if (claimants) return `${quoted}, which packs ${claimants.join(" and ")} declare differently, so it is asked for neither`;
|
|
157
|
+
if (known === SEMANTIC_REVIEWER_NAMES) return `${quoted}, which is not a semantic policy in this build`;
|
|
158
|
+
const sorted = [...known].sort();
|
|
159
|
+
const listed = sorted.length > 6 ? `${sorted.slice(0, 6).join(", ")} and ${sorted.length - 6} more` : sorted.join(", ");
|
|
160
|
+
return `${quoted}, which is not among the Jev checks it is judged against (${listed})`;
|
|
161
|
+
}
|
|
162
|
+
|
|
149
163
|
function whyHard(decl: AuthorityDeclaration): string {
|
|
150
164
|
if (decl.alwaysOn === true) return "it is alwaysOn, and the self-protection guard is never reviewable";
|
|
151
165
|
if (!Array.isArray(decl.reviewedBy) || decl.reviewedBy.length === 0) {
|
|
@@ -238,37 +252,76 @@ export function manifestAuthority(
|
|
|
238
252
|
* So it resolves toward HARD, the way the collapse resolves the effect toward
|
|
239
253
|
* enforce: reviewable only when EVERY declaration resolves to reviewable, and
|
|
240
254
|
* then through the UNION of their checks (more checks that must all come back
|
|
241
|
-
* clear is stricter, never weaker).
|
|
242
|
-
*
|
|
255
|
+
* clear is stricter, never weaker).
|
|
256
|
+
*
|
|
257
|
+
* ## What the merged record may carry, and why it is the RESOLUTION
|
|
258
|
+
*
|
|
259
|
+
* Nothing here copies a declaration through. It used to keep the first one that
|
|
260
|
+
* was not reviewable, so that a refused `reviewable` would be reported again at
|
|
261
|
+
* registration — and that was the hole: a declaration is only ever resolved
|
|
262
|
+
* against the reviewer set of whoever is looking, so a `reviewable` naming a
|
|
263
|
+
* PACK's own check resolved to hard here, against the builtin set, and was
|
|
264
|
+
* copied forward verbatim. `registerPolicy` then resolved that same raw
|
|
265
|
+
* declaration against `effectiveReviewerNames()`, which does have the pack's
|
|
266
|
+
* check — and the policy registered reviewable, with a hard peer behind the same
|
|
267
|
+
* artifact overruled by nothing more than which pack was listed first.
|
|
268
|
+
*
|
|
269
|
+
* Two changes close it. The reviewer set is now a PARAMETER, so the merge judges
|
|
270
|
+
* by the same names registration will; and a non-reviewable merge emits
|
|
271
|
+
* `{ authority: "hard" }` — the resolved value — rather than the fields it was
|
|
272
|
+
* asked for. Either alone would have been enough for the case above; both,
|
|
273
|
+
* because the first is an agreement between two callers and the second holds
|
|
274
|
+
* whether or not they agree.
|
|
275
|
+
*
|
|
276
|
+
* The reason a refused declaration carried is not lost: it comes back as
|
|
277
|
+
* `refused`, for the caller to say through {@link refusedAuthorityWarning} at
|
|
278
|
+
* the point where it knows which artifact and which packs are involved.
|
|
243
279
|
*
|
|
244
280
|
* Returns `record` itself when its own fields already say that, and a copy with
|
|
245
281
|
* only the two authority fields replaced otherwise. `overruled` is true when
|
|
246
282
|
* some declaration asked for reviewable, validly, and did not get it.
|
|
283
|
+
*
|
|
284
|
+
* @param knownReviewers - the checks that can be asked on this machine. Callers
|
|
285
|
+
* that merge what will be REGISTERED must pass `effectiveReviewerNames()`;
|
|
286
|
+
* the default is this build's compiled-in set, which is what a machine with no
|
|
287
|
+
* pack runs.
|
|
247
288
|
*/
|
|
248
289
|
export function withMergedAuthority<T extends AuthorityFields>(
|
|
249
290
|
record: T,
|
|
250
291
|
declarations: ReadonlyArray<AuthorityFields>,
|
|
251
|
-
|
|
252
|
-
|
|
292
|
+
knownReviewers: ReadonlySet<string> = SEMANTIC_REVIEWER_NAMES,
|
|
293
|
+
): { merged: T; overruled: boolean; refused?: string } {
|
|
294
|
+
const resolved = declarations.map((d) => resolvePolicyAuthority(d, knownReviewers));
|
|
253
295
|
const allReviewable = resolved.length > 0 && resolved.every((r) => r.authority === "reviewable");
|
|
296
|
+
// The declaration that decided it is hard. Its own fields are not carried —
|
|
297
|
+
// only whether it said anything at all, because a record that declared
|
|
298
|
+
// nothing is already hard and rewriting it to say so would touch a record the
|
|
299
|
+
// collapse has no business touching.
|
|
300
|
+
const decided = declarations[resolved.findIndex((r) => r.authority !== "reviewable")] ?? {};
|
|
254
301
|
const fields: AuthorityFields = allReviewable
|
|
255
302
|
? { authority: "reviewable", reviewedBy: [...new Set(resolved.flatMap((r) => r.reviewedBy ?? []))] }
|
|
256
|
-
:
|
|
303
|
+
: decided.authority === undefined
|
|
304
|
+
? {}
|
|
305
|
+
: { authority: "hard" };
|
|
257
306
|
const overruled = !allReviewable && resolved.some((r) => r.authority === "reviewable");
|
|
307
|
+
const refused = resolved.find((r) => r.downgraded !== undefined)?.downgraded;
|
|
308
|
+
const extra = refused === undefined ? {} : { refused };
|
|
258
309
|
const same =
|
|
259
310
|
record.authority === fields.authority &&
|
|
260
311
|
JSON.stringify(record.reviewedBy) === JSON.stringify(fields.reviewedBy);
|
|
261
|
-
if (same) return { merged: record, overruled };
|
|
312
|
+
if (same) return { merged: record, overruled, ...extra };
|
|
262
313
|
const { authority: _a, reviewedBy: _r, ...rest } = record;
|
|
263
|
-
return { merged: { ...rest, ...fields } as T, overruled };
|
|
314
|
+
return { merged: { ...rest, ...fields } as T, overruled, ...extra };
|
|
264
315
|
}
|
|
265
316
|
|
|
266
|
-
/**
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
317
|
+
/**
|
|
318
|
+
* The warning for a `reviewable` declaration that was not honored. One
|
|
319
|
+
* phrasing, wherever it is noticed — registration for a policy that reaches it
|
|
320
|
+
* with its declaration intact, and the loader for one the collapse hardened
|
|
321
|
+
* before it got there.
|
|
322
|
+
*/
|
|
323
|
+
export function refusedAuthorityWarning(subject: string, reason: string): string {
|
|
324
|
+
return `${subject} asks to be reviewable, but ${reason} — it stays hard`;
|
|
272
325
|
}
|
|
273
326
|
|
|
274
327
|
const warnedAuthority = new Set<string>();
|
|
@@ -457,13 +457,14 @@ export const POLICY_CATALOG: PolicyCatalogEntry[] = [
|
|
|
457
457
|
// policy off on every machine that configured Jev, while the mark said
|
|
458
458
|
// otherwise.
|
|
459
459
|
//
|
|
460
|
-
// The
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
//
|
|
460
|
+
// The engine has since changed under that argument: a reviewer that FIRES
|
|
461
|
+
// with nobody consenting now keeps the block (`combine.ts`, "A check that
|
|
462
|
+
// fired without consent keeps the floor"), so an instruct-only reviewer is
|
|
463
|
+
// no longer "off". `block-read-outside-cwd`, which also has only an
|
|
464
|
+
// instruct reviewer, stays reviewable on exactly that rule: it clears when
|
|
465
|
+
// the read is requested or the check finds nothing, and keeps the deny on
|
|
466
|
+
// an unrequested read it flags. Making this one reviewable again is a
|
|
467
|
+
// product call nobody has made.
|
|
467
468
|
authority: "hard",
|
|
468
469
|
defaultEnabled: false,
|
|
469
470
|
category: "Git",
|
|
@@ -526,7 +527,8 @@ export const POLICY_CATALOG: PolicyCatalogEntry[] = [
|
|
|
526
527
|
// question as written.
|
|
527
528
|
//
|
|
528
529
|
// A named check that is asked and does not fire answers "no concern", and
|
|
529
|
-
// that CLEARS (`combine.ts`, "A
|
|
530
|
+
// that CLEARS (`combine.ts`, "A check that fired without consent keeps the
|
|
531
|
+
// floor"). So
|
|
530
532
|
// pairing this policy with `destructive-deletion` would not hand the
|
|
531
533
|
// decision to Jev — it would switch the policy off on every machine that
|
|
532
534
|
// configured Jev, which is exactly the `block-work-on-main` mistake. The
|
|
@@ -17,7 +17,16 @@ export { packPolicyParamKey } from "./pack-param-key";
|
|
|
17
17
|
import { DEFAULT_POLICY_NAMESPACE, getPoliciesForEvent } from "./policy-registry";
|
|
18
18
|
// Type-only apart from the two pure functions: the semantic evaluator itself is
|
|
19
19
|
// loaded by handler.ts, lazily, and only when a Jev config exists.
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
combineTwoTier,
|
|
22
|
+
regexOnly,
|
|
23
|
+
type FinalVerdict,
|
|
24
|
+
type JevActivityFields,
|
|
25
|
+
type JevReview,
|
|
26
|
+
type RegexVerdict,
|
|
27
|
+
type ShadowVerdict,
|
|
28
|
+
type TwoTierReview,
|
|
29
|
+
} from "./semantic/combine";
|
|
21
30
|
// The code, not a copy of the string: the log line below turns on it, and a
|
|
22
31
|
// rename should break the build here rather than quietly print "unavailable"
|
|
23
32
|
// for a call Jev actually answered.
|
|
@@ -51,6 +60,10 @@ export interface EvaluationResult {
|
|
|
51
60
|
activity: JevActivityFields;
|
|
52
61
|
/** True when Jev's own verdict is what `policyName` names. */
|
|
53
62
|
decidedByJev: boolean;
|
|
63
|
+
/** The pack a deciding Jev check came from; only with `decidedByJev`. */
|
|
64
|
+
jevOrigin?: { packId: string; packVersion?: string };
|
|
65
|
+
/** Shadow mode: Jev's own deny/instruct, recorded as a "would have" (see `ShadowVerdict`). */
|
|
66
|
+
shadowVerdict?: ShadowVerdict;
|
|
54
67
|
};
|
|
55
68
|
}
|
|
56
69
|
|
|
@@ -233,8 +246,13 @@ export async function evaluatePolicies(
|
|
|
233
246
|
);
|
|
234
247
|
}
|
|
235
248
|
return {
|
|
236
|
-
...formatVerdict(eventType, session, toolName, combined.final),
|
|
237
|
-
twoTier: {
|
|
249
|
+
...formatVerdict(eventType, session, toolName, combined.final, combined.decidedByJev),
|
|
250
|
+
twoTier: {
|
|
251
|
+
activity: combined.activity,
|
|
252
|
+
decidedByJev: combined.decidedByJev,
|
|
253
|
+
...(combined.decidedByJev && review.kind === "answered" && review.origin ? { jevOrigin: review.origin } : {}),
|
|
254
|
+
...(combined.shadowVerdict ? { shadowVerdict: combined.shadowVerdict } : {}),
|
|
255
|
+
},
|
|
238
256
|
};
|
|
239
257
|
}
|
|
240
258
|
|
|
@@ -350,16 +368,17 @@ async function collectVerdicts(
|
|
|
350
368
|
return { verdicts, hardDenied: false };
|
|
351
369
|
}
|
|
352
370
|
|
|
353
|
-
/** A verdict → the calling CLI's response. Which engine decided
|
|
371
|
+
/** A verdict → the calling CLI's response. Which engine decided only changes whom a deny credits. */
|
|
354
372
|
function formatVerdict(
|
|
355
373
|
eventType: HookEventType,
|
|
356
374
|
session: SessionMetadata | undefined,
|
|
357
375
|
toolName: string | undefined,
|
|
358
376
|
final: FinalVerdict,
|
|
377
|
+
byJev = false,
|
|
359
378
|
): EvaluationResult {
|
|
360
379
|
if (final.decision === "deny") {
|
|
361
380
|
const [decider] = final.entries;
|
|
362
|
-
return formatDeny(eventType, session, toolName, decider.policyName, decider.reason);
|
|
381
|
+
return formatDeny(eventType, session, toolName, decider.policyName, decider.reason, byJev);
|
|
363
382
|
}
|
|
364
383
|
if (final.decision === "instruct") return formatInstruct(eventType, session, final.entries);
|
|
365
384
|
if (final.entries.length > 0) return formatAllow(eventType, session, final.entries);
|
|
@@ -372,6 +391,7 @@ function formatDeny(
|
|
|
372
391
|
toolName: string | undefined,
|
|
373
392
|
policyName: string,
|
|
374
393
|
reason: string,
|
|
394
|
+
byJev = false,
|
|
375
395
|
): EvaluationResult {
|
|
376
396
|
// Pick a noun for the deny message that fits the event type. Tool events
|
|
377
397
|
// get the tool name; non-tool events (UserPromptSubmit, SessionStart,
|
|
@@ -391,7 +411,10 @@ function formatDeny(
|
|
|
391
411
|
} else {
|
|
392
412
|
displayTool = "operation";
|
|
393
413
|
}
|
|
394
|
-
|
|
414
|
+
// Jev's reason is its own sentences, ending in "."; no user configured the check.
|
|
415
|
+
const blockedMessage = byJev
|
|
416
|
+
? `Blocked ${displayTool} by failproofai because: ${reason.replace(/[.\s]+$/, "")}, as flagged by Jev semantic review`
|
|
417
|
+
: `Blocked ${displayTool} by failproofai because: ${reason}, as per the policy configured by the user`;
|
|
395
418
|
|
|
396
419
|
// Cursor's hook protocol expects a flat `{permission, user_message,
|
|
397
420
|
// agent_message}` shape for any blocking decision, regardless of which
|
|
@@ -722,7 +745,7 @@ function formatDeny(
|
|
|
722
745
|
hookEventName: eventType,
|
|
723
746
|
decision: {
|
|
724
747
|
behavior: "deny",
|
|
725
|
-
message:
|
|
748
|
+
message: blockedMessage,
|
|
726
749
|
},
|
|
727
750
|
},
|
|
728
751
|
};
|
|
@@ -777,7 +800,7 @@ function formatDeny(
|
|
|
777
800
|
const response = {
|
|
778
801
|
hookSpecificOutput: {
|
|
779
802
|
hookEventName: eventType,
|
|
780
|
-
additionalContext:
|
|
803
|
+
additionalContext: blockedMessage,
|
|
781
804
|
},
|
|
782
805
|
};
|
|
783
806
|
return {
|
|
@@ -127,14 +127,15 @@ export function getPoliciesForEvent(
|
|
|
127
127
|
return result;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
/** `cli`: the agent this pass registers for, which scopes the Jev reviewer set. */
|
|
131
|
+
export function clearPolicies(cli?: string): void {
|
|
131
132
|
const g = globalThis as GlobalWithRegistry;
|
|
132
133
|
g[REGISTRY_KEY] = [];
|
|
133
134
|
setIndexCache(null);
|
|
134
135
|
// The reviewer set describes the policies that are about to be registered, so
|
|
135
136
|
// it is rebuilt with them. Dropping it here is also what keeps one read per
|
|
136
137
|
// evaluation instead of one per policy.
|
|
137
|
-
forgetEffectiveReviewerNames();
|
|
138
|
+
forgetEffectiveReviewerNames(cli);
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
/**
|
|
@@ -49,18 +49,26 @@
|
|
|
49
49
|
* - **Policies from the user's own files.** A custom hook declares its
|
|
50
50
|
* authority inside the module, so counting one means importing and running
|
|
51
51
|
* somebody's JavaScript — from a settings page, on every read. The count of
|
|
52
|
-
*
|
|
53
|
-
*
|
|
52
|
+
* custom policy FILES the hook loads (configured paths and the project and
|
|
53
|
+
* user convention files) is reported instead, the summary says in words that
|
|
54
|
+
* those policies were not counted, and no "never" is diagnosed beside them.
|
|
54
55
|
* - **A session pause.** It suspends local policy for minutes, and a warning
|
|
55
56
|
* about a policy set that is coming back shortly would be noise.
|
|
56
57
|
*/
|
|
57
58
|
import { readActiveCloudManagedPolicies } from "./cloud-managed-policies";
|
|
58
|
-
import {
|
|
59
|
-
import {
|
|
60
|
-
import {
|
|
59
|
+
import { jevPacks } from "./effective-reviewers";
|
|
60
|
+
import { resolve } from "node:path";
|
|
61
|
+
import { discoverPolicyFiles } from "./custom-hooks-loader";
|
|
62
|
+
import { customPoliciesDir } from "./fp-home";
|
|
63
|
+
import { configuredCustomPolicyPaths, findProjectConfigDir, readMergedHooksConfig } from "./hooks-config";
|
|
64
|
+
import { hasInstalledPacks, readInstalledPacks } from "./pack-manifest";
|
|
65
|
+
import { resolvePolicyAuthority, SEMANTIC_REVIEWER_NAMES } from "./policy-authority";
|
|
61
66
|
import { POLICY_CATALOG } from "./policy-catalog";
|
|
62
67
|
import { normalizePolicyName } from "./policy-registry";
|
|
63
68
|
import type { HooksConfig } from "./policy-types";
|
|
69
|
+
// A CLI and dashboard module, never on the hook path, so it may ask the
|
|
70
|
+
// resolver itself (`pack-cli.ts` does the same).
|
|
71
|
+
import { semanticPoliciesFromPacks } from "./semantic/pack-policies";
|
|
64
72
|
|
|
65
73
|
/** Anything that carries an authority declaration: a catalog entry, a pack entry, a cloud record. */
|
|
66
74
|
export interface AuthorityRecord {
|
|
@@ -74,7 +82,10 @@ export interface ReviewableCoverage {
|
|
|
74
82
|
enabled: number;
|
|
75
83
|
/** Of those, the ones Jev may clear a deny or an instruct from. */
|
|
76
84
|
reviewable: number;
|
|
77
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* Custom policy files the hook loads — configured paths and the project and
|
|
87
|
+
* user convention files — whose policies are not in the counts above.
|
|
88
|
+
*/
|
|
78
89
|
customFiles: number;
|
|
79
90
|
}
|
|
80
91
|
|
|
@@ -144,14 +155,21 @@ export function surveyReviewableCoverage(cwd?: string): ReviewableCoverage {
|
|
|
144
155
|
* A pack's `enabled` selection is deliberately not applied: it narrows which
|
|
145
156
|
* of its REGEX policies register, and its semantic set is not selectable.
|
|
146
157
|
*/
|
|
147
|
-
|
|
158
|
+
let reviewers: ReadonlySet<string> | undefined;
|
|
148
159
|
try {
|
|
149
160
|
packsInstalled = hasInstalledPacks();
|
|
150
|
-
|
|
161
|
+
const packs = readInstalledPacks().packs;
|
|
162
|
+
for (const pack of packs) {
|
|
151
163
|
const selected = pack.enabled;
|
|
152
164
|
records.push(...(selected ? pack.policies.filter((p) => selected.includes(p.name)) : pack.policies));
|
|
153
|
-
for (const entry of packSemantic(pack)) packReviewers.add(entry.name);
|
|
154
165
|
}
|
|
166
|
+
// The names of the questions a request will actually carry: only the packs
|
|
167
|
+
// whose checks take part (an observe pack's do not), minus a reserved or
|
|
168
|
+
// contested name AND minus a check the question budget drops — which the
|
|
169
|
+
// hook path's manifest-only `reviewerNamesFor` cannot see. Anything else and
|
|
170
|
+
// the panel and `jev status` promise a clear that cannot happen.
|
|
171
|
+
const asked = semanticPoliciesFromPacks(jevPacks(packs));
|
|
172
|
+
reviewers = asked.fromPack ? new Set(asked.policies.map((p) => p.name)) : SEMANTIC_REVIEWER_NAMES;
|
|
155
173
|
} catch {
|
|
156
174
|
// An unreadable manifest enforces nothing; `readInstalledPacks` already
|
|
157
175
|
// reports that to the hook log on the path that cares.
|
|
@@ -178,14 +196,23 @@ export function surveyReviewableCoverage(cwd?: string): ReviewableCoverage {
|
|
|
178
196
|
// Same fail-open as the handler's own read of this file.
|
|
179
197
|
}
|
|
180
198
|
|
|
199
|
+
// Every file `loadAllCustomHooks` would load, by resolved path so a file
|
|
200
|
+
// both named and discovered (or a project root that IS home) counts once.
|
|
181
201
|
let customFiles = 0;
|
|
182
202
|
try {
|
|
183
|
-
|
|
203
|
+
// Relative paths against the project root, as the loader resolves them.
|
|
204
|
+
const projectRoot = findProjectConfigDir(cwd ?? process.cwd());
|
|
205
|
+
const files = new Set(configuredCustomPolicyPaths(config).map((p) => resolve(projectRoot, p)));
|
|
206
|
+
if (config.customPoliciesEnabled !== false) {
|
|
207
|
+
const projectDir = resolve(projectRoot, ".failproofai", "policies");
|
|
208
|
+
for (const dir of [projectDir, customPoliciesDir()]) for (const f of discoverPolicyFiles(dir)) files.add(f);
|
|
209
|
+
}
|
|
210
|
+
customFiles = files.size;
|
|
184
211
|
} catch {
|
|
185
212
|
customFiles = 0;
|
|
186
213
|
}
|
|
187
214
|
|
|
188
|
-
return { ...countReviewable(records,
|
|
215
|
+
return { ...countReviewable(records, reviewers), customFiles };
|
|
189
216
|
}
|
|
190
217
|
|
|
191
218
|
/**
|
|
@@ -219,7 +246,9 @@ export function reviewableSummary(coverage: ReviewableCoverage): string {
|
|
|
219
246
|
* did not ask.
|
|
220
247
|
*/
|
|
221
248
|
export function reviewableProblem(coverage: ReviewableCoverage): string | null {
|
|
222
|
-
|
|
249
|
+
// Policies from the user's own files were not counted and may be reviewable,
|
|
250
|
+
// so "never" cannot be said honestly; the summary already says what was skipped.
|
|
251
|
+
if (coverage.enabled === 0 || coverage.reviewable > 0 || coverage.customFiles > 0) return null;
|
|
223
252
|
return (
|
|
224
253
|
"Jev can add a deny or an instruction on this machine, but it can never clear one. " +
|
|
225
254
|
"No enabled policy is marked reviewable — a policy pack published before this release carries no such marks — " +
|
|
@@ -47,7 +47,8 @@ export interface RegisteredPolicy {
|
|
|
47
47
|
authority?: PolicyAuthority;
|
|
48
48
|
/**
|
|
49
49
|
* The semantic policies (`src/hooks/semantic/policies.ts`) that must all be
|
|
50
|
-
* asked, and
|
|
50
|
+
* asked, and each clear (see {@link PolicyAuthority}), before Jev clears this
|
|
51
|
+
* policy.
|
|
51
52
|
*/
|
|
52
53
|
reviewedBy?: string[];
|
|
53
54
|
}
|
|
@@ -58,8 +59,9 @@ export interface RegisteredPolicy {
|
|
|
58
59
|
* - `hard`: final. Jev can never clear it.
|
|
59
60
|
* - `reviewable`: Jev may clear it, but only through the semantic policies
|
|
60
61
|
* named in `reviewedBy`, and only when every one of them was actually asked
|
|
61
|
-
* and
|
|
62
|
-
*
|
|
62
|
+
* and each answered `none`, `overridden`, or a deny the human's task
|
|
63
|
+
* softened to a warning (`combine.ts`, "A check that fired without consent
|
|
64
|
+
* keeps the floor").
|
|
63
65
|
* A named check that was NOT asked always keeps the verdict standing.
|
|
64
66
|
*/
|
|
65
67
|
export type PolicyAuthority = "hard" | "reviewable";
|