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.
Files changed (160) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  30. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/index.html +1 -1
  33. package/.next/standalone/.next/server/app/index.rsc +15 -15
  34. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  35. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  36. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  37. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  38. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  41. package/.next/standalone/.next/server/app/policies/page.js +3 -3
  42. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  55. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +20 -20
  59. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  60. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +2 -2
  62. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
  66. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0tmz862._.js +3 -0
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +2 -2
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  73. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
  74. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/ssr/{_0-vcssj._.js → _0n0lja6._.js} +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
  79. package/.next/standalone/.next/server/chunks/ssr/_1p3iz2d._.js +23 -0
  80. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +3 -3
  82. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +1 -1
  89. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  90. package/.next/standalone/.next/server/pages/404.html +1 -1
  91. package/.next/standalone/.next/server/pages/500.html +1 -1
  92. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  93. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  94. package/.next/standalone/.next/static/chunks/{3c3qbmosdjl6w.js → 0247hc9ath5t-.js} +1 -1
  95. package/.next/standalone/.next/static/chunks/{0m-9d6yn9hx4j.js → 03001k9jy-kst.js} +1 -1
  96. package/.next/standalone/.next/static/chunks/{2qdpj67x6ifk_.js → 06k7gsfnb7jkh.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/0qejem4-zt-hy.js +1 -0
  98. package/.next/standalone/.next/static/chunks/{21uv-uusw329x.js → 0snylg3y1i47a.js} +1 -1
  99. package/.next/standalone/.next/static/chunks/1h0if8ky362as.js +1 -0
  100. package/.next/standalone/.next/static/chunks/{0wbjy0zo-m7is.js → 2qsrrrl2maqsb.js} +1 -1
  101. package/.next/standalone/.next/static/chunks/{0bhidk90e-07f.css → 3_e_wg5_oay87.css} +1 -1
  102. package/.next/standalone/.next/static/chunks/3ns-egfh8i8i5.js +6 -0
  103. package/.next/standalone/.next/static/chunks/3rq7fjj9we0v3.js +1 -0
  104. package/.next/standalone/.next/static/chunks/42m0f2hh8vfb1.js +1 -0
  105. package/.next/standalone/app/actions/get-jev-config.ts +2 -2
  106. package/.next/standalone/app/actions/update-jev-config.ts +12 -9
  107. package/.next/standalone/app/components/jev-notices.tsx +10 -10
  108. package/.next/standalone/app/settings/jev-panel.tsx +11 -11
  109. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +2 -0
  110. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +26 -0
  111. package/.next/standalone/fp-cloud-cli/tests/test_output.py +27 -0
  112. package/.next/standalone/hermes-plugin/README.md +18 -8
  113. package/.next/standalone/package.json +10 -10
  114. package/.next/standalone/server.js +1 -1
  115. package/bin/failproofai.mjs +33 -7
  116. package/dist/cli.mjs +556 -521
  117. package/dist/worker.mjs +56 -375
  118. package/hermes-plugin/README.md +18 -8
  119. package/package.json +10 -10
  120. package/scripts/build-policy-pack.mjs +6 -9
  121. package/src/hooks/cloud-connection.ts +12 -6
  122. package/src/hooks/custom-hooks-loader.ts +1 -1
  123. package/src/hooks/effective-reviewers.ts +50 -43
  124. package/src/hooks/handler.ts +25 -16
  125. package/src/hooks/hermes-update.ts +74 -0
  126. package/src/hooks/hook-activity-store.ts +3 -3
  127. package/src/hooks/integrations.ts +349 -55
  128. package/src/hooks/jev-activity.ts +3 -3
  129. package/src/hooks/jev-cli.ts +17 -17
  130. package/src/hooks/jev-cloud-connection.ts +4 -4
  131. package/src/hooks/manager.ts +3 -2
  132. package/src/hooks/pack-cli.ts +24 -23
  133. package/src/hooks/pack-manifest.ts +25 -2
  134. package/src/hooks/pack-store.ts +2 -2
  135. package/src/hooks/policy-authority.ts +51 -32
  136. package/src/hooks/policy-evaluator.ts +5 -5
  137. package/src/hooks/policy-registry.ts +5 -7
  138. package/src/hooks/policy-reviewability.ts +26 -10
  139. package/src/hooks/policy-types.ts +4 -4
  140. package/src/hooks/semantic/combine.ts +13 -13
  141. package/src/hooks/semantic/evaluator.ts +5 -5
  142. package/src/hooks/semantic/jev-client.ts +1 -1
  143. package/src/hooks/semantic/jev-config.ts +21 -12
  144. package/src/hooks/semantic/jev-review.ts +3 -3
  145. package/src/hooks/semantic/jev-stats.ts +17 -17
  146. package/src/hooks/semantic/pack-policies.ts +48 -48
  147. package/src/hooks/semantic/policies.ts +13 -425
  148. package/src/hooks/semantic/precondition-names.ts +1 -1
  149. package/src/hooks/semantic/preconditions.ts +1 -1
  150. package/src/hooks/semantic/types.ts +1 -1
  151. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13t1zkw._.js +0 -3
  152. package/.next/standalone/.next/server/chunks/ssr/_13kfn90._.js +0 -23
  153. package/.next/standalone/.next/static/chunks/06dnzbolj00mc.js +0 -6
  154. package/.next/standalone/.next/static/chunks/16f3fa-lx38hk.js +0 -1
  155. package/.next/standalone/.next/static/chunks/2g2tki08kdhie.js +0 -1
  156. package/.next/standalone/.next/static/chunks/3-nbtkhg9y-1j.js +0 -1
  157. package/.next/standalone/.next/static/chunks/355km0ihuqo1p.js +0 -1
  158. /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → z1C0kJVF0NCtWn5R88hW3}/_buildManifest.js +0 -0
  159. /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → z1C0kJVF0NCtWn5R88hW3}/_clientMiddlewareManifest.js +0 -0
  160. /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
- * `shadow` mode computes and records all of it, and still returns the regex
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 a shadow verdict's model id has to pass the same
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 = "shadow" | "enforce";
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 SHADOW mode — the verdict enforce mode would
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 ShadowVerdict {
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 shadow: would have cleared). */
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
- * Shadow mode only, and only when Jev's own verdict was deny or instruct.
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
- shadowVerdict?: ShadowVerdict;
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, shadow records it, and the
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 shadow: would have). One that
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 === "shadow") {
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 shadowVerdict: ShadowVerdict | undefined =
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, ...(shadowVerdict ? { shadowVerdict } : {}) };
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 (an installed pack's, else the compiled-in one). */
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 set an installed pack declared, or the compiled-in one when no pack
310
- // declares any — see `pack-policies.ts` for the replacement rule. Resolved
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 (`shadow`), or
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" | "shadow" | "legacy-fallback";
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 shadow mode only); a redirect would hand the
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: "shadow"`.
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 | shadow | enforce`. `off` keeps the file — the
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; `shadow` logs Jev and enforces regex; `enforce` applies the combine rules. */
100
- export type JevConfigMode = "off" | "shadow" | "enforce";
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: "shadow" | "enforce" = "enforce";
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 shadow mode.
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
- if (o.mode !== "off" && o.mode !== "shadow" && o.mode !== "enforce") return { ok: false, problem: 'mode must be "off", "shadow" or "enforce"' };
671
- cfg.mode = o.mode;
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 shadow mode it changes nothing, and `off` sends
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 shadow: in enforce mode Jev's answers can clear a deny, " +
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 shadow"
688
- : "Use https, or mode shadow"),
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
- * shadow mode, say — would clear denies under the next config in enforce mode
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 === "shadow" || cfg.mode === "enforce" ? cfg.mode : DEFAULT_JEV_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 === "shadow" ? "shadow" : "two-tier",
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. Shadow mode's would-be clears are in
35
- * {@link JevStats.shadowClearsByPolicy}, so a renderer that prints only this
36
- * field says "nothing cleared" on a shadow-mode machine: print both.
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?: { shadow: number; enforce: number };
66
- /** What Jev would have cleared in shadow mode, where the regex result was enforced instead. */
67
- shadowClearsByPolicy?: Record<string, number>;
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: { shadow: 0, enforce: 0 },
164
- shadowClearsByPolicy: {},
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 shadowClearsByPolicy = new Counter();
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 === "shadow" ? shadowClearsByPolicy : clearsByPolicy;
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.shadowClearsByPolicy = shadowClearsByPolicy.toRecord();
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.shadowClearsByPolicy} as well as
221
- * `clearsByPolicy`: in shadow mode every clear Jev would have made is in the
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.shadowClearsByPolicy && Object.keys(s.shadowClearsByPolicy).length > 0) {
283
- lines.push(` Would clear: ${topCounts(s.shadowClearsByPolicy)} (shadow mode)`);
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.shadow > 0 && s.modes.enforce > 0) {
286
- lines.push(` Modes: enforce ${s.modes.enforce}, shadow ${s.modes.shadow}`);
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 semantic policy set this machine runs: a pack's, or the compiled-in one.
2
+ * Which Jev checks this machine asks: the ones its installed packs declare, and
3
+ * nothing else.
3
4
  *
4
- * ## The replacement rule
5
+ * ## No pack, no questions
5
6
  *
6
- * A FailproofAI pack that declares at least ONE `semantic` entry replaces
7
- * `SEMANTIC_POLICIES` wholesale. It mirrors the rule already in force for the
8
- * regex builtins — installing a pack stops the compiled builtins registering —
9
- * and it exists for the same reason: one source of truth, so a name collision
10
- * between a pack's `destructive-deletion` and the builtin of that name cannot
11
- * arise, and `reviewedBy: ["destructive-deletion"]` in a pack manifest cannot
12
- * silently mean the builtin's question set instead of the pack's.
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
- * Anyone else's checks are ADDED to the compiled-in set instead. Their names
15
- * cannot collide with it (a third party's claim to a builtin name is void:
16
- * `isReservedClaim`),
17
- * and replacing it would drop deny-mode checks like `credential-exfiltration`
18
- * that add denies the regex tier does not have — weaker, not noisier.
19
- *
20
- * Two packs that both declare entries CONCATENATE; a name declared by two packs
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, replacesBuiltinChecks } from "../effective-reviewers";
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, SEMANTIC_POLICIES, TASK_PROBES } from "./policies";
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
- * The real sixteen use `BUILTIN_QUESTION_CHARS` of it, so this is not a
99
- * constraint on the set we ship. A stranger's pack is added to them, so what it
100
- * may ask for is what they leave.
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
- /** What the compiled-in set spends of that budget when third-party checks join it. */
127
- export const BUILTIN_QUESTION_CHARS = SEMANTIC_POLICIES.reduce((n, p) => n + questionChars(p as SemanticManifestEntry), 0);
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, or the compiled-in set when none of them
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: SEMANTIC_POLICIES, fromPack: false, errors: [] };
186
+ if (declared.length === 0) return { policies: [], fromPack: false, errors: [] };
181
187
 
182
- // FailproofAI's own checks replace the compiled-in set; anyone else's join it
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[] = replace ? [] : [...SEMANTIC_POLICIES];
190
+ const policies: SemanticPolicy[] = [];
188
191
  const errors: string[] = [];
189
- const seen = new Set<string>(policies.map((p) => p.name));
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 = replace ? 0 : BUILTIN_QUESTION_CHARS;
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. The compiled-in set is
245
- // the honest answer — it is what the machine ran yesterday — and it is also
246
- // the safe one: what a pack's regex half names in `reviewedBy` will not match
247
- // it, so those policies stay hard rather than being cleared by questions
248
- // nobody validated.
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 the compiled-in set, which is the
266
- * same fail-open posture every other reader of that file takes, and here it also
267
- * fails in the safe direction: the set a pack's `reviewedBy` names is not the
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 SEMANTIC_POLICIES;
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