apex-code 0.0.1-alpha.9 → 0.0.4

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 (513) hide show
  1. package/CHANGELOG.md +150 -2
  2. package/README.md +9 -8
  3. package/dist/cli/args.d.ts +25 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +43 -1
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/mcp-command.d.ts +9 -0
  8. package/dist/cli/mcp-command.d.ts.map +1 -0
  9. package/dist/cli/mcp-command.js +53 -0
  10. package/dist/cli/mcp-command.js.map +1 -0
  11. package/dist/cli/project-trust.d.ts.map +1 -1
  12. package/dist/cli/project-trust.js +3 -1
  13. package/dist/cli/project-trust.js.map +1 -1
  14. package/dist/cli/startup-ui.d.ts.map +1 -1
  15. package/dist/cli/startup-ui.js +2 -1
  16. package/dist/cli/startup-ui.js.map +1 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +94 -5
  19. package/dist/cli.js.map +1 -1
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +24 -5
  22. package/dist/config.js.map +1 -1
  23. package/dist/core/agent-session-services.d.ts +3 -0
  24. package/dist/core/agent-session-services.d.ts.map +1 -1
  25. package/dist/core/agent-session-services.js +1 -0
  26. package/dist/core/agent-session-services.js.map +1 -1
  27. package/dist/core/agent-session.d.ts +157 -8
  28. package/dist/core/agent-session.d.ts.map +1 -1
  29. package/dist/core/agent-session.js +565 -22
  30. package/dist/core/agent-session.js.map +1 -1
  31. package/dist/core/bash-executor.d.ts.map +1 -1
  32. package/dist/core/bash-executor.js +7 -0
  33. package/dist/core/bash-executor.js.map +1 -1
  34. package/dist/core/checkpoints/git-checkpoints.d.ts +39 -0
  35. package/dist/core/checkpoints/git-checkpoints.d.ts.map +1 -0
  36. package/dist/core/checkpoints/git-checkpoints.js +251 -0
  37. package/dist/core/checkpoints/git-checkpoints.js.map +1 -0
  38. package/dist/core/checkpoints/session-checkpoints.d.ts +23 -0
  39. package/dist/core/checkpoints/session-checkpoints.d.ts.map +1 -0
  40. package/dist/core/checkpoints/session-checkpoints.js +36 -0
  41. package/dist/core/checkpoints/session-checkpoints.js.map +1 -0
  42. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  43. package/dist/core/compaction/branch-summarization.js +4 -3
  44. package/dist/core/compaction/branch-summarization.js.map +1 -1
  45. package/dist/core/compaction/compaction.d.ts +5 -0
  46. package/dist/core/compaction/compaction.d.ts.map +1 -1
  47. package/dist/core/compaction/compaction.js +19 -5
  48. package/dist/core/compaction/compaction.js.map +1 -1
  49. package/dist/core/delegation/runtime.d.ts +6 -5
  50. package/dist/core/delegation/runtime.d.ts.map +1 -1
  51. package/dist/core/delegation/runtime.js +4 -3
  52. package/dist/core/delegation/runtime.js.map +1 -1
  53. package/dist/core/exec.d.ts.map +1 -1
  54. package/dist/core/exec.js +5 -2
  55. package/dist/core/exec.js.map +1 -1
  56. package/dist/core/extensions/index.d.ts +1 -1
  57. package/dist/core/extensions/index.d.ts.map +1 -1
  58. package/dist/core/extensions/index.js.map +1 -1
  59. package/dist/core/extensions/loader.d.ts.map +1 -1
  60. package/dist/core/extensions/loader.js +8 -2
  61. package/dist/core/extensions/loader.js.map +1 -1
  62. package/dist/core/extensions/runner.d.ts +5 -0
  63. package/dist/core/extensions/runner.d.ts.map +1 -1
  64. package/dist/core/extensions/runner.js +45 -1
  65. package/dist/core/extensions/runner.js.map +1 -1
  66. package/dist/core/extensions/source-runtime.d.ts +14 -0
  67. package/dist/core/extensions/source-runtime.d.ts.map +1 -0
  68. package/dist/core/extensions/source-runtime.js +56 -0
  69. package/dist/core/extensions/source-runtime.js.map +1 -0
  70. package/dist/core/extensions/types.d.ts +22 -2
  71. package/dist/core/extensions/types.d.ts.map +1 -1
  72. package/dist/core/extensions/types.js.map +1 -1
  73. package/dist/core/formatter-lifecycle.d.ts +69 -0
  74. package/dist/core/formatter-lifecycle.d.ts.map +1 -0
  75. package/dist/core/formatter-lifecycle.js +360 -0
  76. package/dist/core/formatter-lifecycle.js.map +1 -0
  77. package/dist/core/hooks/command-handler.d.ts +19 -0
  78. package/dist/core/hooks/command-handler.d.ts.map +1 -0
  79. package/dist/core/hooks/command-handler.js +112 -0
  80. package/dist/core/hooks/command-handler.js.map +1 -0
  81. package/dist/core/hooks/http-handler.d.ts +10 -0
  82. package/dist/core/hooks/http-handler.d.ts.map +1 -0
  83. package/dist/core/hooks/http-handler.js +36 -0
  84. package/dist/core/hooks/http-handler.js.map +1 -0
  85. package/dist/core/hooks/loader.d.ts +29 -0
  86. package/dist/core/hooks/loader.d.ts.map +1 -0
  87. package/dist/core/hooks/loader.js +139 -0
  88. package/dist/core/hooks/loader.js.map +1 -0
  89. package/dist/core/hooks/runtime.d.ts +13 -0
  90. package/dist/core/hooks/runtime.d.ts.map +1 -0
  91. package/dist/core/hooks/runtime.js +63 -0
  92. package/dist/core/hooks/runtime.js.map +1 -0
  93. package/dist/core/hooks/types.d.ts +83 -0
  94. package/dist/core/hooks/types.d.ts.map +1 -0
  95. package/dist/core/hooks/types.js +24 -0
  96. package/dist/core/hooks/types.js.map +1 -0
  97. package/dist/core/mcp/config.d.ts +16 -0
  98. package/dist/core/mcp/config.d.ts.map +1 -0
  99. package/dist/core/mcp/config.js +166 -0
  100. package/dist/core/mcp/config.js.map +1 -0
  101. package/dist/core/mcp/connector.d.ts +35 -0
  102. package/dist/core/mcp/connector.d.ts.map +1 -0
  103. package/dist/core/mcp/connector.js +105 -0
  104. package/dist/core/mcp/connector.js.map +1 -0
  105. package/dist/core/mcp/contract.d.ts +19 -0
  106. package/dist/core/mcp/contract.d.ts.map +1 -0
  107. package/dist/core/mcp/contract.js +89 -0
  108. package/dist/core/mcp/contract.js.map +1 -0
  109. package/dist/core/mcp/mcp-tool.d.ts +32 -0
  110. package/dist/core/mcp/mcp-tool.d.ts.map +1 -0
  111. package/dist/core/mcp/mcp-tool.js +171 -0
  112. package/dist/core/mcp/mcp-tool.js.map +1 -0
  113. package/dist/core/mcp/metadata-cache.d.ts +29 -0
  114. package/dist/core/mcp/metadata-cache.d.ts.map +1 -0
  115. package/dist/core/mcp/metadata-cache.js +99 -0
  116. package/dist/core/mcp/metadata-cache.js.map +1 -0
  117. package/dist/core/mcp/oauth/authorize.d.ts +21 -0
  118. package/dist/core/mcp/oauth/authorize.d.ts.map +1 -0
  119. package/dist/core/mcp/oauth/authorize.js +32 -0
  120. package/dist/core/mcp/oauth/authorize.js.map +1 -0
  121. package/dist/core/mcp/oauth/discover.d.ts +27 -0
  122. package/dist/core/mcp/oauth/discover.d.ts.map +1 -0
  123. package/dist/core/mcp/oauth/discover.js +116 -0
  124. package/dist/core/mcp/oauth/discover.js.map +1 -0
  125. package/dist/core/mcp/oauth/flow.d.ts +31 -0
  126. package/dist/core/mcp/oauth/flow.d.ts.map +1 -0
  127. package/dist/core/mcp/oauth/flow.js +175 -0
  128. package/dist/core/mcp/oauth/flow.js.map +1 -0
  129. package/dist/core/mcp/oauth/mcp-token.d.ts +51 -0
  130. package/dist/core/mcp/oauth/mcp-token.d.ts.map +1 -0
  131. package/dist/core/mcp/oauth/mcp-token.js +129 -0
  132. package/dist/core/mcp/oauth/mcp-token.js.map +1 -0
  133. package/dist/core/mcp/oauth/pkce.d.ts +12 -0
  134. package/dist/core/mcp/oauth/pkce.d.ts.map +1 -0
  135. package/dist/core/mcp/oauth/pkce.js +11 -0
  136. package/dist/core/mcp/oauth/pkce.js.map +1 -0
  137. package/dist/core/mcp/oauth/register.d.ts +16 -0
  138. package/dist/core/mcp/oauth/register.d.ts.map +1 -0
  139. package/dist/core/mcp/oauth/register.js +42 -0
  140. package/dist/core/mcp/oauth/register.js.map +1 -0
  141. package/dist/core/mcp/oauth/token-client.d.ts +33 -0
  142. package/dist/core/mcp/oauth/token-client.d.ts.map +1 -0
  143. package/dist/core/mcp/oauth/token-client.js +65 -0
  144. package/dist/core/mcp/oauth/token-client.js.map +1 -0
  145. package/dist/core/mcp/runtime.d.ts +20 -0
  146. package/dist/core/mcp/runtime.d.ts.map +1 -0
  147. package/dist/core/mcp/runtime.js +37 -0
  148. package/dist/core/mcp/runtime.js.map +1 -0
  149. package/dist/core/mcp/schema.d.ts +22 -0
  150. package/dist/core/mcp/schema.d.ts.map +1 -0
  151. package/dist/core/mcp/schema.js +20 -0
  152. package/dist/core/mcp/schema.js.map +1 -0
  153. package/dist/core/mcp/server-manager.d.ts +43 -0
  154. package/dist/core/mcp/server-manager.d.ts.map +1 -0
  155. package/dist/core/mcp/server-manager.js +142 -0
  156. package/dist/core/mcp/server-manager.js.map +1 -0
  157. package/dist/core/mcp/types.d.ts +100 -0
  158. package/dist/core/mcp/types.d.ts.map +1 -0
  159. package/dist/core/mcp/types.js +8 -0
  160. package/dist/core/mcp/types.js.map +1 -0
  161. package/dist/core/permissions/gate.d.ts.map +1 -1
  162. package/dist/core/permissions/gate.js +45 -4
  163. package/dist/core/permissions/gate.js.map +1 -1
  164. package/dist/core/permissions/operations.d.ts +20 -0
  165. package/dist/core/permissions/operations.d.ts.map +1 -0
  166. package/dist/core/permissions/operations.js +9 -0
  167. package/dist/core/permissions/operations.js.map +1 -0
  168. package/dist/core/permissions/policy-command.d.ts +42 -0
  169. package/dist/core/permissions/policy-command.d.ts.map +1 -0
  170. package/dist/core/permissions/policy-command.js +44 -0
  171. package/dist/core/permissions/policy-command.js.map +1 -0
  172. package/dist/core/permissions/responder.d.ts +49 -2
  173. package/dist/core/permissions/responder.d.ts.map +1 -1
  174. package/dist/core/permissions/responder.js +20 -8
  175. package/dist/core/permissions/responder.js.map +1 -1
  176. package/dist/core/permissions/rules.d.ts.map +1 -1
  177. package/dist/core/permissions/rules.js +11 -1
  178. package/dist/core/permissions/rules.js.map +1 -1
  179. package/dist/core/permissions/store.d.ts +18 -0
  180. package/dist/core/permissions/store.d.ts.map +1 -1
  181. package/dist/core/permissions/store.js +89 -8
  182. package/dist/core/permissions/store.js.map +1 -1
  183. package/dist/core/policy-executor.d.ts +47 -0
  184. package/dist/core/policy-executor.d.ts.map +1 -0
  185. package/dist/core/policy-executor.js +228 -0
  186. package/dist/core/policy-executor.js.map +1 -0
  187. package/dist/core/policy-loader.d.ts +72 -0
  188. package/dist/core/policy-loader.d.ts.map +1 -0
  189. package/dist/core/policy-loader.js +195 -0
  190. package/dist/core/policy-loader.js.map +1 -0
  191. package/dist/core/project-trust.d.ts +1 -1
  192. package/dist/core/project-trust.d.ts.map +1 -1
  193. package/dist/core/project-trust.js.map +1 -1
  194. package/dist/core/sandbox/bwrap-arguments.d.ts +41 -0
  195. package/dist/core/sandbox/bwrap-arguments.d.ts.map +1 -0
  196. package/dist/core/sandbox/bwrap-arguments.js +136 -0
  197. package/dist/core/sandbox/bwrap-arguments.js.map +1 -0
  198. package/dist/core/sandbox/child-entry.d.ts.map +1 -1
  199. package/dist/core/sandbox/child-entry.js +3 -1
  200. package/dist/core/sandbox/child-entry.js.map +1 -1
  201. package/dist/core/sandbox/cli-launch.d.ts +84 -4
  202. package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
  203. package/dist/core/sandbox/cli-launch.js +115 -16
  204. package/dist/core/sandbox/cli-launch.js.map +1 -1
  205. package/dist/core/sandbox/cli-supervisor.d.ts +2 -0
  206. package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
  207. package/dist/core/sandbox/cli-supervisor.js +50 -2
  208. package/dist/core/sandbox/cli-supervisor.js.map +1 -1
  209. package/dist/core/sandbox/full-access.d.ts +28 -0
  210. package/dist/core/sandbox/full-access.d.ts.map +1 -0
  211. package/dist/core/sandbox/full-access.js +63 -0
  212. package/dist/core/sandbox/full-access.js.map +1 -0
  213. package/dist/core/sandbox/git-identity.d.ts +57 -0
  214. package/dist/core/sandbox/git-identity.d.ts.map +1 -0
  215. package/dist/core/sandbox/git-identity.js +78 -0
  216. package/dist/core/sandbox/git-identity.js.map +1 -0
  217. package/dist/core/sandbox/host-approval.d.ts +42 -0
  218. package/dist/core/sandbox/host-approval.d.ts.map +1 -0
  219. package/dist/core/sandbox/host-approval.js +99 -0
  220. package/dist/core/sandbox/host-approval.js.map +1 -0
  221. package/dist/core/sandbox/linux-backend.d.ts +14 -0
  222. package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
  223. package/dist/core/sandbox/linux-backend.js +158 -80
  224. package/dist/core/sandbox/linux-backend.js.map +1 -1
  225. package/dist/core/sandbox/macos-backend.d.ts +5 -0
  226. package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
  227. package/dist/core/sandbox/macos-backend.js +164 -5
  228. package/dist/core/sandbox/macos-backend.js.map +1 -1
  229. package/dist/core/sandbox/network-proxy.d.ts +19 -0
  230. package/dist/core/sandbox/network-proxy.d.ts.map +1 -1
  231. package/dist/core/sandbox/network-proxy.js +93 -19
  232. package/dist/core/sandbox/network-proxy.js.map +1 -1
  233. package/dist/core/sandbox/policy.d.ts +8 -0
  234. package/dist/core/sandbox/policy.d.ts.map +1 -1
  235. package/dist/core/sandbox/policy.js +29 -12
  236. package/dist/core/sandbox/policy.js.map +1 -1
  237. package/dist/core/sandbox/profiles.d.ts +25 -0
  238. package/dist/core/sandbox/profiles.d.ts.map +1 -0
  239. package/dist/core/sandbox/profiles.js +23 -0
  240. package/dist/core/sandbox/profiles.js.map +1 -0
  241. package/dist/core/sandbox/rpc/command-client.d.ts +28 -0
  242. package/dist/core/sandbox/rpc/command-client.d.ts.map +1 -0
  243. package/dist/core/sandbox/rpc/command-client.js +86 -0
  244. package/dist/core/sandbox/rpc/command-client.js.map +1 -0
  245. package/dist/core/sandbox/rpc/command-proxy.d.ts +48 -0
  246. package/dist/core/sandbox/rpc/command-proxy.d.ts.map +1 -0
  247. package/dist/core/sandbox/rpc/command-proxy.js +120 -0
  248. package/dist/core/sandbox/rpc/command-proxy.js.map +1 -0
  249. package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -1
  250. package/dist/core/sandbox/rpc/credential-proxy.js +2 -96
  251. package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -1
  252. package/dist/core/sandbox/rpc/framing.d.ts +35 -0
  253. package/dist/core/sandbox/rpc/framing.d.ts.map +1 -0
  254. package/dist/core/sandbox/rpc/framing.js +114 -0
  255. package/dist/core/sandbox/rpc/framing.js.map +1 -0
  256. package/dist/core/sandbox/rpc/git-credential-helper.d.ts +47 -0
  257. package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +1 -0
  258. package/dist/core/sandbox/rpc/git-credential-helper.js +222 -0
  259. package/dist/core/sandbox/rpc/git-credential-helper.js.map +1 -0
  260. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +66 -0
  261. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +1 -0
  262. package/dist/core/sandbox/rpc/git-credential-proxy.js +209 -0
  263. package/dist/core/sandbox/rpc/git-credential-proxy.js.map +1 -0
  264. package/dist/core/sandbox/supervisor-temp.d.ts +18 -0
  265. package/dist/core/sandbox/supervisor-temp.d.ts.map +1 -0
  266. package/dist/core/sandbox/supervisor-temp.js +21 -0
  267. package/dist/core/sandbox/supervisor-temp.js.map +1 -0
  268. package/dist/core/sandbox/supervisor.d.ts +12 -1
  269. package/dist/core/sandbox/supervisor.d.ts.map +1 -1
  270. package/dist/core/sandbox/supervisor.js +15 -1
  271. package/dist/core/sandbox/supervisor.js.map +1 -1
  272. package/dist/core/sandbox/terminal-handoff.d.ts +68 -0
  273. package/dist/core/sandbox/terminal-handoff.d.ts.map +1 -0
  274. package/dist/core/sandbox/terminal-handoff.js +221 -0
  275. package/dist/core/sandbox/terminal-handoff.js.map +1 -0
  276. package/dist/core/sandbox/terminal-size.d.ts +13 -0
  277. package/dist/core/sandbox/terminal-size.d.ts.map +1 -1
  278. package/dist/core/sandbox/terminal-size.js +23 -2
  279. package/dist/core/sandbox/terminal-size.js.map +1 -1
  280. package/dist/core/sdk.d.ts +22 -0
  281. package/dist/core/sdk.d.ts.map +1 -1
  282. package/dist/core/sdk.js +53 -1
  283. package/dist/core/sdk.js.map +1 -1
  284. package/dist/core/session-manager.d.ts +13 -1
  285. package/dist/core/session-manager.d.ts.map +1 -1
  286. package/dist/core/session-manager.js +32 -5
  287. package/dist/core/session-manager.js.map +1 -1
  288. package/dist/core/settings-manager.d.ts +163 -1
  289. package/dist/core/settings-manager.d.ts.map +1 -1
  290. package/dist/core/settings-manager.js +107 -0
  291. package/dist/core/settings-manager.js.map +1 -1
  292. package/dist/core/slash-commands.d.ts.map +1 -1
  293. package/dist/core/slash-commands.js +2 -0
  294. package/dist/core/slash-commands.js.map +1 -1
  295. package/dist/core/tools/background-shell.d.ts +39 -0
  296. package/dist/core/tools/background-shell.d.ts.map +1 -0
  297. package/dist/core/tools/background-shell.js +81 -0
  298. package/dist/core/tools/background-shell.js.map +1 -0
  299. package/dist/core/tools/bash-command-segments.d.ts.map +1 -1
  300. package/dist/core/tools/bash-command-segments.js +2 -0
  301. package/dist/core/tools/bash-command-segments.js.map +1 -1
  302. package/dist/core/tools/bash.d.ts +30 -15
  303. package/dist/core/tools/bash.d.ts.map +1 -1
  304. package/dist/core/tools/bash.js +228 -12
  305. package/dist/core/tools/bash.js.map +1 -1
  306. package/dist/core/tools/contract-snapshot.d.ts +58 -0
  307. package/dist/core/tools/contract-snapshot.d.ts.map +1 -0
  308. package/dist/core/tools/contract-snapshot.js +51 -0
  309. package/dist/core/tools/contract-snapshot.js.map +1 -0
  310. package/dist/core/tools/contract.d.ts +53 -5
  311. package/dist/core/tools/contract.d.ts.map +1 -1
  312. package/dist/core/tools/contract.js +22 -2
  313. package/dist/core/tools/contract.js.map +1 -1
  314. package/dist/core/tools/delegate.d.ts +6 -4
  315. package/dist/core/tools/delegate.d.ts.map +1 -1
  316. package/dist/core/tools/delegate.js +21 -11
  317. package/dist/core/tools/delegate.js.map +1 -1
  318. package/dist/core/tools/edit-diff.d.ts +48 -0
  319. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  320. package/dist/core/tools/edit-diff.js +215 -3
  321. package/dist/core/tools/edit-diff.js.map +1 -1
  322. package/dist/core/tools/edit.d.ts +8 -0
  323. package/dist/core/tools/edit.d.ts.map +1 -1
  324. package/dist/core/tools/edit.js +53 -7
  325. package/dist/core/tools/edit.js.map +1 -1
  326. package/dist/core/tools/find.d.ts.map +1 -1
  327. package/dist/core/tools/find.js +5 -2
  328. package/dist/core/tools/find.js.map +1 -1
  329. package/dist/core/tools/grep.d.ts.map +1 -1
  330. package/dist/core/tools/grep.js +5 -2
  331. package/dist/core/tools/grep.js.map +1 -1
  332. package/dist/core/tools/index.d.ts +10 -1
  333. package/dist/core/tools/index.d.ts.map +1 -1
  334. package/dist/core/tools/index.js +3 -0
  335. package/dist/core/tools/index.js.map +1 -1
  336. package/dist/core/tools/ls.d.ts.map +1 -1
  337. package/dist/core/tools/ls.js +5 -2
  338. package/dist/core/tools/ls.js.map +1 -1
  339. package/dist/core/tools/lsp.d.ts +19 -1
  340. package/dist/core/tools/lsp.d.ts.map +1 -1
  341. package/dist/core/tools/lsp.js +163 -10
  342. package/dist/core/tools/lsp.js.map +1 -1
  343. package/dist/core/tools/path-permission.d.ts +3 -0
  344. package/dist/core/tools/path-permission.d.ts.map +1 -1
  345. package/dist/core/tools/path-permission.js +16 -4
  346. package/dist/core/tools/path-permission.js.map +1 -1
  347. package/dist/core/tools/path-utils.d.ts +27 -2
  348. package/dist/core/tools/path-utils.d.ts.map +1 -1
  349. package/dist/core/tools/path-utils.js +221 -4
  350. package/dist/core/tools/path-utils.js.map +1 -1
  351. package/dist/core/tools/read.d.ts.map +1 -1
  352. package/dist/core/tools/read.js +20 -11
  353. package/dist/core/tools/read.js.map +1 -1
  354. package/dist/core/tools/test.d.ts +54 -9
  355. package/dist/core/tools/test.d.ts.map +1 -1
  356. package/dist/core/tools/test.js +239 -19
  357. package/dist/core/tools/test.js.map +1 -1
  358. package/dist/core/tools/write.d.ts +7 -0
  359. package/dist/core/tools/write.d.ts.map +1 -1
  360. package/dist/core/tools/write.js +64 -9
  361. package/dist/core/tools/write.js.map +1 -1
  362. package/dist/core/verification-lifecycle.d.ts +77 -0
  363. package/dist/core/verification-lifecycle.d.ts.map +1 -0
  364. package/dist/core/verification-lifecycle.js +146 -0
  365. package/dist/core/verification-lifecycle.js.map +1 -0
  366. package/dist/core/workspace/artifacts.d.ts +45 -0
  367. package/dist/core/workspace/artifacts.d.ts.map +1 -0
  368. package/dist/core/workspace/artifacts.js +165 -0
  369. package/dist/core/workspace/artifacts.js.map +1 -0
  370. package/dist/core/workspace/comparison.d.ts +27 -0
  371. package/dist/core/workspace/comparison.d.ts.map +1 -0
  372. package/dist/core/workspace/comparison.js +94 -0
  373. package/dist/core/workspace/comparison.js.map +1 -0
  374. package/dist/core/workspace/git-observer.d.ts +48 -0
  375. package/dist/core/workspace/git-observer.d.ts.map +1 -0
  376. package/dist/core/workspace/git-observer.js +346 -0
  377. package/dist/core/workspace/git-observer.js.map +1 -0
  378. package/dist/core/workspace/projection.d.ts +3 -0
  379. package/dist/core/workspace/projection.d.ts.map +1 -0
  380. package/dist/core/workspace/projection.js +56 -0
  381. package/dist/core/workspace/projection.js.map +1 -0
  382. package/dist/core/workspace/state.d.ts +133 -0
  383. package/dist/core/workspace/state.d.ts.map +1 -0
  384. package/dist/core/workspace/state.js +96 -0
  385. package/dist/core/workspace/state.js.map +1 -0
  386. package/dist/extensions/llama/client.d.ts +6 -0
  387. package/dist/extensions/llama/client.d.ts.map +1 -1
  388. package/dist/extensions/llama/client.js +7 -0
  389. package/dist/extensions/llama/client.js.map +1 -1
  390. package/dist/extensions/llama/provider.d.ts +3 -1
  391. package/dist/extensions/llama/provider.d.ts.map +1 -1
  392. package/dist/extensions/llama/provider.js +27 -6
  393. package/dist/extensions/llama/provider.js.map +1 -1
  394. package/dist/index.d.ts +4 -1
  395. package/dist/index.d.ts.map +1 -1
  396. package/dist/index.js +3 -0
  397. package/dist/index.js.map +1 -1
  398. package/dist/main.d.ts +3 -1
  399. package/dist/main.d.ts.map +1 -1
  400. package/dist/main.js +41 -9
  401. package/dist/main.js.map +1 -1
  402. package/dist/modes/acp/server.d.ts +55 -0
  403. package/dist/modes/acp/server.d.ts.map +1 -0
  404. package/dist/modes/acp/server.js +244 -0
  405. package/dist/modes/acp/server.js.map +1 -0
  406. package/dist/modes/acp/translate.d.ts +37 -0
  407. package/dist/modes/acp/translate.d.ts.map +1 -0
  408. package/dist/modes/acp/translate.js +50 -0
  409. package/dist/modes/acp/translate.js.map +1 -0
  410. package/dist/modes/index.d.ts +1 -0
  411. package/dist/modes/index.d.ts.map +1 -1
  412. package/dist/modes/index.js +1 -0
  413. package/dist/modes/index.js.map +1 -1
  414. package/dist/modes/interactive/components/assistant-message.d.ts +29 -0
  415. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  416. package/dist/modes/interactive/components/assistant-message.js +182 -23
  417. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  418. package/dist/modes/interactive/components/bash-execution.d.ts +7 -0
  419. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  420. package/dist/modes/interactive/components/bash-execution.js +20 -0
  421. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  422. package/dist/modes/interactive/components/custom-editor.d.ts +9 -4
  423. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  424. package/dist/modes/interactive/components/custom-editor.js +20 -9
  425. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  426. package/dist/modes/interactive/components/extension-selector.d.ts +7 -1
  427. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  428. package/dist/modes/interactive/components/extension-selector.js +7 -3
  429. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  430. package/dist/modes/interactive/components/footer.d.ts +19 -0
  431. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  432. package/dist/modes/interactive/components/footer.js +70 -10
  433. package/dist/modes/interactive/components/footer.js.map +1 -1
  434. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  435. package/dist/modes/interactive/components/model-selector.js +6 -6
  436. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  437. package/dist/modes/interactive/components/permission-preview.d.ts +15 -0
  438. package/dist/modes/interactive/components/permission-preview.d.ts.map +1 -0
  439. package/dist/modes/interactive/components/permission-preview.js +33 -0
  440. package/dist/modes/interactive/components/permission-preview.js.map +1 -0
  441. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  442. package/dist/modes/interactive/components/session-selector.js +2 -2
  443. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  444. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  445. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  446. package/dist/modes/interactive/components/settings-selector.js +10 -0
  447. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  448. package/dist/modes/interactive/components/tool-execution.d.ts +23 -1
  449. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  450. package/dist/modes/interactive/components/tool-execution.js +64 -10
  451. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  452. package/dist/modes/interactive/interactive-mode.d.ts +34 -0
  453. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  454. package/dist/modes/interactive/interactive-mode.js +231 -26
  455. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  456. package/dist/modes/interactive/theme/theme.d.ts +12 -0
  457. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  458. package/dist/modes/interactive/theme/theme.js +15 -5
  459. package/dist/modes/interactive/theme/theme.js.map +1 -1
  460. package/dist/modes/print-mode.d.ts.map +1 -1
  461. package/dist/modes/print-mode.js +27 -12
  462. package/dist/modes/print-mode.js.map +1 -1
  463. package/dist/modes/rpc/rpc-client.d.ts +7 -0
  464. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  465. package/dist/modes/rpc/rpc-client.js +7 -0
  466. package/dist/modes/rpc/rpc-client.js.map +1 -1
  467. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  468. package/dist/modes/rpc/rpc-mode.js +3 -0
  469. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  470. package/dist/modes/rpc/rpc-types.d.ts +12 -0
  471. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  472. package/dist/modes/rpc/rpc-types.js.map +1 -1
  473. package/dist/package-manager-cli.d.ts.map +1 -1
  474. package/dist/package-manager-cli.js +0 -6
  475. package/dist/package-manager-cli.js.map +1 -1
  476. package/dist/utils/shell.d.ts.map +1 -1
  477. package/dist/utils/shell.js +6 -4
  478. package/dist/utils/shell.js.map +1 -1
  479. package/dist/utils/version-check.d.ts.map +1 -1
  480. package/dist/utils/version-check.js +6 -4
  481. package/dist/utils/version-check.js.map +1 -1
  482. package/docs/compaction.md +4 -0
  483. package/docs/custom-provider.md +7 -7
  484. package/docs/environment-variables.md +1 -1
  485. package/docs/extensions.md +18 -0
  486. package/docs/keybindings.md +1 -1
  487. package/docs/quickstart.md +1 -1
  488. package/docs/rpc.md +23 -0
  489. package/docs/sdk.md +25 -0
  490. package/docs/settings.md +12 -4
  491. package/docs/terminal-setup.md +12 -0
  492. package/docs/usage.md +4 -3
  493. package/examples/extensions/built-in-tool-renderer.ts +8 -4
  494. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  495. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  496. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  497. package/examples/extensions/git-checkpoint.ts +24 -36
  498. package/examples/extensions/gondolin/package-lock.json +2 -2
  499. package/examples/extensions/gondolin/package.json +1 -1
  500. package/examples/extensions/minimal-mode.ts +2 -2
  501. package/examples/extensions/permission-gate.ts +1 -1
  502. package/examples/extensions/plan-mode/index.ts +1 -1
  503. package/examples/extensions/rpc-demo.ts +1 -1
  504. package/examples/extensions/sandbox/package-lock.json +2 -2
  505. package/examples/extensions/sandbox/package.json +1 -1
  506. package/examples/extensions/with-deps/package-lock.json +2 -2
  507. package/examples/extensions/with-deps/package.json +1 -1
  508. package/npm-shrinkwrap.json +194 -46
  509. package/package.json +14 -12
  510. package/dist/server/create-harness.d.ts +0 -26
  511. package/dist/server/create-harness.d.ts.map +0 -1
  512. package/dist/server/create-harness.js +0 -107
  513. package/dist/server/create-harness.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/core/permissions/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,EAAE,KAAK,kBAAkB,EAA0B,MAAM,oBAAoB,CAAC;AAErF,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnE,eAAO,MAAM,gBAAgB,6EAA8E,CAAC;AAC5G,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D;;mDAEmD;AACnD,eAAO,MAAM,2BAA2B,6DAA8D,CAAC;AACvG,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpF,QAAA,MAAM,mBAAmB,uCAAwC,CAAC;AAClE,KAAK,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAK7D,kGAAgG;AAChG,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;AAElE,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,gBAAgB,GACzB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,wBAAwB,CAAC;IAAC,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAA;CAAE,GACnG;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,WAAW,EAAE,wBAAwB,CAAC;IAAC,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAA;CAAE,GACvG;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE,wBAAwB,CAAC;IAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAA;CAAE,GAChG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,WAAW,EAAE,wBAAwB,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC;AAEpF,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC;CACb;AAED,MAAM,WAAW,uBAAuB;IACvC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IACjC,aAAa,EAAE,WAAW,CAAC,wBAAwB,EAAE,cAAc,CAAC,CAAC;IACrE,MAAM,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,IAAI,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC7C,KAAK,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AA4ED,MAAM,WAAW,oCAAoC;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uHAAuH;IACvH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4GAA4G;IAC5G,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,CAAC,CAAC;IACjE,2FAA2F;IAC3F,YAAY,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CACzC;AAED,sFAAsF;AACtF,qBAAa,uBAAwB,YAAW,mBAAmB;IAClE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA+C;IACxE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA4B;IACzD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA+E;IAC5G,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsD;IAEnF,YAAY,OAAO,EAAE,oCAAoC,EAWxD;YAEa,mBAAmB;YAenB,UAAU;IAUlB,QAAQ,IAAI,OAAO,CAAC,uBAAuB,CAAC,CAkCjD;IAEK,KAAK,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CASnD;IAED,OAAO,CAAC,YAAY;CAQpB;AAED,MAAM,WAAW,iCAAiC;IACjD;;6CAEyC;IACzC,MAAM,EAAE,mBAAmB,CAAC;CAC5B;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,0BAA2B,YAAW,mBAAmB;IACrE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA+E;IAC5G,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsD;IAEnF,YAAY,OAAO,EAAE,iCAAiC,EAErD;IAEK,QAAQ,IAAI,OAAO,CAAC,uBAAuB,CAAC,CAWjD;IAEK,KAAK,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAenD;CACD","sourcesContent":["/**\n * Persistence for the permission rule model (ADR 0004). Four of the eight sources\n * are file-backed (policy, local, project, user); the other four are runtime-only\n * (flag, cliArg — parsed once from argv, not stored here; command, session — held\n * in memory for the life of the process). `session`-source updates are never\n * written to disk: \"allow for this session\" dies with the session by design.\n *\n * Reuses the locked-JSON-file backend already proven for Phase 1's operational\n * state (FileAuthStorageBackend) rather than widening `SettingsScope`, which only\n * covers two of these four file-backed sources and represents a different, larger\n * settings domain.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { type AuthStorageBackend, FileAuthStorageBackend } from \"../auth-storage.ts\";\nimport type { PermissionBehavior } from \"../tools/contract.ts\";\nimport type { PermissionRule, PermissionSource } from \"./rules.ts\";\n\nexport const PERMISSION_MODES = [\"default\", \"plan\", \"acceptEdits\", \"bypassPermissions\", \"dontAsk\"] as const;\nexport type PermissionMode = (typeof PERMISSION_MODES)[number];\n\n/** Sources a PermissionUpdate may target. Excludes `policy` (administrator-managed,\n * never user-writable) and `flag`/`cliArg` (parsed once from argv at startup, not\n * something this store updates after the fact). */\nexport const WRITABLE_PERMISSION_SOURCES = [\"local\", \"project\", \"user\", \"command\", \"session\"] as const;\nexport type WritablePermissionSource = (typeof WRITABLE_PERMISSION_SOURCES)[number];\n\nconst FILE_BACKED_SOURCES = [\"local\", \"project\", \"user\"] as const;\ntype FileBackedSource = (typeof FILE_BACKED_SOURCES)[number];\n\nconst RUNTIME_SOURCES = [\"command\", \"session\"] as const;\ntype RuntimeSource = (typeof RUNTIME_SOURCES)[number];\n\n/** A rule as persisted: `source` is never stored — it is implied by which file it came from. */\nexport type StoredPermissionRule = Omit<PermissionRule, \"source\">;\n\nexport interface RuleMatcher {\n\ttoolName: string;\n\truleContent?: string;\n}\n\nexport type PermissionUpdate =\n\t| { type: \"addRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"replaceRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"removeRules\"; destination: WritablePermissionSource; matching: readonly RuleMatcher[] }\n\t| { type: \"setMode\"; destination: WritablePermissionSource; mode: PermissionMode };\n\nexport interface PermissionStoreError {\n\tsource: PermissionSource;\n\terror: Error;\n}\n\nexport interface PermissionStoreSnapshot {\n\trules: readonly PermissionRule[];\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>;\n\terrors: readonly PermissionStoreError[];\n}\n\nexport interface PermissionRuleStore {\n\tsnapshot(): Promise<PermissionStoreSnapshot>;\n\tapply(update: PermissionUpdate): Promise<void>;\n}\n\nconst STORE_VERSION = 1;\n\ninterface StoredPermissionScope {\n\tversion: number;\n\trules: StoredPermissionRule[];\n\tmode?: PermissionMode;\n}\n\nfunction emptyScope(): StoredPermissionScope {\n\treturn { version: STORE_VERSION, rules: [] };\n}\n\nfunction isPermissionBehavior(value: unknown): value is PermissionBehavior {\n\treturn value === \"allow\" || value === \"deny\" || value === \"ask\";\n}\n\nfunction parseScope(content: string | undefined): StoredPermissionScope {\n\tif (!content) return emptyScope();\n\tconst parsed = JSON.parse(content);\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\t// FileAuthStorageBackend (shared with auth.json) seeds a freshly created file\n\t// with the literal text \"{}\". Treat a content-free object as \"nothing written\n\t// yet\" rather than malformed; a real malformed file has some recognizable but\n\t// wrong-shaped field, caught below.\n\tif (!(\"version\" in parsed) && !(\"rules\" in parsed) && !(\"mode\" in parsed)) {\n\t\treturn emptyScope();\n\t}\n\tif (\n\t\tparsed.version !== STORE_VERSION ||\n\t\t(parsed.mode !== undefined && !(PERMISSION_MODES as readonly string[]).includes(parsed.mode)) ||\n\t\t!Array.isArray(parsed.rules) ||\n\t\t!parsed.rules.every(\n\t\t\t(rule: unknown) =>\n\t\t\t\ttypeof rule === \"object\" &&\n\t\t\t\trule !== null &&\n\t\t\t\ttypeof (rule as StoredPermissionRule).toolName === \"string\" &&\n\t\t\t\tisPermissionBehavior((rule as StoredPermissionRule).behavior) &&\n\t\t\t\t((rule as StoredPermissionRule).ruleContent === undefined ||\n\t\t\t\t\ttypeof (rule as StoredPermissionRule).ruleContent === \"string\"),\n\t\t)\n\t) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\treturn parsed as StoredPermissionScope;\n}\n\nfunction applyToScope(scope: StoredPermissionScope, update: PermissionUpdate): StoredPermissionScope {\n\tswitch (update.type) {\n\t\tcase \"addRules\":\n\t\t\treturn { ...scope, rules: [...scope.rules, ...update.rules] };\n\t\tcase \"replaceRules\":\n\t\t\treturn { ...scope, rules: [...update.rules] };\n\t\tcase \"removeRules\":\n\t\t\treturn {\n\t\t\t\t...scope,\n\t\t\t\trules: scope.rules.filter(\n\t\t\t\t\t(rule) =>\n\t\t\t\t\t\t!update.matching.some((m) => m.toolName === rule.toolName && m.ruleContent === rule.ruleContent),\n\t\t\t\t),\n\t\t\t};\n\t\tcase \"setMode\":\n\t\t\treturn { ...scope, mode: update.mode };\n\t}\n}\n\nfunction defaultPolicyPath(): string {\n\tif (process.env.APEX_CODE_POLICY_PATH) return process.env.APEX_CODE_POLICY_PATH;\n\treturn process.platform === \"win32\"\n\t\t? join(process.env.ProgramData ?? \"C:\\\\ProgramData\", \"apex-code\", \"policy.json\")\n\t\t: \"/etc/apex-code/policy.json\";\n}\n\nexport interface CreateFilePermissionRuleStoreOptions {\n\tcwd: string;\n\tagentDir?: string;\n\t/** Overrides the managed policy file path. Defaults to $APEX_CODE_POLICY_PATH, else an OS-specific system location. */\n\tpolicyPath?: string;\n\t/** Test seam: inject backends for the three writable file-backed sources instead of touching real files. */\n\tbackends?: Partial<Record<FileBackedSource, AuthStorageBackend>>;\n\t/** Immutable argv layers (`flag` / `cliArg`), validated by the CLI before construction. */\n\tinitialRules?: readonly PermissionRule[];\n}\n\n/** File-backed store for policy/local/project/user, in-memory for command/session. */\nexport class FilePermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly backends: Record<FileBackedSource, AuthStorageBackend>;\n\tprivate readonly policyPath: string;\n\tprivate readonly initialRules: readonly PermissionRule[];\n\tprivate readonly runtimeRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly runtimeModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\n\tconstructor(options: CreateFilePermissionRuleStoreOptions) {\n\t\tconst agentDir = options.agentDir ?? getAgentDir();\n\t\tconst cwd = resolvePath(options.cwd);\n\t\tthis.backends = {\n\t\t\tuser: new FileAuthStorageBackend(join(agentDir, \"permissions.json\")),\n\t\t\tproject: new FileAuthStorageBackend(join(cwd, \".apex-code\", \"permissions.json\")),\n\t\t\tlocal: new FileAuthStorageBackend(join(cwd, \".apex-code\", \"permissions.local.json\")),\n\t\t\t...options.backends,\n\t\t};\n\t\tthis.policyPath = options.policyPath ?? defaultPolicyPath();\n\t\tthis.initialRules = options.initialRules ?? [];\n\t}\n\n\tprivate async readFileBackedScope(\n\t\tsource: FileBackedSource,\n\t): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tlet content: string | undefined;\n\t\t\tawait this.backends[source].withLockAsync(async (current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate async readPolicy(): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tconst content = await readFile(this.policyPath, \"utf-8\");\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { scope: emptyScope() };\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\tconst [policy, local, project, user] = await Promise.all([\n\t\t\tthis.readPolicy(),\n\t\t\tthis.readFileBackedScope(\"local\"),\n\t\t\tthis.readFileBackedScope(\"project\"),\n\t\t\tthis.readFileBackedScope(\"user\"),\n\t\t]);\n\t\tconst errors: PermissionStoreError[] = [];\n\t\tconst withSource = (\n\t\t\tsource: PermissionSource,\n\t\t\tentry: { scope: StoredPermissionScope; error?: Error },\n\t\t): PermissionRule[] => {\n\t\t\tif (entry.error) errors.push({ source, error: entry.error });\n\t\t\treturn entry.scope.rules.map((rule) => ({ ...rule, source }));\n\t\t};\n\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...withSource(\"policy\", policy),\n\t\t\t...this.initialRules,\n\t\t\t...withSource(\"local\", local),\n\t\t\t...withSource(\"project\", project),\n\t\t\t...withSource(\"user\", user),\n\t\t\t...this.runtimeRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.runtimeRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\n\t\tconst modesBySource = new Map<WritablePermissionSource, PermissionMode>();\n\t\tif (local.scope.mode) modesBySource.set(\"local\", local.scope.mode);\n\t\tif (project.scope.mode) modesBySource.set(\"project\", project.scope.mode);\n\t\tif (user.scope.mode) modesBySource.set(\"user\", user.scope.mode);\n\t\tif (this.runtimeModes.command) modesBySource.set(\"command\", this.runtimeModes.command);\n\t\tif (this.runtimeModes.session) modesBySource.set(\"session\", this.runtimeModes.session);\n\n\t\treturn { rules, modesBySource, errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination === \"command\" || update.destination === \"session\") {\n\t\t\tthis.applyRuntime(update.destination, update);\n\t\t\treturn;\n\t\t}\n\t\tawait this.backends[update.destination].withLockAsync(async (content) => {\n\t\t\tconst next = applyToScope(parseScope(content), update);\n\t\t\treturn { result: undefined, next: JSON.stringify(next, null, 2) };\n\t\t});\n\t}\n\n\tprivate applyRuntime(destination: RuntimeSource, update: PermissionUpdate): void {\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.runtimeModes[destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = { version: STORE_VERSION, rules: this.runtimeRules[destination] };\n\t\tthis.runtimeRules[destination] = applyToScope(current, update).rules;\n\t}\n}\n\nexport interface DerivedPermissionRuleStoreOptions {\n\t/** The parent's live store. Read fresh on every `snapshot()`, never cached, so a\n\t * rule the parent picks up mid-session (e.g. a human's \"always allow\") is visible\n\t * to the child on its very next call. */\n\tparent: PermissionRuleStore;\n}\n\n/**\n * A delegated child's permission store (roadmap Phase 5, ADR 0008). A read-through\n * view over the parent's live snapshot -- including its runtime-only sources\n * (`flag`, `cliArg`, `command`, `session`), which a subprocess-spawned child could\n * never observe -- plus a child-local runtime overlay for the child's own writes.\n *\n * `apply()` never reaches the parent. It writes only into this instance's own\n * overlay, and only for the two runtime-only writable sources (`command`, `session`);\n * a file-backed destination (`local`, `project`, `user`) is rejected outright, not\n * silently redirected -- a delegated child must never be able to persist to the\n * parent's settings files, and \"runtime-only\" is enforced by construction here, not\n * by a convention a future caller could forget. The overlay is discarded with the\n * child: there is no code path from a child's approval back into the parent's store,\n * which is what keeps a child's `persist: true` from widening its parent.\n */\nexport class DerivedPermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly parent: PermissionRuleStore;\n\tprivate readonly overlayRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly overlayModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\n\tconstructor(options: DerivedPermissionRuleStoreOptions) {\n\t\tthis.parent = options.parent;\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\tconst parentSnapshot = await this.parent.snapshot();\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...parentSnapshot.rules,\n\t\t\t...this.overlayRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.overlayRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\t\tconst modesBySource = new Map(parentSnapshot.modesBySource);\n\t\tif (this.overlayModes.command !== undefined) modesBySource.set(\"command\", this.overlayModes.command);\n\t\tif (this.overlayModes.session !== undefined) modesBySource.set(\"session\", this.overlayModes.session);\n\t\treturn { rules, modesBySource, errors: parentSnapshot.errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination !== \"command\" && update.destination !== \"session\") {\n\t\t\tthrow new Error(\n\t\t\t\t`A delegated child cannot persist permission rules to \"${update.destination}\" -- only \"command\" and \"session\" are writable from a derived store (ADR 0008).`,\n\t\t\t);\n\t\t}\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.overlayModes[update.destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = {\n\t\t\tversion: STORE_VERSION,\n\t\t\trules: this.overlayRules[update.destination],\n\t\t};\n\t\tthis.overlayRules[update.destination] = applyToScope(current, update).rules;\n\t}\n}\n"]}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/core/permissions/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Persistence for the permission rule model (ADR 0004). Four of the eight sources\n * are file-backed (policy, local, project, user); the other four are runtime-only\n * (flag, cliArg — parsed once from argv, not stored here; command, session — held\n * in memory for the life of the process). `session`-source updates are never\n * written to disk: \"allow for this session\" dies with the session by design.\n *\n * Reuses the locked-JSON-file backend already proven for Phase 1's operational\n * state (FileAuthStorageBackend) rather than widening `SettingsScope`, which only\n * covers two of these four file-backed sources and represents a different, larger\n * settings domain.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { type AuthStorageBackend, FileAuthStorageBackend } from \"../auth-storage.ts\";\nimport { POLICY_SNAPSHOT_PATH_VARIABLE } from \"../sandbox/cli-launch.ts\";\nimport type { PermissionBehavior } from \"../tools/contract.ts\";\nimport type { PermissionRule, PermissionSource } from \"./rules.ts\";\n\nexport const PERMISSION_MODES = [\"default\", \"plan\", \"acceptEdits\", \"bypassPermissions\", \"dontAsk\"] as const;\nexport type PermissionMode = (typeof PERMISSION_MODES)[number];\n\n/** Sources a PermissionUpdate may target. Excludes `policy` (administrator-managed,\n * never user-writable) and `flag`/`cliArg` (parsed once from argv at startup, not\n * something this store updates after the fact). */\nexport const WRITABLE_PERMISSION_SOURCES = [\"local\", \"project\", \"user\", \"command\", \"session\"] as const;\nexport type WritablePermissionSource = (typeof WRITABLE_PERMISSION_SOURCES)[number];\n\nconst FILE_BACKED_SOURCES = [\"local\", \"project\", \"user\"] as const;\ntype FileBackedSource = (typeof FILE_BACKED_SOURCES)[number];\n\nconst RUNTIME_SOURCES = [\"command\", \"session\"] as const;\ntype RuntimeSource = (typeof RUNTIME_SOURCES)[number];\n\n/** A rule as persisted: `source` is never stored — it is implied by which file it came from. */\nexport type StoredPermissionRule = Omit<PermissionRule, \"source\">;\n\nexport interface RuleMatcher {\n\ttoolName: string;\n\truleContent?: string;\n}\n\nexport type PermissionUpdate =\n\t| { type: \"addRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"replaceRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"removeRules\"; destination: WritablePermissionSource; matching: readonly RuleMatcher[] }\n\t| { type: \"setMode\"; destination: WritablePermissionSource; mode: PermissionMode };\n\nexport interface PermissionStoreError {\n\tsource: PermissionSource;\n\terror: Error;\n}\n\nexport interface PermissionStoreSnapshot {\n\trules: readonly PermissionRule[];\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>;\n\terrors: readonly PermissionStoreError[];\n}\n\nexport interface PermissionRuleStore {\n\tsnapshot(): Promise<PermissionStoreSnapshot>;\n\tapply(update: PermissionUpdate): Promise<void>;\n}\n\nconst STORE_VERSION = 1;\n\ninterface StoredPermissionScope {\n\tversion: number;\n\trules: StoredPermissionRule[];\n\tmode?: PermissionMode;\n}\n\nfunction emptyScope(): StoredPermissionScope {\n\treturn { version: STORE_VERSION, rules: [] };\n}\n\nfunction isPermissionBehavior(value: unknown): value is PermissionBehavior {\n\treturn value === \"allow\" || value === \"deny\" || value === \"ask\";\n}\n\nfunction parseScope(content: string | undefined): StoredPermissionScope {\n\tif (!content) return emptyScope();\n\tconst parsed = JSON.parse(content);\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\t// FileAuthStorageBackend (shared with auth.json) seeds a freshly created file\n\t// with the literal text \"{}\". Treat a content-free object as \"nothing written\n\t// yet\" rather than malformed; a real malformed file has some recognizable but\n\t// wrong-shaped field, caught below.\n\tif (!(\"version\" in parsed) && !(\"rules\" in parsed) && !(\"mode\" in parsed)) {\n\t\treturn emptyScope();\n\t}\n\tif (\n\t\tparsed.version !== STORE_VERSION ||\n\t\t(parsed.mode !== undefined && !(PERMISSION_MODES as readonly string[]).includes(parsed.mode)) ||\n\t\t!Array.isArray(parsed.rules) ||\n\t\t!parsed.rules.every(\n\t\t\t(rule: unknown) =>\n\t\t\t\ttypeof rule === \"object\" &&\n\t\t\t\trule !== null &&\n\t\t\t\ttypeof (rule as StoredPermissionRule).toolName === \"string\" &&\n\t\t\t\tisPermissionBehavior((rule as StoredPermissionRule).behavior) &&\n\t\t\t\t((rule as StoredPermissionRule).ruleContent === undefined ||\n\t\t\t\t\ttypeof (rule as StoredPermissionRule).ruleContent === \"string\"),\n\t\t)\n\t) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\treturn parsed as StoredPermissionScope;\n}\n\nfunction applyToScope(scope: StoredPermissionScope, update: PermissionUpdate): StoredPermissionScope {\n\tswitch (update.type) {\n\t\tcase \"addRules\":\n\t\t\treturn { ...scope, rules: [...scope.rules, ...update.rules] };\n\t\tcase \"replaceRules\":\n\t\t\treturn { ...scope, rules: [...update.rules] };\n\t\tcase \"removeRules\":\n\t\t\treturn {\n\t\t\t\t...scope,\n\t\t\t\trules: scope.rules.filter(\n\t\t\t\t\t(rule) =>\n\t\t\t\t\t\t!update.matching.some((m) => m.toolName === rule.toolName && m.ruleContent === rule.ruleContent),\n\t\t\t\t),\n\t\t\t};\n\t\tcase \"setMode\":\n\t\t\treturn { ...scope, mode: update.mode };\n\t}\n}\n\nfunction defaultPolicyPath(): string {\n\tif (process.env.APEX_CODE_POLICY_PATH) return process.env.APEX_CODE_POLICY_PATH;\n\treturn process.platform === \"win32\"\n\t\t? join(process.env.ProgramData ?? \"C:\\\\ProgramData\", \"apex-code\", \"policy.json\")\n\t\t: \"/etc/apex-code/policy.json\";\n}\n\n/**\n * The supervisor's captured host `policy` and `user` scopes (PS.3).\n *\n * Inside a sandboxed child neither source is reachable at its own path: the child's\n * environment allowlist removes `APEX_CODE_POLICY_PATH`, and `APEX_CODE_CODING_AGENT_DIR`\n * points at a directory inside the workspace that the child itself may write. Reading both\n * from one supervisor-written file outside every writable root is what makes the child's\n * effective authority equal the supervisor's, rather than equal to whatever the child last\n * wrote. Named only by `core/sandbox/cli-launch.ts`, which sets the variable on the\n * launch's own environment and keeps it out of the child-environment allowlist.\n */\n/** Raw file text per source, so the child validates exactly what the host would have. */\ninterface PolicySnapshotFile {\n\tversion: number;\n\tpolicy: string | null;\n\tuser: string | null;\n}\n\nfunction parseSnapshotFile(content: string): PolicySnapshotFile {\n\tconst parsed = JSON.parse(content);\n\tif (\n\t\ttypeof parsed !== \"object\" ||\n\t\tparsed === null ||\n\t\tArray.isArray(parsed) ||\n\t\tparsed.version !== 1 ||\n\t\t(parsed.policy !== null && typeof parsed.policy !== \"string\") ||\n\t\t(parsed.user !== null && typeof parsed.user !== \"string\")\n\t) {\n\t\tthrow new Error(\"Invalid permission snapshot file: unexpected shape\");\n\t}\n\treturn parsed as PolicySnapshotFile;\n}\n\nexport interface CreateFilePermissionRuleStoreOptions {\n\tcwd: string;\n\tagentDir?: string;\n\t/** Overrides the managed policy file path. Defaults to $APEX_CODE_POLICY_PATH, else an OS-specific system location. */\n\tpolicyPath?: string;\n\t/**\n\t * The supervisor's captured host `policy` and `user` scopes. Defaults to\n\t * $APEX_CODE_POLICY_SNAPSHOT_PATH, which only a sandbox supervisor sets. When present\n\t * it replaces both `policyPath` and the `user` file for reading; `apply()` still\n\t * writes a `user` update to this process's own agent directory, which is how an\n\t * in-session approval persists without the child's copy becoming an authority source.\n\t */\n\tpolicySnapshotPath?: string;\n\t/** Test seam: inject backends for the three writable file-backed sources instead of touching real files. */\n\tbackends?: Partial<Record<FileBackedSource, AuthStorageBackend>>;\n\t/** Immutable argv layers (`flag` / `cliArg`), validated by the CLI before construction. */\n\tinitialRules?: readonly PermissionRule[];\n\t/** Resolved before construction. False excludes both project-controlled scopes. */\n\tprojectTrusted?: boolean;\n}\n\n/** File-backed store for policy/local/project/user, in-memory for command/session. */\nexport class FilePermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly backends: Record<FileBackedSource, AuthStorageBackend>;\n\tprivate readonly policyPath: string;\n\tprivate readonly policySnapshotPath?: string;\n\tprivate readonly initialRules: readonly PermissionRule[];\n\tprivate readonly runtimeRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly runtimeModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\tprivate readonly projectTrusted: boolean;\n\tprivate readonly fileScopes: Partial<\n\t\tRecord<FileBackedSource, Promise<{ scope: StoredPermissionScope; error?: Error }>>\n\t> = {};\n\tprivate snapshotScope?: Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}>;\n\tprivate readonly fileScopeUpdates: Partial<Record<FileBackedSource, Promise<void>>> = {};\n\n\tconstructor(options: CreateFilePermissionRuleStoreOptions) {\n\t\tconst agentDir = options.agentDir ?? getAgentDir();\n\t\tconst cwd = resolvePath(options.cwd);\n\t\tthis.backends = {\n\t\t\tuser: new FileAuthStorageBackend(join(agentDir, \"permissions.json\")),\n\t\t\tproject: new FileAuthStorageBackend(join(cwd, \".apex-code\", \"permissions.json\")),\n\t\t\tlocal: new FileAuthStorageBackend(join(cwd, \".apex-code\", \"permissions.local.json\")),\n\t\t\t...options.backends,\n\t\t};\n\t\tthis.policyPath = options.policyPath ?? defaultPolicyPath();\n\t\tthis.policySnapshotPath = options.policySnapshotPath ?? process.env[POLICY_SNAPSHOT_PATH_VARIABLE];\n\t\tthis.initialRules = options.initialRules ?? [];\n\t\tthis.projectTrusted = options.projectTrusted ?? true;\n\t\tif (!this.projectTrusted) {\n\t\t\tthis.fileScopes.local = Promise.resolve({ scope: emptyScope() });\n\t\t\tthis.fileScopes.project = Promise.resolve({ scope: emptyScope() });\n\t\t}\n\t}\n\n\tprivate capturedFileBackedScope(source: FileBackedSource): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\tconst captured = this.fileScopes[source];\n\t\tif (captured) return captured;\n\t\tconst initial = this.readFileBackedScope(source);\n\t\tthis.fileScopes[source] = initial;\n\t\treturn initial;\n\t}\n\n\tprivate async readFileBackedScope(\n\t\tsource: FileBackedSource,\n\t): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tlet content: string | undefined;\n\t\t\tawait this.backends[source].withLockAsync(async (current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate async readPolicy(): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tconst content = await readFile(this.policyPath, \"utf-8\");\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { scope: emptyScope() };\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate async readPolicySnapshot(): Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}> {\n\t\ttry {\n\t\t\tconst snapshot = parseSnapshotFile(await readFile(this.policySnapshotPath as string, \"utf-8\"));\n\t\t\treturn {\n\t\t\t\tpolicy: { scope: parseScope(snapshot.policy ?? undefined) },\n\t\t\t\tuser: { scope: parseScope(snapshot.user ?? undefined) },\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tconst failure = error instanceof Error ? error : new Error(String(error));\n\t\t\treturn {\n\t\t\t\tpolicy: { scope: emptyScope(), error: failure },\n\t\t\t\tuser: { scope: emptyScope() },\n\t\t\t};\n\t\t}\n\t}\n\n\tprivate capturedPolicyAndUser(): Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}> {\n\t\tif (!this.policySnapshotPath) {\n\t\t\treturn Promise.all([this.readPolicy(), this.capturedFileBackedScope(\"user\")]).then(([policy, user]) => ({\n\t\t\t\tpolicy,\n\t\t\t\tuser,\n\t\t\t}));\n\t\t}\n\t\tthis.snapshotScope ??= this.readPolicySnapshot();\n\t\treturn this.snapshotScope;\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\t// File-backed authorization is captured once. Direct filesystem writes,\n\t\t// including symlink replacement, cannot widen the current or next gate\n\t\t// decision. Managed policy and runtime scopes remain live.\n\t\tconst [policyAndUser, local, project] = await Promise.all([\n\t\t\tthis.capturedPolicyAndUser(),\n\t\t\tthis.capturedFileBackedScope(\"local\"),\n\t\t\tthis.capturedFileBackedScope(\"project\"),\n\t\t]);\n\t\tconst { policy, user } = policyAndUser;\n\t\tconst errors: PermissionStoreError[] = [];\n\t\tconst withSource = (\n\t\t\tsource: PermissionSource,\n\t\t\tentry: { scope: StoredPermissionScope; error?: Error },\n\t\t): PermissionRule[] => {\n\t\t\tif (entry.error) errors.push({ source, error: entry.error });\n\t\t\treturn entry.scope.rules.map((rule) => ({ ...rule, source }));\n\t\t};\n\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...withSource(\"policy\", policy),\n\t\t\t...this.initialRules,\n\t\t\t...withSource(\"local\", local),\n\t\t\t...withSource(\"project\", project),\n\t\t\t...withSource(\"user\", user),\n\t\t\t...this.runtimeRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.runtimeRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\n\t\tconst modesBySource = new Map<WritablePermissionSource, PermissionMode>();\n\t\tif (local.scope.mode) modesBySource.set(\"local\", local.scope.mode);\n\t\tif (project.scope.mode) modesBySource.set(\"project\", project.scope.mode);\n\t\tif (user.scope.mode) modesBySource.set(\"user\", user.scope.mode);\n\t\tif (this.runtimeModes.command) modesBySource.set(\"command\", this.runtimeModes.command);\n\t\tif (this.runtimeModes.session) modesBySource.set(\"session\", this.runtimeModes.session);\n\n\t\treturn { rules, modesBySource, errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination === \"command\" || update.destination === \"session\") {\n\t\t\tthis.applyRuntime(update.destination, update);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.projectTrusted && (update.destination === \"local\" || update.destination === \"project\")) {\n\t\t\tthrow new Error(`Cannot update untrusted ${update.destination} permission scope`);\n\t\t}\n\t\tconst destination = update.destination;\n\t\tconst previousUpdate = this.fileScopeUpdates[destination]?.catch(() => undefined) ?? Promise.resolve();\n\t\tconst nextUpdate = previousUpdate.then(async () => {\n\t\t\tconst current =\n\t\t\t\tdestination === \"user\" && this.policySnapshotPath\n\t\t\t\t\t? (await this.capturedPolicyAndUser()).user\n\t\t\t\t\t: await this.capturedFileBackedScope(destination);\n\t\t\tconst next = applyToScope(current.scope, update);\n\t\t\tawait this.backends[destination].withLockAsync(async () => ({\n\t\t\t\tresult: undefined,\n\t\t\t\tnext: JSON.stringify(next, null, 2),\n\t\t\t}));\n\t\t\tthis.fileScopes[destination] = Promise.resolve({ scope: next });\n\t\t\tif (destination === \"user\" && this.policySnapshotPath) {\n\t\t\t\tthis.snapshotScope = this.capturedPolicyAndUser().then((snapshot) => ({\n\t\t\t\t\t...snapshot,\n\t\t\t\t\tuser: { scope: next },\n\t\t\t\t}));\n\t\t\t}\n\t\t});\n\t\tthis.fileScopeUpdates[destination] = nextUpdate;\n\t\tawait nextUpdate;\n\t}\n\n\tprivate applyRuntime(destination: RuntimeSource, update: PermissionUpdate): void {\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.runtimeModes[destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = { version: STORE_VERSION, rules: this.runtimeRules[destination] };\n\t\tthis.runtimeRules[destination] = applyToScope(current, update).rules;\n\t}\n}\n\nexport interface DerivedPermissionRuleStoreOptions {\n\t/** The parent's live store. Read fresh on every `snapshot()`, never cached, so a\n\t * rule the parent picks up mid-session (e.g. a human's \"always allow\") is visible\n\t * to the child on its very next call. */\n\tparent: PermissionRuleStore;\n}\n\n/**\n * A delegated child's permission store (roadmap Phase 5, ADR 0008). A read-through\n * view over the parent's live snapshot -- including its runtime-only sources\n * (`flag`, `cliArg`, `command`, `session`), which a subprocess-spawned child could\n * never observe -- plus a child-local runtime overlay for the child's own writes.\n *\n * `apply()` never reaches the parent. It writes only into this instance's own\n * overlay, and only for the two runtime-only writable sources (`command`, `session`);\n * a file-backed destination (`local`, `project`, `user`) is rejected outright, not\n * silently redirected -- a delegated child must never be able to persist to the\n * parent's settings files, and \"runtime-only\" is enforced by construction here, not\n * by a convention a future caller could forget. The overlay is discarded with the\n * child: there is no code path from a child's approval back into the parent's store,\n * which is what keeps a child's `persist: true` from widening its parent.\n */\nexport class DerivedPermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly parent: PermissionRuleStore;\n\tprivate readonly overlayRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly overlayModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\n\tconstructor(options: DerivedPermissionRuleStoreOptions) {\n\t\tthis.parent = options.parent;\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\tconst parentSnapshot = await this.parent.snapshot();\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...parentSnapshot.rules,\n\t\t\t...this.overlayRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.overlayRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\t\tconst modesBySource = new Map(parentSnapshot.modesBySource);\n\t\tif (this.overlayModes.command !== undefined) modesBySource.set(\"command\", this.overlayModes.command);\n\t\tif (this.overlayModes.session !== undefined) modesBySource.set(\"session\", this.overlayModes.session);\n\t\treturn { rules, modesBySource, errors: parentSnapshot.errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination !== \"command\" && update.destination !== \"session\") {\n\t\t\tthrow new Error(\n\t\t\t\t`A delegated child cannot persist permission rules to \"${update.destination}\" -- only \"command\" and \"session\" are writable from a derived store (ADR 0008).`,\n\t\t\t);\n\t\t}\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.overlayModes[update.destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = {\n\t\t\tversion: STORE_VERSION,\n\t\t\trules: this.overlayRules[update.destination],\n\t\t};\n\t\tthis.overlayRules[update.destination] = applyToScope(current, update).rules;\n\t}\n}\n"]}
@@ -15,6 +15,7 @@ import { join } from "node:path";
15
15
  import { getAgentDir } from "../../config.js";
16
16
  import { resolvePath } from "../../utils/paths.js";
17
17
  import { FileAuthStorageBackend } from "../auth-storage.js";
18
+ import { POLICY_SNAPSHOT_PATH_VARIABLE } from "../sandbox/cli-launch.js";
18
19
  export const PERMISSION_MODES = ["default", "plan", "acceptEdits", "bypassPermissions", "dontAsk"];
19
20
  /** Sources a PermissionUpdate may target. Excludes `policy` (administrator-managed,
20
21
  * never user-writable) and `flag`/`cliArg` (parsed once from argv at startup, not
@@ -78,13 +79,30 @@ function defaultPolicyPath() {
78
79
  ? join(process.env.ProgramData ?? "C:\\ProgramData", "apex-code", "policy.json")
79
80
  : "/etc/apex-code/policy.json";
80
81
  }
82
+ function parseSnapshotFile(content) {
83
+ const parsed = JSON.parse(content);
84
+ if (typeof parsed !== "object" ||
85
+ parsed === null ||
86
+ Array.isArray(parsed) ||
87
+ parsed.version !== 1 ||
88
+ (parsed.policy !== null && typeof parsed.policy !== "string") ||
89
+ (parsed.user !== null && typeof parsed.user !== "string")) {
90
+ throw new Error("Invalid permission snapshot file: unexpected shape");
91
+ }
92
+ return parsed;
93
+ }
81
94
  /** File-backed store for policy/local/project/user, in-memory for command/session. */
82
95
  export class FilePermissionRuleStore {
83
96
  backends;
84
97
  policyPath;
98
+ policySnapshotPath;
85
99
  initialRules;
86
100
  runtimeRules = { command: [], session: [] };
87
101
  runtimeModes = {};
102
+ projectTrusted;
103
+ fileScopes = {};
104
+ snapshotScope;
105
+ fileScopeUpdates = {};
88
106
  constructor(options) {
89
107
  const agentDir = options.agentDir ?? getAgentDir();
90
108
  const cwd = resolvePath(options.cwd);
@@ -95,7 +113,21 @@ export class FilePermissionRuleStore {
95
113
  ...options.backends,
96
114
  };
97
115
  this.policyPath = options.policyPath ?? defaultPolicyPath();
116
+ this.policySnapshotPath = options.policySnapshotPath ?? process.env[POLICY_SNAPSHOT_PATH_VARIABLE];
98
117
  this.initialRules = options.initialRules ?? [];
118
+ this.projectTrusted = options.projectTrusted ?? true;
119
+ if (!this.projectTrusted) {
120
+ this.fileScopes.local = Promise.resolve({ scope: emptyScope() });
121
+ this.fileScopes.project = Promise.resolve({ scope: emptyScope() });
122
+ }
123
+ }
124
+ capturedFileBackedScope(source) {
125
+ const captured = this.fileScopes[source];
126
+ if (captured)
127
+ return captured;
128
+ const initial = this.readFileBackedScope(source);
129
+ this.fileScopes[source] = initial;
130
+ return initial;
99
131
  }
100
132
  async readFileBackedScope(source) {
101
133
  try {
@@ -121,13 +153,42 @@ export class FilePermissionRuleStore {
121
153
  return { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };
122
154
  }
123
155
  }
156
+ async readPolicySnapshot() {
157
+ try {
158
+ const snapshot = parseSnapshotFile(await readFile(this.policySnapshotPath, "utf-8"));
159
+ return {
160
+ policy: { scope: parseScope(snapshot.policy ?? undefined) },
161
+ user: { scope: parseScope(snapshot.user ?? undefined) },
162
+ };
163
+ }
164
+ catch (error) {
165
+ const failure = error instanceof Error ? error : new Error(String(error));
166
+ return {
167
+ policy: { scope: emptyScope(), error: failure },
168
+ user: { scope: emptyScope() },
169
+ };
170
+ }
171
+ }
172
+ capturedPolicyAndUser() {
173
+ if (!this.policySnapshotPath) {
174
+ return Promise.all([this.readPolicy(), this.capturedFileBackedScope("user")]).then(([policy, user]) => ({
175
+ policy,
176
+ user,
177
+ }));
178
+ }
179
+ this.snapshotScope ??= this.readPolicySnapshot();
180
+ return this.snapshotScope;
181
+ }
124
182
  async snapshot() {
125
- const [policy, local, project, user] = await Promise.all([
126
- this.readPolicy(),
127
- this.readFileBackedScope("local"),
128
- this.readFileBackedScope("project"),
129
- this.readFileBackedScope("user"),
183
+ // File-backed authorization is captured once. Direct filesystem writes,
184
+ // including symlink replacement, cannot widen the current or next gate
185
+ // decision. Managed policy and runtime scopes remain live.
186
+ const [policyAndUser, local, project] = await Promise.all([
187
+ this.capturedPolicyAndUser(),
188
+ this.capturedFileBackedScope("local"),
189
+ this.capturedFileBackedScope("project"),
130
190
  ]);
191
+ const { policy, user } = policyAndUser;
131
192
  const errors = [];
132
193
  const withSource = (source, entry) => {
133
194
  if (entry.error)
@@ -161,10 +222,30 @@ export class FilePermissionRuleStore {
161
222
  this.applyRuntime(update.destination, update);
162
223
  return;
163
224
  }
164
- await this.backends[update.destination].withLockAsync(async (content) => {
165
- const next = applyToScope(parseScope(content), update);
166
- return { result: undefined, next: JSON.stringify(next, null, 2) };
225
+ if (!this.projectTrusted && (update.destination === "local" || update.destination === "project")) {
226
+ throw new Error(`Cannot update untrusted ${update.destination} permission scope`);
227
+ }
228
+ const destination = update.destination;
229
+ const previousUpdate = this.fileScopeUpdates[destination]?.catch(() => undefined) ?? Promise.resolve();
230
+ const nextUpdate = previousUpdate.then(async () => {
231
+ const current = destination === "user" && this.policySnapshotPath
232
+ ? (await this.capturedPolicyAndUser()).user
233
+ : await this.capturedFileBackedScope(destination);
234
+ const next = applyToScope(current.scope, update);
235
+ await this.backends[destination].withLockAsync(async () => ({
236
+ result: undefined,
237
+ next: JSON.stringify(next, null, 2),
238
+ }));
239
+ this.fileScopes[destination] = Promise.resolve({ scope: next });
240
+ if (destination === "user" && this.policySnapshotPath) {
241
+ this.snapshotScope = this.capturedPolicyAndUser().then((snapshot) => ({
242
+ ...snapshot,
243
+ user: { scope: next },
244
+ }));
245
+ }
167
246
  });
247
+ this.fileScopeUpdates[destination] = nextUpdate;
248
+ await nextUpdate;
168
249
  }
169
250
  applyRuntime(destination, update) {
170
251
  if (update.type === "setMode") {
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/core/permissions/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Persistence for the permission rule model (ADR 0004). Four of the eight sources\n * are file-backed (policy, local, project, user); the other four are runtime-only\n * (flag, cliArg — parsed once from argv, not stored here; command, session — held\n * in memory for the life of the process). `session`-source updates are never\n * written to disk: \"allow for this session\" dies with the session by design.\n *\n * Reuses the locked-JSON-file backend already proven for Phase 1's operational\n * state (FileAuthStorageBackend) rather than widening `SettingsScope`, which only\n * covers two of these four file-backed sources and represents a different, larger\n * settings domain.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { type AuthStorageBackend, FileAuthStorageBackend } from \"../auth-storage.ts\";\nimport type { PermissionBehavior } from \"../tools/contract.ts\";\nimport type { PermissionRule, PermissionSource } from \"./rules.ts\";\n\nexport const PERMISSION_MODES = [\"default\", \"plan\", \"acceptEdits\", \"bypassPermissions\", \"dontAsk\"] as const;\nexport type PermissionMode = (typeof PERMISSION_MODES)[number];\n\n/** Sources a PermissionUpdate may target. Excludes `policy` (administrator-managed,\n * never user-writable) and `flag`/`cliArg` (parsed once from argv at startup, not\n * something this store updates after the fact). */\nexport const WRITABLE_PERMISSION_SOURCES = [\"local\", \"project\", \"user\", \"command\", \"session\"] as const;\nexport type WritablePermissionSource = (typeof WRITABLE_PERMISSION_SOURCES)[number];\n\nconst FILE_BACKED_SOURCES = [\"local\", \"project\", \"user\"] as const;\ntype FileBackedSource = (typeof FILE_BACKED_SOURCES)[number];\n\nconst RUNTIME_SOURCES = [\"command\", \"session\"] as const;\ntype RuntimeSource = (typeof RUNTIME_SOURCES)[number];\n\n/** A rule as persisted: `source` is never stored — it is implied by which file it came from. */\nexport type StoredPermissionRule = Omit<PermissionRule, \"source\">;\n\nexport interface RuleMatcher {\n\ttoolName: string;\n\truleContent?: string;\n}\n\nexport type PermissionUpdate =\n\t| { type: \"addRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"replaceRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"removeRules\"; destination: WritablePermissionSource; matching: readonly RuleMatcher[] }\n\t| { type: \"setMode\"; destination: WritablePermissionSource; mode: PermissionMode };\n\nexport interface PermissionStoreError {\n\tsource: PermissionSource;\n\terror: Error;\n}\n\nexport interface PermissionStoreSnapshot {\n\trules: readonly PermissionRule[];\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>;\n\terrors: readonly PermissionStoreError[];\n}\n\nexport interface PermissionRuleStore {\n\tsnapshot(): Promise<PermissionStoreSnapshot>;\n\tapply(update: PermissionUpdate): Promise<void>;\n}\n\nconst STORE_VERSION = 1;\n\ninterface StoredPermissionScope {\n\tversion: number;\n\trules: StoredPermissionRule[];\n\tmode?: PermissionMode;\n}\n\nfunction emptyScope(): StoredPermissionScope {\n\treturn { version: STORE_VERSION, rules: [] };\n}\n\nfunction isPermissionBehavior(value: unknown): value is PermissionBehavior {\n\treturn value === \"allow\" || value === \"deny\" || value === \"ask\";\n}\n\nfunction parseScope(content: string | undefined): StoredPermissionScope {\n\tif (!content) return emptyScope();\n\tconst parsed = JSON.parse(content);\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\t// FileAuthStorageBackend (shared with auth.json) seeds a freshly created file\n\t// with the literal text \"{}\". Treat a content-free object as \"nothing written\n\t// yet\" rather than malformed; a real malformed file has some recognizable but\n\t// wrong-shaped field, caught below.\n\tif (!(\"version\" in parsed) && !(\"rules\" in parsed) && !(\"mode\" in parsed)) {\n\t\treturn emptyScope();\n\t}\n\tif (\n\t\tparsed.version !== STORE_VERSION ||\n\t\t(parsed.mode !== undefined && !(PERMISSION_MODES as readonly string[]).includes(parsed.mode)) ||\n\t\t!Array.isArray(parsed.rules) ||\n\t\t!parsed.rules.every(\n\t\t\t(rule: unknown) =>\n\t\t\t\ttypeof rule === \"object\" &&\n\t\t\t\trule !== null &&\n\t\t\t\ttypeof (rule as StoredPermissionRule).toolName === \"string\" &&\n\t\t\t\tisPermissionBehavior((rule as StoredPermissionRule).behavior) &&\n\t\t\t\t((rule as StoredPermissionRule).ruleContent === undefined ||\n\t\t\t\t\ttypeof (rule as StoredPermissionRule).ruleContent === \"string\"),\n\t\t)\n\t) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\treturn parsed as StoredPermissionScope;\n}\n\nfunction applyToScope(scope: StoredPermissionScope, update: PermissionUpdate): StoredPermissionScope {\n\tswitch (update.type) {\n\t\tcase \"addRules\":\n\t\t\treturn { ...scope, rules: [...scope.rules, ...update.rules] };\n\t\tcase \"replaceRules\":\n\t\t\treturn { ...scope, rules: [...update.rules] };\n\t\tcase \"removeRules\":\n\t\t\treturn {\n\t\t\t\t...scope,\n\t\t\t\trules: scope.rules.filter(\n\t\t\t\t\t(rule) =>\n\t\t\t\t\t\t!update.matching.some((m) => m.toolName === rule.toolName && m.ruleContent === rule.ruleContent),\n\t\t\t\t),\n\t\t\t};\n\t\tcase \"setMode\":\n\t\t\treturn { ...scope, mode: update.mode };\n\t}\n}\n\nfunction defaultPolicyPath(): string {\n\tif (process.env.APEX_CODE_POLICY_PATH) return process.env.APEX_CODE_POLICY_PATH;\n\treturn process.platform === \"win32\"\n\t\t? join(process.env.ProgramData ?? \"C:\\\\ProgramData\", \"apex-code\", \"policy.json\")\n\t\t: \"/etc/apex-code/policy.json\";\n}\n\nexport interface CreateFilePermissionRuleStoreOptions {\n\tcwd: string;\n\tagentDir?: string;\n\t/** Overrides the managed policy file path. Defaults to $APEX_CODE_POLICY_PATH, else an OS-specific system location. */\n\tpolicyPath?: string;\n\t/** Test seam: inject backends for the three writable file-backed sources instead of touching real files. */\n\tbackends?: Partial<Record<FileBackedSource, AuthStorageBackend>>;\n\t/** Immutable argv layers (`flag` / `cliArg`), validated by the CLI before construction. */\n\tinitialRules?: readonly PermissionRule[];\n}\n\n/** File-backed store for policy/local/project/user, in-memory for command/session. */\nexport class FilePermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly backends: Record<FileBackedSource, AuthStorageBackend>;\n\tprivate readonly policyPath: string;\n\tprivate readonly initialRules: readonly PermissionRule[];\n\tprivate readonly runtimeRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly runtimeModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\n\tconstructor(options: CreateFilePermissionRuleStoreOptions) {\n\t\tconst agentDir = options.agentDir ?? getAgentDir();\n\t\tconst cwd = resolvePath(options.cwd);\n\t\tthis.backends = {\n\t\t\tuser: new FileAuthStorageBackend(join(agentDir, \"permissions.json\")),\n\t\t\tproject: new FileAuthStorageBackend(join(cwd, \".apex-code\", \"permissions.json\")),\n\t\t\tlocal: new FileAuthStorageBackend(join(cwd, \".apex-code\", \"permissions.local.json\")),\n\t\t\t...options.backends,\n\t\t};\n\t\tthis.policyPath = options.policyPath ?? defaultPolicyPath();\n\t\tthis.initialRules = options.initialRules ?? [];\n\t}\n\n\tprivate async readFileBackedScope(\n\t\tsource: FileBackedSource,\n\t): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tlet content: string | undefined;\n\t\t\tawait this.backends[source].withLockAsync(async (current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate async readPolicy(): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tconst content = await readFile(this.policyPath, \"utf-8\");\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { scope: emptyScope() };\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\tconst [policy, local, project, user] = await Promise.all([\n\t\t\tthis.readPolicy(),\n\t\t\tthis.readFileBackedScope(\"local\"),\n\t\t\tthis.readFileBackedScope(\"project\"),\n\t\t\tthis.readFileBackedScope(\"user\"),\n\t\t]);\n\t\tconst errors: PermissionStoreError[] = [];\n\t\tconst withSource = (\n\t\t\tsource: PermissionSource,\n\t\t\tentry: { scope: StoredPermissionScope; error?: Error },\n\t\t): PermissionRule[] => {\n\t\t\tif (entry.error) errors.push({ source, error: entry.error });\n\t\t\treturn entry.scope.rules.map((rule) => ({ ...rule, source }));\n\t\t};\n\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...withSource(\"policy\", policy),\n\t\t\t...this.initialRules,\n\t\t\t...withSource(\"local\", local),\n\t\t\t...withSource(\"project\", project),\n\t\t\t...withSource(\"user\", user),\n\t\t\t...this.runtimeRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.runtimeRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\n\t\tconst modesBySource = new Map<WritablePermissionSource, PermissionMode>();\n\t\tif (local.scope.mode) modesBySource.set(\"local\", local.scope.mode);\n\t\tif (project.scope.mode) modesBySource.set(\"project\", project.scope.mode);\n\t\tif (user.scope.mode) modesBySource.set(\"user\", user.scope.mode);\n\t\tif (this.runtimeModes.command) modesBySource.set(\"command\", this.runtimeModes.command);\n\t\tif (this.runtimeModes.session) modesBySource.set(\"session\", this.runtimeModes.session);\n\n\t\treturn { rules, modesBySource, errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination === \"command\" || update.destination === \"session\") {\n\t\t\tthis.applyRuntime(update.destination, update);\n\t\t\treturn;\n\t\t}\n\t\tawait this.backends[update.destination].withLockAsync(async (content) => {\n\t\t\tconst next = applyToScope(parseScope(content), update);\n\t\t\treturn { result: undefined, next: JSON.stringify(next, null, 2) };\n\t\t});\n\t}\n\n\tprivate applyRuntime(destination: RuntimeSource, update: PermissionUpdate): void {\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.runtimeModes[destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = { version: STORE_VERSION, rules: this.runtimeRules[destination] };\n\t\tthis.runtimeRules[destination] = applyToScope(current, update).rules;\n\t}\n}\n\nexport interface DerivedPermissionRuleStoreOptions {\n\t/** The parent's live store. Read fresh on every `snapshot()`, never cached, so a\n\t * rule the parent picks up mid-session (e.g. a human's \"always allow\") is visible\n\t * to the child on its very next call. */\n\tparent: PermissionRuleStore;\n}\n\n/**\n * A delegated child's permission store (roadmap Phase 5, ADR 0008). A read-through\n * view over the parent's live snapshot -- including its runtime-only sources\n * (`flag`, `cliArg`, `command`, `session`), which a subprocess-spawned child could\n * never observe -- plus a child-local runtime overlay for the child's own writes.\n *\n * `apply()` never reaches the parent. It writes only into this instance's own\n * overlay, and only for the two runtime-only writable sources (`command`, `session`);\n * a file-backed destination (`local`, `project`, `user`) is rejected outright, not\n * silently redirected -- a delegated child must never be able to persist to the\n * parent's settings files, and \"runtime-only\" is enforced by construction here, not\n * by a convention a future caller could forget. The overlay is discarded with the\n * child: there is no code path from a child's approval back into the parent's store,\n * which is what keeps a child's `persist: true` from widening its parent.\n */\nexport class DerivedPermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly parent: PermissionRuleStore;\n\tprivate readonly overlayRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly overlayModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\n\tconstructor(options: DerivedPermissionRuleStoreOptions) {\n\t\tthis.parent = options.parent;\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\tconst parentSnapshot = await this.parent.snapshot();\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...parentSnapshot.rules,\n\t\t\t...this.overlayRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.overlayRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\t\tconst modesBySource = new Map(parentSnapshot.modesBySource);\n\t\tif (this.overlayModes.command !== undefined) modesBySource.set(\"command\", this.overlayModes.command);\n\t\tif (this.overlayModes.session !== undefined) modesBySource.set(\"session\", this.overlayModes.session);\n\t\treturn { rules, modesBySource, errors: parentSnapshot.errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination !== \"command\" && update.destination !== \"session\") {\n\t\t\tthrow new Error(\n\t\t\t\t`A delegated child cannot persist permission rules to \"${update.destination}\" -- only \"command\" and \"session\" are writable from a derived store (ADR 0008).`,\n\t\t\t);\n\t\t}\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.overlayModes[update.destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = {\n\t\t\tversion: STORE_VERSION,\n\t\t\trules: this.overlayRules[update.destination],\n\t\t};\n\t\tthis.overlayRules[update.destination] = applyToScope(current, update).rules;\n\t}\n}\n"]}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/core/permissions/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAA2B,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC;AAIzE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,mBAAmB,EAAE,SAAS,CAAU,CAAC;AAG5G;;mDAEmD;AACnD,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,CAAU,CAAC;AAGvG,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,CAAU,CAAC;AAGlE,MAAM,eAAe,GAAG,CAAC,SAAS,EAAE,SAAS,CAAU,CAAC;AAiCxD,MAAM,aAAa,GAAG,CAAC,CAAC;AAQxB,SAAS,UAAU,GAA0B;IAC5C,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAAA,CAC7C;AAED,SAAS,oBAAoB,CAAC,KAAc,EAA+B;IAC1E,OAAO,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,KAAK,CAAC;AAAA,CAChE;AAED,SAAS,UAAU,CAAC,OAA2B,EAAyB;IACvE,IAAI,CAAC,OAAO;QAAE,OAAO,UAAU,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5E,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACpE,CAAC;IACD,8EAA8E;IAC9E,8EAA8E;IAC9E,8EAA8E;IAC9E,oCAAoC;IACpC,IAAI,CAAC,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC,EAAE,CAAC;QAC3E,OAAO,UAAU,EAAE,CAAC;IACrB,CAAC;IACD,IACC,MAAM,CAAC,OAAO,KAAK,aAAa;QAChC,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,CAAE,gBAAsC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC7F,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;QAC5B,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAClB,CAAC,IAAa,EAAE,EAAE,CACjB,OAAO,IAAI,KAAK,QAAQ;YACxB,IAAI,KAAK,IAAI;YACb,OAAQ,IAA6B,CAAC,QAAQ,KAAK,QAAQ;YAC3D,oBAAoB,CAAE,IAA6B,CAAC,QAAQ,CAAC;YAC7D,CAAE,IAA6B,CAAC,WAAW,KAAK,SAAS;gBACxD,OAAQ,IAA6B,CAAC,WAAW,KAAK,QAAQ,CAAC,CACjE,EACA,CAAC;QACF,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,MAA+B,CAAC;AAAA,CACvC;AAED,SAAS,YAAY,CAAC,KAA4B,EAAE,MAAwB,EAAyB;IACpG,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,UAAU;YACd,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/D,KAAK,cAAc;YAClB,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,KAAK,aAAa;YACjB,OAAO;gBACN,GAAG,KAAK;gBACR,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CACxB,CAAC,IAAI,EAAE,EAAE,CACR,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,WAAW,KAAK,IAAI,CAAC,WAAW,CAAC,CACjG;aACD,CAAC;QACH,KAAK,SAAS;YACb,OAAO,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IACzC,CAAC;AAAA,CACD;AAED,SAAS,iBAAiB,GAAW;IACpC,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAChF,OAAO,OAAO,CAAC,QAAQ,KAAK,OAAO;QAClC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,iBAAiB,EAAE,WAAW,EAAE,aAAa,CAAC;QAChF,CAAC,CAAC,4BAA4B,CAAC;AAAA,CAChC;AAoBD,SAAS,iBAAiB,CAAC,OAAe,EAAsB;IAC/D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnC,IACC,OAAO,MAAM,KAAK,QAAQ;QAC1B,MAAM,KAAK,IAAI;QACf,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QACrB,MAAM,CAAC,OAAO,KAAK,CAAC;QACpB,CAAC,MAAM,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC;QAC7D,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,EACxD,CAAC;QACF,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,MAA4B,CAAC;AAAA,CACpC;AAuBD,sFAAsF;AACtF,MAAM,OAAO,uBAAuB;IAClB,QAAQ,CAA+C;IACvD,UAAU,CAAS;IACnB,kBAAkB,CAAU;IAC5B,YAAY,CAA4B;IACxC,YAAY,GAAkD,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC3F,YAAY,GAAmD,EAAE,CAAC;IAClE,cAAc,CAAU;IACxB,UAAU,GAEvB,EAAE,CAAC;IACC,aAAa,CAGlB;IACc,gBAAgB,GAAqD,EAAE,CAAC;IAEzF,YAAY,OAA6C,EAAE;QAC1D,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,WAAW,EAAE,CAAC;QACnD,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,QAAQ,GAAG;YACf,IAAI,EAAE,IAAI,sBAAsB,CAAC,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;YACpE,OAAO,EAAE,IAAI,sBAAsB,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,kBAAkB,CAAC,CAAC;YAChF,KAAK,EAAE,IAAI,sBAAsB,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,wBAAwB,CAAC,CAAC;YACpF,GAAG,OAAO,CAAC,QAAQ;SACnB,CAAC;QACF,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,iBAAiB,EAAE,CAAC;QAC5D,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;QACnG,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;QAC/C,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,IAAI,CAAC;QACrD,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YAC1B,IAAI,CAAC,UAAU,CAAC,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;YACjE,IAAI,CAAC,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;QACpE,CAAC;IAAA,CACD;IAEO,uBAAuB,CAAC,MAAwB,EAA4D;QACnH,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;QACjD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC;QAClC,OAAO,OAAO,CAAC;IAAA,CACf;IAEO,KAAK,CAAC,mBAAmB,CAChC,MAAwB,EACmC;QAC3D,IAAI,CAAC;YACJ,IAAI,OAA2B,CAAC;YAChC,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;gBAC5D,OAAO,GAAG,OAAO,CAAC;gBAClB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;YAAA,CAC7B,CAAC,CAAC;YACH,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QAClG,CAAC;IAAA,CACD;IAEO,KAAK,CAAC,UAAU,GAA6D;QACpF,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACzD,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC;YACvF,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QAClG,CAAC;IAAA,CACD;IAEO,KAAK,CAAC,kBAAkB,GAG7B;QACF,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,iBAAiB,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,kBAA4B,EAAE,OAAO,CAAC,CAAC,CAAC;YAC/F,OAAO;gBACN,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,QAAQ,CAAC,MAAM,IAAI,SAAS,CAAC,EAAE;gBAC3D,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,QAAQ,CAAC,IAAI,IAAI,SAAS,CAAC,EAAE;aACvD,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAC1E,OAAO;gBACN,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE;gBAC/C,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;aAC7B,CAAC;QACH,CAAC;IAAA,CACD;IAEO,qBAAqB,GAG1B;QACF,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;gBACvG,MAAM;gBACN,IAAI;aACJ,CAAC,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,aAAa,KAAK,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC,aAAa,CAAC;IAAA,CAC1B;IAED,KAAK,CAAC,QAAQ,GAAqC;QAClD,wEAAwE;QACxE,uEAAuE;QACvE,2DAA2D;QAC3D,MAAM,CAAC,aAAa,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACzD,IAAI,CAAC,qBAAqB,EAAE;YAC5B,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC;YACrC,IAAI,CAAC,uBAAuB,CAAC,SAAS,CAAC;SACvC,CAAC,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC;QACvC,MAAM,MAAM,GAA2B,EAAE,CAAC;QAC1C,MAAM,UAAU,GAAG,CAClB,MAAwB,EACxB,KAAsD,EACnC,EAAE,CAAC;YACtB,IAAI,KAAK,CAAC,KAAK;gBAAE,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;YAC7D,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QAAA,CAC9D,CAAC;QAEF,MAAM,KAAK,GAAqB;YAC/B,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC;YAC/B,GAAG,IAAI,CAAC,YAAY;YACpB,GAAG,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC;YAC7B,GAAG,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC;YACjC,GAAG,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC;YAC3B,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,SAAkB,EAAE,CAAC,CAAC;YACrF,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,SAAkB,EAAE,CAAC,CAAC;SACrF,CAAC;QAEF,MAAM,aAAa,GAAG,IAAI,GAAG,EAA4C,CAAC;QAC1E,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI;YAAE,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnE,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI;YAAE,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI;YAAE,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChE,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO;YAAE,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACvF,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO;YAAE,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAEvF,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;IAAA,CACxC;IAED,KAAK,CAAC,KAAK,CAAC,MAAwB,EAAiB;QACpD,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YAC1E,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YAC9C,OAAO;QACR,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,MAAM,CAAC,WAAW,KAAK,OAAO,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,CAAC,EAAE,CAAC;YAClG,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,CAAC,WAAW,mBAAmB,CAAC,CAAC;QACnF,CAAC;QACD,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;QACvC,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACvG,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAClD,MAAM,OAAO,GACZ,WAAW,KAAK,MAAM,IAAI,IAAI,CAAC,kBAAkB;gBAChD,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC,CAAC,IAAI;gBAC3C,CAAC,CAAC,MAAM,IAAI,CAAC,uBAAuB,CAAC,WAAW,CAAC,CAAC;YACpD,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YACjD,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,aAAa,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;gBAC3D,MAAM,EAAE,SAAS;gBACjB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;aACnC,CAAC,CAAC,CAAC;YACJ,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAChE,IAAI,WAAW,KAAK,MAAM,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBACvD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;oBACrE,GAAG,QAAQ;oBACX,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;iBACrB,CAAC,CAAC,CAAC;YACL,CAAC;QAAA,CACD,CAAC,CAAC;QACH,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,GAAG,UAAU,CAAC;QAChD,MAAM,UAAU,CAAC;IAAA,CACjB;IAEO,YAAY,CAAC,WAA0B,EAAE,MAAwB,EAAQ;QAChF,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC;YAC7C,OAAO;QACR,CAAC;QACD,MAAM,OAAO,GAA0B,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,EAAE,CAAC;QACzG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC;IAAA,CACrE;CACD;AASD;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,0BAA0B;IACrB,MAAM,CAAsB;IAC5B,YAAY,GAAkD,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC3F,YAAY,GAAmD,EAAE,CAAC;IAEnF,YAAY,OAA0C,EAAE;QACvD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAAA,CAC7B;IAED,KAAK,CAAC,QAAQ,GAAqC;QAClD,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpD,MAAM,KAAK,GAAqB;YAC/B,GAAG,cAAc,CAAC,KAAK;YACvB,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,SAAkB,EAAE,CAAC,CAAC;YACrF,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,SAAkB,EAAE,CAAC,CAAC;SACrF,CAAC;QACF,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;QAC5D,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,KAAK,SAAS;YAAE,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACrG,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,KAAK,SAAS;YAAE,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACrG,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,CAAC;IAAA,CAC/D;IAED,KAAK,CAAC,KAAK,CAAC,MAAwB,EAAiB;QACpD,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YAC1E,MAAM,IAAI,KAAK,CACd,yDAAyD,MAAM,CAAC,WAAW,iFAAiF,CAC5J,CAAC;QACH,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC;YACpD,OAAO;QACR,CAAC;QACD,MAAM,OAAO,GAA0B;YACtC,OAAO,EAAE,aAAa;YACtB,KAAK,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,WAAW,CAAC;SAC5C,CAAC;QACF,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC;IAAA,CAC5E;CACD","sourcesContent":["/**\n * Persistence for the permission rule model (ADR 0004). Four of the eight sources\n * are file-backed (policy, local, project, user); the other four are runtime-only\n * (flag, cliArg — parsed once from argv, not stored here; command, session — held\n * in memory for the life of the process). `session`-source updates are never\n * written to disk: \"allow for this session\" dies with the session by design.\n *\n * Reuses the locked-JSON-file backend already proven for Phase 1's operational\n * state (FileAuthStorageBackend) rather than widening `SettingsScope`, which only\n * covers two of these four file-backed sources and represents a different, larger\n * settings domain.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { type AuthStorageBackend, FileAuthStorageBackend } from \"../auth-storage.ts\";\nimport { POLICY_SNAPSHOT_PATH_VARIABLE } from \"../sandbox/cli-launch.ts\";\nimport type { PermissionBehavior } from \"../tools/contract.ts\";\nimport type { PermissionRule, PermissionSource } from \"./rules.ts\";\n\nexport const PERMISSION_MODES = [\"default\", \"plan\", \"acceptEdits\", \"bypassPermissions\", \"dontAsk\"] as const;\nexport type PermissionMode = (typeof PERMISSION_MODES)[number];\n\n/** Sources a PermissionUpdate may target. Excludes `policy` (administrator-managed,\n * never user-writable) and `flag`/`cliArg` (parsed once from argv at startup, not\n * something this store updates after the fact). */\nexport const WRITABLE_PERMISSION_SOURCES = [\"local\", \"project\", \"user\", \"command\", \"session\"] as const;\nexport type WritablePermissionSource = (typeof WRITABLE_PERMISSION_SOURCES)[number];\n\nconst FILE_BACKED_SOURCES = [\"local\", \"project\", \"user\"] as const;\ntype FileBackedSource = (typeof FILE_BACKED_SOURCES)[number];\n\nconst RUNTIME_SOURCES = [\"command\", \"session\"] as const;\ntype RuntimeSource = (typeof RUNTIME_SOURCES)[number];\n\n/** A rule as persisted: `source` is never stored — it is implied by which file it came from. */\nexport type StoredPermissionRule = Omit<PermissionRule, \"source\">;\n\nexport interface RuleMatcher {\n\ttoolName: string;\n\truleContent?: string;\n}\n\nexport type PermissionUpdate =\n\t| { type: \"addRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"replaceRules\"; destination: WritablePermissionSource; rules: readonly StoredPermissionRule[] }\n\t| { type: \"removeRules\"; destination: WritablePermissionSource; matching: readonly RuleMatcher[] }\n\t| { type: \"setMode\"; destination: WritablePermissionSource; mode: PermissionMode };\n\nexport interface PermissionStoreError {\n\tsource: PermissionSource;\n\terror: Error;\n}\n\nexport interface PermissionStoreSnapshot {\n\trules: readonly PermissionRule[];\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>;\n\terrors: readonly PermissionStoreError[];\n}\n\nexport interface PermissionRuleStore {\n\tsnapshot(): Promise<PermissionStoreSnapshot>;\n\tapply(update: PermissionUpdate): Promise<void>;\n}\n\nconst STORE_VERSION = 1;\n\ninterface StoredPermissionScope {\n\tversion: number;\n\trules: StoredPermissionRule[];\n\tmode?: PermissionMode;\n}\n\nfunction emptyScope(): StoredPermissionScope {\n\treturn { version: STORE_VERSION, rules: [] };\n}\n\nfunction isPermissionBehavior(value: unknown): value is PermissionBehavior {\n\treturn value === \"allow\" || value === \"deny\" || value === \"ask\";\n}\n\nfunction parseScope(content: string | undefined): StoredPermissionScope {\n\tif (!content) return emptyScope();\n\tconst parsed = JSON.parse(content);\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\t// FileAuthStorageBackend (shared with auth.json) seeds a freshly created file\n\t// with the literal text \"{}\". Treat a content-free object as \"nothing written\n\t// yet\" rather than malformed; a real malformed file has some recognizable but\n\t// wrong-shaped field, caught below.\n\tif (!(\"version\" in parsed) && !(\"rules\" in parsed) && !(\"mode\" in parsed)) {\n\t\treturn emptyScope();\n\t}\n\tif (\n\t\tparsed.version !== STORE_VERSION ||\n\t\t(parsed.mode !== undefined && !(PERMISSION_MODES as readonly string[]).includes(parsed.mode)) ||\n\t\t!Array.isArray(parsed.rules) ||\n\t\t!parsed.rules.every(\n\t\t\t(rule: unknown) =>\n\t\t\t\ttypeof rule === \"object\" &&\n\t\t\t\trule !== null &&\n\t\t\t\ttypeof (rule as StoredPermissionRule).toolName === \"string\" &&\n\t\t\t\tisPermissionBehavior((rule as StoredPermissionRule).behavior) &&\n\t\t\t\t((rule as StoredPermissionRule).ruleContent === undefined ||\n\t\t\t\t\ttypeof (rule as StoredPermissionRule).ruleContent === \"string\"),\n\t\t)\n\t) {\n\t\tthrow new Error(\"Invalid permission rules file: unexpected shape\");\n\t}\n\treturn parsed as StoredPermissionScope;\n}\n\nfunction applyToScope(scope: StoredPermissionScope, update: PermissionUpdate): StoredPermissionScope {\n\tswitch (update.type) {\n\t\tcase \"addRules\":\n\t\t\treturn { ...scope, rules: [...scope.rules, ...update.rules] };\n\t\tcase \"replaceRules\":\n\t\t\treturn { ...scope, rules: [...update.rules] };\n\t\tcase \"removeRules\":\n\t\t\treturn {\n\t\t\t\t...scope,\n\t\t\t\trules: scope.rules.filter(\n\t\t\t\t\t(rule) =>\n\t\t\t\t\t\t!update.matching.some((m) => m.toolName === rule.toolName && m.ruleContent === rule.ruleContent),\n\t\t\t\t),\n\t\t\t};\n\t\tcase \"setMode\":\n\t\t\treturn { ...scope, mode: update.mode };\n\t}\n}\n\nfunction defaultPolicyPath(): string {\n\tif (process.env.APEX_CODE_POLICY_PATH) return process.env.APEX_CODE_POLICY_PATH;\n\treturn process.platform === \"win32\"\n\t\t? join(process.env.ProgramData ?? \"C:\\\\ProgramData\", \"apex-code\", \"policy.json\")\n\t\t: \"/etc/apex-code/policy.json\";\n}\n\n/**\n * The supervisor's captured host `policy` and `user` scopes (PS.3).\n *\n * Inside a sandboxed child neither source is reachable at its own path: the child's\n * environment allowlist removes `APEX_CODE_POLICY_PATH`, and `APEX_CODE_CODING_AGENT_DIR`\n * points at a directory inside the workspace that the child itself may write. Reading both\n * from one supervisor-written file outside every writable root is what makes the child's\n * effective authority equal the supervisor's, rather than equal to whatever the child last\n * wrote. Named only by `core/sandbox/cli-launch.ts`, which sets the variable on the\n * launch's own environment and keeps it out of the child-environment allowlist.\n */\n/** Raw file text per source, so the child validates exactly what the host would have. */\ninterface PolicySnapshotFile {\n\tversion: number;\n\tpolicy: string | null;\n\tuser: string | null;\n}\n\nfunction parseSnapshotFile(content: string): PolicySnapshotFile {\n\tconst parsed = JSON.parse(content);\n\tif (\n\t\ttypeof parsed !== \"object\" ||\n\t\tparsed === null ||\n\t\tArray.isArray(parsed) ||\n\t\tparsed.version !== 1 ||\n\t\t(parsed.policy !== null && typeof parsed.policy !== \"string\") ||\n\t\t(parsed.user !== null && typeof parsed.user !== \"string\")\n\t) {\n\t\tthrow new Error(\"Invalid permission snapshot file: unexpected shape\");\n\t}\n\treturn parsed as PolicySnapshotFile;\n}\n\nexport interface CreateFilePermissionRuleStoreOptions {\n\tcwd: string;\n\tagentDir?: string;\n\t/** Overrides the managed policy file path. Defaults to $APEX_CODE_POLICY_PATH, else an OS-specific system location. */\n\tpolicyPath?: string;\n\t/**\n\t * The supervisor's captured host `policy` and `user` scopes. Defaults to\n\t * $APEX_CODE_POLICY_SNAPSHOT_PATH, which only a sandbox supervisor sets. When present\n\t * it replaces both `policyPath` and the `user` file for reading; `apply()` still\n\t * writes a `user` update to this process's own agent directory, which is how an\n\t * in-session approval persists without the child's copy becoming an authority source.\n\t */\n\tpolicySnapshotPath?: string;\n\t/** Test seam: inject backends for the three writable file-backed sources instead of touching real files. */\n\tbackends?: Partial<Record<FileBackedSource, AuthStorageBackend>>;\n\t/** Immutable argv layers (`flag` / `cliArg`), validated by the CLI before construction. */\n\tinitialRules?: readonly PermissionRule[];\n\t/** Resolved before construction. False excludes both project-controlled scopes. */\n\tprojectTrusted?: boolean;\n}\n\n/** File-backed store for policy/local/project/user, in-memory for command/session. */\nexport class FilePermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly backends: Record<FileBackedSource, AuthStorageBackend>;\n\tprivate readonly policyPath: string;\n\tprivate readonly policySnapshotPath?: string;\n\tprivate readonly initialRules: readonly PermissionRule[];\n\tprivate readonly runtimeRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly runtimeModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\tprivate readonly projectTrusted: boolean;\n\tprivate readonly fileScopes: Partial<\n\t\tRecord<FileBackedSource, Promise<{ scope: StoredPermissionScope; error?: Error }>>\n\t> = {};\n\tprivate snapshotScope?: Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}>;\n\tprivate readonly fileScopeUpdates: Partial<Record<FileBackedSource, Promise<void>>> = {};\n\n\tconstructor(options: CreateFilePermissionRuleStoreOptions) {\n\t\tconst agentDir = options.agentDir ?? getAgentDir();\n\t\tconst cwd = resolvePath(options.cwd);\n\t\tthis.backends = {\n\t\t\tuser: new FileAuthStorageBackend(join(agentDir, \"permissions.json\")),\n\t\t\tproject: new FileAuthStorageBackend(join(cwd, \".apex-code\", \"permissions.json\")),\n\t\t\tlocal: new FileAuthStorageBackend(join(cwd, \".apex-code\", \"permissions.local.json\")),\n\t\t\t...options.backends,\n\t\t};\n\t\tthis.policyPath = options.policyPath ?? defaultPolicyPath();\n\t\tthis.policySnapshotPath = options.policySnapshotPath ?? process.env[POLICY_SNAPSHOT_PATH_VARIABLE];\n\t\tthis.initialRules = options.initialRules ?? [];\n\t\tthis.projectTrusted = options.projectTrusted ?? true;\n\t\tif (!this.projectTrusted) {\n\t\t\tthis.fileScopes.local = Promise.resolve({ scope: emptyScope() });\n\t\t\tthis.fileScopes.project = Promise.resolve({ scope: emptyScope() });\n\t\t}\n\t}\n\n\tprivate capturedFileBackedScope(source: FileBackedSource): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\tconst captured = this.fileScopes[source];\n\t\tif (captured) return captured;\n\t\tconst initial = this.readFileBackedScope(source);\n\t\tthis.fileScopes[source] = initial;\n\t\treturn initial;\n\t}\n\n\tprivate async readFileBackedScope(\n\t\tsource: FileBackedSource,\n\t): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tlet content: string | undefined;\n\t\t\tawait this.backends[source].withLockAsync(async (current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate async readPolicy(): Promise<{ scope: StoredPermissionScope; error?: Error }> {\n\t\ttry {\n\t\t\tconst content = await readFile(this.policyPath, \"utf-8\");\n\t\t\treturn { scope: parseScope(content) };\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { scope: emptyScope() };\n\t\t\treturn { scope: emptyScope(), error: error instanceof Error ? error : new Error(String(error)) };\n\t\t}\n\t}\n\n\tprivate async readPolicySnapshot(): Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}> {\n\t\ttry {\n\t\t\tconst snapshot = parseSnapshotFile(await readFile(this.policySnapshotPath as string, \"utf-8\"));\n\t\t\treturn {\n\t\t\t\tpolicy: { scope: parseScope(snapshot.policy ?? undefined) },\n\t\t\t\tuser: { scope: parseScope(snapshot.user ?? undefined) },\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tconst failure = error instanceof Error ? error : new Error(String(error));\n\t\t\treturn {\n\t\t\t\tpolicy: { scope: emptyScope(), error: failure },\n\t\t\t\tuser: { scope: emptyScope() },\n\t\t\t};\n\t\t}\n\t}\n\n\tprivate capturedPolicyAndUser(): Promise<{\n\t\tpolicy: { scope: StoredPermissionScope; error?: Error };\n\t\tuser: { scope: StoredPermissionScope; error?: Error };\n\t}> {\n\t\tif (!this.policySnapshotPath) {\n\t\t\treturn Promise.all([this.readPolicy(), this.capturedFileBackedScope(\"user\")]).then(([policy, user]) => ({\n\t\t\t\tpolicy,\n\t\t\t\tuser,\n\t\t\t}));\n\t\t}\n\t\tthis.snapshotScope ??= this.readPolicySnapshot();\n\t\treturn this.snapshotScope;\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\t// File-backed authorization is captured once. Direct filesystem writes,\n\t\t// including symlink replacement, cannot widen the current or next gate\n\t\t// decision. Managed policy and runtime scopes remain live.\n\t\tconst [policyAndUser, local, project] = await Promise.all([\n\t\t\tthis.capturedPolicyAndUser(),\n\t\t\tthis.capturedFileBackedScope(\"local\"),\n\t\t\tthis.capturedFileBackedScope(\"project\"),\n\t\t]);\n\t\tconst { policy, user } = policyAndUser;\n\t\tconst errors: PermissionStoreError[] = [];\n\t\tconst withSource = (\n\t\t\tsource: PermissionSource,\n\t\t\tentry: { scope: StoredPermissionScope; error?: Error },\n\t\t): PermissionRule[] => {\n\t\t\tif (entry.error) errors.push({ source, error: entry.error });\n\t\t\treturn entry.scope.rules.map((rule) => ({ ...rule, source }));\n\t\t};\n\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...withSource(\"policy\", policy),\n\t\t\t...this.initialRules,\n\t\t\t...withSource(\"local\", local),\n\t\t\t...withSource(\"project\", project),\n\t\t\t...withSource(\"user\", user),\n\t\t\t...this.runtimeRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.runtimeRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\n\t\tconst modesBySource = new Map<WritablePermissionSource, PermissionMode>();\n\t\tif (local.scope.mode) modesBySource.set(\"local\", local.scope.mode);\n\t\tif (project.scope.mode) modesBySource.set(\"project\", project.scope.mode);\n\t\tif (user.scope.mode) modesBySource.set(\"user\", user.scope.mode);\n\t\tif (this.runtimeModes.command) modesBySource.set(\"command\", this.runtimeModes.command);\n\t\tif (this.runtimeModes.session) modesBySource.set(\"session\", this.runtimeModes.session);\n\n\t\treturn { rules, modesBySource, errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination === \"command\" || update.destination === \"session\") {\n\t\t\tthis.applyRuntime(update.destination, update);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.projectTrusted && (update.destination === \"local\" || update.destination === \"project\")) {\n\t\t\tthrow new Error(`Cannot update untrusted ${update.destination} permission scope`);\n\t\t}\n\t\tconst destination = update.destination;\n\t\tconst previousUpdate = this.fileScopeUpdates[destination]?.catch(() => undefined) ?? Promise.resolve();\n\t\tconst nextUpdate = previousUpdate.then(async () => {\n\t\t\tconst current =\n\t\t\t\tdestination === \"user\" && this.policySnapshotPath\n\t\t\t\t\t? (await this.capturedPolicyAndUser()).user\n\t\t\t\t\t: await this.capturedFileBackedScope(destination);\n\t\t\tconst next = applyToScope(current.scope, update);\n\t\t\tawait this.backends[destination].withLockAsync(async () => ({\n\t\t\t\tresult: undefined,\n\t\t\t\tnext: JSON.stringify(next, null, 2),\n\t\t\t}));\n\t\t\tthis.fileScopes[destination] = Promise.resolve({ scope: next });\n\t\t\tif (destination === \"user\" && this.policySnapshotPath) {\n\t\t\t\tthis.snapshotScope = this.capturedPolicyAndUser().then((snapshot) => ({\n\t\t\t\t\t...snapshot,\n\t\t\t\t\tuser: { scope: next },\n\t\t\t\t}));\n\t\t\t}\n\t\t});\n\t\tthis.fileScopeUpdates[destination] = nextUpdate;\n\t\tawait nextUpdate;\n\t}\n\n\tprivate applyRuntime(destination: RuntimeSource, update: PermissionUpdate): void {\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.runtimeModes[destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = { version: STORE_VERSION, rules: this.runtimeRules[destination] };\n\t\tthis.runtimeRules[destination] = applyToScope(current, update).rules;\n\t}\n}\n\nexport interface DerivedPermissionRuleStoreOptions {\n\t/** The parent's live store. Read fresh on every `snapshot()`, never cached, so a\n\t * rule the parent picks up mid-session (e.g. a human's \"always allow\") is visible\n\t * to the child on its very next call. */\n\tparent: PermissionRuleStore;\n}\n\n/**\n * A delegated child's permission store (roadmap Phase 5, ADR 0008). A read-through\n * view over the parent's live snapshot -- including its runtime-only sources\n * (`flag`, `cliArg`, `command`, `session`), which a subprocess-spawned child could\n * never observe -- plus a child-local runtime overlay for the child's own writes.\n *\n * `apply()` never reaches the parent. It writes only into this instance's own\n * overlay, and only for the two runtime-only writable sources (`command`, `session`);\n * a file-backed destination (`local`, `project`, `user`) is rejected outright, not\n * silently redirected -- a delegated child must never be able to persist to the\n * parent's settings files, and \"runtime-only\" is enforced by construction here, not\n * by a convention a future caller could forget. The overlay is discarded with the\n * child: there is no code path from a child's approval back into the parent's store,\n * which is what keeps a child's `persist: true` from widening its parent.\n */\nexport class DerivedPermissionRuleStore implements PermissionRuleStore {\n\tprivate readonly parent: PermissionRuleStore;\n\tprivate readonly overlayRules: Record<RuntimeSource, StoredPermissionRule[]> = { command: [], session: [] };\n\tprivate readonly overlayModes: Partial<Record<RuntimeSource, PermissionMode>> = {};\n\n\tconstructor(options: DerivedPermissionRuleStoreOptions) {\n\t\tthis.parent = options.parent;\n\t}\n\n\tasync snapshot(): Promise<PermissionStoreSnapshot> {\n\t\tconst parentSnapshot = await this.parent.snapshot();\n\t\tconst rules: PermissionRule[] = [\n\t\t\t...parentSnapshot.rules,\n\t\t\t...this.overlayRules.command.map((rule) => ({ ...rule, source: \"command\" as const })),\n\t\t\t...this.overlayRules.session.map((rule) => ({ ...rule, source: \"session\" as const })),\n\t\t];\n\t\tconst modesBySource = new Map(parentSnapshot.modesBySource);\n\t\tif (this.overlayModes.command !== undefined) modesBySource.set(\"command\", this.overlayModes.command);\n\t\tif (this.overlayModes.session !== undefined) modesBySource.set(\"session\", this.overlayModes.session);\n\t\treturn { rules, modesBySource, errors: parentSnapshot.errors };\n\t}\n\n\tasync apply(update: PermissionUpdate): Promise<void> {\n\t\tif (update.destination !== \"command\" && update.destination !== \"session\") {\n\t\t\tthrow new Error(\n\t\t\t\t`A delegated child cannot persist permission rules to \"${update.destination}\" -- only \"command\" and \"session\" are writable from a derived store (ADR 0008).`,\n\t\t\t);\n\t\t}\n\t\tif (update.type === \"setMode\") {\n\t\t\tthis.overlayModes[update.destination] = update.mode;\n\t\t\treturn;\n\t\t}\n\t\tconst current: StoredPermissionScope = {\n\t\t\tversion: STORE_VERSION,\n\t\t\trules: this.overlayRules[update.destination],\n\t\t};\n\t\tthis.overlayRules[update.destination] = applyToScope(current, update).rules;\n\t}\n}\n"]}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * VF.3: the bounded argv command executor (spec
3
+ * 2026-09-01-configured-verification-and-formatting.md § 2). Runs a
4
+ * `CommandPolicy` from the VF.2 loader verbatim: executable + argv spawn
5
+ * with no shell, under the policy's numeric bounds, with process-tree
6
+ * termination on timeout and cancellation. Outcomes are structured values;
7
+ * a command that merely fails is data, never a throw.
8
+ *
9
+ * Trusting the loader: this module does NOT re-validate policy fields the
10
+ * loader already guarantees (numeric bounds, shell:false, argv shapes). The
11
+ * one defensive re-check is the cwd confinement, because a resolved path is
12
+ * cheap to verify and the worst failure here is running outside the
13
+ * workspace.
14
+ */
15
+ import type { CommandPolicy } from "./policy-loader.ts";
16
+ import type { WorkspaceArtifactRef, WorkspaceArtifactStore } from "./workspace/artifacts.ts";
17
+ export type PolicyRunStatus = "passed" | "failed" | "spawn-failed" | "timeout" | "cancelled" | "refused"
18
+ /** PS.2: the command ran but wrote outside its declared scope, so nothing outside was promoted. Never "passed". */
19
+ | "scope-violated";
20
+ export interface PolicyRunOptions {
21
+ workspaceRoot: string;
22
+ signal?: AbortSignal;
23
+ /** When present, the full combined output is retained under the session's artifact policy. */
24
+ artifactStore?: WorkspaceArtifactStore;
25
+ }
26
+ export interface PolicyRunOutcome {
27
+ status: PolicyRunStatus;
28
+ policyId: string;
29
+ executable: string;
30
+ argv: string[];
31
+ /** The working directory the command actually ran in (or would have run in). */
32
+ cwd: string;
33
+ durationMs: number;
34
+ exitCode?: number;
35
+ signal?: string;
36
+ /** True when output was captured only partially because a bound was hit. */
37
+ truncated: boolean;
38
+ stdoutBytes: number;
39
+ stderrBytes: number;
40
+ /** Bounded, sanitized text for the model; full bytes go to the artifact store. */
41
+ outputExcerpt: string;
42
+ artifact?: WorkspaceArtifactRef;
43
+ /** Why a command could not run at all ("refused" / "spawn-failed"). */
44
+ refusalReason?: string;
45
+ }
46
+ export declare function runPolicyCommand(command: CommandPolicy, options: PolicyRunOptions): Promise<PolicyRunOutcome>;
47
+ //# sourceMappingURL=policy-executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-executor.d.ts","sourceRoot":"","sources":["../../src/core/policy-executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAE7F,MAAM,MAAM,eAAe,GACxB,QAAQ,GACR,QAAQ,GACR,cAAc,GACd,SAAS,GACT,WAAW,GACX,SAAS;AACX,mHAAmH;GACjH,gBAAgB,CAAC;AAEpB,MAAM,WAAW,gBAAgB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,8FAA8F;IAC9F,aAAa,CAAC,EAAE,sBAAsB,CAAC;CACvC;AAED,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,eAAe,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AA2ED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgKnH","sourcesContent":["/**\n * VF.3: the bounded argv command executor (spec\n * 2026-09-01-configured-verification-and-formatting.md § 2). Runs a\n * `CommandPolicy` from the VF.2 loader verbatim: executable + argv spawn\n * with no shell, under the policy's numeric bounds, with process-tree\n * termination on timeout and cancellation. Outcomes are structured values;\n * a command that merely fails is data, never a throw.\n *\n * Trusting the loader: this module does NOT re-validate policy fields the\n * loader already guarantees (numeric bounds, shell:false, argv shapes). The\n * one defensive re-check is the cwd confinement, because a resolved path is\n * cheap to verify and the worst failure here is running outside the\n * workspace.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { isAbsolute, relative, resolve } from \"node:path\";\nimport { killProcessTree, sanitizeBinaryOutput } from \"../utils/shell.ts\";\nimport type { CommandPolicy } from \"./policy-loader.ts\";\nimport type { WorkspaceArtifactRef, WorkspaceArtifactStore } from \"./workspace/artifacts.ts\";\n\nexport type PolicyRunStatus =\n\t| \"passed\"\n\t| \"failed\"\n\t| \"spawn-failed\"\n\t| \"timeout\"\n\t| \"cancelled\"\n\t| \"refused\"\n\t/** PS.2: the command ran but wrote outside its declared scope, so nothing outside was promoted. Never \"passed\". */\n\t| \"scope-violated\";\n\nexport interface PolicyRunOptions {\n\tworkspaceRoot: string;\n\tsignal?: AbortSignal;\n\t/** When present, the full combined output is retained under the session's artifact policy. */\n\tartifactStore?: WorkspaceArtifactStore;\n}\n\nexport interface PolicyRunOutcome {\n\tstatus: PolicyRunStatus;\n\tpolicyId: string;\n\texecutable: string;\n\targv: string[];\n\t/** The working directory the command actually ran in (or would have run in). */\n\tcwd: string;\n\tdurationMs: number;\n\texitCode?: number;\n\tsignal?: string;\n\t/** True when output was captured only partially because a bound was hit. */\n\ttruncated: boolean;\n\tstdoutBytes: number;\n\tstderrBytes: number;\n\t/** Bounded, sanitized text for the model; full bytes go to the artifact store. */\n\toutputExcerpt: string;\n\tartifact?: WorkspaceArtifactRef;\n\t/** Why a command could not run at all (\"refused\" / \"spawn-failed\"). */\n\trefusalReason?: string;\n}\n\n/** Hard retention ceiling for artifact bytes, independent of policy caps. */\nconst MAX_RETENTION_BYTES = 8 * 1024 * 1024;\n\ninterface CapturedStream {\n\t/** Full stream bytes up to the retention ceiling — what the artifact keeps. */\n\tchunks: Buffer[];\n\t/** True total bytes seen, even past the retention ceiling. */\n\tbytes: number;\n\t/** Last capBytes of the stream — what the excerpt shows. */\n\twindow: Buffer[];\n\ttruncated: boolean;\n}\n\nfunction newStream(): CapturedStream {\n\treturn { chunks: [], bytes: 0, window: [], truncated: false };\n}\n\nfunction capture(stream: CapturedStream, chunk: Buffer, capBytes: number): void {\n\tstream.bytes += chunk.byteLength;\n\tif (stream.bytes > capBytes) {\n\t\tstream.truncated = true;\n\t}\n\tconst retentionTotal = stream.chunks.reduce((sum, part) => sum + part.byteLength, 0);\n\tif (retentionTotal < MAX_RETENTION_BYTES) {\n\t\tstream.chunks.push(chunk);\n\t}\n\t// Keep the excerpt window bounded to the last capBytes so it shows the\n\t// most recent output, not the start.\n\tstream.window.push(chunk);\n\tconst windowTotal = stream.window.reduce((sum, part) => sum + part.byteLength, 0);\n\tif (windowTotal > capBytes) {\n\t\tstream.truncated = true;\n\t\tlet drop = windowTotal - capBytes;\n\t\twhile (drop > 0 && stream.window.length > 0) {\n\t\t\tconst first = stream.window[0];\n\t\t\tif (first.byteLength <= drop) {\n\t\t\t\tdrop -= first.byteLength;\n\t\t\t\tstream.window.shift();\n\t\t\t} else {\n\t\t\t\tstream.window[0] = first.subarray(drop);\n\t\t\t\tdrop = 0;\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction decode(stream: CapturedStream): string {\n\treturn sanitizeBinaryOutput(Buffer.concat(stream.window).toString(\"utf-8\"));\n}\n\nfunction excerptFor(stdout: CapturedStream, stderr: CapturedStream, policy: CommandPolicy): string {\n\tconst sections: string[] = [];\n\tconst stdoutText = decode(stdout);\n\tconst stderrText = decode(stderr);\n\tif (stdoutText.length > 0) {\n\t\tsections.push(stdoutText);\n\t}\n\tif (stderrText.length > 0) {\n\t\tsections.push(stderrText);\n\t}\n\tconst combined = sections.join(\"\\n\");\n\tconst lines = combined.split(\"\\n\");\n\tif (lines.length > policy.maxOutputLines) {\n\t\t// The notice lives INSIDE the budget: the excerpt never exceeds\n\t\t// maxOutputLines lines in total.\n\t\treturn [\n\t\t\t...lines.slice(0, policy.maxOutputLines - 1),\n\t\t\t`<${lines.length - policy.maxOutputLines + 1} more lines truncated>`,\n\t\t].join(\"\\n\");\n\t}\n\treturn combined;\n}\n\nexport async function runPolicyCommand(command: CommandPolicy, options: PolicyRunOptions): Promise<PolicyRunOutcome> {\n\tconst started = Date.now();\n\tconst base = {\n\t\tpolicyId: command.id,\n\t\texecutable: command.executable,\n\t\targv: command.argv,\n\t};\n\n\tif (options.signal?.aborted) {\n\t\treturn {\n\t\t\t...base,\n\t\t\targv: base.argv,\n\t\t\tcwd: options.workspaceRoot,\n\t\t\tdurationMs: 0,\n\t\t\tstatus: \"cancelled\",\n\t\t\ttruncated: false,\n\t\t\tstdoutBytes: 0,\n\t\t\tstderrBytes: 0,\n\t\t\toutputExcerpt: \"\",\n\t\t\trefusalReason: \"cancelled before the command started\",\n\t\t};\n\t}\n\n\t// Defense in depth against a cwd escaping the workspace. The loader\n\t// already rejects absolute paths and \"..\" segments; resolve() here is the\n\t// second gate, on the computed path rather than the configured string.\n\tconst cwd = command.cwd === \"workspace\" ? options.workspaceRoot : resolve(options.workspaceRoot, command.cwd);\n\t// The containment check uses relative() — a startsWith string comparison\n\t// breaks on Windows, where resolve() switches to backslash separators.\n\tconst containmentRoot = resolve(options.workspaceRoot);\n\tconst containmentRel = relative(containmentRoot, cwd);\n\tconst insideWorkspace = containmentRel === \"\" || (!containmentRel.startsWith(\"..\") && !isAbsolute(containmentRel));\n\tif (!insideWorkspace) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tcwd,\n\t\t\tdurationMs: 0,\n\t\t\tstatus: \"refused\",\n\t\t\ttruncated: false,\n\t\t\tstdoutBytes: 0,\n\t\t\tstderrBytes: 0,\n\t\t\toutputExcerpt: \"\",\n\t\t\trefusalReason: `cwd ${command.cwd} resolves outside the workspace`,\n\t\t};\n\t}\n\n\treturn await new Promise<PolicyRunOutcome>((resolveRun) => {\n\t\tlet child: ReturnType<typeof spawn>;\n\t\ttry {\n\t\t\tchild = spawn(command.executable, command.argv, {\n\t\t\t\tcwd,\n\t\t\t\tshell: false,\n\t\t\t\tdetached: true,\n\t\t\t\twindowsHide: true,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tresolveRun({\n\t\t\t\t...base,\n\t\t\t\tcwd,\n\t\t\t\tdurationMs: Date.now() - started,\n\t\t\t\tstatus: \"spawn-failed\",\n\t\t\t\ttruncated: false,\n\t\t\t\tstdoutBytes: 0,\n\t\t\t\tstderrBytes: 0,\n\t\t\t\toutputExcerpt: \"\",\n\t\t\t\trefusalReason: error instanceof Error ? error.message : String(error),\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst stdout = newStream();\n\t\tconst stderr = newStream();\n\t\tlet settled = false;\n\t\tlet killed: \"timeout\" | \"cancelled\" | undefined;\n\t\tlet spawnError: Error | undefined;\n\n\t\tconst finish = (extra: Partial<PolicyRunOutcome> & { status: PolicyRunStatus }) => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timer);\n\t\t\toptions.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\tconst outputExcerpt = excerptFor(stdout, stderr, command);\n\t\t\t// Only stdout: the artifact is the raw stream. Both streams: a\n\t\t\t// stderr section is appended so the raw stdout bytes stay intact.\n\t\t\tconst retained: Buffer[] = [...stdout.chunks];\n\t\t\tif (stderr.chunks.length > 0) {\n\t\t\t\tif (retained.length > 0) {\n\t\t\t\t\tretained.push(Buffer.from(\"\\n\"));\n\t\t\t\t}\n\t\t\t\tretained.push(Buffer.from(\"stderr:\\n\"), ...stderr.chunks);\n\t\t\t}\n\t\t\tconst artifactPromise =\n\t\t\t\toptions.artifactStore && retained.length > 0\n\t\t\t\t\t? options.artifactStore.writeArtifact(Buffer.concat(retained)).catch(() => undefined)\n\t\t\t\t\t: Promise.resolve(undefined);\n\n\t\t\tvoid artifactPromise.then((artifact) => {\n\t\t\t\tresolveRun({\n\t\t\t\t\t...base,\n\t\t\t\t\tcwd,\n\t\t\t\t\tdurationMs: Date.now() - started,\n\t\t\t\t\ttruncated: stdout.truncated || stderr.truncated,\n\t\t\t\t\tstdoutBytes: stdout.bytes,\n\t\t\t\t\tstderrBytes: stderr.bytes,\n\t\t\t\t\toutputExcerpt,\n\t\t\t\t\t...(artifact !== undefined ? { artifact } : {}),\n\t\t\t\t\t...extra,\n\t\t\t\t});\n\t\t\t});\n\t\t};\n\n\t\tconst onAbort = () => {\n\t\t\tif (settled || child.pid === undefined) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tkilled = \"cancelled\";\n\t\t\tkillProcessTree(child.pid);\n\t\t};\n\t\toptions.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tif (settled || child.pid === undefined) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tkilled = \"timeout\";\n\t\t\tkillProcessTree(child.pid);\n\t\t}, command.timeoutMs);\n\n\t\tchild.once(\"error\", (error) => {\n\t\t\tspawnError = error;\n\t\t});\n\t\tchild.stdout?.on(\"data\", (chunk: Buffer) => capture(stdout, chunk, command.maxOutputBytes));\n\t\tchild.stderr?.on(\"data\", (chunk: Buffer) => capture(stderr, chunk, command.maxOutputBytes));\n\n\t\tchild.once(\"close\", (code, termSignal) => {\n\t\t\tif (spawnError !== undefined) {\n\t\t\t\tfinish({\n\t\t\t\t\tstatus: \"spawn-failed\",\n\t\t\t\t\trefusalReason: spawnError.message,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (killed === \"timeout\") {\n\t\t\t\tfinish({ status: \"timeout\", ...(termSignal ? { signal: termSignal } : {}) });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (killed === \"cancelled\") {\n\t\t\t\tfinish({ status: \"cancelled\", ...(termSignal ? { signal: termSignal } : {}) });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish({\n\t\t\t\tstatus: code === 0 ? \"passed\" : \"failed\",\n\t\t\t\t...(code !== null ? { exitCode: code } : {}),\n\t\t\t\t...(termSignal ? { signal: termSignal } : {}),\n\t\t\t});\n\t\t});\n\t});\n}\n"]}