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.
Files changed (191) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +5 -5
  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/build-manifest.json +2 -2
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  15. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  32. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  33. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  35. package/.next/standalone/.next/server/app/index.html +1 -1
  36. package/.next/standalone/.next/server/app/index.rsc +15 -15
  37. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  38. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  39. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  40. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  41. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  45. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  46. package/.next/standalone/.next/server/app/policies/page.js +4 -4
  47. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  50. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  51. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  59. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  60. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
  63. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  64. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +20 -20
  68. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +2 -2
  71. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
  73. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03m2iw6._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  77. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
  78. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
  80. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__12e7nhs._.js → [root-of-the-server]__1-bm5zf._.js} +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +2 -2
  83. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +2 -2
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
  86. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/ssr/_11ilmst._.js +23 -0
  90. package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +3 -3
  91. package/.next/standalone/.next/server/chunks/ssr/{_0-vcssj._.js → _1jatlmf._.js} +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +3 -3
  94. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  96. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +1 -1
  102. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  103. package/.next/standalone/.next/server/pages/404.html +1 -1
  104. package/.next/standalone/.next/server/pages/500.html +1 -1
  105. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  106. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  107. package/.next/standalone/.next/static/chunks/{06jxjnw8bfczc.js → 0542cuk_va8g1.js} +1 -1
  108. package/.next/standalone/.next/static/chunks/{0wbjy0zo-m7is.js → 08ckontevw-0w.js} +1 -1
  109. package/.next/standalone/.next/static/chunks/0pefjgvu4x-sx.js +1 -0
  110. package/.next/standalone/.next/static/chunks/{21uv-uusw329x.js → 1-d48e9k-7e1a.js} +1 -1
  111. package/.next/standalone/.next/static/chunks/{0m-9d6yn9hx4j.js → 1r74hmr1wm_sd.js} +1 -1
  112. package/.next/standalone/.next/static/chunks/1uec0n0lcx5h1.js +1 -0
  113. package/.next/standalone/.next/static/chunks/21-pqc3-l9ak2.js +6 -0
  114. package/.next/standalone/.next/static/chunks/21qlo00jis4w-.js +1 -0
  115. package/.next/standalone/.next/static/chunks/2n00c3zqyloy9.js +1 -0
  116. package/.next/standalone/.next/static/chunks/{0bhidk90e-07f.css → 3_e_wg5_oay87.css} +1 -1
  117. package/.next/standalone/.next/static/chunks/{3c3qbmosdjl6w.js → 3tv107_for_n7.js} +1 -1
  118. package/.next/standalone/.next/static/chunks/{2qdpj67x6ifk_.js → 3vp54x0dyrrm6.js} +1 -1
  119. package/.next/standalone/.next/static/chunks/{turbopack-38nzuvp95_2rv.js → turbopack-11dn2pegmg9_9.js} +1 -1
  120. package/.next/standalone/app/actions/get-jev-config.ts +2 -2
  121. package/.next/standalone/app/actions/update-jev-config.ts +12 -9
  122. package/.next/standalone/app/components/jev-notices.tsx +10 -10
  123. package/.next/standalone/app/settings/jev-panel.tsx +11 -11
  124. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +2 -0
  125. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +26 -0
  126. package/.next/standalone/fp-cloud-cli/tests/test_output.py +27 -0
  127. package/.next/standalone/fp-cloud-cli/uv.lock +3 -3
  128. package/.next/standalone/hermes-plugin/README.md +18 -8
  129. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  130. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  131. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
  132. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  133. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  134. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  135. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  136. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  137. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  138. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  139. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  140. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  141. package/.next/standalone/node_modules/next/package.json +10 -10
  142. package/.next/standalone/package.json +12 -12
  143. package/.next/standalone/sdk/python/uv.lock +18 -18
  144. package/.next/standalone/server.js +1 -1
  145. package/bin/failproofai.mjs +33 -7
  146. package/dist/cli.mjs +1385 -1158
  147. package/dist/worker.mjs +59 -375
  148. package/hermes-plugin/README.md +18 -8
  149. package/package.json +12 -12
  150. package/scripts/build-policy-pack.mjs +6 -9
  151. package/src/hooks/cloud-connection.ts +12 -6
  152. package/src/hooks/custom-hooks-loader.ts +1 -1
  153. package/src/hooks/effective-reviewers.ts +50 -43
  154. package/src/hooks/handler.ts +25 -16
  155. package/src/hooks/hermes-update.ts +74 -0
  156. package/src/hooks/hook-activity-store.ts +3 -3
  157. package/src/hooks/integrations.ts +472 -68
  158. package/src/hooks/jev-activity.ts +8 -3
  159. package/src/hooks/jev-cli.ts +17 -17
  160. package/src/hooks/jev-cloud-connection.ts +4 -4
  161. package/src/hooks/manager.ts +3 -2
  162. package/src/hooks/pack-cli.ts +24 -23
  163. package/src/hooks/pack-manifest.ts +25 -2
  164. package/src/hooks/pack-store.ts +2 -2
  165. package/src/hooks/policy-authority.ts +51 -32
  166. package/src/hooks/policy-evaluator.ts +5 -5
  167. package/src/hooks/policy-registry.ts +5 -7
  168. package/src/hooks/policy-reviewability.ts +30 -12
  169. package/src/hooks/policy-types.ts +4 -4
  170. package/src/hooks/safe-config-write.ts +119 -0
  171. package/src/hooks/semantic/combine.ts +13 -13
  172. package/src/hooks/semantic/evaluator.ts +5 -5
  173. package/src/hooks/semantic/jev-client.ts +1 -1
  174. package/src/hooks/semantic/jev-config.ts +21 -12
  175. package/src/hooks/semantic/jev-review.ts +3 -3
  176. package/src/hooks/semantic/jev-stats.ts +17 -17
  177. package/src/hooks/semantic/pack-policies.ts +48 -48
  178. package/src/hooks/semantic/policies.ts +13 -425
  179. package/src/hooks/semantic/precondition-names.ts +1 -1
  180. package/src/hooks/semantic/preconditions.ts +1 -1
  181. package/src/hooks/semantic/types.ts +1 -1
  182. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13t1zkw._.js +0 -3
  183. package/.next/standalone/.next/server/chunks/ssr/_13kfn90._.js +0 -23
  184. package/.next/standalone/.next/static/chunks/06dnzbolj00mc.js +0 -6
  185. package/.next/standalone/.next/static/chunks/16f3fa-lx38hk.js +0 -1
  186. package/.next/standalone/.next/static/chunks/2g2tki08kdhie.js +0 -1
  187. package/.next/standalone/.next/static/chunks/3-nbtkhg9y-1j.js +0 -1
  188. package/.next/standalone/.next/static/chunks/355km0ihuqo1p.js +0 -1
  189. /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → ozuWoyIwOTqeUUjKqOKiH}/_buildManifest.js +0 -0
  190. /package/.next/standalone/.next/static/{5yKM8NoTOpUI0MoQl8eo5 → ozuWoyIwOTqeUUjKqOKiH}/_clientMiddlewareManifest.js +0 -0
  191. /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(["shadow", "enforce"]);
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 === "shadow" ? "would have cleared" : "cleared"} ${cleared.join(", ")}`);
382
+ facts.push(`${e.jevMode === "observe" ? "would have cleared" : "cleared"} ${cleared.join(", ")}`);
378
383
  }
379
- if (e.jevMode === "shadow") facts.push("shadow mode: the regex result was enforced");
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).
@@ -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|shadow|enforce] (FailproofAI Cloud: no key, no URL)",
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
- : "shadow — Jev is asked and logged; the regex result is what is enforced";
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 shadow mode every clear Jev would have made is in
462
- // `shadowClearsByPolicy` instead and this one is empty. Printing the first
463
- // alone would tell a shadow user "cleared nothing" — the one number they
464
- // turned shadow mode on to watch. The field is optional because the stats
465
- // module T1 builds against does not have it yet (T8 adds it).
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
- ...(shadowClears ? ([["would have cleared (shadow)", shadowClears]] as Array<[string, string]>) : []),
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
- // Shadow unless told otherwise, as `config --token` starts it: Jev on a new
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
- const mode = values.get("--mode") ?? (sameProvider && typeof next.mode === "string" ? next.mode : "shadow");
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 shadow mode, when there is no jev.json yet:",
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 shadow mode lets a loopback http URL through validation. Null when
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: "shadow", ...displayOnlyCloudOrigin(raw) };
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 shadow", "Switch it back on (shadow logs Jev and keeps enforcing regex; enforce lets it clear), here or from the dashboard:", opts),
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 shadow"
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 shadow mode — logged, never enforced, until its owner
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 shadow file. The
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 = "shadow" as const;
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(): "shadow" | "enforce" | null {
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;
@@ -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 { hasInstalledPacks, readInstalledPacks } from "./pack-manifest";
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
- if (!hasInstalledPacks()) {
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
@@ -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 { BUILTIN_QUESTION_CHARS, MAX_PACK_QUESTION_CHARS, questionChars, semanticPoliciesFromPacks } from "./semantic/pack-policies";
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 is ADDED to the built-in checks, which every
488
- // machine spends the budget on first (`semanticPoliciesFromPacks`), so it gets
489
- // only what they leave.
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 : BUILTIN_QUESTION_CHARS);
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 the 16 built-in checks every machine asks take ${BUILTIN_QUESTION_CHARS} of it first.`),
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
- // the builtin list would call it a name "this build does not have". A pack
515
- // with no semantic entries still answers to the builtin set, which is what its
516
- // machines will be running.
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, and the replacement rule
594
- // turns that into "this pack replaced the compiled-in set with nothing".
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
- : `${firstParty ? "replacing" : "added to"} the built-in checks where it installs.`),
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
- * How a pack's Jev checks sit beside this build's, for add, show and the
2655
- * picker alike: a FailproofAI pack's replace them, anyone else's are added
2656
- * (`replacesBuiltinChecks`), and only where `jevPacks` lets the pack take part —
2657
- * never for an observe pack, only for its agents when scoped. One phrase, so
2658
- * the three cannot drift apart again.
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 besideBuiltinChecks(pack: { source?: string; effect?: PolicyEffect; clis?: string[] | null }): string {
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 = isFirstPartyPack(pack) ? "replacing this build's own set" : "added to this build's own checks";
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
- `${besideBuiltinChecks(preview)}.\n See: failproofai policies show ${source}\n\n`,
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)}, ${besideBuiltinChecks(result)}. ` +
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 replaces the compiled-in set (`effectiveReviewerNames`), so
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, ${besideBuiltinChecks(pack)}.`,
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. A pack that declares at least one REPLACES the compiled-in
156
- * semantic set wholesale — see `semantic/pack-policies.ts`.
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")
@@ -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", and the replacement
1107
- // rule would then have it replace the compiled-in set with nothing.
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 this build does not have, or an `alwaysOn` policy: all hard.
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 `reviewedBy` may use: the semantic policies in
59
- * `src/hooks/semantic/policies.ts`. Not the probes (`INJECTION_PROBE`,
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
- * Written out rather than read off `SEMANTIC_POLICIES`, and pinned to it by
64
- * `policy-authority.test.ts`. The registry imports this module, so a runtime
65
- * import of the semantic policies would load all sixteen of Jev's prompts on
66
- * every hook event of a machine that never configured Jev, and bundle them into
67
- * every pack artifact that registers a policy.
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 compiled-in reviewer set, and the DEFAULT rather than the only one.
90
- *
91
- * A pack that declares its own `semantic` entries replaces
92
- * `SEMANTIC_POLICIES` wholesale on the machine that installed it, so the names
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 SEMANTIC_REVIEWER_NAMES: ReadonlySet<string> = new Set(SEMANTIC_POLICY_NAMES);
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 semantic policy this build has,
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. Defaults to the compiled-in set, which is what runs until a pack
119
- * ships its own; see {@link SEMANTIC_REVIEWER_NAMES}. It is a parameter rather
120
- * than a lookup because this module is imported by the registry, and reading
121
- * which set is live means reading a file — a cost registration is willing to
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> = SEMANTIC_REVIEWER_NAMES,
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 the
148
- * compiled-in set: a pack's own checks, or a name two packs disagree on, is a
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, this build's set otherwise. A pack that
209
- * carries both halves of the two-tier set names its own checks, and refusing
210
- * that would make the feature unpublishable by the tool that implements it.
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 this build's compiled-in set, which is what a machine with no
287
- * pack runs.
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> = SEMANTIC_REVIEWER_NAMES,
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 ShadowVerdict,
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
- /** Shadow mode: Jev's own deny/instruct, recorded as a "would have" (see `ShadowVerdict`). */
66
- shadowVerdict?: ShadowVerdict;
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 === "shadow" ? "would clear" : "cleared"}: ${combined.cleared.join(", ")}`);
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.shadowVerdict ? { shadowVerdict: combined.shadowVerdict } : {}),
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, not against the ones this
77
- // build compiled in. A pack that ships its own `semantic` set replaces the
78
- // compiled one, so its policies name checks that exist here and nowhere in
79
- // `SEMANTIC_POLICY_NAMES` — and judging them by the builtin list would
80
- // downgrade the whole rewritten set to `hard` while reporting nothing but a
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,