@nathapp/nax-agent 0.1.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 (398) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +49 -0
  4. package/dist/coding-tools/coding-tool-bash.d.ts +46 -0
  5. package/dist/coding-tools/coding-tool-bash.js +44 -0
  6. package/dist/coding-tools/coding-tool-extras.d.ts +35 -0
  7. package/dist/coding-tools/coding-tool-extras.js +56 -0
  8. package/dist/coding-tools/coding-tool-sandbox.d.ts +74 -0
  9. package/dist/coding-tools/coding-tool-sandbox.js +215 -0
  10. package/dist/coding-tools/coding-tool-support.d.ts +132 -0
  11. package/dist/coding-tools/coding-tool-support.js +159 -0
  12. package/dist/coding-tools/universal-coding-tools.d.ts +11 -0
  13. package/dist/coding-tools/universal-coding-tools.js +5 -0
  14. package/dist/command-interceptor/index.d.ts +55 -0
  15. package/dist/command-interceptor/index.js +60 -0
  16. package/dist/command-interceptor/shell.d.ts +36 -0
  17. package/dist/command-interceptor/shell.js +137 -0
  18. package/dist/command-safety/build.d.ts +19 -0
  19. package/dist/command-safety/build.js +47 -0
  20. package/dist/command-safety/guard.d.ts +32 -0
  21. package/dist/command-safety/guard.js +179 -0
  22. package/dist/command-safety/identifiers.d.ts +24 -0
  23. package/dist/command-safety/identifiers.js +20 -0
  24. package/dist/command-safety/index.d.ts +17 -0
  25. package/dist/command-safety/index.js +11 -0
  26. package/dist/command-safety/questions.d.ts +19 -0
  27. package/dist/command-safety/questions.js +60 -0
  28. package/dist/command-safety/row.d.ts +2 -0
  29. package/dist/command-safety/row.js +14 -0
  30. package/dist/command-safety/rule-scorer.d.ts +9 -0
  31. package/dist/command-safety/rule-scorer.js +391 -0
  32. package/dist/command-safety/shadow.d.ts +33 -0
  33. package/dist/command-safety/shadow.js +188 -0
  34. package/dist/command-safety/systemone-client.d.ts +16 -0
  35. package/dist/command-safety/systemone-client.js +92 -0
  36. package/dist/command-safety/tap.d.ts +37 -0
  37. package/dist/command-safety/tap.js +77 -0
  38. package/dist/command-safety/temp-only.d.ts +6 -0
  39. package/dist/command-safety/temp-only.js +215 -0
  40. package/dist/command-safety/tmp-write.d.ts +2 -0
  41. package/dist/command-safety/tmp-write.js +112 -0
  42. package/dist/command-safety/types.d.ts +185 -0
  43. package/dist/command-safety/types.js +21 -0
  44. package/dist/config/bash-approval.d.ts +33 -0
  45. package/dist/config/bash-approval.js +30 -0
  46. package/dist/config/catalog-overrides.d.ts +94 -0
  47. package/dist/config/catalog-overrides.js +7 -0
  48. package/dist/config/native-agent/index.d.ts +8 -0
  49. package/dist/config/native-agent/index.js +8 -0
  50. package/dist/config/schemas-sandbox.d.ts +31 -0
  51. package/dist/config/schemas-sandbox.js +45 -0
  52. package/dist/cost/core/index.d.ts +8 -0
  53. package/dist/cost/core/index.js +7 -0
  54. package/dist/cost/estimate.d.ts +34 -0
  55. package/dist/cost/estimate.js +65 -0
  56. package/dist/cost/model-spec.d.ts +28 -0
  57. package/dist/cost/model-spec.js +29 -0
  58. package/dist/cost/standard-types.d.ts +9 -0
  59. package/dist/cost/standard-types.js +1 -0
  60. package/dist/cost/usage-math.d.ts +18 -0
  61. package/dist/cost/usage-math.js +46 -0
  62. package/dist/index.d.ts +26 -0
  63. package/dist/index.js +21 -0
  64. package/dist/infra/agent-logger.d.ts +22 -0
  65. package/dist/infra/agent-logger.js +19 -0
  66. package/dist/infra/credentials-config.d.ts +20 -0
  67. package/dist/infra/credentials-config.js +15 -0
  68. package/dist/infra/errors.d.ts +9 -0
  69. package/dist/infra/errors.js +11 -0
  70. package/dist/infra/index.d.ts +7 -0
  71. package/dist/infra/index.js +7 -0
  72. package/dist/infra/nax-error.d.ts +10 -0
  73. package/dist/infra/nax-error.js +16 -0
  74. package/dist/infra/spin-breaker/hash.d.ts +2 -0
  75. package/dist/infra/spin-breaker/hash.js +5 -0
  76. package/dist/infra/spin-breaker/index.d.ts +120 -0
  77. package/dist/infra/spin-breaker/index.js +340 -0
  78. package/dist/internal/agent-output-env.d.ts +44 -0
  79. package/dist/internal/agent-output-env.js +57 -0
  80. package/dist/internal/argv-exec.d.ts +58 -0
  81. package/dist/internal/argv-exec.js +224 -0
  82. package/dist/internal/bounded-io.d.ts +24 -0
  83. package/dist/internal/bounded-io.js +51 -0
  84. package/dist/internal/command-spec/index.d.ts +29 -0
  85. package/dist/internal/command-spec/index.js +36 -0
  86. package/dist/internal/describe-value-type.d.ts +8 -0
  87. package/dist/internal/describe-value-type.js +14 -0
  88. package/dist/internal/exec-framing.d.ts +18 -0
  89. package/dist/internal/exec-framing.js +22 -0
  90. package/dist/internal/file-lock.d.ts +91 -0
  91. package/dist/internal/file-lock.js +212 -0
  92. package/dist/internal/file-size.d.ts +2 -0
  93. package/dist/internal/file-size.js +12 -0
  94. package/dist/internal/git-add.d.ts +43 -0
  95. package/dist/internal/git-add.js +59 -0
  96. package/dist/internal/git-env.d.ts +59 -0
  97. package/dist/internal/git-env.js +93 -0
  98. package/dist/internal/git-exec.d.ts +58 -0
  99. package/dist/internal/git-exec.js +115 -0
  100. package/dist/internal/path-file-lock.d.ts +23 -0
  101. package/dist/internal/path-file-lock.js +25 -0
  102. package/dist/internal/process-alive.d.ts +42 -0
  103. package/dist/internal/process-alive.js +56 -0
  104. package/dist/internal/process-kill.d.ts +27 -0
  105. package/dist/internal/process-kill.js +54 -0
  106. package/dist/internal/realpath.d.ts +29 -0
  107. package/dist/internal/realpath.js +52 -0
  108. package/dist/internal/redact.d.ts +40 -0
  109. package/dist/internal/redact.js +161 -0
  110. package/dist/internal/shell-quote.d.ts +2 -0
  111. package/dist/internal/shell-quote.js +4 -0
  112. package/dist/internal/sort.d.ts +20 -0
  113. package/dist/internal/sort.js +20 -0
  114. package/dist/internal/strip-control-chars.d.ts +19 -0
  115. package/dist/internal/strip-control-chars.js +38 -0
  116. package/dist/internal/thenable.d.ts +10 -0
  117. package/dist/internal/thenable.js +15 -0
  118. package/dist/internal.d.ts +91 -0
  119. package/dist/internal.js +91 -0
  120. package/dist/native/adapter-deps.d.ts +46 -0
  121. package/dist/native/adapter-deps.js +43 -0
  122. package/dist/native/auth-types.d.ts +70 -0
  123. package/dist/native/auth-types.js +13 -0
  124. package/dist/native/auth.d.ts +133 -0
  125. package/dist/native/auth.js +362 -0
  126. package/dist/native/client.d.ts +50 -0
  127. package/dist/native/client.js +174 -0
  128. package/dist/native/complete.d.ts +33 -0
  129. package/dist/native/complete.js +88 -0
  130. package/dist/native/credentials/chained-store.d.ts +30 -0
  131. package/dist/native/credentials/chained-store.js +68 -0
  132. package/dist/native/credentials/change-guard.d.ts +51 -0
  133. package/dist/native/credentials/change-guard.js +136 -0
  134. package/dist/native/credentials/exec-source.d.ts +50 -0
  135. package/dist/native/credentials/exec-source.js +222 -0
  136. package/dist/native/credentials/fingerprint.d.ts +26 -0
  137. package/dist/native/credentials/fingerprint.js +174 -0
  138. package/dist/native/credentials/helper-process.d.ts +79 -0
  139. package/dist/native/credentials/helper-process.js +259 -0
  140. package/dist/native/credentials/helper-protocol.d.ts +36 -0
  141. package/dist/native/credentials/helper-protocol.js +79 -0
  142. package/dist/native/credentials/index.d.ts +60 -0
  143. package/dist/native/credentials/index.js +184 -0
  144. package/dist/native/errors.d.ts +34 -0
  145. package/dist/native/errors.js +109 -0
  146. package/dist/native/index.d.ts +25 -0
  147. package/dist/native/index.js +23 -0
  148. package/dist/native/model-resolver.d.ts +57 -0
  149. package/dist/native/model-resolver.js +73 -0
  150. package/dist/native/models.d.ts +116 -0
  151. package/dist/native/models.js +183 -0
  152. package/dist/native/session/ask-human.d.ts +15 -0
  153. package/dist/native/session/ask-human.js +22 -0
  154. package/dist/native/session/compaction.d.ts +94 -0
  155. package/dist/native/session/compaction.js +170 -0
  156. package/dist/native/session/handle-invalid-tool-call.d.ts +111 -0
  157. package/dist/native/session/handle-invalid-tool-call.js +159 -0
  158. package/dist/native/session/loop-events/cache-boundary.d.ts +11 -0
  159. package/dist/native/session/loop-events/cache-boundary.js +29 -0
  160. package/dist/native/session/loop-events/external-handler.d.ts +38 -0
  161. package/dist/native/session/loop-events/external-handler.js +193 -0
  162. package/dist/native/session/loop-events/index.d.ts +16 -0
  163. package/dist/native/session/loop-events/index.js +15 -0
  164. package/dist/native/session/loop-events/payload-guard.d.ts +9 -0
  165. package/dist/native/session/loop-events/payload-guard.js +31 -0
  166. package/dist/native/session/loop-events/registry.d.ts +84 -0
  167. package/dist/native/session/loop-events/registry.js +305 -0
  168. package/dist/native/session/loop-events/types.d.ts +251 -0
  169. package/dist/native/session/loop-events/types.js +14 -0
  170. package/dist/native/session/loop-handlers.d.ts +73 -0
  171. package/dist/native/session/loop-handlers.js +137 -0
  172. package/dist/native/session/nudge.d.ts +29 -0
  173. package/dist/native/session/nudge.js +35 -0
  174. package/dist/native/session/rate-provenance.d.ts +17 -0
  175. package/dist/native/session/rate-provenance.js +39 -0
  176. package/dist/native/session/session.d.ts +139 -0
  177. package/dist/native/session/session.js +208 -0
  178. package/dist/native/session/tool-input-exemplar.d.ts +2 -0
  179. package/dist/native/session/tool-input-exemplar.js +66 -0
  180. package/dist/native/session/tool-input-validation.d.ts +15 -0
  181. package/dist/native/session/tool-input-validation.js +146 -0
  182. package/dist/native/session/tool-mapping.d.ts +18 -0
  183. package/dist/native/session/tool-mapping.js +27 -0
  184. package/dist/native/session/tool-result.d.ts +37 -0
  185. package/dist/native/session/tool-result.js +25 -0
  186. package/dist/native/session/transcript-store.d.ts +77 -0
  187. package/dist/native/session/transcript-store.js +200 -0
  188. package/dist/native/session/truncation-handler.d.ts +47 -0
  189. package/dist/native/session/truncation-handler.js +83 -0
  190. package/dist/native/session/turn-accumulator.d.ts +45 -0
  191. package/dist/native/session/turn-accumulator.js +67 -0
  192. package/dist/native/session/turn-ask-human.d.ts +23 -0
  193. package/dist/native/session/turn-ask-human.js +41 -0
  194. package/dist/native/session/turn-compaction-step.d.ts +74 -0
  195. package/dist/native/session/turn-compaction-step.js +128 -0
  196. package/dist/native/session/turn-complete-step.d.ts +77 -0
  197. package/dist/native/session/turn-complete-step.js +159 -0
  198. package/dist/native/session/turn-end-event.d.ts +3 -0
  199. package/dist/native/session/turn-end-event.js +17 -0
  200. package/dist/native/session/turn-events.d.ts +63 -0
  201. package/dist/native/session/turn-events.js +48 -0
  202. package/dist/native/session/turn-loop-round-trip.d.ts +116 -0
  203. package/dist/native/session/turn-loop-round-trip.js +318 -0
  204. package/dist/native/session/turn-loop.d.ts +23 -0
  205. package/dist/native/session/turn-loop.js +250 -0
  206. package/dist/native/session/turn-result.d.ts +39 -0
  207. package/dist/native/session/turn-result.js +64 -0
  208. package/dist/native/session/turn-retry.d.ts +120 -0
  209. package/dist/native/session/turn-retry.js +182 -0
  210. package/dist/native/session/turn-tool-batch.d.ts +61 -0
  211. package/dist/native/session/turn-tool-batch.js +259 -0
  212. package/dist/native/session/turn-types.d.ts +164 -0
  213. package/dist/native/session/turn-types.js +36 -0
  214. package/dist/native/session-adapter.d.ts +56 -0
  215. package/dist/native/session-adapter.js +369 -0
  216. package/dist/native/session-affinity.d.ts +36 -0
  217. package/dist/native/session-affinity.js +43 -0
  218. package/dist/native/tier-providers.d.ts +18 -0
  219. package/dist/native/tier-providers.js +20 -0
  220. package/dist/permissions/approval-audit.d.ts +10 -0
  221. package/dist/permissions/approval-audit.js +22 -0
  222. package/dist/permissions/approvals-link.d.ts +20 -0
  223. package/dist/permissions/approvals-link.js +62 -0
  224. package/dist/permissions/approvals-store.d.ts +168 -0
  225. package/dist/permissions/approvals-store.js +259 -0
  226. package/dist/permissions/approvals-taint.d.ts +33 -0
  227. package/dist/permissions/approvals-taint.js +138 -0
  228. package/dist/permissions/ask-chain.d.ts +69 -0
  229. package/dist/permissions/ask-chain.js +32 -0
  230. package/dist/permissions/ask.d.ts +23 -0
  231. package/dist/permissions/ask.js +25 -0
  232. package/dist/permissions/bash-lex.d.ts +59 -0
  233. package/dist/permissions/bash-lex.js +279 -0
  234. package/dist/permissions/grammar.d.ts +12 -0
  235. package/dist/permissions/grammar.js +24 -0
  236. package/dist/permissions/index.d.ts +11 -0
  237. package/dist/permissions/index.js +9 -0
  238. package/dist/permissions/secret-spans.d.ts +19 -0
  239. package/dist/permissions/secret-spans.js +88 -0
  240. package/dist/permissions/types.d.ts +85 -0
  241. package/dist/permissions/types.js +1 -0
  242. package/dist/runtime/index.d.ts +3 -0
  243. package/dist/runtime/index.js +2 -0
  244. package/dist/runtime/node-glob.d.ts +3 -0
  245. package/dist/runtime/node-glob.js +83 -0
  246. package/dist/runtime/node-runtime.d.ts +3 -0
  247. package/dist/runtime/node-runtime.js +126 -0
  248. package/dist/runtime/slot.d.ts +11 -0
  249. package/dist/runtime/slot.js +17 -0
  250. package/dist/runtime/types.d.ts +51 -0
  251. package/dist/runtime/types.js +1 -0
  252. package/dist/runtime/which.d.ts +8 -0
  253. package/dist/runtime/which.js +32 -0
  254. package/dist/sandbox/argv-quote.d.ts +11 -0
  255. package/dist/sandbox/argv-quote.js +13 -0
  256. package/dist/sandbox/defaults.d.ts +27 -0
  257. package/dist/sandbox/defaults.js +47 -0
  258. package/dist/sandbox/git-guards.d.ts +64 -0
  259. package/dist/sandbox/git-guards.js +161 -0
  260. package/dist/sandbox/index.d.ts +11 -0
  261. package/dist/sandbox/index.js +10 -0
  262. package/dist/sandbox/launcher.d.ts +37 -0
  263. package/dist/sandbox/launcher.js +160 -0
  264. package/dist/sandbox/messages.d.ts +17 -0
  265. package/dist/sandbox/messages.js +46 -0
  266. package/dist/sandbox/policy-builder.d.ts +44 -0
  267. package/dist/sandbox/policy-builder.js +149 -0
  268. package/dist/sandbox/policy-inputs.d.ts +53 -0
  269. package/dist/sandbox/policy-inputs.js +76 -0
  270. package/dist/sandbox/probe.d.ts +8 -0
  271. package/dist/sandbox/probe.js +66 -0
  272. package/dist/sandbox/registry.d.ts +17 -0
  273. package/dist/sandbox/registry.js +57 -0
  274. package/dist/sandbox/session-tmp.d.ts +20 -0
  275. package/dist/sandbox/session-tmp.js +95 -0
  276. package/dist/sandbox/srt-backend.d.ts +21 -0
  277. package/dist/sandbox/srt-backend.js +99 -0
  278. package/dist/sandbox/types.d.ts +102 -0
  279. package/dist/sandbox/types.js +1 -0
  280. package/dist/session/adapter-failure.d.ts +60 -0
  281. package/dist/session/adapter-failure.js +11 -0
  282. package/dist/session/agent-stream-events.d.ts +103 -0
  283. package/dist/session/agent-stream-events.js +6 -0
  284. package/dist/session/interaction-handler.d.ts +53 -0
  285. package/dist/session/interaction-handler.js +2 -0
  286. package/dist/session/no-op-interaction-handler.d.ts +9 -0
  287. package/dist/session/no-op-interaction-handler.js +11 -0
  288. package/dist/session/protocol-types.d.ts +12 -0
  289. package/dist/session/protocol-types.js +1 -0
  290. package/dist/session/session-types.d.ts +456 -0
  291. package/dist/session/session-types.js +64 -0
  292. package/dist/session/tool-descriptor.d.ts +23 -0
  293. package/dist/session/tool-descriptor.js +5 -0
  294. package/dist/session/turn-deadline.d.ts +17 -0
  295. package/dist/session/turn-deadline.js +23 -0
  296. package/dist/tools/ask-request.d.ts +35 -0
  297. package/dist/tools/ask-request.js +57 -0
  298. package/dist/tools/bash-cwd.d.ts +82 -0
  299. package/dist/tools/bash-cwd.js +55 -0
  300. package/dist/tools/bash.d.ts +69 -0
  301. package/dist/tools/bash.js +283 -0
  302. package/dist/tools/delete.d.ts +57 -0
  303. package/dist/tools/delete.js +154 -0
  304. package/dist/tools/denial-redirect.d.ts +42 -0
  305. package/dist/tools/denial-redirect.js +266 -0
  306. package/dist/tools/deny-paths.d.ts +35 -0
  307. package/dist/tools/deny-paths.js +76 -0
  308. package/dist/tools/edit-region.d.ts +50 -0
  309. package/dist/tools/edit-region.js +90 -0
  310. package/dist/tools/edit.d.ts +25 -0
  311. package/dist/tools/edit.js +114 -0
  312. package/dist/tools/exec-allowlist-text.d.ts +18 -0
  313. package/dist/tools/exec-allowlist-text.js +24 -0
  314. package/dist/tools/exec-guard.d.ts +69 -0
  315. package/dist/tools/exec-guard.js +151 -0
  316. package/dist/tools/git-commit.d.ts +59 -0
  317. package/dist/tools/git-commit.js +220 -0
  318. package/dist/tools/git-flags/index.d.ts +10 -0
  319. package/dist/tools/git-flags/index.js +10 -0
  320. package/dist/tools/git.d.ts +88 -0
  321. package/dist/tools/git.js +397 -0
  322. package/dist/tools/glob.d.ts +29 -0
  323. package/dist/tools/glob.js +153 -0
  324. package/dist/tools/grep.d.ts +21 -0
  325. package/dist/tools/grep.js +162 -0
  326. package/dist/tools/index.d.ts +36 -0
  327. package/dist/tools/index.js +28 -0
  328. package/dist/tools/narrow-grants.d.ts +19 -0
  329. package/dist/tools/narrow-grants.js +54 -0
  330. package/dist/tools/nax-owned-writes.d.ts +135 -0
  331. package/dist/tools/nax-owned-writes.js +259 -0
  332. package/dist/tools/package-managers-table.d.ts +146 -0
  333. package/dist/tools/package-managers-table.js +325 -0
  334. package/dist/tools/package-managers-types.d.ts +36 -0
  335. package/dist/tools/package-managers-types.js +6 -0
  336. package/dist/tools/package-managers.d.ts +85 -0
  337. package/dist/tools/package-managers.js +391 -0
  338. package/dist/tools/path-list.d.ts +38 -0
  339. package/dist/tools/path-list.js +62 -0
  340. package/dist/tools/policy-bash-raw.d.ts +19 -0
  341. package/dist/tools/policy-bash-raw.js +210 -0
  342. package/dist/tools/policy-bash.d.ts +64 -0
  343. package/dist/tools/policy-bash.js +232 -0
  344. package/dist/tools/policy-command-branch.d.ts +32 -0
  345. package/dist/tools/policy-command-branch.js +63 -0
  346. package/dist/tools/policy-input.d.ts +2 -0
  347. package/dist/tools/policy-input.js +10 -0
  348. package/dist/tools/policy-match.d.ts +75 -0
  349. package/dist/tools/policy-match.js +117 -0
  350. package/dist/tools/policy-paths-branch.d.ts +97 -0
  351. package/dist/tools/policy-paths-branch.js +314 -0
  352. package/dist/tools/policy.d.ts +65 -0
  353. package/dist/tools/policy.js +232 -0
  354. package/dist/tools/protected-paths.d.ts +26 -0
  355. package/dist/tools/protected-paths.js +8 -0
  356. package/dist/tools/provider-adapt.d.ts +4 -0
  357. package/dist/tools/provider-adapt.js +32 -0
  358. package/dist/tools/provider-advertise.d.ts +39 -0
  359. package/dist/tools/provider-advertise.js +50 -0
  360. package/dist/tools/provider-grants.d.ts +36 -0
  361. package/dist/tools/provider-grants.js +72 -0
  362. package/dist/tools/provider-sanitize.d.ts +19 -0
  363. package/dist/tools/provider-sanitize.js +53 -0
  364. package/dist/tools/provider-types.d.ts +41 -0
  365. package/dist/tools/provider-types.js +26 -0
  366. package/dist/tools/read-continuation.d.ts +147 -0
  367. package/dist/tools/read-continuation.js +176 -0
  368. package/dist/tools/read-file.d.ts +32 -0
  369. package/dist/tools/read-file.js +85 -0
  370. package/dist/tools/read.d.ts +25 -0
  371. package/dist/tools/read.js +182 -0
  372. package/dist/tools/registry.d.ts +125 -0
  373. package/dist/tools/registry.js +67 -0
  374. package/dist/tools/request-capability.d.ts +13 -0
  375. package/dist/tools/request-capability.js +23 -0
  376. package/dist/tools/run-command-exec.d.ts +14 -0
  377. package/dist/tools/run-command-exec.js +161 -0
  378. package/dist/tools/run-command.d.ts +107 -0
  379. package/dist/tools/run-command.js +398 -0
  380. package/dist/tools/runtime-calltool.d.ts +146 -0
  381. package/dist/tools/runtime-calltool.js +223 -0
  382. package/dist/tools/runtime.d.ts +149 -0
  383. package/dist/tools/runtime.js +345 -0
  384. package/dist/tools/scratchpad.d.ts +20 -0
  385. package/dist/tools/scratchpad.js +197 -0
  386. package/dist/tools/spill.d.ts +81 -0
  387. package/dist/tools/spill.js +251 -0
  388. package/dist/tools/tool-audit.d.ts +171 -0
  389. package/dist/tools/tool-audit.js +183 -0
  390. package/dist/tools/truncate.d.ts +98 -0
  391. package/dist/tools/truncate.js +297 -0
  392. package/dist/tools/types.d.ts +145 -0
  393. package/dist/tools/types.js +24 -0
  394. package/dist/tools/verb-denial-argv-hint.d.ts +35 -0
  395. package/dist/tools/verb-denial-argv-hint.js +40 -0
  396. package/dist/tools/write.d.ts +9 -0
  397. package/dist/tools/write.js +45 -0
  398. package/package.json +54 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Pattern compilation for the tool policy: globs, argv tokens, and the
3
+ * deny/ask rule maps.
4
+ *
5
+ * Extracted from `policy.ts` to keep that file under the 600-line source
6
+ * limit. `src/` reaches this module only through `policy.ts`, so the barrel
7
+ * surface is unchanged; tests may import it directly.
8
+ */
9
+ import type { ToolGrant } from "./types.ts";
10
+ /**
11
+ * Minimatch-style glob to RegExp: `**` spans separators, `*` does not.
12
+ *
13
+ * `**` followed by `/` is zero or more COMPLETE directory segments, which is
14
+ * why it does not simply compile to `.*`. `.*` both spans separators and
15
+ * matches the empty string, so emitting it and dropping the separator erased
16
+ * the boundary: `src/**\/config.ts` became `^src\/.*config\.ts$`, and a grant
17
+ * for files named `config.ts` also admitted `src/legacyconfig.ts`.
18
+ *
19
+ * That was never a root escape -- containment runs before any pattern matching
20
+ * -- but it made a *scoped* grant wider than its author wrote, which is the one
21
+ * thing a scoped profile exists to prevent.
22
+ */
23
+ export declare function globToRegExp(pattern: string): RegExp;
24
+ /** A compiled glob, keeping its source so verb names can be told from paths. */
25
+ export interface CompiledPattern {
26
+ readonly source: string;
27
+ readonly re: RegExp;
28
+ }
29
+ /**
30
+ * A deny/ask rule list compiled for one tool.
31
+ *
32
+ * Unlike the allow compiler (last-write-wins per tool, by design),
33
+ * rules for the same tool MERGE here: patterns are concatenated and any
34
+ * `"*"` makes the whole entry unconditional. That is safe -- and correct --
35
+ * because deny/ask are additive; a later rule must never silently drop an
36
+ * earlier one.
37
+ */
38
+ export interface CompiledEntry {
39
+ readonly unconditional: boolean;
40
+ readonly matchers: CompiledPattern[];
41
+ readonly argvPatterns: readonly (readonly CompiledPattern[])[];
42
+ readonly raw: readonly string[];
43
+ /** Patterns grouped by their original rule expression for ask telemetry. */
44
+ readonly rulePatterns?: readonly (readonly string[])[];
45
+ }
46
+ export declare function matchesAny(patterns: readonly CompiledPattern[], value: string): boolean;
47
+ /** The source of the first compiled glob matching `value`, for naming a rule. */
48
+ export declare function matchedGlobSource(patterns: readonly CompiledPattern[], value: string): string | undefined;
49
+ /**
50
+ * Split a grant pattern into whitespace-separated tokens, each compiled
51
+ * independently. Per-token compilation (rather than joining argv with
52
+ * spaces and matching one regex against the joined string) is deliberate:
53
+ * a joined string lets a value containing spaces or glob metacharacters
54
+ * shift what a later token appears to match. `"bun add*"` must mean "argv[0]
55
+ * is exactly bun, argv[1] starts with add", never "the space-joined argv
56
+ * matches this glob".
57
+ */
58
+ export declare function compileArgvPattern(pattern: string): readonly CompiledPattern[];
59
+ /** A grant pattern's tokens are a PREFIX of argv — trailing argv tokens
60
+ * (the package name, flags, ...) are the payload a prefix grant like
61
+ * `bun add*` does not itself constrain. */
62
+ export declare function matchesArgvPattern(tokens: readonly CompiledPattern[], argv: readonly string[]): boolean;
63
+ export declare function matchesArgvGrant(argvPatterns: readonly (readonly CompiledPattern[])[], argv: readonly string[]): boolean;
64
+ /**
65
+ * Compile a deny/ask list. Deliberately separate from the allow compiler: the
66
+ * allow map overwrites, and unifying the two would change verdicts for any
67
+ * config carrying two expressions for one tool (the byte-identity regression
68
+ * gate pins that). Merging is safe for deny/ask because those lists are new
69
+ * and additive.
70
+ */
71
+ export declare function compileRuleMap(rules: readonly ToolGrant[] | undefined): Map<string, CompiledEntry>;
72
+ /** The configured expression-group containing a matched pattern, if any. */
73
+ export declare function matchedRulePatterns(entry: CompiledEntry, source: string): readonly string[];
74
+ /** The original pattern whose tokens matched `argv`, for naming an ask rule. */
75
+ export declare function matchedArgvSource(entry: CompiledEntry, argv: readonly string[]): string | undefined;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Pattern compilation for the tool policy: globs, argv tokens, and the
3
+ * deny/ask rule maps.
4
+ *
5
+ * Extracted from `policy.ts` to keep that file under the 600-line source
6
+ * limit. `src/` reaches this module only through `policy.ts`, so the barrel
7
+ * surface is unchanged; tests may import it directly.
8
+ */
9
+ /**
10
+ * Minimatch-style glob to RegExp: `**` spans separators, `*` does not.
11
+ *
12
+ * `**` followed by `/` is zero or more COMPLETE directory segments, which is
13
+ * why it does not simply compile to `.*`. `.*` both spans separators and
14
+ * matches the empty string, so emitting it and dropping the separator erased
15
+ * the boundary: `src/**\/config.ts` became `^src\/.*config\.ts$`, and a grant
16
+ * for files named `config.ts` also admitted `src/legacyconfig.ts`.
17
+ *
18
+ * That was never a root escape -- containment runs before any pattern matching
19
+ * -- but it made a *scoped* grant wider than its author wrote, which is the one
20
+ * thing a scoped profile exists to prevent.
21
+ */
22
+ export function globToRegExp(pattern) {
23
+ let out = "";
24
+ for (let i = 0; i < pattern.length; i += 1) {
25
+ const char = pattern[i];
26
+ if (char === "*") {
27
+ if (pattern[i + 1] === "*") {
28
+ i += 1;
29
+ if (pattern[i + 1] === "/") {
30
+ // Optional, so `x/**/y` still matches `x/y` -- minimatch's behaviour.
31
+ out += "(?:.*/)?";
32
+ i += 1;
33
+ }
34
+ else {
35
+ out += ".*";
36
+ }
37
+ }
38
+ else {
39
+ out += "[^/]*";
40
+ }
41
+ continue;
42
+ }
43
+ if (char === "?") {
44
+ out += "[^/]";
45
+ continue;
46
+ }
47
+ out += char.replace(/[.+^${}()|[\]\\]/g, "\\$&");
48
+ }
49
+ return new RegExp(`^${out}$`);
50
+ }
51
+ export function matchesAny(patterns, value) {
52
+ return patterns.some((p) => p.re.test(value));
53
+ }
54
+ /** The source of the first compiled glob matching `value`, for naming a rule. */
55
+ export function matchedGlobSource(patterns, value) {
56
+ return patterns.find((p) => p.re.test(value))?.source;
57
+ }
58
+ /**
59
+ * Split a grant pattern into whitespace-separated tokens, each compiled
60
+ * independently. Per-token compilation (rather than joining argv with
61
+ * spaces and matching one regex against the joined string) is deliberate:
62
+ * a joined string lets a value containing spaces or glob metacharacters
63
+ * shift what a later token appears to match. `"bun add*"` must mean "argv[0]
64
+ * is exactly bun, argv[1] starts with add", never "the space-joined argv
65
+ * matches this glob".
66
+ */
67
+ export function compileArgvPattern(pattern) {
68
+ return pattern
69
+ .split(/\s+/)
70
+ .filter((token) => token.length > 0)
71
+ .map((token) => ({ source: token, re: globToRegExp(token) }));
72
+ }
73
+ /** A grant pattern's tokens are a PREFIX of argv — trailing argv tokens
74
+ * (the package name, flags, ...) are the payload a prefix grant like
75
+ * `bun add*` does not itself constrain. */
76
+ export function matchesArgvPattern(tokens, argv) {
77
+ if (tokens.length > argv.length)
78
+ return false;
79
+ return tokens.every((token, i) => token.re.test(argv[i]));
80
+ }
81
+ export function matchesArgvGrant(argvPatterns, argv) {
82
+ return argvPatterns.some((tokens) => matchesArgvPattern(tokens, argv));
83
+ }
84
+ /**
85
+ * Compile a deny/ask list. Deliberately separate from the allow compiler: the
86
+ * allow map overwrites, and unifying the two would change verdicts for any
87
+ * config carrying two expressions for one tool (the byte-identity regression
88
+ * gate pins that). Merging is safe for deny/ask because those lists are new
89
+ * and additive.
90
+ */
91
+ export function compileRuleMap(rules) {
92
+ const map = new Map();
93
+ for (const rule of rules ?? []) {
94
+ const existing = map.get(rule.tool);
95
+ const patterns = existing === undefined ? rule.patterns : [...existing.raw, ...rule.patterns];
96
+ const nonWildcard = patterns.filter((p) => p !== "*");
97
+ map.set(rule.tool, {
98
+ unconditional: patterns.includes("*"),
99
+ matchers: nonWildcard.map((source) => ({ source, re: globToRegExp(source) })),
100
+ argvPatterns: nonWildcard.map((source) => compileArgvPattern(source)),
101
+ raw: patterns,
102
+ rulePatterns: [...(existing?.rulePatterns ?? []), rule.patterns],
103
+ });
104
+ }
105
+ return map;
106
+ }
107
+ /** The configured expression-group containing a matched pattern, if any. */
108
+ export function matchedRulePatterns(entry, source) {
109
+ return entry.rulePatterns?.find((patterns) => patterns.includes(source)) ?? entry.raw;
110
+ }
111
+ /** The original pattern whose tokens matched `argv`, for naming an ask rule. */
112
+ export function matchedArgvSource(entry, argv) {
113
+ const index = entry.argvPatterns.findIndex((tokens) => matchesArgvPattern(tokens, argv));
114
+ if (index === -1)
115
+ return undefined;
116
+ return entry.raw.filter((p) => p !== "*")[index];
117
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The policy's path branch: containment first, then deny -> ask -> allow per
3
+ * resolved path, across the four kinds of path-bearing input field.
4
+ *
5
+ * Split out of `policy.ts` (cognitive-complexity drain A8). Each field kind is
6
+ * its own named handler, and the per-path sequence all four share — resolve,
7
+ * refuse containment breaches, apply rules, glob-check, record — is one helper
8
+ * whose arguments name the only things that differ between the loops: the
9
+ * denial's subject spelling, and whether a glob miss denies.
10
+ *
11
+ * Behavioural shape worth naming because the handlers must not be "unified":
12
+ * `pathFields` and `listPathFields` deny on a glob miss whenever the grant is
13
+ * conditional (`enforcePathGlobs`), while `arrayPathFields` and `refPathFields`
14
+ * deny only when the grant is conditional AND carries at least one path glob
15
+ * (`restrictPaths`). A verb-only grant therefore DENIES string path fields but
16
+ * leaves array/ref paths bounded by the root alone — pinned by `policy.test.ts`
17
+ * ("a verb-only grant leaves paths bounded by the root alone") and
18
+ * `policy-paths-branch-edges.test.ts`.
19
+ *
20
+ * Everything here is reached through `policy.ts`; this module imports nothing
21
+ * from it (the collaborators it shares — deny, askVerdict, applyPathRules —
22
+ * arrive by reference in `PathsBranchContext`, read at call time).
23
+ */
24
+ import { type CompiledEntry } from "./policy-match.ts";
25
+ import type { PolicyVerdict, ToolScope } from "./types.ts";
26
+ /** Mutable scratch shared by `check()`'s branch helpers: first ask rule matched. */
27
+ export interface RuleState {
28
+ ask?: string;
29
+ }
30
+ /**
31
+ * Per-policy collaborators the path branch shares with the rest of the policy.
32
+ * Passed by reference; every member is read at call time, so a caller that
33
+ * rebuilds or reassigns between checks stays authoritative.
34
+ */
35
+ export interface PathsBranchContext {
36
+ /** The policy's containment root, symlink-resolved at compile time. */
37
+ readonly resolvedRoot: string;
38
+ readonly deny: (reason: string, breach?: boolean, escalatable?: boolean) => PolicyVerdict;
39
+ readonly askVerdict: (resolvedPaths: readonly string[], rule: string) => PolicyVerdict;
40
+ readonly applyPathRules: (tool: string, rel: string, state: RuleState) => PolicyVerdict | undefined;
41
+ }
42
+ /**
43
+ * Absolute, symlink-resolved form of `candidate` if it lies inside `root`,
44
+ * is not itself (and does not lie under) `.git/`, and is not one of nax's own
45
+ * config files.
46
+ *
47
+ * The single containment seam. Multi-root support (a future configurable
48
+ * extension) changes this function and nothing else, which is why every tool
49
+ * receives an already-resolved path rather than resolving one itself. The
50
+ * `.git/` exclusion lives here for the same reason: it applies to every
51
+ * path-bearing tool uniformly, reads included, rather than being
52
+ * re-remembered per tool -- which is exactly the failure mode a declaration
53
+ * on `ToolScope` would reintroduce (nax#1943). `.git/` sits INSIDE the root,
54
+ * so containment alone never bars it, and an unconditional ("*") grant --
55
+ * what every non-Exec tool gets under the default `unrestricted` profile --
56
+ * skips glob matching entirely, so nothing downstream of this function would
57
+ * catch it either.
58
+ *
59
+ * Pre-single-frame, this seam carried one exception: an `execTouchedPaths` set
60
+ * (Task 10) admitted a workspace install's repo-ROOT manifest/lockfile even
61
+ * though it sat outside the containment root, which was then the story's
62
+ * package dir. The root move made the containment root the repo root, so the
63
+ * manifest is in-root by construction and that carve-out was retired
64
+ * (PR2/Task 13). There is no exception to this seam now.
65
+ */
66
+ export declare function resolveWithin(root: string, candidate: string): string | null;
67
+ /**
68
+ * Containment runs before any pattern matching and wins over everything.
69
+ * Each resolved path is then evaluated deny -> ask -> allow. A verb-only
70
+ * grant declares no path globs, leaving the root as the only bound --
71
+ * unchanged behaviour, now an authoring choice rather than something the
72
+ * grant syntax could not express.
73
+ *
74
+ * `confineTo` (tool-declared, see `ToolScope`) shifts the root passed to
75
+ * `resolveWithin` from `<root>` to `<root>/<confineTo>`: containment stays
76
+ * the one seam, and only its ROOT changes. `relativeTo` keeps rooting at
77
+ * `resolvedRoot` so grant globs, deny rules and `naxOwnedWriteRefusal`
78
+ * continue to see the canonical repo-root-relative spelling -- authors
79
+ * write `.nax/scratchpad/**`, never `**`, regardless of `confineTo`.
80
+ *
81
+ * `confineTo` is bound to stay INSIDE `resolvedRoot`: an authoring typo of
82
+ * `..` or `../shared` would otherwise widen the containment root past the
83
+ * policy boundary and re-scope `resolveWithin`'s `.git/`-metadata and
84
+ * `isNaxConfigFile` protections to a root that no longer aligns with the
85
+ * segments those checks assume -- the repo's own `.nax/config.json` would
86
+ stop being segment-matched against `.nax`. The boundary is the policy
87
+ root's invariant, so an out-of-root confineTo refuses the call outright
88
+ rather than silently widening.
89
+ */
90
+ export declare function pathsBranch(args: {
91
+ readonly ctx: PathsBranchContext;
92
+ readonly tool: string;
93
+ readonly scope: ToolScope;
94
+ readonly input: Record<string, unknown>;
95
+ readonly grant: CompiledEntry;
96
+ readonly state: RuleState;
97
+ }): PolicyVerdict;
@@ -0,0 +1,314 @@
1
+ /**
2
+ * The policy's path branch: containment first, then deny -> ask -> allow per
3
+ * resolved path, across the four kinds of path-bearing input field.
4
+ *
5
+ * Split out of `policy.ts` (cognitive-complexity drain A8). Each field kind is
6
+ * its own named handler, and the per-path sequence all four share — resolve,
7
+ * refuse containment breaches, apply rules, glob-check, record — is one helper
8
+ * whose arguments name the only things that differ between the loops: the
9
+ * denial's subject spelling, and whether a glob miss denies.
10
+ *
11
+ * Behavioural shape worth naming because the handlers must not be "unified":
12
+ * `pathFields` and `listPathFields` deny on a glob miss whenever the grant is
13
+ * conditional (`enforcePathGlobs`), while `arrayPathFields` and `refPathFields`
14
+ * deny only when the grant is conditional AND carries at least one path glob
15
+ * (`restrictPaths`). A verb-only grant therefore DENIES string path fields but
16
+ * leaves array/ref paths bounded by the root alone — pinned by `policy.test.ts`
17
+ * ("a verb-only grant leaves paths bounded by the root alone") and
18
+ * `policy-paths-branch-edges.test.ts`.
19
+ *
20
+ * Everything here is reached through `policy.ts`; this module imports nothing
21
+ * from it (the collaborators it shares — deny, askVerdict, applyPathRules —
22
+ * arrive by reference in `PathsBranchContext`, read at call time).
23
+ */
24
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
25
+ import { isInside, realOrRaw } from "#src/internal/realpath";
26
+ import { isNaxConfigFile } from "./nax-owned-writes.js";
27
+ import { pathListElements } from "./path-list.js";
28
+ import { pathFieldValue } from "./policy-input.js";
29
+ import { matchesAny } from "./policy-match.js";
30
+ /**
31
+ * Does `resolved` (already absolute and symlink-resolved) enter a `.git`
32
+ * directory anywhere along its path relative to `root`?
33
+ *
34
+ * A path-SEGMENT match, never a prefix or substring one: `.gitignore` and
35
+ * `.github/` are ordinary tracked names a tool must still be able to reach,
36
+ * and `startsWith(".git")` would wrongly swallow both -- the exact defect
37
+ * this function exists to avoid (nax#1943).
38
+ *
39
+ * Matches ANY segment, not only a leading one. A nested repository or a
40
+ * submodule checked into the tree (`vendor/some-lib/.git`, which may be a
41
+ * directory or, for a submodule, a file pointing at the real gitdir
42
+ * elsewhere) carries the same integrity and containment risk `.git/` at the
43
+ * root does -- the ruling behind this function is "no path-bearing tool ever
44
+ * addresses git metadata", not "only the top-level repository's".
45
+ */
46
+ function entersGitMetadata(root, resolved) {
47
+ const rel = relative(realOrRaw(root), resolved);
48
+ if (rel === "" || rel.startsWith(".."))
49
+ return false;
50
+ return rel.split(sep).includes(".git");
51
+ }
52
+ /**
53
+ * Absolute, symlink-resolved form of `candidate` if it lies inside `root`,
54
+ * is not itself (and does not lie under) `.git/`, and is not one of nax's own
55
+ * config files.
56
+ *
57
+ * The single containment seam. Multi-root support (a future configurable
58
+ * extension) changes this function and nothing else, which is why every tool
59
+ * receives an already-resolved path rather than resolving one itself. The
60
+ * `.git/` exclusion lives here for the same reason: it applies to every
61
+ * path-bearing tool uniformly, reads included, rather than being
62
+ * re-remembered per tool -- which is exactly the failure mode a declaration
63
+ * on `ToolScope` would reintroduce (nax#1943). `.git/` sits INSIDE the root,
64
+ * so containment alone never bars it, and an unconditional ("*") grant --
65
+ * what every non-Exec tool gets under the default `unrestricted` profile --
66
+ * skips glob matching entirely, so nothing downstream of this function would
67
+ * catch it either.
68
+ *
69
+ * Pre-single-frame, this seam carried one exception: an `execTouchedPaths` set
70
+ * (Task 10) admitted a workspace install's repo-ROOT manifest/lockfile even
71
+ * though it sat outside the containment root, which was then the story's
72
+ * package dir. The root move made the containment root the repo root, so the
73
+ * manifest is in-root by construction and that carve-out was retired
74
+ * (PR2/Task 13). There is no exception to this seam now.
75
+ */
76
+ export function resolveWithin(root, candidate) {
77
+ const absolute = isAbsolute(candidate) ? candidate : resolve(root, candidate);
78
+ if (isInside(root, absolute)) {
79
+ const resolved = realOrRaw(absolute);
80
+ if (entersGitMetadata(root, resolved) || isNaxConfigFile(root, resolved))
81
+ return null;
82
+ return resolved;
83
+ }
84
+ return null;
85
+ }
86
+ /**
87
+ * The reason text for a path `resolveWithin` refused (fix round 1, Task 10;
88
+ * `.git/` case added for nax#1943).
89
+ *
90
+ * A bare "resolves outside the permitted root" taught the model nothing
91
+ * the last time this shape of denial mattered: in the run that motivated
92
+ * this whole feature, that message is what led an agent to delete a
93
+ * tsconfig entry instead of installing the package it needed. The design's
94
+ * own rule is that a denial returns the reason AND what would have been
95
+ * allowed.
96
+ *
97
+ * A `.git/`-metadata refusal gets its own branch, checked first: unlike
98
+ * every other refusal this function handles, the candidate is genuinely
99
+ * INSIDE the root, so "resolves outside the permitted root" would be
100
+ * actively misleading rather than merely unhelpful.
101
+ *
102
+ * Every other refused path keeps the plain message. The GitCommit-specific
103
+ * manifest/lockfile message and its `isKnownManifestOrLockfileName` table
104
+ * were retired with the `execTouchedPaths` carve-out (PR2/Task 13): that
105
+ * message existed only to explain the carve-out's rule, and post root move
106
+ * a repo-root manifest is inside the root by construction, so there is no
107
+ * distinct rule left to explain.
108
+ *
109
+ * This must never get chattier for ordinary containment denials, and must
110
+ * never reveal repository structure for a path the model never touched.
111
+ *
112
+ * The root itself IS named, deliberately. The rule above -- never reveal
113
+ * repository structure -- is about paths the model never touched; this path is
114
+ * one the model just passed, and telling it where the boundary is is the
115
+ * difference between "adapt" and "work around". The dispatch preamble
116
+ * (src/prompts/sections/agent-scope.ts) states the same boundary only as a
117
+ * package-relative label (`packages/api`), never as an absolute path, so this
118
+ * message is where the agent first sees the absolute containment root -- and
119
+ * naming a path the model itself handed in is the right disclosure.
120
+ */
121
+ function outOfRootReason(root, candidate) {
122
+ const absolute = isAbsolute(candidate) ? candidate : resolve(root, candidate);
123
+ if (isInside(root, absolute) && isNaxConfigFile(root, realOrRaw(absolute))) {
124
+ return ("is one of nax's own config files, which every tool is refused regardless of grant -- " +
125
+ "`quality.commands` and `acceptance.command` are run through a shell WITHOUT passing the " +
126
+ "permission gate because a human wrote them, so editing this file is a route to running " +
127
+ "an ungated command on the next run");
128
+ }
129
+ if (isInside(root, absolute) && entersGitMetadata(root, realOrRaw(absolute))) {
130
+ return ("targets git metadata under .git/, which every tool is refused regardless of grant -- " +
131
+ "writing there can corrupt the repository beyond git's own recovery, and reading its " +
132
+ "config is a route to influencing what nax executes without passing through Exec (nax#1943)");
133
+ }
134
+ return `resolves outside the permitted root (${root}), which is the only directory this tool can reach`;
135
+ }
136
+ /**
137
+ * The path globs in a grant, which for a verb-gated tool means the patterns
138
+ * that are not verb names.
139
+ *
140
+ * A grant list is overloaded -- verbs for Git, path globs for Write -- so
141
+ * matching every pattern against a path denied everything for Git, and
142
+ * matching none left its paths bounded by the root alone. `allowedVerbs` is a
143
+ * closed set the tool declares, so the two kinds separate without guessing.
144
+ */
145
+ function pathMatchers(matchers, scope) {
146
+ const verbs = scope.allowedVerbs;
147
+ return verbs === undefined ? matchers : matchers.filter((m) => !verbs.includes(m.source));
148
+ }
149
+ /** Canonical repo-root-relative spelling grant globs and rules are matched against. */
150
+ function repoRelative(resolvedRoot, resolved) {
151
+ return relative(resolvedRoot, resolved).split(sep).join("/");
152
+ }
153
+ /**
154
+ * A path spelled with the confined prefix the tool is already bound to
155
+ * (`.nax/scratchpad/spill/x.txt`) has it removed before `resolveWithin`
156
+ * runs, so both spellings reach the same file. This is a prefix REMOVAL,
157
+ * not a second containment root: what is left is still resolved inside
158
+ * the effective root, so a `..` after the prefix remains a breach.
159
+ */
160
+ function stripConfinePrefix(confinePrefix, value) {
161
+ return confinePrefix !== "" && value.startsWith(confinePrefix) ? value.slice(confinePrefix.length) : value;
162
+ }
163
+ /**
164
+ * Resolve one candidate inside the effective root, then evaluate
165
+ * deny -> ask -> glob -> record. Returns a verdict to stop the walk with, or
166
+ * undefined when the candidate is admitted and the walk continues.
167
+ * `enforceGlobs` is the CALLER's glob predicate (`enforcePathGlobs` for string
168
+ * fields, `restrictPaths` for array/ref fields — see the module comment).
169
+ */
170
+ function checkPathSegment(frame, candidate, enforceGlobs) {
171
+ const resolved = resolveWithin(frame.effectiveRoot, stripConfinePrefix(frame.confinePrefix, candidate.path));
172
+ if (resolved === null) {
173
+ return frame.ctx.deny(`${candidate.denialSubject} ${outOfRootReason(frame.effectiveRoot, candidate.outOfRootTarget)}`, true);
174
+ }
175
+ const rel = repoRelative(frame.ctx.resolvedRoot, resolved);
176
+ const ruleDenial = frame.ctx.applyPathRules(frame.tool, rel, frame.state);
177
+ if (ruleDenial !== undefined)
178
+ return ruleDenial;
179
+ if (enforceGlobs && !matchesAny(frame.globs, rel)) {
180
+ return frame.ctx.deny(`${frame.tool} is not granted "${rel}" for this stage`);
181
+ }
182
+ frame.resolvedPaths.push(resolved);
183
+ return undefined;
184
+ }
185
+ /** `scope.pathFields`: each field holds ONE path, checked as a whole. */
186
+ function runPathFields(frame) {
187
+ for (const field of frame.scope.pathFields) {
188
+ const value = pathFieldValue(frame.input, field);
189
+ if (value === undefined)
190
+ continue;
191
+ if (typeof value !== "string")
192
+ return frame.ctx.deny(`"${field}" must be a string path`);
193
+ const denial = checkPathSegment(frame, { path: value, denialSubject: `path "${value}"`, outOfRootTarget: value }, frame.enforcePathGlobs);
194
+ if (denial !== undefined)
195
+ return denial;
196
+ }
197
+ return undefined;
198
+ }
199
+ /** `scope.listPathFields`: each field holds a whitespace-separated list, or an array, of paths. */
200
+ function runListPathFields(frame) {
201
+ for (const field of frame.scope.listPathFields ?? []) {
202
+ const value = pathFieldValue(frame.input, field);
203
+ if (value === undefined)
204
+ continue;
205
+ const elements = typeof value === "string"
206
+ ? pathListElements(value, frame.effectiveRoot)
207
+ : Array.isArray(value) && value.every((element) => typeof element === "string")
208
+ ? value
209
+ : null;
210
+ if (elements === null)
211
+ return frame.ctx.deny(`"${field}" must be a string path or an array of string paths`);
212
+ for (const element of elements) {
213
+ const denial = checkPathSegment(frame, { path: element, denialSubject: `path "${element}"`, outOfRootTarget: element }, frame.enforcePathGlobs);
214
+ if (denial !== undefined)
215
+ return denial;
216
+ }
217
+ }
218
+ return undefined;
219
+ }
220
+ /** `scope.arrayPathFields`: each field holds an array whose every element is a path. */
221
+ function runArrayPathFields(frame) {
222
+ for (const field of frame.scope.arrayPathFields ?? []) {
223
+ const values = frame.input[field];
224
+ if (values === undefined)
225
+ continue;
226
+ if (!Array.isArray(values))
227
+ return frame.ctx.deny(`"${field}" must be an array of string paths`);
228
+ for (const value of values) {
229
+ if (typeof value !== "string")
230
+ return frame.ctx.deny(`"${field}" entries must be strings`);
231
+ const denial = checkPathSegment(frame, { path: value, denialSubject: `"${field}" entry "${value}"`, outOfRootTarget: value }, frame.restrictPaths);
232
+ if (denial !== undefined)
233
+ return denial;
234
+ }
235
+ }
236
+ return undefined;
237
+ }
238
+ /** `scope.refPathFields`: each field holds an array of refs that MAY carry a `<rev>:<path>` path half. */
239
+ function runRefPathFields(frame) {
240
+ for (const field of frame.scope.refPathFields ?? []) {
241
+ const values = frame.input[field];
242
+ if (values === undefined)
243
+ continue;
244
+ if (!Array.isArray(values))
245
+ return frame.ctx.deny(`"${field}" must be an array of string refs`);
246
+ for (const value of values) {
247
+ if (typeof value !== "string")
248
+ return frame.ctx.deny(`"${field}" entries must be strings`);
249
+ const colonAt = value.indexOf(":");
250
+ if (colonAt === -1)
251
+ continue; // pure revision, no path to check
252
+ const candidatePath = value.slice(colonAt + 1);
253
+ if (candidatePath === "")
254
+ continue; // e.g. "HEAD:" — no path to check
255
+ const denial = checkPathSegment(frame, { path: candidatePath, denialSubject: `"${field}" entry "${value}"`, outOfRootTarget: candidatePath }, frame.restrictPaths);
256
+ if (denial !== undefined)
257
+ return denial;
258
+ }
259
+ }
260
+ return undefined;
261
+ }
262
+ /**
263
+ * Containment runs before any pattern matching and wins over everything.
264
+ * Each resolved path is then evaluated deny -> ask -> allow. A verb-only
265
+ * grant declares no path globs, leaving the root as the only bound --
266
+ * unchanged behaviour, now an authoring choice rather than something the
267
+ * grant syntax could not express.
268
+ *
269
+ * `confineTo` (tool-declared, see `ToolScope`) shifts the root passed to
270
+ * `resolveWithin` from `<root>` to `<root>/<confineTo>`: containment stays
271
+ * the one seam, and only its ROOT changes. `relativeTo` keeps rooting at
272
+ * `resolvedRoot` so grant globs, deny rules and `naxOwnedWriteRefusal`
273
+ * continue to see the canonical repo-root-relative spelling -- authors
274
+ * write `.nax/scratchpad/**`, never `**`, regardless of `confineTo`.
275
+ *
276
+ * `confineTo` is bound to stay INSIDE `resolvedRoot`: an authoring typo of
277
+ * `..` or `../shared` would otherwise widen the containment root past the
278
+ * policy boundary and re-scope `resolveWithin`'s `.git/`-metadata and
279
+ * `isNaxConfigFile` protections to a root that no longer aligns with the
280
+ * segments those checks assume -- the repo's own `.nax/config.json` would
281
+ stop being segment-matched against `.nax`. The boundary is the policy
282
+ root's invariant, so an out-of-root confineTo refuses the call outright
283
+ rather than silently widening.
284
+ */
285
+ export function pathsBranch(args) {
286
+ const { ctx, tool, scope, input, grant, state } = args;
287
+ const globs = pathMatchers(grant.matchers, scope);
288
+ let effectiveRoot = ctx.resolvedRoot;
289
+ if (scope.confineTo !== undefined) {
290
+ effectiveRoot = realOrRaw(join(ctx.resolvedRoot, scope.confineTo));
291
+ if (!isInside(ctx.resolvedRoot, effectiveRoot)) {
292
+ return ctx.deny(`${tool} declares confineTo "${scope.confineTo}" which resolves outside the policy root "${ctx.resolvedRoot}" -- confineTo must be a path INSIDE the policy root, never one that widens it`);
293
+ }
294
+ }
295
+ const frame = {
296
+ ctx,
297
+ tool,
298
+ scope,
299
+ input,
300
+ globs,
301
+ effectiveRoot,
302
+ state,
303
+ resolvedPaths: [],
304
+ confinePrefix: scope.confineTo === undefined ? "" : `${scope.confineTo}/`,
305
+ enforcePathGlobs: !grant.unconditional,
306
+ restrictPaths: !grant.unconditional && globs.length > 0,
307
+ };
308
+ const denial = runPathFields(frame) ?? runListPathFields(frame) ?? runArrayPathFields(frame) ?? runRefPathFields(frame);
309
+ if (denial !== undefined)
310
+ return denial;
311
+ return state.ask === undefined
312
+ ? { allowed: true, resolvedPaths: frame.resolvedPaths }
313
+ : ctx.askVerdict(frame.resolvedPaths, state.ask);
314
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Compile declarative grants into a policy, and answer one call at a time.
3
+ *
4
+ * nax-permission-mode-allow: applies grants resolved by resolvePermissions;
5
+ * decides no permission of its own.
6
+ *
7
+ * Containment runs BEFORE pattern matching and is not expressible in config:
8
+ * the root is a boundary no PROFILE can widen. `unrestricted` means "any
9
+ * tool, any path within the root", never "any path on the machine".
10
+ *
11
+ * The `execTouchedPaths` carve-out (Task 10) was retired in the single-frame
12
+ * root move (PR2/Task 13): the containment root became the repo root, so a
13
+ * workspace install's root manifest is in-root by construction and no
14
+ * exception to the boundary is needed.
15
+ */
16
+ import type { BashApprovalMode } from "#src/config/bash-approval";
17
+ import { resolveWithin } from "./policy-paths-branch.ts";
18
+ import { type ToolGrant, type ToolPolicy } from "./types.ts";
19
+ /** The single containment seam now lives beside the path branch that drives it. */
20
+ export { resolveWithin };
21
+ export interface ToolPolicyOptions {
22
+ /**
23
+ * Stage deny rules (spec R6). Same {tool, patterns} shape as grants; matched
24
+ * per branch and evaluated BEFORE ask and allow. A tool's unconditional
25
+ * deny rule also de-advertises it (see `grantedTools`).
26
+ */
27
+ readonly denyRules?: readonly ToolGrant[];
28
+ /**
29
+ * Stage ask rules (spec R1/R6). An ask match does not grant or refuse on its
30
+ * own: it marks a call the stage already granted as needing approval. An ask
31
+ * on an ungranted call is a plain denial.
32
+ */
33
+ readonly askRules?: readonly ToolGrant[];
34
+ /**
35
+ * The ONE nax-owned path this session may write despite `naxOwnedWriteRefusal`
36
+ * (nax#2115): the ABSOLUTE `fileOutput` path the dispatching op declared. It is
37
+ * canonicalised into this policy's own root-relative frame below rather than by
38
+ * the caller, so alternate spellings of the same file cannot diverge from the
39
+ * string the guard compares. Only the feature-PRD refusal honours it; the nax
40
+ * CONFIG refusal is deliberately not exempted.
41
+ */
42
+ readonly ownedWriteExemption?: string;
43
+ /** nax#2260: `execution.sandbox.filesystem.allowWrite`; a listed top-level `.nax/` entry becomes writable. */
44
+ readonly naxAllowWrite?: readonly string[];
45
+ /**
46
+ * How a bash command string is adjudicated (ADR-030). Absent means `gated` —
47
+ * today's behaviour — so every caller that does not opt in is unchanged.
48
+ *
49
+ * `raw` is a COMPILE-TIME input rather than a post-check transform on
50
+ * purpose: `checkBashCommand` lexes before it evaluates grants, so a `Bash(*)`
51
+ * grant cannot produce pass-through, and a post-check deny→allow would widen
52
+ * genuine containment denials too.
53
+ */
54
+ readonly bashApproval?: BashApprovalMode;
55
+ /**
56
+ * P4: set when `execution.sandbox.enabled` is true and the probe found the
57
+ * sandbox unavailable. Under `raw`, every Bash call is then denied with this
58
+ * reason -- raw requires the sandbox once it is enabled, and a silent
59
+ * fallback to unsandboxed raw would be a posture downgrade (spec S5).
60
+ * Ignored under gated/escalate, which run unwrapped with a warning.
61
+ */
62
+ readonly rawBashRefusal?: string;
63
+ readonly sandboxWrapped?: boolean;
64
+ }
65
+ export declare function compileToolPolicy(grants: readonly ToolGrant[], root: string, options?: ToolPolicyOptions): ToolPolicy;