failproofai 1.0.8 → 1.0.9-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 +4 -4
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +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.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +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 +3 -3
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[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/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]__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]__0tmz862._.js +3 -0
- 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/{_0-vcssj._.js → _0n0lja6._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1p3iz2d._.js +23 -0
- 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/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 +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 +23 -23
- package/.next/standalone/.next/static/chunks/{3c3qbmosdjl6w.js → 0247hc9ath5t-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0m-9d6yn9hx4j.js → 03001k9jy-kst.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2qdpj67x6ifk_.js → 06k7gsfnb7jkh.js} +1 -1
- package/.next/standalone/.next/static/chunks/0qejem4-zt-hy.js +1 -0
- package/.next/standalone/.next/static/chunks/{21uv-uusw329x.js → 0snylg3y1i47a.js} +1 -1
- package/.next/standalone/.next/static/chunks/1h0if8ky362as.js +1 -0
- package/.next/standalone/.next/static/chunks/{0wbjy0zo-m7is.js → 2qsrrrl2maqsb.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0bhidk90e-07f.css → 3_e_wg5_oay87.css} +1 -1
- package/.next/standalone/.next/static/chunks/3ns-egfh8i8i5.js +6 -0
- package/.next/standalone/.next/static/chunks/3rq7fjj9we0v3.js +1 -0
- package/.next/standalone/.next/static/chunks/42m0f2hh8vfb1.js +1 -0
- 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/hermes-plugin/README.md +18 -8
- package/.next/standalone/package.json +10 -10
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +33 -7
- package/dist/cli.mjs +556 -521
- package/dist/worker.mjs +56 -375
- package/hermes-plugin/README.md +18 -8
- package/package.json +10 -10
- 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 +349 -55
- package/src/hooks/jev-activity.ts +3 -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 +26 -10
- package/src/hooks/policy-types.ts +4 -4
- 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 → z1C0kJVF0NCtWn5R88hW3}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → z1C0kJVF0NCtWn5R88hW3}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → z1C0kJVF0NCtWn5R88hW3}/_ssgManifest.js +0 -0
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* | Jev answered | reviewable denies/instructs Jev covered are cleared; final =|
|
|
23
23
|
* | | the most severe of {remaining regex results, Jev's verdict} |
|
|
24
24
|
*
|
|
25
|
-
* `
|
|
25
|
+
* `observe` mode computes and records all of it, and still returns the regex
|
|
26
26
|
* result.
|
|
27
27
|
*
|
|
28
28
|
* ## Where this departs from plan §4
|
|
@@ -232,14 +232,14 @@ import type { PolicyAuthority } from "../policy-types";
|
|
|
232
232
|
// The only value imports, and deliberately from the activity vocabulary rather
|
|
233
233
|
// than local literals: the fallback reason this module writes itself has to
|
|
234
234
|
// be a code the activity store's closed list names, or it is stored and
|
|
235
|
-
// shipped as `other`, and
|
|
235
|
+
// shipped as `other`, and an observe-mode verdict's model id has to pass the same
|
|
236
236
|
// shape check the row's own `jevModel` does. Importing them makes a rename a
|
|
237
237
|
// compile error here. `jev-activity.ts` is pure (no node imports, no semantic
|
|
238
238
|
// modules), so this costs the hook path nothing.
|
|
239
239
|
import { JEV_MODEL_RE, JEV_REASON_REQUEST_CUT } from "../jev-activity";
|
|
240
240
|
|
|
241
241
|
export type Decision = "allow" | "deny" | "instruct";
|
|
242
|
-
export type JevMode = "
|
|
242
|
+
export type JevMode = "observe" | "enforce";
|
|
243
243
|
|
|
244
244
|
/** One regex policy's verdict, in evaluation order. */
|
|
245
245
|
export interface RegexVerdict {
|
|
@@ -383,13 +383,13 @@ export interface JevActivityFields {
|
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
/**
|
|
386
|
-
* Jev's own deny or instruct in
|
|
386
|
+
* Jev's own deny or instruct in OBSERVE mode — the verdict enforce mode would
|
|
387
387
|
* have applied, recorded rather than applied. The handler files it in the
|
|
388
388
|
* activity row's `observed` list, the "would have" record observe-mode cloud
|
|
389
389
|
* and pack policies already use, so FailproofAI Cloud's policy page counts it
|
|
390
390
|
* with no change on its side.
|
|
391
391
|
*/
|
|
392
|
-
export interface
|
|
392
|
+
export interface ObserveVerdict {
|
|
393
393
|
/** `semantic/<check>` — the name enforce mode would have attributed it to. */
|
|
394
394
|
policyName: string;
|
|
395
395
|
decision: "deny" | "instruct";
|
|
@@ -401,7 +401,7 @@ export interface ShadowVerdict {
|
|
|
401
401
|
|
|
402
402
|
export interface CombineOutcome {
|
|
403
403
|
final: FinalVerdict;
|
|
404
|
-
/** Reviewable regex policies Jev cleared (in
|
|
404
|
+
/** Reviewable regex policies Jev cleared (in observe mode: would have cleared). */
|
|
405
405
|
cleared: string[];
|
|
406
406
|
/**
|
|
407
407
|
* True when the first entry of `final` came from THIS TIER rather than from
|
|
@@ -414,11 +414,11 @@ export interface CombineOutcome {
|
|
|
414
414
|
decidedByJev: boolean;
|
|
415
415
|
activity: JevActivityFields;
|
|
416
416
|
/**
|
|
417
|
-
*
|
|
417
|
+
* Observe mode only, and only when Jev's own verdict was deny or instruct.
|
|
418
418
|
* Absent otherwise — including every enforce outcome, where the verdict was
|
|
419
419
|
* APPLIED and `decidedByJev` / the final entries already say so.
|
|
420
420
|
*/
|
|
421
|
-
|
|
421
|
+
observeVerdict?: ObserveVerdict;
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
/**
|
|
@@ -495,7 +495,7 @@ export function combineTwoTier(
|
|
|
495
495
|
const notDenied = new Set(review.notDenied);
|
|
496
496
|
const cleared = wholePicture && !review.unclearableWarned ? verdicts.filter((v) => clears(v, asked, notDenied)).map((v) => v.policyName) : [];
|
|
497
497
|
|
|
498
|
-
// Built once, for both modes: enforce applies it,
|
|
498
|
+
// Built once, for both modes: enforce applies it, observe records it, and the
|
|
499
499
|
// two must never disagree about what the verdict WAS.
|
|
500
500
|
const jevEntry = { policyName: review.policyName, reason: review.reason ?? `Flagged by semantic review (${review.policyName})` };
|
|
501
501
|
const enforced = resolveEnforce(verdicts, cleared, review.decision, jevEntry);
|
|
@@ -510,7 +510,7 @@ export function combineTwoTier(
|
|
|
510
510
|
evaluator: review.requestCut ? "jev-fallback" : "jev",
|
|
511
511
|
...(review.requestCut ? { jevFallbackReason: JEV_REASON_REQUEST_CUT } : {}),
|
|
512
512
|
jevDecision: review.decision,
|
|
513
|
-
// Only a clear that SOFTENED the call (in
|
|
513
|
+
// Only a clear that SOFTENED the call (in observe mode: would have). One that
|
|
514
514
|
// Jev's own deny, or another regex deny, still decided over changed
|
|
515
515
|
// nothing — and `jev status` and the policy page's "Cleared by Jev" both
|
|
516
516
|
// read `jevCleared` as calls Jev let through.
|
|
@@ -520,11 +520,11 @@ export function combineTwoTier(
|
|
|
520
520
|
jevMode: mode,
|
|
521
521
|
};
|
|
522
522
|
|
|
523
|
-
if (mode === "
|
|
523
|
+
if (mode === "observe") {
|
|
524
524
|
// Jev's own deny or instruct, exactly as enforce mode would have applied it
|
|
525
525
|
// (upward only: a cut or injected call keeps it, as the enforce branch
|
|
526
526
|
// below does). A clear is recorded in `jevCleared`, not here.
|
|
527
|
-
const
|
|
527
|
+
const observeVerdict: ObserveVerdict | undefined =
|
|
528
528
|
review.decision === "deny" || review.decision === "instruct"
|
|
529
529
|
? {
|
|
530
530
|
policyName: jevEntry.policyName,
|
|
@@ -535,7 +535,7 @@ export function combineTwoTier(
|
|
|
535
535
|
version: review.model && JEV_MODEL_RE.test(review.model) ? review.model : "jev",
|
|
536
536
|
}
|
|
537
537
|
: undefined;
|
|
538
|
-
return { final: legacy, cleared, decidedByJev: false, activity, ...(
|
|
538
|
+
return { final: legacy, cleared, decidedByJev: false, activity, ...(observeVerdict ? { observeVerdict } : {}) };
|
|
539
539
|
}
|
|
540
540
|
|
|
541
541
|
return { ...enforced, cleared, activity };
|
|
@@ -98,7 +98,7 @@ export interface SemanticOptions {
|
|
|
98
98
|
*/
|
|
99
99
|
signal?: AbortSignal;
|
|
100
100
|
model?: string;
|
|
101
|
-
/** Overrides the resolved set (
|
|
101
|
+
/** Overrides the resolved set (the installed packs' checks; empty with none). */
|
|
102
102
|
policies?: ReadonlyArray<SemanticPolicy>;
|
|
103
103
|
/** The agent the call is for: a pack scoped to other agents contributes no checks. */
|
|
104
104
|
cli?: string;
|
|
@@ -306,8 +306,8 @@ export function prepareSemantic(input: SemanticInput, opts: SemanticOptions = {}
|
|
|
306
306
|
scanned,
|
|
307
307
|
input.projectRoot ?? null,
|
|
308
308
|
);
|
|
309
|
-
// The
|
|
310
|
-
//
|
|
309
|
+
// The checks the installed packs declare — none when no pack declares any,
|
|
310
|
+
// and then nothing is asked (see `pack-policies.ts`). Resolved
|
|
311
311
|
// here rather than by the caller because this is the one place the policy set
|
|
312
312
|
// is read, and a second resolution site is a second answer to "what does this
|
|
313
313
|
// machine ask Jev". A caller that supplies `policies` (a replay, an ablation,
|
|
@@ -496,7 +496,7 @@ export interface VerdictLogMeta {
|
|
|
496
496
|
eventType: string;
|
|
497
497
|
/**
|
|
498
498
|
* What the handler did with the outcome: combined it with the regex results
|
|
499
|
-
* (`two-tier`), logged it while enforcing the regex result (`
|
|
499
|
+
* (`two-tier`), logged it while enforcing the regex result (`observe`), or
|
|
500
500
|
* kept the regex result because Jev never answered (`legacy-fallback`).
|
|
501
501
|
*
|
|
502
502
|
* A TRUNCATED call is `two-tier`, not `legacy-fallback`: its clears were
|
|
@@ -504,7 +504,7 @@ export interface VerdictLogMeta {
|
|
|
504
504
|
* `combine.ts`). `truncated` on the same row is what says the clearing half
|
|
505
505
|
* was off for it; the activity row records `jev-fallback` / `truncated`.
|
|
506
506
|
*/
|
|
507
|
-
applied: "two-tier" | "
|
|
507
|
+
applied: "two-tier" | "observe" | "legacy-fallback";
|
|
508
508
|
}
|
|
509
509
|
|
|
510
510
|
export function verdictLogRow(input: SemanticInput, outcome: SemanticOutcome, meta: VerdictLogMeta): Record<string, unknown> {
|
|
@@ -469,7 +469,7 @@ async function postJson(url: string, bearer: string, body: unknown, signal: Abor
|
|
|
469
469
|
body: JSON.stringify(body),
|
|
470
470
|
signal,
|
|
471
471
|
// Never followed. The configured URL is the one `validateBaseUrl` checked
|
|
472
|
-
// (https, or loopback http in
|
|
472
|
+
// (https, or loopback http in observe mode only); a redirect would hand the
|
|
473
473
|
// answer — the thing that can clear a deny — to an origin nobody checked,
|
|
474
474
|
// plain http included. No provider redirects this POST.
|
|
475
475
|
redirect: "manual",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
* The one provider whose key is NOT in this file. `failproofai config --token`
|
|
56
56
|
* with a key carrying `jev:evaluate` stores that key in the `jev` slot of
|
|
57
57
|
* `credentials.json` and, when there is no `jev.json` yet, writes one naming
|
|
58
|
-
* this provider, the Cloud origin + `/enforcement/v1/jev` and `mode: "
|
|
58
|
+
* this provider, the Cloud origin + `/enforcement/v1/jev` and `mode: "observe"`.
|
|
59
59
|
* So for this provider:
|
|
60
60
|
*
|
|
61
61
|
* - the key comes from `credentials.json` (`readJevCloudCredential`), read with
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
*
|
|
81
81
|
* # `mode: "off"`
|
|
82
82
|
*
|
|
83
|
-
* Every provider accepts `off |
|
|
83
|
+
* Every provider accepts `off | observe | enforce`. `off` keeps the file — the
|
|
84
84
|
* endpoint, and for BYOK the key — while Jev does not run at all:
|
|
85
85
|
* `loadJevConfig` returns null exactly as for an absent file. It exists so the
|
|
86
86
|
* dashboard can switch the Cloud route off without deleting the file that
|
|
@@ -96,8 +96,16 @@ export type { JevCloudCredential } from "../fp-config";
|
|
|
96
96
|
|
|
97
97
|
export type JevProviderKind = "typesafe" | "openrouter" | "vercel" | "cloudflare" | "custom" | "failproofai";
|
|
98
98
|
|
|
99
|
-
/** `off` does not run Jev at all; `
|
|
100
|
-
export type JevConfigMode = "off" | "
|
|
99
|
+
/** `off` does not run Jev at all; `observe` logs Jev and enforces regex; `enforce` applies the combine rules. */
|
|
100
|
+
export type JevConfigMode = "off" | "observe" | "enforce";
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A mode as read from a file, a flag or a request: `off`, `observe` or
|
|
104
|
+
* `enforce`. Null for anything else.
|
|
105
|
+
*/
|
|
106
|
+
export function parseJevMode(raw: unknown): JevConfigMode | null {
|
|
107
|
+
return raw === "off" || raw === "observe" || raw === "enforce" ? raw : null;
|
|
108
|
+
}
|
|
101
109
|
|
|
102
110
|
export interface JevConfig {
|
|
103
111
|
provider: JevProviderKind;
|
|
@@ -130,7 +138,7 @@ export interface JevConfig {
|
|
|
130
138
|
credentialOrigin?: string;
|
|
131
139
|
}
|
|
132
140
|
|
|
133
|
-
export const DEFAULT_JEV_MODE: "
|
|
141
|
+
export const DEFAULT_JEV_MODE: "observe" | "enforce" = "enforce";
|
|
134
142
|
|
|
135
143
|
export const JEV_PROVIDER_KINDS: readonly JevProviderKind[] = ["typesafe", "openrouter", "vercel", "cloudflare", "custom", "failproofai"];
|
|
136
144
|
|
|
@@ -311,7 +319,7 @@ function credentialQueryProblem(url: URL): string | null {
|
|
|
311
319
|
* No credentials in the URL — not as userinfo, not as a query parameter — and no
|
|
312
320
|
* fragment, because a key belongs in the key field, where it is sent as a bearer
|
|
313
321
|
* and never printed. `validateJevConfig` further accepts the loopback http form
|
|
314
|
-
* only in
|
|
322
|
+
* only in observe mode.
|
|
315
323
|
*/
|
|
316
324
|
export function validateBaseUrl(raw: unknown): ValidationResult<string> {
|
|
317
325
|
if (typeof raw !== "string" || raw.trim() === "") return { ok: false, problem: "baseUrl must be a non-empty string" };
|
|
@@ -667,25 +675,26 @@ export function validateJevConfig(
|
|
|
667
675
|
}
|
|
668
676
|
|
|
669
677
|
if (o.mode !== undefined) {
|
|
670
|
-
|
|
671
|
-
|
|
678
|
+
const mode = parseJevMode(o.mode);
|
|
679
|
+
if (mode === null) return { ok: false, problem: 'mode must be "off", "observe" or "enforce"' };
|
|
680
|
+
cfg.mode = mode;
|
|
672
681
|
}
|
|
673
682
|
|
|
674
683
|
// Plain http reaches only a loopback host (`validateBaseUrl`), and nothing
|
|
675
684
|
// authenticates the server there: while the local proxy is down, any process
|
|
676
685
|
// of this user — the agent being judged included — can bind its port and
|
|
677
686
|
// answer "none" to every question. In enforce mode that answer clears
|
|
678
|
-
// reviewable denies; in
|
|
687
|
+
// reviewable denies; in observe mode it changes nothing, and `off` sends
|
|
679
688
|
// nothing at all, so enforce is the one mode it is refused in.
|
|
680
689
|
if (cfg.baseUrl !== undefined && isPlainHttp(cfg.baseUrl) && cfg.mode === "enforce") {
|
|
681
690
|
return {
|
|
682
691
|
ok: false,
|
|
683
692
|
problem:
|
|
684
|
-
"plain http (to localhost) is accepted only with mode
|
|
693
|
+
"plain http (to localhost) is accepted only with mode observe: in enforce mode Jev's answers can clear a deny, " +
|
|
685
694
|
"and while the local proxy is down any process on this machine could take its port and answer. " +
|
|
686
695
|
(cfg.provider === JEV_CLOUD_PROVIDER
|
|
687
|
-
? "Reconnect to an https FailproofAI Cloud URL (failproofai config --token <key> --url https://…), or keep mode
|
|
688
|
-
: "Use https, or mode
|
|
696
|
+
? "Reconnect to an https FailproofAI Cloud URL (failproofai config --token <key> --url https://…), or keep mode observe"
|
|
697
|
+
: "Use https, or mode observe"),
|
|
689
698
|
};
|
|
690
699
|
}
|
|
691
700
|
|
|
@@ -114,7 +114,7 @@ function servedFromCache(response: unknown): boolean {
|
|
|
114
114
|
* `jev.json`, and the request alone does not say who answered it: two
|
|
115
115
|
* providers asked for the same model build byte-identical requests. Unscoped,
|
|
116
116
|
* an answer one config got — from a plain-http loopback proxy T1 allows only in
|
|
117
|
-
*
|
|
117
|
+
* observe mode, say — would clear denies under the next config in enforce mode
|
|
118
118
|
* without that provider ever being asked. The scope is only hashed into the
|
|
119
119
|
* cache key, never stored, so a URL carrying a credential is safe here.
|
|
120
120
|
*/
|
|
@@ -152,7 +152,7 @@ export interface JevCallContext {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
export function resolveMode(cfg: JevConfig): JevMode {
|
|
155
|
-
return cfg.mode === "
|
|
155
|
+
return cfg.mode === "observe" || cfg.mode === "enforce" ? cfg.mode : DEFAULT_JEV_MODE;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
/**
|
|
@@ -359,7 +359,7 @@ export function startJevReview(cfg: JevConfig, call: JevCallContext): TwoTierRev
|
|
|
359
359
|
sessionId: call.sessionId,
|
|
360
360
|
cli: call.cli,
|
|
361
361
|
eventType: call.eventType,
|
|
362
|
-
applied: abandoned || review.kind !== "answered" ? "legacy-fallback" : mode === "
|
|
362
|
+
applied: abandoned || review.kind !== "answered" ? "legacy-fallback" : mode === "observe" ? "observe" : "two-tier",
|
|
363
363
|
});
|
|
364
364
|
appendVerdictLog(hit ? { ...row, cached: true } : row);
|
|
365
365
|
}
|
|
@@ -31,9 +31,9 @@ export interface JevStats {
|
|
|
31
31
|
latencyP95Ms: number | null;
|
|
32
32
|
/**
|
|
33
33
|
* Reviewable policies Jev cleared, by name — enforce mode only, where a
|
|
34
|
-
* clear changed the outcome.
|
|
35
|
-
* {@link JevStats.
|
|
36
|
-
* field says "nothing cleared" on
|
|
34
|
+
* clear changed the outcome. Observe mode's would-be clears are in
|
|
35
|
+
* {@link JevStats.observeClearsByPolicy}, so a renderer that prints only this
|
|
36
|
+
* field says "nothing cleared" on an observe-mode machine: print both.
|
|
37
37
|
*/
|
|
38
38
|
clearsByPolicy: Record<string, number>;
|
|
39
39
|
// The fields below are optional in the TYPE only so that a caller written
|
|
@@ -62,9 +62,9 @@ export interface JevStats {
|
|
|
62
62
|
*/
|
|
63
63
|
decisions?: { allow: number; instruct: number; deny: number };
|
|
64
64
|
/** Consulted calls (`total`) per rollout mode; a row written without a mode is counted in neither. */
|
|
65
|
-
modes?: {
|
|
66
|
-
/** What Jev would have cleared in
|
|
67
|
-
|
|
65
|
+
modes?: { observe: number; enforce: number };
|
|
66
|
+
/** What Jev would have cleared in observe mode, where the regex result was enforced instead. */
|
|
67
|
+
observeClearsByPolicy?: Record<string, number>;
|
|
68
68
|
/** Model ids that answered, by count. */
|
|
69
69
|
models?: Record<string, number>;
|
|
70
70
|
}
|
|
@@ -160,14 +160,14 @@ export function computeJevStats(
|
|
|
160
160
|
notConsulted: 0,
|
|
161
161
|
noRequest: 0,
|
|
162
162
|
decisions: { allow: 0, instruct: 0, deny: 0 },
|
|
163
|
-
modes: {
|
|
164
|
-
|
|
163
|
+
modes: { observe: 0, enforce: 0 },
|
|
164
|
+
observeClearsByPolicy: {},
|
|
165
165
|
models: {},
|
|
166
166
|
};
|
|
167
167
|
const latencies: number[] = [];
|
|
168
168
|
const fallbackReasons = new Counter();
|
|
169
169
|
const clearsByPolicy = new Counter();
|
|
170
|
-
const
|
|
170
|
+
const observeClearsByPolicy = new Counter();
|
|
171
171
|
const models = new Counter();
|
|
172
172
|
|
|
173
173
|
for (const raw of entries) {
|
|
@@ -197,13 +197,13 @@ export function computeJevStats(
|
|
|
197
197
|
if (e.jevDecision) stats.decisions[e.jevDecision] += 1;
|
|
198
198
|
if (e.jevLatencyMs !== undefined) latencies.push(e.jevLatencyMs);
|
|
199
199
|
if (e.jevModel) models.bump(e.jevModel);
|
|
200
|
-
const clears = e.jevMode === "
|
|
200
|
+
const clears = e.jevMode === "observe" ? observeClearsByPolicy : clearsByPolicy;
|
|
201
201
|
for (const name of e.jevCleared ?? []) clears.bump(name);
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
stats.fallbackReasons = fallbackReasons.toRecord();
|
|
205
205
|
stats.clearsByPolicy = clearsByPolicy.toRecord();
|
|
206
|
-
stats.
|
|
206
|
+
stats.observeClearsByPolicy = observeClearsByPolicy.toRecord();
|
|
207
207
|
stats.models = models.toRecord();
|
|
208
208
|
stats.fallbackRate = stats.total > 0 ? stats.fallbacks / stats.total : 0;
|
|
209
209
|
latencies.sort((a, b) => a - b);
|
|
@@ -217,8 +217,8 @@ export function computeJevStats(
|
|
|
217
217
|
* {@link clampJevStatsWindow}), read from the hook activity store. Never
|
|
218
218
|
* throws: an unreadable store reads as no activity.
|
|
219
219
|
*
|
|
220
|
-
* A renderer must show {@link JevStats.
|
|
221
|
-
* `clearsByPolicy`: in
|
|
220
|
+
* A renderer must show {@link JevStats.observeClearsByPolicy} as well as
|
|
221
|
+
* `clearsByPolicy`: in observe mode every clear Jev would have made is in the
|
|
222
222
|
* former, and the latter is empty. {@link formatJevStats} prints both.
|
|
223
223
|
*/
|
|
224
224
|
export async function jevStats(opts: { windowMs?: number; now?: number } = {}): Promise<JevStatsDetail> {
|
|
@@ -279,11 +279,11 @@ export function formatJevStats(s: JevStats): string {
|
|
|
279
279
|
lines.push(...notAsked);
|
|
280
280
|
if (s.latencyP50Ms !== null) lines.push(` Latency: p50 ${s.latencyP50Ms} ms, p95 ${s.latencyP95Ms} ms`);
|
|
281
281
|
if (Object.keys(s.clearsByPolicy).length > 0) lines.push(` Cleared: ${topCounts(s.clearsByPolicy)}`);
|
|
282
|
-
if (s.
|
|
283
|
-
lines.push(` Would clear: ${topCounts(s.
|
|
282
|
+
if (s.observeClearsByPolicy && Object.keys(s.observeClearsByPolicy).length > 0) {
|
|
283
|
+
lines.push(` Would clear: ${topCounts(s.observeClearsByPolicy)} (observe mode)`);
|
|
284
284
|
}
|
|
285
|
-
if (s.modes && s.modes.
|
|
286
|
-
lines.push(` Modes: enforce ${s.modes.enforce},
|
|
285
|
+
if (s.modes && s.modes.observe > 0 && s.modes.enforce > 0) {
|
|
286
|
+
lines.push(` Modes: enforce ${s.modes.enforce}, observe ${s.modes.observe}`);
|
|
287
287
|
}
|
|
288
288
|
return lines.join("\n");
|
|
289
289
|
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Which
|
|
2
|
+
* Which Jev checks this machine asks: the ones its installed packs declare, and
|
|
3
|
+
* nothing else.
|
|
3
4
|
*
|
|
4
|
-
* ##
|
|
5
|
+
* ## No pack, no questions
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* The npm package ships NO Jev checks. The sixteen FailproofAI writes are
|
|
8
|
+
* published as the `FailproofAI/jev-policies` pack, and a machine asks them only
|
|
9
|
+
* after `failproofai policies add FailproofAI/jev-policies`. With no installed
|
|
10
|
+
* pack declaring a `semantic` entry the set is EMPTY: nothing is asked, no
|
|
11
|
+
* request is sent (the hook path does not even start a review —
|
|
12
|
+
* `jevChecksInstalled` in `effective-reviewers.ts`), and no regex verdict can be
|
|
13
|
+
* cleared, because the reviewer names come from the same packs and there are
|
|
14
|
+
* none. That holds whether or not the machine is Jev-configured or connected to
|
|
15
|
+
* FailproofAI Cloud: configuring a provider says where to ask, a pack says what.
|
|
13
16
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* keeps the first and drops the later one, because the answer map is keyed by
|
|
22
|
-
* policy name and the second would overwrite the first's questions in the
|
|
23
|
-
* compiled request.
|
|
17
|
+
* Several declaring packs CONCATENATE, FailproofAI's first. A name declared by
|
|
18
|
+
* two packs keeps the first and drops the later one, because the answer map is
|
|
19
|
+
* keyed by policy name and the second would overwrite the first's questions in
|
|
20
|
+
* the compiled request. The sixteen FailproofAI names are reserved: a pack not
|
|
21
|
+
* installed from a FailproofAI repository that declares one is ignored for that
|
|
22
|
+
* name (`isReservedClaim`), so it cannot become the question that clears the
|
|
23
|
+
* core pack's policies.
|
|
24
24
|
*
|
|
25
25
|
* ## Where the manifest ends and this begins
|
|
26
26
|
*
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* regex policies cover — a machine locked out over a typo in the half of the
|
|
40
40
|
* system whose job is to let more real work through.
|
|
41
41
|
*/
|
|
42
|
-
import { contestedSemanticNames, isFirstPartyPack, isReservedClaim, jevPacks
|
|
42
|
+
import { contestedSemanticNames, isFirstPartyPack, isReservedClaim, jevPacks } from "../effective-reviewers";
|
|
43
43
|
import { hookLogWarn } from "../hook-logger";
|
|
44
44
|
import {
|
|
45
45
|
packSemantic,
|
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
} from "../pack-manifest";
|
|
51
51
|
import { MAX_REQUEST_CHARS, userAskedQuestion } from "./compile";
|
|
52
52
|
import { MAX_STATE_CHARS } from "./envelope";
|
|
53
|
-
import { INJECTION_PROBE, SCOPE_PROBE,
|
|
53
|
+
import { INJECTION_PROBE, SCOPE_PROBE, TASK_PROBES } from "./policies";
|
|
54
54
|
import { preconditionFor } from "./preconditions";
|
|
55
55
|
import { isPackPreconditionName } from "./precondition-names";
|
|
56
56
|
import type { NoulQuestion, SemanticPolicy } from "./types";
|
|
@@ -95,9 +95,9 @@ const GLOBAL_QUESTION_CHARS = Math.max(
|
|
|
95
95
|
* "somebody installed a big pack", and the flag that was supposed to be
|
|
96
96
|
* unreachable from ordinary work becomes routine.
|
|
97
97
|
*
|
|
98
|
-
*
|
|
99
|
-
* constraint on the set we ship.
|
|
100
|
-
*
|
|
98
|
+
* `FailproofAI/jev-policies` uses `JEV_POLICIES_QUESTION_CHARS` of it, so this
|
|
99
|
+
* is not a constraint on the set we ship. FailproofAI's packs spend the budget
|
|
100
|
+
* first, so a stranger's pack installed beside that one gets what it leaves.
|
|
101
101
|
*/
|
|
102
102
|
export const MAX_PACK_QUESTION_CHARS = MAX_REQUEST_CHARS - MAX_STATE_CHARS - GLOBAL_QUESTION_CHARS;
|
|
103
103
|
|
|
@@ -123,8 +123,15 @@ export function questionChars(entry: SemanticManifestEntry): number {
|
|
|
123
123
|
return JSON.stringify(questions).length;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
/**
|
|
127
|
-
|
|
126
|
+
/**
|
|
127
|
+
* What `FailproofAI/jev-policies`' sixteen checks spend of that budget, which a
|
|
128
|
+
* pack from anyone else shares with them on a machine that has both installed.
|
|
129
|
+
* Measured, not chosen: `pack-semantic-registry.test.ts` pins it to the
|
|
130
|
+
* reference copy in `__tests__/fixtures/jev-policies.ts`. Only `publish` reads
|
|
131
|
+
* it, to warn an author before release; the machine measures what is actually
|
|
132
|
+
* installed.
|
|
133
|
+
*/
|
|
134
|
+
export const JEV_POLICIES_QUESTION_CHARS = 18490;
|
|
128
135
|
|
|
129
136
|
export interface ResolvedSemanticPolicies {
|
|
130
137
|
policies: ReadonlyArray<SemanticPolicy>;
|
|
@@ -166,8 +173,7 @@ function toSemanticPolicy(entry: SemanticManifestEntry, pack: { id: string; vers
|
|
|
166
173
|
}
|
|
167
174
|
|
|
168
175
|
/**
|
|
169
|
-
* The semantic set these packs declare
|
|
170
|
-
* declares any.
|
|
176
|
+
* The semantic set these packs declare — empty when none of them declares any.
|
|
171
177
|
*
|
|
172
178
|
* Pure: it takes the packs rather than reading them, so the caller that already
|
|
173
179
|
* has them does not read `installed.json` a second time and a test does not need
|
|
@@ -177,16 +183,13 @@ export function semanticPoliciesFromPacks(
|
|
|
177
183
|
packs: ReadonlyArray<Pick<ResolvedPack, "id" | "semantic"> & { source?: string; version?: string }>,
|
|
178
184
|
): ResolvedSemanticPolicies {
|
|
179
185
|
const declared = packs.filter((p) => packSemantic(p).length > 0);
|
|
180
|
-
if (declared.length === 0) return { policies:
|
|
186
|
+
if (declared.length === 0) return { policies: [], fromPack: false, errors: [] };
|
|
181
187
|
|
|
182
|
-
//
|
|
183
|
-
// (`replacesBuiltinChecks`, which the reviewer set applies too). First-party
|
|
184
|
-
// packs spend the budget first, so install order cannot starve them.
|
|
185
|
-
const replace = replacesBuiltinChecks(declared);
|
|
188
|
+
// First-party packs spend the budget first, so install order cannot starve them.
|
|
186
189
|
const ordered = [...declared.filter((p) => isFirstPartyPack(p)), ...declared.filter((p) => !isFirstPartyPack(p))];
|
|
187
|
-
const policies: SemanticPolicy[] =
|
|
190
|
+
const policies: SemanticPolicy[] = [];
|
|
188
191
|
const errors: string[] = [];
|
|
189
|
-
const seen = new Set<string>(
|
|
192
|
+
const seen = new Set<string>();
|
|
190
193
|
// A name two packs declare DIFFERENTLY is asked for nobody. Keeping the first
|
|
191
194
|
// was the escalation: the question that decides another pack's policies came
|
|
192
195
|
// from whichever pack was listed first, so installing a permissive
|
|
@@ -196,7 +199,7 @@ export function semanticPoliciesFromPacks(
|
|
|
196
199
|
// be the same name — a check in the set with nobody's question, or a question
|
|
197
200
|
// nobody may name, are both worse than neither.
|
|
198
201
|
const contested = contestedSemanticNames(declared);
|
|
199
|
-
let spent =
|
|
202
|
+
let spent = 0;
|
|
200
203
|
for (const pack of ordered) {
|
|
201
204
|
for (const entry of packSemantic(pack)) {
|
|
202
205
|
if (isReservedClaim(pack, entry.name)) {
|
|
@@ -241,13 +244,11 @@ export function semanticPoliciesFromPacks(
|
|
|
241
244
|
}
|
|
242
245
|
}
|
|
243
246
|
|
|
244
|
-
// Every entry a declaring pack shipped was unusable
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
if (policies.length === (replace ? 0 : SEMANTIC_POLICIES.length)) return { policies: SEMANTIC_POLICIES, fromPack: false, errors };
|
|
250
|
-
return { policies, fromPack: true, errors };
|
|
247
|
+
// Every entry a declaring pack shipped was unusable: nothing is asked, and
|
|
248
|
+
// `fromPack` says no pack supplied anything. There is no compiled-in set to
|
|
249
|
+
// fall back to, and that is the safe direction — nothing is asked, so nothing
|
|
250
|
+
// a `reviewedBy` names is ever answered, and those policies stay hard.
|
|
251
|
+
return { policies, fromPack: policies.length > 0, errors };
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
const warned = new Set<string>();
|
|
@@ -262,10 +263,9 @@ const warned = new Set<string>();
|
|
|
262
263
|
* exactly that reason. The read costs one digest per pack against a call that is
|
|
263
264
|
* about to spend up to three seconds on the network.
|
|
264
265
|
*
|
|
265
|
-
* Never throws. An unreadable manifest yields
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
* builtin set, so nothing gets cleared by a policy nobody could read.
|
|
266
|
+
* Never throws. An unreadable manifest yields NO checks: nothing is asked and
|
|
267
|
+
* nothing is cleared, so every regex verdict stands. There is deliberately no
|
|
268
|
+
* compiled-in set to fall back to — the package ships none.
|
|
269
269
|
*/
|
|
270
270
|
export function resolveSemanticPolicies(cli?: string): ReadonlyArray<SemanticPolicy> {
|
|
271
271
|
let packs: ReadonlyArray<ResolvedPack> = [];
|
|
@@ -275,7 +275,7 @@ export function resolveSemanticPolicies(cli?: string): ReadonlyArray<SemanticPol
|
|
|
275
275
|
packs = read.packs;
|
|
276
276
|
manifestErrors = read.warnings ?? [];
|
|
277
277
|
} catch {
|
|
278
|
-
return
|
|
278
|
+
return [];
|
|
279
279
|
}
|
|
280
280
|
const resolved = semanticPoliciesFromPacks(jevPacks(packs, cli));
|
|
281
281
|
// Once per process per message, like `warnAuthority`: this runs on every gate
|