failproofai 1.0.8 → 1.0.9-beta.1
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 +5 -5
- 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/build-manifest.json +2 -2
- 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/build-manifest.json +2 -2
- 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.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/build-manifest.json +2 -2
- 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 +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/build-manifest.json +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/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +4 -4
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
- 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/build-manifest.json +2 -2
- 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/build-manifest.json +2 -2
- 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/build-manifest.json +2 -2
- 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/[root-of-the-server]__0o07qi9._.js +1 -1
- 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 +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03m2iw6._.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]__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]__12e7nhs._.js → [root-of-the-server]__1-bm5zf._.js} +1 -1
- 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]__15578wp._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +2 -2
- 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/_11ilmst._.js +23 -0
- package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{_0-vcssj._.js → _1jatlmf._.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 +3 -3
- 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_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- 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 +23 -23
- package/.next/standalone/.next/static/chunks/{06jxjnw8bfczc.js → 0542cuk_va8g1.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0wbjy0zo-m7is.js → 08ckontevw-0w.js} +1 -1
- package/.next/standalone/.next/static/chunks/0pefjgvu4x-sx.js +1 -0
- package/.next/standalone/.next/static/chunks/{21uv-uusw329x.js → 1-d48e9k-7e1a.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0m-9d6yn9hx4j.js → 1r74hmr1wm_sd.js} +1 -1
- package/.next/standalone/.next/static/chunks/1uec0n0lcx5h1.js +1 -0
- package/.next/standalone/.next/static/chunks/21-pqc3-l9ak2.js +6 -0
- package/.next/standalone/.next/static/chunks/21qlo00jis4w-.js +1 -0
- package/.next/standalone/.next/static/chunks/2n00c3zqyloy9.js +1 -0
- package/.next/standalone/.next/static/chunks/{0bhidk90e-07f.css → 3_e_wg5_oay87.css} +1 -1
- package/.next/standalone/.next/static/chunks/{3c3qbmosdjl6w.js → 3tv107_for_n7.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2qdpj67x6ifk_.js → 3vp54x0dyrrm6.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-38nzuvp95_2rv.js → turbopack-11dn2pegmg9_9.js} +1 -1
- package/.next/standalone/app/actions/get-jev-config.ts +2 -2
- package/.next/standalone/app/actions/update-jev-config.ts +12 -9
- package/.next/standalone/app/components/jev-notices.tsx +10 -10
- package/.next/standalone/app/settings/jev-panel.tsx +11 -11
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +2 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +26 -0
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +27 -0
- package/.next/standalone/fp-cloud-cli/uv.lock +3 -3
- package/.next/standalone/hermes-plugin/README.md +18 -8
- package/.next/standalone/node_modules/@next/env/package.json +1 -1
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +10 -10
- package/.next/standalone/package.json +12 -12
- package/.next/standalone/sdk/python/uv.lock +18 -18
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +33 -7
- package/dist/cli.mjs +1385 -1158
- package/dist/worker.mjs +59 -375
- package/hermes-plugin/README.md +18 -8
- package/package.json +12 -12
- package/scripts/build-policy-pack.mjs +6 -9
- package/src/hooks/cloud-connection.ts +12 -6
- package/src/hooks/custom-hooks-loader.ts +1 -1
- package/src/hooks/effective-reviewers.ts +50 -43
- package/src/hooks/handler.ts +25 -16
- package/src/hooks/hermes-update.ts +74 -0
- package/src/hooks/hook-activity-store.ts +3 -3
- package/src/hooks/integrations.ts +472 -68
- package/src/hooks/jev-activity.ts +8 -3
- package/src/hooks/jev-cli.ts +17 -17
- package/src/hooks/jev-cloud-connection.ts +4 -4
- package/src/hooks/manager.ts +3 -2
- package/src/hooks/pack-cli.ts +24 -23
- package/src/hooks/pack-manifest.ts +25 -2
- package/src/hooks/pack-store.ts +2 -2
- package/src/hooks/policy-authority.ts +51 -32
- package/src/hooks/policy-evaluator.ts +5 -5
- package/src/hooks/policy-registry.ts +5 -7
- package/src/hooks/policy-reviewability.ts +30 -12
- package/src/hooks/policy-types.ts +4 -4
- package/src/hooks/safe-config-write.ts +119 -0
- package/src/hooks/semantic/combine.ts +13 -13
- package/src/hooks/semantic/evaluator.ts +5 -5
- package/src/hooks/semantic/jev-client.ts +1 -1
- package/src/hooks/semantic/jev-config.ts +21 -12
- package/src/hooks/semantic/jev-review.ts +3 -3
- package/src/hooks/semantic/jev-stats.ts +17 -17
- package/src/hooks/semantic/pack-policies.ts +48 -48
- package/src/hooks/semantic/policies.ts +13 -425
- package/src/hooks/semantic/precondition-names.ts +1 -1
- package/src/hooks/semantic/preconditions.ts +1 -1
- package/src/hooks/semantic/types.ts +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13t1zkw._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_13kfn90._.js +0 -23
- package/.next/standalone/.next/static/chunks/06dnzbolj00mc.js +0 -6
- package/.next/standalone/.next/static/chunks/16f3fa-lx38hk.js +0 -1
- package/.next/standalone/.next/static/chunks/2g2tki08kdhie.js +0 -1
- package/.next/standalone/.next/static/chunks/3-nbtkhg9y-1j.js +0 -1
- package/.next/standalone/.next/static/chunks/355km0ihuqo1p.js +0 -1
- /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → ozuWoyIwOTqeUUjKqOKiH}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → ozuWoyIwOTqeUUjKqOKiH}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → ozuWoyIwOTqeUUjKqOKiH}/_ssgManifest.js +0 -0
|
@@ -57,7 +57,7 @@ export const JEV_ACTIVITY_KEYS = [
|
|
|
57
57
|
|
|
58
58
|
const EVALUATORS = new Set(["jev", "jev-fallback"]);
|
|
59
59
|
const DECISIONS = new Set(["allow", "instruct", "deny"]);
|
|
60
|
-
const MODES = new Set(["
|
|
60
|
+
const MODES = new Set(["observe", "enforce"]);
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* A reason's leading code — lowercase kebab-case — either alone or followed by
|
|
@@ -236,7 +236,12 @@ export function sanitizeJevActivity<T extends JevActivityFields>(entry: T): T {
|
|
|
236
236
|
|
|
237
237
|
if (!(typeof out.evaluator === "string" && EVALUATORS.has(out.evaluator))) drop("evaluator");
|
|
238
238
|
if (!(typeof out.jevDecision === "string" && DECISIONS.has(out.jevDecision))) drop("jevDecision");
|
|
239
|
+
// A mode this build does not know (a row written before the mode was named
|
|
240
|
+
// `observe`) cannot say whether its clears were enforced, so they are not
|
|
241
|
+
// counted as anything: dropping only the mode would file them as enforced.
|
|
242
|
+
const unknownMode = out.jevMode !== undefined && !(typeof out.jevMode === "string" && MODES.has(out.jevMode));
|
|
239
243
|
if (!(typeof out.jevMode === "string" && MODES.has(out.jevMode))) drop("jevMode");
|
|
244
|
+
if (unknownMode) drop("jevCleared");
|
|
240
245
|
|
|
241
246
|
if (Array.isArray(out.jevCleared)) {
|
|
242
247
|
const names = [
|
|
@@ -374,9 +379,9 @@ export function describeJevActivity(raw: JevActivityFields): string[] | null {
|
|
|
374
379
|
if (e.jevDecision) facts.push(`Jev verdict: ${e.jevDecision}`);
|
|
375
380
|
const cleared = e.jevCleared ?? [];
|
|
376
381
|
if (cleared.length > 0) {
|
|
377
|
-
facts.push(`${e.jevMode === "
|
|
382
|
+
facts.push(`${e.jevMode === "observe" ? "would have cleared" : "cleared"} ${cleared.join(", ")}`);
|
|
378
383
|
}
|
|
379
|
-
if (e.jevMode === "
|
|
384
|
+
if (e.jevMode === "observe") facts.push("observe mode: the regex result was enforced");
|
|
380
385
|
if (e.jevLatencyMs !== undefined) facts.push(`${e.jevLatencyMs} ms`);
|
|
381
386
|
if (e.jevModel) facts.push(e.jevModel);
|
|
382
387
|
// Answered, but nothing this build can show (a verdict in a shape it cannot read).
|
package/src/hooks/jev-cli.ts
CHANGED
|
@@ -186,7 +186,7 @@ export const JEV_USAGE = [
|
|
|
186
186
|
"Usage:",
|
|
187
187
|
" failproofai jev --url <url> [--key-stdin | --token <token>] [options]",
|
|
188
188
|
" failproofai jev setup --provider <kind> [--key-stdin | --key-from-env] [options]",
|
|
189
|
-
" failproofai jev setup --provider failproofai [--mode off|
|
|
189
|
+
" failproofai jev setup --provider failproofai [--mode off|observe|enforce] (FailproofAI Cloud: no key, no URL)",
|
|
190
190
|
" failproofai jev status [--json]",
|
|
191
191
|
" failproofai jev test [--json]",
|
|
192
192
|
" failproofai jev models [--provider <kind>] [--url <base>] [--json]",
|
|
@@ -269,7 +269,7 @@ function modeLine(mode: NonNullable<JevConfig["mode"]>): string {
|
|
|
269
269
|
if (mode === "off") return "off — Jev is not asked at all; the regex policies decide alone";
|
|
270
270
|
return mode === "enforce"
|
|
271
271
|
? "enforce — Jev's verdicts apply: it may clear a reviewable policy's deny and add its own"
|
|
272
|
-
: "
|
|
272
|
+
: "observe — Jev is asked and logged; the regex result is what is enforced";
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
/**
|
|
@@ -458,12 +458,11 @@ export function jevStatsLines(stats: JevStats | null, opts: RenderOpts = {}): st
|
|
|
458
458
|
.join(", ");
|
|
459
459
|
const clears = byPolicy(stats.clearsByPolicy);
|
|
460
460
|
// `clearsByPolicy` counts clears that CHANGED an outcome, which only enforce
|
|
461
|
-
// mode can do; in
|
|
462
|
-
// `
|
|
463
|
-
// alone would tell
|
|
464
|
-
// turned
|
|
465
|
-
|
|
466
|
-
const shadowClears = byPolicy((stats as { shadowClearsByPolicy?: Record<string, number> }).shadowClearsByPolicy);
|
|
461
|
+
// mode can do; in observe mode every clear Jev would have made is in
|
|
462
|
+
// `observeClearsByPolicy` instead and this one is empty. Printing the first
|
|
463
|
+
// alone would tell an observe-mode user "cleared nothing" — the one number
|
|
464
|
+
// they turned observe mode on to watch.
|
|
465
|
+
const observeClears = byPolicy(stats.observeClearsByPolicy);
|
|
467
466
|
const ms = (v: number | null) => (v === null ? "—" : `${Math.round(v)} ms`);
|
|
468
467
|
return stack(
|
|
469
468
|
heading,
|
|
@@ -473,7 +472,7 @@ export function jevStatsLines(stats: JevStats | null, opts: RenderOpts = {}): st
|
|
|
473
472
|
["fell back to regex", `${pct(stats.fallbackRate)}${reasons ? ` (${reasons})` : ""}`],
|
|
474
473
|
["latency", `p50 ${ms(stats.latencyP50Ms)} · p95 ${ms(stats.latencyP95Ms)}`],
|
|
475
474
|
["cleared", clears || "nothing"],
|
|
476
|
-
...(
|
|
475
|
+
...(observeClears ? ([["would have cleared (observe)", observeClears]] as Array<[string, string]>) : []),
|
|
477
476
|
],
|
|
478
477
|
opts,
|
|
479
478
|
),
|
|
@@ -1206,11 +1205,10 @@ async function cloudSetup(values: Map<string, string>, bools: Set<string>, opts:
|
|
|
1206
1205
|
delete next.apiKey;
|
|
1207
1206
|
delete next.accountId;
|
|
1208
1207
|
if (!sameProvider && existing?.raw.timeoutMs !== undefined) next.timeoutMs = existing.raw.timeoutMs;
|
|
1209
|
-
//
|
|
1208
|
+
// Observe unless told otherwise, as `config --token` starts it: Jev on a new
|
|
1210
1209
|
// route is logged before it is allowed to clear anything. A mode already in a
|
|
1211
1210
|
// Cloud file is kept.
|
|
1212
|
-
|
|
1213
|
-
next.mode = mode;
|
|
1211
|
+
next.mode = values.get("--mode") ?? (sameProvider && typeof next.mode === "string" ? next.mode : "observe");
|
|
1214
1212
|
if (values.has("--timeout-ms")) {
|
|
1215
1213
|
const rawTimeout = values.get("--timeout-ms") as string;
|
|
1216
1214
|
const n = Number(rawTimeout);
|
|
@@ -1247,7 +1245,7 @@ async function cloudSetup(values: Map<string, string>, bools: Set<string>, opts:
|
|
|
1247
1245
|
]
|
|
1248
1246
|
: [
|
|
1249
1247
|
"Not saved: this machine is not connected to FailproofAI Cloud with a key that carries jev:evaluate.",
|
|
1250
|
-
"Connect it with one that does (the \"machine\" preset) — that also turns Jev on, in
|
|
1248
|
+
"Connect it with one that does (the \"machine\" preset) — that also turns Jev on, in observe mode, when there is no jev.json yet:",
|
|
1251
1249
|
]),
|
|
1252
1250
|
" failproofai config --token <key>",
|
|
1253
1251
|
"",
|
|
@@ -1332,7 +1330,7 @@ async function cloudSetup(values: Map<string, string>, bools: Set<string>, opts:
|
|
|
1332
1330
|
/**
|
|
1333
1331
|
* Where a file would send requests, for display, whether or not it loads. Only
|
|
1334
1332
|
* the routing fields are used — the key and model do not decide where requests
|
|
1335
|
-
* go — and
|
|
1333
|
+
* go — and observe mode lets a loopback http URL through validation. Null when
|
|
1336
1334
|
* it names nothing recognisable.
|
|
1337
1335
|
*/
|
|
1338
1336
|
/**
|
|
@@ -1366,7 +1364,7 @@ function displayOnlyCloudOrigin(routing: { provider?: unknown; baseUrl?: unknown
|
|
|
1366
1364
|
|
|
1367
1365
|
function namedEndpoint(raw: Record<string, unknown> | null): string | null {
|
|
1368
1366
|
if (!raw || typeof raw.provider !== "string" || !(JEV_PROVIDER_KINDS as readonly string[]).includes(raw.provider)) return null;
|
|
1369
|
-
const routing: Record<string, unknown> = { provider: raw.provider, apiKey: ENV_KEY_STAND_IN, mode: "
|
|
1367
|
+
const routing: Record<string, unknown> = { provider: raw.provider, apiKey: ENV_KEY_STAND_IN, mode: "observe", ...displayOnlyCloudOrigin(raw) };
|
|
1370
1368
|
if (raw.baseUrl !== undefined) routing.baseUrl = raw.baseUrl;
|
|
1371
1369
|
if (raw.accountId !== undefined) routing.accountId = raw.accountId;
|
|
1372
1370
|
try {
|
|
@@ -1499,6 +1497,8 @@ async function status(argv: string[], opts: RenderOpts): Promise<JevCliResult> {
|
|
|
1499
1497
|
enabled: coverage.enabled,
|
|
1500
1498
|
reviewable: coverage.reviewable,
|
|
1501
1499
|
customPolicyFiles: coverage.customFiles,
|
|
1500
|
+
// Checks an installed pack gives Jev to ask; 0 means Jev is inert.
|
|
1501
|
+
jevChecks: coverage.jevChecks,
|
|
1502
1502
|
problem: coverageProblem,
|
|
1503
1503
|
},
|
|
1504
1504
|
}
|
|
@@ -1599,7 +1599,7 @@ async function status(argv: string[], opts: RenderOpts): Promise<JevCliResult> {
|
|
|
1599
1599
|
],
|
|
1600
1600
|
opts,
|
|
1601
1601
|
),
|
|
1602
|
-
nextStep("failproofai jev setup --mode
|
|
1602
|
+
nextStep("failproofai jev setup --mode observe", "Switch it back on (observe logs Jev and keeps enforcing regex; enforce lets it clear), here or from the dashboard:", opts),
|
|
1603
1603
|
legacyNote,
|
|
1604
1604
|
jevStatsLines(stats, opts),
|
|
1605
1605
|
),
|
|
@@ -1786,7 +1786,7 @@ async function test(argv: string[], deps: JevCliDeps, opts: RenderOpts): Promise
|
|
|
1786
1786
|
: inspection.status === "key-missing"
|
|
1787
1787
|
? "failproofai jev setup --key-stdin < key-file"
|
|
1788
1788
|
: inspection.status === "off"
|
|
1789
|
-
? "failproofai jev setup --mode
|
|
1789
|
+
? "failproofai jev setup --mode observe"
|
|
1790
1790
|
: inspection.status === "not-connected" || inspection.status === "key-lacks-jev"
|
|
1791
1791
|
? "failproofai config --token <key>"
|
|
1792
1792
|
: "failproofai jev setup --provider <kind> --key-stdin";
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `config --token` with a key that carries `jev:evaluate` stores the key in the
|
|
5
5
|
* `jev` slot of `credentials.json` (`cloud-connection.ts`) and then, ONLY when
|
|
6
6
|
* this machine has no `jev.json` at all, writes one that turns Jev on through
|
|
7
|
-
* FailproofAI Cloud in
|
|
7
|
+
* FailproofAI Cloud in observe mode — logged, never enforced, until its owner
|
|
8
8
|
* says otherwise. Never under `--no-transcripts`: that connection asked for
|
|
9
9
|
* decisions only, so it stores the key and says Jev is available, and
|
|
10
10
|
* `jev setup --provider failproofai` is the opt-in. `config --disconnect`
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* status screen. A BYOK file keeps working without the Cloud and is not the
|
|
30
30
|
* Cloud's to delete, so it stays; so does a file this build cannot read, and a
|
|
31
31
|
* Cloud file switched off — `--mode off` is the owner's opt-out, and deleting
|
|
32
|
-
* it would let the next connect write a fresh
|
|
32
|
+
* it would let the next connect write a fresh observe-mode file. The
|
|
33
33
|
* file is moved aside before it is judged, so the file judged is the file
|
|
34
34
|
* deleted — see `removeCloudJevConfig`.
|
|
35
35
|
*
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
} from "./semantic/jev-config";
|
|
68
68
|
|
|
69
69
|
/** The mode a Cloud `jev.json` starts in (the user's decision: log first, enforce later). */
|
|
70
|
-
export const CLOUD_JEV_INITIAL_MODE = "
|
|
70
|
+
export const CLOUD_JEV_INITIAL_MODE = "observe" as const;
|
|
71
71
|
|
|
72
72
|
export type CloudJevConfigWrite =
|
|
73
73
|
| { status: "written"; path: string; mode: typeof CLOUD_JEV_INITIAL_MODE; baseUrl: string }
|
|
@@ -145,7 +145,7 @@ export function existingJevConfig(cloudBase: string): CloudJevConfigWrite | null
|
|
|
145
145
|
* Asked the way a hook asks (`inspectJevConfig`), so "on" here means a tool
|
|
146
146
|
* call's next Jev request really goes to FailproofAI Cloud.
|
|
147
147
|
*/
|
|
148
|
-
export function cloudJevRunningMode(): "
|
|
148
|
+
export function cloudJevRunningMode(): "observe" | "enforce" | null {
|
|
149
149
|
try {
|
|
150
150
|
const r = inspectJevConfig();
|
|
151
151
|
if (r.status !== "ok" || r.config.provider !== JEV_CLOUD_PROVIDER) return null;
|
package/src/hooks/manager.ts
CHANGED
|
@@ -28,7 +28,7 @@ import { customPoliciesDir, globalPolicyConfigFile } from "./fp-home";
|
|
|
28
28
|
import { readActiveCloudManagedPolicies } from "./cloud-managed-policies";
|
|
29
29
|
import { CORE_SOURCE, addPack, setPackPolicyEnabled } from "./pack-store";
|
|
30
30
|
import type { ResolvedPack } from "./pack-manifest";
|
|
31
|
-
import {
|
|
31
|
+
import { hasInstalledRegexPacks, readInstalledPacks } from "./pack-manifest";
|
|
32
32
|
import { packPolicyParamKey } from "./policy-evaluator";
|
|
33
33
|
import { probeDaemonPolicyEvaluation } from "./daemon-service";
|
|
34
34
|
import {
|
|
@@ -654,7 +654,8 @@ async function installHooksImpl(
|
|
|
654
654
|
//
|
|
655
655
|
// With a pack already installed the names are switched on individually
|
|
656
656
|
// instead, which is additive and touches nothing else.
|
|
657
|
-
|
|
657
|
+
// A Jev-only pack has no policy to switch on, so it does not count here.
|
|
658
|
+
if (!hasInstalledRegexPacks()) {
|
|
658
659
|
// Fetched, not unpacked from this package: there is no copy in here any
|
|
659
660
|
// more. That makes this the one path in `policies --install` that needs
|
|
660
661
|
// the network, so its failure is reported rather than thrown — the names
|
package/src/hooks/pack-cli.ts
CHANGED
|
@@ -26,7 +26,7 @@ import { compareVersions, parseSemver } from "./semver-precedence";
|
|
|
26
26
|
// budget, which means reaching the semantic side. This is a CLI module — loaded
|
|
27
27
|
// by `failproofai publish`, never by a hook — so the rule that keeps those
|
|
28
28
|
// modules off an unconfigured machine's hook path does not apply here.
|
|
29
|
-
import {
|
|
29
|
+
import { JEV_POLICIES_QUESTION_CHARS, MAX_PACK_QUESTION_CHARS, questionChars, semanticPoliciesFromPacks } from "./semantic/pack-policies";
|
|
30
30
|
import {
|
|
31
31
|
AmbiguousPackId,
|
|
32
32
|
PACK_CHECKSUMS_ASSET,
|
|
@@ -484,19 +484,20 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
484
484
|
// the user's machine, in manifest order — a pack that enforces less than it
|
|
485
485
|
// says, which is the failure this whole lane is built to avoid.
|
|
486
486
|
//
|
|
487
|
-
// A pack from outside FailproofAI
|
|
488
|
-
//
|
|
489
|
-
//
|
|
487
|
+
// A pack from outside FailproofAI shares one request with FailproofAI's own
|
|
488
|
+
// checks wherever both are installed, and those spend the budget first
|
|
489
|
+
// (`semanticPoliciesFromPacks`), so it is held to what they leave.
|
|
490
490
|
const questionCost = semantic.reduce((total, entry) => total + questionChars(entry), 0);
|
|
491
491
|
const firstParty = isFirstPartyPack(packSource);
|
|
492
|
-
const questionBudget = MAX_PACK_QUESTION_CHARS - (firstParty ? 0 :
|
|
492
|
+
const questionBudget = MAX_PACK_QUESTION_CHARS - (firstParty ? 0 : JEV_POLICIES_QUESTION_CHARS);
|
|
493
493
|
if (questionCost > questionBudget) {
|
|
494
494
|
return fail([
|
|
495
495
|
`This pack's ${semantic.length} semantic policies compile to ${questionCost} characters of questions, ` +
|
|
496
496
|
(firstParty
|
|
497
497
|
? `over the ${MAX_PACK_QUESTION_CHARS} one Jev request has room for.`
|
|
498
498
|
: `over the ${questionBudget} a machine leaves a pack from outside FailproofAI: one Jev request has room ` +
|
|
499
|
-
`for ${MAX_PACK_QUESTION_CHARS}, and
|
|
499
|
+
`for ${MAX_PACK_QUESTION_CHARS}, and FailproofAI/jev-policies' 16 checks take ${JEV_POLICIES_QUESTION_CHARS} of it first ` +
|
|
500
|
+
`where both are installed.`),
|
|
500
501
|
"Shorten the probe instructions and criteria, or ship fewer policies per pack.",
|
|
501
502
|
]);
|
|
502
503
|
}
|
|
@@ -511,9 +512,9 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
511
512
|
// Judged against the reviewers the PACK SHIPS WITH, not this build's: a pack
|
|
512
513
|
// that carries both tiers replaces the compiled semantic set on every machine
|
|
513
514
|
// that installs it, so `reviewedBy: ["its-own-check"]` is exactly right and
|
|
514
|
-
//
|
|
515
|
-
// with no semantic entries
|
|
516
|
-
// machines
|
|
515
|
+
// FailproofAI's names would call it a name "this build does not have". A pack
|
|
516
|
+
// with no semantic entries answers to FailproofAI's sixteen names, which is
|
|
517
|
+
// what `FailproofAI/jev-policies` brings to the machines that install both.
|
|
517
518
|
const reviewers = semantic.length > 0 ? new Set(semantic.map((s) => s.name)) : undefined;
|
|
518
519
|
const authorityProblems = hooks.flatMap((hook) => {
|
|
519
520
|
const problem = authorityProblem({ authority: hook.authority, reviewedBy: hook.reviewedBy }, reviewers);
|
|
@@ -590,8 +591,8 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
590
591
|
//
|
|
591
592
|
// `minCliVersion` and `semantic` follow the same rule, and for `semantic` it is
|
|
592
593
|
// load-bearing rather than tidy: an EMPTY array would still be "a pack that
|
|
593
|
-
// declares semantic entries" to a careless reader,
|
|
594
|
-
//
|
|
594
|
+
// declares semantic entries" to a careless reader, which would count it as a
|
|
595
|
+
// pack giving Jev checks when it gives none.
|
|
595
596
|
const manifest =
|
|
596
597
|
JSON.stringify(
|
|
597
598
|
{
|
|
@@ -631,7 +632,7 @@ async function build(rest: string[]): Promise<PackCliResult> {
|
|
|
631
632
|
`(${questionCost} characters of questions), ` +
|
|
632
633
|
(identity.effect === "observe"
|
|
633
634
|
? "not asked where it installs: Jev asks only the checks of packs that enforce."
|
|
634
|
-
:
|
|
635
|
+
: "asked by Jev wherever it installs."),
|
|
635
636
|
]
|
|
636
637
|
: []),
|
|
637
638
|
...(requiredCli ? [` Requires failproofai ${requiredCli} or newer.`] : []),
|
|
@@ -2651,17 +2652,17 @@ function semanticPhrase(count: number): string {
|
|
|
2651
2652
|
}
|
|
2652
2653
|
|
|
2653
2654
|
/**
|
|
2654
|
-
*
|
|
2655
|
-
*
|
|
2656
|
-
*
|
|
2657
|
-
*
|
|
2658
|
-
*
|
|
2655
|
+
* Whether a pack's Jev checks are asked, for add, show and the picker alike:
|
|
2656
|
+
* only where `jevPacks` lets the pack take part — never for an observe pack,
|
|
2657
|
+
* only for its agents when scoped. The package ships no checks of its own, so
|
|
2658
|
+
* a pack's are the whole set, beside any other pack's. One phrase, so the
|
|
2659
|
+
* three cannot drift apart again.
|
|
2659
2660
|
*/
|
|
2660
|
-
function
|
|
2661
|
+
function howJevAsksChecks(pack: { source?: string; effect?: PolicyEffect; clis?: string[] | null }): string {
|
|
2661
2662
|
if (jevPacks([{ effect: pack.effect ?? "enforce", clis: null }]).length === 0) {
|
|
2662
2663
|
return "not asked: this pack only observes, and Jev asks only the checks of packs that enforce";
|
|
2663
2664
|
}
|
|
2664
|
-
const how =
|
|
2665
|
+
const how = "asked by Jev on every tool call they apply to";
|
|
2665
2666
|
return pack.clis && pack.clis.length > 0 ? `${how}, for ${pack.clis.join(", ")} only` : how;
|
|
2666
2667
|
}
|
|
2667
2668
|
|
|
@@ -2716,7 +2717,7 @@ async function pickFromSource(
|
|
|
2716
2717
|
if (preview.semantic.length > 0) {
|
|
2717
2718
|
io.stdout.write(
|
|
2718
2719
|
`\n This pack also brings ${semanticPhrase(preview.semantic.length)} (not selectable), ` +
|
|
2719
|
-
`${
|
|
2720
|
+
`${howJevAsksChecks(preview)}.\n See: failproofai policies show ${source}\n\n`,
|
|
2720
2721
|
);
|
|
2721
2722
|
}
|
|
2722
2723
|
const picked = await multiSelect<string>({
|
|
@@ -2851,7 +2852,7 @@ async function add(rest: string[]): Promise<PackCliResult> {
|
|
|
2851
2852
|
// the half somebody installed did nothing and nothing said so.
|
|
2852
2853
|
if (result.semantic > 0) {
|
|
2853
2854
|
lines.push(
|
|
2854
|
-
` ${semanticPhrase(result.semantic)}, ${
|
|
2855
|
+
` ${semanticPhrase(result.semantic)}, ${howJevAsksChecks(result)}. ` +
|
|
2855
2856
|
"They apply only where you configured Jev (`failproofai jev status`).",
|
|
2856
2857
|
);
|
|
2857
2858
|
}
|
|
@@ -3133,7 +3134,7 @@ function relativeAge(iso: string): string {
|
|
|
3133
3134
|
*
|
|
3134
3135
|
* Judged with `resolvePolicyAuthority` against the pack's OWN check names,
|
|
3135
3136
|
* because that is the rule the installing machine applies: a pack declaring
|
|
3136
|
-
* semantic entries
|
|
3137
|
+
* semantic entries supplies the only checks it can name (`effectiveReviewerNames`), so
|
|
3137
3138
|
* its `reviewedBy` may name only its own checks, and a declaration that names
|
|
3138
3139
|
* anything else — or one sitting under `authority: "hard"`, which a manifest may
|
|
3139
3140
|
* carry because the two fields are parsed independently — registers HARD. A
|
|
@@ -3247,7 +3248,7 @@ export function jevChecksSection(
|
|
|
3247
3248
|
"",
|
|
3248
3249
|
...note(
|
|
3249
3250
|
"Nothing toggles them: `--policy` cannot name one and `failproofai policies` never lists them. " +
|
|
3250
|
-
`They arrive whole, ${
|
|
3251
|
+
`They arrive whole, ${howJevAsksChecks(pack)}.`,
|
|
3251
3252
|
opts,
|
|
3252
3253
|
),
|
|
3253
3254
|
...note(
|
|
@@ -152,8 +152,8 @@ export interface InstalledPackRecord {
|
|
|
152
152
|
*
|
|
153
153
|
* `unknown` like `policies`, and optional like it is not: a pack published
|
|
154
154
|
* before this field existed carries none, and every one of them must keep
|
|
155
|
-
* parsing.
|
|
156
|
-
*
|
|
155
|
+
* parsing. The package ships no Jev checks, so these are the only ones a
|
|
156
|
+
* machine asks — see `semantic/pack-policies.ts`.
|
|
157
157
|
*/
|
|
158
158
|
semantic?: unknown;
|
|
159
159
|
/**
|
|
@@ -339,6 +339,29 @@ export function hasInstalledPacks(): boolean {
|
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Whether an installed pack carries regex policies — the question the
|
|
344
|
+
* migration shim and `policies add <name>` actually mean to ask.
|
|
345
|
+
*
|
|
346
|
+
* `hasInstalledPacks` is the wrong question for them since the package stopped
|
|
347
|
+
* shipping Jev's checks: `FailproofAI/jev-policies` is now the normal way to
|
|
348
|
+
* turn Jev on, and it carries no regex policies. Counting it as "a pack is
|
|
349
|
+
* installed" switched off a legacy machine's `enabledPolicies` and sent a later
|
|
350
|
+
* `policies add block-rm-rf` to a pack that cannot enable it — both silently.
|
|
351
|
+
*/
|
|
352
|
+
export function hasInstalledRegexPacks(): boolean {
|
|
353
|
+
try {
|
|
354
|
+
const raw = JSON.parse(readFileSync(installedFilePath(), "utf8")) as { packs?: unknown };
|
|
355
|
+
if (!Array.isArray(raw.packs)) return false;
|
|
356
|
+
return raw.packs.some(
|
|
357
|
+
(p) => !!p && typeof p === "object" && Array.isArray((p as { policies?: unknown }).policies)
|
|
358
|
+
&& ((p as { policies: unknown[] }).policies.length > 0),
|
|
359
|
+
);
|
|
360
|
+
} catch {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
342
365
|
export function installedFilePath(): string {
|
|
343
366
|
return process.env.FAILPROOFAI_PACK_DIR
|
|
344
367
|
? resolve(process.env.FAILPROOFAI_PACK_DIR, "installed.json")
|
package/src/hooks/pack-store.ts
CHANGED
|
@@ -1103,8 +1103,8 @@ export async function addPack(
|
|
|
1103
1103
|
// installed did nothing.
|
|
1104
1104
|
//
|
|
1105
1105
|
// Omitted when empty for the same reason the manifest omits it: an empty
|
|
1106
|
-
// array reads as "this pack declares semantic entries"
|
|
1107
|
-
//
|
|
1106
|
+
// array reads as "this pack declares semantic entries" — a pack giving Jev
|
|
1107
|
+
// checks when it gives none.
|
|
1108
1108
|
...(fetched.semantic.length > 0 ? { semantic: fetched.semantic } : {}),
|
|
1109
1109
|
// Recorded so the READER re-checks it. This CLI has already satisfied it or
|
|
1110
1110
|
// refused the install, but the record outlives this CLI: a downgrade, or a
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
* - `hard` is the default and the answer to anything unclear. Absent, a value
|
|
12
12
|
* that is not one of the two, a `reviewedBy` that is empty or malformed or
|
|
13
|
-
* names a check
|
|
13
|
+
* names a check no installed pack declares, or an `alwaysOn` policy: all hard.
|
|
14
|
+
* With no pack declaring Jev checks there are no reviewers at all, and every
|
|
15
|
+
* policy is hard.
|
|
14
16
|
* - `reviewable` means Jev may clear the policy's verdict — and only through the
|
|
15
17
|
* semantic policies it names, every one of which must have been asked.
|
|
16
18
|
*
|
|
@@ -55,16 +57,23 @@ export interface ResolvedAuthority {
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
/**
|
|
58
|
-
* The names
|
|
59
|
-
* `
|
|
60
|
+
* The names of the sixteen Jev checks FailproofAI publishes in the
|
|
61
|
+
* `FailproofAI/jev-policies` pack. Not the probes (`INJECTION_PROBE`,
|
|
60
62
|
* `SCOPE_PROBE`, the task probes) — those are inputs to Jev's decision, not
|
|
61
63
|
* checks a regex verdict can be cleared by.
|
|
62
64
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
65
|
+
* Names only: the package ships none of the checks themselves, and this list is
|
|
66
|
+
* NOT a set of reviewers any machine has. It is used for two things:
|
|
67
|
+
*
|
|
68
|
+
* - **Reserved names.** Declared by a pack not installed from a FailproofAI
|
|
69
|
+
* repository, one of these is void (`isReservedClaim`), so a third party
|
|
70
|
+
* cannot become the question that clears the core pack's policies.
|
|
71
|
+
* - **Build-time vocabulary.** The core pack (`scripts/build-policy-pack.mjs`)
|
|
72
|
+
* and a regex-only pack being published name these checks in `reviewedBy`
|
|
73
|
+
* while carrying none of them; they live in `FailproofAI/jev-policies`.
|
|
74
|
+
*
|
|
75
|
+
* Pinned to the reference copy of that pack (`__tests__/fixtures/jev-policies.ts`)
|
|
76
|
+
* by `policy-authority.test.ts`.
|
|
68
77
|
*/
|
|
69
78
|
export const SEMANTIC_POLICY_NAMES = [
|
|
70
79
|
"destructive-deletion",
|
|
@@ -85,17 +94,16 @@ export const SEMANTIC_POLICY_NAMES = [
|
|
|
85
94
|
"external-data-egress",
|
|
86
95
|
] as const;
|
|
87
96
|
|
|
97
|
+
/** {@link SEMANTIC_POLICY_NAMES} as a set. Reserved names and build-time vocabulary — never a machine's live reviewers. */
|
|
98
|
+
export const SEMANTIC_REVIEWER_NAMES: ReadonlySet<string> = new Set(SEMANTIC_POLICY_NAMES);
|
|
99
|
+
|
|
88
100
|
/**
|
|
89
|
-
* The
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* `
|
|
93
|
-
* a `reviewedBy` may use there are the pack's. Callers that know which set is
|
|
94
|
-
* live pass it (`effectiveReviewerNames()` in `effective-reviewers.ts`, which reads
|
|
95
|
-
* the manifest and imports nothing from `semantic/`); everyone else gets this
|
|
96
|
-
* one, which is what a machine with no pack runs.
|
|
101
|
+
* The reviewer set of a machine with no installed pack declaring Jev checks —
|
|
102
|
+
* the vanilla install — and the default wherever a caller does not say which
|
|
103
|
+
* set is live. Empty: with nothing to ask, nothing can clear, and every
|
|
104
|
+
* `reviewable` declaration registers `hard`.
|
|
97
105
|
*/
|
|
98
|
-
export const
|
|
106
|
+
export const NO_REVIEWERS: ReadonlySet<string> = new Set<string>();
|
|
99
107
|
|
|
100
108
|
const isName = (n: unknown): n is string => typeof n === "string" && n.length > 0;
|
|
101
109
|
|
|
@@ -103,7 +111,7 @@ const isName = (n: unknown): n is string => typeof n === "string" && n.length >
|
|
|
103
111
|
* Judge one declaration.
|
|
104
112
|
*
|
|
105
113
|
* Stricter than `effectiveAuthority`, which is the §7 contract and needs only
|
|
106
|
-
* one usable name: here EVERY entry must be a
|
|
114
|
+
* one usable name: here EVERY entry must be a check this machine can ask,
|
|
107
115
|
* and anything else makes the whole declaration hard rather than being dropped.
|
|
108
116
|
* `reviewedBy` is a conjunction, so dropping a name the author wrote — a typo, a
|
|
109
117
|
* stray `7` — would let Jev clear the policy on fewer checks than they asked
|
|
@@ -114,16 +122,23 @@ const isName = (n: unknown): n is string => typeof n === "string" && n.length >
|
|
|
114
122
|
* What this returns is what `registerPolicy` stores, so a registered policy's
|
|
115
123
|
* `reviewedBy` is always clean and the two functions agree on it.
|
|
116
124
|
*
|
|
125
|
+
* With NO known reviewers — no installed pack declares a Jev check — a
|
|
126
|
+
* well-formed `reviewable` resolves `hard` WITHOUT a `downgraded` reason
|
|
127
|
+
* (unless a name it uses is contested between packs, which is still said). It is
|
|
128
|
+
* not a declaration going wrong but the machine having no Jev checks, which
|
|
129
|
+
* `jev status` and the dashboard say once; a warning per policy per hook event
|
|
130
|
+
* would say it fifteen times on every tool call of a core-pack machine.
|
|
131
|
+
*
|
|
117
132
|
* @param knownReviewers - the semantic policies that CAN be asked on this
|
|
118
|
-
* machine
|
|
119
|
-
*
|
|
120
|
-
* than a lookup because this module is imported by the
|
|
121
|
-
* which set is live means reading a file — a cost
|
|
122
|
-
* pay once and this judgement must not pay per call.
|
|
133
|
+
* machine (`effectiveReviewerNames()` on the hook path). Defaults to
|
|
134
|
+
* {@link NO_REVIEWERS}, so a caller that does not say is fail-safe. It is a
|
|
135
|
+
* parameter rather than a lookup because this module is imported by the
|
|
136
|
+
* registry, and reading which set is live means reading a file — a cost
|
|
137
|
+
* registration is willing to pay once and this judgement must not pay per call.
|
|
123
138
|
*/
|
|
124
139
|
export function resolvePolicyAuthority(
|
|
125
140
|
decl: AuthorityDeclaration | undefined,
|
|
126
|
-
knownReviewers: ReadonlySet<string> =
|
|
141
|
+
knownReviewers: ReadonlySet<string> = NO_REVIEWERS,
|
|
127
142
|
/** Names left out of `knownReviewers` because packs declare them differently, with those packs. */
|
|
128
143
|
contested?: ReadonlyMap<string, string[]>,
|
|
129
144
|
): ResolvedAuthority {
|
|
@@ -137,6 +152,8 @@ export function resolvePolicyAuthority(
|
|
|
137
152
|
return { authority: "hard", downgraded: "reviewedBy is not a list of semantic policy names" };
|
|
138
153
|
}
|
|
139
154
|
const unknown = names.filter((n) => !knownReviewers.has(n));
|
|
155
|
+
// No checks at all, and no contest to explain: quietly hard (see above).
|
|
156
|
+
if (knownReviewers.size === 0 && !unknown.some((n) => contested?.has(n))) return { authority: "hard" };
|
|
140
157
|
if (unknown.length > 0) {
|
|
141
158
|
return { authority: "hard", downgraded: `reviewedBy names ${unknown.map((n) => whyUnknown(n, knownReviewers, contested)).join("; ")}` };
|
|
142
159
|
}
|
|
@@ -144,8 +161,8 @@ export function resolvePolicyAuthority(
|
|
|
144
161
|
}
|
|
145
162
|
|
|
146
163
|
/**
|
|
147
|
-
* The rule that kept one name out. "Not in this build" is only true of
|
|
148
|
-
*
|
|
164
|
+
* The rule that kept one name out. "Not in this build" is only true of
|
|
165
|
+
* FailproofAI's own names, judged at build or publish time: a pack's own checks, or a name two packs disagree on, is a
|
|
149
166
|
* different reason, and the author reading it has a different fix.
|
|
150
167
|
*/
|
|
151
168
|
function whyUnknown(name: string, known: ReadonlySet<string>, contested?: ReadonlyMap<string, string[]>): string {
|
|
@@ -205,9 +222,11 @@ export function authorityProblem(
|
|
|
205
222
|
decl: AuthorityDeclaration,
|
|
206
223
|
/**
|
|
207
224
|
* The reviewers the pack being built will SHIP WITH — its own `semantic`
|
|
208
|
-
* entries when it declares any,
|
|
209
|
-
*
|
|
210
|
-
* that
|
|
225
|
+
* entries when it declares any, FailproofAI's sixteen names otherwise (a
|
|
226
|
+
* regex-only pack names checks that ship in `FailproofAI/jev-policies`). A
|
|
227
|
+
* pack that carries both halves of the two-tier set names its own checks, and
|
|
228
|
+
* refusing that would make the feature unpublishable by the tool that
|
|
229
|
+
* implements it.
|
|
211
230
|
*/
|
|
212
231
|
knownReviewers: ReadonlySet<string> = SEMANTIC_REVIEWER_NAMES,
|
|
213
232
|
): string | undefined {
|
|
@@ -283,13 +302,13 @@ export function manifestAuthority(
|
|
|
283
302
|
*
|
|
284
303
|
* @param knownReviewers - the checks that can be asked on this machine. Callers
|
|
285
304
|
* that merge what will be REGISTERED must pass `effectiveReviewerNames()`;
|
|
286
|
-
* the default is
|
|
287
|
-
*
|
|
305
|
+
* the default is {@link NO_REVIEWERS}, which is what a machine with no pack
|
|
306
|
+
* declaring Jev checks has.
|
|
288
307
|
*/
|
|
289
308
|
export function withMergedAuthority<T extends AuthorityFields>(
|
|
290
309
|
record: T,
|
|
291
310
|
declarations: ReadonlyArray<AuthorityFields>,
|
|
292
|
-
knownReviewers: ReadonlySet<string> =
|
|
311
|
+
knownReviewers: ReadonlySet<string> = NO_REVIEWERS,
|
|
293
312
|
): { merged: T; overruled: boolean; refused?: string } {
|
|
294
313
|
const resolved = declarations.map((d) => resolvePolicyAuthority(d, knownReviewers));
|
|
295
314
|
const allReviewable = resolved.length > 0 && resolved.every((r) => r.authority === "reviewable");
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
type JevActivityFields,
|
|
25
25
|
type JevReview,
|
|
26
26
|
type RegexVerdict,
|
|
27
|
-
type
|
|
27
|
+
type ObserveVerdict,
|
|
28
28
|
type TwoTierReview,
|
|
29
29
|
} from "./semantic/combine";
|
|
30
30
|
// The code, not a copy of the string: the log line below turns on it, and a
|
|
@@ -62,8 +62,8 @@ export interface EvaluationResult {
|
|
|
62
62
|
decidedByJev: boolean;
|
|
63
63
|
/** The pack a deciding Jev check came from; only with `decidedByJev`. */
|
|
64
64
|
jevOrigin?: { packId: string; packVersion?: string };
|
|
65
|
-
/**
|
|
66
|
-
|
|
65
|
+
/** Observe mode: Jev's own deny/instruct, recorded as a "would have" (see `ObserveVerdict`). */
|
|
66
|
+
observeVerdict?: ObserveVerdict;
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -219,7 +219,7 @@ export async function evaluatePolicies(
|
|
|
219
219
|
}
|
|
220
220
|
const combined = combineTwoTier(collected.verdicts, review, twoTier.mode);
|
|
221
221
|
if (combined.cleared.length > 0) {
|
|
222
|
-
hookLogInfo(`jev ${twoTier.mode === "
|
|
222
|
+
hookLogInfo(`jev ${twoTier.mode === "observe" ? "would clear" : "cleared"}: ${combined.cleared.join(", ")}`);
|
|
223
223
|
}
|
|
224
224
|
if (combined.activity.evaluator === "jev-fallback") {
|
|
225
225
|
// Info, not warn: during an outage this is every gated call, and warn-level
|
|
@@ -251,7 +251,7 @@ export async function evaluatePolicies(
|
|
|
251
251
|
activity: combined.activity,
|
|
252
252
|
decidedByJev: combined.decidedByJev,
|
|
253
253
|
...(combined.decidedByJev && review.kind === "answered" && review.origin ? { jevOrigin: review.origin } : {}),
|
|
254
|
-
...(combined.
|
|
254
|
+
...(combined.observeVerdict ? { observeVerdict: combined.observeVerdict } : {}),
|
|
255
255
|
},
|
|
256
256
|
};
|
|
257
257
|
}
|
|
@@ -73,13 +73,11 @@ export function registerPolicy(
|
|
|
73
73
|
const canonical = normalizePolicyName(name);
|
|
74
74
|
const registry = getRegistry();
|
|
75
75
|
const idx = registry.findIndex((p) => p.name === canonical);
|
|
76
|
-
// Judged against the reviewers this MACHINE can ask
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
// `
|
|
80
|
-
//
|
|
81
|
-
// warning. `effectiveReviewerNames` reads the manifest — already where a
|
|
82
|
-
// pack's `reviewedBy` itself comes from — once per registration pass.
|
|
76
|
+
// Judged against the reviewers this MACHINE can ask: the checks its installed
|
|
77
|
+
// packs declare, and none when no pack declares any (the package ships no
|
|
78
|
+
// Jev checks), in which case every policy registers `hard`.
|
|
79
|
+
// `effectiveReviewerNames` reads the manifest — already where a pack's
|
|
80
|
+
// `reviewedBy` itself comes from — once per registration pass.
|
|
83
81
|
const authority = meta ? resolvePolicyAuthority(meta, effectiveReviewerNames()) : undefined;
|
|
84
82
|
const entry: RegisteredPolicy = {
|
|
85
83
|
name: canonical, description, fn, match, priority,
|