@lannguyensi/harness 0.64.0 → 1.0.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 (710) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +46 -42
  3. package/dist/cli/add/index.js +17 -21
  4. package/dist/cli/add/index.js.map +1 -1
  5. package/dist/cli/adopt/derive.d.ts +2 -2
  6. package/dist/cli/adopt/derive.js +2 -19
  7. package/dist/cli/adopt/derive.js.map +1 -1
  8. package/dist/cli/adopt/index.d.ts +1 -1
  9. package/dist/cli/adopt/index.js +1 -12
  10. package/dist/cli/adopt/index.js.map +1 -1
  11. package/dist/cli/apply/apply.d.ts +20 -35
  12. package/dist/cli/apply/apply.js +61 -136
  13. package/dist/cli/apply/apply.js.map +1 -1
  14. package/dist/cli/apply/generate-codex-config.js +10 -58
  15. package/dist/cli/apply/generate-codex-config.js.map +1 -1
  16. package/dist/cli/apply/generate-memory-index.js +1 -2
  17. package/dist/cli/apply/generate-memory-index.js.map +1 -1
  18. package/dist/cli/apply/generate-opencode-config.d.ts +2 -10
  19. package/dist/cli/apply/generate-opencode-config.js +74 -113
  20. package/dist/cli/apply/generate-opencode-config.js.map +1 -1
  21. package/dist/cli/apply/generate-settings.d.ts +11 -64
  22. package/dist/cli/apply/generate-settings.js +27 -132
  23. package/dist/cli/apply/generate-settings.js.map +1 -1
  24. package/dist/cli/apply/index.d.ts +1 -1
  25. package/dist/cli/apply/index.js +1 -1
  26. package/dist/cli/apply/index.js.map +1 -1
  27. package/dist/cli/apply/install-codex-config.d.ts +153 -1
  28. package/dist/cli/apply/install-codex-config.js +445 -70
  29. package/dist/cli/apply/install-codex-config.js.map +1 -1
  30. package/dist/cli/apply/next-steps.d.ts +7 -0
  31. package/dist/cli/apply/next-steps.js +13 -1
  32. package/dist/cli/apply/next-steps.js.map +1 -1
  33. package/dist/cli/bounded-stdin.d.ts +0 -23
  34. package/dist/cli/bounded-stdin.js +2 -49
  35. package/dist/cli/bounded-stdin.js.map +1 -1
  36. package/dist/cli/describe.d.ts +1 -1
  37. package/dist/cli/describe.js +0 -3
  38. package/dist/cli/describe.js.map +1 -1
  39. package/dist/cli/diff/engine.d.ts +0 -11
  40. package/dist/cli/diff/engine.js +1 -17
  41. package/dist/cli/diff/engine.js.map +1 -1
  42. package/dist/cli/diff/index.js +14 -25
  43. package/dist/cli/diff/index.js.map +1 -1
  44. package/dist/cli/doctor/claude-mcp.d.ts +4 -19
  45. package/dist/cli/doctor/claude-mcp.js +11 -22
  46. package/dist/cli/doctor/claude-mcp.js.map +1 -1
  47. package/dist/cli/doctor/codex.d.ts +12 -14
  48. package/dist/cli/doctor/codex.js +31 -144
  49. package/dist/cli/doctor/codex.js.map +1 -1
  50. package/dist/cli/doctor/format.js +23 -345
  51. package/dist/cli/doctor/format.js.map +1 -1
  52. package/dist/cli/doctor/hook-liveness.d.ts +56 -0
  53. package/dist/cli/doctor/hook-liveness.js +455 -0
  54. package/dist/cli/doctor/hook-liveness.js.map +1 -0
  55. package/dist/cli/doctor/index.d.ts +13 -87
  56. package/dist/cli/doctor/index.js +57 -713
  57. package/dist/cli/doctor/index.js.map +1 -1
  58. package/dist/cli/doctor/opencode.d.ts +2 -1
  59. package/dist/cli/doctor/opencode.js.map +1 -1
  60. package/dist/cli/doctor/types.d.ts +21 -387
  61. package/dist/cli/doctor/types.js.map +1 -1
  62. package/dist/cli/drain-stdio.d.ts +31 -0
  63. package/dist/cli/drain-stdio.js +76 -0
  64. package/dist/cli/drain-stdio.js.map +1 -0
  65. package/dist/cli/export.d.ts +3 -1
  66. package/dist/cli/export.js +7 -16
  67. package/dist/cli/export.js.map +1 -1
  68. package/dist/cli/gate/codex-disable.d.ts +29 -0
  69. package/dist/cli/gate/codex-disable.js +195 -0
  70. package/dist/cli/gate/codex-disable.js.map +1 -0
  71. package/dist/cli/gate/codex-enable.d.ts +41 -0
  72. package/dist/cli/gate/codex-enable.js +127 -0
  73. package/dist/cli/gate/codex-enable.js.map +1 -0
  74. package/dist/cli/gate/codex-snapshot.d.ts +208 -0
  75. package/dist/cli/gate/codex-snapshot.js +402 -0
  76. package/dist/cli/gate/codex-snapshot.js.map +1 -0
  77. package/dist/cli/gate/disable.d.ts +9 -0
  78. package/dist/cli/gate/disable.js +263 -28
  79. package/dist/cli/gate/disable.js.map +1 -1
  80. package/dist/cli/gate/enable.d.ts +26 -6
  81. package/dist/cli/gate/enable.js +152 -80
  82. package/dist/cli/gate/enable.js.map +1 -1
  83. package/dist/cli/gate/link-chain.d.ts +53 -0
  84. package/dist/cli/gate/link-chain.js +122 -0
  85. package/dist/cli/gate/link-chain.js.map +1 -0
  86. package/dist/cli/gate/snapshot.d.ts +289 -12
  87. package/dist/cli/gate/snapshot.js +481 -22
  88. package/dist/cli/gate/snapshot.js.map +1 -1
  89. package/dist/cli/index.d.ts +0 -9
  90. package/dist/cli/index.js +9 -2131
  91. package/dist/cli/index.js.map +1 -1
  92. package/dist/cli/init/composer.d.ts +3 -6
  93. package/dist/cli/init/composer.js +6 -370
  94. package/dist/cli/init/composer.js.map +1 -1
  95. package/dist/cli/init/dependencies.d.ts +1 -2
  96. package/dist/cli/init/dependencies.js +4 -80
  97. package/dist/cli/init/dependencies.js.map +1 -1
  98. package/dist/cli/init/detect.js +5 -4
  99. package/dist/cli/init/detect.js.map +1 -1
  100. package/dist/cli/init/interactive.d.ts +1 -23
  101. package/dist/cli/init/interactive.js +33 -247
  102. package/dist/cli/init/interactive.js.map +1 -1
  103. package/dist/cli/init/profiles.d.ts +2 -2
  104. package/dist/cli/init/profiles.js +51 -245
  105. package/dist/cli/init/profiles.js.map +1 -1
  106. package/dist/cli/init/templates.d.ts +2 -37
  107. package/dist/cli/init/templates.js +25 -1237
  108. package/dist/cli/init/templates.js.map +1 -1
  109. package/dist/cli/list.d.ts +1 -1
  110. package/dist/cli/list.js +1 -38
  111. package/dist/cli/list.js.map +1 -1
  112. package/dist/cli/loader.d.ts +15 -1
  113. package/dist/cli/loader.js +56 -39
  114. package/dist/cli/loader.js.map +1 -1
  115. package/dist/cli/main.js +7 -2
  116. package/dist/cli/main.js.map +1 -1
  117. package/dist/cli/migrate-home/index.js +2 -2
  118. package/dist/cli/migrate-home/index.js.map +1 -1
  119. package/dist/cli/pack/add.js +1 -1
  120. package/dist/cli/pack/add.js.map +1 -1
  121. package/dist/cli/pack/hook-bootstrap.d.ts +5 -164
  122. package/dist/cli/pack/hook-bootstrap.js +15 -291
  123. package/dist/cli/pack/hook-bootstrap.js.map +1 -1
  124. package/dist/cli/pack/hook-branch-protection.d.ts +28 -21
  125. package/dist/cli/pack/hook-branch-protection.js +365 -340
  126. package/dist/cli/pack/hook-branch-protection.js.map +1 -1
  127. package/dist/cli/pack/index.d.ts +0 -1
  128. package/dist/cli/pack/index.js +0 -1
  129. package/dist/cli/pack/index.js.map +1 -1
  130. package/dist/cli/pack/list.js +1 -6
  131. package/dist/cli/pack/list.js.map +1 -1
  132. package/dist/cli/pack/mutate.d.ts +7 -9
  133. package/dist/cli/pack/mutate.js +6 -9
  134. package/dist/cli/pack/mutate.js.map +1 -1
  135. package/dist/cli/pack/reseed.d.ts +2 -2
  136. package/dist/cli/pack/reseed.js +16 -22
  137. package/dist/cli/pack/reseed.js.map +1 -1
  138. package/dist/cli/{delegate/register.d.ts → register-gate-group.d.ts} +1 -1
  139. package/dist/cli/register-gate-group.js +348 -0
  140. package/dist/cli/register-gate-group.js.map +1 -0
  141. package/dist/cli/register-inspect-group.d.ts +6 -0
  142. package/dist/cli/register-inspect-group.js +189 -0
  143. package/dist/cli/register-inspect-group.js.map +1 -0
  144. package/dist/cli/register-operator-lifecycle.d.ts +1 -1
  145. package/dist/cli/register-operator-lifecycle.js +3 -100
  146. package/dist/cli/register-operator-lifecycle.js.map +1 -1
  147. package/dist/cli/register-pack-group.d.ts +5 -0
  148. package/dist/cli/register-pack-group.js +134 -0
  149. package/dist/cli/register-pack-group.js.map +1 -0
  150. package/dist/cli/{register-audit-group.d.ts → register-setup-group.d.ts} +1 -1
  151. package/dist/cli/register-setup-group.js +495 -0
  152. package/dist/cli/register-setup-group.js.map +1 -0
  153. package/dist/cli/remove/index.d.ts +0 -14
  154. package/dist/cli/remove/index.js +2 -115
  155. package/dist/cli/remove/index.js.map +1 -1
  156. package/dist/cli/remove/mutate.d.ts +0 -2
  157. package/dist/cli/remove/mutate.js +1 -19
  158. package/dist/cli/remove/mutate.js.map +1 -1
  159. package/dist/cli/remove/register.js +2 -19
  160. package/dist/cli/remove/register.js.map +1 -1
  161. package/dist/cli/shell-quote.d.ts +7 -0
  162. package/dist/cli/shell-quote.js +17 -0
  163. package/dist/cli/shell-quote.js.map +1 -0
  164. package/dist/cli/uninstall/index.d.ts +4 -2
  165. package/dist/cli/uninstall/index.js +15 -7
  166. package/dist/cli/uninstall/index.js.map +1 -1
  167. package/dist/cli/uninstall/register.js +6 -4
  168. package/dist/cli/uninstall/register.js.map +1 -1
  169. package/dist/cli/uninstall/snapshot.d.ts +15 -0
  170. package/dist/cli/uninstall/snapshot.js +38 -0
  171. package/dist/cli/uninstall/snapshot.js.map +1 -1
  172. package/dist/cli/validate/checks.d.ts +0 -77
  173. package/dist/cli/validate/checks.js +3 -1049
  174. package/dist/cli/validate/checks.js.map +1 -1
  175. package/dist/cli/validate/index.js +19 -15
  176. package/dist/cli/validate/index.js.map +1 -1
  177. package/dist/index.d.ts +0 -3
  178. package/dist/index.js +0 -3
  179. package/dist/index.js.map +1 -1
  180. package/dist/io/claude-mcp.d.ts +1 -1
  181. package/dist/io/claude-mcp.js +27 -14
  182. package/dist/io/claude-mcp.js.map +1 -1
  183. package/dist/io/harness-lock.js +2 -2
  184. package/dist/io/harness-lock.js.map +1 -1
  185. package/dist/io/lock.d.ts +0 -26
  186. package/dist/io/lock.js +10 -27
  187. package/dist/io/lock.js.map +1 -1
  188. package/dist/io/merge-settings.js +6 -4
  189. package/dist/io/merge-settings.js.map +1 -1
  190. package/dist/io/project-name.d.ts +22 -39
  191. package/dist/io/project-name.js +25 -42
  192. package/dist/io/project-name.js.map +1 -1
  193. package/dist/io/read-regular-file.d.ts +103 -40
  194. package/dist/io/read-regular-file.js +196 -35
  195. package/dist/io/read-regular-file.js.map +1 -1
  196. package/dist/io/restart-hints.js +3 -6
  197. package/dist/io/restart-hints.js.map +1 -1
  198. package/dist/io/version-compare.d.ts +1 -3
  199. package/dist/io/version-compare.js +1 -3
  200. package/dist/io/version-compare.js.map +1 -1
  201. package/dist/io/write-regular-file.d.ts +30 -0
  202. package/dist/io/write-regular-file.js +85 -0
  203. package/dist/io/write-regular-file.js.map +1 -0
  204. package/dist/overrides/index.d.ts +1 -0
  205. package/dist/overrides/index.js +1 -0
  206. package/dist/overrides/index.js.map +1 -1
  207. package/dist/overrides/merge-layers.d.ts +43 -0
  208. package/dist/overrides/merge-layers.js +76 -0
  209. package/dist/overrides/merge-layers.js.map +1 -0
  210. package/dist/overrides/merge.d.ts +16 -0
  211. package/dist/overrides/merge.js +21 -8
  212. package/dist/overrides/merge.js.map +1 -1
  213. package/dist/policy-packs/builtin/branch-protection-runtime.d.ts +4 -69
  214. package/dist/policy-packs/builtin/branch-protection-runtime.js +12 -84
  215. package/dist/policy-packs/builtin/branch-protection-runtime.js.map +1 -1
  216. package/dist/policy-packs/builtin/branch-protection.d.ts +7 -30
  217. package/dist/policy-packs/builtin/branch-protection.js +56 -89
  218. package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
  219. package/dist/policy-packs/config-check.d.ts +7 -5
  220. package/dist/policy-packs/config-check.js +2 -2
  221. package/dist/policy-packs/config-check.js.map +1 -1
  222. package/dist/policy-packs/expand.d.ts +1 -3
  223. package/dist/policy-packs/expand.js +5 -42
  224. package/dist/policy-packs/expand.js.map +1 -1
  225. package/dist/policy-packs/index.d.ts +3 -3
  226. package/dist/policy-packs/index.js +2 -2
  227. package/dist/policy-packs/index.js.map +1 -1
  228. package/dist/policy-packs/registry.d.ts +9 -38
  229. package/dist/policy-packs/registry.js +5 -65
  230. package/dist/policy-packs/registry.js.map +1 -1
  231. package/dist/policy-packs/runtime.d.ts +0 -4
  232. package/dist/policy-packs/runtime.js +9 -22
  233. package/dist/policy-packs/runtime.js.map +1 -1
  234. package/dist/policy-packs/types.d.ts +0 -19
  235. package/dist/policy-packs/ux-compare.d.ts +1 -11
  236. package/dist/policy-packs/ux-compare.js +6 -42
  237. package/dist/policy-packs/ux-compare.js.map +1 -1
  238. package/dist/policy-packs/ux-drift-check.d.ts +5 -5
  239. package/dist/policy-packs/ux-drift-check.js +17 -22
  240. package/dist/policy-packs/ux-drift-check.js.map +1 -1
  241. package/dist/policy-packs/version-check.d.ts +5 -26
  242. package/dist/policy-packs/version-check.js +19 -74
  243. package/dist/policy-packs/version-check.js.map +1 -1
  244. package/dist/probes/memory.d.ts +1 -1
  245. package/dist/probes/memory.js +1 -1
  246. package/dist/probes/memory.js.map +1 -1
  247. package/dist/runtime/agent-facing.d.ts +9 -0
  248. package/dist/runtime/agent-facing.js +28 -14
  249. package/dist/runtime/agent-facing.js.map +1 -1
  250. package/dist/runtime/bounded-text.d.ts +15 -0
  251. package/dist/runtime/bounded-text.js +27 -0
  252. package/dist/runtime/bounded-text.js.map +1 -0
  253. package/dist/runtime/git-branch.d.ts +125 -0
  254. package/dist/runtime/git-branch.js +381 -0
  255. package/dist/runtime/git-branch.js.map +1 -0
  256. package/dist/runtime/hermetic-spawn-guard.d.ts +2 -2
  257. package/dist/runtime/hermetic-spawn-guard.js +24 -34
  258. package/dist/runtime/hermetic-spawn-guard.js.map +1 -1
  259. package/dist/runtime/home-dir.js +3 -2
  260. package/dist/runtime/home-dir.js.map +1 -1
  261. package/dist/runtime/tool-name-aliases.d.ts +0 -6
  262. package/dist/runtime/tool-name-aliases.js +5 -18
  263. package/dist/runtime/tool-name-aliases.js.map +1 -1
  264. package/dist/schema/grounding.d.ts +7 -54
  265. package/dist/schema/grounding.js +10 -12
  266. package/dist/schema/grounding.js.map +1 -1
  267. package/dist/schema/hooks.d.ts +39 -136
  268. package/dist/schema/hooks.js +3 -6
  269. package/dist/schema/hooks.js.map +1 -1
  270. package/dist/schema/index.d.ts +114 -3828
  271. package/dist/schema/index.js +41 -80
  272. package/dist/schema/index.js.map +1 -1
  273. package/dist/schema/memory.d.ts +54 -131
  274. package/dist/schema/memory.js +2 -2
  275. package/dist/schema/memory.js.map +1 -1
  276. package/dist/schema/policy-packs.d.ts +3 -45
  277. package/dist/schema/policy-packs.js +7 -13
  278. package/dist/schema/policy-packs.js.map +1 -1
  279. package/dist/schema/policy-ux.d.ts +7 -0
  280. package/dist/schema/policy-ux.js +18 -0
  281. package/dist/schema/policy-ux.js.map +1 -0
  282. package/dist/schema/removed-keys.d.ts +185 -0
  283. package/dist/schema/removed-keys.js +531 -0
  284. package/dist/schema/removed-keys.js.map +1 -0
  285. package/dist/schema/tools.d.ts +30 -268
  286. package/dist/schema/tools.js +4 -4
  287. package/dist/schema/tools.js.map +1 -1
  288. package/package.json +5 -16
  289. package/dist/cli/approve/branch-protection.d.ts +0 -69
  290. package/dist/cli/approve/branch-protection.js +0 -144
  291. package/dist/cli/approve/branch-protection.js.map +0 -1
  292. package/dist/cli/approve/risk.d.ts +0 -112
  293. package/dist/cli/approve/risk.js +0 -197
  294. package/dist/cli/approve/risk.js.map +0 -1
  295. package/dist/cli/approve/stdin-report.d.ts +0 -57
  296. package/dist/cli/approve/stdin-report.js +0 -145
  297. package/dist/cli/approve/stdin-report.js.map +0 -1
  298. package/dist/cli/approve/understanding.d.ts +0 -410
  299. package/dist/cli/approve/understanding.js +0 -996
  300. package/dist/cli/approve/understanding.js.map +0 -1
  301. package/dist/cli/audit.d.ts +0 -80
  302. package/dist/cli/audit.js +0 -252
  303. package/dist/cli/audit.js.map +0 -1
  304. package/dist/cli/delegate/index.d.ts +0 -76
  305. package/dist/cli/delegate/index.js +0 -444
  306. package/dist/cli/delegate/index.js.map +0 -1
  307. package/dist/cli/delegate/register.js +0 -90
  308. package/dist/cli/delegate/register.js.map +0 -1
  309. package/dist/cli/doctor/auto-approve-mode.d.ts +0 -37
  310. package/dist/cli/doctor/auto-approve-mode.js +0 -80
  311. package/dist/cli/doctor/auto-approve-mode.js.map +0 -1
  312. package/dist/cli/doctor/bypass-without-auto-approve.d.ts +0 -38
  313. package/dist/cli/doctor/bypass-without-auto-approve.js +0 -95
  314. package/dist/cli/doctor/bypass-without-auto-approve.js.map +0 -1
  315. package/dist/cli/doctor/codex-config-drift.d.ts +0 -28
  316. package/dist/cli/doctor/codex-config-drift.js +0 -240
  317. package/dist/cli/doctor/codex-config-drift.js.map +0 -1
  318. package/dist/cli/doctor/expire-on-tool-match.d.ts +0 -15
  319. package/dist/cli/doctor/expire-on-tool-match.js +0 -58
  320. package/dist/cli/doctor/expire-on-tool-match.js.map +0 -1
  321. package/dist/cli/doctor/rogue-ledger.d.ts +0 -53
  322. package/dist/cli/doctor/rogue-ledger.js +0 -153
  323. package/dist/cli/doctor/rogue-ledger.js.map +0 -1
  324. package/dist/cli/doctor/session-start-preflight-setup-version.d.ts +0 -83
  325. package/dist/cli/doctor/session-start-preflight-setup-version.js +0 -114
  326. package/dist/cli/doctor/session-start-preflight-setup-version.js.map +0 -1
  327. package/dist/cli/doctor/settings-drift.d.ts +0 -25
  328. package/dist/cli/doctor/settings-drift.js +0 -213
  329. package/dist/cli/doctor/settings-drift.js.map +0 -1
  330. package/dist/cli/doctor/toolchain-parity.d.ts +0 -63
  331. package/dist/cli/doctor/toolchain-parity.js +0 -154
  332. package/dist/cli/doctor/toolchain-parity.js.map +0 -1
  333. package/dist/cli/doctor/ug-auto-approvals.d.ts +0 -40
  334. package/dist/cli/doctor/ug-auto-approvals.js +0 -91
  335. package/dist/cli/doctor/ug-auto-approvals.js.map +0 -1
  336. package/dist/cli/doctor/ug-delegations.d.ts +0 -26
  337. package/dist/cli/doctor/ug-delegations.js +0 -121
  338. package/dist/cli/doctor/ug-delegations.js.map +0 -1
  339. package/dist/cli/doctor/ug-inflight.d.ts +0 -35
  340. package/dist/cli/doctor/ug-inflight.js +0 -57
  341. package/dist/cli/doctor/ug-inflight.js.map +0 -1
  342. package/dist/cli/doctor/understanding-mode-env.d.ts +0 -51
  343. package/dist/cli/doctor/understanding-mode-env.js +0 -98
  344. package/dist/cli/doctor/understanding-mode-env.js.map +0 -1
  345. package/dist/cli/dry-run.d.ts +0 -58
  346. package/dist/cli/dry-run.js +0 -283
  347. package/dist/cli/dry-run.js.map +0 -1
  348. package/dist/cli/enriched-event.d.ts +0 -35
  349. package/dist/cli/enriched-event.js +0 -60
  350. package/dist/cli/enriched-event.js.map +0 -1
  351. package/dist/cli/event-input.d.ts +0 -28
  352. package/dist/cli/event-input.js +0 -73
  353. package/dist/cli/event-input.js.map +0 -1
  354. package/dist/cli/explain-action.d.ts +0 -25
  355. package/dist/cli/explain-action.js +0 -57
  356. package/dist/cli/explain-action.js.map +0 -1
  357. package/dist/cli/explain-policy.d.ts +0 -147
  358. package/dist/cli/explain-policy.js +0 -248
  359. package/dist/cli/explain-policy.js.map +0 -1
  360. package/dist/cli/explain.d.ts +0 -31
  361. package/dist/cli/explain.js +0 -159
  362. package/dist/cli/explain.js.map +0 -1
  363. package/dist/cli/gc/index.d.ts +0 -63
  364. package/dist/cli/gc/index.js +0 -566
  365. package/dist/cli/gc/index.js.map +0 -1
  366. package/dist/cli/pack/approve-escape.d.ts +0 -19
  367. package/dist/cli/pack/approve-escape.js +0 -190
  368. package/dist/cli/pack/approve-escape.js.map +0 -1
  369. package/dist/cli/pack/auto-approve-path.d.ts +0 -146
  370. package/dist/cli/pack/auto-approve-path.js +0 -462
  371. package/dist/cli/pack/auto-approve-path.js.map +0 -1
  372. package/dist/cli/pack/hook-codex-post-tool-use.d.ts +0 -25
  373. package/dist/cli/pack/hook-codex-post-tool-use.js +0 -160
  374. package/dist/cli/pack/hook-codex-post-tool-use.js.map +0 -1
  375. package/dist/cli/pack/hook-codex-pre-tool-use.d.ts +0 -48
  376. package/dist/cli/pack/hook-codex-pre-tool-use.js +0 -474
  377. package/dist/cli/pack/hook-codex-pre-tool-use.js.map +0 -1
  378. package/dist/cli/pack/hook-codex-stop.d.ts +0 -35
  379. package/dist/cli/pack/hook-codex-stop.js +0 -329
  380. package/dist/cli/pack/hook-codex-stop.js.map +0 -1
  381. package/dist/cli/pack/hook-codex-user-prompt-submit.d.ts +0 -24
  382. package/dist/cli/pack/hook-codex-user-prompt-submit.js +0 -93
  383. package/dist/cli/pack/hook-codex-user-prompt-submit.js.map +0 -1
  384. package/dist/cli/pack/hook-post-merge-gate-record.d.ts +0 -37
  385. package/dist/cli/pack/hook-post-merge-gate-record.js +0 -190
  386. package/dist/cli/pack/hook-post-merge-gate-record.js.map +0 -1
  387. package/dist/cli/pack/hook-post-merge-gate.d.ts +0 -34
  388. package/dist/cli/pack/hook-post-merge-gate.js +0 -277
  389. package/dist/cli/pack/hook-post-merge-gate.js.map +0 -1
  390. package/dist/cli/pack/hook-post-tool-use.d.ts +0 -45
  391. package/dist/cli/pack/hook-post-tool-use.js +0 -137
  392. package/dist/cli/pack/hook-post-tool-use.js.map +0 -1
  393. package/dist/cli/pack/hook-pre-tool-use.d.ts +0 -92
  394. package/dist/cli/pack/hook-pre-tool-use.js +0 -1328
  395. package/dist/cli/pack/hook-pre-tool-use.js.map +0 -1
  396. package/dist/cli/pack/hook-runtime-reality.d.ts +0 -63
  397. package/dist/cli/pack/hook-runtime-reality.js +0 -210
  398. package/dist/cli/pack/hook-runtime-reality.js.map +0 -1
  399. package/dist/cli/pack/hook-solution-acceptance-writeguard.d.ts +0 -32
  400. package/dist/cli/pack/hook-solution-acceptance-writeguard.js +0 -438
  401. package/dist/cli/pack/hook-solution-acceptance-writeguard.js.map +0 -1
  402. package/dist/cli/pack/hook-solution-acceptance.d.ts +0 -157
  403. package/dist/cli/pack/hook-solution-acceptance.js +0 -530
  404. package/dist/cli/pack/hook-solution-acceptance.js.map +0 -1
  405. package/dist/cli/pack/hook-stay-in-scope.d.ts +0 -37
  406. package/dist/cli/pack/hook-stay-in-scope.js +0 -128
  407. package/dist/cli/pack/hook-stay-in-scope.js.map +0 -1
  408. package/dist/cli/pack/hook-subagent-start.d.ts +0 -23
  409. package/dist/cli/pack/hook-subagent-start.js +0 -88
  410. package/dist/cli/pack/hook-subagent-start.js.map +0 -1
  411. package/dist/cli/pack/hook-subagent-stop.d.ts +0 -19
  412. package/dist/cli/pack/hook-subagent-stop.js +0 -41
  413. package/dist/cli/pack/hook-subagent-stop.js.map +0 -1
  414. package/dist/cli/pack/hook-track-active-claim.d.ts +0 -27
  415. package/dist/cli/pack/hook-track-active-claim.js +0 -275
  416. package/dist/cli/pack/hook-track-active-claim.js.map +0 -1
  417. package/dist/cli/pack/transcript-report-scan.d.ts +0 -71
  418. package/dist/cli/pack/transcript-report-scan.js +0 -359
  419. package/dist/cli/pack/transcript-report-scan.js.map +0 -1
  420. package/dist/cli/pack/understanding-report-schema-hint.d.ts +0 -26
  421. package/dist/cli/pack/understanding-report-schema-hint.js +0 -120
  422. package/dist/cli/pack/understanding-report-schema-hint.js.map +0 -1
  423. package/dist/cli/pack/upgrade.d.ts +0 -32
  424. package/dist/cli/pack/upgrade.js +0 -273
  425. package/dist/cli/pack/upgrade.js.map +0 -1
  426. package/dist/cli/pause/index.d.ts +0 -113
  427. package/dist/cli/pause/index.js +0 -251
  428. package/dist/cli/pause/index.js.map +0 -1
  429. package/dist/cli/pause-check.d.ts +0 -22
  430. package/dist/cli/pause-check.js +0 -54
  431. package/dist/cli/pause-check.js.map +0 -1
  432. package/dist/cli/policy/intercept.d.ts +0 -233
  433. package/dist/cli/policy/intercept.js +0 -1022
  434. package/dist/cli/policy/intercept.js.map +0 -1
  435. package/dist/cli/policy/risk-envelope-enrichment.d.ts +0 -40
  436. package/dist/cli/policy/risk-envelope-enrichment.js +0 -110
  437. package/dist/cli/policy/risk-envelope-enrichment.js.map +0 -1
  438. package/dist/cli/record/index.d.ts +0 -81
  439. package/dist/cli/record/index.js +0 -348
  440. package/dist/cli/record/index.js.map +0 -1
  441. package/dist/cli/register-audit-group.js +0 -91
  442. package/dist/cli/register-audit-group.js.map +0 -1
  443. package/dist/cli/resolve-env.d.ts +0 -32
  444. package/dist/cli/resolve-env.js +0 -46
  445. package/dist/cli/resolve-env.js.map +0 -1
  446. package/dist/cli/session-export/index.d.ts +0 -46
  447. package/dist/cli/session-export/index.js +0 -169
  448. package/dist/cli/session-export/index.js.map +0 -1
  449. package/dist/cli/session-export/redact.d.ts +0 -22
  450. package/dist/cli/session-export/redact.js +0 -47
  451. package/dist/cli/session-export/redact.js.map +0 -1
  452. package/dist/cli/session-export/transcript.d.ts +0 -24
  453. package/dist/cli/session-export/transcript.js +0 -164
  454. package/dist/cli/session-export/transcript.js.map +0 -1
  455. package/dist/cli/session-start/branch-check.d.ts +0 -18
  456. package/dist/cli/session-start/branch-check.js +0 -140
  457. package/dist/cli/session-start/branch-check.js.map +0 -1
  458. package/dist/cli/session-start/index.d.ts +0 -121
  459. package/dist/cli/session-start/index.js +0 -591
  460. package/dist/cli/session-start/index.js.map +0 -1
  461. package/dist/cli/session-start/shared-options.d.ts +0 -103
  462. package/dist/cli/session-start/shared-options.js +0 -107
  463. package/dist/cli/session-start/shared-options.js.map +0 -1
  464. package/dist/cli/session-start/stale-base-check.d.ts +0 -46
  465. package/dist/cli/session-start/stale-base-check.js +0 -352
  466. package/dist/cli/session-start/stale-base-check.js.map +0 -1
  467. package/dist/cli/session-start/toolchain-parity.d.ts +0 -267
  468. package/dist/cli/session-start/toolchain-parity.js +0 -797
  469. package/dist/cli/session-start/toolchain-parity.js.map +0 -1
  470. package/dist/cli/smoke/assertions.d.ts +0 -46
  471. package/dist/cli/smoke/assertions.js +0 -170
  472. package/dist/cli/smoke/assertions.js.map +0 -1
  473. package/dist/cli/smoke/index.d.ts +0 -76
  474. package/dist/cli/smoke/index.js +0 -245
  475. package/dist/cli/smoke/index.js.map +0 -1
  476. package/dist/cli/smoke/runner.d.ts +0 -55
  477. package/dist/cli/smoke/runner.js +0 -134
  478. package/dist/cli/smoke/runner.js.map +0 -1
  479. package/dist/cli/smoke/stream-parser.d.ts +0 -65
  480. package/dist/cli/smoke/stream-parser.js +0 -115
  481. package/dist/cli/smoke/stream-parser.js.map +0 -1
  482. package/dist/cli/test-risk.d.ts +0 -27
  483. package/dist/cli/test-risk.js +0 -38
  484. package/dist/cli/test-risk.js.map +0 -1
  485. package/dist/io/display-path.d.ts +0 -11
  486. package/dist/io/display-path.js +0 -29
  487. package/dist/io/display-path.js.map +0 -1
  488. package/dist/io/duration.d.ts +0 -5
  489. package/dist/io/duration.js +0 -50
  490. package/dist/io/duration.js.map +0 -1
  491. package/dist/io/extract.d.ts +0 -79
  492. package/dist/io/extract.js +0 -226
  493. package/dist/io/extract.js.map +0 -1
  494. package/dist/io/ledger-record.d.ts +0 -83
  495. package/dist/io/ledger-record.js +0 -301
  496. package/dist/io/ledger-record.js.map +0 -1
  497. package/dist/io/read-json-dir-entries.d.ts +0 -36
  498. package/dist/io/read-json-dir-entries.js +0 -48
  499. package/dist/io/read-json-dir-entries.js.map +0 -1
  500. package/dist/io/safe-json-parse.d.ts +0 -5
  501. package/dist/io/safe-json-parse.js +0 -21
  502. package/dist/io/safe-json-parse.js.map +0 -1
  503. package/dist/policies/index.d.ts +0 -5
  504. package/dist/policies/index.js +0 -6
  505. package/dist/policies/index.js.map +0 -1
  506. package/dist/policies/ledger-client.d.ts +0 -113
  507. package/dist/policies/ledger-client.js +0 -500
  508. package/dist/policies/ledger-client.js.map +0 -1
  509. package/dist/policies/producers.d.ts +0 -12
  510. package/dist/policies/producers.js +0 -61
  511. package/dist/policies/producers.js.map +0 -1
  512. package/dist/policies/requires.d.ts +0 -75
  513. package/dist/policies/requires.js +0 -240
  514. package/dist/policies/requires.js.map +0 -1
  515. package/dist/policies/timestamp.d.ts +0 -14
  516. package/dist/policies/timestamp.js +0 -36
  517. package/dist/policies/timestamp.js.map +0 -1
  518. package/dist/policy-packs/builtin/permission-profiles.d.ts +0 -11
  519. package/dist/policy-packs/builtin/permission-profiles.js +0 -74
  520. package/dist/policy-packs/builtin/permission-profiles.js.map +0 -1
  521. package/dist/policy-packs/builtin/post-merge-gate-runtime.d.ts +0 -286
  522. package/dist/policy-packs/builtin/post-merge-gate-runtime.js +0 -397
  523. package/dist/policy-packs/builtin/post-merge-gate-runtime.js.map +0 -1
  524. package/dist/policy-packs/builtin/post-merge-gate.d.ts +0 -56
  525. package/dist/policy-packs/builtin/post-merge-gate.js +0 -347
  526. package/dist/policy-packs/builtin/post-merge-gate.js.map +0 -1
  527. package/dist/policy-packs/builtin/solution-acceptance-reconnect.d.ts +0 -50
  528. package/dist/policy-packs/builtin/solution-acceptance-reconnect.js +0 -151
  529. package/dist/policy-packs/builtin/solution-acceptance-reconnect.js.map +0 -1
  530. package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +0 -323
  531. package/dist/policy-packs/builtin/solution-acceptance-runtime.js +0 -672
  532. package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +0 -1
  533. package/dist/policy-packs/builtin/solution-acceptance.d.ts +0 -45
  534. package/dist/policy-packs/builtin/solution-acceptance.js +0 -226
  535. package/dist/policy-packs/builtin/solution-acceptance.js.map +0 -1
  536. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.d.ts +0 -25
  537. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js +0 -94
  538. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js.map +0 -1
  539. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.d.ts +0 -35
  540. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.js +0 -86
  541. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.js.map +0 -1
  542. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.d.ts +0 -210
  543. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js +0 -361
  544. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js.map +0 -1
  545. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.d.ts +0 -304
  546. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js +0 -734
  547. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js.map +0 -1
  548. package/dist/policy-packs/builtin/understanding-before-execution/expiry-reason.d.ts +0 -36
  549. package/dist/policy-packs/builtin/understanding-before-execution/expiry-reason.js +0 -73
  550. package/dist/policy-packs/builtin/understanding-before-execution/expiry-reason.js.map +0 -1
  551. package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +0 -19
  552. package/dist/policy-packs/builtin/understanding-before-execution/index.js +0 -83
  553. package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +0 -1
  554. package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.d.ts +0 -135
  555. package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js +0 -643
  556. package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js.map +0 -1
  557. package/dist/policy-packs/builtin/understanding-before-execution/ledger.d.ts +0 -25
  558. package/dist/policy-packs/builtin/understanding-before-execution/ledger.js +0 -56
  559. package/dist/policy-packs/builtin/understanding-before-execution/ledger.js.map +0 -1
  560. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.d.ts +0 -20
  561. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js +0 -106
  562. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js.map +0 -1
  563. package/dist/policy-packs/builtin/understanding-before-execution/markers.d.ts +0 -126
  564. package/dist/policy-packs/builtin/understanding-before-execution/markers.js +0 -245
  565. package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +0 -1
  566. package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.d.ts +0 -29
  567. package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.js +0 -82
  568. package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.js.map +0 -1
  569. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.d.ts +0 -71
  570. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js +0 -175
  571. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js.map +0 -1
  572. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +0 -625
  573. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +0 -1076
  574. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +0 -1
  575. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.d.ts +0 -128
  576. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +0 -213
  577. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +0 -1
  578. package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.d.ts +0 -129
  579. package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.js +0 -166
  580. package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.js.map +0 -1
  581. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.d.ts +0 -164
  582. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js +0 -275
  583. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js.map +0 -1
  584. package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +0 -1
  585. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +0 -10
  586. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +0 -1
  587. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +0 -526
  588. package/dist/policy-packs/builtin/understanding-before-execution.js +0 -1078
  589. package/dist/policy-packs/builtin/understanding-before-execution.js.map +0 -1
  590. package/dist/policy-packs/permission-translator.d.ts +0 -9
  591. package/dist/policy-packs/permission-translator.js +0 -76
  592. package/dist/policy-packs/permission-translator.js.map +0 -1
  593. package/dist/runtime/action-envelope.d.ts +0 -64
  594. package/dist/runtime/action-envelope.js +0 -46
  595. package/dist/runtime/action-envelope.js.map +0 -1
  596. package/dist/runtime/approval-signing.d.ts +0 -111
  597. package/dist/runtime/approval-signing.js +0 -384
  598. package/dist/runtime/approval-signing.js.map +0 -1
  599. package/dist/runtime/bash-match-facts.d.ts +0 -60
  600. package/dist/runtime/bash-match-facts.js +0 -196
  601. package/dist/runtime/bash-match-facts.js.map +0 -1
  602. package/dist/runtime/bash-match-registry.d.ts +0 -112
  603. package/dist/runtime/bash-match-registry.js +0 -406
  604. package/dist/runtime/bash-match-registry.js.map +0 -1
  605. package/dist/runtime/bash-prefix-parse.d.ts +0 -31
  606. package/dist/runtime/bash-prefix-parse.js +0 -576
  607. package/dist/runtime/bash-prefix-parse.js.map +0 -1
  608. package/dist/runtime/command-normalize.d.ts +0 -658
  609. package/dist/runtime/command-normalize.js +0 -2410
  610. package/dist/runtime/command-normalize.js.map +0 -1
  611. package/dist/runtime/deletion-target-resolve.d.ts +0 -28
  612. package/dist/runtime/deletion-target-resolve.js +0 -1078
  613. package/dist/runtime/deletion-target-resolve.js.map +0 -1
  614. package/dist/runtime/destructive-shell-floor.d.ts +0 -19
  615. package/dist/runtime/destructive-shell-floor.js +0 -611
  616. package/dist/runtime/destructive-shell-floor.js.map +0 -1
  617. package/dist/runtime/environment-resolver.d.ts +0 -36
  618. package/dist/runtime/environment-resolver.js +0 -138
  619. package/dist/runtime/environment-resolver.js.map +0 -1
  620. package/dist/runtime/git-context.d.ts +0 -201
  621. package/dist/runtime/git-context.js +0 -422
  622. package/dist/runtime/git-context.js.map +0 -1
  623. package/dist/runtime/index.d.ts +0 -12
  624. package/dist/runtime/index.js +0 -13
  625. package/dist/runtime/index.js.map +0 -1
  626. package/dist/runtime/intercept.d.ts +0 -623
  627. package/dist/runtime/intercept.js +0 -1384
  628. package/dist/runtime/intercept.js.map +0 -1
  629. package/dist/runtime/kube-context.d.ts +0 -16
  630. package/dist/runtime/kube-context.js +0 -63
  631. package/dist/runtime/kube-context.js.map +0 -1
  632. package/dist/runtime/kubectl-target-parse.d.ts +0 -31
  633. package/dist/runtime/kubectl-target-parse.js +0 -249
  634. package/dist/runtime/kubectl-target-parse.js.map +0 -1
  635. package/dist/runtime/ledger-add.d.ts +0 -16
  636. package/dist/runtime/ledger-add.js +0 -150
  637. package/dist/runtime/ledger-add.js.map +0 -1
  638. package/dist/runtime/ledger-writer.d.ts +0 -27
  639. package/dist/runtime/ledger-writer.js +0 -46
  640. package/dist/runtime/ledger-writer.js.map +0 -1
  641. package/dist/runtime/pause-sentinel.d.ts +0 -60
  642. package/dist/runtime/pause-sentinel.js +0 -156
  643. package/dist/runtime/pause-sentinel.js.map +0 -1
  644. package/dist/runtime/pending-approval.d.ts +0 -23
  645. package/dist/runtime/pending-approval.js +0 -67
  646. package/dist/runtime/pending-approval.js.map +0 -1
  647. package/dist/runtime/read-only-bash.d.ts +0 -141
  648. package/dist/runtime/read-only-bash.js +0 -2204
  649. package/dist/runtime/read-only-bash.js.map +0 -1
  650. package/dist/runtime/recovery-git-commit.d.ts +0 -22
  651. package/dist/runtime/recovery-git-commit.js +0 -284
  652. package/dist/runtime/recovery-git-commit.js.map +0 -1
  653. package/dist/runtime/reject-malformed-session-id.d.ts +0 -8
  654. package/dist/runtime/reject-malformed-session-id.js +0 -16
  655. package/dist/runtime/reject-malformed-session-id.js.map +0 -1
  656. package/dist/runtime/risk-classifier.d.ts +0 -38
  657. package/dist/runtime/risk-classifier.js +0 -311
  658. package/dist/runtime/risk-classifier.js.map +0 -1
  659. package/dist/runtime/session-id.d.ts +0 -113
  660. package/dist/runtime/session-id.js +0 -193
  661. package/dist/runtime/session-id.js.map +0 -1
  662. package/dist/runtime/shell-word.d.ts +0 -62
  663. package/dist/runtime/shell-word.js +0 -410
  664. package/dist/runtime/shell-word.js.map +0 -1
  665. package/dist/runtime/task-providers/agent-tasks.d.ts +0 -105
  666. package/dist/runtime/task-providers/agent-tasks.js +0 -247
  667. package/dist/runtime/task-providers/agent-tasks.js.map +0 -1
  668. package/dist/runtime/when-eval.d.ts +0 -62
  669. package/dist/runtime/when-eval.js +0 -212
  670. package/dist/runtime/when-eval.js.map +0 -1
  671. package/dist/runtime/workflow-policies.d.ts +0 -207
  672. package/dist/runtime/workflow-policies.js +0 -596
  673. package/dist/runtime/workflow-policies.js.map +0 -1
  674. package/dist/schema/audit.d.ts +0 -71
  675. package/dist/schema/audit.js +0 -32
  676. package/dist/schema/audit.js.map +0 -1
  677. package/dist/schema/doctor.d.ts +0 -25
  678. package/dist/schema/doctor.js +0 -23
  679. package/dist/schema/doctor.js.map +0 -1
  680. package/dist/schema/environments.d.ts +0 -215
  681. package/dist/schema/environments.js +0 -101
  682. package/dist/schema/environments.js.map +0 -1
  683. package/dist/schema/extract.d.ts +0 -9
  684. package/dist/schema/extract.js +0 -86
  685. package/dist/schema/extract.js.map +0 -1
  686. package/dist/schema/permission-profiles.d.ts +0 -2292
  687. package/dist/schema/permission-profiles.js +0 -60
  688. package/dist/schema/permission-profiles.js.map +0 -1
  689. package/dist/schema/policies.d.ts +0 -974
  690. package/dist/schema/policies.js +0 -298
  691. package/dist/schema/policies.js.map +0 -1
  692. package/dist/schema/requires.d.ts +0 -131
  693. package/dist/schema/requires.js +0 -88
  694. package/dist/schema/requires.js.map +0 -1
  695. package/dist/schema/risk.d.ts +0 -144
  696. package/dist/schema/risk.js +0 -194
  697. package/dist/schema/risk.js.map +0 -1
  698. package/dist/schema/session-start-preflight.d.ts +0 -10
  699. package/dist/schema/session-start-preflight.js +0 -106
  700. package/dist/schema/session-start-preflight.js.map +0 -1
  701. package/dist/schema/stale-base-check.d.ts +0 -18
  702. package/dist/schema/stale-base-check.js +0 -37
  703. package/dist/schema/stale-base-check.js.map +0 -1
  704. package/dist/schema/toolchain-parity.d.ts +0 -21
  705. package/dist/schema/toolchain-parity.js +0 -48
  706. package/dist/schema/toolchain-parity.js.map +0 -1
  707. package/dist/schema/workflows.d.ts +0 -519
  708. package/dist/schema/workflows.js +0 -81
  709. package/dist/schema/workflows.js.map +0 -1
  710. package/scripts/runtime-reality-docker-probe.mjs +0 -71
@@ -1,1384 +0,0 @@
1
- // Phase 4 #5 — runtime hook interceptor + policy_decision audit log.
2
- //
3
- // Pure orchestration: takes a parsed event + a ledger client + the manifest,
4
- // runs every matching policy through the Phase 4 #1/#2/#3 pipeline, returns
5
- // the decisions and the Claude Code deny-JSON (or null when all allow).
6
- // Side effects (stdin, stdout, ledger I/O) live in the thin CLI entrypoint
7
- // that wraps this.
8
- import * as fs from "node:fs";
9
- import * as path from "node:path";
10
- import { evaluateExtract, evaluateRequires, firstInputMatchMismatch, parseDurationSeconds, substituteTemplate, } from "../policies/index.js";
11
- import { renderProducers } from "../policies/producers.js";
12
- import { buildActionEnvelope } from "./action-envelope.js";
13
- import { renderAgentFacing } from "./agent-facing.js";
14
- import { normalizeCommand, normalizeCommandAmpAware, normalizeCommandQuoteAware, segmentViewOf, } from "./command-normalize.js";
15
- import { resolveEnvironment, } from "./environment-resolver.js";
16
- import { resolveDeletionTarget, } from "./deletion-target-resolve.js";
17
- import { DEFAULT_SAFE_DELETION_ROOTS } from "../schema/risk.js";
18
- import { resolveGitContext } from "./git-context.js";
19
- import { POLICY_DECISION_TYPE } from "../io/ledger-record.js";
20
- import { classifyRisk } from "./risk-classifier.js";
21
- import { resolveSessionId } from "./session-id.js";
22
- import { expandToolNameAliases, extractShellCommand, } from "./tool-name-aliases.js";
23
- import { evaluateWhen, UNCLASSIFIED_FALLBACK_SEVERITY } from "./when-eval.js";
24
- /**
25
- * The sentence PREPENDED to a `ux:`-declared policy's `cannot:` text when
26
- * the deny was caused by the unclassified fallback rather than a real
27
- * classification (task 2929c5b7).
28
- *
29
- * Everything variable in it is interpolated: the RESOLVED environment
30
- * name and the policy's OWN declared threshold. Hard-coding "production"
31
- * and "critical" made both halves wrong for an unscoped
32
- * `severity_at_least: high` policy on a feature branch, which reported a
33
- * production context that had not been resolved and a critical-severity
34
- * comparison that never ran. The fallback rung itself comes from
35
- * `when-eval.ts`'s exported constant, not a second literal here.
36
- *
37
- * A policy whose `when:` block declares no `severity_at_least` can still
38
- * set the flag (via `risk.category_in` / `action.reversible`, which keep
39
- * the blanket "unknown is not safe" fallback), so the threshold-free
40
- * wording is a real case, not a defensive branch.
41
- */
42
- function unclassifiedFallbackPrefix(environmentName, threshold) {
43
- const env = environmentName ?? "unknown";
44
- const article = /^[aeiou]/i.test(env) ? "an" : "a";
45
- const lead = `This is an unclassified action in ${article} ${env} context: no risk classifier pattern recognized it, so`;
46
- return threshold === undefined
47
- ? `${lead} the fail-closed unclassified rule satisfied this policy's when: clause, rather than a genuine risk classification.`
48
- : `${lead} the fail-closed severity fallback (treated as ${UNCLASSIFIED_FALLBACK_SEVERITY}) satisfied this policy's severity_at_least: ${threshold}, rather than a genuine ${threshold}-severity match.`;
49
- }
50
- /**
51
- * Build the Action Envelope for an event and run it through the Risk
52
- * Classifier (#3) and Context Resolver (#4). Pure: every host fact
53
- * arrives via `riskContext`; when it is absent the envelope is built
54
- * from the event alone (unclassified risk, `unknown` environment).
55
- */
56
- function enrichEnvelope(manifest, event, riskContext, now) {
57
- const rc = riskContext;
58
- const envelope = buildActionEnvelope(event, {
59
- cwd: rc?.cwd ?? (typeof event.cwd === "string" ? event.cwd : ""),
60
- git: rc?.git ?? { repo: "", branch: "", sha: "" },
61
- user: rc?.user ?? "",
62
- host: rc?.host ?? "",
63
- now: now ?? new Date(),
64
- });
65
- const risk = classifyRisk(envelope, manifest.risk.classifiers);
66
- const environment = resolveEnvironment(envelope, manifest.environments.resolvers, {
67
- env: rc?.env ?? {},
68
- kubeContext: rc?.kubeContext ?? "",
69
- kubeNamespace: rc?.kubeNamespace ?? "",
70
- });
71
- // Static deletion-target resolution (task d03af8f6) needs only the raw
72
- // command — unlike the environment resolver above, it deliberately
73
- // does not consult `riskContext` (cwd, env, kube): a relative target is
74
- // UNRESOLVABLE by design, not resolved against ambient cwd. See
75
- // `deletion-target-resolve.ts`'s module doc for the full rationale.
76
- const deletionShellCommand = extractShellCommand({ raw_input: envelope.raw_input });
77
- const deletionTarget = deletionShellCommand === null
78
- ? null
79
- : resolveDeletionTarget(deletionShellCommand,
80
- // Defensive fallback (not just the schema `.default()`): a
81
- // hand-built `Manifest` test fixture that constructs `risk:`
82
- // directly, bypassing `RiskSchema.parse`, may omit this field
83
- // entirely — never trust it present.
84
- manifest.risk.safe_deletion_roots ?? DEFAULT_SAFE_DELETION_ROOTS);
85
- return { risk, environment, deletionTarget };
86
- }
87
- /**
88
- * Does a policy's `trigger:` match this event? This is the WHICH-tool-
89
- * calls filter; the WHETHER-it-applies filter is `policy.when:`,
90
- * evaluated separately (`evaluateWhen`). A policy fires only when both
91
- * hold. Exported so `harness explain-policy` can report the trigger
92
- * verdict on its own.
93
- *
94
- * `precomputedNormalizedCommand` is an optional caller-supplied
95
- * `NormalizedCommand` for the event's command: `intercept()` below
96
- * resolves ONE `NormalizedCommand` per event (via
97
- * `options.normalizedCommand`, itself computed once by
98
- * `runInterceptCli`) and threads it into every `policyMatchesEvent`
99
- * call in its `matching` loop, so a raw-miss event normalises the
100
- * command exactly ONCE across the whole manifest instead of once per
101
- * policy. Omitted by standalone callers (`harness explain-policy`, most
102
- * tests), which fall back to computing it lazily right here — correct
103
- * either way, since `normalizeCommand` is a pure function of the
104
- * command string alone.
105
- *
106
- * `ampNormalizedCommandThunk` (task aabbad63) is the SAME
107
- * resolved-by-the-wrapper pattern for the ampersand-aware SECOND
108
- * normalisation pass, but threaded as a memoised THUNK rather than a
109
- * precomputed value — see `InterceptOptions.ampNormalizedCommandThunk`
110
- * for why laziness matters here specifically. Omitted callers fall back
111
- * to calling `normalizeCommandAmpAware` directly, same shape as the
112
- * `precomputedNormalizedCommand` fallback above.
113
- *
114
- * `quoteNormalizedCommandThunk` (task cf3dff51) is the SAME thunk pattern
115
- * again, for the quote-aware THIRD normalisation pass — see
116
- * `InterceptOptions.quoteNormalizedCommandThunk`. Omitted callers fall
117
- * back to calling `normalizeCommandQuoteAware` directly, same shape as
118
- * the two fallbacks above.
119
- */
120
- export function policyMatchesEvent(policy, event, precomputedNormalizedCommand, ampNormalizedCommandThunk, quoteNormalizedCommandThunk) {
121
- if (policy.trigger.event !== event.hook_event_name)
122
- return false;
123
- if (policy.trigger.match !== undefined) {
124
- if (typeof event.tool_name !== "string")
125
- return false;
126
- const toolNames = expandToolNameAliases(event.tool_name);
127
- if (!toolNames.some((toolName) => toolName.includes(policy.trigger.match))) {
128
- return false;
129
- }
130
- }
131
- // `input_match` (task 2699b476): literal equality against the tool
132
- // call's own arguments, ANDed onto the tool-name match above. This is
133
- // what separates `task_finish { autoMerge: true }` (a merge, gated)
134
- // from a plain `task_finish` (not a merge, not gated) without needing
135
- // two different tool names. Evaluated from the SAME `toolArgs` context
136
- // `trigger.extract` reads (`buildEventContext`) for a SINGLE-envelope
137
- // event, and additionally against BOTH `tool_input` and `raw_input`
138
- // when a payload carries both as non-null objects (review round 1,
139
- // task 2699b476 round 2, see `inputMatchMismatchesEvent` below).
140
- // Mirrored in `policyMatchesTool` (`src/cli/dry-run.ts`) for the
141
- // single-envelope case only, since dry-run's `--input` is always one
142
- // object; `harness policy dry-run` cannot reproduce the mixed-envelope
143
- // arm. Both `input_match` parity and the mixed-envelope-is-intercept-only
144
- // caveat are recorded in docs/okf/debug-verb-selection.md's
145
- // trigger-matching parity paragraph.
146
- if (policy.trigger.input_match !== undefined) {
147
- if (inputMatchMismatchesEvent(policy.trigger.input_match, event)) {
148
- return false;
149
- }
150
- }
151
- if (policy.trigger.bash_match !== undefined) {
152
- const command = extractShellCommand(event);
153
- if (command === null)
154
- return false;
155
- let re;
156
- try {
157
- re = new RegExp(policy.trigger.bash_match);
158
- }
159
- catch {
160
- return false;
161
- }
162
- // Raw-OR-normalised-OR-amp-normalised-OR-quote-normalised (D-003, run
163
- // 2026-07-27-gate-target-repo-resolution; third arm added task
164
- // aabbad63; fourth arm added task cf3dff51): test the RAW command
165
- // first — cheap, and byte-identical to the pre-fix behaviour — then,
166
- // only if that fails, the primary NORMALISED command (wrapper
167
- // prefixes peeled, git global options dropped, whitespace collapsed,
168
- // BOUNDARY_RE segmentation) — then, only if THAT also fails, the
169
- // ampersand-aware second pass (AMP_BOUNDARY_RE segmentation, closing
170
- // the bare-`&` family BOUNDARY_RE cannot see: `A=x&env -C /tmp git
171
- // status`, `echo hi & nice git status`) — then, only if THAT also
172
- // fails, the quote-aware third pass (BOUNDARY_RE's own alphabet, but
173
- // quote-tracking, closing a shell-boundary character sitting INSIDE a
174
- // quoted assignment value: `VAR='a; b' git push origin master`).
175
- // Strictly additive at every step: a command that matched today keeps
176
- // matching via the raw test alone; the primary normalised form can
177
- // only ADD a match (env/nice/command wrappers, extra git global
178
- // options, doubled whitespace); the amp-aware form can only add a
179
- // FURTHER match on top of those two; the quote-aware form can only
180
- // add a FOURTH match on top of all three, never remove one any of the
181
- // others already found. Replacing the matcher input instead of OR-ing
182
- // it in risks silently REMOVING an existing match if some pass ever
183
- // mangles a shape — the fail-open direction for a gate — so this
184
- // stays additive rather than a substitution at every arm.
185
- if (!re.test(command)) {
186
- const { normalized } = precomputedNormalizedCommand ?? normalizeCommand(command);
187
- if (!re.test(normalized)) {
188
- const amp = ampNormalizedCommandThunk
189
- ? ampNormalizedCommandThunk()
190
- : normalizeCommandAmpAware(command);
191
- if (!re.test(amp.normalized)) {
192
- const quoted = quoteNormalizedCommandThunk
193
- ? quoteNormalizedCommandThunk()
194
- : normalizeCommandQuoteAware(command);
195
- if (!re.test(quoted.normalized))
196
- return false;
197
- }
198
- }
199
- }
200
- }
201
- return true;
202
- }
203
- function buildEventContext(event) {
204
- return contextWithToolArgs(event, event.tool_input ?? event.raw_input ?? event.input);
205
- }
206
- function contextWithToolArgs(event, toolArgs) {
207
- return {
208
- toolArgs,
209
- event,
210
- session: { id: event.session_id ?? "" },
211
- git: {},
212
- };
213
- }
214
- function isNonNullObject(value) {
215
- return typeof value === "object" && value !== null;
216
- }
217
- /**
218
- * Whether `input_match` fails to arm the gate for this event (review
219
- * round 1, task 2699b476 round 2, MEDIUM security finding).
220
- *
221
- * The default `toolArgs` context (`buildEventContext`) resolves ONE
222
- * field with `tool_input ?? raw_input ?? input`, so a payload that
223
- * carries a benign value in the PREFERRED field and the actual merge
224
- * request in the OTHER field never gets read:
225
- * `{tool_input:{taskId:"t"}, raw_input:{taskId:"t",autoMerge:true}}`
226
- * resolves `toolArgs` to `tool_input` alone, `autoMerge` reads as
227
- * absent, and the narrowed `task_finish` gate stays unarmed for a call
228
- * that DOES request an auto-merge.
229
- *
230
- * Mirrors the two-field handling `resolveCodexExemptionCommand`
231
- * (`hook-codex-pre-tool-use.ts`) already established for this exact
232
- * shape, but in the opposite fail-closed direction: that function
233
- * REFUSES an exemption when the two fields disagree (an exemption is an
234
- * allow, so disagreement must not grant it); here a `requires:` gate is
235
- * a block, so disagreement must not WITHHOLD it. When both `tool_input`
236
- * and `raw_input` are present as non-null objects, `input_match` is
237
- * evaluated against BOTH; if either one matches, the predicate holds
238
- * (the gate is armed) regardless of what the other field says. Only
239
- * when NEITHER field matches does the gate stay unarmed.
240
- */
241
- function inputMatchMismatchesEvent(inputMatch, event) {
242
- const toolInput = event.tool_input;
243
- const rawInput = event.raw_input;
244
- if (isNonNullObject(toolInput) && isNonNullObject(rawInput)) {
245
- const toolInputMismatch = firstInputMatchMismatch(inputMatch, contextWithToolArgs(event, toolInput));
246
- const rawInputMismatch = firstInputMatchMismatch(inputMatch, contextWithToolArgs(event, rawInput));
247
- return toolInputMismatch !== null && rawInputMismatch !== null;
248
- }
249
- return firstInputMatchMismatch(inputMatch, buildEventContext(event)) !== null;
250
- }
251
- /** Map a failed-`requires` policy to its decision outcome by enforcement. */
252
- function outcomeForFailedRequires(enforcement) {
253
- switch (enforcement) {
254
- case "block":
255
- return "deny";
256
- case "warn":
257
- return "warn";
258
- case "require_approval":
259
- return "require_approval";
260
- }
261
- }
262
- /**
263
- * Does a decision abort the tool call? Phase 7 #6 makes the Risk Gate
264
- * authoritative at the `PreToolUse` boundary:
265
- * - `deny` aborts (a `block`-enforcement policy whose requires failed,
266
- * the Phase 4 mechanism, unchanged).
267
- * - `require_approval` aborts until the approval evidence exists. In
268
- * Phase 7 #5 this outcome was returned but did not block; #6 makes
269
- * it block. The approval tag is satisfiable through the policy's
270
- * `requires:` (an operator runs `harness approve risk`); once the
271
- * tag is on record the requires evaluation passes and the outcome
272
- * is `allow` instead.
273
- * - `deny-degraded` aborts (task f1aea826): it is only ever produced
274
- * for `block` / `require_approval` policies under the default
275
- * `preserve_enforcement` posture (see `degradedOutcome`), so its
276
- * presence alone means "an incident-preventing gate could not read
277
- * its evidence" — fail closed.
278
- * - `allow` / `warn` / `warn-degraded` never abort.
279
- *
280
- * Exported for the CLI wrapper's pending-approval staging
281
- * (`src/cli/policy/intercept.ts`), which must agree with the runtime on
282
- * WHICH decision is the first blocking one — a hand-rolled copy there
283
- * drifted when `deny-degraded` was added (review 2026-08-08, low
284
- * finding). Keep this the single definition.
285
- */
286
- export function isBlockingDecision(d) {
287
- if (d.outcome === "deny")
288
- return d.enforcement === "block";
289
- if (d.outcome === "deny-degraded")
290
- return true;
291
- return d.outcome === "require_approval";
292
- }
293
- /**
294
- * Outcome for a policy whose `requires` could NOT be evaluated at all —
295
- * degraded/thrown ledger query, unresolved template variables, invalid
296
- * `within`, thrown `evaluateRequires`, or the defensive
297
- * schema-invariant branch. The fail posture is derived from the
298
- * policy's own `enforcement:` (task f1aea826):
299
- *
300
- * warn → `warn-degraded` (availability first,
301
- * unchanged: advisory friction never
302
- * bricks the session)
303
- * block / require_approval → `deny-degraded` (fail closed: a gate
304
- * against an irreversible incident must
305
- * not open because its evidence source
306
- * is unreadable)
307
- *
308
- * The manifest-level opt-out `risk.degraded_fail_posture: fail_open`
309
- * restores the pre-0.45 availability-first mapping (`warn-degraded`
310
- * for every tier). The hand-built-manifest case (`options.manifest.risk`
311
- * absent, only possible past `harness validate`) defaults to the
312
- * fail-closed posture, matching the schema default.
313
- */
314
- function degradedOutcome(policy, manifest) {
315
- if (policy.enforcement === "warn")
316
- return "warn-degraded";
317
- const posture = manifest.risk?.degraded_fail_posture ?? "preserve_enforcement";
318
- return posture === "fail_open" ? "warn-degraded" : "deny-degraded";
319
- }
320
- /**
321
- * Control characters (C0 range plus DEL), built via fromCharCode so the
322
- * source file itself stays free of raw control bytes. Matches what the
323
- * envelope sanitiser collapses to a single space.
324
- */
325
- const ENVELOPE_CONTROL_CHARS = new RegExp(`[${String.fromCharCode(0)}-${String.fromCharCode(31)}${String.fromCharCode(127)}]+`, "g");
326
- /**
327
- * Characters that are not printable text but can hide, reorder or smuggle
328
- * what a reader sees, or start a new "line" in model-visible text. Matched
329
- * as a rule rather than a hand list: every code point with General_Category
330
- * Cf (format: bidi marks and controls, U+200B, U+FEFF, U+2060-U+2064,
331
- * U+206A-U+206F, U+FFF9-U+FFFB, U+180E, U+00AD, the tag characters
332
- * U+E0001 and U+E0020-U+E007F, ...) or Default_Ignorable_Code_Point (adds
333
- * the variation selectors U+FE00-U+FE0F and U+E0100-U+E01EF, U+034F,
334
- * U+3164 and the rest of U+E0000-U+E0FFF), plus the C1 controls
335
- * (U+0080-U+009F, including NEL) and the line and paragraph separators
336
- * (U+2028, U+2029). The zero width joiner U+200D is kept, because it joins
337
- * emoji sequences that are printable text. The backslash is escaped too, so
338
- * a name that literally spells an escape cannot be mistaken for one. Each
339
- * match is replaced by a visible `\u{XXXX}` escape (four or five hex
340
- * digits) rather than dropped, so a reader still sees that the name carried
341
- * it. Written as escapes so this source file holds none of them raw.
342
- */
343
- const ENVELOPE_ESCAPED_CHARS = /(?!\u200D)[\u0080-\u009F\u2028\u2029\\\p{Cf}\p{Default_Ignorable_Code_Point}]/u;
344
- const ENVELOPE_MAX_LENGTH = 200;
345
- /**
346
- * Bound and clean a transport-level reason before it is interpolated
347
- * into the agent-facing deny-degraded envelope. The string can embed
348
- * output captured from the grounding-mcp SUBPROCESS (`exitDiagnostic`
349
- * in `src/policies/ledger-client.ts` appends the child's last stderr
350
- * line), i.e. untrusted content that previously only reached stderr
351
- * and the audit ledger, never model-visible text. The full, untouched
352
- * string still goes to the audit row and the verbose diagnostic; only
353
- * the envelope interpolation and the default-verbosity operator hint
354
- * are bounded.
355
- *
356
- * Exported for the CLI wrapper's deny-degraded operator hint (review
357
- * 2026-08-08, round 3; default-verbosity only, suppressed under
358
- * verbose), which interpolates the same untrusted reason into its
359
- * one-line stderr surface.
360
- */
361
- export function sanitizeEnvelopeReason(reason) {
362
- const collapsed = reason.replace(ENVELOPE_CONTROL_CHARS, " ");
363
- // Build whole tokens (a code point, or one escape) so the length bound can
364
- // never cut an escape sequence or a surrogate pair in half.
365
- const tokens = [];
366
- for (const ch of collapsed) {
367
- tokens.push(ENVELOPE_ESCAPED_CHARS.test(ch)
368
- ? `\\u{${ch.codePointAt(0).toString(16).padStart(4, "0")}}`
369
- : ch);
370
- }
371
- let total = 0;
372
- for (const t of tokens)
373
- total += t.length;
374
- if (total <= ENVELOPE_MAX_LENGTH)
375
- return tokens.join("");
376
- let out = "";
377
- for (const t of tokens) {
378
- if (out.length + t.length > ENVELOPE_MAX_LENGTH)
379
- break;
380
- out += t;
381
- }
382
- return `${out}...`;
383
- }
384
- /**
385
- * Agent-facing sentence appended to a block whose blocking decision came from
386
- * a foreign attributed context. Names the repository and directory the
387
- * command targets (both come from the agent's own command and the work tree
388
- * it resolves to, so both pass through `sanitizeEnvelopeReason`) and says the
389
- * missing evidence belongs to that repository, not to the working directory.
390
- * Names no command: which step produces the evidence is the policy's own,
391
- * and it has to be produced for the target repository.
392
- */
393
- function foreignTargetSentence(target, structured) {
394
- const sentence = `This command targets repository \`${sanitizeEnvelopeReason(target.repo)}\` ` +
395
- `(directory \`${sanitizeEnvelopeReason(target.dir)}\`). ` +
396
- `The required evidence is missing for that repository, not for the working directory, ` +
397
- `so it has to be produced for that repository itself.`;
398
- return structured ? `\n\n${sentence}` : ` ${sentence}`;
399
- }
400
- const EMPTY_REPO_MESSAGE = "no git repository was found for this command: the working directory (or the directory the command targets) is not inside a git repository, so the repository-scoped evidence this policy checks cannot be looked up. " +
401
- "Change into the target repository first (`cd <repo>`) or name it explicitly (`git -C <repo> ...`), then retry the command.";
402
- /**
403
- * Names the repository whose HEAD is detached, so a detached working
404
- * directory that is denied next to a `git -C <other repo>` target is
405
- * recognisable as the working directory's own repository. The name is the
406
- * resolved `${REPO}` value: the work-tree basename, a `HARNESS_REPO`
407
- * override, or the value of a policy extract named `REPO` (which can
408
- * carry tool input), so it is cleaned and bounded like any other untrusted
409
- * text before it reaches the agent envelope: C0 controls and DEL
410
- * collapsed to a space, invisible and format characters escaped as
411
- * `\u{XXXX}`, at most 200 characters (`sanitizeEnvelopeReason`).
412
- */
413
- function emptyBranchMessage(repo) {
414
- const name = sanitizeEnvelopeReason(repo);
415
- return (`no branch is checked out in repository \`${name}\`: HEAD is detached, so the branch-scoped evidence this policy checks cannot be looked up. ` +
416
- "Check out a named branch there (`git switch <branch>`, or `git switch -c <branch>` for a new one), create the evidence for that branch, then retry the command.");
417
- }
418
- function isBlankIdentifier(value) {
419
- return value === undefined || value.trim().length === 0;
420
- }
421
- /**
422
- * Does `ledgerTagTemplate` reference `${REPO}` / `${BRANCH}` while the
423
- * value resolved for this context is empty? An empty identifier must
424
- * never be rendered into a ledger tag: `preflight:` is a substring of
425
- * EVERY preflight fact, so a blank tag lets any unrelated fact satisfy
426
- * the gate (and `preflight-before-push` would render "You cannot push
427
- * branch yet."). The caller decides per enforcement with
428
- * `message` instead of querying the ledger.
429
- *
430
- * `REPO` wins over `BRANCH`, and a branch-only template evaluated where
431
- * REPO is empty too (no repository at all) gets the no-repository text,
432
- * since "HEAD is detached" would be false there. The messages name a
433
- * state the agent can establish (`cd` / `git -C` / `git switch`), never
434
- * a tag to produce, so there is no producer trap, and never an opt-out
435
- * (a block that names its own disable path is not a gate).
436
- *
437
- * Exported so `harness dry-run` shows the same hint instead of a blank
438
- * tag (`src/cli/dry-run.ts`).
439
- */
440
- export function emptyIdentifierGuard(ledgerTagTemplate, values) {
441
- const refsRepo = ledgerTagTemplate.includes("${REPO}");
442
- const refsBranch = ledgerTagTemplate.includes("${BRANCH}");
443
- if (refsRepo && isBlankIdentifier(values.REPO)) {
444
- return { identifier: "REPO", message: EMPTY_REPO_MESSAGE };
445
- }
446
- if (refsBranch && isBlankIdentifier(values.BRANCH)) {
447
- return isBlankIdentifier(values.REPO)
448
- ? { identifier: "REPO", message: EMPTY_REPO_MESSAGE }
449
- : { identifier: "BRANCH", message: emptyBranchMessage(values.REPO ?? "") };
450
- }
451
- return null;
452
- }
453
- /**
454
- * Placeholder `ledgerTag` recorded on an `operator_only` decision (and on
455
- * the defensive schema-invariant-violated branch below). Both outcomes
456
- * are decided WITHOUT ever substituting or querying a real tag, so this
457
- * is a readable marker for `harness audit` / `explain --trace` / the
458
- * stderr diagnostic, not a value anything matches against.
459
- */
460
- const NO_LEDGER_TAG = "(operator-only: no ledger tag — never evaluated)";
461
- async function evaluateOnePolicy(policy, options) {
462
- const evaluatedAt = (options.now ?? new Date()).toISOString();
463
- const ctx = buildEventContext(options.event);
464
- const extract = evaluateExtract(policy.trigger.extract ?? {}, ctx, options.builtins);
465
- // Unconditional operator-only deny (schema `operator_only: true`, task
466
- // 2cc73f55). The schema's superRefine guarantees this form carries NO
467
- // `requires:` and `enforcement: block`, so short-circuit here, BEFORE
468
- // the requires pipeline: no ledger query, no template substitution, no
469
- // `evaluateRequires` call. That is the load-bearing property — there is
470
- // no in-session evidence (ledger tag, marker file, flag) this branch
471
- // ever reads, so none can flip the outcome to allow. `extract` above is
472
- // still computed (cheap, pure) only because a `ux:`/`producers:` block
473
- // on this policy may reference `${VAR}`s from `trigger.extract`; it
474
- // plays no role in the outcome below.
475
- if (policy.operator_only === true) {
476
- return {
477
- policyName: policy.name,
478
- enforcement: policy.enforcement,
479
- outcome: "deny",
480
- reason: "operator-only: this policy declares no requires: and cannot be satisfied by any in-session evidence",
481
- extractValues: extract.values,
482
- ledgerTag: NO_LEDGER_TAG,
483
- evaluatedAt,
484
- };
485
- }
486
- const requires = policy.requires;
487
- if (requires === undefined) {
488
- // Unreachable under a schema-validated manifest: PolicySchema's
489
- // superRefine requires either `requires:` or `operator_only: true`.
490
- // Defensive branch only, for a manifest that bypassed
491
- // `harness validate` (hand-built Policy object, stale cached parse).
492
- //
493
- // Deliberately NOT a plain `deny`: this is the SAME "could not
494
- // decide" family as the other degraded returns in this function
495
- // (unresolved template variables, a degraded ledger query, an
496
- // invalid `within`, a throwing `evaluateRequires`) — the contract
497
- // for all of them is "the evaluator could not form a real verdict",
498
- // routed through `degradedOutcome` so the policy's own enforcement
499
- // decides the fail posture (task f1aea826): `warn` stays the
500
- // non-blocking `warn-degraded`, `block`/`require_approval` fail
501
- // closed as `deny-degraded`. Failing this branch as plain `deny`
502
- // would treat an internal schema-invariant violation as if it were
503
- // a deliberate `operator_only: true` policy authored by the
504
- // operator, which it is not — `deny-degraded` keeps the two
505
- // observably distinct in every audit row and envelope.
506
- return {
507
- policyName: policy.name,
508
- enforcement: policy.enforcement,
509
- outcome: degradedOutcome(policy, options.manifest),
510
- reason: "policy declares neither requires: nor operator_only: true (schema invariant violated)",
511
- extractValues: extract.values,
512
- ledgerTag: NO_LEDGER_TAG,
513
- evaluatedAt,
514
- };
515
- }
516
- // Empty-identifier guard: decided BEFORE any tag is rendered or the
517
- // ledger is queried, per the policy's own enforcement (block -> deny,
518
- // require_approval -> require_approval, warn -> warn). Deliberately NOT
519
- // routed through `degradedOutcome` / the unresolved-variable branch
520
- // below: that path ends in the `deny-degraded` envelope, which blames
521
- // an unreadable ledger and tells the agent to ask the operator to
522
- // check grounding-mcp, false and misleading for an empty identifier.
523
- const emptyGuard = emptyIdentifierGuard(requires.ledger_tag, extract.values);
524
- if (emptyGuard !== null) {
525
- return {
526
- policyName: policy.name,
527
- enforcement: policy.enforcement,
528
- outcome: outcomeForFailedRequires(policy.enforcement),
529
- reason: emptyGuard.message,
530
- extractValues: extract.values,
531
- ledgerTag: `(empty ${emptyGuard.identifier}: no ledger tag rendered, no ledger query)`,
532
- emptyIdentifier: emptyGuard.identifier,
533
- evaluatedAt,
534
- };
535
- }
536
- const missingExtracts = extract.traceData
537
- .filter((t) => t.source === "missing")
538
- .map((t) => t.var);
539
- const sub = substituteTemplate(requires.ledger_tag, extract.values);
540
- const ledgerTag = sub.result;
541
- const unresolved = [...missingExtracts, ...sub.missing];
542
- if (unresolved.length > 0) {
543
- return {
544
- policyName: policy.name,
545
- enforcement: policy.enforcement,
546
- outcome: degradedOutcome(policy, options.manifest),
547
- reason: `template variables unresolved: ${unresolved.join(", ")}`,
548
- extractValues: extract.values,
549
- ledgerTag,
550
- evaluatedAt,
551
- };
552
- }
553
- const sessionId = resolveSessionId(options.event.session_id);
554
- let queryResult;
555
- try {
556
- queryResult = await options.ledger.query(ledgerTag, sessionId, options.ledgerTimeoutMs);
557
- }
558
- catch (err) {
559
- queryResult = {
560
- kind: "degraded",
561
- reason: `ledger query threw: ${err.message}`,
562
- };
563
- }
564
- if (queryResult.kind === "degraded") {
565
- return {
566
- policyName: policy.name,
567
- enforcement: policy.enforcement,
568
- outcome: degradedOutcome(policy, options.manifest),
569
- reason: queryResult.reason,
570
- extractValues: extract.values,
571
- ledgerTag,
572
- evaluatedAt,
573
- };
574
- }
575
- // Pre-validate `within` against the runtime parser so a manifest that
576
- // bypassed `harness validate` doesn't throw uncaught.
577
- if (requires.within !== undefined) {
578
- try {
579
- parseDurationSeconds(requires.within);
580
- }
581
- catch {
582
- return {
583
- policyName: policy.name,
584
- enforcement: policy.enforcement,
585
- outcome: degradedOutcome(policy, options.manifest),
586
- reason: `invalid within: ${requires.within}`,
587
- extractValues: extract.values,
588
- ledgerTag,
589
- evaluatedAt,
590
- };
591
- }
592
- }
593
- const evalOpts = {
594
- ...(options.now && { now: options.now }),
595
- ...(options.currentHeadSha !== undefined &&
596
- options.currentHeadSha.length > 0 && {
597
- currentHeadSha: options.currentHeadSha,
598
- }),
599
- };
600
- const filtered = filterEntriesByTag(queryResult.entries, ledgerTag);
601
- let evaluation;
602
- try {
603
- evaluation = evaluateRequires({ ...requires, ledger_tag: ledgerTag }, filtered, evalOpts);
604
- }
605
- catch (err) {
606
- return {
607
- policyName: policy.name,
608
- enforcement: policy.enforcement,
609
- outcome: degradedOutcome(policy, options.manifest),
610
- reason: `requires eval threw: ${err.message}`,
611
- extractValues: extract.values,
612
- ledgerTag,
613
- evaluatedAt,
614
- };
615
- }
616
- // Four-way decision (Phase 7 #5). A satisfied `requires` always
617
- // `allow`s; a failed one is mapped by the policy's enforcement —
618
- // `block` → `deny`, `warn` → `warn`, `require_approval` →
619
- // `require_approval`. The evaluator only RETURNS `require_approval`
620
- // here; Phase 7 #6 makes it block.
621
- const outcome = evaluation.allowed
622
- ? "allow"
623
- : outcomeForFailedRequires(policy.enforcement);
624
- return {
625
- policyName: policy.name,
626
- enforcement: policy.enforcement,
627
- outcome,
628
- reason: evaluation.reason,
629
- extractValues: extract.values,
630
- ledgerTag,
631
- requiresEval: {
632
- matchedCount: evaluation.matchedCount,
633
- reason: evaluation.reason,
634
- },
635
- recordHint: evaluation.recordHint,
636
- evaluatedAt,
637
- };
638
- }
639
- function filterEntriesByTag(entries, tag) {
640
- // The ledger client returns the entire session's entries; filter to those
641
- // whose content/source matches the substituted tag. evaluateRequires also
642
- // does a substring match, but pre-filtering here keeps the trace quieter
643
- // and avoids the requires evaluator iterating unrelated session entries.
644
- //
645
- // Phase 5 #4 — also drop `policy_decision` rows so a past audit
646
- // payload doesn't incidentally match the same tag the decision was
647
- // about (the substring-pollution bug from PR #39's dogfood). The
648
- // requires evaluator's entryMatches has the same guard, but
649
- // pre-filtering keeps matchedCount honest in the trace data.
650
- return entries.filter((e) => e.type !== POLICY_DECISION_TYPE &&
651
- // Legacy backstop for pre-Phase-5-#4 rows: they were stored as
652
- // type='fact' but carry the `policy_decision:` content prefix.
653
- // Drop them at the same gate so a user upgrading harness without
654
- // flushing their dev ledger doesn't keep paying the pollution
655
- // tax. New rows are caught by the type check above.
656
- !e.content.startsWith(`${POLICY_DECISION_TYPE}:`) &&
657
- (e.content.includes(tag) ||
658
- (e.source !== undefined && e.source.includes(tag))));
659
- }
660
- /**
661
- * Which of a policy's own trigger-satisfying segments name a target — the
662
- * attribution sibling of `policyMatchesEvent` (task `98ad072f`, T-003).
663
- * Re-tests the policy's OWN `bash_match` regex against each segment's
664
- * `text` in isolation: every shipped `bash_match` alternation includes a
665
- * `^` branch, so testing it against one segment's canonicalised text
666
- * alone (no leading boundary character — see `CommandSegment.text`'s own
667
- * doc comment) is well-defined. Returns every segment that matches on its
668
- * own — usually one, occasionally several (D-004: a decoy read and a cwd
669
- * read chained in one command can each independently satisfy the SAME
670
- * trigger).
671
- *
672
- * Never changes WHETHER a policy matches — `policyMatchesEvent` already
673
- * decided that from the WHOLE command (raw-or-normalised-or-amp-
674
- * normalised), unchanged by this function. This only narrows down WHICH
675
- * segment(s), if any, are individually responsible for the match, so
676
- * `intercept()` below knows which segment's target — if any — to resolve
677
- * the `${REPO}`/`${BRANCH}`/`currentHeadSha` builtins from instead of the
678
- * event's own cwd. `[]` when the policy has no `bash_match` trigger (an
679
- * MCP-tool-name-triggered policy — no segment concept applies), when its
680
- * regex is malformed (mirrors `policyMatchesEvent`'s own defensive
681
- * `try/catch`), or when the whole-command match came ONLY from the
682
- * ampersand-aware third arm: `segments` here is always built from the
683
- * PRIMARY (`BOUNDARY_RE`) segmentation, which — by construction — cannot
684
- * itself contain the bare-`&` split the amp arm relies on, so an amp-
685
- * only match finds no individually-matching segment and this returns
686
- * `[]` (D-003: cwd builtins, identical to shipped).
687
- */
688
- export function attributeTriggerSegments(policy, segments) {
689
- if (policy.trigger.bash_match === undefined)
690
- return [];
691
- let re;
692
- try {
693
- re = new RegExp(policy.trigger.bash_match);
694
- }
695
- catch {
696
- return [];
697
- }
698
- return segments.filter((seg) => re.test(seg.text));
699
- }
700
- /**
701
- * Does a policy's `requires:` reference the per-repo `${REPO}`/`${BRANCH}`
702
- * builtins, or ask for `at_head`? Only these policies pay the per-policy
703
- * attribution cost below — every other matching policy keeps the plain,
704
- * per-event cwd builtins `options.builtins` already carries, byte-
705
- * identical to before this task. D-005: `at_head` is included even when
706
- * `ledger_tag` itself doesn't reference `${REPO}`/`${BRANCH}`, because
707
- * `at_head` compares against `currentHeadSha`, which this task resolves
708
- * from the SAME per-policy context — splitting the two would check
709
- * `at_head` against the cwd's HEAD while `${REPO}` (if present elsewhere)
710
- * named a different repo, the worse inconsistency D-005 rejects.
711
- */
712
- export function usesPerRepoBuiltins(policy) {
713
- const requires = policy.requires;
714
- if (requires === undefined)
715
- return false;
716
- return (requires.at_head === true ||
717
- requires.ledger_tag.includes("${REPO}") ||
718
- requires.ledger_tag.includes("${BRANCH}"));
719
- }
720
- /**
721
- * Bound on the number of DISTINCT attributed contexts one policy is
722
- * evaluated against for one event (D-013, fix round, run
723
- * 2026-08-02-per-repo-gate-scoping-redesign). Each distinct context costs
724
- * one ledger query and one audit write in `intercept()`'s evaluation loop
725
- * below; on a manifest with several per-repo-builtins policies (the
726
- * shipped `FULL_TEMPLATE` has four), an event naming K distinct targets
727
- * amplifies to 4K queries/writes, unbounded by `MAX_NORMALIZE_LENGTH` —
728
- * measured 200/200 at K=200 by reviewer 2, on a hook budget whose timeout
729
- * is ALLOW (same class as the 07-27 quadratic hot-path fail-open: a slow
730
- * enough event silently passes). `resolveAttributedContexts` returns a
731
- * `"bounded"` result instead of a `contexts` array once a policy's DISTINCT
732
- * targets would exceed this constant; `intercept()` denies that policy
733
- * directly (naming the ambiguity) without querying the ledger for any of
734
- * them, rather than silently evaluating all of them.
735
- */
736
- export const MAX_ATTRIBUTED_CONTEXTS = 4;
737
- /**
738
- * Realpath a path for identity comparison, never throwing (D-012, fix
739
- * round, run 2026-08-02-per-repo-gate-scoping-redesign). Falls back to the
740
- * LEXICAL path unchanged when the target does not exist or is otherwise
741
- * unreadable (`fs.realpathSync.native` throws `ENOENT` for a path this
742
- * module was never guaranteed to have on disk, and this function must
743
- * never throw — same fail-safe posture as `resolveGitContext` itself,
744
- * which returns empty strings rather than throwing on a bad path).
745
- * `.native` (not the plain JS `fs.realpathSync`) resolves via the OS
746
- * syscall directly — cheaper, and avoids Node's own pure-JS symlink-loop
747
- * bookkeeping for a value this function only uses for an identity
748
- * comparison, never for a filesystem walk of its own.
749
- */
750
- function realpathOrSelf(p) {
751
- try {
752
- return fs.realpathSync.native(p);
753
- }
754
- catch {
755
- return p;
756
- }
757
- }
758
- /**
759
- * Resolve the distinct `${REPO}`/`${BRANCH}`/`currentHeadSha` contexts a
760
- * `usesPerRepoBuiltins` policy must be evaluated against (task `98ad072f`,
761
- * T-003, `01-plan.md` Proposed Approach items 2-4). The engine trusts a
762
- * trigger-satisfying segment's `effectiveTarget` UNIFORMLY as the
763
- * directory that segment's own invocation genuinely runs in (bash
764
- * semantics) — orchestrator decision D-010, 2026-08-02: an earlier
765
- * revision of this function additionally distrusted an inherited target
766
- * whenever a DIFFERENT invocation intervened between the `cd` and the
767
- * satisfying segment — REJECTED: that coupling has no basis in bash's own
768
- * semantics, is dodgeable by inserting any harmless read between the `cd`
769
- * and the gated verb, and carried its own unverified `cd`-recognition
770
- * gap. The `cd <B> && git log && git push` shape this was meant to guard
771
- * is not a regression at all under this design — `git push` genuinely
772
- * runs inside B — see `tests/runtime/intercept-cli.test.ts`'s "leading-cd
773
- * is now a deliverable" block.
774
- *
775
- * D-021 (UNIVERSAL-ADDITIVE, operator decision, fix round, run
776
- * 2026-08-02-per-repo-gate-scoping-redesign): attribution is uniformly
777
- * ADDITIVE, never REPLACE. Every satisfying segment with an attributable
778
- * target — `seg.effectiveTarget !== null`, whether it came from the
779
- * segment's OWN explicit repo-relocating flag (`-C`/`env -C`/`--git-dir`;
780
- * `--work-tree` is excluded from ever producing a target at all, D-017)
781
- * or was INHERITED from a preceding `cd` — demands the cwd context AND
782
- * that target's own context, side by side; `seg.ownTarget` is not
783
- * consulted for this decision. Any unsatisfied context blocks the whole
784
- * event (the existing per-context evaluation machinery below is
785
- * unchanged). `seg.effectiveTarget === null` (fully unattributable —
786
- * D-003) still adds ONLY the cwd context — there is no foreign target to
787
- * be additive WITH.
788
- *
789
- * Demanding cwd unconditionally makes the engine structurally immune to
790
- * misattribution of the foreign target: no gap in `command-normalize.ts`'s
791
- * static, string-only model of shell control flow (subshells, pipes,
792
- * `cd -`, a later `cd`, an unmodeled git flag composition) can ever make a
793
- * gate WEAKER than the shipped, cwd-only engine, because cwd's own demand
794
- * is never dropped — only added to. This replaces an earlier REPLACE-for-
795
- * own-target design (D-011) that trusted a statically extracted own
796
- * target as proof of "this one invocation operates there"; four
797
- * independent review passes each measured a live bypass of that
798
- * invariant against the shipped binary (a forged `.git/HEAD` directory
799
- * reached through, respectively, a non-persisting `cd`, `--work-tree`,
800
- * more than one repo-relocating flag, and a tilde-valued flag not counted
801
- * by the multi-flag guard) — see `03-decisions.md` D-011/D-017/D-018/
802
- * D-019/D-020/D-021 for the full history. The static-analysis-proves-it
803
- * invariant those bypasses each disproved is not carried forward here.
804
- * Cost accepted by the operator: a legitimate `git -C B` from cwd A now
805
- * demands BOTH A's and B's context, where the pre-D-021 engine demanded
806
- * only B's.
807
- *
808
- * One exception to "never dropped": when the working directory is outside
809
- * every git repository (a blank `${REPO}` for the policy, as the guard
810
- * sees it), its cwd context is not demanded next to a segment whose own
811
- * target resolved to a real repository (see the loop body). That
812
- * exception relies on two static models: the target attribution, whose
813
- * known misattribution (a `GIT_DIR=` prefix or a third repository reached
814
- * through another construct) exists for every cwd, and the check that the
815
- * cwd is outside every repository, which errs toward inside: it holds only
816
- * when neither the cwd nor any ancestor has an entry named `HEAD` or
817
- * `.git` (`mayBeInsideRepository`). A detached cwd and every other
818
- * non-blank cwd context are still never dropped.
819
- *
820
- * D-012: a target reached through a symlink resolves to its REAL
821
- * (realpath'd) repository identity, not the symlink's own lexical
822
- * basename — `resolveGitContext` derives `repo` from the basename of
823
- * wherever it's pointed, and never itself realpaths, so an agent could
824
- * otherwise pick the demanded repo identity by naming a symlink.
825
- *
826
- * D-013: distinct contexts are bounded at `MAX_ATTRIBUTED_CONTEXTS`; see
827
- * that constant's own comment.
828
- *
829
- * D-015: the cwd context is deduped by its OWN `[REPO, BRANCH, sha]`
830
- * signature (not the literal string `"cwd"`), so a foreign path that
831
- * resolves to the SAME repository identity as cwd (e.g. a subdirectory of
832
- * the cwd repo reached via an explicit `-C`) collapses into the cwd
833
- * context instead of producing a spurious duplicate decision/audit write.
834
- * `repoOverridden` / `branchOverridden` (from `InterceptOptions`, both
835
- * default `false`) keep an operator's `HARNESS_REPO`/`HARNESS_BRANCH`
836
- * override intact in an attributed foreign context instead of letting the
837
- * target repo's own identity silently overwrite it — independently per
838
- * field. `satisfying` is a `Set` (not `Array.includes` per segment) for
839
- * O(1) membership tests instead of O(n) per segment.
840
- *
841
- * A policy with no attributable foreign target (no `bash_match`, no
842
- * individually-matching segment, only an amp-arm-only match, an
843
- * unattributable composition, a target resolving to the SAME repository
844
- * identity as cwd, or a target outside any git repo — D-003) is evaluated
845
- * EXACTLY as it is today, with the EXACT SAME `options.builtins` object
846
- * reference (no clone) when nothing attributed, keeping that the
847
- * byte-identical common case.
848
- *
849
- * `resolveGitContextMemo` is a per-`intercept()`-call cache keyed by the
850
- * REALPATH'D absolute path (never module-level state — no cross-event
851
- * caching), so several policies (or several satisfying segments) naming
852
- * the same foreign path within one event pay the `fs` cost once, not
853
- * once per policy per segment. `mayBeInsideRepositoryMemo` is the same
854
- * kind of per-call cache for the cwd's own outside-every-repository
855
- * check, keyed by the cwd's real path, so that walk runs once per event.
856
- */
857
- export function resolveAttributedContexts(policy, segments, cwdBuiltins, cwdCurrentHeadSha, resolveGitContextMemo, mayBeInsideRepositoryMemo, repoOverridden, branchOverridden, extractContext) {
858
- const cwdContext = { builtins: cwdBuiltins, currentHeadSha: cwdCurrentHeadSha };
859
- const satisfying = new Set(attributeTriggerSegments(policy, segments));
860
- if (satisfying.size === 0)
861
- return { kind: "contexts", contexts: [cwdContext] };
862
- const cwdSignature = [cwdBuiltins.REPO, cwdBuiltins.BRANCH, cwdCurrentHeadSha ?? ""].join("|");
863
- const seenSignatures = new Set();
864
- const contexts = [];
865
- const addCwdOnce = () => {
866
- if (seenSignatures.has(cwdSignature))
867
- return;
868
- seenSignatures.add(cwdSignature);
869
- contexts.push(cwdContext);
870
- };
871
- const cwdReal = realpathOrSelf(cwdBuiltins.CWD);
872
- // Decided from the same values `evaluateOnePolicy` guards on: the
873
- // policy's own `trigger.extract` evaluated against the cwd builtins, so
874
- // an extract named `REPO` / `BRANCH` that shadows a builtin is honoured
875
- // here exactly as it is there.
876
- const cwdGuard = emptyIdentifierGuard(policy.requires?.ledger_tag ?? "", evaluateExtract(policy.trigger.extract ?? {}, extractContext, cwdBuiltins).values);
877
- // "Outside every repository" is checked on the cwd's real path: the
878
- // builtins resolve the path as given, while git runs in the physical
879
- // directory, so a symlink into a repository is not outside it. An empty
880
- // CWD names no directory at all and never qualifies. The check is the
881
- // conservative `mayBeInsideRepository`, not the static
882
- // `resolveGitContext` the builtins come from: that resolver misses
883
- // repositories git still finds (see the helper's doc comment), and here
884
- // a miss would drop a demand. It runs only for a blank `${REPO}`, at
885
- // most once per event (`mayBeInsideRepositoryMemo`).
886
- const cwdOutsideEveryRepository = cwdGuard?.identifier === "REPO" &&
887
- cwdBuiltins.CWD.length > 0 &&
888
- !mayBeInsideRepositoryMemoised(cwdReal, mayBeInsideRepositoryMemo);
889
- for (const seg of segments) {
890
- if (!satisfying.has(seg))
891
- continue;
892
- if (seg.effectiveTarget === null) {
893
- // D-003: fully unattributable — cwd only, no foreign target to be
894
- // additive with.
895
- addCwdOnce();
896
- continue;
897
- }
898
- // D-021 (UNIVERSAL-ADDITIVE, operator decision after the four-pass
899
- // halt — see this function's own doc comment): the cwd context is
900
- // demanded UNCONDITIONALLY here, regardless of whether the target came
901
- // from the segment's own explicit flag or was inherited from a
902
- // preceding `cd`. `seg.ownTarget` is no longer read for this decision.
903
- // The one exception is `cwdOutsideEveryRepository` below: it is
904
- // decided after the target resolved, so the cwd context is added from
905
- // the branches of this loop body instead of up front, in the same
906
- // order as before.
907
- const resolvedLexical = path.resolve(cwdBuiltins.CWD, seg.effectiveTarget);
908
- const resolved = realpathOrSelf(resolvedLexical);
909
- if (resolved === cwdReal) {
910
- addCwdOnce();
911
- continue;
912
- }
913
- let gitCtx = resolveGitContextMemo.get(resolved);
914
- if (gitCtx === undefined) {
915
- gitCtx = resolveGitContext(resolved);
916
- resolveGitContextMemo.set(resolved, gitCtx);
917
- }
918
- if (gitCtx.repo.length === 0) {
919
- // D-003: outside any repo → cwd fallback, not a distinct context.
920
- addCwdOnce();
921
- continue;
922
- }
923
- const signature = [gitCtx.repo, gitCtx.branch, gitCtx.sha].join("|");
924
- // Empty-identifier exception to the universal-additive rule, limited
925
- // to a working directory outside every git repository. That cwd
926
- // context has a blank `${REPO}` (the value the guard sees, after the
927
- // policy's own extract) and can never be satisfied: the
928
- // empty-identifier guard denies it without a ledger query. Demanding
929
- // it next to a target that resolved to a real repository would deny
930
- // the very remedy the guard's message names (`git -C <repo> ...`, or
931
- // `cd <repo> && ...` in one command). The skip applies only when the
932
- // cwd is outside every repository and this segment's own target
933
- // resolved to a real repository; the target's own context is still
934
- // demanded in full. It relies on two static models: the target
935
- // attribution, whose known misattribution (a `GIT_DIR=` prefix or a
936
- // third repository reached through another construct) exists for every
937
- // cwd, and the cwd resolution, which is conservative: the skip applies
938
- // only when neither the cwd's real path nor any ancestor up to the
939
- // filesystem root holds an entry named `HEAD` or `.git` (any type) and
940
- // no lookup failed other than with ENOENT (`mayBeInsideRepository`).
941
- // What remains outside both models: a third repository the command
942
- // really runs in (a `GIT_DIR=` prefix, or a `cd` into another
943
- // repository before the misattributed segment), state the command
944
- // itself creates while it runs (a `.git` it links or writes before the
945
- // git verb), and an ambient `GIT_DIR` / `GIT_COMMON_DIR` in the
946
- // environment git runs with. A
947
- // detached cwd (non-blank `${REPO}`, blank `${BRANCH}`) and
948
- // every non-blank cwd context keep the rule above unchanged: the
949
- // detached remedy (`git switch` in that repository) is establishable,
950
- // and dropping that context would let a misattributed push from the
951
- // detached HEAD pass on the target's evidence. A segment without a
952
- // resolved target (a bare `git status`) keeps its cwd context and
953
- // still denies with the hint.
954
- if (!cwdOutsideEveryRepository)
955
- addCwdOnce();
956
- if (signature === cwdSignature) {
957
- // D-015: a foreign target that resolves to cwd's own REAL identity
958
- // (e.g. a subdirectory of the cwd repo reached via `-C`, a
959
- // different literal path than `cwdBuiltins.CWD` itself but the SAME
960
- // `.git`) collapses into the cwd context regardless of segment
961
- // order — not just when a bare cwd-reading segment happened to add
962
- // it first.
963
- addCwdOnce();
964
- continue;
965
- }
966
- if (seenSignatures.has(signature))
967
- continue;
968
- if (contexts.length >= MAX_ATTRIBUTED_CONTEXTS) {
969
- // D-013: fail CLOSED — do not evaluate any of them, name the
970
- // ambiguity instead. `contexts.length + 1` names "at least this
971
- // many distinct targets", the count observed before bailing, not
972
- // necessarily the final total (the loop stops here).
973
- return { kind: "bounded", distinctCount: contexts.length + 1 };
974
- }
975
- seenSignatures.add(signature);
976
- contexts.push({
977
- builtins: {
978
- ...cwdBuiltins,
979
- REPO: repoOverridden ? cwdBuiltins.REPO : gitCtx.repo,
980
- BRANCH: branchOverridden ? cwdBuiltins.BRANCH : gitCtx.branch,
981
- },
982
- currentHeadSha: gitCtx.sha.length > 0 ? gitCtx.sha : undefined,
983
- foreignTarget: { repo: gitCtx.repo, dir: resolved },
984
- });
985
- }
986
- // Defensive fallback: every satisfying segment existed but somehow none
987
- // was added above (should not happen — every branch above adds either
988
- // the cwd context or a foreign one). Never leave a matched, per-repo-
989
- // builtins policy with zero contexts to evaluate against.
990
- return { kind: "contexts", contexts: contexts.length > 0 ? contexts : [cwdContext] };
991
- }
992
- /**
993
- * Could git find a repository from `dir`? Decided for the one place that
994
- * drops a demand on the answer (the cwd-outside-every-repository exception
995
- * in `resolveAttributedContexts`), so every doubt counts as inside. The
996
- * static `resolveGitContext` walk that resolves the `REPO` / `BRANCH`
997
- * builtins is left as it is for its other callers; it reports no
998
- * repository where git still finds one: a `.git` directory without `HEAD`
999
- * ends its walk (git skips it and walks on), its walk stops after 128
1000
- * levels, and it never recognises a directory that is itself a git
1001
- * directory (a bare repository, or one whose objects live elsewhere
1002
- * through a `commondir` file).
1003
- *
1004
- * This walk does not list layouts. It uses the one structural fact every
1005
- * git directory shares: git accepts a directory as a git directory only
1006
- * when it holds an entry named `HEAD`, and it reaches a repository from a
1007
- * working directory only through a `.git` entry (directory or `gitdir:`
1008
- * file) or a git directory on the way up. So, from `dir` up to the
1009
- * filesystem root with no depth bound:
1010
- *
1011
- * - an entry named `HEAD` or `.git`, of any type, valid or not, counts as
1012
- * inside (never followed, read or validated);
1013
- * - an `lstat` error other than ENOENT counts as inside (the entry may
1014
- * exist).
1015
- *
1016
- * The cost is a conservative false inside: a directory that merely holds
1017
- * an entry named `HEAD` (also `head` or `Head` on a case-insensitive
1018
- * volume), or lies below one, keeps its cwd context and denies with the
1019
- * no-repository hint (fail closed). An ambient `GIT_DIR` /
1020
- * `GIT_COMMON_DIR` in git's environment is not visible here.
1021
- */
1022
- function mayBeInsideRepository(dir) {
1023
- let current = path.resolve(dir);
1024
- for (;;) {
1025
- if (entryMayExist(path.join(current, ".git")))
1026
- return true;
1027
- if (entryMayExist(path.join(current, "HEAD")))
1028
- return true;
1029
- const parent = path.dirname(current);
1030
- if (parent === current)
1031
- return false;
1032
- current = parent;
1033
- }
1034
- }
1035
- /**
1036
- * `false` only when `lstat` proves the entry absent (ENOENT, reported as
1037
- * `undefined` without building an exception); every other error counts as
1038
- * present.
1039
- */
1040
- function entryMayExist(entryPath) {
1041
- try {
1042
- return fs.lstatSync(entryPath, { throwIfNoEntry: false }) !== undefined;
1043
- }
1044
- catch (error) {
1045
- return error.code !== "ENOENT";
1046
- }
1047
- }
1048
- /**
1049
- * `mayBeInsideRepository(dir)`, computed at most once per `dir` within one
1050
- * `intercept()` call. `memo` is that call's own map (never module-level
1051
- * state), so no answer outlives the event it was computed for.
1052
- */
1053
- function mayBeInsideRepositoryMemoised(dir, memo) {
1054
- const known = memo.get(dir);
1055
- if (known !== undefined)
1056
- return known;
1057
- const inside = mayBeInsideRepository(dir);
1058
- memo.set(dir, inside);
1059
- return inside;
1060
- }
1061
- /**
1062
- * Lazily resolve the event's per-segment view for attribution, computed
1063
- * at most once per `intercept()` call (the caller, `intercept()` below,
1064
- * memoises the RESULT in its own `segmentsForAttribution` local — this
1065
- * function itself is called at most once per call already, but stays
1066
- * side-effect-free so that remains true regardless of caller changes).
1067
- * Prefers `options.commandSegmentsThunk` (D-015 fix round: the deferred,
1068
- * compute-only-if-needed seam — see its own doc comment), then the eager
1069
- * `options.commandSegments`, then falls back to computing it directly
1070
- * from the event's own command for standalone callers/tests that inject
1071
- * neither. `null` (truncated — see `segmentViewOf`) and "no Bash command
1072
- * on this event" both collapse to `[]`, the same "nothing to attribute,
1073
- * cwd builtins" shape as a policy with no `bash_match` at all.
1074
- */
1075
- function resolveCommandSegments(options) {
1076
- if (options.commandSegmentsThunk !== undefined)
1077
- return options.commandSegmentsThunk() ?? [];
1078
- if (options.commandSegments !== undefined)
1079
- return options.commandSegments ?? [];
1080
- const command = extractShellCommand(options.event);
1081
- if (command === null)
1082
- return [];
1083
- return segmentViewOf(command) ?? [];
1084
- }
1085
- /**
1086
- * Synthesise the single decision `intercept()` records for a policy whose
1087
- * distinct attributed contexts exceeded `MAX_ATTRIBUTED_CONTEXTS` (D-013).
1088
- * Routes through `outcomeForFailedRequires` — the SAME enforcement-to-
1089
- * outcome mapping every other "could not safely evaluate" branch in
1090
- * `evaluateOnePolicy` uses — rather than a hardcoded outcome, so a
1091
- * `block`-enforcement policy (the security-critical case: preflight/push/
1092
- * merge gates) genuinely denies, while a `warn`-enforcement policy warns
1093
- * instead of hard-blocking, consistent with how every other failed-
1094
- * evaluation branch in this module already respects the policy's own
1095
- * declared enforcement. No ledger query, no template substitution against
1096
- * a resolved `ledger_tag` (there is no single context to substitute one
1097
- * against) — `extractValues` is still computed from `trigger.extract`
1098
- * against the CWD builtins (cheap, pure, matches the `operator_only`
1099
- * short-circuit's own reasoning) so a `ux:`/`producers:` block on this
1100
- * policy can still render.
1101
- */
1102
- function boundedContextsDecision(policy, event, distinctCount, cwdBuiltins, evaluatedAt) {
1103
- const extract = evaluateExtract(policy.trigger.extract ?? {}, buildEventContext(event), cwdBuiltins);
1104
- return {
1105
- policyName: policy.name,
1106
- enforcement: policy.enforcement,
1107
- outcome: outcomeForFailedRequires(policy.enforcement),
1108
- reason: `ambiguous: this command names at least ${distinctCount} distinct repository targets for this policy, ` +
1109
- `exceeding the ${MAX_ATTRIBUTED_CONTEXTS}-context bound — refusing to evaluate all of them`,
1110
- extractValues: extract.values,
1111
- ledgerTag: "(bounded: too many distinct attributed targets — no context queried)",
1112
- evaluatedAt,
1113
- };
1114
- }
1115
- export async function intercept(options) {
1116
- const { manifest, event } = options;
1117
- // The Risk Gate is active only when some policy declares a `when:`
1118
- // block. A manifest with none — every Phase 4 / 5 / 6 manifest — skips
1119
- // envelope enrichment entirely: no `buildActionEnvelope`, no
1120
- // classifier, no resolver, and decisions carry no `risk` / `environment`.
1121
- // That keeps such manifests byte-for-byte identical to pre-Phase-7-#5.
1122
- const riskGateActive = manifest.policies.some((p) => p.when !== undefined);
1123
- const enriched = riskGateActive
1124
- ? enrichEnvelope(manifest, event, options.riskContext, options.now)
1125
- : undefined;
1126
- // A policy fires only when its `trigger:` matches AND — when declared
1127
- // — every `when:` clause holds against the enriched envelope.
1128
- //
1129
- // For each matching when:-bearing policy we also record its
1130
- // `unclassifiedFallback` flag so the decision record and deny message
1131
- // can distinguish a genuine classification hit from a fail-closed
1132
- // unclassified command (M7: runtime audit + block message). Policies
1133
- // that have no `when:` block are not inserted into the map, so a
1134
- // later `whenFallbackMap.get(name) === true` test is unambiguous.
1135
- //
1136
- // Explicit loop rather than Array.filter() so the map is built as a
1137
- // first-class step: a filter predicate is expected to be pure, and a
1138
- // future refactor that parallelises the filter would silently break the
1139
- // audit flag if the mutation were still hiding inside the predicate.
1140
- const whenFallbackMap = new Map();
1141
- const matching = [];
1142
- for (const p of manifest.policies) {
1143
- if (!policyMatchesEvent(p, event, options.normalizedCommand, options.ampNormalizedCommandThunk, options.quoteNormalizedCommandThunk)) {
1144
- continue;
1145
- }
1146
- if (p.when === undefined) {
1147
- matching.push(p);
1148
- continue;
1149
- }
1150
- // `enriched` is defined here: a policy with `when:` set `riskGateActive`.
1151
- const whenEval = evaluateWhen(p.when, enriched);
1152
- if (whenEval.matched) {
1153
- whenFallbackMap.set(p.name, whenEval.unclassifiedFallback);
1154
- matching.push(p);
1155
- }
1156
- }
1157
- // Per-policy attribution (task `98ad072f`, T-003): `segmentsForAttribution`
1158
- // is resolved at most once, lazily, only if some matching policy actually
1159
- // uses `${REPO}`/`${BRANCH}`/`at_head` — a manifest with none of those
1160
- // (every Phase 4/5/6-only manifest) never computes it.
1161
- // `resolveGitContextMemo` is this call's own, non-module-level cache
1162
- // (task constraint: no eager/global filesystem work) — see
1163
- // `resolveAttributedContexts`'s own comment. `mayBeInsideRepositoryMemo`
1164
- // is the same kind of per-call cache for the cwd walk.
1165
- let segmentsForAttribution;
1166
- const resolveGitContextMemo = new Map();
1167
- const mayBeInsideRepositoryMemo = new Map();
1168
- const decisions = [];
1169
- for (const policy of matching) {
1170
- const attributed = usesPerRepoBuiltins(policy)
1171
- ? resolveAttributedContexts(policy, (segmentsForAttribution ??= resolveCommandSegments(options)), options.builtins, options.currentHeadSha, resolveGitContextMemo, mayBeInsideRepositoryMemo, options.repoOverridden === true, options.branchOverridden === true, buildEventContext(options.event))
1172
- : { kind: "contexts", contexts: [{ builtins: options.builtins, currentHeadSha: options.currentHeadSha }] };
1173
- if (attributed.kind === "bounded") {
1174
- // D-013: fail CLOSED without querying the ledger for any of the
1175
- // (too many) distinct targets — one synthetic decision, one audit
1176
- // write, then move on to the next policy. Ledger-query count for
1177
- // THIS policy stays at zero regardless of how many distinct targets
1178
- // the command actually names, instead of scaling with them.
1179
- const evaluatedAt = (options.now ?? new Date()).toISOString();
1180
- const decision = boundedContextsDecision(policy, event, attributed.distinctCount, options.builtins, evaluatedAt);
1181
- decisions.push(decision);
1182
- try {
1183
- await options.ledger.record(decision, resolveSessionId(event.session_id));
1184
- }
1185
- catch (err) {
1186
- (options.stderr ?? process.stderr).write(`harness runtime intercept: audit-write failed for ${decision.policyName}: ${err instanceof Error ? err.message : String(err)}\n`);
1187
- }
1188
- continue;
1189
- }
1190
- for (const context of attributed.contexts) {
1191
- // Same object reference as `options` on the (overwhelmingly common)
1192
- // single-cwd-context path — no clone, byte-identical to the
1193
- // pre-attribution call shape. Only a genuinely foreign context
1194
- // builds a shallow override.
1195
- const contextOptions = context.builtins === options.builtins &&
1196
- context.currentHeadSha === options.currentHeadSha
1197
- ? options
1198
- : { ...options, builtins: context.builtins, currentHeadSha: context.currentHeadSha };
1199
- const base = await evaluateOnePolicy(policy, contextOptions);
1200
- // Attach the per-event Risk Gate verdicts so `harness audit` /
1201
- // `explain --trace` can replay the classification + environment
1202
- // that the `when:` match was made against. Also carry the
1203
- // unclassifiedFallback flag (M7) when the when: evaluation set it
1204
- // to true; leave the field absent otherwise so decisions from
1205
- // manifests without a `when:` policy stay byte-identical.
1206
- const whenFallback = whenFallbackMap.get(policy.name);
1207
- const decision = {
1208
- ...(enriched
1209
- ? {
1210
- ...base,
1211
- risk: enriched.risk,
1212
- environment: enriched.environment,
1213
- ...(whenFallback === true ? { whenUnclassifiedFallback: true } : {}),
1214
- }
1215
- : base),
1216
- ...(context.foreignTarget !== undefined ? { foreignTarget: context.foreignTarget } : {}),
1217
- };
1218
- decisions.push(decision);
1219
- try {
1220
- await options.ledger.record(decision, resolveSessionId(event.session_id));
1221
- }
1222
- catch (err) {
1223
- // Audit-write failure must not block; the decision is still applied.
1224
- // Surface the failure to stderr so a persistently-failing recorder
1225
- // does not silently leave `harness audit` / `explain --trace` blind.
1226
- // Goes to stderr to keep the stdout deny-JSON contract intact.
1227
- (options.stderr ?? process.stderr).write(`harness runtime intercept: audit-write failed for ${decision.policyName}: ${err instanceof Error ? err.message : String(err)}\n`);
1228
- }
1229
- }
1230
- }
1231
- // First blocking decision wins the envelope. `deny` and
1232
- // `require_approval` both abort (Phase 7 #6); the search order is the
1233
- // manifest's policy order, same as Phase 4.
1234
- const blocking = decisions.find(isBlockingDecision);
1235
- if (blocking) {
1236
- const sessionId = resolveSessionId(options.event.session_id);
1237
- // Append the "to satisfy" hint so Claude Code's deny message tells
1238
- // the operator (or the agent reading the same surface) what evidence
1239
- // would unblock the gate, instead of just naming the missing tag.
1240
- // The hint is content + window only; it does not prescribe a
1241
- // recording verb so the deny path stays neutral on producer (see
1242
- // agent-tasks/88ca4bb3 for why "use mcp__..." would be the wrong
1243
- // suggestion when the engine is the source of that suggestion).
1244
- //
1245
- // It DOES name the sessionId namespace the entry must be written
1246
- // under — this runtime session's id (the value shown), not the
1247
- // agent-tasks task UUID. Naming an identity is not a producer
1248
- // verb, so this stays compatible with the producer-neutrality above.
1249
- // The two namespaces are a known production footgun: an entry written
1250
- // under the agent-tasks task UUID never satisfies a harness runtime
1251
- // gate, which keys off the runtime session id (2026-05-17 incident,
1252
- // harness PRs #174/#175 — first attempt used the task UUID and was
1253
- // rejected, second used the session id and passed).
1254
- const hintSuffix = blocking.recordHint
1255
- ? ` To satisfy: ${blocking.recordHint}, under this runtime session's id \`${sessionId}\` (not the agent-tasks task UUID).`
1256
- : "";
1257
- // Opt-in producer block: when the policy declares `producers:` in
1258
- // the manifest, render the structured remediation list (bash / mcp
1259
- // / ask recipes) with ${VAR} placeholders substituted against the
1260
- // same extract.values the ledger_tag was resolved with. Schema
1261
- // validation guarantees at least one `mcp` producer per declared
1262
- // list, so an agent stuck in a Bash lockout always has an ungated
1263
- // recovery path. Policies without `producers:` get the legacy
1264
- // neutral deny envelope unchanged (agent-tasks/3804b785).
1265
- const blockingPolicy = matching.find((p) => p.name === blocking.policyName);
1266
- // When the policy declares `ux:`, the agent-facing surface
1267
- // becomes the plain-language `{cannot, required, run}` shape
1268
- // instead of the engine-vocabulary deny envelope. The internal
1269
- // decision (reason, recordHint, requiresEval, ledgerTag) is
1270
- // unchanged and still recorded to the audit ledger above. The
1271
- // producers block is suppressed when ux is declared because
1272
- // `run:` is the canonical remedy surface and rendering both would
1273
- // give the agent two different command suggestions.
1274
- let reasonText;
1275
- if (blocking.outcome === "deny-degraded") {
1276
- // Degraded-specific envelope (task f1aea826). Deliberately takes
1277
- // precedence over the policy's `ux:` surface: the operator-curated
1278
- // ux text describes the MISSING-evidence case ("run the producer,
1279
- // then retry"), which is actively misleading here — the evidence
1280
- // could not be READ, so producing the tag cannot unblock anything
1281
- // until the ledger is reachable again. The envelope names the real
1282
- // cause (the transport-level reason, sanitised — it embeds output
1283
- // from the grounding-mcp subprocess) and the posture, so nobody
1284
- // debugs a phantom missing tag.
1285
- //
1286
- // The `risk.degraded_fail_posture: fail_open` opt-out is
1287
- // deliberately NOT named here (review 2026-08-08, high finding):
1288
- // this text is fed back to the very agent that was just blocked,
1289
- // and nothing prevents that agent from editing the manifest — a
1290
- // deny that includes its own disable recipe is not a gate. The
1291
- // opt-out lives on operator surfaces only: the stderr diagnostic
1292
- // (`formatDecisionDiagnostic`), docs/risk-gate.md, and the OKF
1293
- // fail-posture matrix. Same reasoning as the producer-neutrality
1294
- // norm above and the operator-only framing of `harness pause`.
1295
- reasonText =
1296
- `${blocking.policyName}: required evidence could not be read ` +
1297
- `(evidence ledger degraded: ${sanitizeEnvelopeReason(blocking.reason)}). ` +
1298
- `This ${blocking.enforcement} policy fails closed while its evidence ` +
1299
- `source is unreadable; producing the required tag will not unblock it ` +
1300
- `until the ledger is reachable again. Ask your operator to check ` +
1301
- `grounding-mcp (harness doctor), then retry. Session: ${sessionId}.`;
1302
- }
1303
- else if (blocking.emptyIdentifier !== undefined) {
1304
- // Empty ${REPO} / ${BRANCH}: the decision's own reason names the
1305
- // state the agent can establish (`cd` / `git -C` / `git switch`).
1306
- // Takes precedence over `ux:` (whose `cannot:` text would render
1307
- // the blank identifier, "You cannot push branch yet.") and over
1308
- // `producers:` / the record hint (both would send the agent to
1309
- // produce a tag the gate will not read in this state). Names no
1310
- // opt-out, same reasoning as the degraded envelope above.
1311
- reasonText = `${blocking.policyName}: ${blocking.reason}`;
1312
- }
1313
- else if (blockingPolicy?.ux) {
1314
- // The ux surface is operator-curated plain language. Task
1315
- // 2929c5b7: a ux-declared policy's `cannot:` text used to be
1316
- // rendered unchanged even when the match was a fail-closed
1317
- // unclassified hit, not a genuine classification — so
1318
- // gate-prod-destructive's "You cannot run this critical
1319
- // destructive action against production." was shown verbatim for
1320
- // an unrecognized READ (e.g. `cat`/`sed -n`/`curl`) the moment the
1321
- // environment resolved to production, which is exactly the false
1322
- // positive this task exists to fix. When `whenUnclassifiedFallback`
1323
- // is set, a fallback-specific sentence is PREPENDED naming the real
1324
- // cause before the operator's own `cannot:` text, which is left
1325
- // byte-for-byte intact (added to, never replaced): the operator
1326
- // still chose that wording for the genuine-classification case,
1327
- // which keeps rendering unchanged. See docs/risk-gate.md,
1328
- // "Unclassified actions and the fail-close rule".
1329
- const uxText = renderAgentFacing(blockingPolicy.ux, {
1330
- ...blocking.extractValues,
1331
- SESSION_ID: sessionId,
1332
- });
1333
- reasonText = blocking.whenUnclassifiedFallback
1334
- ? `${unclassifiedFallbackPrefix(enriched?.environment.name, blockingPolicy.when?.["risk.severity_at_least"])} ${uxText}`
1335
- : uxText;
1336
- }
1337
- else {
1338
- const producersBlock = renderProducers(blockingPolicy?.producers, blocking.extractValues);
1339
- // M7: when the policy matched only because the action was
1340
- // unclassified (fail-closed), insert an operator-facing note so a
1341
- // deny caused by an unknown command is distinguishable from a deny
1342
- // caused by a genuine critical-severity classification. The note is
1343
- // placed after the base reason but BEFORE hintSuffix so the cause
1344
- // precedes the remedy ("why this fired" before "how to unblock").
1345
- // Neutral deny envelope only (not the ux path above).
1346
- const unclassifiedClause = blocking.whenUnclassifiedFallback
1347
- ? " (matched via the fail-closed unclassified rule, not a real risk classification)"
1348
- : "";
1349
- reasonText = `${blocking.policyName}: ${blocking.reason}.${unclassifiedClause}${hintSuffix}${producersBlock}`;
1350
- }
1351
- // A decision made for a foreign attributed context (a nested or
1352
- // vendored work tree named by the command) is explained by the
1353
- // repository it resolved to: the producer the message points at runs
1354
- // for the working directory, whose own evidence may already be on
1355
- // record. Appended only for a missing-evidence block; the degraded and
1356
- // empty-identifier envelopes above name their own cause and stay as
1357
- // they are. Deliberately policy-neutral and names no opt-out.
1358
- if (blocking.foreignTarget !== undefined &&
1359
- blocking.outcome !== "deny-degraded" &&
1360
- blocking.emptyIdentifier === undefined) {
1361
- reasonText += foreignTargetSentence(blocking.foreignTarget, blockingPolicy?.ux !== undefined);
1362
- }
1363
- const block = {
1364
- decision: "block",
1365
- reason: reasonText,
1366
- };
1367
- // permissionDecision is documented for PreToolUse only; emitting the
1368
- // envelope on other events would invent a shape Claude Code does not
1369
- // define, so we restrict it strictly. Other event kinds still block
1370
- // via the top-level `decision: "block"`.
1371
- if (options.event.hook_event_name === "PreToolUse") {
1372
- block.hookSpecificOutput = {
1373
- hookEventName: "PreToolUse",
1374
- permissionDecision: "deny",
1375
- // Duplicates `reason` intentionally: legacy consumers read the
1376
- // top-level field, modern PreToolUse consumers read this one.
1377
- permissionDecisionReason: reasonText,
1378
- };
1379
- }
1380
- return { decisions, blockJson: block };
1381
- }
1382
- return { decisions, blockJson: null };
1383
- }
1384
- //# sourceMappingURL=intercept.js.map