failproofai 1.0.2-beta.7 → 1.0.2-beta.9

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 (176) 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 +3 -3
  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 +3 -2
  24. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -3
  26. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
  29. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -3
  31. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/logout/route.js +4 -4
  33. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/status/route.js +4 -4
  35. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  38. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  39. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/index.html +1 -1
  42. package/.next/standalone/.next/server/app/index.rsc +15 -15
  43. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  44. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  45. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  46. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  47. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  48. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  51. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  52. package/.next/standalone/.next/server/app/policies/page.js +6 -5
  53. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  56. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  65. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  66. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  67. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  68. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
  69. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  70. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/chunks/[externals]__0-9ti7z._.js +3 -0
  73. package/.next/standalone/.next/server/chunks/{[externals]__1qzqcio._.js → [externals]__0sg9-3p._.js} +1 -1
  74. package/.next/standalone/.next/server/chunks/[externals]__0u3wrvq._.js +3 -0
  75. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p8poxd._.js → [root-of-the-server]__0igxt32._.js} +2 -2
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zlqo2t._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1anxb5k._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +5 -0
  82. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +27 -0
  83. package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +3 -0
  84. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/{_19uwam-._.js → _1-akg6w._.js} +2 -2
  87. package/.next/standalone/.next/server/chunks/_185gyk9._.js +3 -0
  88. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +5 -7
  89. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_09d5c79._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js} +1 -1
  90. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1d2g73o._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js} +1 -1
  91. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +10 -0
  94. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
  99. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +2 -2
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n_lxhg._.js +5 -0
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +2 -2
  104. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1jjjg6g._.js → [root-of-the-server]__1_7n3xe._.js} +2 -2
  105. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +2 -2
  106. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/{_1uvml5y._.js → _0b6xubz._.js} +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07gm3zl._.js → _1es2j7i._.js} +7 -21
  109. package/.next/standalone/.next/server/chunks/ssr/{_0_nsohj._.js → _1pj-r7q._.js} +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +17 -0
  113. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  116. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +10 -0
  118. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +3 -0
  120. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  121. package/.next/standalone/.next/server/middleware.js +1 -1
  122. package/.next/standalone/.next/server/pages/404.html +1 -1
  123. package/.next/standalone/.next/server/pages/500.html +1 -1
  124. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  125. package/.next/standalone/.next/server/server-reference-manifest.json +19 -19
  126. package/.next/standalone/.next/static/chunks/{3swqstog1cd9u.js → 06zoac_7ffkus.js} +1 -1
  127. package/.next/standalone/.next/static/chunks/{0icodhlwr9x_e.js → 0bshgqm5waivd.js} +2 -2
  128. package/.next/standalone/.next/static/chunks/{2-9lvhuto1iwo.js → 13grq7pifzvu5.js} +1 -1
  129. package/.next/standalone/.next/static/chunks/{3i8rg2fv9nd6e.js → 16pmxmg9x48ob.js} +1 -1
  130. package/.next/standalone/.next/static/chunks/{44oamw33aso19.js → 1da7-a7ek66uy.js} +1 -1
  131. package/.next/standalone/.next/static/chunks/{1d6c85605lsgp.js → 1fg7drro5kle-.js} +1 -1
  132. package/.next/standalone/.next/static/chunks/{0tvmd0p_1i_g6.js → 1opyu56l4cdfk.js} +1 -1
  133. package/.next/standalone/.next/static/chunks/{1d7o3wnt3h6se.js → 1p-o1i0lyt9un.js} +1 -1
  134. package/.next/standalone/.next/static/chunks/{3vqddiy9tvlwf.js → 2mfojtsy214sf.js} +1 -1
  135. package/.next/standalone/.next/static/chunks/{3jt6n16p2qpex.js → 32jj4qy40hln_.js} +1 -1
  136. package/.next/standalone/.next/static/chunks/{1vzyn9h2kvj-i.js → 3ww5ik9eo_1b_.js} +1 -1
  137. package/.next/standalone/.next/static/chunks/{1mfzsja6ftim5.css → 40nr6ehj4p3cw.css} +1 -1
  138. package/.next/standalone/.next/static/chunks/{turbopack-2x-o099q64i8h.js → turbopack-1gehh2qpjyj5_.js} +1 -1
  139. package/.next/standalone/app/actions/get-hooks-config.ts +8 -1
  140. package/.next/standalone/app/actions/pack-actions.ts +12 -1
  141. package/.next/standalone/app/policies/hooks-client.tsx +41 -4
  142. package/.next/standalone/fp-cloud-cli/tests/test_pagination.py +204 -0
  143. package/.next/standalone/package.json +9 -9
  144. package/.next/standalone/server.js +1 -1
  145. package/bin/failproofai.mjs +57 -13
  146. package/dist/cli.mjs +23076 -20000
  147. package/dist/worker.mjs +1252 -542
  148. package/package.json +9 -9
  149. package/scripts/build-policy-pack.mjs +28 -4
  150. package/src/audit/index.ts +24 -4
  151. package/src/hooks/builtin-policies.ts +933 -8
  152. package/src/hooks/custom-hooks-loader.ts +98 -8
  153. package/src/hooks/handler.ts +26 -14
  154. package/src/hooks/manager.ts +36 -2
  155. package/src/hooks/pack-cli.ts +1224 -121
  156. package/src/hooks/pack-failclosed.ts +100 -4
  157. package/src/hooks/pack-manifest.ts +63 -1
  158. package/src/hooks/pack-param-key.ts +36 -0
  159. package/src/hooks/pack-store.ts +309 -25
  160. package/src/hooks/policy-catalog.ts +9 -1
  161. package/src/hooks/policy-evaluator.ts +77 -1
  162. package/src/hooks/tui.ts +21 -1
  163. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06cuf1y._.js +0 -3
  164. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
  165. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
  166. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +0 -3
  167. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
  168. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
  169. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tg4pu6._.js +0 -3
  170. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
  171. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
  172. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mgrjnx._.js +0 -10
  173. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1opxw-7._.js +0 -5
  174. /package/.next/standalone/.next/static/{PKwyBPAHjy3DB8qqapaH4 → BFSuuqOY0YU7NpThROqre}/_buildManifest.js +0 -0
  175. /package/.next/standalone/.next/static/{PKwyBPAHjy3DB8qqapaH4 → BFSuuqOY0YU7NpThROqre}/_clientMiddlewareManifest.js +0 -0
  176. /package/.next/standalone/.next/static/{PKwyBPAHjy3DB8qqapaH4 → BFSuuqOY0YU7NpThROqre}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "1.0.2-beta.7",
3
+ "version": "1.0.2-beta.9",
4
4
  "description": "Observability and enforcement for AI agent harnesses. 39 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
5
5
  "bin": {
6
6
  "failproofai": "./dist/cli.mjs",
@@ -120,15 +120,15 @@
120
120
  "sharp": "0.35.0"
121
121
  },
122
122
  "optionalDependencies": {
123
- "@failproofai/failproofaid-linux-x64": "1.0.2-beta.7",
124
- "@failproofai/failproofaid-linux-arm64": "1.0.2-beta.7",
125
- "@failproofai/failproofaid-darwin-x64": "1.0.2-beta.7",
126
- "@failproofai/failproofaid-darwin-arm64": "1.0.2-beta.7"
123
+ "@failproofai/failproofaid-linux-x64": "1.0.2-beta.9",
124
+ "@failproofai/failproofaid-linux-arm64": "1.0.2-beta.9",
125
+ "@failproofai/failproofaid-darwin-x64": "1.0.2-beta.9",
126
+ "@failproofai/failproofaid-darwin-arm64": "1.0.2-beta.9"
127
127
  },
128
128
  "failproofaidBinaries": {
129
- "linux-x64": "75a8e231e057986f2b357e1fcb596641afd82a4b58f9271bb4b342951a364c2d",
130
- "linux-arm64": "47d2933d06e70faefa78fe06fdd4e06bc03e94a319d13a8837145d192cd84d8c",
131
- "darwin-x64": "6eae79c75746efe6922ca84e7dd899d01a50e651879abc8f90de69cd51c403f3",
132
- "darwin-arm64": "ddca616decc3d2415ce6a58ae5fe15786bd2cfa5a5319803f4207a01d40e4a0a"
129
+ "linux-x64": "906b0c8a7502ad6a0494231a2220fff65db7ff8335ad7bdf15b58079a001562f",
130
+ "linux-arm64": "8b40f161cedf07ec3504dd736c906666d18bf042613af0a6a38f4daae8d7c7d4",
131
+ "darwin-x64": "7407d02234c24932ac9ece07bca348bd40c7a4edff72e0a8db9ac230a1e28a8c",
132
+ "darwin-arm64": "77131a4f0c1b9d3e5611ad3f4834542e26e32a404f1311903a1cf7783fe99fee"
133
133
  }
134
134
  }
@@ -37,6 +37,9 @@ import { createHash } from "node:crypto";
37
37
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
38
38
  import { dirname, join, resolve } from "node:path";
39
39
  import { fileURLToPath } from "node:url";
40
+ // The one place the pack's id is decided. Imported rather than restated — see
41
+ // PACK_ID below for what restating it cost.
42
+ import { CORE_SOURCE } from "../src/hooks/pack-store.ts";
40
43
 
41
44
  const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
42
45
  // `--out <dir>` lets the conformance test generate into a temp directory rather
@@ -47,10 +50,17 @@ const outArg = process.argv.indexOf("--out");
47
50
  const OUT_DIR = outArg !== -1 && process.argv[outArg + 1]
48
51
  ? resolve(process.argv[outArg + 1])
49
52
  : join(ROOT, "policy-pack");
50
- // Not `failproofai/builtins`. These stop being builtins the moment they ship as
51
- // a pack, and an id is the most durable place a retired word can hide — it ends
52
- // up in every machine's installed.json and in the listing every user reads.
53
- const PACK_ID = "failproofai/core";
53
+ // Not restated. The id is the most durable place a retired word can hide — it
54
+ // ends up in every machine's installed.json and in the listing every user reads
55
+ // — and restating it here is how it hid twice: `failproofai/builtins` shipped
56
+ // first, was corrected to `failproofai/core` HERE and nowhere else, and then
57
+ // `core` was retired in pack-store while this line went on saying it. The one
58
+ // published release still declares `failproofai/builtins`, so a machine that
59
+ // installed it cannot be addressed by either name anybody would type.
60
+ //
61
+ // Read from the layer that owns the set, so the next rename reaches this file
62
+ // whether or not somebody remembers it exists.
63
+ const PACK_ID = CORE_SOURCE;
54
64
  const ENTRY_ASSET = "failproofai-pack.mjs";
55
65
  const MANIFEST_ASSET = "failproofai-pack.json";
56
66
  const CHECKSUMS_ASSET = "SHA256SUMS";
@@ -60,6 +70,17 @@ const version = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).ver
60
70
  // A generated entry that registers every non-alwaysOn builtin through the public
61
71
  // policy API — the same API a third-party pack uses. `failproofai` stays external
62
72
  // so the loader's shim resolves it, exactly as it does for any other pack.
73
+ //
74
+ // It registers `category` and `defaultEnabled` as well as name/description/
75
+ // match/fn, and that is not decoration. This script builds ITS manifest straight
76
+ // from POLICY_CATALOG, so omitting them from the ENTRY looked harmless — and was
77
+ // not: the entry IS the artifact, and anything that rebuilds a manifest from it
78
+ // saw 38 policies filed under "General" with none on by default. `failproofai
79
+ // publish` does exactly that, which is how a release that would have enforced
80
+ // nothing on a default install got as far as a dry run.
81
+ //
82
+ // Note for whoever edits the template below: it is a template literal, so a
83
+ // backtick inside it ends the string. Comments in there stay backtick-free.
63
84
  const entrySource = `
64
85
  import { customPolicies } from "failproofai";
65
86
  import { BUILTIN_POLICIES } from ${JSON.stringify(join(ROOT, "src/hooks/builtin-policies.ts"))};
@@ -69,6 +90,9 @@ for (const policy of BUILTIN_POLICIES) {
69
90
  customPolicies.add({
70
91
  name: policy.name,
71
92
  description: policy.description,
93
+ // Both fields matter here; see the note above entrySource.
94
+ category: policy.category,
95
+ defaultEnabled: policy.defaultEnabled === true,
72
96
  match: policy.match,
73
97
  fn: policy.fn,
74
98
  });
@@ -100,6 +100,17 @@ interface ScanOutcome {
100
100
  result: TranscriptAuditResult;
101
101
  bytesScanned: number;
102
102
  detectorState: DetectorSessionState;
103
+ /**
104
+ * Whether this result covers the TAIL only, or the whole transcript.
105
+ *
106
+ * The caller merges a cached prefix onto the result, which is right for a
107
+ * resumed scan and wrong for a full one — and a resume that could not be
108
+ * honoured falls back to a full read RIGHT HERE, several returns away from
109
+ * the merge. Without this the fallback merged a whole-file result onto a
110
+ * prefix that already contained it, and every finding before the resume point
111
+ * was counted twice.
112
+ */
113
+ resumed: boolean;
103
114
  }
104
115
 
105
116
  async function scanOneTranscript(
@@ -125,6 +136,7 @@ async function scanOneTranscript(
125
136
  // `errors` rather than silently returning an empty hits map.
126
137
  let events: NormalizedToolEvent[];
127
138
  let bytesScanned = 0;
139
+ let resumed = false;
128
140
  // A resumed scan carries the stateful detectors' state forward across the
129
141
  // boundary. Starting them empty would silently change what they find — the
130
142
  // re-read detector's countdown spans tool calls, so an edit before the offset
@@ -151,6 +163,7 @@ async function scanOneTranscript(
151
163
  events = scan.events;
152
164
  bytesScanned = scan.bytesConsumed;
153
165
  empty.cwd = scan.cwd ?? "";
166
+ resumed = true;
154
167
  }
155
168
  } else {
156
169
  // A source with no byte offsets to speak of — a database. Scanned whole,
@@ -160,7 +173,7 @@ async function scanOneTranscript(
160
173
  }
161
174
 
162
175
  if (events.length === 0) {
163
- return { result: empty, bytesScanned, detectorState: sessionState };
176
+ return { result: empty, bytesScanned, detectorState: sessionState, resumed };
164
177
  }
165
178
 
166
179
  const result = empty;
@@ -201,7 +214,7 @@ async function scanOneTranscript(
201
214
  }
202
215
  }
203
216
 
204
- return { result, bytesScanned, detectorState: sessionState };
217
+ return { result, bytesScanned, detectorState: sessionState, resumed };
205
218
  }
206
219
 
207
220
  function formatPolicyExample(_policyName: string, event: NormalizedToolEvent): string {
@@ -447,8 +460,15 @@ async function runAuditInner(opts: RunAuditOptions, startedAt: number): Promise<
447
460
  try {
448
461
  const scan = await scanOneTranscript(meta, resume);
449
462
  // A resumed scan produced hits for the TAIL only; the cached result holds
450
- // everything before it. Merging is what makes the two halves one answer.
451
- const fresh = cachedPrefix ? mergeIncremental(cachedPrefix, scan.result) : scan.result;
463
+ // everything before it. Merging is what makes the two halves one answer —
464
+ // and `scan.resumed`, not `cachedPrefix`, is what says there are two
465
+ // halves. A resume the reader could not honour (truncated, unparseable
466
+ // from that offset, rewritten under us) falls back to reading the WHOLE
467
+ // file, and merging the prefix onto that counted everything before the
468
+ // resume point twice.
469
+ const fresh = cachedPrefix && scan.resumed
470
+ ? mergeIncremental(cachedPrefix, scan.result)
471
+ : scan.result;
452
472
  if (!opts.noCache) {
453
473
  writeCachedTranscriptResult(
454
474
  meta.transcriptPath,