@penclipai/server 2026.704.0 → 2026.714.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (485) hide show
  1. package/dist/adapters/index.d.ts +1 -1
  2. package/dist/adapters/index.d.ts.map +1 -1
  3. package/dist/adapters/index.js.map +1 -1
  4. package/dist/adapters/process/execute.d.ts.map +1 -1
  5. package/dist/adapters/process/execute.js +1 -0
  6. package/dist/adapters/process/execute.js.map +1 -1
  7. package/dist/adapters/registry.d.ts.map +1 -1
  8. package/dist/adapters/registry.js +79 -51
  9. package/dist/adapters/registry.js.map +1 -1
  10. package/dist/adapters/utils.d.ts +5 -0
  11. package/dist/adapters/utils.d.ts.map +1 -1
  12. package/dist/adapters/utils.js.map +1 -1
  13. package/dist/agent-auth-jwt.d.ts +5 -1
  14. package/dist/agent-auth-jwt.d.ts.map +1 -1
  15. package/dist/agent-auth-jwt.js +72 -20
  16. package/dist/agent-auth-jwt.js.map +1 -1
  17. package/dist/app.d.ts +2 -0
  18. package/dist/app.d.ts.map +1 -1
  19. package/dist/app.js +10 -0
  20. package/dist/app.js.map +1 -1
  21. package/dist/built-ins/agents/reflection-coach/AGENTS.md +47 -0
  22. package/dist/built-ins/agents/reflection-coach/routines/recent-agent-reflection.md +77 -0
  23. package/dist/errors.d.ts +1 -1
  24. package/dist/errors.d.ts.map +1 -1
  25. package/dist/errors.js +2 -2
  26. package/dist/errors.js.map +1 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +208 -116
  29. package/dist/index.js.map +1 -1
  30. package/dist/middleware/api-compression.d.ts +8 -0
  31. package/dist/middleware/api-compression.d.ts.map +1 -0
  32. package/dist/middleware/api-compression.js +207 -0
  33. package/dist/middleware/api-compression.js.map +1 -0
  34. package/dist/middleware/auth.d.ts.map +1 -1
  35. package/dist/middleware/auth.js +130 -3
  36. package/dist/middleware/auth.js.map +1 -1
  37. package/dist/middleware/error-handler.d.ts.map +1 -1
  38. package/dist/middleware/error-handler.js +27 -0
  39. package/dist/middleware/error-handler.js.map +1 -1
  40. package/dist/onboarding-assets/default/AGENTS.md +1 -0
  41. package/dist/redaction.d.ts.map +1 -1
  42. package/dist/redaction.js +11 -0
  43. package/dist/redaction.js.map +1 -1
  44. package/dist/routes/access.d.ts.map +1 -1
  45. package/dist/routes/access.js +4 -3
  46. package/dist/routes/access.js.map +1 -1
  47. package/dist/routes/adapters.d.ts.map +1 -1
  48. package/dist/routes/adapters.js +2 -0
  49. package/dist/routes/adapters.js.map +1 -1
  50. package/dist/routes/agents.d.ts.map +1 -1
  51. package/dist/routes/agents.js +222 -87
  52. package/dist/routes/agents.js.map +1 -1
  53. package/dist/routes/attention.d.ts +3 -0
  54. package/dist/routes/attention.d.ts.map +1 -0
  55. package/dist/routes/attention.js +24 -0
  56. package/dist/routes/attention.js.map +1 -0
  57. package/dist/routes/authz.d.ts.map +1 -1
  58. package/dist/routes/authz.js +29 -1
  59. package/dist/routes/authz.js.map +1 -1
  60. package/dist/routes/board-chat.d.ts.map +1 -1
  61. package/dist/routes/board-chat.js +4 -1
  62. package/dist/routes/board-chat.js.map +1 -1
  63. package/dist/routes/built-in-agents.d.ts +3 -0
  64. package/dist/routes/built-in-agents.d.ts.map +1 -0
  65. package/dist/routes/built-in-agents.js +267 -0
  66. package/dist/routes/built-in-agents.js.map +1 -0
  67. package/dist/routes/cases.d.ts +46 -0
  68. package/dist/routes/cases.d.ts.map +1 -0
  69. package/dist/routes/cases.js +1395 -0
  70. package/dist/routes/cases.js.map +1 -0
  71. package/dist/routes/companies.d.ts.map +1 -1
  72. package/dist/routes/companies.js +4 -1
  73. package/dist/routes/companies.js.map +1 -1
  74. package/dist/routes/company-skills.d.ts.map +1 -1
  75. package/dist/routes/company-skills.js +453 -33
  76. package/dist/routes/company-skills.js.map +1 -1
  77. package/dist/routes/environments.d.ts.map +1 -1
  78. package/dist/routes/environments.js +71 -8
  79. package/dist/routes/environments.js.map +1 -1
  80. package/dist/routes/execution-workspaces.d.ts.map +1 -1
  81. package/dist/routes/execution-workspaces.js +89 -3
  82. package/dist/routes/execution-workspaces.js.map +1 -1
  83. package/dist/routes/health.d.ts +2 -0
  84. package/dist/routes/health.d.ts.map +1 -1
  85. package/dist/routes/health.js +30 -0
  86. package/dist/routes/health.js.map +1 -1
  87. package/dist/routes/inbox-dismissals.d.ts.map +1 -1
  88. package/dist/routes/inbox-dismissals.js +80 -19
  89. package/dist/routes/inbox-dismissals.js.map +1 -1
  90. package/dist/routes/index.d.ts +2 -0
  91. package/dist/routes/index.d.ts.map +1 -1
  92. package/dist/routes/index.js +2 -0
  93. package/dist/routes/index.js.map +1 -1
  94. package/dist/routes/issues.d.ts +25 -0
  95. package/dist/routes/issues.d.ts.map +1 -1
  96. package/dist/routes/issues.js +1455 -46
  97. package/dist/routes/issues.js.map +1 -1
  98. package/dist/routes/openapi.d.ts.map +1 -1
  99. package/dist/routes/openapi.js +448 -11
  100. package/dist/routes/openapi.js.map +1 -1
  101. package/dist/routes/pipelines.js +2 -2
  102. package/dist/routes/pipelines.js.map +1 -1
  103. package/dist/routes/resource-memberships.d.ts.map +1 -1
  104. package/dist/routes/resource-memberships.js +13 -5
  105. package/dist/routes/resource-memberships.js.map +1 -1
  106. package/dist/routes/secrets.d.ts.map +1 -1
  107. package/dist/routes/secrets.js +261 -1
  108. package/dist/routes/secrets.js.map +1 -1
  109. package/dist/routes/sidebar-badges.d.ts.map +1 -1
  110. package/dist/routes/sidebar-badges.js +18 -2
  111. package/dist/routes/sidebar-badges.js.map +1 -1
  112. package/dist/routes/user-profiles.d.ts.map +1 -1
  113. package/dist/routes/user-profiles.js +5 -4
  114. package/dist/routes/user-profiles.js.map +1 -1
  115. package/dist/server-info.d.ts +2 -0
  116. package/dist/server-info.d.ts.map +1 -1
  117. package/dist/server-info.js +20 -5
  118. package/dist/server-info.js.map +1 -1
  119. package/dist/services/access.d.ts +4 -4
  120. package/dist/services/access.d.ts.map +1 -1
  121. package/dist/services/activity.d.ts +3 -3
  122. package/dist/services/activity.d.ts.map +1 -1
  123. package/dist/services/activity.js +5 -4
  124. package/dist/services/activity.js.map +1 -1
  125. package/dist/services/adapter-registry-bootstrap.reconcile.test.js +1 -0
  126. package/dist/services/adapter-registry-bootstrap.reconcile.test.js.map +1 -1
  127. package/dist/services/agent-secret-bindings.d.ts +15 -0
  128. package/dist/services/agent-secret-bindings.d.ts.map +1 -1
  129. package/dist/services/agent-secret-bindings.js +43 -0
  130. package/dist/services/agent-secret-bindings.js.map +1 -1
  131. package/dist/services/agents.d.ts +194 -133
  132. package/dist/services/agents.d.ts.map +1 -1
  133. package/dist/services/agents.js +107 -4
  134. package/dist/services/agents.js.map +1 -1
  135. package/dist/services/approvals.d.ts +10 -10
  136. package/dist/services/approvals.d.ts.map +1 -1
  137. package/dist/services/approvals.js +9 -1
  138. package/dist/services/approvals.js.map +1 -1
  139. package/dist/services/assets.d.ts +5 -5
  140. package/dist/services/attention.d.ts +11 -0
  141. package/dist/services/attention.d.ts.map +1 -0
  142. package/dist/services/attention.js +1024 -0
  143. package/dist/services/attention.js.map +1 -0
  144. package/dist/services/authorization.d.ts +15 -2
  145. package/dist/services/authorization.d.ts.map +1 -1
  146. package/dist/services/authorization.js +324 -8
  147. package/dist/services/authorization.js.map +1 -1
  148. package/dist/services/board-auth.d.ts +2 -2
  149. package/dist/services/budgets.d.ts.map +1 -1
  150. package/dist/services/budgets.js +8 -7
  151. package/dist/services/budgets.js.map +1 -1
  152. package/dist/services/built-in-agent-metadata.d.ts +9 -0
  153. package/dist/services/built-in-agent-metadata.d.ts.map +1 -0
  154. package/dist/services/built-in-agent-metadata.js +39 -0
  155. package/dist/services/built-in-agent-metadata.js.map +1 -0
  156. package/dist/services/built-in-agents.d.ts +394 -0
  157. package/dist/services/built-in-agents.d.ts.map +1 -0
  158. package/dist/services/built-in-agents.js +1426 -0
  159. package/dist/services/built-in-agents.js.map +1 -0
  160. package/dist/services/change-consent-gate.d.ts +18 -0
  161. package/dist/services/change-consent-gate.d.ts.map +1 -0
  162. package/dist/services/change-consent-gate.js +170 -0
  163. package/dist/services/change-consent-gate.js.map +1 -0
  164. package/dist/services/companies.d.ts +14 -8
  165. package/dist/services/companies.d.ts.map +1 -1
  166. package/dist/services/companies.js +6 -1
  167. package/dist/services/companies.js.map +1 -1
  168. package/dist/services/company-artifacts.d.ts +4 -1
  169. package/dist/services/company-artifacts.d.ts.map +1 -1
  170. package/dist/services/company-artifacts.js +9 -1
  171. package/dist/services/company-artifacts.js.map +1 -1
  172. package/dist/services/company-member-roles.d.ts.map +1 -1
  173. package/dist/services/company-member-roles.js +2 -0
  174. package/dist/services/company-member-roles.js.map +1 -1
  175. package/dist/services/company-portability.d.ts +1 -1
  176. package/dist/services/company-portability.d.ts.map +1 -1
  177. package/dist/services/company-portability.js +113 -8
  178. package/dist/services/company-portability.js.map +1 -1
  179. package/dist/services/company-search.d.ts.map +1 -1
  180. package/dist/services/company-search.js +843 -294
  181. package/dist/services/company-search.js.map +1 -1
  182. package/dist/services/company-skills.d.ts +64 -3
  183. package/dist/services/company-skills.d.ts.map +1 -1
  184. package/dist/services/company-skills.js +1205 -20
  185. package/dist/services/company-skills.js.map +1 -1
  186. package/dist/services/costs.d.ts +9 -8
  187. package/dist/services/costs.d.ts.map +1 -1
  188. package/dist/services/costs.js +9 -2
  189. package/dist/services/costs.js.map +1 -1
  190. package/dist/services/dashboard.d.ts +2 -0
  191. package/dist/services/dashboard.d.ts.map +1 -1
  192. package/dist/services/dashboard.js +60 -17
  193. package/dist/services/dashboard.js.map +1 -1
  194. package/dist/services/database-backup-health.d.ts +34 -0
  195. package/dist/services/database-backup-health.d.ts.map +1 -0
  196. package/dist/services/database-backup-health.js +104 -0
  197. package/dist/services/database-backup-health.js.map +1 -0
  198. package/dist/services/document-annotations.d.ts +169 -1
  199. package/dist/services/document-annotations.d.ts.map +1 -1
  200. package/dist/services/document-annotations.js +272 -1
  201. package/dist/services/document-annotations.js.map +1 -1
  202. package/dist/services/documents.d.ts +368 -0
  203. package/dist/services/documents.d.ts.map +1 -1
  204. package/dist/services/documents.js +2 -2
  205. package/dist/services/documents.js.map +1 -1
  206. package/dist/services/environment-config.d.ts.map +1 -1
  207. package/dist/services/environment-config.js +6 -0
  208. package/dist/services/environment-config.js.map +1 -1
  209. package/dist/services/environment-custom-image-runtime.d.ts +28 -0
  210. package/dist/services/environment-custom-image-runtime.d.ts.map +1 -1
  211. package/dist/services/environment-custom-image-runtime.js +106 -7
  212. package/dist/services/environment-custom-image-runtime.js.map +1 -1
  213. package/dist/services/environment-custom-images.d.ts +31 -1
  214. package/dist/services/environment-custom-images.d.ts.map +1 -1
  215. package/dist/services/environment-custom-images.js +110 -4
  216. package/dist/services/environment-custom-images.js.map +1 -1
  217. package/dist/services/environment-execution-target.d.ts.map +1 -1
  218. package/dist/services/environment-execution-target.js +1 -3
  219. package/dist/services/environment-execution-target.js.map +1 -1
  220. package/dist/services/environment-probe.d.ts +1 -0
  221. package/dist/services/environment-probe.d.ts.map +1 -1
  222. package/dist/services/environment-probe.js +99 -5
  223. package/dist/services/environment-probe.js.map +1 -1
  224. package/dist/services/environments.d.ts +3 -1
  225. package/dist/services/environments.d.ts.map +1 -1
  226. package/dist/services/environments.js +98 -3
  227. package/dist/services/environments.js.map +1 -1
  228. package/dist/services/execution-workspace-policy.d.ts +29 -3
  229. package/dist/services/execution-workspace-policy.d.ts.map +1 -1
  230. package/dist/services/execution-workspace-policy.js +44 -1
  231. package/dist/services/execution-workspace-policy.js.map +1 -1
  232. package/dist/services/execution-workspaces.d.ts +64 -1
  233. package/dist/services/execution-workspaces.d.ts.map +1 -1
  234. package/dist/services/execution-workspaces.js +634 -3
  235. package/dist/services/execution-workspaces.js.map +1 -1
  236. package/dist/services/external-objects.d.ts +26 -26
  237. package/dist/services/feedback.d.ts +2 -2
  238. package/dist/services/finance.d.ts +5 -5
  239. package/dist/services/goals.d.ts +8 -8
  240. package/dist/services/heartbeat-stop-metadata.d.ts +2 -2
  241. package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -1
  242. package/dist/services/heartbeat-stop-metadata.js +4 -0
  243. package/dist/services/heartbeat-stop-metadata.js.map +1 -1
  244. package/dist/services/heartbeat-stop-metadata.test.js +9 -0
  245. package/dist/services/heartbeat-stop-metadata.test.js.map +1 -1
  246. package/dist/services/heartbeat.d.ts +133 -28
  247. package/dist/services/heartbeat.d.ts.map +1 -1
  248. package/dist/services/heartbeat.js +1963 -239
  249. package/dist/services/heartbeat.js.map +1 -1
  250. package/dist/services/inbox-dismissals.d.ts +26 -0
  251. package/dist/services/inbox-dismissals.d.ts.map +1 -1
  252. package/dist/services/inbox-dismissals.js +33 -18
  253. package/dist/services/inbox-dismissals.js.map +1 -1
  254. package/dist/services/index.d.ts +3 -1
  255. package/dist/services/index.d.ts.map +1 -1
  256. package/dist/services/index.js +3 -1
  257. package/dist/services/index.js.map +1 -1
  258. package/dist/services/instance-settings.d.ts +25 -1
  259. package/dist/services/instance-settings.d.ts.map +1 -1
  260. package/dist/services/instance-settings.js +103 -5
  261. package/dist/services/instance-settings.js.map +1 -1
  262. package/dist/services/issue-continuation-summary.js +1 -1
  263. package/dist/services/issue-continuation-summary.js.map +1 -1
  264. package/dist/services/issue-recovery-actions.d.ts +3 -0
  265. package/dist/services/issue-recovery-actions.d.ts.map +1 -1
  266. package/dist/services/issue-recovery-actions.js +9 -0
  267. package/dist/services/issue-recovery-actions.js.map +1 -1
  268. package/dist/services/issue-rewake-throttle.d.ts +92 -0
  269. package/dist/services/issue-rewake-throttle.d.ts.map +1 -0
  270. package/dist/services/issue-rewake-throttle.js +139 -0
  271. package/dist/services/issue-rewake-throttle.js.map +1 -0
  272. package/dist/services/issue-thread-interactions.d.ts +8 -1
  273. package/dist/services/issue-thread-interactions.d.ts.map +1 -1
  274. package/dist/services/issue-thread-interactions.js +231 -16
  275. package/dist/services/issue-thread-interactions.js.map +1 -1
  276. package/dist/services/issue-visibility.d.ts +4 -0
  277. package/dist/services/issue-visibility.d.ts.map +1 -0
  278. package/dist/services/issue-visibility.js +9 -0
  279. package/dist/services/issue-visibility.js.map +1 -0
  280. package/dist/services/issues.d.ts +225 -105
  281. package/dist/services/issues.d.ts.map +1 -1
  282. package/dist/services/issues.js +540 -18
  283. package/dist/services/issues.js.map +1 -1
  284. package/dist/services/local-service-supervisor.d.ts +3 -0
  285. package/dist/services/local-service-supervisor.d.ts.map +1 -1
  286. package/dist/services/local-service-supervisor.js +51 -0
  287. package/dist/services/local-service-supervisor.js.map +1 -1
  288. package/dist/services/pipeline-case-outputs.d.ts.map +1 -1
  289. package/dist/services/pipeline-case-outputs.js +2 -1
  290. package/dist/services/pipeline-case-outputs.js.map +1 -1
  291. package/dist/services/pipelines-aggregation.d.ts.map +1 -1
  292. package/dist/services/pipelines-aggregation.js +3 -2
  293. package/dist/services/pipelines-aggregation.js.map +1 -1
  294. package/dist/services/pipelines.d.ts +165 -165
  295. package/dist/services/pipelines.d.ts.map +1 -1
  296. package/dist/services/pipelines.js +6 -4
  297. package/dist/services/pipelines.js.map +1 -1
  298. package/dist/services/plugin-database.d.ts +2 -2
  299. package/dist/services/plugin-environment-driver.d.ts +1 -1
  300. package/dist/services/plugin-host-services.d.ts.map +1 -1
  301. package/dist/services/plugin-host-services.js +3 -1
  302. package/dist/services/plugin-host-services.js.map +1 -1
  303. package/dist/services/plugin-managed-routines.d.ts +1 -0
  304. package/dist/services/plugin-managed-routines.d.ts.map +1 -1
  305. package/dist/services/plugin-registry.d.ts +21 -21
  306. package/dist/services/productivity-review.d.ts +1 -0
  307. package/dist/services/productivity-review.d.ts.map +1 -1
  308. package/dist/services/productivity-review.js +6 -5
  309. package/dist/services/productivity-review.js.map +1 -1
  310. package/dist/services/projects.d.ts +9 -9
  311. package/dist/services/recovery/service.d.ts +82 -54
  312. package/dist/services/recovery/service.d.ts.map +1 -1
  313. package/dist/services/recovery/service.js +200 -35
  314. package/dist/services/recovery/service.js.map +1 -1
  315. package/dist/services/recovery/successful-run-handoff.d.ts +1 -0
  316. package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -1
  317. package/dist/services/recovery/successful-run-handoff.js +2 -0
  318. package/dist/services/recovery/successful-run-handoff.js.map +1 -1
  319. package/dist/services/recovery/successful-run-handoff.test.js +20 -0
  320. package/dist/services/recovery/successful-run-handoff.test.js.map +1 -1
  321. package/dist/services/resource-memberships.d.ts +13 -10
  322. package/dist/services/resource-memberships.d.ts.map +1 -1
  323. package/dist/services/resource-memberships.js +89 -20
  324. package/dist/services/resource-memberships.js.map +1 -1
  325. package/dist/services/responsible-user-denial-run-outcomes.d.ts +60 -0
  326. package/dist/services/responsible-user-denial-run-outcomes.d.ts.map +1 -0
  327. package/dist/services/responsible-user-denial-run-outcomes.js +56 -0
  328. package/dist/services/responsible-user-denial-run-outcomes.js.map +1 -0
  329. package/dist/services/responsible-user-denial-run-outcomes.test.d.ts +2 -0
  330. package/dist/services/responsible-user-denial-run-outcomes.test.d.ts.map +1 -0
  331. package/dist/services/responsible-user-denial-run-outcomes.test.js +75 -0
  332. package/dist/services/responsible-user-denial-run-outcomes.test.js.map +1 -0
  333. package/dist/services/routines.d.ts +30 -8
  334. package/dist/services/routines.d.ts.map +1 -1
  335. package/dist/services/routines.js +241 -18
  336. package/dist/services/routines.js.map +1 -1
  337. package/dist/services/run-liveness.d.ts.map +1 -1
  338. package/dist/services/run-liveness.js +21 -0
  339. package/dist/services/run-liveness.js.map +1 -1
  340. package/dist/services/run-log-store.d.ts +1 -0
  341. package/dist/services/run-log-store.d.ts.map +1 -1
  342. package/dist/services/run-log-store.js +4 -0
  343. package/dist/services/run-log-store.js.map +1 -1
  344. package/dist/services/run-scratch.d.ts +42 -0
  345. package/dist/services/run-scratch.d.ts.map +1 -0
  346. package/dist/services/run-scratch.js +111 -0
  347. package/dist/services/run-scratch.js.map +1 -0
  348. package/dist/services/run-scratch.test.d.ts +2 -0
  349. package/dist/services/run-scratch.test.d.ts.map +1 -0
  350. package/dist/services/run-scratch.test.js +98 -0
  351. package/dist/services/run-scratch.test.js.map +1 -0
  352. package/dist/services/secrets.d.ts +1261 -64
  353. package/dist/services/secrets.d.ts.map +1 -1
  354. package/dist/services/secrets.js +1089 -96
  355. package/dist/services/secrets.js.map +1 -1
  356. package/dist/services/task-watchdogs.d.ts +1 -0
  357. package/dist/services/task-watchdogs.d.ts.map +1 -1
  358. package/dist/services/task-watchdogs.js +78 -9
  359. package/dist/services/task-watchdogs.js.map +1 -1
  360. package/dist/services/work-timeline.d.ts.map +1 -1
  361. package/dist/services/work-timeline.js +34 -2
  362. package/dist/services/work-timeline.js.map +1 -1
  363. package/dist/services/workspace-runtime-read-model.d.ts +30 -29
  364. package/dist/services/workspace-runtime-read-model.d.ts.map +1 -1
  365. package/dist/services/workspace-runtime-read-model.js.map +1 -1
  366. package/dist/services/workspace-runtime.d.ts +59 -13
  367. package/dist/services/workspace-runtime.d.ts.map +1 -1
  368. package/dist/services/workspace-runtime.js +1121 -102
  369. package/dist/services/workspace-runtime.js.map +1 -1
  370. package/dist/skills-catalog/catalog/bundled/paperclip-operations/reflection-coach/SKILL.md +202 -0
  371. package/dist/skills-catalog/catalog/bundled/product/paperclip-capsules/SKILL.md +1 -1
  372. package/dist/skills-catalog/catalog/bundled/product/wireframe/SKILL.md +1 -1
  373. package/dist/skills-catalog/catalog/optional/content/release-announcement/SKILL.md +90 -0
  374. package/dist/skills-catalog/catalog/optional/finance/ramp/SKILL.md +98 -0
  375. package/dist/skills-catalog/generated/catalog.json +84 -12
  376. package/dist/ui-branding.d.ts +7 -0
  377. package/dist/ui-branding.d.ts.map +1 -1
  378. package/dist/ui-branding.js +8 -2
  379. package/dist/ui-branding.js.map +1 -1
  380. package/dist/version.d.ts +14 -0
  381. package/dist/version.d.ts.map +1 -1
  382. package/dist/version.js +114 -3
  383. package/dist/version.js.map +1 -1
  384. package/package.json +19 -20
  385. package/skills/paperclip/SKILL.md +45 -10
  386. package/skills/paperclip/references/api-reference.md +246 -1
  387. package/skills/paperclip/references/cases.md +295 -0
  388. package/skills/paperclip/references/company-skills.md +1 -1
  389. package/skills/paperclip-board/SKILL.md +3 -4
  390. package/skills/paperclip-converting-plans-to-tasks/SKILL.md +3 -7
  391. package/skills/para-memory-files/SKILL.md +3 -7
  392. package/ui-dist/assets/{abnfDiagram-VRR7QNED-BsFz2IiG.js → abnfDiagram-VRR7QNED-Dtgfy1PK.js} +1 -1
  393. package/ui-dist/assets/{arc-HZhAwbOs.js → arc-BGdvNvs8.js} +1 -1
  394. package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-BteMjuOn.js → architectureDiagram-ZJ3FMSHR-CK19SQDx.js} +1 -1
  395. package/ui-dist/assets/{blockDiagram-677ZJIJ3-Iq66uZjf.js → blockDiagram-677ZJIJ3-Kh4N3XG4.js} +1 -1
  396. package/ui-dist/assets/{browser-ponyfill-BBsuyZJt.js → browser-ponyfill-B3hXtEFW.js} +1 -1
  397. package/ui-dist/assets/{c4Diagram-LMCZKHZV-_nlglj7I.js → c4Diagram-LMCZKHZV-CBHpGrgU.js} +1 -1
  398. package/ui-dist/assets/channel-4fjB8e8N.js +1 -0
  399. package/ui-dist/assets/{chunk-2Q5K7J3B-Cvz7WwSQ.js → chunk-2Q5K7J3B-BdHbqi7k.js} +1 -1
  400. package/ui-dist/assets/{chunk-32BRIVSS-LLubniCS.js → chunk-32BRIVSS-DxtzZzpt.js} +1 -1
  401. package/ui-dist/assets/{chunk-5VM5RSS4-BOst5hcr.js → chunk-5VM5RSS4-DMKxBbgu.js} +1 -1
  402. package/ui-dist/assets/{chunk-EX3LRPZG-D5WNShVm.js → chunk-EX3LRPZG-BaIOPi4v.js} +1 -1
  403. package/ui-dist/assets/{chunk-JWPE2WC7-CA07x4av.js → chunk-JWPE2WC7-DGo3MPmr.js} +1 -1
  404. package/ui-dist/assets/{chunk-MOJQB5TN-Clfi83rK.js → chunk-MOJQB5TN-BEhQg0uD.js} +1 -1
  405. package/ui-dist/assets/{chunk-RYQCIY6F-BhZ6p7YJ.js → chunk-RYQCIY6F-HPJ6zRpJ.js} +1 -1
  406. package/ui-dist/assets/{chunk-V7JOEXUC-CdexJGoV.js → chunk-V7JOEXUC-Bl_6cAJD.js} +1 -1
  407. package/ui-dist/assets/{chunk-VR4S4FIN-8vjmBhr1.js → chunk-VR4S4FIN-KMjgGCsw.js} +1 -1
  408. package/ui-dist/assets/{chunk-XXDRQBXY-CqmCaplj.js → chunk-XXDRQBXY-oWGAIKjr.js} +1 -1
  409. package/ui-dist/assets/classDiagram-OUVF2IWQ-CTXxO5l9.js +1 -0
  410. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CTXxO5l9.js +1 -0
  411. package/ui-dist/assets/{cose-bilkent-JH36ORCC-CY-zAygY.js → cose-bilkent-JH36ORCC-Ckqse1pH.js} +1 -1
  412. package/ui-dist/assets/{cynefin-VYW2F7L2-BQSQ1HKr.js → cynefin-VYW2F7L2-DfZ8Hzqi.js} +1 -1
  413. package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-NJCuFrZL.js → cynefinDiagram-TSTJHNR4-BkDQo9oi.js} +1 -1
  414. package/ui-dist/assets/{dagre-VKFMJZFB-DAUPIc8g.js → dagre-VKFMJZFB-ClghXQ6E.js} +1 -1
  415. package/ui-dist/assets/{diagram-FQU43EPY-DvMwJlCi.js → diagram-FQU43EPY-D5a0CwpV.js} +1 -1
  416. package/ui-dist/assets/{diagram-G47NLZAW-DPY0Jz_y.js → diagram-G47NLZAW-hVjYR_xl.js} +1 -1
  417. package/ui-dist/assets/{diagram-NH7WQ7WH-BpopeEhp.js → diagram-NH7WQ7WH-BlDPgD5Z.js} +1 -1
  418. package/ui-dist/assets/{diagram-OA4YK3LP-Qpu05z9X.js → diagram-OA4YK3LP-D0iZKIS3.js} +1 -1
  419. package/ui-dist/assets/{diagram-WEI45ONY-DHnvw9XU.js → diagram-WEI45ONY-m6hrfD0J.js} +1 -1
  420. package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-D61S54_7.js → ebnfDiagram-CCIWWBDH-D4SQYLod.js} +1 -1
  421. package/ui-dist/assets/{erDiagram-Q63AITRT-pplAN_x2.js → erDiagram-Q63AITRT-Byzb6-I5.js} +1 -1
  422. package/ui-dist/assets/{flowDiagram-23GEKE2U-BaBcbyDq.js → flowDiagram-23GEKE2U-CaTUkFO6.js} +1 -1
  423. package/ui-dist/assets/{ganttDiagram-NO4QXBWP-BOJSII2b.js → ganttDiagram-NO4QXBWP-DmdMQ3ES.js} +1 -1
  424. package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-aYAaO4a2.js → gitGraphDiagram-IHSO6WYX-Bt-1g2CS.js} +1 -1
  425. package/ui-dist/assets/{index-CZxYajxF.js → index-2Dk9yO0W.js} +1 -1
  426. package/ui-dist/assets/{index-B8H0SHw1.js → index-8jLpgFNU.js} +1 -1
  427. package/ui-dist/assets/{index-3EKATXgc.js → index-AP_iimxs.js} +1 -1
  428. package/ui-dist/assets/{index-C2JgdjPa.js → index-BNJasQY9.js} +1 -1
  429. package/ui-dist/assets/{index-BaiskB3o.js → index-BO-aNmyX.js} +1 -1
  430. package/ui-dist/assets/{index-B5YenxAQ.js → index-BPCzCn1Y.js} +1 -1
  431. package/ui-dist/assets/{index-Da2kqT1M.js → index-BSLnLTb2.js} +1 -1
  432. package/ui-dist/assets/{index-By0oeEvz.js → index-CCZ2qgHN.js} +1 -1
  433. package/ui-dist/assets/{index-bUFCSEgv.js → index-CLZXR1k9.js} +1 -1
  434. package/ui-dist/assets/{index-CJlw4l9L.js → index-CTNfrE1s.js} +1 -1
  435. package/ui-dist/assets/{index-TmpY6Xh_.js → index-ChNd25s7.js} +1 -1
  436. package/ui-dist/assets/{index-BRgdHUNR.js → index-CjHiMOEO.js} +1 -1
  437. package/ui-dist/assets/{index-DrL6pHSJ.js → index-CmeJQpXZ.js} +1 -1
  438. package/ui-dist/assets/index-CyuYtn7U.css +1 -0
  439. package/ui-dist/assets/{index-CKKytnOd.js → index-CzZjM8eh.js} +1 -1
  440. package/ui-dist/assets/{index-Dceamza_.js → index-D-6T0Aft.js} +1 -1
  441. package/ui-dist/assets/{index-CjDqB1SX.js → index-DG_7V1pG.js} +1 -1
  442. package/ui-dist/assets/index-DIKcgxCt.js +676 -0
  443. package/ui-dist/assets/{index-BKVeJ3Jh.js → index-DiUerHmi.js} +1 -1
  444. package/ui-dist/assets/{index-C-nKjFZh.js → index-NZ7v5Fc2.js} +1 -1
  445. package/ui-dist/assets/{index-CvPPuNs1.js → index-Py70EvBg.js} +1 -1
  446. package/ui-dist/assets/{index-LHkZdu0h.js → index-R3aYn0qM.js} +1 -1
  447. package/ui-dist/assets/{index-BT8b79Bd.js → index-YXpm7gW-.js} +1 -1
  448. package/ui-dist/assets/{index-CemenVOf.js → index-n4q3gzki.js} +1 -1
  449. package/ui-dist/assets/{index-D3ODiUup.js → index-vlZ3GoYY.js} +1 -1
  450. package/ui-dist/assets/{infoDiagram-FWYZ7A6U-C7_90qGq.js → infoDiagram-FWYZ7A6U-jdi5ciVg.js} +1 -1
  451. package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-T-nPPdTt.js → ishikawaDiagram-FXEZZL3T-CMz5VvYp.js} +1 -1
  452. package/ui-dist/assets/{journeyDiagram-5HDEW3XC-C3hqQN0R.js → journeyDiagram-5HDEW3XC-DK6dk2R-.js} +1 -1
  453. package/ui-dist/assets/{kanban-definition-HUTT4EX6-CPoD9DUm.js → kanban-definition-HUTT4EX6-DX5cZL-D.js} +1 -1
  454. package/ui-dist/assets/{linear-D-S8yr3Z.js → linear-CI96ABKp.js} +1 -1
  455. package/ui-dist/assets/{mermaid.core-DhvH8shK.js → mermaid.core-DnsEVfbd.js} +4 -4
  456. package/ui-dist/assets/{mindmap-definition-LN4V7U3C-CxCIKrKo.js → mindmap-definition-LN4V7U3C-BdDCY7hf.js} +1 -1
  457. package/ui-dist/assets/{pegDiagram-2B236MQR-D4G-PC6N.js → pegDiagram-2B236MQR-DuY4TF-f.js} +1 -1
  458. package/ui-dist/assets/{pieDiagram-ENE6RG2P-N4X8UvqY.js → pieDiagram-ENE6RG2P-bgFw7dm3.js} +1 -1
  459. package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-CZohoDln.js → quadrantDiagram-ABIIQ3AL-BCZVWyWS.js} +1 -1
  460. package/ui-dist/assets/{railroadDiagram-RFXS5EU6-fMGQVhwF.js → railroadDiagram-RFXS5EU6-D9V5_Cgc.js} +1 -1
  461. package/ui-dist/assets/{requirementDiagram-TGXJPOKE-CK0IuUmk.js → requirementDiagram-TGXJPOKE-CItsya2r.js} +1 -1
  462. package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-pSfb3CqG.js → sankeyDiagram-HTMAVEWB-Cz9IHsZO.js} +1 -1
  463. package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-CbhkqIeq.js → sequenceDiagram-DBY2YBRQ-hMSqTiwK.js} +1 -1
  464. package/ui-dist/assets/{sizeCapture-X5ZJPWSS-B6vqqPx-.js → sizeCapture-X5ZJPWSS-0g9M5wMf.js} +1 -1
  465. package/ui-dist/assets/{stateDiagram-2N3HPSRC-DIJf9h5C.js → stateDiagram-2N3HPSRC-F8_YhgWl.js} +1 -1
  466. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-fZImUBtq.js +1 -0
  467. package/ui-dist/assets/{swimlanes-5IMT3BWC-DIX_LV8O.js → swimlanes-5IMT3BWC-CCZqPbpE.js} +2 -2
  468. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-9C1UT51R.js +8 -0
  469. package/ui-dist/assets/{timeline-definition-FHXFAJF6-CD-Pxb_g.js → timeline-definition-FHXFAJF6-BeUIr18x.js} +1 -1
  470. package/ui-dist/assets/{vennDiagram-L72KCM5P-BAZsWL0R.js → vennDiagram-L72KCM5P-D3d5h_G4.js} +1 -1
  471. package/ui-dist/assets/{wardleyDiagram-EHGQE667-CnNyfwX5.js → wardleyDiagram-EHGQE667-Bzt-As9A.js} +1 -1
  472. package/ui-dist/assets/{xychartDiagram-FW5EYKEG-j9YgsD3Y.js → xychartDiagram-FW5EYKEG-C2mly0xf.js} +1 -1
  473. package/ui-dist/fonts/InterVariable-Italic.woff2 +0 -0
  474. package/ui-dist/fonts/InterVariable.woff2 +0 -0
  475. package/ui-dist/fonts/NOTICE.md +17 -0
  476. package/ui-dist/index.html +2 -2
  477. package/ui-dist/locales/en/common.json +1142 -5
  478. package/ui-dist/locales/zh-CN/common.json +1140 -3
  479. package/ui-dist/assets/channel-CsiI2LYw.js +0 -1
  480. package/ui-dist/assets/classDiagram-OUVF2IWQ-CBn1Jqo8.js +0 -1
  481. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CBn1Jqo8.js +0 -1
  482. package/ui-dist/assets/index-BsL8K1vL.css +0 -1
  483. package/ui-dist/assets/index-D__fTsb2.js +0 -643
  484. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DpwLD7YL.js +0 -1
  485. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-Cklauutg.js +0 -8
@@ -1,12 +1,12 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { and, desc, eq, inArray, like, ne, notInArray, or, sql } from "drizzle-orm";
3
- import { agents, companySecretBindings, companySecretProviderConfigs, companySecrets, companySecretVersions, environments, heartbeatRuns, issues, projects, routines, secretAccessEvents, } from "@penclipai/db";
3
+ import { agents, companySecretBindings, companySecretProviderConfigs, companySecrets, companySecretVersions, companyMemberships, environments, heartbeatRuns, issues, projects, routines, secretAccessEvents, userSecretDeclarations, userSecretDefinitions, } from "@penclipai/db";
4
4
  import { createSecretProviderConfigSchema, deriveProjectUrlKey, envBindingSchema, isUuidLike, normalizeAgentUrlKey, secretProviderConfigPayloadSchema, secretProviderConfigDiscoveryPreviewSchema, updateSecretProviderConfigSchema, } from "@penclipai/shared";
5
5
  import { conflict, forbidden, HttpError, notFound, unprocessable } from "../errors.js";
6
6
  import { logger } from "../middleware/logger.js";
7
7
  import { checkSecretProviders, getSecretProvider, listSecretProviders, } from "../secrets/provider-registry.js";
8
8
  import { isSecretProviderClientError } from "../secrets/types.js";
9
- import { authorizationService } from "./authorization.js";
9
+ import { authorizationDeniedDetails, authorizationService } from "./authorization.js";
10
10
  import { findActiveServerAdapter } from "../adapters/index.js";
11
11
  const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
12
12
  const SENSITIVE_ENV_KEY_RE = /(api[-_]?key|access[-_]?token|auth(?:_?token)?|authorization|bearer|secret|passwd|password|credential|jwt|private[-_]?key|cookie|connectionstring)/i;
@@ -18,6 +18,21 @@ const COMING_SOON_SECRET_PROVIDERS = new Set([
18
18
  const FALLBACK_ADAPTER_SCHEMA_SECRET_FIELDS = {
19
19
  hermes_gateway: ["apiKey"],
20
20
  };
21
+ const USER_SECRET_DEFINITION_KEY_UNIQUE_CONSTRAINT = "user_secret_definitions_company_key_uq";
22
+ const USER_SECRET_VALUE_UNIQUE_CONSTRAINT = "company_secrets_user_definition_owner_uq";
23
+ function isUniqueConstraintViolation(error, constraintName) {
24
+ const seen = new Set();
25
+ let current = error;
26
+ while (typeof current === "object" && current !== null && !seen.has(current)) {
27
+ seen.add(current);
28
+ const maybe = current;
29
+ const constraint = maybe.constraint ?? maybe.constraint_name;
30
+ if (maybe.code === "23505" && constraint === constraintName)
31
+ return true;
32
+ current = maybe.cause;
33
+ }
34
+ return false;
35
+ }
21
36
  function remoteProviderHttpError(error, context) {
22
37
  if (isSecretProviderClientError(error)) {
23
38
  logger.warn({
@@ -42,10 +57,110 @@ function remoteProviderHttpError(error, context) {
42
57
  }, "remote secret provider request failed");
43
58
  return new HttpError(502, "Remote secret provider request failed.", safeRemoteProviderErrorDetails(null, context));
44
59
  }
60
+ function remoteProviderWriteHttpError(error, context) {
61
+ return remoteProviderHttpError(error, {
62
+ companyId: context.companyId,
63
+ provider: context.provider,
64
+ providerConfigId: context.providerConfig?.id ?? context.providerConfigId ?? "deployment-default",
65
+ operation: context.operation,
66
+ providerConfig: context.providerConfig?.config ?? null,
67
+ });
68
+ }
69
+ async function throwProviderWriteOrReservedRowRollbackError(input) {
70
+ const providerError = remoteProviderWriteHttpError(input.error, input);
71
+ try {
72
+ await input.rollbackReservedRow();
73
+ }
74
+ catch (rollbackError) {
75
+ const providerConfigId = input.providerConfig?.id ?? input.providerConfigId ?? "deployment-default";
76
+ logger.warn({
77
+ err: rollbackError,
78
+ providerErr: providerError,
79
+ companyId: input.companyId,
80
+ provider: input.provider,
81
+ providerConfigId,
82
+ operation: input.operation,
83
+ }, "remote secret provider write failed and reserved secret rollback failed");
84
+ throw new HttpError(500, "Secret create failed and Paperclip could not roll back the local secret reservation.", {
85
+ code: "secret_create_rollback_failed",
86
+ provider: input.provider,
87
+ operation: input.operation,
88
+ providerConfigId,
89
+ providerError: {
90
+ status: providerError.status,
91
+ message: providerError.message,
92
+ details: providerError.details ?? null,
93
+ },
94
+ });
95
+ }
96
+ throw providerError;
97
+ }
98
+ function providerConfigIdentifier(input) {
99
+ return input.providerConfig?.id ?? input.providerConfigId ?? "deployment-default";
100
+ }
101
+ async function deleteLocalSecretCreateReservationOrThrow(input) {
102
+ try {
103
+ await input.db.delete(companySecretVersions).where(eq(companySecretVersions.secretId, input.secretId));
104
+ await input.db.delete(companySecrets).where(eq(companySecrets.id, input.secretId));
105
+ }
106
+ catch (rollbackError) {
107
+ const providerConfigId = providerConfigIdentifier(input);
108
+ logger.warn({
109
+ err: rollbackError,
110
+ companyId: input.companyId,
111
+ provider: input.provider,
112
+ providerConfigId,
113
+ operation: input.operation,
114
+ }, "secret create failed and local reserved secret rollback failed");
115
+ throw new HttpError(500, "Secret create failed and Paperclip could not roll back the local secret reservation.", {
116
+ code: "secret_create_rollback_failed",
117
+ provider: input.provider,
118
+ operation: input.operation,
119
+ providerConfigId,
120
+ });
121
+ }
122
+ }
123
+ function throwProviderCleanupFailedAfterCreateRollback(input) {
124
+ const providerConfigId = providerConfigIdentifier(input);
125
+ throw new HttpError(500, "Secret create failed and Paperclip could not clean up the remote provider secret.", {
126
+ code: "secret_create_provider_cleanup_failed",
127
+ provider: input.provider,
128
+ operation: input.operation,
129
+ providerConfigId,
130
+ localCleanupHandle: true,
131
+ });
132
+ }
45
133
  function safeRemoteProviderErrorDetails(error, context) {
46
134
  if (context.provider !== "aws_secrets_manager" ||
47
135
  context.operation !== "secret_provider_config.discovery.preview") {
48
- return { code: error?.code ?? "provider_error" };
136
+ if (context.provider !== "aws_secrets_manager") {
137
+ return { code: error?.code ?? "provider_error" };
138
+ }
139
+ const details = {
140
+ code: error?.code ?? "provider_error",
141
+ provider: context.provider,
142
+ operation: context.operation,
143
+ providerConfigId: context.providerConfigId,
144
+ };
145
+ const region = safeString(context.providerConfig?.region);
146
+ if (region)
147
+ details.region = region;
148
+ details.credentialPath = "Paperclip server runtime/provider credential path";
149
+ if (error?.code === "access_denied") {
150
+ if (context.operation === "secret.create") {
151
+ details.requiredCapability = "secretsmanager:CreateSecret";
152
+ details.actionableMessage =
153
+ "AWS managed secret creation needs secretsmanager:CreateSecret in the selected region for this provider vault. If the vault config uses a KMS key, the runtime credentials also need KMS write permissions for that key.";
154
+ details.safeAlternative =
155
+ "If the secret already exists in AWS, link it as an external reference instead of creating a Paperclip-managed value.";
156
+ }
157
+ else if (context.operation === "secret.rotate") {
158
+ details.requiredCapability = "secretsmanager:PutSecretValue";
159
+ details.actionableMessage =
160
+ "AWS managed secret rotation needs secretsmanager:PutSecretValue for the selected provider vault and managed secret path.";
161
+ }
162
+ }
163
+ return details;
49
164
  }
50
165
  const details = {
51
166
  code: error?.code ?? "provider_error",
@@ -145,6 +260,15 @@ function canonicalizeBinding(binding) {
145
260
  if (binding.type === "plain") {
146
261
  return { type: "plain", value: String(binding.value) };
147
262
  }
263
+ if (binding.type === "user_secret_ref") {
264
+ return {
265
+ type: "user_secret_ref",
266
+ key: binding.key,
267
+ version: binding.version ?? "latest",
268
+ required: binding.required ?? true,
269
+ allowMissingOverride: binding.allowMissingOverride ?? false,
270
+ };
271
+ }
148
272
  return {
149
273
  type: "secret_ref",
150
274
  secretId: binding.secretId,
@@ -170,6 +294,18 @@ function secretResolutionErrorCode(error) {
170
294
  }
171
295
  if (error.message === "Secret is not active")
172
296
  return "secret_inactive";
297
+ if (error.message === "User secret value is not configured")
298
+ return "user_secret_missing";
299
+ if (error.message === "Responsible user is required for user secret resolution") {
300
+ return "responsible_user_missing";
301
+ }
302
+ if (error.message === "User secret definition not found")
303
+ return "user_secret_definition_missing";
304
+ if (error.message === "User secret definition is not active")
305
+ return "user_secret_definition_inactive";
306
+ if (error.message === "User-scoped secrets must be resolved through user secret declarations") {
307
+ return "secret_scope_invalid";
308
+ }
173
309
  if (error.message === "Secret version not found")
174
310
  return "version_missing";
175
311
  if (error.message === "Secret version is not active")
@@ -183,6 +319,22 @@ function secretResolutionErrorCode(error) {
183
319
  }
184
320
  return "provider_error";
185
321
  }
322
+ function missingUserSecretDefinitionRuntimeBinding(entry, context, definition, errorCode) {
323
+ return {
324
+ consumerType: context.consumerType,
325
+ consumerId: context.consumerId,
326
+ configPath: entry.configPath,
327
+ envKey: entry.key,
328
+ bindingType: "user_secret_ref",
329
+ secretId: null,
330
+ secretName: null,
331
+ userSecretDefinitionId: definition?.id ?? null,
332
+ userSecretDefinitionKey: definition?.key ?? entry.binding.key,
333
+ userSecretDefinitionName: definition?.name ?? null,
334
+ responsibleUserId: context.responsibleUserId ?? null,
335
+ errorCode,
336
+ };
337
+ }
186
338
  function assertSelectableProviderConfig(config, companyId, provider) {
187
339
  if (config.companyId !== companyId)
188
340
  throw unprocessable("Provider vault must belong to same company");
@@ -208,9 +360,54 @@ export function secretService(db) {
208
360
  return db
209
361
  .select()
210
362
  .from(companySecrets)
211
- .where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.name, name), ne(companySecrets.status, "deleted")))
363
+ .where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "company"), eq(companySecrets.name, name), ne(companySecrets.status, "deleted")))
364
+ .then((rows) => rows[0] ?? null);
365
+ }
366
+ async function getUserSecretDefinitionById(companyId, definitionId, source = db) {
367
+ return source
368
+ .select()
369
+ .from(userSecretDefinitions)
370
+ .where(and(eq(userSecretDefinitions.companyId, companyId), eq(userSecretDefinitions.id, definitionId)))
371
+ .then((rows) => rows[0] ?? null);
372
+ }
373
+ async function getUserSecretDefinitionByKey(companyId, key, source = db) {
374
+ return source
375
+ .select()
376
+ .from(userSecretDefinitions)
377
+ .where(and(eq(userSecretDefinitions.companyId, companyId), eq(userSecretDefinitions.key, key), ne(userSecretDefinitions.status, "deleted")))
378
+ .then((rows) => rows[0] ?? null);
379
+ }
380
+ async function resolveUserSecretDefinition(companyId, input, source = db) {
381
+ const definition = input.definitionId
382
+ ? await getUserSecretDefinitionById(companyId, input.definitionId, source)
383
+ : input.definitionKey
384
+ ? await getUserSecretDefinitionByKey(companyId, input.definitionKey, source)
385
+ : null;
386
+ if (!definition || definition.deletedAt || definition.status === "deleted") {
387
+ throw notFound("User secret definition not found");
388
+ }
389
+ if (definition.companyId !== companyId) {
390
+ throw unprocessable("User secret definition must belong to same company");
391
+ }
392
+ return definition;
393
+ }
394
+ async function getUserSecretValue(input) {
395
+ return db
396
+ .select()
397
+ .from(companySecrets)
398
+ .where(and(eq(companySecrets.companyId, input.companyId), eq(companySecrets.scope, "user"), eq(companySecrets.ownerUserId, input.ownerUserId), eq(companySecrets.userSecretDefinitionId, input.definitionId), ne(companySecrets.status, "deleted")))
212
399
  .then((rows) => rows[0] ?? null);
213
400
  }
401
+ async function getUserSecretValueById(companyId, ownerUserId, secretId) {
402
+ const secret = await getById(secretId);
403
+ if (!secret || secret.status === "deleted" || secret.scope !== "user") {
404
+ throw notFound("User secret value not found");
405
+ }
406
+ if (secret.companyId !== companyId || secret.ownerUserId !== ownerUserId) {
407
+ throw notFound("User secret value not found");
408
+ }
409
+ return secret;
410
+ }
214
411
  async function getSecretVersion(secretId, version) {
215
412
  return db
216
413
  .select()
@@ -253,8 +450,14 @@ export function secretService(db) {
253
450
  await db.insert(secretAccessEvents).values({
254
451
  companyId: input.companyId,
255
452
  secretId: input.secretId,
453
+ userSecretDefinitionId: input.userSecretDefinitionId ?? null,
454
+ secretScope: input.secretScope ?? "company",
256
455
  version: input.version,
257
456
  provider: input.provider,
457
+ responsibleUserId: input.context.responsibleUserId ?? null,
458
+ credentialOwnerUserId: input.credentialOwnerUserId ?? null,
459
+ credentialSubjectType: input.credentialSubjectType ?? null,
460
+ credentialSubjectId: input.credentialSubjectId ?? null,
258
461
  actorType: input.context.actorType ?? "system",
259
462
  actorId: input.context.actorId ?? null,
260
463
  consumerType: input.context.consumerType,
@@ -275,6 +478,8 @@ export function secretService(db) {
275
478
  throw notFound("Secret not found");
276
479
  if (secret.companyId !== companyId)
277
480
  throw unprocessable("Secret must belong to same company");
481
+ if (secret.scope !== "company")
482
+ throw unprocessable("Secret references require company-scoped secrets");
278
483
  return secret;
279
484
  }
280
485
  async function getProviderConfigById(id) {
@@ -377,6 +582,11 @@ export function secretService(db) {
377
582
  throw notFound("Secret not found");
378
583
  if (secret.companyId !== companyId)
379
584
  throw unprocessable("Secret must belong to same company");
585
+ if (secret.scope !== "company" && !options?.allowUserSecretScope) {
586
+ throw unprocessable("User-scoped secrets must be resolved through user secret declarations", {
587
+ code: "secret_scope_invalid",
588
+ });
589
+ }
380
590
  const resolvedVersion = version === "latest" ? secret.latestVersion : version;
381
591
  const providerId = secret.provider;
382
592
  const configPath = accessContext?.configPath ?? null;
@@ -421,9 +631,14 @@ export function secretService(db) {
421
631
  recordAccessEvent({
422
632
  companyId,
423
633
  secretId: secret.id,
634
+ userSecretDefinitionId: secret.userSecretDefinitionId ?? null,
635
+ secretScope: secret.scope,
424
636
  version: resolvedVersion,
425
637
  provider: providerId,
426
638
  context: accessContext,
639
+ credentialOwnerUserId: secret.ownerUserId ?? null,
640
+ credentialSubjectType: secret.scope === "user" ? "user" : null,
641
+ credentialSubjectId: secret.ownerUserId ?? null,
427
642
  outcome: "success",
428
643
  }).catch(() => undefined),
429
644
  ]);
@@ -447,9 +662,14 @@ export function secretService(db) {
447
662
  await recordAccessEvent({
448
663
  companyId,
449
664
  secretId: secret.id,
665
+ userSecretDefinitionId: secret.userSecretDefinitionId ?? null,
666
+ secretScope: secret.scope,
450
667
  version: resolvedVersion,
451
668
  provider: providerId,
452
669
  context: accessContext,
670
+ credentialOwnerUserId: secret.ownerUserId ?? null,
671
+ credentialSubjectType: secret.scope === "user" ? "user" : null,
672
+ credentialSubjectId: secret.ownerUserId ?? null,
453
673
  outcome: "failure",
454
674
  errorCode,
455
675
  }).catch(() => undefined);
@@ -494,7 +714,7 @@ export function secretService(db) {
494
714
  resource: { type: "company", companyId },
495
715
  });
496
716
  if (!decision.allowed) {
497
- throw forbidden(decision.explanation);
717
+ throw forbidden(decision.explanation, authorizationDeniedDetails(decision));
498
718
  }
499
719
  return (await resolveSecretValueInternal(companyId, secretId, version, {
500
720
  accessContext: context,
@@ -524,6 +744,10 @@ export function secretService(db) {
524
744
  normalized[key] = binding;
525
745
  continue;
526
746
  }
747
+ if (binding.type === "user_secret_ref") {
748
+ normalized[key] = binding;
749
+ continue;
750
+ }
527
751
  await assertSecretInCompany(companyId, binding.secretId);
528
752
  normalized[key] = {
529
753
  type: "secret_ref",
@@ -594,6 +818,9 @@ export function secretService(db) {
594
818
  version: binding.version,
595
819
  };
596
820
  }
821
+ if (binding.type === "user_secret_ref") {
822
+ throw unprocessable(`${input.key} must be a string, plain binding, or company secret reference`);
823
+ }
597
824
  const value = binding.value.trim();
598
825
  if (!value)
599
826
  return undefined;
@@ -625,7 +852,7 @@ export function secretService(db) {
625
852
  const duplicateKey = await db
626
853
  .select()
627
854
  .from(companySecrets)
628
- .where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.key, key), ne(companySecrets.status, "deleted")))
855
+ .where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "company"), eq(companySecrets.key, key), ne(companySecrets.status, "deleted")))
629
856
  .then((rows) => rows[0] ?? null);
630
857
  if (duplicateKey)
631
858
  throw conflict(`Secret key already exists: ${key}`);
@@ -946,6 +1173,238 @@ export function secretService(db) {
946
1173
  assertSelectableProviderConfig(providerConfig, companyId, provider);
947
1174
  return { providerConfig, provider, runtimeConfig: toProviderVaultRuntimeConfig(providerConfig) };
948
1175
  }
1176
+ async function createUserSecretValueInternal(companyId, ownerUserId, input, actor) {
1177
+ const definition = await resolveUserSecretDefinition(companyId, input);
1178
+ if (definition.status !== "active") {
1179
+ throw unprocessable("User secret definition is not active");
1180
+ }
1181
+ const existing = await getUserSecretValue({
1182
+ companyId,
1183
+ ownerUserId,
1184
+ definitionId: definition.id,
1185
+ });
1186
+ if (existing)
1187
+ throw conflict("User secret value already exists");
1188
+ const providerId = definition.provider;
1189
+ const managedMode = definition.managedMode;
1190
+ if (managedMode === "external_reference" && !input.externalRef?.trim()) {
1191
+ throw unprocessable("External reference user secrets require externalRef");
1192
+ }
1193
+ if (managedMode === "paperclip_managed" && input.externalRef?.trim()) {
1194
+ throw unprocessable("Managed user secrets cannot override externalRef");
1195
+ }
1196
+ if (managedMode === "paperclip_managed" && !input.value?.trim()) {
1197
+ throw unprocessable("Managed user secrets require value");
1198
+ }
1199
+ const providerConfigId = input.providerConfigId === undefined ? definition.providerConfigId : input.providerConfigId;
1200
+ const provider = getSecretProvider(providerId);
1201
+ const providerConfig = await getSelectableRuntimeProviderConfig({
1202
+ companyId,
1203
+ provider: providerId,
1204
+ providerConfigId,
1205
+ });
1206
+ const idSuffix = randomUUID();
1207
+ const key = normalizeSecretKey(`user.${definition.key}.${idSuffix}`);
1208
+ const name = `${definition.name} (${ownerUserId})`;
1209
+ const providerWriteContext = {
1210
+ companyId,
1211
+ secretKey: key,
1212
+ secretName: definition.name,
1213
+ version: 1,
1214
+ };
1215
+ let reservedSecret;
1216
+ try {
1217
+ reservedSecret = await db
1218
+ .insert(companySecrets)
1219
+ .values({
1220
+ companyId,
1221
+ scope: "user",
1222
+ ownerUserId,
1223
+ userSecretDefinitionId: definition.id,
1224
+ key,
1225
+ name,
1226
+ provider: providerId,
1227
+ providerConfigId: providerConfigId ?? null,
1228
+ status: "archived",
1229
+ managedMode,
1230
+ externalRef: null,
1231
+ providerMetadata: definition.providerMetadata ?? null,
1232
+ latestVersion: 0,
1233
+ description: definition.description ?? null,
1234
+ createdByAgentId: actor?.agentId ?? null,
1235
+ createdByUserId: actor?.userId ?? null,
1236
+ })
1237
+ .returning()
1238
+ .then((rows) => rows[0]);
1239
+ }
1240
+ catch (error) {
1241
+ if (isUniqueConstraintViolation(error, USER_SECRET_VALUE_UNIQUE_CONSTRAINT)) {
1242
+ throw conflict("User secret value already exists");
1243
+ }
1244
+ throw error;
1245
+ }
1246
+ let prepared;
1247
+ try {
1248
+ prepared =
1249
+ managedMode === "external_reference"
1250
+ ? await provider.linkExternalSecret({
1251
+ externalRef: input.externalRef ?? "",
1252
+ providerVersionRef: input.providerVersionRef ?? null,
1253
+ providerConfig,
1254
+ context: providerWriteContext,
1255
+ })
1256
+ : await provider.createSecret({
1257
+ value: input.value ?? "",
1258
+ externalRef: null,
1259
+ providerConfig,
1260
+ context: providerWriteContext,
1261
+ });
1262
+ }
1263
+ catch (error) {
1264
+ throw await throwProviderWriteOrReservedRowRollbackError({
1265
+ error,
1266
+ rollbackReservedRow: () => db.delete(companySecrets).where(eq(companySecrets.id, reservedSecret.id)),
1267
+ companyId,
1268
+ provider: provider.id,
1269
+ providerConfigId,
1270
+ providerConfig,
1271
+ operation: "secret.create",
1272
+ });
1273
+ }
1274
+ try {
1275
+ return await db.transaction(async (tx) => {
1276
+ await tx.insert(companySecretVersions).values({
1277
+ secretId: reservedSecret.id,
1278
+ version: 1,
1279
+ material: prepared.material,
1280
+ valueSha256: prepared.valueSha256,
1281
+ fingerprintSha256: prepared.fingerprintSha256 ?? prepared.valueSha256,
1282
+ providerVersionRef: prepared.providerVersionRef ?? null,
1283
+ status: "current",
1284
+ createdByAgentId: actor?.agentId ?? null,
1285
+ createdByUserId: actor?.userId ?? null,
1286
+ });
1287
+ const secret = await tx
1288
+ .update(companySecrets)
1289
+ .set({
1290
+ status: "active",
1291
+ externalRef: prepared.externalRef,
1292
+ latestVersion: 1,
1293
+ lastRotatedAt: new Date(),
1294
+ updatedAt: new Date(),
1295
+ })
1296
+ .where(eq(companySecrets.id, reservedSecret.id))
1297
+ .returning()
1298
+ .then((rows) => rows[0]);
1299
+ if (!secret)
1300
+ throw notFound("User secret value not found");
1301
+ return secret;
1302
+ });
1303
+ }
1304
+ catch (error) {
1305
+ if (managedMode === "paperclip_managed") {
1306
+ const cleaned = await cleanupPreparedProviderWrite({
1307
+ provider,
1308
+ prepared,
1309
+ providerConfig,
1310
+ context: providerWriteContext,
1311
+ mode: "delete",
1312
+ operation: "user_secret_value.create_rollback",
1313
+ });
1314
+ if (!cleaned) {
1315
+ throwProviderCleanupFailedAfterCreateRollback({
1316
+ companyId,
1317
+ provider: provider.id,
1318
+ providerConfigId,
1319
+ providerConfig,
1320
+ operation: "user_secret_value.create_rollback",
1321
+ });
1322
+ }
1323
+ }
1324
+ await deleteLocalSecretCreateReservationOrThrow({
1325
+ db,
1326
+ secretId: reservedSecret.id,
1327
+ companyId,
1328
+ provider: provider.id,
1329
+ providerConfigId,
1330
+ providerConfig,
1331
+ operation: "user_secret_value.create_rollback",
1332
+ });
1333
+ throw error;
1334
+ }
1335
+ }
1336
+ async function removeSecretInternal(secretId) {
1337
+ const secret = await getById(secretId);
1338
+ if (!secret)
1339
+ return null;
1340
+ const versionRow = await getSecretVersion(secret.id, secret.latestVersion);
1341
+ const providerId = secret.provider;
1342
+ const provider = getSecretProvider(providerId);
1343
+ if (secret.status !== "deleted") {
1344
+ await db
1345
+ .update(companySecrets)
1346
+ .set({
1347
+ key: `${secret.key}__deleted__${secret.id}`,
1348
+ name: `${secret.name}__deleted__${secret.id}`,
1349
+ status: "deleted",
1350
+ deletedAt: secret.deletedAt ?? new Date(),
1351
+ updatedAt: new Date(),
1352
+ })
1353
+ .where(eq(companySecrets.id, secretId));
1354
+ }
1355
+ const providerConfig = secret.providerConfigId
1356
+ ? await getProviderConfigById(secret.providerConfigId)
1357
+ : null;
1358
+ const providerRuntimeConfig = providerConfig && providerConfig.status !== "disabled" && providerConfig.status !== "coming_soon"
1359
+ ? toProviderVaultRuntimeConfig(providerConfig)
1360
+ : null;
1361
+ if (!secret.providerConfigId || providerRuntimeConfig) {
1362
+ try {
1363
+ await provider.deleteOrArchive({
1364
+ material: versionRow?.material,
1365
+ externalRef: secret.externalRef,
1366
+ providerConfig: providerRuntimeConfig,
1367
+ context: {
1368
+ companyId: secret.companyId,
1369
+ secretKey: secret.key,
1370
+ secretName: secret.name,
1371
+ version: secret.latestVersion,
1372
+ },
1373
+ mode: "delete",
1374
+ });
1375
+ }
1376
+ catch (error) {
1377
+ if (!isSecretProviderClientError(error) || error.code !== "not_found") {
1378
+ throw error;
1379
+ }
1380
+ }
1381
+ }
1382
+ await db.delete(companySecrets).where(eq(companySecrets.id, secretId));
1383
+ return secret;
1384
+ }
1385
+ async function removeUserSecretDefinitionInternal(companyId, definitionId, actor) {
1386
+ const existing = await resolveUserSecretDefinition(companyId, { definitionId });
1387
+ const values = await db
1388
+ .select({ id: companySecrets.id })
1389
+ .from(companySecrets)
1390
+ .where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "user"), eq(companySecrets.userSecretDefinitionId, definitionId)));
1391
+ for (const value of values) {
1392
+ await removeSecretInternal(value.id);
1393
+ }
1394
+ return db
1395
+ .update(userSecretDefinitions)
1396
+ .set({
1397
+ key: `${existing.key}__deleted__${existing.id}`,
1398
+ status: "deleted",
1399
+ deletedAt: existing.deletedAt ?? new Date(),
1400
+ updatedByAgentId: actor?.agentId ?? null,
1401
+ updatedByUserId: actor?.userId ?? null,
1402
+ updatedAt: new Date(),
1403
+ })
1404
+ .where(and(eq(userSecretDefinitions.companyId, companyId), eq(userSecretDefinitions.id, definitionId)))
1405
+ .returning()
1406
+ .then((rows) => rows[0] ?? null);
1407
+ }
949
1408
  return {
950
1409
  listProviders: () => listSecretProviders(),
951
1410
  checkProviders: () => checkSecretProviders(),
@@ -1159,7 +1618,7 @@ export function secretService(db) {
1159
1618
  db
1160
1619
  .select()
1161
1620
  .from(companySecrets)
1162
- .where(and(eq(companySecrets.companyId, companyId), ne(companySecrets.status, "deleted")))
1621
+ .where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "company"), ne(companySecrets.status, "deleted")))
1163
1622
  .orderBy(desc(companySecrets.createdAt)),
1164
1623
  db
1165
1624
  .select({
@@ -1201,6 +1660,270 @@ export function secretService(db) {
1201
1660
  .from(secretAccessEvents)
1202
1661
  .where(and(eq(secretAccessEvents.companyId, companyId), eq(secretAccessEvents.secretId, secretId)))
1203
1662
  .orderBy(desc(secretAccessEvents.createdAt)),
1663
+ listUserSecretDefinitions: (companyId) => db
1664
+ .select()
1665
+ .from(userSecretDefinitions)
1666
+ .where(and(eq(userSecretDefinitions.companyId, companyId), ne(userSecretDefinitions.status, "deleted")))
1667
+ .orderBy(desc(userSecretDefinitions.createdAt)),
1668
+ getUserSecretDefinitionById: (companyId, definitionId) => getUserSecretDefinitionById(companyId, definitionId),
1669
+ createUserSecretDefinition: async (companyId, input, actor) => {
1670
+ const key = input.key.trim();
1671
+ const duplicate = await getUserSecretDefinitionByKey(companyId, key);
1672
+ if (duplicate)
1673
+ throw conflict(`User secret definition already exists: ${key}`);
1674
+ await assertProviderConfigForSecret(companyId, input.provider, input.providerConfigId);
1675
+ try {
1676
+ return await db
1677
+ .insert(userSecretDefinitions)
1678
+ .values({
1679
+ companyId,
1680
+ key,
1681
+ name: input.name.trim(),
1682
+ description: input.description ?? null,
1683
+ status: input.status ?? "active",
1684
+ provider: input.provider,
1685
+ providerConfigId: input.providerConfigId ?? null,
1686
+ managedMode: input.managedMode ?? "paperclip_managed",
1687
+ providerMetadata: input.providerMetadata ?? null,
1688
+ usageGuidance: input.usageGuidance ?? null,
1689
+ createdByAgentId: actor?.agentId ?? null,
1690
+ createdByUserId: actor?.userId ?? null,
1691
+ updatedByAgentId: actor?.agentId ?? null,
1692
+ updatedByUserId: actor?.userId ?? null,
1693
+ })
1694
+ .returning()
1695
+ .then((rows) => rows[0]);
1696
+ }
1697
+ catch (error) {
1698
+ if (isUniqueConstraintViolation(error, USER_SECRET_DEFINITION_KEY_UNIQUE_CONSTRAINT)) {
1699
+ throw conflict(`User secret definition already exists: ${key}`);
1700
+ }
1701
+ throw error;
1702
+ }
1703
+ },
1704
+ updateUserSecretDefinition: async (companyId, definitionId, patch, actor) => {
1705
+ const existing = await resolveUserSecretDefinition(companyId, { definitionId });
1706
+ if (patch.status === "deleted") {
1707
+ return removeUserSecretDefinitionInternal(companyId, existing.id, actor);
1708
+ }
1709
+ const nextKey = patch.key?.trim() ?? existing.key;
1710
+ if (nextKey !== existing.key) {
1711
+ const duplicate = await getUserSecretDefinitionByKey(companyId, nextKey);
1712
+ if (duplicate && duplicate.id !== existing.id) {
1713
+ throw conflict(`User secret definition already exists: ${nextKey}`);
1714
+ }
1715
+ }
1716
+ if (patch.providerConfigId !== undefined) {
1717
+ await assertProviderConfigForSecret(companyId, existing.provider, patch.providerConfigId);
1718
+ }
1719
+ return db
1720
+ .update(userSecretDefinitions)
1721
+ .set({
1722
+ key: nextKey,
1723
+ name: patch.name?.trim() ?? existing.name,
1724
+ description: patch.description === undefined ? existing.description : patch.description,
1725
+ status: patch.status ?? existing.status,
1726
+ providerConfigId: patch.providerConfigId === undefined ? existing.providerConfigId : patch.providerConfigId,
1727
+ providerMetadata: patch.providerMetadata === undefined ? existing.providerMetadata : patch.providerMetadata,
1728
+ usageGuidance: patch.usageGuidance === undefined ? existing.usageGuidance : patch.usageGuidance,
1729
+ updatedByAgentId: actor?.agentId ?? null,
1730
+ updatedByUserId: actor?.userId ?? null,
1731
+ deletedAt: existing.deletedAt,
1732
+ updatedAt: new Date(),
1733
+ })
1734
+ .where(and(eq(userSecretDefinitions.companyId, companyId), eq(userSecretDefinitions.id, definitionId)))
1735
+ .returning()
1736
+ .then((rows) => rows[0] ?? null);
1737
+ },
1738
+ removeUserSecretDefinition: async (companyId, definitionId, actor) => removeUserSecretDefinitionInternal(companyId, definitionId, actor),
1739
+ getUserSecretDefinitionCoverage: async (companyId, definitionId) => {
1740
+ await resolveUserSecretDefinition(companyId, { definitionId });
1741
+ const [members, values] = await Promise.all([
1742
+ db
1743
+ .select({ principalId: companyMemberships.principalId })
1744
+ .from(companyMemberships)
1745
+ .where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.principalType, "user"), eq(companyMemberships.status, "active"))),
1746
+ db
1747
+ .select({ status: companySecrets.status, ownerUserId: companySecrets.ownerUserId })
1748
+ .from(companySecrets)
1749
+ .where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "user"), eq(companySecrets.userSecretDefinitionId, definitionId), ne(companySecrets.status, "deleted"))),
1750
+ ]);
1751
+ const memberIds = new Set(members.map((member) => member.principalId));
1752
+ const configuredCount = values.filter((value) => value.status === "active" && value.ownerUserId && memberIds.has(value.ownerUserId)).length;
1753
+ const inactiveCount = values.filter((value) => value.status !== "active" && value.ownerUserId && memberIds.has(value.ownerUserId)).length;
1754
+ return {
1755
+ definitionId,
1756
+ configuredCount,
1757
+ inactiveCount,
1758
+ missingCount: Math.max(0, memberIds.size - configuredCount - inactiveCount),
1759
+ };
1760
+ },
1761
+ listCurrentUserSecretValues: async (companyId, ownerUserId) => {
1762
+ const definitions = await db
1763
+ .select()
1764
+ .from(userSecretDefinitions)
1765
+ .where(and(eq(userSecretDefinitions.companyId, companyId), ne(userSecretDefinitions.status, "deleted")))
1766
+ .orderBy(desc(userSecretDefinitions.createdAt));
1767
+ const values = await db
1768
+ .select()
1769
+ .from(companySecrets)
1770
+ .where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "user"), eq(companySecrets.ownerUserId, ownerUserId), ne(companySecrets.status, "deleted")));
1771
+ const valuesByDefinitionId = new Map(values.map((value) => [value.userSecretDefinitionId, value]));
1772
+ return definitions.map((definition) => ({
1773
+ definition,
1774
+ secret: valuesByDefinitionId.get(definition.id) ?? null,
1775
+ }));
1776
+ },
1777
+ createCurrentUserSecretValue: createUserSecretValueInternal,
1778
+ rotateCurrentUserSecretValue: async (companyId, ownerUserId, secretId, input, actor) => {
1779
+ const secret = await getUserSecretValueById(companyId, ownerUserId, secretId);
1780
+ return await (async () => {
1781
+ await resolveUserSecretDefinition(companyId, { definitionId: secret.userSecretDefinitionId });
1782
+ return (await secretService(db).rotate(secret.id, input, actor));
1783
+ })();
1784
+ },
1785
+ updateCurrentUserSecretValue: async (companyId, ownerUserId, secretId, patch, actor) => {
1786
+ const secret = await getUserSecretValueById(companyId, ownerUserId, secretId);
1787
+ if (patch.value != null ||
1788
+ patch.externalRef != null ||
1789
+ patch.providerVersionRef != null ||
1790
+ patch.providerConfigId != null) {
1791
+ return await secretService(db).rotateCurrentUserSecretValue(companyId, ownerUserId, secret.id, patch, actor);
1792
+ }
1793
+ if (patch.status === "deleted") {
1794
+ return await secretService(db).removeCurrentUserSecretValue(companyId, ownerUserId, secret.id);
1795
+ }
1796
+ return db
1797
+ .update(companySecrets)
1798
+ .set({
1799
+ status: patch.status ?? secret.status,
1800
+ updatedAt: new Date(),
1801
+ })
1802
+ .where(eq(companySecrets.id, secret.id))
1803
+ .returning()
1804
+ .then((rows) => rows[0] ?? null);
1805
+ },
1806
+ removeCurrentUserSecretValue: async (companyId, ownerUserId, secretId) => {
1807
+ const secret = await getUserSecretValueById(companyId, ownerUserId, secretId);
1808
+ return await secretService(db).remove(secret.id);
1809
+ },
1810
+ syncUserSecretDeclarationsForTarget: async (companyId, target, refs, options) => {
1811
+ const targetDb = options?.db ?? db;
1812
+ const normalizedRefs = [];
1813
+ for (const ref of refs) {
1814
+ const definition = await resolveUserSecretDefinition(companyId, { definitionKey: ref.definitionKey }, targetDb);
1815
+ normalizedRefs.push({
1816
+ definitionId: definition.id,
1817
+ configPath: ref.configPath,
1818
+ envKey: ref.envKey,
1819
+ versionSelector: ref.versionSelector ?? "latest",
1820
+ required: ref.required ?? true,
1821
+ allowMissingOverride: ref.allowMissingOverride ?? false,
1822
+ label: ref.label ?? null,
1823
+ });
1824
+ }
1825
+ const pathPrefix = target.pathPrefix ?? "env";
1826
+ const writeDeclarations = async (executor) => {
1827
+ if (options?.replaceAll) {
1828
+ await executor
1829
+ .delete(userSecretDeclarations)
1830
+ .where(and(eq(userSecretDeclarations.companyId, companyId), eq(userSecretDeclarations.targetType, target.targetType), eq(userSecretDeclarations.targetId, target.targetId)));
1831
+ }
1832
+ else {
1833
+ await executor
1834
+ .delete(userSecretDeclarations)
1835
+ .where(and(eq(userSecretDeclarations.companyId, companyId), eq(userSecretDeclarations.targetType, target.targetType), eq(userSecretDeclarations.targetId, target.targetId), like(userSecretDeclarations.configPath, `${pathPrefix}.%`)));
1836
+ }
1837
+ if (normalizedRefs.length === 0)
1838
+ return;
1839
+ await executor.insert(userSecretDeclarations).values(normalizedRefs.map((ref) => ({
1840
+ companyId,
1841
+ userSecretDefinitionId: ref.definitionId,
1842
+ targetType: target.targetType,
1843
+ targetId: target.targetId,
1844
+ configPath: ref.configPath,
1845
+ envKey: ref.envKey,
1846
+ versionSelector: String(ref.versionSelector),
1847
+ required: ref.required,
1848
+ allowMissingOverride: ref.allowMissingOverride,
1849
+ label: ref.label,
1850
+ })));
1851
+ };
1852
+ if (options?.db) {
1853
+ await writeDeclarations(targetDb);
1854
+ }
1855
+ else {
1856
+ await db.transaction(async (tx) => writeDeclarations(tx));
1857
+ }
1858
+ return normalizedRefs;
1859
+ },
1860
+ resolveUserSecretValue: async (companyId, input, context) => {
1861
+ const responsibleUserId = input.responsibleUserId ?? context?.responsibleUserId ?? null;
1862
+ const optionalBinding = input.allowMissingOverride || input.required === false;
1863
+ let definition;
1864
+ try {
1865
+ definition = await resolveUserSecretDefinition(companyId, input);
1866
+ }
1867
+ catch (error) {
1868
+ if (optionalBinding && error instanceof HttpError && error.status === 404)
1869
+ return null;
1870
+ throw error;
1871
+ }
1872
+ if (definition.status !== "active") {
1873
+ if (optionalBinding)
1874
+ return null;
1875
+ throw unprocessable("User secret definition is not active");
1876
+ }
1877
+ if (!responsibleUserId?.trim()) {
1878
+ if (optionalBinding)
1879
+ return null;
1880
+ throw unprocessable("Responsible user is required for user secret resolution", {
1881
+ code: "responsible_user_missing",
1882
+ });
1883
+ }
1884
+ let declaration = null;
1885
+ if (context?.configPath) {
1886
+ declaration = await db
1887
+ .select()
1888
+ .from(userSecretDeclarations)
1889
+ .where(and(eq(userSecretDeclarations.companyId, companyId), eq(userSecretDeclarations.userSecretDefinitionId, definition.id), eq(userSecretDeclarations.targetType, context.consumerType), eq(userSecretDeclarations.targetId, context.consumerId), eq(userSecretDeclarations.configPath, context.configPath)))
1890
+ .then((rows) => rows[0] ?? null);
1891
+ if (!declaration) {
1892
+ if (optionalBinding)
1893
+ return null;
1894
+ throw unprocessable(`User secret is not declared for ${context.consumerType}:${context.consumerId} at ${context.configPath}`, { code: "binding_missing" });
1895
+ }
1896
+ }
1897
+ if (Array.isArray(context?.allowedBindingIds) &&
1898
+ (!declaration || !context.allowedBindingIds.includes(declaration.id))) {
1899
+ throw unprocessable("User secret declaration is outside the active low-trust boundary", { code: "binding_not_allowed" });
1900
+ }
1901
+ const secret = await getUserSecretValue({
1902
+ companyId,
1903
+ ownerUserId: responsibleUserId,
1904
+ definitionId: definition.id,
1905
+ });
1906
+ if (!secret) {
1907
+ if (optionalBinding)
1908
+ return null;
1909
+ throw unprocessable("User secret value is not configured", {
1910
+ code: "user_secret_missing",
1911
+ definitionId: definition.id,
1912
+ responsibleUserId,
1913
+ });
1914
+ }
1915
+ const resolution = await resolveSecretValueInternal(companyId, secret.id, input.version ?? "latest", {
1916
+ accessContext: context ? { ...context, responsibleUserId } : undefined,
1917
+ allowUserSecretScope: true,
1918
+ });
1919
+ return {
1920
+ ...resolution,
1921
+ manifestEntry: {
1922
+ ...resolution.manifestEntry,
1923
+ bindingId: declaration?.id ?? resolution.manifestEntry.bindingId ?? null,
1924
+ },
1925
+ };
1926
+ },
1204
1927
  previewRemoteImport: async (companyId, input) => {
1205
1928
  const { providerConfig, provider: providerId, runtimeConfig } = await getRemoteImportProviderConfig(companyId, input.providerConfigId);
1206
1929
  const provider = getSecretProvider(providerId);
@@ -1478,7 +2201,7 @@ export function secretService(db) {
1478
2201
  const duplicateKey = await db
1479
2202
  .select()
1480
2203
  .from(companySecrets)
1481
- .where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.key, key), ne(companySecrets.status, "deleted")))
2204
+ .where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.scope, "company"), eq(companySecrets.key, key), ne(companySecrets.status, "deleted")))
1482
2205
  .then((rows) => rows[0] ?? null);
1483
2206
  if (duplicateKey)
1484
2207
  throw conflict(`Secret key already exists: ${key}`);
@@ -1541,8 +2264,15 @@ export function secretService(db) {
1541
2264
  });
1542
2265
  }
1543
2266
  catch (error) {
1544
- await db.delete(companySecrets).where(eq(companySecrets.id, reservedSecret.id)).catch(() => undefined);
1545
- throw error;
2267
+ throw await throwProviderWriteOrReservedRowRollbackError({
2268
+ error,
2269
+ rollbackReservedRow: () => db.delete(companySecrets).where(eq(companySecrets.id, reservedSecret.id)),
2270
+ companyId,
2271
+ provider: provider.id,
2272
+ providerConfigId: input.providerConfigId ?? null,
2273
+ providerConfig,
2274
+ operation: "secret.create",
2275
+ });
1546
2276
  }
1547
2277
  try {
1548
2278
  await db
@@ -1575,15 +2305,25 @@ export function secretService(db) {
1575
2305
  mode: "delete",
1576
2306
  operation: "create.prepare_rollback",
1577
2307
  });
1578
- if (cleaned) {
1579
- await db.delete(companySecretVersions).where(eq(companySecretVersions.secretId, reservedSecret.id)).catch(() => undefined);
1580
- await db.delete(companySecrets).where(eq(companySecrets.id, reservedSecret.id)).catch(() => undefined);
2308
+ if (!cleaned) {
2309
+ throwProviderCleanupFailedAfterCreateRollback({
2310
+ companyId,
2311
+ provider: provider.id,
2312
+ providerConfigId: input.providerConfigId ?? null,
2313
+ providerConfig,
2314
+ operation: "create.prepare_rollback",
2315
+ });
1581
2316
  }
1582
2317
  }
1583
- else {
1584
- await db.delete(companySecretVersions).where(eq(companySecretVersions.secretId, reservedSecret.id)).catch(() => undefined);
1585
- await db.delete(companySecrets).where(eq(companySecrets.id, reservedSecret.id)).catch(() => undefined);
1586
- }
2318
+ await deleteLocalSecretCreateReservationOrThrow({
2319
+ db,
2320
+ secretId: reservedSecret.id,
2321
+ companyId,
2322
+ provider: provider.id,
2323
+ providerConfigId: input.providerConfigId ?? null,
2324
+ providerConfig,
2325
+ operation: "create.prepare_rollback",
2326
+ });
1587
2327
  throw error;
1588
2328
  }
1589
2329
  try {
@@ -1619,15 +2359,25 @@ export function secretService(db) {
1619
2359
  mode: "delete",
1620
2360
  operation: "create.rollback",
1621
2361
  });
1622
- if (cleaned) {
1623
- await db.delete(companySecretVersions).where(eq(companySecretVersions.secretId, reservedSecret.id)).catch(() => undefined);
1624
- await db.delete(companySecrets).where(eq(companySecrets.id, reservedSecret.id)).catch(() => undefined);
2362
+ if (!cleaned) {
2363
+ throwProviderCleanupFailedAfterCreateRollback({
2364
+ companyId,
2365
+ provider: provider.id,
2366
+ providerConfigId: input.providerConfigId ?? null,
2367
+ providerConfig,
2368
+ operation: "create.rollback",
2369
+ });
1625
2370
  }
1626
2371
  }
1627
- else {
1628
- await db.delete(companySecretVersions).where(eq(companySecretVersions.secretId, reservedSecret.id)).catch(() => undefined);
1629
- await db.delete(companySecrets).where(eq(companySecrets.id, reservedSecret.id)).catch(() => undefined);
1630
- }
2372
+ await deleteLocalSecretCreateReservationOrThrow({
2373
+ db,
2374
+ secretId: reservedSecret.id,
2375
+ companyId,
2376
+ provider: provider.id,
2377
+ providerConfigId: input.providerConfigId ?? null,
2378
+ providerConfig,
2379
+ operation: "create.rollback",
2380
+ });
1631
2381
  throw error;
1632
2382
  }
1633
2383
  },
@@ -1661,19 +2411,32 @@ export function secretService(db) {
1661
2411
  secretName: secret.name,
1662
2412
  version: nextVersion,
1663
2413
  };
1664
- const prepared = secret.managedMode === "external_reference"
1665
- ? await provider.linkExternalSecret({
1666
- externalRef: input.externalRef ?? secret.externalRef ?? "",
1667
- providerVersionRef: input.providerVersionRef ?? null,
1668
- providerConfig,
1669
- context: providerWriteContext,
1670
- })
1671
- : await provider.createVersion({
1672
- value: input.value ?? "",
1673
- externalRef: secret.externalRef ?? null,
2414
+ let prepared;
2415
+ try {
2416
+ prepared =
2417
+ secret.managedMode === "external_reference"
2418
+ ? await provider.linkExternalSecret({
2419
+ externalRef: input.externalRef ?? secret.externalRef ?? "",
2420
+ providerVersionRef: input.providerVersionRef ?? null,
2421
+ providerConfig,
2422
+ context: providerWriteContext,
2423
+ })
2424
+ : await provider.createVersion({
2425
+ value: input.value ?? "",
2426
+ externalRef: secret.externalRef ?? null,
2427
+ providerConfig,
2428
+ context: providerWriteContext,
2429
+ });
2430
+ }
2431
+ catch (error) {
2432
+ throw remoteProviderWriteHttpError(error, {
2433
+ companyId: secret.companyId,
2434
+ provider: provider.id,
2435
+ providerConfigId,
1674
2436
  providerConfig,
1675
- context: providerWriteContext,
2437
+ operation: "secret.rotate",
1676
2438
  });
2439
+ }
1677
2440
  try {
1678
2441
  await db.insert(companySecretVersions).values({
1679
2442
  secretId: secret.id,
@@ -1766,7 +2529,7 @@ export function secretService(db) {
1766
2529
  const duplicateKey = await db
1767
2530
  .select()
1768
2531
  .from(companySecrets)
1769
- .where(and(eq(companySecrets.companyId, secret.companyId), eq(companySecrets.key, nextKey), ne(companySecrets.status, "deleted")))
2532
+ .where(and(eq(companySecrets.companyId, secret.companyId), eq(companySecrets.scope, "company"), eq(companySecrets.key, nextKey), ne(companySecrets.status, "deleted")))
1770
2533
  .then((rows) => rows[0] ?? null);
1771
2534
  if (duplicateKey && duplicateKey.id !== secret.id) {
1772
2535
  throw conflict(`Secret key already exists: ${nextKey}`);
@@ -1892,6 +2655,7 @@ export function secretService(db) {
1892
2655
  syncEnvBindingsForTarget: async (companyId, target, envValue, options) => {
1893
2656
  const record = asRecord(envValue) ?? {};
1894
2657
  const refs = [];
2658
+ const userRefs = [];
1895
2659
  const pathPrefix = target.pathPrefix ?? "env";
1896
2660
  const bindingDb = options?.db ?? db;
1897
2661
  for (const [key, rawBinding] of Object.entries(record)) {
@@ -1899,6 +2663,18 @@ export function secretService(db) {
1899
2663
  if (!parsed.success)
1900
2664
  continue;
1901
2665
  const binding = canonicalizeBinding(parsed.data);
2666
+ if (binding.type === "user_secret_ref") {
2667
+ await resolveUserSecretDefinition(companyId, { definitionKey: binding.key }, bindingDb);
2668
+ userRefs.push({
2669
+ definitionKey: binding.key,
2670
+ configPath: `${pathPrefix}.${key}`,
2671
+ envKey: key,
2672
+ versionSelector: binding.version,
2673
+ required: binding.required,
2674
+ allowMissingOverride: binding.allowMissingOverride,
2675
+ });
2676
+ continue;
2677
+ }
1902
2678
  if (binding.type !== "secret_ref")
1903
2679
  continue;
1904
2680
  await assertSecretInCompany(companyId, binding.secretId, bindingDb);
@@ -1924,63 +2700,42 @@ export function secretService(db) {
1924
2700
  required: true,
1925
2701
  })));
1926
2702
  };
2703
+ const writeUserDeclarations = async (targetDb) => {
2704
+ await targetDb
2705
+ .delete(userSecretDeclarations)
2706
+ .where(and(eq(userSecretDeclarations.companyId, companyId), eq(userSecretDeclarations.targetType, target.targetType), eq(userSecretDeclarations.targetId, target.targetId), like(userSecretDeclarations.configPath, `${pathPrefix}.%`)));
2707
+ if (userRefs.length === 0)
2708
+ return;
2709
+ const definitions = new Map();
2710
+ for (const ref of userRefs) {
2711
+ const definition = await resolveUserSecretDefinition(companyId, { definitionKey: ref.definitionKey }, targetDb);
2712
+ definitions.set(ref.definitionKey, definition.id);
2713
+ }
2714
+ await targetDb.insert(userSecretDeclarations).values(userRefs.map((ref) => ({
2715
+ companyId,
2716
+ userSecretDefinitionId: definitions.get(ref.definitionKey),
2717
+ targetType: target.targetType,
2718
+ targetId: target.targetId,
2719
+ configPath: ref.configPath,
2720
+ envKey: ref.envKey,
2721
+ versionSelector: String(ref.versionSelector),
2722
+ required: ref.required,
2723
+ allowMissingOverride: ref.allowMissingOverride,
2724
+ })));
2725
+ };
1927
2726
  if (options?.db) {
1928
2727
  await writeBindings(options.db);
2728
+ await writeUserDeclarations(options.db);
1929
2729
  }
1930
2730
  else {
1931
- await db.transaction(async (tx) => writeBindings(tx));
2731
+ await db.transaction(async (tx) => {
2732
+ await writeBindings(tx);
2733
+ await writeUserDeclarations(tx);
2734
+ });
1932
2735
  }
1933
2736
  return refs;
1934
2737
  },
1935
- remove: async (secretId) => {
1936
- const secret = await getById(secretId);
1937
- if (!secret)
1938
- return null;
1939
- const versionRow = await getSecretVersion(secret.id, secret.latestVersion);
1940
- const providerId = secret.provider;
1941
- const provider = getSecretProvider(providerId);
1942
- if (secret.status !== "deleted") {
1943
- await db
1944
- .update(companySecrets)
1945
- .set({
1946
- key: `${secret.key}__deleted__${secret.id}`,
1947
- name: `${secret.name}__deleted__${secret.id}`,
1948
- status: "deleted",
1949
- deletedAt: secret.deletedAt ?? new Date(),
1950
- updatedAt: new Date(),
1951
- })
1952
- .where(eq(companySecrets.id, secretId));
1953
- }
1954
- const providerConfig = secret.providerConfigId
1955
- ? await getProviderConfigById(secret.providerConfigId)
1956
- : null;
1957
- const providerRuntimeConfig = providerConfig && providerConfig.status !== "disabled" && providerConfig.status !== "coming_soon"
1958
- ? toProviderVaultRuntimeConfig(providerConfig)
1959
- : null;
1960
- if (!secret.providerConfigId || providerRuntimeConfig) {
1961
- try {
1962
- await provider.deleteOrArchive({
1963
- material: versionRow?.material,
1964
- externalRef: secret.externalRef,
1965
- providerConfig: providerRuntimeConfig,
1966
- context: {
1967
- companyId: secret.companyId,
1968
- secretKey: secret.key,
1969
- secretName: secret.name,
1970
- version: secret.latestVersion,
1971
- },
1972
- mode: "delete",
1973
- });
1974
- }
1975
- catch (error) {
1976
- if (!isSecretProviderClientError(error) || error.code !== "not_found") {
1977
- throw error;
1978
- }
1979
- }
1980
- }
1981
- await db.delete(companySecrets).where(eq(companySecrets.id, secretId));
1982
- return secret;
1983
- },
2738
+ remove: removeSecretInternal,
1984
2739
  normalizeAdapterConfigForPersistence: async (companyId, adapterConfig, opts) => normalizeAdapterConfigForPersistenceInternal(companyId, adapterConfig, opts),
1985
2740
  normalizeEnvBindingsForPersistence: async (companyId, envValue, opts) => normalizeEnvConfig(companyId, envValue, opts),
1986
2741
  normalizeHireApprovalPayloadForPersistence: async (companyId, payload, opts) => {
@@ -2010,7 +2765,7 @@ export function secretService(db) {
2010
2765
  if (binding.type === "plain") {
2011
2766
  resolved[key] = binding.value;
2012
2767
  }
2013
- else {
2768
+ else if (binding.type === "secret_ref") {
2014
2769
  const secretResolution = await resolveSecretValueInternal(companyId, binding.secretId, binding.version, context
2015
2770
  ? {
2016
2771
  bindingContext: { ...context, configPath: `env.${key}` },
@@ -2021,6 +2776,25 @@ export function secretService(db) {
2021
2776
  manifest.push(secretResolution.manifestEntry);
2022
2777
  secretKeys.add(key);
2023
2778
  }
2779
+ else {
2780
+ const secretResolution = await secretService(db).resolveUserSecretValue(companyId, {
2781
+ definitionKey: binding.key,
2782
+ version: binding.version,
2783
+ required: binding.required,
2784
+ allowMissingOverride: binding.allowMissingOverride,
2785
+ }, context
2786
+ ? {
2787
+ ...context,
2788
+ configPath: `env.${key}`,
2789
+ responsibleUserId: context.responsibleUserId ?? null,
2790
+ }
2791
+ : undefined);
2792
+ if (secretResolution) {
2793
+ resolved[key] = secretResolution.value;
2794
+ manifest.push(secretResolution.manifestEntry);
2795
+ secretKeys.add(key);
2796
+ }
2797
+ }
2024
2798
  }
2025
2799
  return { env: resolved, secretKeys, manifest };
2026
2800
  },
@@ -2043,7 +2817,20 @@ export function secretService(db) {
2043
2817
  return [];
2044
2818
  return [{ key, configPath: `env.${key}`, secretId: binding.secretId }];
2045
2819
  });
2046
- if (secretRefs.length === 0)
2820
+ const userSecretRefs = Object.entries(record).flatMap(([key, rawBinding]) => {
2821
+ if (!ENV_KEY_RE.test(key))
2822
+ return [];
2823
+ const parsed = envBindingSchema.safeParse(rawBinding);
2824
+ if (!parsed.success)
2825
+ return [];
2826
+ const binding = canonicalizeBinding(parsed.data);
2827
+ if (binding.type !== "user_secret_ref")
2828
+ return [];
2829
+ if (!binding.required || binding.allowMissingOverride)
2830
+ return [];
2831
+ return [{ key, configPath: `env.${key}`, binding }];
2832
+ });
2833
+ if (secretRefs.length === 0 && userSecretRefs.length === 0)
2047
2834
  return [];
2048
2835
  const bindingChecks = await Promise.all(secretRefs.map(async (entry) => ({
2049
2836
  entry,
@@ -2058,21 +2845,96 @@ export function secretService(db) {
2058
2845
  const missingEntries = bindingChecks
2059
2846
  .filter((check) => !check.found)
2060
2847
  .map((check) => check.entry);
2061
- if (missingEntries.length === 0)
2062
- return [];
2063
2848
  const secretRows = await Promise.all([...new Set(missingEntries.map((entry) => entry.secretId))].map(async (secretId) => [
2064
2849
  secretId,
2065
2850
  await getById(secretId).catch(() => null),
2066
2851
  ]));
2067
2852
  const secretsById = new Map(secretRows);
2068
- return missingEntries.map((entry) => ({
2853
+ const missingSecretBindings = missingEntries.map((entry) => ({
2069
2854
  consumerType: context.consumerType,
2070
2855
  consumerId: context.consumerId,
2071
2856
  configPath: entry.configPath,
2072
2857
  envKey: entry.key,
2858
+ bindingType: "secret_ref",
2073
2859
  secretId: entry.secretId,
2074
2860
  secretName: secretsById.get(entry.secretId)?.name ?? null,
2075
2861
  }));
2862
+ const missingUserSecretBindings = [];
2863
+ for (const entry of userSecretRefs) {
2864
+ let definition = null;
2865
+ try {
2866
+ definition = await resolveUserSecretDefinition(companyId, { definitionKey: entry.binding.key });
2867
+ }
2868
+ catch {
2869
+ missingUserSecretBindings.push(missingUserSecretDefinitionRuntimeBinding(entry, context, null, "user_secret_definition_missing"));
2870
+ continue;
2871
+ }
2872
+ if (definition.status !== "active") {
2873
+ missingUserSecretBindings.push(missingUserSecretDefinitionRuntimeBinding(entry, context, definition, "user_secret_definition_inactive"));
2874
+ continue;
2875
+ }
2876
+ const declaration = await db
2877
+ .select()
2878
+ .from(userSecretDeclarations)
2879
+ .where(and(eq(userSecretDeclarations.companyId, companyId), eq(userSecretDeclarations.userSecretDefinitionId, definition.id), eq(userSecretDeclarations.targetType, context.consumerType), eq(userSecretDeclarations.targetId, context.consumerId), eq(userSecretDeclarations.configPath, entry.configPath)))
2880
+ .then((rows) => rows[0] ?? null);
2881
+ if (!declaration) {
2882
+ missingUserSecretBindings.push({
2883
+ consumerType: context.consumerType,
2884
+ consumerId: context.consumerId,
2885
+ configPath: entry.configPath,
2886
+ envKey: entry.key,
2887
+ bindingType: "user_secret_ref",
2888
+ secretId: null,
2889
+ secretName: null,
2890
+ userSecretDefinitionId: definition.id,
2891
+ userSecretDefinitionKey: definition.key,
2892
+ userSecretDefinitionName: definition.name,
2893
+ responsibleUserId: context.responsibleUserId ?? null,
2894
+ errorCode: "binding_missing",
2895
+ });
2896
+ continue;
2897
+ }
2898
+ if (!context.responsibleUserId?.trim()) {
2899
+ missingUserSecretBindings.push({
2900
+ consumerType: context.consumerType,
2901
+ consumerId: context.consumerId,
2902
+ configPath: entry.configPath,
2903
+ envKey: entry.key,
2904
+ bindingType: "user_secret_ref",
2905
+ secretId: null,
2906
+ secretName: null,
2907
+ userSecretDefinitionId: definition.id,
2908
+ userSecretDefinitionKey: definition.key,
2909
+ userSecretDefinitionName: definition.name,
2910
+ responsibleUserId: null,
2911
+ errorCode: "responsible_user_missing",
2912
+ });
2913
+ continue;
2914
+ }
2915
+ const secret = await getUserSecretValue({
2916
+ companyId,
2917
+ ownerUserId: context.responsibleUserId,
2918
+ definitionId: definition.id,
2919
+ });
2920
+ if (!secret || secret.status !== "active") {
2921
+ missingUserSecretBindings.push({
2922
+ consumerType: context.consumerType,
2923
+ consumerId: context.consumerId,
2924
+ configPath: entry.configPath,
2925
+ envKey: entry.key,
2926
+ bindingType: "user_secret_ref",
2927
+ secretId: secret?.id ?? null,
2928
+ secretName: null,
2929
+ userSecretDefinitionId: definition.id,
2930
+ userSecretDefinitionKey: definition.key,
2931
+ userSecretDefinitionName: definition.name,
2932
+ responsibleUserId: context.responsibleUserId,
2933
+ errorCode: secret ? "secret_inactive" : "user_secret_missing",
2934
+ });
2935
+ }
2936
+ }
2937
+ return [...missingSecretBindings, ...missingUserSecretBindings];
2076
2938
  },
2077
2939
  collectMissingAdapterConfigRuntimeBindings: async (companyId, adapterConfig, adapterType, context) => {
2078
2940
  const secretFieldKeys = await listAdapterSchemaSecretFieldKeys(adapterType);
@@ -2085,7 +2947,18 @@ export function secretService(db) {
2085
2947
  return [];
2086
2948
  return [{ key, configPath: key, secretId: binding.secretId }];
2087
2949
  });
2088
- if (secretRefs.length === 0)
2950
+ const userSecretRefs = secretFieldKeys.flatMap((key) => {
2951
+ const parsed = envBindingSchema.safeParse(adapterConfig[key]);
2952
+ if (!parsed.success)
2953
+ return [];
2954
+ const binding = canonicalizeBinding(parsed.data);
2955
+ if (binding.type !== "user_secret_ref")
2956
+ return [];
2957
+ if (!binding.required || binding.allowMissingOverride)
2958
+ return [];
2959
+ return [{ key, configPath: key, binding }];
2960
+ });
2961
+ if (secretRefs.length === 0 && userSecretRefs.length === 0)
2089
2962
  return [];
2090
2963
  const bindingChecks = await Promise.all(secretRefs.map(async (entry) => ({
2091
2964
  entry,
@@ -2100,21 +2973,96 @@ export function secretService(db) {
2100
2973
  const missingEntries = bindingChecks
2101
2974
  .filter((check) => !check.found)
2102
2975
  .map((check) => check.entry);
2103
- if (missingEntries.length === 0)
2104
- return [];
2105
2976
  const secretRows = await Promise.all([...new Set(missingEntries.map((entry) => entry.secretId))].map(async (secretId) => [
2106
2977
  secretId,
2107
2978
  await getById(secretId).catch(() => null),
2108
2979
  ]));
2109
2980
  const secretsById = new Map(secretRows);
2110
- return missingEntries.map((entry) => ({
2981
+ const missingSecretBindings = missingEntries.map((entry) => ({
2111
2982
  consumerType: context.consumerType,
2112
2983
  consumerId: context.consumerId,
2113
2984
  configPath: entry.configPath,
2114
2985
  envKey: entry.key,
2986
+ bindingType: "secret_ref",
2115
2987
  secretId: entry.secretId,
2116
2988
  secretName: secretsById.get(entry.secretId)?.name ?? null,
2117
2989
  }));
2990
+ const missingUserSecretBindings = [];
2991
+ for (const entry of userSecretRefs) {
2992
+ let definition = null;
2993
+ try {
2994
+ definition = await resolveUserSecretDefinition(companyId, { definitionKey: entry.binding.key });
2995
+ }
2996
+ catch {
2997
+ missingUserSecretBindings.push(missingUserSecretDefinitionRuntimeBinding(entry, context, null, "user_secret_definition_missing"));
2998
+ continue;
2999
+ }
3000
+ if (definition.status !== "active") {
3001
+ missingUserSecretBindings.push(missingUserSecretDefinitionRuntimeBinding(entry, context, definition, "user_secret_definition_inactive"));
3002
+ continue;
3003
+ }
3004
+ const declaration = await db
3005
+ .select()
3006
+ .from(userSecretDeclarations)
3007
+ .where(and(eq(userSecretDeclarations.companyId, companyId), eq(userSecretDeclarations.userSecretDefinitionId, definition.id), eq(userSecretDeclarations.targetType, context.consumerType), eq(userSecretDeclarations.targetId, context.consumerId), eq(userSecretDeclarations.configPath, entry.configPath)))
3008
+ .then((rows) => rows[0] ?? null);
3009
+ if (!declaration) {
3010
+ missingUserSecretBindings.push({
3011
+ consumerType: context.consumerType,
3012
+ consumerId: context.consumerId,
3013
+ configPath: entry.configPath,
3014
+ envKey: entry.key,
3015
+ bindingType: "user_secret_ref",
3016
+ secretId: null,
3017
+ secretName: null,
3018
+ userSecretDefinitionId: definition.id,
3019
+ userSecretDefinitionKey: definition.key,
3020
+ userSecretDefinitionName: definition.name,
3021
+ responsibleUserId: context.responsibleUserId ?? null,
3022
+ errorCode: "binding_missing",
3023
+ });
3024
+ continue;
3025
+ }
3026
+ if (!context.responsibleUserId?.trim()) {
3027
+ missingUserSecretBindings.push({
3028
+ consumerType: context.consumerType,
3029
+ consumerId: context.consumerId,
3030
+ configPath: entry.configPath,
3031
+ envKey: entry.key,
3032
+ bindingType: "user_secret_ref",
3033
+ secretId: null,
3034
+ secretName: null,
3035
+ userSecretDefinitionId: definition.id,
3036
+ userSecretDefinitionKey: definition.key,
3037
+ userSecretDefinitionName: definition.name,
3038
+ responsibleUserId: null,
3039
+ errorCode: "responsible_user_missing",
3040
+ });
3041
+ continue;
3042
+ }
3043
+ const secret = await getUserSecretValue({
3044
+ companyId,
3045
+ ownerUserId: context.responsibleUserId,
3046
+ definitionId: definition.id,
3047
+ });
3048
+ if (!secret || secret.status !== "active") {
3049
+ missingUserSecretBindings.push({
3050
+ consumerType: context.consumerType,
3051
+ consumerId: context.consumerId,
3052
+ configPath: entry.configPath,
3053
+ envKey: entry.key,
3054
+ bindingType: "user_secret_ref",
3055
+ secretId: secret?.id ?? null,
3056
+ secretName: null,
3057
+ userSecretDefinitionId: definition.id,
3058
+ userSecretDefinitionKey: definition.key,
3059
+ userSecretDefinitionName: definition.name,
3060
+ responsibleUserId: context.responsibleUserId,
3061
+ errorCode: secret ? "secret_inactive" : "user_secret_missing",
3062
+ });
3063
+ }
3064
+ }
3065
+ return [...missingSecretBindings, ...missingUserSecretBindings];
2118
3066
  },
2119
3067
  resolveAdapterConfigForRuntime: async (companyId, adapterConfig, context, opts) => {
2120
3068
  const resolved = { ...adapterConfig };
@@ -2139,7 +3087,7 @@ export function secretService(db) {
2139
3087
  if (binding.type === "plain") {
2140
3088
  env[key] = binding.value;
2141
3089
  }
2142
- else {
3090
+ else if (binding.type === "secret_ref") {
2143
3091
  const secretResolution = await resolveSecretValueInternal(companyId, binding.secretId, binding.version, context
2144
3092
  ? {
2145
3093
  bindingContext: { ...context, configPath: `env.${key}` },
@@ -2150,6 +3098,27 @@ export function secretService(db) {
2150
3098
  manifest.push(secretResolution.manifestEntry);
2151
3099
  secretKeys.add(key);
2152
3100
  }
3101
+ else {
3102
+ if (opts?.skipUserSecrets)
3103
+ continue;
3104
+ const secretResolution = await secretService(db).resolveUserSecretValue(companyId, {
3105
+ definitionKey: binding.key,
3106
+ version: binding.version,
3107
+ required: binding.required,
3108
+ allowMissingOverride: binding.allowMissingOverride,
3109
+ }, context
3110
+ ? {
3111
+ ...context,
3112
+ configPath: `env.${key}`,
3113
+ responsibleUserId: context.responsibleUserId ?? null,
3114
+ }
3115
+ : undefined);
3116
+ if (secretResolution) {
3117
+ env[key] = secretResolution.value;
3118
+ manifest.push(secretResolution.manifestEntry);
3119
+ secretKeys.add(key);
3120
+ }
3121
+ }
2153
3122
  }
2154
3123
  resolved.env = env;
2155
3124
  }
@@ -2162,6 +3131,30 @@ export function secretService(db) {
2162
3131
  const binding = canonicalizeBinding(parsed.data);
2163
3132
  if (binding.type === "plain")
2164
3133
  continue;
3134
+ if (binding.type === "user_secret_ref") {
3135
+ if (opts?.skipUserSecrets) {
3136
+ delete resolved[key];
3137
+ continue;
3138
+ }
3139
+ const secretResolution = await secretService(db).resolveUserSecretValue(companyId, {
3140
+ definitionKey: binding.key,
3141
+ version: binding.version,
3142
+ required: binding.required,
3143
+ allowMissingOverride: binding.allowMissingOverride,
3144
+ }, context
3145
+ ? {
3146
+ ...context,
3147
+ configPath: key,
3148
+ responsibleUserId: context.responsibleUserId ?? null,
3149
+ }
3150
+ : undefined);
3151
+ if (secretResolution) {
3152
+ resolved[key] = secretResolution.value;
3153
+ manifest.push(secretResolution.manifestEntry);
3154
+ secretKeys.add(key);
3155
+ }
3156
+ continue;
3157
+ }
2165
3158
  const secretResolution = await resolveSecretValueInternal(companyId, binding.secretId, binding.version, context
2166
3159
  ? {
2167
3160
  bindingContext: { ...context, configPath: key },