vellum 0.2.13 → 0.3.2

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 (1019) hide show
  1. package/bin/vellum.js +2 -0
  2. package/package.json +6 -65
  3. package/.dockerignore +0 -27
  4. package/.env.example +0 -22
  5. package/Dockerfile +0 -99
  6. package/Dockerfile.sandbox +0 -5
  7. package/README.md +0 -169
  8. package/bun.lock +0 -1743
  9. package/bunfig.toml +0 -2
  10. package/docs/skills.md +0 -158
  11. package/drizzle/0000_dizzy_maggott.sql +0 -301
  12. package/drizzle/meta/0000_snapshot.json +0 -1999
  13. package/drizzle/meta/_journal.json +0 -13
  14. package/drizzle.config.ts +0 -7
  15. package/eslint.config.mjs +0 -17
  16. package/hook-templates/debug-prompt-logger/hook.json +0 -7
  17. package/hook-templates/debug-prompt-logger/run.sh +0 -68
  18. package/knip.json +0 -9
  19. package/scripts/capture-x-graphql.ts +0 -545
  20. package/scripts/ipc/check-contract-inventory.ts +0 -104
  21. package/scripts/ipc/check-swift-decoder-drift.ts +0 -164
  22. package/scripts/ipc/generate-swift.ts +0 -492
  23. package/scripts/test-filesystem-tools.sh +0 -48
  24. package/scripts/test.sh +0 -127
  25. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -2316
  26. package/src/__tests__/account-registry.test.ts +0 -245
  27. package/src/__tests__/active-skill-tools.test.ts +0 -378
  28. package/src/__tests__/agent-heartbeat-service.test.ts +0 -250
  29. package/src/__tests__/agent-loop-thinking.test.ts +0 -81
  30. package/src/__tests__/agent-loop.test.ts +0 -1135
  31. package/src/__tests__/anthropic-provider.test.ts +0 -778
  32. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -290
  33. package/src/__tests__/app-bundler.test.ts +0 -292
  34. package/src/__tests__/app-executors.test.ts +0 -613
  35. package/src/__tests__/app-open-proxy.test.ts +0 -62
  36. package/src/__tests__/asset-materialize-tool.test.ts +0 -452
  37. package/src/__tests__/asset-search-tool.test.ts +0 -477
  38. package/src/__tests__/assistant-attachment-directive.test.ts +0 -401
  39. package/src/__tests__/assistant-attachments.test.ts +0 -437
  40. package/src/__tests__/assistant-event-hub.test.ts +0 -226
  41. package/src/__tests__/assistant-event.test.ts +0 -123
  42. package/src/__tests__/attachments-store.test.ts +0 -476
  43. package/src/__tests__/attachments.test.ts +0 -134
  44. package/src/__tests__/audit-log-rotation.test.ts +0 -154
  45. package/src/__tests__/browser-fill-credential.test.ts +0 -309
  46. package/src/__tests__/browser-manager.test.ts +0 -203
  47. package/src/__tests__/browser-runtime-check.test.ts +0 -55
  48. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +0 -68
  49. package/src/__tests__/browser-skill-endstate.test.ts +0 -195
  50. package/src/__tests__/bundle-scanner.test.ts +0 -313
  51. package/src/__tests__/call-bridge.test.ts +0 -425
  52. package/src/__tests__/call-constants.test.ts +0 -40
  53. package/src/__tests__/call-orchestrator.test.ts +0 -512
  54. package/src/__tests__/call-recovery.test.ts +0 -518
  55. package/src/__tests__/call-routes-http.test.ts +0 -459
  56. package/src/__tests__/call-state-machine.test.ts +0 -143
  57. package/src/__tests__/call-state.test.ts +0 -174
  58. package/src/__tests__/call-store.test.ts +0 -691
  59. package/src/__tests__/checker.test.ts +0 -3960
  60. package/src/__tests__/clarification-resolver.test.ts +0 -159
  61. package/src/__tests__/classifier.test.ts +0 -67
  62. package/src/__tests__/claude-code-skill-regression.test.ts +0 -127
  63. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -88
  64. package/src/__tests__/cli-discover.test.ts +0 -85
  65. package/src/__tests__/cli.test.ts +0 -81
  66. package/src/__tests__/clipboard.test.ts +0 -80
  67. package/src/__tests__/commit-guarantee.test.ts +0 -335
  68. package/src/__tests__/commit-message-enrichment-service.test.ts +0 -550
  69. package/src/__tests__/compaction.benchmark.test.ts +0 -176
  70. package/src/__tests__/computer-use-session-compaction.test.ts +0 -132
  71. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -293
  72. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -117
  73. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -74
  74. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -89
  75. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -217
  76. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -107
  77. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +0 -54
  78. package/src/__tests__/computer-use-tools.test.ts +0 -250
  79. package/src/__tests__/config-schema.test.ts +0 -1343
  80. package/src/__tests__/conflict-store.test.ts +0 -330
  81. package/src/__tests__/connection-policy.test.ts +0 -102
  82. package/src/__tests__/contacts-tools.test.ts +0 -331
  83. package/src/__tests__/context-memory-e2e.test.ts +0 -434
  84. package/src/__tests__/context-token-estimator.test.ts +0 -135
  85. package/src/__tests__/context-window-manager.test.ts +0 -376
  86. package/src/__tests__/contradiction-checker.test.ts +0 -216
  87. package/src/__tests__/conversation-store.test.ts +0 -612
  88. package/src/__tests__/credential-broker-browser-fill.test.ts +0 -517
  89. package/src/__tests__/credential-broker-server-use.test.ts +0 -554
  90. package/src/__tests__/credential-broker.test.ts +0 -167
  91. package/src/__tests__/credential-host-pattern-match.test.ts +0 -104
  92. package/src/__tests__/credential-metadata-store.test.ts +0 -779
  93. package/src/__tests__/credential-policy-validate.test.ts +0 -121
  94. package/src/__tests__/credential-resolve.test.ts +0 -328
  95. package/src/__tests__/credential-security-e2e.test.ts +0 -352
  96. package/src/__tests__/credential-security-invariants.test.ts +0 -567
  97. package/src/__tests__/credential-selection.test.ts +0 -354
  98. package/src/__tests__/credential-vault.test.ts +0 -852
  99. package/src/__tests__/daemon-assistant-events.test.ts +0 -164
  100. package/src/__tests__/daemon-server-session-init.test.ts +0 -522
  101. package/src/__tests__/date-context.test.ts +0 -373
  102. package/src/__tests__/db-schedule-syntax-migration.test.ts +0 -129
  103. package/src/__tests__/delete-managed-skill-tool.test.ts +0 -97
  104. package/src/__tests__/diff.test.ts +0 -121
  105. package/src/__tests__/domain-normalize.test.ts +0 -112
  106. package/src/__tests__/domain-policy.test.ts +0 -124
  107. package/src/__tests__/doordash-client.test.ts +0 -186
  108. package/src/__tests__/doordash-session.test.ts +0 -152
  109. package/src/__tests__/dynamic-page-surface.test.ts +0 -91
  110. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -132
  111. package/src/__tests__/edit-engine.test.ts +0 -180
  112. package/src/__tests__/elevenlabs-client.test.ts +0 -209
  113. package/src/__tests__/email-cli.test.ts +0 -283
  114. package/src/__tests__/encrypted-store.test.ts +0 -332
  115. package/src/__tests__/entity-extractor.test.ts +0 -190
  116. package/src/__tests__/ephemeral-permissions.test.ts +0 -312
  117. package/src/__tests__/evaluate-typescript-tool.test.ts +0 -286
  118. package/src/__tests__/event-bus.test.ts +0 -222
  119. package/src/__tests__/file-edit-tool.test.ts +0 -122
  120. package/src/__tests__/file-ops-service.test.ts +0 -330
  121. package/src/__tests__/file-read-tool.test.ts +0 -75
  122. package/src/__tests__/file-write-tool.test.ts +0 -113
  123. package/src/__tests__/fixtures/credential-security-fixtures.ts +0 -181
  124. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -126
  125. package/src/__tests__/fixtures/mock-signup-server.ts +0 -387
  126. package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
  127. package/src/__tests__/followup-tools.test.ts +0 -303
  128. package/src/__tests__/forbidden-legacy-symbols.test.ts +0 -71
  129. package/src/__tests__/fuzzy-match-property.test.ts +0 -216
  130. package/src/__tests__/fuzzy-match.test.ts +0 -138
  131. package/src/__tests__/gateway-only-enforcement.test.ts +0 -436
  132. package/src/__tests__/gemini-image-service.test.ts +0 -261
  133. package/src/__tests__/gemini-provider.test.ts +0 -651
  134. package/src/__tests__/get-weather.test.ts +0 -318
  135. package/src/__tests__/gmail-integration.test.ts +0 -73
  136. package/src/__tests__/handlers-cu-observation-blob.test.ts +0 -351
  137. package/src/__tests__/handlers-ipc-blob-probe.test.ts +0 -190
  138. package/src/__tests__/handlers-slack-config.test.ts +0 -199
  139. package/src/__tests__/handlers-task-submit-slash.test.ts +0 -38
  140. package/src/__tests__/handlers-twitter-config.test.ts +0 -718
  141. package/src/__tests__/headless-browser-interactions.test.ts +0 -536
  142. package/src/__tests__/headless-browser-navigate.test.ts +0 -211
  143. package/src/__tests__/headless-browser-read-tools.test.ts +0 -261
  144. package/src/__tests__/headless-browser-snapshot.test.ts +0 -185
  145. package/src/__tests__/history-repair-observability.test.ts +0 -56
  146. package/src/__tests__/history-repair.test.ts +0 -510
  147. package/src/__tests__/home-base-bootstrap.test.ts +0 -82
  148. package/src/__tests__/hooks-blocking.test.ts +0 -128
  149. package/src/__tests__/hooks-cli.test.ts +0 -144
  150. package/src/__tests__/hooks-config.test.ts +0 -93
  151. package/src/__tests__/hooks-discovery.test.ts +0 -199
  152. package/src/__tests__/hooks-integration.test.ts +0 -189
  153. package/src/__tests__/hooks-manager.test.ts +0 -187
  154. package/src/__tests__/hooks-runner.test.ts +0 -178
  155. package/src/__tests__/hooks-settings.test.ts +0 -154
  156. package/src/__tests__/hooks-templates.test.ts +0 -137
  157. package/src/__tests__/hooks-ts-runner.test.ts +0 -125
  158. package/src/__tests__/hooks-watch.test.ts +0 -100
  159. package/src/__tests__/host-file-edit-tool.test.ts +0 -104
  160. package/src/__tests__/host-file-read-tool.test.ts +0 -61
  161. package/src/__tests__/host-file-write-tool.test.ts +0 -77
  162. package/src/__tests__/host-shell-tool.test.ts +0 -311
  163. package/src/__tests__/ingress-url-consistency.test.ts +0 -214
  164. package/src/__tests__/intent-routing.test.ts +0 -259
  165. package/src/__tests__/ipc-blob-store.test.ts +0 -315
  166. package/src/__tests__/ipc-contract-inventory.test.ts +0 -54
  167. package/src/__tests__/ipc-contract.test.ts +0 -74
  168. package/src/__tests__/ipc-protocol.test.ts +0 -113
  169. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +0 -237
  170. package/src/__tests__/ipc-snapshot.test.ts +0 -1698
  171. package/src/__tests__/ipc-validate.test.ts +0 -357
  172. package/src/__tests__/key-migration.test.ts +0 -183
  173. package/src/__tests__/keychain.test.ts +0 -258
  174. package/src/__tests__/llm-usage-store.test.ts +0 -221
  175. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -257
  176. package/src/__tests__/managed-store.test.ts +0 -608
  177. package/src/__tests__/media-generate-image.test.ts +0 -238
  178. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -676
  179. package/src/__tests__/media-visibility-policy.test.ts +0 -141
  180. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -235
  181. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -481
  182. package/src/__tests__/memory-query-builder.test.ts +0 -59
  183. package/src/__tests__/memory-recall-quality.test.ts +0 -846
  184. package/src/__tests__/memory-regressions.experimental.test.ts +0 -538
  185. package/src/__tests__/memory-regressions.test.ts +0 -4336
  186. package/src/__tests__/memory-retrieval-budget.test.ts +0 -49
  187. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -430
  188. package/src/__tests__/migration-cli-flows.test.ts +0 -169
  189. package/src/__tests__/migration-ordering.test.ts +0 -249
  190. package/src/__tests__/mock-signup-server.test.ts +0 -528
  191. package/src/__tests__/oauth-callback-registry.test.ts +0 -85
  192. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -285
  193. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -176
  194. package/src/__tests__/onboarding-template-contract.test.ts +0 -58
  195. package/src/__tests__/openai-provider.test.ts +0 -753
  196. package/src/__tests__/parallel-tool.benchmark.test.ts +0 -294
  197. package/src/__tests__/parser.test.ts +0 -472
  198. package/src/__tests__/path-classifier.test.ts +0 -73
  199. package/src/__tests__/path-policy.test.ts +0 -435
  200. package/src/__tests__/platform-move-helper.test.ts +0 -99
  201. package/src/__tests__/platform-socket-path.test.ts +0 -52
  202. package/src/__tests__/platform-workspace-migration.test.ts +0 -1000
  203. package/src/__tests__/platform.test.ts +0 -131
  204. package/src/__tests__/playbook-tools.test.ts +0 -342
  205. package/src/__tests__/prebuilt-home-base-seed.test.ts +0 -75
  206. package/src/__tests__/pricing.test.ts +0 -256
  207. package/src/__tests__/profile-compiler.test.ts +0 -374
  208. package/src/__tests__/provider-commit-message-generator.test.ts +0 -342
  209. package/src/__tests__/provider-registry-ollama.test.ts +0 -16
  210. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -773
  211. package/src/__tests__/proxy-approval-callback.test.ts +0 -601
  212. package/src/__tests__/public-ingress-urls.test.ts +0 -222
  213. package/src/__tests__/ratelimit.test.ts +0 -297
  214. package/src/__tests__/recurrence-engine-rruleset.test.ts +0 -78
  215. package/src/__tests__/recurrence-engine.test.ts +0 -69
  216. package/src/__tests__/recurrence-types.test.ts +0 -71
  217. package/src/__tests__/registry.test.ts +0 -494
  218. package/src/__tests__/relay-server.test.ts +0 -688
  219. package/src/__tests__/reminder-store.test.ts +0 -223
  220. package/src/__tests__/reminder.test.ts +0 -229
  221. package/src/__tests__/request-file-tool.test.ts +0 -158
  222. package/src/__tests__/run-orchestrator-assistant-events.test.ts +0 -222
  223. package/src/__tests__/run-orchestrator.test.ts +0 -200
  224. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -189
  225. package/src/__tests__/runtime-events-sse-parity.test.ts +0 -343
  226. package/src/__tests__/runtime-events-sse.test.ts +0 -162
  227. package/src/__tests__/runtime-runs-http.test.ts +0 -433
  228. package/src/__tests__/runtime-runs.test.ts +0 -273
  229. package/src/__tests__/sandbox-diagnostics.test.ts +0 -408
  230. package/src/__tests__/sandbox-host-parity.test.ts +0 -950
  231. package/src/__tests__/scaffold-managed-skill-tool.test.ts +0 -253
  232. package/src/__tests__/schedule-store.test.ts +0 -482
  233. package/src/__tests__/schedule-tools.test.ts +0 -700
  234. package/src/__tests__/scheduler-recurrence.test.ts +0 -329
  235. package/src/__tests__/script-proxy-certs.test.ts +0 -90
  236. package/src/__tests__/script-proxy-connect-tunnel.test.ts +0 -177
  237. package/src/__tests__/script-proxy-decision-trace.test.ts +0 -156
  238. package/src/__tests__/script-proxy-http-forwarder.test.ts +0 -281
  239. package/src/__tests__/script-proxy-injection-runtime.test.ts +0 -401
  240. package/src/__tests__/script-proxy-mitm-handler.test.ts +0 -407
  241. package/src/__tests__/script-proxy-policy-runtime.test.ts +0 -287
  242. package/src/__tests__/script-proxy-policy.test.ts +0 -310
  243. package/src/__tests__/script-proxy-rewrite-specificity.test.ts +0 -135
  244. package/src/__tests__/script-proxy-router.test.ts +0 -180
  245. package/src/__tests__/script-proxy-session-manager.test.ts +0 -382
  246. package/src/__tests__/script-proxy-session-runtime.test.ts +0 -113
  247. package/src/__tests__/secret-allowlist.test.ts +0 -229
  248. package/src/__tests__/secret-ingress-handler.test.ts +0 -99
  249. package/src/__tests__/secret-onetime-send.test.ts +0 -130
  250. package/src/__tests__/secret-prompt-log-hygiene.test.ts +0 -106
  251. package/src/__tests__/secret-response-routing.test.ts +0 -93
  252. package/src/__tests__/secret-scanner-executor.test.ts +0 -348
  253. package/src/__tests__/secret-scanner.test.ts +0 -857
  254. package/src/__tests__/secure-keys.test.ts +0 -323
  255. package/src/__tests__/server-history-render.test.ts +0 -431
  256. package/src/__tests__/session-abort-tool-results.test.ts +0 -240
  257. package/src/__tests__/session-conflict-gate.test.ts +0 -700
  258. package/src/__tests__/session-error.test.ts +0 -369
  259. package/src/__tests__/session-evictor.test.ts +0 -188
  260. package/src/__tests__/session-init.benchmark.test.ts +0 -462
  261. package/src/__tests__/session-load-history-repair.test.ts +0 -222
  262. package/src/__tests__/session-pre-run-repair.test.ts +0 -213
  263. package/src/__tests__/session-profile-injection.test.ts +0 -444
  264. package/src/__tests__/session-provider-retry-repair.test.ts +0 -306
  265. package/src/__tests__/session-queue.test.ts +0 -1535
  266. package/src/__tests__/session-runtime-assembly.test.ts +0 -476
  267. package/src/__tests__/session-runtime-workspace.test.ts +0 -183
  268. package/src/__tests__/session-skill-tools.test.ts +0 -2431
  269. package/src/__tests__/session-slash-known.test.ts +0 -368
  270. package/src/__tests__/session-slash-queue.test.ts +0 -288
  271. package/src/__tests__/session-slash-unknown.test.ts +0 -271
  272. package/src/__tests__/session-surfaces-task-progress.test.ts +0 -104
  273. package/src/__tests__/session-tool-setup-app-refresh.test.ts +0 -473
  274. package/src/__tests__/session-tool-setup-memory-scope.test.ts +0 -140
  275. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +0 -140
  276. package/src/__tests__/session-undo.test.ts +0 -75
  277. package/src/__tests__/session-workspace-cache-state.test.ts +0 -246
  278. package/src/__tests__/session-workspace-injection.test.ts +0 -327
  279. package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -240
  280. package/src/__tests__/shared-filesystem-errors.test.ts +0 -78
  281. package/src/__tests__/shell-credential-ref.test.ts +0 -187
  282. package/src/__tests__/shell-parser-fuzz.test.ts +0 -544
  283. package/src/__tests__/shell-parser-property.test.ts +0 -433
  284. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -272
  285. package/src/__tests__/signup-e2e.test.ts +0 -353
  286. package/src/__tests__/size-guard.test.ts +0 -117
  287. package/src/__tests__/skill-include-graph.test.ts +0 -303
  288. package/src/__tests__/skill-load-tool.test.ts +0 -409
  289. package/src/__tests__/skill-projection.benchmark.test.ts +0 -328
  290. package/src/__tests__/skill-script-runner-host.test.ts +0 -489
  291. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -349
  292. package/src/__tests__/skill-script-runner.test.ts +0 -159
  293. package/src/__tests__/skill-tool-factory.test.ts +0 -252
  294. package/src/__tests__/skill-tool-manifest.test.ts +0 -658
  295. package/src/__tests__/skill-version-hash.test.ts +0 -182
  296. package/src/__tests__/skills.test.ts +0 -680
  297. package/src/__tests__/slash-commands-catalog.test.ts +0 -86
  298. package/src/__tests__/slash-commands-parser.test.ts +0 -119
  299. package/src/__tests__/slash-commands-resolver.test.ts +0 -193
  300. package/src/__tests__/slash-commands-rewrite.test.ts +0 -39
  301. package/src/__tests__/speaker-identification.test.ts +0 -52
  302. package/src/__tests__/starter-bundle.test.ts +0 -136
  303. package/src/__tests__/starter-task-flow.test.ts +0 -143
  304. package/src/__tests__/subagent-manager-notify.test.ts +0 -404
  305. package/src/__tests__/subagent-tools.test.ts +0 -218
  306. package/src/__tests__/subagent-types.test.ts +0 -78
  307. package/src/__tests__/swarm-orchestrator.test.ts +0 -428
  308. package/src/__tests__/swarm-plan-validator.test.ts +0 -330
  309. package/src/__tests__/swarm-recursion.test.ts +0 -165
  310. package/src/__tests__/swarm-router-planner.test.ts +0 -208
  311. package/src/__tests__/swarm-session-integration.test.ts +0 -274
  312. package/src/__tests__/swarm-tool.test.ts +0 -145
  313. package/src/__tests__/swarm-worker-backend.test.ts +0 -129
  314. package/src/__tests__/swarm-worker-runner.test.ts +0 -272
  315. package/src/__tests__/system-prompt.test.ts +0 -439
  316. package/src/__tests__/task-compiler.test.ts +0 -284
  317. package/src/__tests__/task-runner.test.ts +0 -216
  318. package/src/__tests__/task-scheduler.test.ts +0 -217
  319. package/src/__tests__/task-tools.test.ts +0 -595
  320. package/src/__tests__/terminal-sandbox-docker.test.ts +0 -1064
  321. package/src/__tests__/terminal-sandbox.integration.test.ts +0 -178
  322. package/src/__tests__/terminal-sandbox.test.ts +0 -202
  323. package/src/__tests__/test-support/browser-skill-harness.ts +0 -90
  324. package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -45
  325. package/src/__tests__/tool-audit-listener.test.ts +0 -113
  326. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -253
  327. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -500
  328. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -516
  329. package/src/__tests__/tool-executor-redaction.test.ts +0 -289
  330. package/src/__tests__/tool-executor.test.ts +0 -2055
  331. package/src/__tests__/tool-metrics-listener.test.ts +0 -225
  332. package/src/__tests__/tool-notification-listener.test.ts +0 -49
  333. package/src/__tests__/tool-policy.test.ts +0 -54
  334. package/src/__tests__/tool-profiling-listener.test.ts +0 -268
  335. package/src/__tests__/tool-result-truncation.test.ts +0 -217
  336. package/src/__tests__/tool-trace-listener.test.ts +0 -226
  337. package/src/__tests__/top-level-renderer.test.ts +0 -121
  338. package/src/__tests__/top-level-scanner.test.ts +0 -141
  339. package/src/__tests__/trace-emitter.test.ts +0 -173
  340. package/src/__tests__/trust-store.test.ts +0 -2031
  341. package/src/__tests__/turn-commit.test.ts +0 -554
  342. package/src/__tests__/twilio-provider.test.ts +0 -179
  343. package/src/__tests__/twilio-routes-twiml.test.ts +0 -127
  344. package/src/__tests__/twilio-routes.test.ts +0 -822
  345. package/src/__tests__/twitter-auth-handler.test.ts +0 -666
  346. package/src/__tests__/url-safety.test.ts +0 -418
  347. package/src/__tests__/view-image-tool.test.ts +0 -217
  348. package/src/__tests__/weather-skill-regression.test.ts +0 -225
  349. package/src/__tests__/web-fetch.test.ts +0 -869
  350. package/src/__tests__/web-search.test.ts +0 -584
  351. package/src/__tests__/workspace-git-service.test.ts +0 -1153
  352. package/src/__tests__/workspace-heartbeat-service.test.ts +0 -486
  353. package/src/__tests__/workspace-lifecycle.test.ts +0 -292
  354. package/src/agent/attachments.ts +0 -35
  355. package/src/agent/loop.ts +0 -500
  356. package/src/agent/message-types.ts +0 -17
  357. package/src/agent-heartbeat/agent-heartbeat-service.ts +0 -155
  358. package/src/autonomy/autonomy-resolver.ts +0 -60
  359. package/src/autonomy/autonomy-store.ts +0 -122
  360. package/src/autonomy/disposition-mapper.ts +0 -31
  361. package/src/autonomy/index.ts +0 -11
  362. package/src/autonomy/types.ts +0 -39
  363. package/src/bundler/app-bundler.ts +0 -295
  364. package/src/bundler/bundle-scanner.ts +0 -535
  365. package/src/bundler/bundle-signer.ts +0 -124
  366. package/src/bundler/manifest.ts +0 -21
  367. package/src/bundler/signature-verifier.ts +0 -184
  368. package/src/calls/call-bridge.ts +0 -95
  369. package/src/calls/call-constants.ts +0 -48
  370. package/src/calls/call-domain.ts +0 -278
  371. package/src/calls/call-orchestrator.ts +0 -412
  372. package/src/calls/call-recovery.ts +0 -207
  373. package/src/calls/call-state-machine.ts +0 -68
  374. package/src/calls/call-state.ts +0 -87
  375. package/src/calls/call-store.ts +0 -416
  376. package/src/calls/elevenlabs-client.ts +0 -89
  377. package/src/calls/elevenlabs-config.ts +0 -29
  378. package/src/calls/relay-server.ts +0 -390
  379. package/src/calls/speaker-identification.ts +0 -213
  380. package/src/calls/twilio-config.ts +0 -45
  381. package/src/calls/twilio-provider.ts +0 -178
  382. package/src/calls/twilio-routes.ts +0 -316
  383. package/src/calls/types.ts +0 -37
  384. package/src/calls/voice-provider.ts +0 -14
  385. package/src/calls/voice-quality.ts +0 -92
  386. package/src/cli/autonomy.ts +0 -188
  387. package/src/cli/config-commands.ts +0 -334
  388. package/src/cli/contacts.ts +0 -149
  389. package/src/cli/core-commands.ts +0 -784
  390. package/src/cli/doordash.ts +0 -1055
  391. package/src/cli/email-guardrails.ts +0 -200
  392. package/src/cli/email.ts +0 -405
  393. package/src/cli/ipc-client.ts +0 -82
  394. package/src/cli/main-screen.tsx +0 -53
  395. package/src/cli/map.ts +0 -270
  396. package/src/cli/twitter.ts +0 -575
  397. package/src/cli.ts +0 -937
  398. package/src/commands/__tests__/cc-command-registry.test.ts +0 -319
  399. package/src/commands/cc-command-registry.ts +0 -209
  400. package/src/config/bundled-skills/.gitkeep +0 -0
  401. package/src/config/bundled-skills/agentmail/SKILL.md +0 -128
  402. package/src/config/bundled-skills/agentmail/icon.svg +0 -21
  403. package/src/config/bundled-skills/app-builder/SKILL.md +0 -1404
  404. package/src/config/bundled-skills/app-builder/TOOLS.json +0 -279
  405. package/src/config/bundled-skills/app-builder/icon.svg +0 -9
  406. package/src/config/bundled-skills/app-builder/tools/app-create.ts +0 -15
  407. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +0 -10
  408. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -11
  409. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -10
  410. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -18
  411. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -11
  412. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -10
  413. package/src/config/bundled-skills/app-builder/tools/app-query.ts +0 -10
  414. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -20
  415. package/src/config/bundled-skills/browser/SKILL.md +0 -28
  416. package/src/config/bundled-skills/browser/TOOLS.json +0 -234
  417. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -9
  418. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -9
  419. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -9
  420. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -9
  421. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -9
  422. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -9
  423. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -9
  424. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -9
  425. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -9
  426. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -9
  427. package/src/config/bundled-skills/claude-code/SKILL.md +0 -50
  428. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -40
  429. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -9
  430. package/src/config/bundled-skills/computer-use/SKILL.md +0 -17
  431. package/src/config/bundled-skills/computer-use/TOOLS.json +0 -326
  432. package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +0 -9
  433. package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +0 -9
  434. package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +0 -9
  435. package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +0 -9
  436. package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +0 -9
  437. package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +0 -9
  438. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -9
  439. package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +0 -9
  440. package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +0 -9
  441. package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +0 -9
  442. package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +0 -9
  443. package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +0 -9
  444. package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +0 -9
  445. package/src/config/bundled-skills/contacts/SKILL.md +0 -39
  446. package/src/config/bundled-skills/contacts/TOOLS.json +0 -122
  447. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +0 -9
  448. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -9
  449. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -9
  450. package/src/config/bundled-skills/document/SKILL.md +0 -26
  451. package/src/config/bundled-skills/document/TOOLS.json +0 -53
  452. package/src/config/bundled-skills/document/tools/document-create.ts +0 -9
  453. package/src/config/bundled-skills/document/tools/document-update.ts +0 -9
  454. package/src/config/bundled-skills/doordash/SKILL.md +0 -163
  455. package/src/config/bundled-skills/followups/SKILL.md +0 -32
  456. package/src/config/bundled-skills/followups/TOOLS.json +0 -100
  457. package/src/config/bundled-skills/followups/icon.svg +0 -24
  458. package/src/config/bundled-skills/followups/tools/followup-create.ts +0 -9
  459. package/src/config/bundled-skills/followups/tools/followup-list.ts +0 -9
  460. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +0 -9
  461. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  462. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -108
  463. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -165
  464. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -21
  465. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -42
  466. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -13
  467. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -30
  468. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -41
  469. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -18
  470. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  471. package/src/config/bundled-skills/image-studio/SKILL.md +0 -32
  472. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -42
  473. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +0 -115
  474. package/src/config/bundled-skills/macos-automation/SKILL.md +0 -66
  475. package/src/config/bundled-skills/messaging/SKILL.md +0 -130
  476. package/src/config/bundled-skills/messaging/TOOLS.json +0 -357
  477. package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +0 -23
  478. package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +0 -23
  479. package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +0 -25
  480. package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +0 -26
  481. package/src/config/bundled-skills/messaging/tools/gmail-label.ts +0 -25
  482. package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +0 -23
  483. package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +0 -84
  484. package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +0 -18
  485. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +0 -125
  486. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +0 -16
  487. package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +0 -49
  488. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +0 -21
  489. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +0 -25
  490. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +0 -28
  491. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +0 -29
  492. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +0 -22
  493. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -27
  494. package/src/config/bundled-skills/messaging/tools/shared.ts +0 -71
  495. package/src/config/bundled-skills/messaging/tools/slack-add-reaction.ts +0 -25
  496. package/src/config/bundled-skills/messaging/tools/slack-leave-channel.ts +0 -23
  497. package/src/config/bundled-skills/phone-calls/SKILL.md +0 -414
  498. package/src/config/bundled-skills/playbooks/SKILL.md +0 -31
  499. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -126
  500. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +0 -9
  501. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +0 -9
  502. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +0 -9
  503. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +0 -9
  504. package/src/config/bundled-skills/public-ingress/SKILL.md +0 -183
  505. package/src/config/bundled-skills/reminder/SKILL.md +0 -20
  506. package/src/config/bundled-skills/reminder/TOOLS.json +0 -67
  507. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +0 -9
  508. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +0 -9
  509. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +0 -9
  510. package/src/config/bundled-skills/schedule/SKILL.md +0 -74
  511. package/src/config/bundled-skills/schedule/TOOLS.json +0 -135
  512. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +0 -9
  513. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +0 -9
  514. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +0 -9
  515. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +0 -9
  516. package/src/config/bundled-skills/self-upgrade/SKILL.md +0 -68
  517. package/src/config/bundled-skills/start-the-day/SKILL.md +0 -70
  518. package/src/config/bundled-skills/start-the-day/icon.svg +0 -13
  519. package/src/config/bundled-skills/subagent/SKILL.md +0 -25
  520. package/src/config/bundled-skills/subagent/TOOLS.json +0 -107
  521. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +0 -9
  522. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +0 -9
  523. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +0 -9
  524. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +0 -9
  525. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +0 -9
  526. package/src/config/bundled-skills/tasks/SKILL.md +0 -28
  527. package/src/config/bundled-skills/tasks/TOOLS.json +0 -281
  528. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -9
  529. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -9
  530. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -9
  531. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -9
  532. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -9
  533. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -9
  534. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -9
  535. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -9
  536. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -9
  537. package/src/config/bundled-skills/transcribe/SKILL.md +0 -25
  538. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -32
  539. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +0 -370
  540. package/src/config/bundled-skills/twitter/SKILL.md +0 -134
  541. package/src/config/bundled-skills/watcher/SKILL.md +0 -27
  542. package/src/config/bundled-skills/watcher/TOOLS.json +0 -147
  543. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -9
  544. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -9
  545. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -9
  546. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -9
  547. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -9
  548. package/src/config/bundled-skills/weather/SKILL.md +0 -37
  549. package/src/config/bundled-skills/weather/TOOLS.json +0 -32
  550. package/src/config/bundled-skills/weather/icon.svg +0 -24
  551. package/src/config/bundled-skills/weather/tools/get-weather.ts +0 -9
  552. package/src/config/computer-use-prompt.ts +0 -97
  553. package/src/config/defaults.ts +0 -252
  554. package/src/config/loader.ts +0 -339
  555. package/src/config/schema.ts +0 -1356
  556. package/src/config/skill-state.ts +0 -95
  557. package/src/config/skills.ts +0 -972
  558. package/src/config/system-prompt.ts +0 -675
  559. package/src/config/templates/BOOTSTRAP.md +0 -70
  560. package/src/config/templates/IDENTITY.md +0 -25
  561. package/src/config/templates/LOOKS.md +0 -25
  562. package/src/config/templates/SOUL.md +0 -37
  563. package/src/config/templates/USER.md +0 -19
  564. package/src/config/types.ts +0 -40
  565. package/src/config/vellum-skills/deploy-fullstack-vercel/SKILL.md +0 -179
  566. package/src/config/vellum-skills/document-writer/SKILL.md +0 -195
  567. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
  568. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +0 -153
  569. package/src/config/vellum-skills/telegram-setup/SKILL.md +0 -102
  570. package/src/contacts/contact-store.ts +0 -410
  571. package/src/contacts/index.ts +0 -11
  572. package/src/contacts/types.ts +0 -28
  573. package/src/context/token-estimator.ts +0 -108
  574. package/src/context/tool-result-truncation.ts +0 -128
  575. package/src/context/window-manager.ts +0 -531
  576. package/src/daemon/assistant-attachments.ts +0 -689
  577. package/src/daemon/classifier.ts +0 -110
  578. package/src/daemon/computer-use-session.ts +0 -903
  579. package/src/daemon/connection-policy.ts +0 -41
  580. package/src/daemon/date-context.ts +0 -136
  581. package/src/daemon/handlers/apps.ts +0 -461
  582. package/src/daemon/handlers/browser.ts +0 -54
  583. package/src/daemon/handlers/computer-use.ts +0 -187
  584. package/src/daemon/handlers/config.ts +0 -707
  585. package/src/daemon/handlers/diagnostics.ts +0 -338
  586. package/src/daemon/handlers/documents.ts +0 -173
  587. package/src/daemon/handlers/home-base.ts +0 -78
  588. package/src/daemon/handlers/identity.ts +0 -127
  589. package/src/daemon/handlers/index.ts +0 -128
  590. package/src/daemon/handlers/misc.ts +0 -331
  591. package/src/daemon/handlers/open-bundle-handler.ts +0 -80
  592. package/src/daemon/handlers/publish.ts +0 -187
  593. package/src/daemon/handlers/sessions.ts +0 -539
  594. package/src/daemon/handlers/shared.ts +0 -569
  595. package/src/daemon/handlers/signing.ts +0 -37
  596. package/src/daemon/handlers/skills.ts +0 -501
  597. package/src/daemon/handlers/subagents.ts +0 -210
  598. package/src/daemon/handlers/twitter-auth.ts +0 -198
  599. package/src/daemon/handlers/work-items.ts +0 -632
  600. package/src/daemon/handlers/workspace-files.ts +0 -75
  601. package/src/daemon/handlers.ts +0 -17
  602. package/src/daemon/history-repair.ts +0 -214
  603. package/src/daemon/ipc-blob-store.ts +0 -231
  604. package/src/daemon/ipc-contract-inventory.json +0 -463
  605. package/src/daemon/ipc-contract-inventory.ts +0 -126
  606. package/src/daemon/ipc-contract.ts +0 -2352
  607. package/src/daemon/ipc-protocol.ts +0 -75
  608. package/src/daemon/ipc-validate.ts +0 -171
  609. package/src/daemon/lifecycle.ts +0 -580
  610. package/src/daemon/main.ts +0 -21
  611. package/src/daemon/media-visibility-policy.ts +0 -57
  612. package/src/daemon/ride-shotgun-handler.ts +0 -309
  613. package/src/daemon/server.ts +0 -1207
  614. package/src/daemon/session-agent-loop.ts +0 -922
  615. package/src/daemon/session-attachments.ts +0 -196
  616. package/src/daemon/session-conflict-gate.ts +0 -128
  617. package/src/daemon/session-dynamic-profile.ts +0 -63
  618. package/src/daemon/session-error.ts +0 -290
  619. package/src/daemon/session-evictor.ts +0 -196
  620. package/src/daemon/session-history.ts +0 -437
  621. package/src/daemon/session-lifecycle.ts +0 -147
  622. package/src/daemon/session-media-retry.ts +0 -147
  623. package/src/daemon/session-memory.ts +0 -212
  624. package/src/daemon/session-messaging.ts +0 -145
  625. package/src/daemon/session-notifiers.ts +0 -193
  626. package/src/daemon/session-process.ts +0 -264
  627. package/src/daemon/session-queue-manager.ts +0 -82
  628. package/src/daemon/session-runtime-assembly.ts +0 -447
  629. package/src/daemon/session-skill-tools.ts +0 -356
  630. package/src/daemon/session-slash.ts +0 -305
  631. package/src/daemon/session-surfaces.ts +0 -702
  632. package/src/daemon/session-tool-setup.ts +0 -524
  633. package/src/daemon/session-usage.ts +0 -72
  634. package/src/daemon/session-workspace.ts +0 -19
  635. package/src/daemon/session.ts +0 -400
  636. package/src/daemon/trace-emitter.ts +0 -82
  637. package/src/daemon/video-thumbnail.ts +0 -60
  638. package/src/daemon/watch-handler.ts +0 -274
  639. package/src/doordash/client.ts +0 -999
  640. package/src/doordash/queries.ts +0 -1311
  641. package/src/doordash/query-extractor.ts +0 -93
  642. package/src/doordash/session.ts +0 -82
  643. package/src/email/provider.ts +0 -117
  644. package/src/email/providers/agentmail.ts +0 -317
  645. package/src/email/providers/index.ts +0 -58
  646. package/src/email/service.ts +0 -303
  647. package/src/email/types.ts +0 -126
  648. package/src/events/bus.ts +0 -157
  649. package/src/events/domain-events.ts +0 -83
  650. package/src/events/index.ts +0 -18
  651. package/src/events/tool-audit-listener.ts +0 -80
  652. package/src/events/tool-domain-event-publisher.ts +0 -111
  653. package/src/events/tool-metrics-listener.ts +0 -159
  654. package/src/events/tool-notification-listener.ts +0 -17
  655. package/src/events/tool-profiling-listener.ts +0 -158
  656. package/src/events/tool-trace-listener.ts +0 -75
  657. package/src/export/formatter.ts +0 -98
  658. package/src/followups/followup-store.ts +0 -168
  659. package/src/followups/index.ts +0 -10
  660. package/src/followups/types.ts +0 -29
  661. package/src/gallery/default-gallery.ts +0 -795
  662. package/src/gallery/gallery-manifest.ts +0 -24
  663. package/src/home-base/app-link-store.ts +0 -82
  664. package/src/home-base/bootstrap.ts +0 -68
  665. package/src/home-base/prebuilt/index.html +0 -662
  666. package/src/home-base/prebuilt/seed-metadata.json +0 -21
  667. package/src/home-base/prebuilt/seed.ts +0 -112
  668. package/src/home-base/prebuilt-home-base-updater.ts +0 -30
  669. package/src/hooks/cli.ts +0 -163
  670. package/src/hooks/config.ts +0 -88
  671. package/src/hooks/discovery.ts +0 -110
  672. package/src/hooks/manager.ts +0 -128
  673. package/src/hooks/runner.ts +0 -123
  674. package/src/hooks/templates.ts +0 -52
  675. package/src/hooks/types.ts +0 -72
  676. package/src/inbound/public-ingress-urls.ts +0 -123
  677. package/src/index.ts +0 -75
  678. package/src/instrument.ts +0 -60
  679. package/src/logfire.ts +0 -99
  680. package/src/media/gemini-image-service.ts +0 -136
  681. package/src/memory/account-store.ts +0 -108
  682. package/src/memory/admin.ts +0 -211
  683. package/src/memory/app-store.ts +0 -556
  684. package/src/memory/attachments-store.ts +0 -397
  685. package/src/memory/channel-delivery-store.ts +0 -353
  686. package/src/memory/checkpoints.ts +0 -52
  687. package/src/memory/clarification-resolver.ts +0 -298
  688. package/src/memory/conflict-intent.ts +0 -114
  689. package/src/memory/conflict-store.ts +0 -342
  690. package/src/memory/contradiction-checker.ts +0 -330
  691. package/src/memory/conversation-key-store.ts +0 -107
  692. package/src/memory/conversation-store.ts +0 -470
  693. package/src/memory/db.ts +0 -1825
  694. package/src/memory/embedding-backend.ts +0 -229
  695. package/src/memory/embedding-gemini.ts +0 -52
  696. package/src/memory/embedding-local.ts +0 -75
  697. package/src/memory/embedding-ollama.ts +0 -55
  698. package/src/memory/embedding-openai.ts +0 -25
  699. package/src/memory/entity-extractor.ts +0 -474
  700. package/src/memory/fingerprint.ts +0 -20
  701. package/src/memory/indexer.ts +0 -156
  702. package/src/memory/items-extractor.ts +0 -461
  703. package/src/memory/job-handlers/backfill.ts +0 -139
  704. package/src/memory/job-handlers/cleanup.ts +0 -58
  705. package/src/memory/job-handlers/conflict.ts +0 -121
  706. package/src/memory/job-handlers/embedding.ts +0 -61
  707. package/src/memory/job-handlers/extraction.ts +0 -123
  708. package/src/memory/job-handlers/index-maintenance.ts +0 -54
  709. package/src/memory/job-handlers/summarization.ts +0 -286
  710. package/src/memory/job-utils.ts +0 -170
  711. package/src/memory/jobs-store.ts +0 -401
  712. package/src/memory/jobs-worker.ts +0 -274
  713. package/src/memory/llm-request-log-store.ts +0 -45
  714. package/src/memory/llm-usage-store.ts +0 -60
  715. package/src/memory/message-content.ts +0 -54
  716. package/src/memory/profile-compiler.ts +0 -160
  717. package/src/memory/published-pages-store.ts +0 -137
  718. package/src/memory/qdrant-client.ts +0 -366
  719. package/src/memory/qdrant-manager.ts +0 -242
  720. package/src/memory/query-builder.ts +0 -45
  721. package/src/memory/retrieval-budget.ts +0 -30
  722. package/src/memory/retriever.ts +0 -653
  723. package/src/memory/runs-store.ts +0 -255
  724. package/src/memory/schema.ts +0 -588
  725. package/src/memory/search/entity.ts +0 -298
  726. package/src/memory/search/formatting.ts +0 -207
  727. package/src/memory/search/lexical.ts +0 -227
  728. package/src/memory/search/ranking.ts +0 -401
  729. package/src/memory/search/semantic.ts +0 -121
  730. package/src/memory/search/types.ts +0 -137
  731. package/src/memory/segmenter.ts +0 -68
  732. package/src/memory/shared-app-links-store.ts +0 -138
  733. package/src/memory/tool-usage-store.ts +0 -62
  734. package/src/messaging/activity-analyzer.ts +0 -76
  735. package/src/messaging/draft-store.ts +0 -88
  736. package/src/messaging/index.ts +0 -3
  737. package/src/messaging/provider-types.ts +0 -80
  738. package/src/messaging/provider.ts +0 -43
  739. package/src/messaging/providers/gmail/adapter.ts +0 -193
  740. package/src/messaging/providers/gmail/client.ts +0 -204
  741. package/src/messaging/providers/gmail/types.ts +0 -90
  742. package/src/messaging/providers/slack/adapter.ts +0 -202
  743. package/src/messaging/providers/slack/client.ts +0 -198
  744. package/src/messaging/providers/slack/types.ts +0 -119
  745. package/src/messaging/registry.ts +0 -34
  746. package/src/messaging/style-analyzer.ts +0 -159
  747. package/src/messaging/thread-summarizer.ts +0 -306
  748. package/src/messaging/triage-engine.ts +0 -323
  749. package/src/messaging/types.ts +0 -55
  750. package/src/permissions/checker.ts +0 -636
  751. package/src/permissions/defaults.ts +0 -254
  752. package/src/permissions/prompter.ts +0 -102
  753. package/src/permissions/secret-prompter.ts +0 -114
  754. package/src/permissions/trust-store.ts +0 -584
  755. package/src/permissions/types.ts +0 -62
  756. package/src/playbooks/index.ts +0 -2
  757. package/src/playbooks/playbook-compiler.ts +0 -90
  758. package/src/playbooks/types.ts +0 -55
  759. package/src/providers/anthropic/client.ts +0 -751
  760. package/src/providers/failover.ts +0 -129
  761. package/src/providers/fireworks/client.ts +0 -20
  762. package/src/providers/gemini/client.ts +0 -285
  763. package/src/providers/ollama/client.ts +0 -30
  764. package/src/providers/openai/client.ts +0 -337
  765. package/src/providers/openrouter/client.ts +0 -20
  766. package/src/providers/ratelimit.ts +0 -93
  767. package/src/providers/registry.ts +0 -146
  768. package/src/providers/retry.ts +0 -106
  769. package/src/providers/stream-timeout.ts +0 -38
  770. package/src/providers/types.ts +0 -109
  771. package/src/runtime/assistant-event-hub.ts +0 -120
  772. package/src/runtime/assistant-event.ts +0 -82
  773. package/src/runtime/gateway-client.ts +0 -42
  774. package/src/runtime/http-server.ts +0 -1056
  775. package/src/runtime/http-types.ts +0 -66
  776. package/src/runtime/routes/app-routes.ts +0 -174
  777. package/src/runtime/routes/attachment-routes.ts +0 -133
  778. package/src/runtime/routes/call-routes.ts +0 -140
  779. package/src/runtime/routes/channel-routes.ts +0 -382
  780. package/src/runtime/routes/conversation-routes.ts +0 -352
  781. package/src/runtime/routes/events-routes.ts +0 -79
  782. package/src/runtime/routes/run-routes.ts +0 -262
  783. package/src/runtime/routes/secret-routes.ts +0 -76
  784. package/src/runtime/run-orchestrator.ts +0 -296
  785. package/src/schedule/recurrence-engine.ts +0 -138
  786. package/src/schedule/recurrence-types.ts +0 -67
  787. package/src/schedule/schedule-store.ts +0 -497
  788. package/src/schedule/scheduler.ts +0 -171
  789. package/src/security/encrypted-store.ts +0 -238
  790. package/src/security/keychain.ts +0 -252
  791. package/src/security/oauth-callback-registry.ts +0 -66
  792. package/src/security/oauth2.ts +0 -274
  793. package/src/security/redaction.ts +0 -89
  794. package/src/security/secret-allowlist.ts +0 -164
  795. package/src/security/secret-ingress.ts +0 -57
  796. package/src/security/secret-scanner.ts +0 -543
  797. package/src/security/secure-keys.ts +0 -180
  798. package/src/security/token-manager.ts +0 -141
  799. package/src/services/published-app-updater.ts +0 -69
  800. package/src/services/vercel-deploy.ts +0 -73
  801. package/src/skills/active-skill-tools.ts +0 -81
  802. package/src/skills/clawhub.ts +0 -414
  803. package/src/skills/include-graph.ts +0 -146
  804. package/src/skills/managed-store.ts +0 -233
  805. package/src/skills/path-classifier.ts +0 -128
  806. package/src/skills/slash-commands.ts +0 -174
  807. package/src/skills/tool-manifest.ts +0 -165
  808. package/src/skills/version-hash.ts +0 -110
  809. package/src/slack/slack-webhook.ts +0 -61
  810. package/src/subagent/index.ts +0 -19
  811. package/src/subagent/manager.ts +0 -511
  812. package/src/subagent/types.ts +0 -69
  813. package/src/swarm/backend-claude-code.ts +0 -145
  814. package/src/swarm/index.ts +0 -44
  815. package/src/swarm/limits.ts +0 -37
  816. package/src/swarm/orchestrator.ts +0 -279
  817. package/src/swarm/plan-validator.ts +0 -151
  818. package/src/swarm/router-planner.ts +0 -100
  819. package/src/swarm/router-prompts.ts +0 -36
  820. package/src/swarm/synthesizer.ts +0 -62
  821. package/src/swarm/types.ts +0 -62
  822. package/src/swarm/worker-backend.ts +0 -121
  823. package/src/swarm/worker-prompts.ts +0 -79
  824. package/src/swarm/worker-runner.ts +0 -164
  825. package/src/tasks/SPEC.md +0 -139
  826. package/src/tasks/candidate-store.ts +0 -86
  827. package/src/tasks/ephemeral-permissions.ts +0 -50
  828. package/src/tasks/task-compiler.ts +0 -199
  829. package/src/tasks/task-runner.ts +0 -90
  830. package/src/tasks/task-scheduler.ts +0 -20
  831. package/src/tasks/task-store.ts +0 -127
  832. package/src/tasks/tool-sanitizer.ts +0 -36
  833. package/src/tools/apps/definitions.ts +0 -59
  834. package/src/tools/apps/executors.ts +0 -313
  835. package/src/tools/apps/open-proxy.ts +0 -43
  836. package/src/tools/apps/registry.ts +0 -16
  837. package/src/tools/assets/materialize.ts +0 -218
  838. package/src/tools/assets/search.ts +0 -361
  839. package/src/tools/browser/__tests__/auth-cache.test.ts +0 -219
  840. package/src/tools/browser/__tests__/auth-detector.test.ts +0 -362
  841. package/src/tools/browser/__tests__/jit-auth.test.ts +0 -189
  842. package/src/tools/browser/api-map.ts +0 -293
  843. package/src/tools/browser/auth-cache.ts +0 -149
  844. package/src/tools/browser/auth-detector.ts +0 -347
  845. package/src/tools/browser/auto-navigate.ts +0 -270
  846. package/src/tools/browser/browser-execution.ts +0 -980
  847. package/src/tools/browser/browser-handoff.ts +0 -79
  848. package/src/tools/browser/browser-manager.ts +0 -715
  849. package/src/tools/browser/browser-screencast.ts +0 -217
  850. package/src/tools/browser/headless-browser.ts +0 -450
  851. package/src/tools/browser/jit-auth.ts +0 -51
  852. package/src/tools/browser/network-recorder.ts +0 -349
  853. package/src/tools/browser/network-recording-types.ts +0 -49
  854. package/src/tools/browser/recording-store.ts +0 -49
  855. package/src/tools/browser/runtime-check.ts +0 -43
  856. package/src/tools/browser/x-auto-navigate.ts +0 -207
  857. package/src/tools/calls/call-end.ts +0 -67
  858. package/src/tools/calls/call-start.ts +0 -73
  859. package/src/tools/calls/call-status.ts +0 -81
  860. package/src/tools/claude-code/claude-code.ts +0 -428
  861. package/src/tools/computer-use/definitions.ts +0 -443
  862. package/src/tools/computer-use/registry.ts +0 -22
  863. package/src/tools/computer-use/request-computer-control.ts +0 -53
  864. package/src/tools/computer-use/skill-proxy-bridge.ts +0 -28
  865. package/src/tools/contacts/contact-merge.ts +0 -55
  866. package/src/tools/contacts/contact-search.ts +0 -58
  867. package/src/tools/contacts/contact-upsert.ts +0 -64
  868. package/src/tools/credentials/account-registry.ts +0 -127
  869. package/src/tools/credentials/broker-types.ts +0 -107
  870. package/src/tools/credentials/broker.ts +0 -372
  871. package/src/tools/credentials/domain-policy.ts +0 -51
  872. package/src/tools/credentials/host-pattern-match.ts +0 -60
  873. package/src/tools/credentials/metadata-store.ts +0 -335
  874. package/src/tools/credentials/policy-types.ts +0 -52
  875. package/src/tools/credentials/policy-validate.ts +0 -80
  876. package/src/tools/credentials/resolve.ts +0 -122
  877. package/src/tools/credentials/selection.ts +0 -159
  878. package/src/tools/credentials/tool-policy.ts +0 -25
  879. package/src/tools/credentials/vault.ts +0 -657
  880. package/src/tools/document/document-tool.ts +0 -92
  881. package/src/tools/document/editor-template.ts +0 -237
  882. package/src/tools/executor.ts +0 -944
  883. package/src/tools/filesystem/edit.ts +0 -127
  884. package/src/tools/filesystem/fuzzy-match.ts +0 -202
  885. package/src/tools/filesystem/read.ts +0 -71
  886. package/src/tools/filesystem/view-image.ts +0 -199
  887. package/src/tools/filesystem/write.ts +0 -79
  888. package/src/tools/followups/followup_create.ts +0 -76
  889. package/src/tools/followups/followup_list.ts +0 -60
  890. package/src/tools/followups/followup_resolve.ts +0 -56
  891. package/src/tools/host-filesystem/edit.ts +0 -125
  892. package/src/tools/host-filesystem/read.ts +0 -80
  893. package/src/tools/host-filesystem/write.ts +0 -76
  894. package/src/tools/host-terminal/cli-discover.ts +0 -180
  895. package/src/tools/host-terminal/host-shell.ts +0 -191
  896. package/src/tools/memory/definitions.ts +0 -69
  897. package/src/tools/memory/handlers.ts +0 -246
  898. package/src/tools/memory/register.ts +0 -66
  899. package/src/tools/network/__tests__/web-search.test.ts +0 -427
  900. package/src/tools/network/domain-normalize.ts +0 -85
  901. package/src/tools/network/script-proxy/__tests__/logging.test.ts +0 -248
  902. package/src/tools/network/script-proxy/__tests__/policy.test.ts +0 -234
  903. package/src/tools/network/script-proxy/__tests__/router.test.ts +0 -76
  904. package/src/tools/network/script-proxy/certs.ts +0 -237
  905. package/src/tools/network/script-proxy/connect-tunnel.ts +0 -82
  906. package/src/tools/network/script-proxy/http-forwarder.ts +0 -151
  907. package/src/tools/network/script-proxy/index.ts +0 -28
  908. package/src/tools/network/script-proxy/logging.ts +0 -196
  909. package/src/tools/network/script-proxy/mitm-handler.ts +0 -269
  910. package/src/tools/network/script-proxy/policy.ts +0 -152
  911. package/src/tools/network/script-proxy/router.ts +0 -60
  912. package/src/tools/network/script-proxy/server.ts +0 -136
  913. package/src/tools/network/script-proxy/session-manager.ts +0 -534
  914. package/src/tools/network/script-proxy/types.ts +0 -125
  915. package/src/tools/network/url-safety.ts +0 -227
  916. package/src/tools/network/web-fetch.ts +0 -713
  917. package/src/tools/network/web-search.ts +0 -319
  918. package/src/tools/playbooks/index.ts +0 -4
  919. package/src/tools/playbooks/playbook-create.ts +0 -96
  920. package/src/tools/playbooks/playbook-delete.ts +0 -52
  921. package/src/tools/playbooks/playbook-list.ts +0 -74
  922. package/src/tools/playbooks/playbook-update.ts +0 -111
  923. package/src/tools/registry.ts +0 -295
  924. package/src/tools/reminder/reminder-store.ts +0 -148
  925. package/src/tools/reminder/reminder.ts +0 -80
  926. package/src/tools/schedule/create.ts +0 -81
  927. package/src/tools/schedule/delete.ts +0 -28
  928. package/src/tools/schedule/list.ts +0 -69
  929. package/src/tools/schedule/update.ts +0 -90
  930. package/src/tools/shared/filesystem/edit-engine.ts +0 -56
  931. package/src/tools/shared/filesystem/errors.ts +0 -85
  932. package/src/tools/shared/filesystem/file-ops-service.ts +0 -215
  933. package/src/tools/shared/filesystem/format-diff.ts +0 -35
  934. package/src/tools/shared/filesystem/path-policy.ts +0 -125
  935. package/src/tools/shared/filesystem/size-guard.ts +0 -41
  936. package/src/tools/shared/filesystem/types.ts +0 -80
  937. package/src/tools/shared/shell-output.ts +0 -52
  938. package/src/tools/skills/delete-managed.ts +0 -60
  939. package/src/tools/skills/load.ts +0 -139
  940. package/src/tools/skills/sandbox-runner.ts +0 -279
  941. package/src/tools/skills/scaffold-managed.ts +0 -150
  942. package/src/tools/skills/script-contract.ts +0 -6
  943. package/src/tools/skills/skill-script-runner.ts +0 -86
  944. package/src/tools/skills/skill-tool-factory.ts +0 -64
  945. package/src/tools/skills/vellum-catalog.ts +0 -217
  946. package/src/tools/subagent/abort.ts +0 -33
  947. package/src/tools/subagent/message.ts +0 -39
  948. package/src/tools/subagent/read.ts +0 -67
  949. package/src/tools/subagent/spawn.ts +0 -46
  950. package/src/tools/subagent/status.ts +0 -45
  951. package/src/tools/swarm/delegate.ts +0 -183
  952. package/src/tools/system/request-permission.ts +0 -98
  953. package/src/tools/system/version.ts +0 -43
  954. package/src/tools/tasks/index.ts +0 -27
  955. package/src/tools/tasks/task-delete.ts +0 -82
  956. package/src/tools/tasks/task-list.ts +0 -44
  957. package/src/tools/tasks/task-run.ts +0 -97
  958. package/src/tools/tasks/task-save.ts +0 -47
  959. package/src/tools/tasks/work-item-enqueue.ts +0 -234
  960. package/src/tools/tasks/work-item-list.ts +0 -55
  961. package/src/tools/tasks/work-item-remove.ts +0 -60
  962. package/src/tools/tasks/work-item-run.ts +0 -78
  963. package/src/tools/tasks/work-item-update.ts +0 -114
  964. package/src/tools/terminal/backends/docker.ts +0 -372
  965. package/src/tools/terminal/backends/native.ts +0 -190
  966. package/src/tools/terminal/backends/types.ts +0 -26
  967. package/src/tools/terminal/evaluate-typescript.ts +0 -275
  968. package/src/tools/terminal/parser.ts +0 -415
  969. package/src/tools/terminal/safe-env.ts +0 -37
  970. package/src/tools/terminal/sandbox-diagnostics.ts +0 -149
  971. package/src/tools/terminal/sandbox.ts +0 -44
  972. package/src/tools/terminal/shell.ts +0 -257
  973. package/src/tools/tool-manifest.ts +0 -198
  974. package/src/tools/types.ts +0 -183
  975. package/src/tools/ui-surface/definitions.ts +0 -244
  976. package/src/tools/ui-surface/registry.ts +0 -14
  977. package/src/tools/watch/screen-watch.ts +0 -130
  978. package/src/tools/watch/watch-state.ts +0 -119
  979. package/src/tools/watcher/create.ts +0 -64
  980. package/src/tools/watcher/delete.ts +0 -27
  981. package/src/tools/watcher/digest.ts +0 -50
  982. package/src/tools/watcher/list.ts +0 -60
  983. package/src/tools/watcher/update.ts +0 -56
  984. package/src/tools/weather/service.ts +0 -551
  985. package/src/twitter/client.ts +0 -690
  986. package/src/twitter/session.ts +0 -91
  987. package/src/usage/actors.ts +0 -24
  988. package/src/usage/types.ts +0 -37
  989. package/src/util/clipboard.ts +0 -33
  990. package/src/util/content-id.ts +0 -16
  991. package/src/util/diff.ts +0 -181
  992. package/src/util/errors.ts +0 -129
  993. package/src/util/logger.ts +0 -243
  994. package/src/util/platform.ts +0 -607
  995. package/src/util/pricing.ts +0 -150
  996. package/src/util/spinner.ts +0 -51
  997. package/src/util/time.ts +0 -16
  998. package/src/util/truncate.ts +0 -6
  999. package/src/util/xml.ts +0 -4
  1000. package/src/version.ts +0 -3
  1001. package/src/watcher/constants.ts +0 -11
  1002. package/src/watcher/engine.ts +0 -199
  1003. package/src/watcher/provider-registry.ts +0 -15
  1004. package/src/watcher/provider-types.ts +0 -48
  1005. package/src/watcher/providers/gmail.ts +0 -198
  1006. package/src/watcher/providers/google-calendar.ts +0 -228
  1007. package/src/watcher/providers/slack.ts +0 -129
  1008. package/src/watcher/watcher-store.ts +0 -419
  1009. package/src/work-items/work-item-runner.ts +0 -171
  1010. package/src/work-items/work-item-store.ts +0 -325
  1011. package/src/workspace/commit-message-enrichment-service.ts +0 -284
  1012. package/src/workspace/commit-message-provider.ts +0 -95
  1013. package/src/workspace/git-service.ts +0 -840
  1014. package/src/workspace/heartbeat-service.ts +0 -345
  1015. package/src/workspace/provider-commit-message-generator.ts +0 -285
  1016. package/src/workspace/top-level-renderer.ts +0 -19
  1017. package/src/workspace/top-level-scanner.ts +0 -41
  1018. package/src/workspace/turn-commit.ts +0 -175
  1019. package/tsconfig.json +0 -21
@@ -1,2031 +0,0 @@
1
- import { describe, test, expect, beforeEach, mock, spyOn } from 'bun:test';
2
- import * as fs from 'node:fs';
3
- import { mkdtempSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { join, dirname } from 'node:path';
6
-
7
- // Create a temp directory for the trust file
8
- const testDir = mkdtempSync(join(tmpdir(), 'trust-store-test-'));
9
-
10
- // Mock platform module so trust-store writes to temp dir instead of ~/.vellum
11
- mock.module('../util/platform.js', () => ({
12
- getRootDir: () => testDir,
13
- getDataDir: () => testDir,
14
- isMacOS: () => process.platform === 'darwin',
15
- isLinux: () => process.platform === 'linux',
16
- isWindows: () => process.platform === 'win32',
17
- getSocketPath: () => join(testDir, 'test.sock'),
18
- getPidPath: () => join(testDir, 'test.pid'),
19
- getDbPath: () => join(testDir, 'test.db'),
20
- getLogPath: () => join(testDir, 'test.log'),
21
- ensureDataDir: () => {},
22
- }));
23
-
24
- // Mock logger to suppress output during tests
25
- mock.module('../util/logger.js', () => ({
26
- getLogger: () => ({
27
- info: () => {},
28
- warn: () => {},
29
- error: () => {},
30
- debug: () => {},
31
- trace: () => {},
32
- fatal: () => {},
33
- child: () => ({
34
- info: () => {},
35
- warn: () => {},
36
- error: () => {},
37
- debug: () => {},
38
- }),
39
- }),
40
- }));
41
-
42
- import { addRule, removeRule, updateRule, findMatchingRule, findDenyRule, findHighestPriorityRule, getAllRules, clearAllRules, clearCache } from '../permissions/trust-store.js';
43
- import { getDefaultRuleTemplates } from '../permissions/defaults.js';
44
-
45
- const trustPath = join(testDir, 'protected', 'trust.json');
46
- const DEFAULT_TEMPLATES = getDefaultRuleTemplates();
47
- const NUM_DEFAULTS = DEFAULT_TEMPLATES.length;
48
- const DEFAULT_PRIORITY_BY_ID = new Map(DEFAULT_TEMPLATES.map((t) => [t.id, t.priority]));
49
-
50
- describe('Trust Store', () => {
51
- beforeEach(() => {
52
- // Clear cached rules and remove the trust file between tests
53
- clearCache();
54
- try { rmSync(trustPath); } catch { /* may not exist */ }
55
- });
56
-
57
- // Intentionally do not remove `testDir` in afterAll.
58
- // A late async log flush can still attempt to open `test.log` under this dir,
59
- // which intermittently causes an unhandled ENOENT in CI if the dir is removed.
60
- // ── addRule ─────────────────────────────────────────────────────
61
-
62
- describe('addRule', () => {
63
- test('adds a rule and returns it', () => {
64
- const rule = addRule('bash', 'git *', '/home/user/project');
65
- expect(rule.id).toBeDefined();
66
- expect(rule.tool).toBe('bash');
67
- expect(rule.pattern).toBe('git *');
68
- expect(rule.scope).toBe('/home/user/project');
69
- expect(rule.decision).toBe('allow');
70
- expect(rule.priority).toBe(100);
71
- expect(rule.createdAt).toBeGreaterThan(0);
72
- });
73
-
74
- test('assigns unique IDs to each rule', () => {
75
- const rule1 = addRule('bash', 'npm *', '/tmp');
76
- const rule2 = addRule('bash', 'bun *', '/tmp');
77
- expect(rule1.id).not.toBe(rule2.id);
78
- });
79
-
80
- test('persists rule to disk', () => {
81
- addRule('bash', 'git push', '/home/user');
82
- const raw = readFileSync(trustPath, 'utf-8');
83
- const data = JSON.parse(raw);
84
- expect(data.version).toBe(3);
85
- expect(data.rules).toHaveLength(1 + NUM_DEFAULTS);
86
- const userRule = data.rules.find((r: { pattern: string }) => r.pattern === 'git push');
87
- expect(userRule).toBeDefined();
88
- expect(userRule.priority).toBe(100);
89
- });
90
-
91
- test('multiple rules accumulate', () => {
92
- addRule('bash', 'git *', '/tmp');
93
- addRule('file_write', '/tmp/*', '/tmp');
94
- addRule('bash', 'npm *', '/tmp');
95
- expect(getAllRules()).toHaveLength(3 + NUM_DEFAULTS);
96
- });
97
-
98
- test('default priority is 100', () => {
99
- const rule = addRule('bash', 'git *', '/tmp');
100
- expect(rule.priority).toBe(100);
101
- });
102
-
103
- test('custom priority is respected', () => {
104
- const rule = addRule('bash', 'git *', '/tmp', 'allow', 5);
105
- expect(rule.priority).toBe(5);
106
- });
107
-
108
- test('rules are sorted by priority descending in getAllRules', () => {
109
- addRule('bash', 'low *', '/tmp', 'allow', 0);
110
- addRule('bash', 'high *', '/tmp', 'allow', 2);
111
- addRule('bash', 'med *', '/tmp', 'allow', 1);
112
- const rules = getAllRules();
113
- // Default ask rules have higher priority than user rules
114
- const maxDefaultPriority = Math.max(...DEFAULT_TEMPLATES.map((t) => t.priority));
115
- expect(rules[0].priority).toBe(maxDefaultPriority);
116
- const userRules = rules.filter((r) => !r.id.startsWith('default:'));
117
- expect(userRules[0].priority).toBe(2);
118
- expect(userRules[1].priority).toBe(1);
119
- expect(userRules[2].priority).toBe(0);
120
- });
121
-
122
- test('accepts allowHighRisk option and persists it', () => {
123
- const rule = addRule('bash', 'sudo *', 'everywhere', 'allow', 100, { allowHighRisk: true });
124
- expect(rule.allowHighRisk).toBe(true);
125
- // Verify it persists to disk
126
- clearCache();
127
- const rules = getAllRules();
128
- const found = rules.find((r) => r.id === rule.id);
129
- expect(found).toBeDefined();
130
- expect(found!.allowHighRisk).toBe(true);
131
- });
132
-
133
- test('addRule without allowHighRisk option does not set the field', () => {
134
- const rule = addRule('bash', 'git *', '/tmp');
135
- expect(rule.allowHighRisk).toBeUndefined();
136
- // Verify on disk
137
- const raw = JSON.parse(readFileSync(trustPath, 'utf-8'));
138
- const diskRule = raw.rules.find((r: { id: string }) => r.id === rule.id);
139
- expect(diskRule).toBeDefined();
140
- expect(diskRule).not.toHaveProperty('allowHighRisk');
141
- });
142
-
143
- test('at same priority deny rules sort before allow rules', () => {
144
- addRule('bash', 'allow *', '/tmp', 'allow', 100);
145
- addRule('bash', 'deny *', '/tmp', 'deny', 100);
146
- const userRules = getAllRules().filter((r) => !r.id.startsWith('default:'));
147
- expect(userRules[0].decision).toBe('deny');
148
- expect(userRules[1].decision).toBe('allow');
149
- });
150
-
151
- test('accepts principal and executionTarget options and persists them', () => {
152
- const rule = addRule('skill_tool', 'skill_tool:*', '/tmp', 'allow', 100, {
153
- principalKind: 'skill',
154
- principalId: 'my-skill-42',
155
- principalVersion: 'sha256-abc123',
156
- executionTarget: 'sandbox',
157
- });
158
- expect(rule.principalKind).toBe('skill');
159
- expect(rule.principalId).toBe('my-skill-42');
160
- expect(rule.principalVersion).toBe('sha256-abc123');
161
- expect(rule.executionTarget).toBe('sandbox');
162
-
163
- // Verify persistence to disk
164
- clearCache();
165
- const rules = getAllRules();
166
- const found = rules.find((r) => r.id === rule.id);
167
- expect(found).toBeDefined();
168
- expect(found!.principalKind).toBe('skill');
169
- expect(found!.principalId).toBe('my-skill-42');
170
- expect(found!.principalVersion).toBe('sha256-abc123');
171
- expect(found!.executionTarget).toBe('sandbox');
172
- });
173
-
174
- test('accepts all contextual options together (principal, target, allowHighRisk)', () => {
175
- const rule = addRule('risky_tool', 'risky_tool:*', 'everywhere', 'allow', 100, {
176
- allowHighRisk: true,
177
- principalKind: 'skill',
178
- principalId: 'dangerous-skill',
179
- principalVersion: 'sha256-deadbeef',
180
- executionTarget: 'host',
181
- });
182
- expect(rule.allowHighRisk).toBe(true);
183
- expect(rule.principalKind).toBe('skill');
184
- expect(rule.principalId).toBe('dangerous-skill');
185
- expect(rule.principalVersion).toBe('sha256-deadbeef');
186
- expect(rule.executionTarget).toBe('host');
187
-
188
- // Verify on disk
189
- const raw = JSON.parse(readFileSync(trustPath, 'utf-8'));
190
- const diskRule = raw.rules.find((r: { id: string }) => r.id === rule.id);
191
- expect(diskRule).toBeDefined();
192
- expect(diskRule.allowHighRisk).toBe(true);
193
- expect(diskRule.principalKind).toBe('skill');
194
- expect(diskRule.principalId).toBe('dangerous-skill');
195
- expect(diskRule.principalVersion).toBe('sha256-deadbeef');
196
- expect(diskRule.executionTarget).toBe('host');
197
- });
198
-
199
- test('addRule without principal options does not set principal fields', () => {
200
- const rule = addRule('bash', 'echo *', '/tmp');
201
- expect(rule.principalKind).toBeUndefined();
202
- expect(rule.principalId).toBeUndefined();
203
- expect(rule.principalVersion).toBeUndefined();
204
- expect(rule.executionTarget).toBeUndefined();
205
-
206
- // Verify on disk
207
- const raw = JSON.parse(readFileSync(trustPath, 'utf-8'));
208
- const diskRule = raw.rules.find((r: { id: string }) => r.id === rule.id);
209
- expect(diskRule).toBeDefined();
210
- expect(diskRule).not.toHaveProperty('principalKind');
211
- expect(diskRule).not.toHaveProperty('principalId');
212
- expect(diskRule).not.toHaveProperty('principalVersion');
213
- expect(diskRule).not.toHaveProperty('executionTarget');
214
- });
215
- });
216
-
217
- // ── removeRule ──────────────────────────────────────────────────
218
-
219
- describe('removeRule', () => {
220
- test('removes an existing rule', () => {
221
- const rule = addRule('bash', 'git *', '/tmp');
222
- expect(removeRule(rule.id)).toBe(true);
223
- expect(getAllRules()).toHaveLength(NUM_DEFAULTS);
224
- });
225
-
226
- test('returns false for non-existent ID', () => {
227
- expect(removeRule('non-existent-id')).toBe(false);
228
- });
229
-
230
- test('persists removal to disk', () => {
231
- const rule = addRule('bash', 'npm *', '/tmp');
232
- removeRule(rule.id);
233
- // Reload from disk to verify
234
- clearCache();
235
- expect(getAllRules()).toHaveLength(NUM_DEFAULTS);
236
- });
237
-
238
- test('only removes the targeted rule', () => {
239
- const rule1 = addRule('bash', 'git *', '/tmp');
240
- const rule2 = addRule('bash', 'npm *', '/tmp');
241
- removeRule(rule1.id);
242
- const remaining = getAllRules();
243
- expect(remaining).toHaveLength(1 + NUM_DEFAULTS);
244
- expect(remaining.find((r) => r.id === rule2.id)).toBeDefined();
245
- });
246
- });
247
-
248
- // ── updateRule ─────────────────────────────────────────────────
249
-
250
- describe('updateRule', () => {
251
- test('updates pattern on an existing rule', () => {
252
- const rule = addRule('bash', 'git *', '/tmp');
253
- const updated = updateRule(rule.id, { pattern: 'git push *' });
254
- expect(updated.pattern).toBe('git push *');
255
- expect(updated.id).toBe(rule.id);
256
- expect(updated.tool).toBe('bash');
257
- });
258
-
259
- test('updates multiple fields at once', () => {
260
- const rule = addRule('bash', 'npm *', '/tmp');
261
- const updated = updateRule(rule.id, { tool: 'file_write', scope: '/home', decision: 'deny', priority: 50 });
262
- expect(updated.tool).toBe('file_write');
263
- expect(updated.scope).toBe('/home');
264
- expect(updated.decision).toBe('deny');
265
- expect(updated.priority).toBe(50);
266
- });
267
-
268
- test('throws for non-existent rule ID', () => {
269
- expect(() => updateRule('non-existent-id', { pattern: 'test' })).toThrow('Trust rule not found: non-existent-id');
270
- });
271
-
272
- test('persists update to disk', () => {
273
- const rule = addRule('bash', 'git *', '/tmp');
274
- updateRule(rule.id, { pattern: 'git status' });
275
- clearCache();
276
- const rules = getAllRules();
277
- const found = rules.find((r) => r.id === rule.id);
278
- expect(found).toBeDefined();
279
- expect(found!.pattern).toBe('git status');
280
- });
281
-
282
- test('re-sorts rules after priority change', () => {
283
- const rule1 = addRule('bash', 'low *', '/tmp', 'allow', 10);
284
- const rule2 = addRule('bash', 'high *', '/tmp', 'allow', 200);
285
- // rule2 should be first (higher priority)
286
- let userRules = getAllRules().filter((r) => !r.id.startsWith('default:'));
287
- expect(userRules[0].id).toBe(rule2.id);
288
- // Update rule1 to have higher priority
289
- updateRule(rule1.id, { priority: 300 });
290
- userRules = getAllRules().filter((r) => !r.id.startsWith('default:'));
291
- expect(userRules[0].id).toBe(rule1.id);
292
- });
293
-
294
- test('leaves unchanged fields intact', () => {
295
- const rule = addRule('bash', 'git *', '/home/user', 'allow', 100);
296
- updateRule(rule.id, { pattern: 'git push *' });
297
- const updated = getAllRules().find((r) => r.id === rule.id)!;
298
- expect(updated.tool).toBe('bash');
299
- expect(updated.scope).toBe('/home/user');
300
- expect(updated.decision).toBe('allow');
301
- expect(updated.priority).toBe(100);
302
- expect(updated.createdAt).toBe(rule.createdAt);
303
- });
304
- });
305
-
306
- // ── findMatchingRule ────────────────────────────────────────────
307
-
308
- describe('findMatchingRule', () => {
309
- test('finds exact match', () => {
310
- addRule('bash', 'git push', '/tmp');
311
- const match = findMatchingRule('bash', 'git push', '/tmp');
312
- expect(match).not.toBeNull();
313
- expect(match!.pattern).toBe('git push');
314
- });
315
-
316
- test('finds glob wildcard match', () => {
317
- addRule('bash', 'git *', '/tmp');
318
- const match = findMatchingRule('bash', 'git push origin main', '/tmp');
319
- expect(match).not.toBeNull();
320
- });
321
-
322
- test('returns null when tool does not match', () => {
323
- addRule('file_write', 'git *', '/tmp');
324
- // host_bash default is 'ask' so findMatchingRule (allow-only) won't find it
325
- const match = findMatchingRule('host_bash', 'git push', '/tmp');
326
- expect(match).toBeNull();
327
- });
328
-
329
- test('returns null when pattern does not match', () => {
330
- addRule('host_bash', 'git *', '/tmp');
331
- const match = findMatchingRule('host_bash', 'npm install', '/tmp');
332
- expect(match).toBeNull();
333
- });
334
-
335
- // Scope matching
336
- describe('scope matching', () => {
337
- test('matches when scope equals rule scope', () => {
338
- addRule('bash', 'npm *', '/home/user/project');
339
- const match = findMatchingRule('bash', 'npm install', '/home/user/project');
340
- expect(match).not.toBeNull();
341
- });
342
-
343
- test('matches when scope is under rule scope (prefix)', () => {
344
- addRule('bash', 'npm *', '/home/user');
345
- const match = findMatchingRule('bash', 'npm install', '/home/user/project/sub');
346
- expect(match).not.toBeNull();
347
- });
348
-
349
- test('does not match when scope is outside rule scope', () => {
350
- addRule('host_bash', 'npm *', '/home/user/project');
351
- const match = findMatchingRule('host_bash', 'npm install', '/home/other');
352
- expect(match).toBeNull();
353
- });
354
-
355
- test('everywhere scope matches any directory', () => {
356
- addRule('bash', 'git *', 'everywhere');
357
- const match = findMatchingRule('bash', 'git status', '/any/random/path');
358
- expect(match).not.toBeNull();
359
- });
360
-
361
- test('everywhere scope matches root', () => {
362
- addRule('bash', 'ls', 'everywhere');
363
- const match = findMatchingRule('bash', 'ls', '/');
364
- expect(match).not.toBeNull();
365
- });
366
-
367
- test('does not match sibling path with shared prefix', () => {
368
- addRule('host_bash', 'npm *', '/home/user/project');
369
- const match = findMatchingRule('host_bash', 'npm install', '/home/user/project-evil');
370
- expect(match).toBeNull();
371
- });
372
-
373
- test('matches exact scope with trailing slash on working dir', () => {
374
- addRule('bash', 'npm *', '/home/user/project');
375
- const match = findMatchingRule('bash', 'npm install', '/home/user/project/');
376
- expect(match).not.toBeNull();
377
- });
378
-
379
- test('matches when rule scope has trailing slash', () => {
380
- addRule('bash', 'npm *', '/home/user/project/');
381
- const match = findMatchingRule('bash', 'npm install', '/home/user/project');
382
- expect(match).not.toBeNull();
383
- });
384
-
385
- test('does not match sibling with glob-suffixed scope', () => {
386
- addRule('host_bash', 'npm *', '/home/user/project*');
387
- const match = findMatchingRule('host_bash', 'npm install', '/home/user/project-evil');
388
- expect(match).toBeNull();
389
- });
390
- });
391
-
392
- // Pattern matching with minimatch
393
- describe('pattern matching', () => {
394
- test('matches * wildcard', () => {
395
- addRule('bash', 'npm *', '/tmp');
396
- expect(findMatchingRule('bash', 'npm install', '/tmp')).not.toBeNull();
397
- expect(findMatchingRule('bash', 'npm test', '/tmp')).not.toBeNull();
398
- });
399
-
400
- test('matches exact string', () => {
401
- addRule('host_bash', 'git status', '/tmp');
402
- expect(findMatchingRule('host_bash', 'git status', '/tmp')).not.toBeNull();
403
- expect(findMatchingRule('host_bash', 'git push', '/tmp')).toBeNull();
404
- });
405
-
406
- test('matches file path pattern', () => {
407
- addRule('file_write', '/tmp/*', '/tmp');
408
- expect(findMatchingRule('file_write', '/tmp/file.txt', '/tmp')).not.toBeNull();
409
- });
410
-
411
- test('star pattern matches single-segment strings', () => {
412
- addRule('file_write', '*', '/tmp');
413
- // minimatch '*' matches strings without path separators
414
- expect(findMatchingRule('file_write', 'file.txt', '/tmp')).not.toBeNull();
415
- });
416
-
417
- test('star pattern does not match paths with slashes', () => {
418
- addRule('file_write', '*', '/tmp');
419
- // minimatch '*' does not cross '/' boundaries
420
- expect(findMatchingRule('file_write', '/any/path/file.txt', '/tmp')).toBeNull();
421
- });
422
- });
423
- });
424
-
425
- // ── findHighestPriorityRule ──────────────────────────────────────
426
-
427
- describe('findHighestPriorityRule', () => {
428
- test('returns highest priority matching rule', () => {
429
- addRule('bash', 'rm *', '/tmp', 'allow', 0);
430
- addRule('bash', 'rm *', '/tmp', 'deny', 100);
431
- const match = findHighestPriorityRule('bash', ['rm file.txt'], '/tmp');
432
- expect(match).not.toBeNull();
433
- expect(match!.decision).toBe('deny');
434
- expect(match!.priority).toBe(100);
435
- });
436
-
437
- test('higher priority allow beats lower priority deny', () => {
438
- addRule('bash', 'rm *', '/tmp', 'deny', 0);
439
- addRule('bash', 'rm *', '/tmp', 'allow', 100);
440
- const match = findHighestPriorityRule('bash', ['rm file.txt'], '/tmp');
441
- expect(match).not.toBeNull();
442
- expect(match!.decision).toBe('allow');
443
- });
444
-
445
- test('same priority: deny beats allow', () => {
446
- addRule('bash', 'rm *', '/tmp', 'allow', 100);
447
- addRule('bash', 'rm *', '/tmp', 'deny', 100);
448
- const match = findHighestPriorityRule('bash', ['rm file.txt'], '/tmp');
449
- expect(match).not.toBeNull();
450
- expect(match!.decision).toBe('deny');
451
- });
452
-
453
- test('checks multiple command candidates', () => {
454
- addRule('web_fetch', 'web_fetch:https://example.com/*', '/tmp', 'allow');
455
- const match = findHighestPriorityRule(
456
- 'web_fetch',
457
- ['web_fetch:https://example.com/page', 'web_fetch:https://example.com/*'],
458
- '/tmp',
459
- );
460
- expect(match).not.toBeNull();
461
- });
462
-
463
- test('returns null when no rule matches', () => {
464
- // Use file_read with a non-workspace path — file_read defaults only
465
- // cover specific workspace files, so /tmp paths won't match any default.
466
- addRule('file_read', 'file_read:/specific/*', '/tmp', 'allow');
467
- const match = findHighestPriorityRule('file_read', ['file_read:/other/path'], '/tmp');
468
- expect(match).toBeNull();
469
- });
470
-
471
- test('respects scope matching', () => {
472
- // Use file_read — bash has a global default allow rule that matches everywhere.
473
- addRule('file_read', 'file_read:/home/user/project/*', '/home/user/project', 'deny');
474
- expect(findHighestPriorityRule('file_read', ['file_read:/home/user/project/file.txt'], '/home/user/project/sub')).not.toBeNull();
475
- expect(findHighestPriorityRule('file_read', ['file_read:/home/user/project/file.txt'], '/home/other')).toBeNull();
476
- });
477
-
478
- test('everywhere scope matches any directory', () => {
479
- addRule('bash', 'git *', 'everywhere', 'allow');
480
- const match = findHighestPriorityRule('bash', ['git status'], '/any/random/path');
481
- expect(match).not.toBeNull();
482
- });
483
- });
484
-
485
- // ── getAllRules ─────────────────────────────────────────────────
486
-
487
- describe('getAllRules', () => {
488
- test('returns default rules when no user rules exist', () => {
489
- const rules = getAllRules();
490
- expect(rules).toHaveLength(NUM_DEFAULTS);
491
- expect(rules.every((r) => r.id.startsWith('default:'))).toBe(true);
492
- });
493
-
494
- test('returns a copy (not the internal array)', () => {
495
- addRule('bash', 'git *', '/tmp');
496
- const rules1 = getAllRules();
497
- const rules2 = getAllRules();
498
- expect(rules1).toEqual(rules2);
499
- expect(rules1).not.toBe(rules2); // different references
500
- });
501
- });
502
-
503
- // ── clearCache ─────────────────────────────────────────────────
504
-
505
- describe('clearCache', () => {
506
- test('forces reload from disk on next access', () => {
507
- addRule('bash', 'git *', '/tmp');
508
- expect(getAllRules()).toHaveLength(1 + NUM_DEFAULTS);
509
- clearCache();
510
- // After clearing cache, rules are reloaded from disk
511
- expect(getAllRules()).toHaveLength(1 + NUM_DEFAULTS);
512
- });
513
- });
514
-
515
- // ── persistence ─────────────────────────────────────────────────
516
-
517
- describe('persistence', () => {
518
- test('rules survive cache clear (loaded from disk)', () => {
519
- const rule = addRule('bash', 'npm *', '/tmp');
520
- clearCache();
521
- const rules = getAllRules();
522
- expect(rules).toHaveLength(1 + NUM_DEFAULTS);
523
- expect(rules.find((r) => r.id === rule.id)).toBeDefined();
524
- });
525
-
526
- test('trust file has correct structure', () => {
527
- addRule('bash', 'git *', '/tmp');
528
- const data = JSON.parse(readFileSync(trustPath, 'utf-8'));
529
- expect(data).toHaveProperty('version', 3);
530
- expect(data).toHaveProperty('rules');
531
- expect(Array.isArray(data.rules)).toBe(true);
532
- const userRule = data.rules.find((r: { pattern: string }) => r.pattern === 'git *');
533
- expect(userRule).toHaveProperty('priority', 100);
534
- });
535
- });
536
-
537
- // ── deny rules ─────────────────────────────────────────────────
538
-
539
- describe('deny rules', () => {
540
- test('addRule with deny decision creates a deny rule', () => {
541
- const rule = addRule('bash', 'rm -rf *', '/tmp', 'deny');
542
- expect(rule.decision).toBe('deny');
543
- expect(rule.tool).toBe('bash');
544
- expect(rule.pattern).toBe('rm -rf *');
545
- });
546
-
547
- test('deny rule persists to disk', () => {
548
- addRule('bash', 'rm *', '/tmp', 'deny');
549
- clearCache();
550
- const rules = getAllRules();
551
- expect(rules).toHaveLength(1 + NUM_DEFAULTS);
552
- const userRule = rules.find((r) => r.pattern === 'rm *');
553
- expect(userRule).toBeDefined();
554
- expect(userRule!.decision).toBe('deny');
555
- });
556
-
557
- test('findDenyRule finds deny rules', () => {
558
- addRule('bash', 'rm *', '/tmp', 'deny');
559
- const match = findDenyRule('bash', 'rm file.txt', '/tmp');
560
- expect(match).not.toBeNull();
561
- expect(match!.decision).toBe('deny');
562
- });
563
-
564
- test('findDenyRule ignores allow rules', () => {
565
- addRule('bash', 'rm *', '/tmp', 'allow');
566
- const match = findDenyRule('bash', 'rm file.txt', '/tmp');
567
- expect(match).toBeNull();
568
- });
569
-
570
- test('findMatchingRule ignores deny rules', () => {
571
- // Use host_bash — bash has a default allow rule that would match.
572
- addRule('host_bash', 'rm *', '/tmp', 'deny');
573
- const match = findMatchingRule('host_bash', 'rm file.txt', '/tmp');
574
- expect(match).toBeNull();
575
- });
576
-
577
- test('deny and allow rules coexist', () => {
578
- addRule('bash', 'git *', '/tmp', 'allow');
579
- addRule('bash', 'git push --force *', '/tmp', 'deny');
580
- expect(findMatchingRule('bash', 'git status', '/tmp')).not.toBeNull();
581
- expect(findDenyRule('bash', 'git push --force origin', '/tmp')).not.toBeNull();
582
- });
583
-
584
- test('deny rule with scope matching', () => {
585
- addRule('bash', 'rm *', '/home/user/project', 'deny');
586
- expect(findDenyRule('bash', 'rm file.txt', '/home/user/project/sub')).not.toBeNull();
587
- expect(findDenyRule('bash', 'rm file.txt', '/home/other')).toBeNull();
588
- });
589
-
590
- test('deny rule with everywhere scope', () => {
591
- addRule('bash', 'rm -rf *', 'everywhere', 'deny');
592
- expect(findDenyRule('bash', 'rm -rf /', '/any/path')).not.toBeNull();
593
- });
594
-
595
- test('removeRule works for deny rules', () => {
596
- const rule = addRule('bash', 'rm *', '/tmp', 'deny');
597
- expect(removeRule(rule.id)).toBe(true);
598
- expect(findDenyRule('bash', 'rm file.txt', '/tmp')).toBeNull();
599
- });
600
- });
601
-
602
- // ── v1 migration ───────────────────────────────────────────────
603
-
604
- describe('v1 migration', () => {
605
- test('v1 rules get priority 100 on load', () => {
606
- mkdirSync(dirname(trustPath), { recursive: true });
607
- writeFileSync(trustPath, JSON.stringify({
608
- version: 1,
609
- rules: [{
610
- id: 'test-v1-id',
611
- tool: 'bash',
612
- pattern: 'git *',
613
- scope: '/tmp',
614
- decision: 'allow',
615
- createdAt: 1000,
616
- }],
617
- }));
618
- clearCache();
619
- const rules = getAllRules();
620
- expect(rules).toHaveLength(1 + NUM_DEFAULTS);
621
- const migratedRule = rules.find((r) => r.id === 'test-v1-id');
622
- expect(migratedRule).toBeDefined();
623
- expect(migratedRule!.priority).toBe(100);
624
- });
625
-
626
- test('v1 file is upgraded to v3 on disk', () => {
627
- mkdirSync(dirname(trustPath), { recursive: true });
628
- writeFileSync(trustPath, JSON.stringify({
629
- version: 1,
630
- rules: [{
631
- id: 'migrate-me',
632
- tool: 'bash',
633
- pattern: 'npm *',
634
- scope: 'everywhere',
635
- decision: 'allow',
636
- createdAt: 2000,
637
- }],
638
- }));
639
- clearCache();
640
- getAllRules(); // triggers load + migration
641
- const data = JSON.parse(readFileSync(trustPath, 'utf-8'));
642
- expect(data.version).toBe(3);
643
- const migratedRule = data.rules.find((r: { id: string }) => r.id === 'migrate-me');
644
- expect(migratedRule.priority).toBe(100);
645
- });
646
- });
647
-
648
- // ── loadFromDisk resilience ─────────────────────────────────────
649
-
650
- describe('loadFromDisk resilience', () => {
651
- test('returns in-memory rules when saveToDisk fails during migration', () => {
652
- // Write a v1 trust file that triggers needsSave on load
653
- mkdirSync(dirname(trustPath), { recursive: true });
654
- writeFileSync(trustPath, JSON.stringify({
655
- version: 1,
656
- rules: [{
657
- id: 'v1-readonly',
658
- tool: 'bash',
659
- pattern: 'git *',
660
- scope: '/tmp',
661
- decision: 'allow' as const,
662
- createdAt: 1000,
663
- }],
664
- }));
665
-
666
- // Spy on writeFileSync to throw when saveToDisk is called during migration.
667
- // This is deterministic regardless of user privileges (unlike chmod 0o555).
668
- const spy = spyOn(fs, 'writeFileSync').mockImplementation(() => {
669
- throw new Error('Simulated write failure');
670
- });
671
-
672
- try {
673
- clearCache();
674
- const rules = getAllRules();
675
- // Should still return the migrated rules + defaults in-memory
676
- expect(rules).toHaveLength(1 + NUM_DEFAULTS);
677
- const migratedRule = rules.find((r) => r.id === 'v1-readonly');
678
- expect(migratedRule).toBeDefined();
679
- expect(migratedRule!.priority).toBe(100);
680
- // Verify that saveToDisk was attempted (writeFileSync was called)
681
- expect(spy).toHaveBeenCalled();
682
- } finally {
683
- spy.mockRestore();
684
- }
685
- });
686
- });
687
-
688
- // ── default rules ─────────────────────────────────────────────
689
-
690
- describe('default rules', () => {
691
- test('backfills default rules on first load', () => {
692
- const rules = getAllRules();
693
- const defaults = rules.filter((r) => r.id.startsWith('default:'));
694
- expect(defaults).toHaveLength(NUM_DEFAULTS);
695
- for (const rule of defaults) {
696
- expect(rule.priority).toBe(DEFAULT_PRIORITY_BY_ID.get(rule.id)!);
697
- if (rule.id === 'default:allow-bash-rm-bootstrap') {
698
- expect(rule.scope).toBe(join(testDir, 'workspace'));
699
- } else {
700
- expect(rule.scope).toBe('everywhere');
701
- }
702
- }
703
-
704
- });
705
-
706
- test('default rules cover file, host file, host shell, and workspace prompt tools', () => {
707
- const rules = getAllRules();
708
- const defaultTools = [...new Set(
709
- rules
710
- .filter((r) => r.id.startsWith('default:'))
711
- .map((r) => r.tool),
712
- )].sort();
713
- expect(defaultTools).toEqual([
714
- 'bash',
715
- 'browser_click',
716
- 'browser_close',
717
- 'browser_extract',
718
- 'browser_fill_credential',
719
- 'browser_navigate',
720
- 'browser_press_key',
721
- 'browser_screenshot',
722
- 'browser_snapshot',
723
- 'browser_type',
724
- 'browser_wait_for',
725
- 'computer_use_click',
726
- 'computer_use_double_click',
727
- 'computer_use_drag',
728
- 'computer_use_key',
729
- 'computer_use_open_app',
730
- 'computer_use_request_control',
731
- 'computer_use_right_click',
732
- 'computer_use_run_applescript',
733
- 'computer_use_scroll',
734
- 'computer_use_type_text',
735
- 'computer_use_wait',
736
- 'delete_managed_skill',
737
- 'file_edit',
738
- 'file_read',
739
- 'file_write',
740
- 'host_bash',
741
- 'host_file_edit',
742
- 'host_file_read',
743
- 'host_file_write',
744
- 'memory_search',
745
- 'scaffold_managed_skill',
746
- 'skill_load',
747
- 'ui_dismiss',
748
- 'ui_update',
749
- 'view_image',
750
- ]);
751
- });
752
-
753
- test('default rules are not duplicated on reload', () => {
754
- getAllRules(); // first load
755
- clearCache();
756
- const rules = getAllRules(); // second load
757
- const defaults = rules.filter((r) => r.id.startsWith('default:'));
758
- expect(defaults).toHaveLength(NUM_DEFAULTS);
759
- });
760
-
761
- test('default rules persist to disk', () => {
762
- getAllRules(); // triggers backfill + save
763
- const data = JSON.parse(readFileSync(trustPath, 'utf-8'));
764
- const defaults = data.rules.filter((r: { id: string }) => r.id.startsWith('default:'));
765
- expect(defaults).toHaveLength(NUM_DEFAULTS);
766
- });
767
-
768
- test('default rules are backfilled alongside v1 migration', () => {
769
- mkdirSync(dirname(trustPath), { recursive: true });
770
- writeFileSync(trustPath, JSON.stringify({
771
- version: 1,
772
- rules: [{
773
- id: 'v1-user-rule',
774
- tool: 'bash',
775
- pattern: 'git *',
776
- scope: '/tmp',
777
- decision: 'allow',
778
- createdAt: 1000,
779
- }],
780
- }));
781
- clearCache();
782
- const rules = getAllRules();
783
- expect(rules).toHaveLength(1 + NUM_DEFAULTS);
784
- expect(rules.find((r) => r.id === 'v1-user-rule')!.priority).toBe(100);
785
- const defaults = rules.filter((r) => r.id.startsWith('default:'));
786
- expect(defaults).toHaveLength(NUM_DEFAULTS);
787
- expect(defaults.every((r) => r.priority === DEFAULT_PRIORITY_BY_ID.get(r.id))).toBe(true);
788
- });
789
-
790
- test('removed default rule is re-backfilled on next load', () => {
791
- // First load backfills defaults
792
- getAllRules();
793
- // Remove one default rule by editing trust.json directly on disk
794
- // (removeRule() throws for default rules, so we simulate external editing)
795
- const raw = JSON.parse(readFileSync(trustPath, 'utf-8'));
796
- raw.rules = raw.rules.filter((r: { id: string }) => r.id !== 'default:ask-host_file_read-global');
797
- writeFileSync(trustPath, JSON.stringify(raw, null, 2));
798
- // After reload, the rule is re-backfilled (defaults are always present)
799
- clearCache();
800
- const rules = getAllRules();
801
- expect(rules.find((r) => r.id === 'default:ask-host_file_read-global')).toBeDefined();
802
- });
803
-
804
- test('findHighestPriorityRule matches default ask for host_file_read', () => {
805
- const match = findHighestPriorityRule('host_file_read', ['host_file_read:/etc/hosts'], '/tmp');
806
- expect(match).not.toBeNull();
807
- expect(match!.id).toBe('default:ask-host_file_read-global');
808
- expect(match!.decision).toBe('ask');
809
- expect(match!.priority).toBe(DEFAULT_PRIORITY_BY_ID.get('default:ask-host_file_read-global')!);
810
- });
811
-
812
- test('findHighestPriorityRule matches default ask for host_file_write', () => {
813
- const match = findHighestPriorityRule('host_file_write', ['host_file_write:/etc/hosts'], '/tmp');
814
- expect(match).not.toBeNull();
815
- expect(match!.id).toBe('default:ask-host_file_write-global');
816
- expect(match!.decision).toBe('ask');
817
- expect(match!.priority).toBe(DEFAULT_PRIORITY_BY_ID.get('default:ask-host_file_write-global')!);
818
- });
819
-
820
- test('findHighestPriorityRule matches default ask for host_file_edit', () => {
821
- const match = findHighestPriorityRule('host_file_edit', ['host_file_edit:/etc/hosts'], '/tmp');
822
- expect(match).not.toBeNull();
823
- expect(match!.id).toBe('default:ask-host_file_edit-global');
824
- expect(match!.decision).toBe('ask');
825
- expect(match!.priority).toBe(DEFAULT_PRIORITY_BY_ID.get('default:ask-host_file_edit-global')!);
826
- });
827
-
828
- test('findHighestPriorityRule matches default ask for host_bash', () => {
829
- const match = findHighestPriorityRule('host_bash', ['ls'], '/tmp');
830
- expect(match).not.toBeNull();
831
- expect(match!.id).toBe('default:ask-host_bash-global');
832
- expect(match!.decision).toBe('ask');
833
- expect(match!.priority).toBe(DEFAULT_PRIORITY_BY_ID.get('default:ask-host_bash-global')!);
834
- });
835
-
836
- test('findHighestPriorityRule matches default ask for computer_use_click', () => {
837
- const match = findHighestPriorityRule('computer_use_click', ['computer_use_click:'], '/tmp');
838
- expect(match).not.toBeNull();
839
- expect(match!.id).toBe('default:ask-computer_use_click-global');
840
- expect(match!.decision).toBe('ask');
841
- expect(match!.priority).toBe(DEFAULT_PRIORITY_BY_ID.get('default:ask-computer_use_click-global')!);
842
- });
843
-
844
- test('findHighestPriorityRule matches default ask for computer_use_request_control', () => {
845
- const match = findHighestPriorityRule('computer_use_request_control', ['computer_use_request_control:'], '/tmp');
846
- expect(match).not.toBeNull();
847
- expect(match!.id).toBe('default:ask-computer_use_request_control-global');
848
- expect(match!.decision).toBe('ask');
849
- expect(match!.priority).toBe(DEFAULT_PRIORITY_BY_ID.get('default:ask-computer_use_request_control-global')!);
850
- });
851
-
852
- test('bootstrap delete rule matches only when workingDir is the workspace dir', () => {
853
- const workspaceDir = join(testDir, 'workspace');
854
- // Should match when workingDir is the workspace directory — the bootstrap
855
- // rule (priority 100) outranks the global default allow (priority 50).
856
- const match = findHighestPriorityRule('bash', ['rm BOOTSTRAP.md'], workspaceDir);
857
- expect(match).not.toBeNull();
858
- expect(match!.id).toBe('default:allow-bash-rm-bootstrap');
859
- expect(match!.decision).toBe('allow');
860
- // Outside workspace, the bootstrap rule doesn't match — the global
861
- // default:allow-bash-global rule matches instead (not the bootstrap rule).
862
- const other = findHighestPriorityRule('bash', ['rm BOOTSTRAP.md'], '/tmp/other-project');
863
- expect(other).not.toBeNull();
864
- expect(other!.id).not.toBe('default:allow-bash-rm-bootstrap');
865
- expect(other!.id).toBe('default:allow-bash-global');
866
- });
867
-
868
- test('default ask does not affect files outside protected directory', () => {
869
- const safePath = join(testDir, 'data', 'assistant.db');
870
- const match = findHighestPriorityRule('file_read', [`file_read:${safePath}`], '/tmp');
871
- // Should not match a default deny rule
872
- expect(match === null || !match.id.startsWith('default:')).toBe(true);
873
- });
874
-
875
- test('default rules are backfilled after malformed JSON in trust file', () => {
876
- mkdirSync(dirname(trustPath), { recursive: true });
877
- writeFileSync(trustPath, 'NOT VALID JSON {{{');
878
- clearCache();
879
- const rules = getAllRules();
880
- const defaults = rules.filter((r) => r.id.startsWith('default:'));
881
- expect(defaults).toHaveLength(NUM_DEFAULTS);
882
- });
883
-
884
- test('default rules are backfilled in-memory after unknown file version without overwriting disk', () => {
885
- mkdirSync(dirname(trustPath), { recursive: true });
886
- const originalContent = JSON.stringify({ version: 9999, rules: [{ id: 'future-rule', tool: 'bash', pattern: 'future *', scope: 'everywhere', decision: 'allow', priority: 50, createdAt: 1000 }] });
887
- writeFileSync(trustPath, originalContent);
888
- clearCache();
889
- const rules = getAllRules();
890
- // Defaults should be present in-memory
891
- const defaults = rules.filter((r) => r.id.startsWith('default:'));
892
- expect(defaults).toHaveLength(NUM_DEFAULTS);
893
- // The on-disk file must NOT be overwritten — it preserves the unknown format
894
- const diskContent = readFileSync(trustPath, 'utf-8');
895
- expect(diskContent).toBe(originalContent);
896
- });
897
-
898
- test('clearAllRules preserves default rules', () => {
899
- addRule('bash', 'git *', '/tmp');
900
- clearAllRules();
901
- const rules = getAllRules();
902
- // User rules should be gone, but defaults should remain
903
- expect(rules.filter((r) => !r.id.startsWith('default:'))).toHaveLength(0);
904
- const defaults = rules.filter((r) => r.id.startsWith('default:'));
905
- expect(defaults).toHaveLength(NUM_DEFAULTS);
906
- });
907
-
908
- // ── skill source mutation rules ────────────────────────────────
909
-
910
- test('default rules include ask rules for file_write on skill source paths', () => {
911
- const rules = getAllRules();
912
- const managed = rules.find((r) => r.id === 'default:ask-file_write-managed-skills');
913
- expect(managed).toBeDefined();
914
- expect(managed!.tool).toBe('file_write');
915
- expect(managed!.decision).toBe('ask');
916
- expect(managed!.priority).toBe(50);
917
- expect(managed!.pattern).toContain('workspace/skills/**');
918
-
919
- const bundled = rules.find((r) => r.id === 'default:ask-file_write-bundled-skills');
920
- expect(bundled).toBeDefined();
921
- expect(bundled!.tool).toBe('file_write');
922
- expect(bundled!.decision).toBe('ask');
923
- expect(bundled!.priority).toBe(50);
924
- });
925
-
926
- test('default rules include ask rules for file_edit on skill source paths', () => {
927
- const rules = getAllRules();
928
- const managed = rules.find((r) => r.id === 'default:ask-file_edit-managed-skills');
929
- expect(managed).toBeDefined();
930
- expect(managed!.tool).toBe('file_edit');
931
- expect(managed!.decision).toBe('ask');
932
- expect(managed!.priority).toBe(50);
933
- expect(managed!.pattern).toContain('workspace/skills/**');
934
-
935
- const bundled = rules.find((r) => r.id === 'default:ask-file_edit-bundled-skills');
936
- expect(bundled).toBeDefined();
937
- expect(bundled!.tool).toBe('file_edit');
938
- expect(bundled!.decision).toBe('ask');
939
- expect(bundled!.priority).toBe(50);
940
- });
941
-
942
- // ── default allow: skill_load ────────────────────────────────
943
-
944
- test('skill_load default allow rule exists in templates', () => {
945
- const templates = getDefaultRuleTemplates();
946
- const skillLoadRule = templates.find(t => t.id === 'default:allow-skill_load-global');
947
- expect(skillLoadRule).toBeDefined();
948
- expect(skillLoadRule!.tool).toBe('skill_load');
949
- expect(skillLoadRule!.pattern).toBe('skill_load:*');
950
- expect(skillLoadRule!.decision).toBe('allow');
951
- expect(skillLoadRule!.scope).toBe('everywhere');
952
- });
953
-
954
- test('findHighestPriorityRule matches default allow for skill_load', () => {
955
- const match = findHighestPriorityRule('skill_load', ['skill_load:browser'], '/tmp');
956
- expect(match).not.toBeNull();
957
- expect(match!.id).toBe('default:allow-skill_load-global');
958
- expect(match!.decision).toBe('allow');
959
- expect(match!.priority).toBe(100);
960
- });
961
-
962
- test('findHighestPriorityRule matches default allow for skill_load with any skill name', () => {
963
- const match = findHighestPriorityRule('skill_load', ['skill_load:some-random-skill'], '/tmp');
964
- expect(match).not.toBeNull();
965
- expect(match!.id).toBe('default:allow-skill_load-global');
966
- expect(match!.decision).toBe('allow');
967
- });
968
-
969
- // ── default allow: browser tools ────────────────────────────
970
-
971
- test('all 10 browser tools have default allow rules', () => {
972
- const templates = getDefaultRuleTemplates();
973
- const browserTools = [
974
- 'browser_navigate', 'browser_snapshot', 'browser_screenshot', 'browser_close',
975
- 'browser_click', 'browser_type', 'browser_press_key', 'browser_wait_for',
976
- 'browser_extract', 'browser_fill_credential',
977
- ];
978
-
979
- for (const tool of browserTools) {
980
- const rule = templates.find(t => t.id === `default:allow-${tool}-global`);
981
- expect(rule).toBeDefined();
982
- expect(rule!.tool).toBe(tool);
983
- // browser_navigate uses standalone "**" because its candidates
984
- // contain URLs with "/" that single "*" cannot match.
985
- const expectedPattern = tool === 'browser_navigate' ? '**' : `${tool}:*`;
986
- expect(rule!.pattern).toBe(expectedPattern);
987
- expect(rule!.decision).toBe('allow');
988
- expect(rule!.scope).toBe('everywhere');
989
- }
990
- });
991
-
992
- test('browser tool default rules match via findHighestPriorityRule', () => {
993
- // Use a candidate without slashes so the `browser_snapshot:*` pattern
994
- // matches (minimatch `*` does not cross `/` boundaries).
995
- const result = findHighestPriorityRule('browser_snapshot', ['browser_snapshot:'], '/tmp');
996
- expect(result).toBeDefined();
997
- expect(result!.decision).toBe('allow');
998
- });
999
-
1000
- test('no default ask rules exist for file_read on skill source paths', () => {
1001
- const rules = getAllRules();
1002
- // There should be no default rules with IDs matching file_read for skill sources
1003
- const readManagedSkill = rules.find((r) => r.id === 'default:ask-file_read-managed-skills');
1004
- const readBundledSkill = rules.find((r) => r.id === 'default:ask-file_read-bundled-skills');
1005
- expect(readManagedSkill).toBeUndefined();
1006
- expect(readBundledSkill).toBeUndefined();
1007
- });
1008
-
1009
- test('findHighestPriorityRule matches default ask for file_write on managed skill path', () => {
1010
- const skillFile = join(testDir, 'workspace', 'skills', 'my-skill', 'SKILL.md');
1011
- const match = findHighestPriorityRule('file_write', [`file_write:${skillFile}`], '/tmp');
1012
- expect(match).not.toBeNull();
1013
- expect(match!.id).toBe('default:ask-file_write-managed-skills');
1014
- expect(match!.decision).toBe('ask');
1015
- });
1016
-
1017
- test('findHighestPriorityRule matches default ask for file_edit on managed skill path', () => {
1018
- const skillFile = join(testDir, 'workspace', 'skills', 'my-skill', 'tools.ts');
1019
- const match = findHighestPriorityRule('file_edit', [`file_edit:${skillFile}`], '/tmp');
1020
- expect(match).not.toBeNull();
1021
- expect(match!.id).toBe('default:ask-file_edit-managed-skills');
1022
- expect(match!.decision).toBe('ask');
1023
- });
1024
- });
1025
-
1026
- // ── trust rule schema v3 (PR 14) ──────────────────────────────
1027
-
1028
- describe('trust rule schema v3 (PR 14)', () => {
1029
- test('new rules can include principal fields', () => {
1030
- const rule = addRule('bash', 'git *', '/tmp');
1031
- // Manually set v3 principal fields on the rule and persist
1032
- rule.principalKind = 'skill';
1033
- rule.principalId = 'my-skill';
1034
- rule.principalVersion = 'abc123';
1035
- rule.executionTarget = '/usr/local/bin/node';
1036
- rule.allowHighRisk = true;
1037
- // Re-persist the updated rules
1038
- const rules = getAllRules().map((r) =>
1039
- r.id === rule.id ? rule : r,
1040
- );
1041
- // Write directly to verify round-trip
1042
- const trustData = { version: 3, rules };
1043
- writeFileSync(trustPath, JSON.stringify(trustData, null, 2));
1044
- clearCache();
1045
- const reloaded = getAllRules();
1046
- const found = reloaded.find((r) => r.id === rule.id);
1047
- expect(found).toBeDefined();
1048
- expect(found!.principalKind).toBe('skill');
1049
- expect(found!.principalId).toBe('my-skill');
1050
- expect(found!.principalVersion).toBe('abc123');
1051
- expect(found!.executionTarget).toBe('/usr/local/bin/node');
1052
- expect(found!.allowHighRisk).toBe(true);
1053
- });
1054
-
1055
- test('v2 file is upgraded to v3 on disk', () => {
1056
- mkdirSync(dirname(trustPath), { recursive: true });
1057
- writeFileSync(trustPath, JSON.stringify({
1058
- version: 2,
1059
- rules: [{
1060
- id: 'v2-rule',
1061
- tool: 'bash',
1062
- pattern: 'npm *',
1063
- scope: 'everywhere',
1064
- decision: 'allow',
1065
- priority: 100,
1066
- createdAt: 3000,
1067
- }],
1068
- }));
1069
- clearCache();
1070
- getAllRules(); // triggers load + migration
1071
- const data = JSON.parse(readFileSync(trustPath, 'utf-8'));
1072
- expect(data.version).toBe(3);
1073
- });
1074
-
1075
- test('v2 rules survive v3 migration with no principal fields', () => {
1076
- mkdirSync(dirname(trustPath), { recursive: true });
1077
- writeFileSync(trustPath, JSON.stringify({
1078
- version: 2,
1079
- rules: [
1080
- {
1081
- id: 'user-v2-a',
1082
- tool: 'bash',
1083
- pattern: 'git *',
1084
- scope: '/tmp',
1085
- decision: 'allow',
1086
- priority: 100,
1087
- createdAt: 4000,
1088
- },
1089
- {
1090
- id: 'user-v2-b',
1091
- tool: 'file_write',
1092
- pattern: '/tmp/*',
1093
- scope: '/tmp',
1094
- decision: 'deny',
1095
- priority: 50,
1096
- createdAt: 4001,
1097
- },
1098
- ],
1099
- }));
1100
- clearCache();
1101
- const rules = getAllRules();
1102
- const ruleA = rules.find((r) => r.id === 'user-v2-a');
1103
- const ruleB = rules.find((r) => r.id === 'user-v2-b');
1104
- expect(ruleA).toBeDefined();
1105
- expect(ruleB).toBeDefined();
1106
- expect(ruleA!.pattern).toBe('git *');
1107
- expect(ruleB!.decision).toBe('deny');
1108
- // No principal fields should be present
1109
- expect(ruleA).not.toHaveProperty('principalKind');
1110
- expect(ruleA).not.toHaveProperty('principalId');
1111
- expect(ruleA).not.toHaveProperty('principalVersion');
1112
- expect(ruleA).not.toHaveProperty('executionTarget');
1113
- expect(ruleA).not.toHaveProperty('allowHighRisk');
1114
- });
1115
-
1116
- test('trust file persists with version 3', () => {
1117
- addRule('bash', 'echo *', '/tmp');
1118
- const data = JSON.parse(readFileSync(trustPath, 'utf-8'));
1119
- expect(data.version).toBe(3);
1120
- });
1121
- });
1122
-
1123
- // ── v2 → v3 migration hardening (PR 15) ────────────────────────
1124
-
1125
- describe('v2 → v3 migration hardening (PR 15)', () => {
1126
- test('v2 rules with extra unknown fields survive migration cleanly', () => {
1127
- mkdirSync(dirname(trustPath), { recursive: true });
1128
- writeFileSync(trustPath, JSON.stringify({
1129
- version: 2,
1130
- rules: [{
1131
- id: 'v2-extra-fields',
1132
- tool: 'bash',
1133
- pattern: 'git *',
1134
- scope: '/tmp',
1135
- decision: 'allow',
1136
- priority: 100,
1137
- createdAt: 5000,
1138
- customField: 'should-survive',
1139
- nested: { deep: true },
1140
- }],
1141
- }));
1142
- clearCache();
1143
- const rules = getAllRules();
1144
- const rule = rules.find((r) => r.id === 'v2-extra-fields');
1145
- expect(rule).toBeDefined();
1146
- expect(rule!.tool).toBe('bash');
1147
- expect(rule!.pattern).toBe('git *');
1148
- // Extra fields pass through because the migration does not strip them
1149
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- asserting extra fields pass through migration
1150
- expect((rule as any).customField).toBe('should-survive');
1151
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- asserting extra fields pass through migration
1152
- expect((rule as any).nested).toEqual({ deep: true });
1153
- });
1154
-
1155
- test('v2 file with empty rules array migrates correctly', () => {
1156
- mkdirSync(dirname(trustPath), { recursive: true });
1157
- writeFileSync(trustPath, JSON.stringify({
1158
- version: 2,
1159
- rules: [],
1160
- }));
1161
- clearCache();
1162
- const rules = getAllRules();
1163
- // Should only have default rules, no user rules
1164
- expect(rules).toHaveLength(NUM_DEFAULTS);
1165
- expect(rules.every((r) => r.id.startsWith('default:'))).toBe(true);
1166
- // File should be upgraded to v3 on disk
1167
- const data = JSON.parse(readFileSync(trustPath, 'utf-8'));
1168
- expect(data.version).toBe(3);
1169
- });
1170
-
1171
- test('v2 file with no rules field at all migrates correctly', () => {
1172
- mkdirSync(dirname(trustPath), { recursive: true });
1173
- writeFileSync(trustPath, JSON.stringify({
1174
- version: 2,
1175
- }));
1176
- clearCache();
1177
- const rules = getAllRules();
1178
- // rules defaults to [] so only defaults should appear
1179
- expect(rules).toHaveLength(NUM_DEFAULTS);
1180
- expect(rules.every((r) => r.id.startsWith('default:'))).toBe(true);
1181
- // File should be upgraded to v3 on disk
1182
- const data = JSON.parse(readFileSync(trustPath, 'utf-8'));
1183
- expect(data.version).toBe(3);
1184
- });
1185
-
1186
- test('malformed v2 file (rules is a string instead of array) is handled gracefully', () => {
1187
- mkdirSync(dirname(trustPath), { recursive: true });
1188
- writeFileSync(trustPath, JSON.stringify({
1189
- version: 2,
1190
- rules: 'not-an-array',
1191
- }));
1192
- clearCache();
1193
- const rules = getAllRules();
1194
- // Should fall back to empty rules and backfill defaults
1195
- expect(rules).toHaveLength(NUM_DEFAULTS);
1196
- expect(rules.every((r) => r.id.startsWith('default:'))).toBe(true);
1197
- });
1198
-
1199
- test('malformed v2 file (rules is an object instead of array) is handled gracefully', () => {
1200
- mkdirSync(dirname(trustPath), { recursive: true });
1201
- writeFileSync(trustPath, JSON.stringify({
1202
- version: 2,
1203
- rules: { notAnArray: true },
1204
- }));
1205
- clearCache();
1206
- const rules = getAllRules();
1207
- expect(rules).toHaveLength(NUM_DEFAULTS);
1208
- expect(rules.every((r) => r.id.startsWith('default:'))).toBe(true);
1209
- });
1210
-
1211
- test('malformed file (valid JSON but null) is handled gracefully', () => {
1212
- mkdirSync(dirname(trustPath), { recursive: true });
1213
- writeFileSync(trustPath, 'null');
1214
- clearCache();
1215
- const rules = getAllRules();
1216
- // Accessing null.version throws TypeError, caught by try/catch,
1217
- // falls through to backfill defaults
1218
- expect(rules).toHaveLength(NUM_DEFAULTS);
1219
- });
1220
-
1221
- test('concurrent v2 → v3 migration (loading twice in sequence) is idempotent', () => {
1222
- mkdirSync(dirname(trustPath), { recursive: true });
1223
- writeFileSync(trustPath, JSON.stringify({
1224
- version: 2,
1225
- rules: [{
1226
- id: 'idempotent-rule',
1227
- tool: 'bash',
1228
- pattern: 'npm *',
1229
- scope: 'everywhere',
1230
- decision: 'allow',
1231
- priority: 100,
1232
- createdAt: 6000,
1233
- }],
1234
- }));
1235
- // First load — triggers v2 → v3 migration
1236
- clearCache();
1237
- const rules1 = getAllRules();
1238
- const rule1 = rules1.find((r) => r.id === 'idempotent-rule');
1239
- expect(rule1).toBeDefined();
1240
- expect(rule1!.pattern).toBe('npm *');
1241
-
1242
- // Second load — should load the already-migrated v3 file without re-migrating
1243
- clearCache();
1244
- const rules2 = getAllRules();
1245
- const rule2 = rules2.find((r) => r.id === 'idempotent-rule');
1246
- expect(rule2).toBeDefined();
1247
- expect(rule2!.pattern).toBe('npm *');
1248
- expect(rule2!.priority).toBe(100);
1249
-
1250
- // Verify file is still v3 and rule count is stable
1251
- const data = JSON.parse(readFileSync(trustPath, 'utf-8'));
1252
- expect(data.version).toBe(3);
1253
- const userRules = data.rules.filter((r: { id: string }) => !r.id.startsWith('default:'));
1254
- expect(userRules).toHaveLength(1);
1255
- });
1256
-
1257
- test('v3 file with principal fields is loaded correctly without re-migration', () => {
1258
- mkdirSync(dirname(trustPath), { recursive: true });
1259
- const v3Rules = [
1260
- {
1261
- id: 'v3-with-principal',
1262
- tool: 'bash',
1263
- pattern: 'skill-cmd *',
1264
- scope: '/tmp',
1265
- decision: 'allow',
1266
- priority: 100,
1267
- createdAt: 7000,
1268
- principalKind: 'skill',
1269
- principalId: 'my-skill',
1270
- principalVersion: 'sha256-abc',
1271
- executionTarget: '/usr/bin/node',
1272
- allowHighRisk: false,
1273
- },
1274
- {
1275
- id: 'v3-without-principal',
1276
- tool: 'bash',
1277
- pattern: 'git *',
1278
- scope: '/tmp',
1279
- decision: 'allow',
1280
- priority: 100,
1281
- createdAt: 7001,
1282
- },
1283
- ];
1284
- writeFileSync(trustPath, JSON.stringify({ version: 3, rules: v3Rules }));
1285
- clearCache();
1286
- const rules = getAllRules();
1287
-
1288
- // Rule with principal fields should have them preserved
1289
- const withPrincipal = rules.find((r) => r.id === 'v3-with-principal');
1290
- expect(withPrincipal).toBeDefined();
1291
- expect(withPrincipal!.principalKind).toBe('skill');
1292
- expect(withPrincipal!.principalId).toBe('my-skill');
1293
- expect(withPrincipal!.principalVersion).toBe('sha256-abc');
1294
- expect(withPrincipal!.executionTarget).toBe('/usr/bin/node');
1295
- expect(withPrincipal!.allowHighRisk).toBe(false);
1296
-
1297
- // Rule without principal fields should remain without them
1298
- const withoutPrincipal = rules.find((r) => r.id === 'v3-without-principal');
1299
- expect(withoutPrincipal).toBeDefined();
1300
- expect(withoutPrincipal).not.toHaveProperty('principalKind');
1301
- expect(withoutPrincipal).not.toHaveProperty('principalId');
1302
- });
1303
-
1304
- test('v2 migration preserves rule meaning exactly — no default principal values added', () => {
1305
- mkdirSync(dirname(trustPath), { recursive: true });
1306
- const originalRules = [
1307
- {
1308
- id: 'preserve-a',
1309
- tool: 'bash',
1310
- pattern: 'git *',
1311
- scope: '/home/user',
1312
- decision: 'allow' as const,
1313
- priority: 100,
1314
- createdAt: 8000,
1315
- },
1316
- {
1317
- id: 'preserve-b',
1318
- tool: 'file_write',
1319
- pattern: '/tmp/**',
1320
- scope: 'everywhere',
1321
- decision: 'deny' as const,
1322
- priority: 50,
1323
- createdAt: 8001,
1324
- },
1325
- ];
1326
- writeFileSync(trustPath, JSON.stringify({ version: 2, rules: originalRules }));
1327
- clearCache();
1328
- const rules = getAllRules();
1329
-
1330
- for (const original of originalRules) {
1331
- const migrated = rules.find((r) => r.id === original.id);
1332
- expect(migrated).toBeDefined();
1333
- // Every original field is preserved exactly
1334
- expect(migrated!.tool).toBe(original.tool);
1335
- expect(migrated!.pattern).toBe(original.pattern);
1336
- expect(migrated!.scope).toBe(original.scope);
1337
- expect(migrated!.decision).toBe(original.decision);
1338
- expect(migrated!.priority).toBe(original.priority);
1339
- expect(migrated!.createdAt).toBe(original.createdAt);
1340
- // No principal fields were injected by migration
1341
- expect(migrated).not.toHaveProperty('principalKind');
1342
- expect(migrated).not.toHaveProperty('principalId');
1343
- expect(migrated).not.toHaveProperty('principalVersion');
1344
- expect(migrated).not.toHaveProperty('executionTarget');
1345
- expect(migrated).not.toHaveProperty('allowHighRisk');
1346
- }
1347
-
1348
- // Verify disk representation also has no principal fields on user rules
1349
- const data = JSON.parse(readFileSync(trustPath, 'utf-8'));
1350
- for (const original of originalRules) {
1351
- const diskRule = data.rules.find((r: { id: string }) => r.id === original.id);
1352
- expect(diskRule).toBeDefined();
1353
- expect(diskRule).not.toHaveProperty('principalKind');
1354
- expect(diskRule).not.toHaveProperty('principalId');
1355
- }
1356
- });
1357
-
1358
- test('v1 → v3 full migration preserves rules and adds priority', () => {
1359
- mkdirSync(dirname(trustPath), { recursive: true });
1360
- writeFileSync(trustPath, JSON.stringify({
1361
- version: 1,
1362
- rules: [{
1363
- id: 'v1-full-migration',
1364
- tool: 'bash',
1365
- pattern: 'docker *',
1366
- scope: '/srv',
1367
- decision: 'allow',
1368
- createdAt: 9000,
1369
- }],
1370
- }));
1371
- clearCache();
1372
- const rules = getAllRules();
1373
- const rule = rules.find((r) => r.id === 'v1-full-migration');
1374
- expect(rule).toBeDefined();
1375
- // v1 → v2 adds priority 100
1376
- expect(rule!.priority).toBe(100);
1377
- // v2 → v3 adds no principal fields
1378
- expect(rule).not.toHaveProperty('principalKind');
1379
- expect(rule).not.toHaveProperty('principalId');
1380
- // File should be v3 on disk
1381
- const data = JSON.parse(readFileSync(trustPath, 'utf-8'));
1382
- expect(data.version).toBe(3);
1383
- });
1384
- });
1385
-
1386
- // ── backward compat: addRule without principal options (PR 2/40) ──
1387
- // These tests verify that addRule() without explicit principal options
1388
- // creates wildcard rules. The TrustRule schema *does* support principal
1389
- // and version fields (since PR 14), but they are only set when explicitly
1390
- // provided via the options parameter.
1391
-
1392
- describe('backward compat: addRule without principal options (PR 2/40)', () => {
1393
- test('addRule without principal options creates rules without principal fields', () => {
1394
- const rule = addRule('skill_test_tool', 'skill_test_tool:*', '/tmp');
1395
- expect(rule).not.toHaveProperty('principalKind');
1396
- expect(rule).not.toHaveProperty('principalId');
1397
- expect(rule).not.toHaveProperty('principalVersion');
1398
- expect(rule).not.toHaveProperty('executionTarget');
1399
- expect(rule).not.toHaveProperty('allowHighRisk');
1400
- });
1401
-
1402
- test('findHighestPriorityRule matches without policy context (backward compat)', () => {
1403
- addRule('skill_test_tool', 'skill_test_tool:*', '/tmp', 'allow', 200);
1404
- // Calling without the optional 4th ctx parameter still matches wildcard rules
1405
- const match = findHighestPriorityRule('skill_test_tool', ['skill_test_tool:do-thing'], '/tmp');
1406
- expect(match).not.toBeNull();
1407
- expect(match!.decision).toBe('allow');
1408
- });
1409
-
1410
- test('trust file schema is v3 (rules created without principal fields)', () => {
1411
- addRule('skill_test_tool', 'skill_test_tool:*', '/tmp');
1412
- const raw = JSON.parse(readFileSync(trustPath, 'utf-8'));
1413
- expect(raw.version).toBe(3);
1414
- const userRule = raw.rules.find((r: { pattern: string }) => r.pattern === 'skill_test_tool:*');
1415
- expect(userRule).toBeDefined();
1416
- // addRule without principal options doesn't set principal fields
1417
- expect(userRule).not.toHaveProperty('principalVersion');
1418
- expect(userRule).not.toHaveProperty('principalKind');
1419
- });
1420
- });
1421
-
1422
- // ── principal-aware rule matching (PR 16) ──────────────────────
1423
-
1424
- describe('principal-aware rule matching (PR 16)', () => {
1425
- /**
1426
- * Helper: write a v3 trust file with the given rules directly to disk,
1427
- * then clear the cache so the next getRules() call picks them up.
1428
- */
1429
- function seedRules(rules: Array<Record<string, unknown>>): void {
1430
- mkdirSync(dirname(trustPath), { recursive: true });
1431
- writeFileSync(trustPath, JSON.stringify({ version: 3, rules }));
1432
- clearCache();
1433
- }
1434
-
1435
- // ── wildcard semantics (no principal fields on rule) ──────────
1436
-
1437
- describe('wildcard semantics — rules without principal fields', () => {
1438
- test('rule with no principal fields matches when no context is provided', () => {
1439
- addRule('bash', 'git *', '/tmp', 'allow', 200);
1440
- const match = findHighestPriorityRule('bash', ['git status'], '/tmp');
1441
- expect(match).not.toBeNull();
1442
- expect(match!.decision).toBe('allow');
1443
- });
1444
-
1445
- test('rule with no principal fields matches any principal context', () => {
1446
- addRule('bash', 'git *', '/tmp', 'allow', 200);
1447
- const match = findHighestPriorityRule('bash', ['git status'], '/tmp', {
1448
- principal: { kind: 'skill', id: 'my-skill', version: 'v1' },
1449
- });
1450
- expect(match).not.toBeNull();
1451
- expect(match!.decision).toBe('allow');
1452
- });
1453
-
1454
- test('rule with no principal fields matches any execution target', () => {
1455
- addRule('bash', 'git *', '/tmp', 'allow', 200);
1456
- const match = findHighestPriorityRule('bash', ['git status'], '/tmp', {
1457
- executionTarget: '/usr/bin/node',
1458
- });
1459
- expect(match).not.toBeNull();
1460
- expect(match!.decision).toBe('allow');
1461
- });
1462
-
1463
- test('rule with no principal fields matches context with both principal and target', () => {
1464
- addRule('bash', 'npm *', '/tmp', 'allow', 200);
1465
- const match = findHighestPriorityRule('bash', ['npm install'], '/tmp', {
1466
- principal: { kind: 'skill', id: 'builder', version: 'sha256-xyz' },
1467
- executionTarget: '/usr/local/bin/bun',
1468
- });
1469
- expect(match).not.toBeNull();
1470
- });
1471
- });
1472
-
1473
- // ── principalKind matching ────────────────────────────────────
1474
-
1475
- describe('principalKind matching', () => {
1476
- test('rule with principalKind matches when context kind matches', () => {
1477
- seedRules([{
1478
- id: 'pk-match',
1479
- tool: 'bash',
1480
- pattern: 'echo *',
1481
- scope: 'everywhere',
1482
- decision: 'allow',
1483
- priority: 200,
1484
- createdAt: Date.now(),
1485
- principalKind: 'skill',
1486
- }]);
1487
- const match = findHighestPriorityRule('bash', ['echo hello'], '/tmp', {
1488
- principal: { kind: 'skill' },
1489
- });
1490
- expect(match).not.toBeNull();
1491
- expect(match!.id).toBe('pk-match');
1492
- });
1493
-
1494
- test('rule with principalKind does NOT match when context kind differs', () => {
1495
- seedRules([{
1496
- id: 'pk-mismatch',
1497
- tool: 'bash',
1498
- pattern: 'echo *',
1499
- scope: 'everywhere',
1500
- decision: 'allow',
1501
- priority: 200,
1502
- createdAt: Date.now(),
1503
- principalKind: 'skill',
1504
- }]);
1505
- const match = findHighestPriorityRule('bash', ['echo hello'], '/tmp', {
1506
- principal: { kind: 'core' },
1507
- });
1508
- // Should not match the pk-mismatch rule; may still match a default rule
1509
- expect(match === null || match.id !== 'pk-mismatch').toBe(true);
1510
- });
1511
-
1512
- test('rule with principalKind does NOT match when no context is provided', () => {
1513
- seedRules([{
1514
- id: 'pk-no-ctx',
1515
- tool: 'bash',
1516
- pattern: 'echo *',
1517
- scope: 'everywhere',
1518
- decision: 'allow',
1519
- priority: 200,
1520
- createdAt: Date.now(),
1521
- principalKind: 'skill',
1522
- }]);
1523
- const match = findHighestPriorityRule('bash', ['echo hello'], '/tmp');
1524
- expect(match === null || match.id !== 'pk-no-ctx').toBe(true);
1525
- });
1526
- });
1527
-
1528
- // ── principalId matching ──────────────────────────────────────
1529
-
1530
- describe('principalId matching', () => {
1531
- test('rule with principalKind + principalId matches exact principal', () => {
1532
- seedRules([{
1533
- id: 'pid-exact',
1534
- tool: 'bash',
1535
- pattern: 'deploy *',
1536
- scope: 'everywhere',
1537
- decision: 'allow',
1538
- priority: 200,
1539
- createdAt: Date.now(),
1540
- principalKind: 'skill',
1541
- principalId: 'deployer',
1542
- }]);
1543
- const match = findHighestPriorityRule('bash', ['deploy prod'], '/tmp', {
1544
- principal: { kind: 'skill', id: 'deployer' },
1545
- });
1546
- expect(match).not.toBeNull();
1547
- expect(match!.id).toBe('pid-exact');
1548
- });
1549
-
1550
- test('rule with principalId does NOT match different id', () => {
1551
- seedRules([{
1552
- id: 'pid-diff',
1553
- tool: 'bash',
1554
- pattern: 'deploy *',
1555
- scope: 'everywhere',
1556
- decision: 'allow',
1557
- priority: 200,
1558
- createdAt: Date.now(),
1559
- principalKind: 'skill',
1560
- principalId: 'deployer',
1561
- }]);
1562
- const match = findHighestPriorityRule('bash', ['deploy prod'], '/tmp', {
1563
- principal: { kind: 'skill', id: 'other-skill' },
1564
- });
1565
- expect(match === null || match.id !== 'pid-diff').toBe(true);
1566
- });
1567
- });
1568
-
1569
- // ── principalVersion matching ─────────────────────────────────
1570
-
1571
- describe('principalVersion matching', () => {
1572
- test('rule with principalVersion matches exact version', () => {
1573
- seedRules([{
1574
- id: 'pv-exact',
1575
- tool: 'bash',
1576
- pattern: 'build *',
1577
- scope: 'everywhere',
1578
- decision: 'allow',
1579
- priority: 200,
1580
- createdAt: Date.now(),
1581
- principalKind: 'skill',
1582
- principalId: 'builder',
1583
- principalVersion: 'sha256-abc123',
1584
- }]);
1585
- const match = findHighestPriorityRule('bash', ['build all'], '/tmp', {
1586
- principal: { kind: 'skill', id: 'builder', version: 'sha256-abc123' },
1587
- });
1588
- expect(match).not.toBeNull();
1589
- expect(match!.id).toBe('pv-exact');
1590
- });
1591
-
1592
- test('rule with principalVersion does NOT match different version', () => {
1593
- seedRules([{
1594
- id: 'pv-diff',
1595
- tool: 'bash',
1596
- pattern: 'build *',
1597
- scope: 'everywhere',
1598
- decision: 'allow',
1599
- priority: 200,
1600
- createdAt: Date.now(),
1601
- principalKind: 'skill',
1602
- principalId: 'builder',
1603
- principalVersion: 'sha256-abc123',
1604
- }]);
1605
- const match = findHighestPriorityRule('bash', ['build all'], '/tmp', {
1606
- principal: { kind: 'skill', id: 'builder', version: 'sha256-DIFFERENT' },
1607
- });
1608
- expect(match === null || match.id !== 'pv-diff').toBe(true);
1609
- });
1610
-
1611
- test('rule WITHOUT principalVersion matches any version (wildcard)', () => {
1612
- seedRules([{
1613
- id: 'pv-wildcard',
1614
- tool: 'bash',
1615
- pattern: 'build *',
1616
- scope: 'everywhere',
1617
- decision: 'allow',
1618
- priority: 200,
1619
- createdAt: Date.now(),
1620
- principalKind: 'skill',
1621
- principalId: 'builder',
1622
- // no principalVersion — should match any version
1623
- }]);
1624
- const matchV1 = findHighestPriorityRule('bash', ['build all'], '/tmp', {
1625
- principal: { kind: 'skill', id: 'builder', version: 'v1' },
1626
- });
1627
- expect(matchV1).not.toBeNull();
1628
- expect(matchV1!.id).toBe('pv-wildcard');
1629
-
1630
- const matchV2 = findHighestPriorityRule('bash', ['build all'], '/tmp', {
1631
- principal: { kind: 'skill', id: 'builder', version: 'v2' },
1632
- });
1633
- expect(matchV2).not.toBeNull();
1634
- expect(matchV2!.id).toBe('pv-wildcard');
1635
-
1636
- const matchNoVersion = findHighestPriorityRule('bash', ['build all'], '/tmp', {
1637
- principal: { kind: 'skill', id: 'builder' },
1638
- });
1639
- expect(matchNoVersion).not.toBeNull();
1640
- expect(matchNoVersion!.id).toBe('pv-wildcard');
1641
- });
1642
- });
1643
-
1644
- // ── executionTarget matching ──────────────────────────────────
1645
-
1646
- describe('executionTarget matching', () => {
1647
- test('rule with executionTarget matches exact target', () => {
1648
- seedRules([{
1649
- id: 'et-exact',
1650
- tool: 'bash',
1651
- pattern: 'run *',
1652
- scope: 'everywhere',
1653
- decision: 'allow',
1654
- priority: 200,
1655
- createdAt: Date.now(),
1656
- executionTarget: '/usr/local/bin/node',
1657
- }]);
1658
- const match = findHighestPriorityRule('bash', ['run script.js'], '/tmp', {
1659
- executionTarget: '/usr/local/bin/node',
1660
- });
1661
- expect(match).not.toBeNull();
1662
- expect(match!.id).toBe('et-exact');
1663
- });
1664
-
1665
- test('rule with executionTarget does NOT match different target', () => {
1666
- seedRules([{
1667
- id: 'et-diff',
1668
- tool: 'bash',
1669
- pattern: 'run *',
1670
- scope: 'everywhere',
1671
- decision: 'allow',
1672
- priority: 200,
1673
- createdAt: Date.now(),
1674
- executionTarget: '/usr/local/bin/node',
1675
- }]);
1676
- const match = findHighestPriorityRule('bash', ['run script.js'], '/tmp', {
1677
- executionTarget: '/usr/local/bin/bun',
1678
- });
1679
- expect(match === null || match.id !== 'et-diff').toBe(true);
1680
- });
1681
-
1682
- test('rule with executionTarget does NOT match when no target in context', () => {
1683
- seedRules([{
1684
- id: 'et-no-ctx',
1685
- tool: 'bash',
1686
- pattern: 'run *',
1687
- scope: 'everywhere',
1688
- decision: 'allow',
1689
- priority: 200,
1690
- createdAt: Date.now(),
1691
- executionTarget: '/usr/local/bin/node',
1692
- }]);
1693
- const match = findHighestPriorityRule('bash', ['run script.js'], '/tmp', {});
1694
- expect(match === null || match.id !== 'et-no-ctx').toBe(true);
1695
- });
1696
-
1697
- test('rule WITHOUT executionTarget matches any target (wildcard)', () => {
1698
- addRule('bash', 'run *', '/tmp', 'allow', 200);
1699
- const match = findHighestPriorityRule('bash', ['run script.js'], '/tmp', {
1700
- executionTarget: '/any/path/to/runtime',
1701
- });
1702
- expect(match).not.toBeNull();
1703
- expect(match!.pattern).toBe('run *');
1704
- });
1705
- });
1706
-
1707
- // ── combined principal + executionTarget ───────────────────────
1708
-
1709
- describe('combined principal + executionTarget matching', () => {
1710
- test('rule with both principal and executionTarget matches when all fields match', () => {
1711
- seedRules([{
1712
- id: 'combo-match',
1713
- tool: 'bash',
1714
- pattern: 'deploy *',
1715
- scope: 'everywhere',
1716
- decision: 'allow',
1717
- priority: 200,
1718
- createdAt: Date.now(),
1719
- principalKind: 'skill',
1720
- principalId: 'deployer',
1721
- principalVersion: 'sha256-abc',
1722
- executionTarget: '/usr/bin/node',
1723
- }]);
1724
- const match = findHighestPriorityRule('bash', ['deploy prod'], '/tmp', {
1725
- principal: { kind: 'skill', id: 'deployer', version: 'sha256-abc' },
1726
- executionTarget: '/usr/bin/node',
1727
- });
1728
- expect(match).not.toBeNull();
1729
- expect(match!.id).toBe('combo-match');
1730
- });
1731
-
1732
- test('rule with both principal and executionTarget fails if principal mismatches', () => {
1733
- seedRules([{
1734
- id: 'combo-bad-principal',
1735
- tool: 'bash',
1736
- pattern: 'deploy *',
1737
- scope: 'everywhere',
1738
- decision: 'allow',
1739
- priority: 200,
1740
- createdAt: Date.now(),
1741
- principalKind: 'skill',
1742
- principalId: 'deployer',
1743
- executionTarget: '/usr/bin/node',
1744
- }]);
1745
- const match = findHighestPriorityRule('bash', ['deploy prod'], '/tmp', {
1746
- principal: { kind: 'skill', id: 'other-skill' },
1747
- executionTarget: '/usr/bin/node',
1748
- });
1749
- expect(match === null || match.id !== 'combo-bad-principal').toBe(true);
1750
- });
1751
-
1752
- test('rule with both principal and executionTarget fails if target mismatches', () => {
1753
- seedRules([{
1754
- id: 'combo-bad-target',
1755
- tool: 'bash',
1756
- pattern: 'deploy *',
1757
- scope: 'everywhere',
1758
- decision: 'allow',
1759
- priority: 200,
1760
- createdAt: Date.now(),
1761
- principalKind: 'skill',
1762
- principalId: 'deployer',
1763
- executionTarget: '/usr/bin/node',
1764
- }]);
1765
- const match = findHighestPriorityRule('bash', ['deploy prod'], '/tmp', {
1766
- principal: { kind: 'skill', id: 'deployer' },
1767
- executionTarget: '/usr/bin/bun',
1768
- });
1769
- expect(match === null || match.id !== 'combo-bad-target').toBe(true);
1770
- });
1771
- });
1772
-
1773
- // ── priority interaction with principal filtering ──────────────
1774
-
1775
- describe('priority interaction with principal filtering', () => {
1776
- test('higher-priority principal-specific rule wins over lower-priority wildcard', () => {
1777
- seedRules([
1778
- {
1779
- id: 'wildcard-low',
1780
- tool: 'bash',
1781
- pattern: 'test *',
1782
- scope: 'everywhere',
1783
- decision: 'deny',
1784
- priority: 50,
1785
- createdAt: Date.now(),
1786
- },
1787
- {
1788
- id: 'specific-high',
1789
- tool: 'bash',
1790
- pattern: 'test *',
1791
- scope: 'everywhere',
1792
- decision: 'allow',
1793
- priority: 200,
1794
- createdAt: Date.now(),
1795
- principalKind: 'skill',
1796
- principalId: 'tester',
1797
- },
1798
- ]);
1799
- const match = findHighestPriorityRule('bash', ['test unit'], '/tmp', {
1800
- principal: { kind: 'skill', id: 'tester' },
1801
- });
1802
- expect(match).not.toBeNull();
1803
- expect(match!.id).toBe('specific-high');
1804
- expect(match!.decision).toBe('allow');
1805
- });
1806
-
1807
- test('non-matching principal rule is skipped, falling through to wildcard rule', () => {
1808
- seedRules([
1809
- {
1810
- id: 'specific-high',
1811
- tool: 'bash',
1812
- pattern: 'test *',
1813
- scope: 'everywhere',
1814
- decision: 'allow',
1815
- priority: 200,
1816
- createdAt: Date.now(),
1817
- principalKind: 'skill',
1818
- principalId: 'deployer',
1819
- },
1820
- {
1821
- id: 'wildcard-low',
1822
- tool: 'bash',
1823
- pattern: 'test *',
1824
- scope: 'everywhere',
1825
- decision: 'deny',
1826
- priority: 50,
1827
- createdAt: Date.now(),
1828
- },
1829
- ]);
1830
- // Context has kind=skill, id=tester — doesn't match 'deployer'
1831
- const match = findHighestPriorityRule('bash', ['test unit'], '/tmp', {
1832
- principal: { kind: 'skill', id: 'tester' },
1833
- });
1834
- expect(match).not.toBeNull();
1835
- expect(match!.id).toBe('wildcard-low');
1836
- expect(match!.decision).toBe('deny');
1837
- });
1838
- });
1839
-
1840
- // ── backward compatibility ────────────────────────────────────
1841
-
1842
- describe('backward compatibility', () => {
1843
- test('existing callers without ctx parameter still work', () => {
1844
- addRule('bash', 'git *', '/tmp', 'allow', 200);
1845
- // Calling without the 4th argument — must still match
1846
- const match = findHighestPriorityRule('bash', ['git status'], '/tmp');
1847
- expect(match).not.toBeNull();
1848
- expect(match!.pattern).toBe('git *');
1849
- });
1850
-
1851
- test('existing default rules (no principal fields) match with any context', () => {
1852
- // Default rules have no principal fields and should match regardless of context.
1853
- // Use host_file_read which has a default ask rule (default:ask-host_file_read-global).
1854
- const match = findHighestPriorityRule(
1855
- 'host_file_read',
1856
- ['host_file_read:/etc/hosts'],
1857
- '/tmp',
1858
- { principal: { kind: 'skill', id: 'random-skill', version: 'v99' } },
1859
- );
1860
- expect(match).not.toBeNull();
1861
- expect(match!.decision).toBe('ask');
1862
- });
1863
-
1864
- test('empty PolicyContext object behaves the same as no context', () => {
1865
- addRule('bash', 'ls *', '/tmp', 'allow', 200);
1866
- const matchNoCtx = findHighestPriorityRule('bash', ['ls -la'], '/tmp');
1867
- const matchEmptyCtx = findHighestPriorityRule('bash', ['ls -la'], '/tmp', {});
1868
- expect(matchNoCtx).not.toBeNull();
1869
- expect(matchEmptyCtx).not.toBeNull();
1870
- expect(matchNoCtx!.id).toBe(matchEmptyCtx!.id);
1871
- });
1872
- });
1873
- });
1874
-
1875
- // ── network_request trust rule matching ────────────────────────
1876
-
1877
- describe('network_request trust rules', () => {
1878
- test('exact origin rule matches network_request candidates', () => {
1879
- addRule('network_request', 'network_request:https://api.example.com/*', 'everywhere');
1880
- const rule = findHighestPriorityRule(
1881
- 'network_request',
1882
- ['network_request:https://api.example.com/v1/data', 'network_request:https://api.example.com/*'],
1883
- '/tmp',
1884
- );
1885
- expect(rule).not.toBeNull();
1886
- expect(rule!.decision).toBe('allow');
1887
- });
1888
-
1889
- test('exact url rule matches only that url candidate', () => {
1890
- addRule('network_request', 'network_request:https://api.example.com/v1/data', 'everywhere');
1891
- const match = findHighestPriorityRule(
1892
- 'network_request',
1893
- ['network_request:https://api.example.com/v1/data', 'network_request:https://api.example.com/*'],
1894
- '/tmp',
1895
- );
1896
- expect(match).not.toBeNull();
1897
-
1898
- const noMatch = findHighestPriorityRule(
1899
- 'network_request',
1900
- ['network_request:https://api.example.com/v2/other'],
1901
- '/tmp',
1902
- );
1903
- expect(noMatch).toBeNull();
1904
- });
1905
-
1906
- test('globstar rule matches any network_request candidate', () => {
1907
- // minimatch treats standalone "**" as globstar (matching "/"), but
1908
- // "network_request:*" uses single "*" which doesn't cross slashes.
1909
- // The tool field is already filtered by findHighestPriorityRule, so
1910
- // "**" is the correct catch-all pattern.
1911
- addRule('network_request', '**', 'everywhere');
1912
- const rule = findHighestPriorityRule(
1913
- 'network_request',
1914
- ['network_request:https://any-host.example.org/path'],
1915
- '/tmp',
1916
- );
1917
- expect(rule).not.toBeNull();
1918
- });
1919
-
1920
- test('single-star wildcard matches flat candidates only', () => {
1921
- // "network_request:*" won't match URLs with slashes — consistent
1922
- // with the behavior of web_fetch:* and browser_navigate:* patterns.
1923
- addRule('network_request', 'network_request:*', 'everywhere');
1924
- const noSlashMatch = findHighestPriorityRule(
1925
- 'network_request',
1926
- ['network_request:flat-target'],
1927
- '/tmp',
1928
- );
1929
- expect(noSlashMatch).not.toBeNull();
1930
-
1931
- const slashNoMatch = findHighestPriorityRule(
1932
- 'network_request',
1933
- ['network_request:https://example.com/path'],
1934
- '/tmp',
1935
- );
1936
- // Single "*" does not match "/" so this URL candidate won't match.
1937
- expect(slashNoMatch).toBeNull();
1938
- });
1939
-
1940
- test('network_request rule does not match web_fetch tool', () => {
1941
- addRule('network_request', 'network_request:https://api.example.com/*', 'everywhere');
1942
- const rule = findHighestPriorityRule(
1943
- 'web_fetch',
1944
- ['web_fetch:https://api.example.com/v1/data', 'web_fetch:https://api.example.com/*'],
1945
- '/tmp',
1946
- );
1947
- expect(rule).toBeNull();
1948
- });
1949
-
1950
- test('web_fetch rule does not match network_request tool', () => {
1951
- addRule('web_fetch', 'web_fetch:https://api.example.com/*', 'everywhere');
1952
- const rule = findHighestPriorityRule(
1953
- 'network_request',
1954
- ['network_request:https://api.example.com/v1/data', 'network_request:https://api.example.com/*'],
1955
- '/tmp',
1956
- );
1957
- expect(rule).toBeNull();
1958
- });
1959
-
1960
- test('deny rule takes precedence over allow at same priority', () => {
1961
- addRule('network_request', 'network_request:https://api.example.com/*', 'everywhere', 'allow', 100);
1962
- addRule('network_request', 'network_request:https://api.example.com/*', 'everywhere', 'deny', 100);
1963
- const rule = findHighestPriorityRule(
1964
- 'network_request',
1965
- ['network_request:https://api.example.com/v1/data', 'network_request:https://api.example.com/*'],
1966
- '/tmp',
1967
- );
1968
- expect(rule).not.toBeNull();
1969
- expect(rule!.decision).toBe('deny');
1970
- });
1971
-
1972
- test('higher-priority allow overrides lower-priority deny', () => {
1973
- addRule('network_request', 'network_request:https://api.example.com/*', 'everywhere', 'deny', 50);
1974
- addRule('network_request', 'network_request:https://api.example.com/*', 'everywhere', 'allow', 100);
1975
- const rule = findHighestPriorityRule(
1976
- 'network_request',
1977
- ['network_request:https://api.example.com/v1/data', 'network_request:https://api.example.com/*'],
1978
- '/tmp',
1979
- );
1980
- expect(rule).not.toBeNull();
1981
- expect(rule!.decision).toBe('allow');
1982
- });
1983
-
1984
- test('scope restricts network_request rule matching', () => {
1985
- addRule('network_request', 'network_request:https://api.example.com/*', '/home/user/project');
1986
- const inScope = findHighestPriorityRule(
1987
- 'network_request',
1988
- ['network_request:https://api.example.com/*'],
1989
- '/home/user/project',
1990
- );
1991
- expect(inScope).not.toBeNull();
1992
-
1993
- const outOfScope = findHighestPriorityRule(
1994
- 'network_request',
1995
- ['network_request:https://api.example.com/*'],
1996
- '/tmp/other',
1997
- );
1998
- expect(outOfScope).toBeNull();
1999
- });
2000
- });
2001
- });
2002
-
2003
- describe('computer-use tool trust rule matching', () => {
2004
- test('actionable CU tools have default ask trust rules', () => {
2005
- // Actionable CU tools (those that perform screen interactions) should
2006
- // have default "ask" rules so strict mode prompts before use.
2007
- const actionableCuTools = [
2008
- 'computer_use_click',
2009
- 'computer_use_type_text',
2010
- 'computer_use_request_control',
2011
- ];
2012
-
2013
- for (const name of actionableCuTools) {
2014
- const rule = findHighestPriorityRule(name, [name], '/tmp/test');
2015
- expect(rule).not.toBeNull();
2016
- expect(rule!.decision).toBe('ask');
2017
- }
2018
- });
2019
-
2020
- test('terminal CU tools (done/respond) have no default trust rules', () => {
2021
- // computer_use_done and computer_use_respond are terminal signal tools
2022
- // with RiskLevel.Low — they should not have ask rules since they don't
2023
- // perform any screen action.
2024
- const terminalCuTools = ['computer_use_done', 'computer_use_respond'];
2025
-
2026
- for (const name of terminalCuTools) {
2027
- const defaultRule = DEFAULT_TEMPLATES.find((t) => t.tool === name);
2028
- expect(defaultRule).toBeUndefined();
2029
- }
2030
- });
2031
- });