@selvajs/platform 0.21.0-beta.3 → 1.0.0-beta.5

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 (362) hide show
  1. package/README.md +11 -8
  2. package/dist/access/index.d.ts +0 -1
  3. package/dist/access/index.js +0 -1
  4. package/dist/access/rules.d.ts +7 -14
  5. package/dist/access/rules.js +22 -26
  6. package/dist/apiTokens/index.d.ts +1 -2
  7. package/dist/apiTokens/index.js +1 -2
  8. package/dist/apiTokens/interface.d.ts +0 -1
  9. package/dist/apiTokens/interface.js +0 -1
  10. package/dist/apiTokens/scope.d.ts +0 -1
  11. package/dist/apiTokens/scope.js +0 -1
  12. package/dist/apiTokens/types.d.ts +0 -1
  13. package/dist/apiTokens/types.js +0 -1
  14. package/dist/auth/index.d.ts +0 -1
  15. package/dist/auth/index.js +0 -1
  16. package/dist/auth/interface.d.ts +4 -23
  17. package/dist/auth/interface.js +0 -1
  18. package/dist/auth/types.d.ts +4 -2
  19. package/dist/auth/types.js +0 -1
  20. package/dist/bindings/index.d.ts +0 -1
  21. package/dist/bindings/index.js +0 -1
  22. package/dist/bindings/interface.d.ts +0 -1
  23. package/dist/bindings/interface.js +0 -1
  24. package/dist/computeServer/index.d.ts +1 -7
  25. package/dist/computeServer/index.js +2 -8
  26. package/dist/computeServer/interface.d.ts +9 -6
  27. package/dist/computeServer/interface.js +0 -1
  28. package/dist/computeServer/public.d.ts +6 -0
  29. package/dist/computeServer/public.js +2 -0
  30. package/dist/computeServer/secrets.d.ts +12 -6
  31. package/dist/computeServer/secrets.js +35 -15
  32. package/dist/computeServer/types.d.ts +5 -2
  33. package/dist/computeServer/types.js +4 -1
  34. package/dist/computeServer/utils.d.ts +17 -6
  35. package/dist/computeServer/utils.js +31 -21
  36. package/dist/config.d.ts +1 -2
  37. package/dist/config.js +0 -1
  38. package/dist/context.d.ts +11 -1
  39. package/dist/context.js +51 -2
  40. package/dist/data/index.d.ts +0 -1
  41. package/dist/data/index.js +0 -1
  42. package/dist/data/interface.d.ts +0 -1
  43. package/dist/data/interface.js +0 -1
  44. package/dist/definitions/index.d.ts +2 -3
  45. package/dist/definitions/index.js +2 -3
  46. package/dist/definitions/interface.d.ts +0 -1
  47. package/dist/definitions/interface.js +0 -1
  48. package/dist/definitions/paths.d.ts +0 -1
  49. package/dist/definitions/paths.js +1 -12
  50. package/dist/definitions/schemas.d.ts +0 -21
  51. package/dist/definitions/schemas.js +6 -19
  52. package/dist/definitions/types.d.ts +3 -4
  53. package/dist/definitions/types.js +0 -3
  54. package/dist/errors/interface.d.ts +1 -2
  55. package/dist/errors/interface.js +0 -1
  56. package/dist/errors.d.ts +8 -1
  57. package/dist/errors.js +11 -1
  58. package/dist/events/audit.d.ts +1 -2
  59. package/dist/events/audit.js +0 -1
  60. package/dist/events/index.d.ts +0 -1
  61. package/dist/events/index.js +0 -1
  62. package/dist/events/interface.d.ts +0 -1
  63. package/dist/events/interface.js +0 -1
  64. package/dist/index.d.ts +18 -61
  65. package/dist/index.js +18 -24
  66. package/dist/invites/index.d.ts +0 -1
  67. package/dist/invites/index.js +0 -1
  68. package/dist/invites/interface.d.ts +5 -6
  69. package/dist/invites/interface.js +0 -1
  70. package/dist/invites/types.d.ts +0 -1
  71. package/dist/invites/types.js +0 -1
  72. package/dist/logging/interface.d.ts +0 -1
  73. package/dist/logging/interface.js +0 -1
  74. package/dist/metrics/interface.d.ts +0 -1
  75. package/dist/metrics/interface.js +0 -1
  76. package/dist/notifications/index.d.ts +0 -1
  77. package/dist/notifications/index.js +0 -1
  78. package/dist/notifications/interface.d.ts +1 -3
  79. package/dist/notifications/interface.js +1 -3
  80. package/dist/notifications/types.d.ts +0 -1
  81. package/dist/notifications/types.js +0 -1
  82. package/dist/organizations/index.d.ts +2 -3
  83. package/dist/organizations/index.js +1 -2
  84. package/dist/organizations/interface.d.ts +3 -5
  85. package/dist/organizations/interface.js +0 -1
  86. package/dist/organizations/paths.d.ts +1 -6
  87. package/dist/organizations/paths.js +2 -20
  88. package/dist/organizations/schemas.d.ts +5 -7
  89. package/dist/organizations/schemas.js +20 -13
  90. package/dist/organizations/types.d.ts +0 -1
  91. package/dist/organizations/types.js +0 -1
  92. package/dist/pagination.d.ts +21 -3
  93. package/dist/pagination.js +34 -1
  94. package/dist/permissions/index.d.ts +2 -2
  95. package/dist/permissions/index.js +2 -2
  96. package/dist/permissions/interface.d.ts +4 -4
  97. package/dist/permissions/interface.js +0 -1
  98. package/dist/permissions/types.d.ts +0 -1
  99. package/dist/permissions/types.js +0 -1
  100. package/dist/platformProjects/index.d.ts +1 -3
  101. package/dist/platformProjects/index.js +1 -2
  102. package/dist/platformProjects/interface.d.ts +0 -1
  103. package/dist/platformProjects/interface.js +0 -1
  104. package/dist/platformProjects/schemas.d.ts +0 -1
  105. package/dist/platformProjects/schemas.js +0 -1
  106. package/dist/platformProjects/types.d.ts +3 -2
  107. package/dist/platformProjects/types.js +0 -1
  108. package/dist/projects/index.d.ts +0 -1
  109. package/dist/projects/index.js +0 -1
  110. package/dist/projects/interface.d.ts +0 -1
  111. package/dist/projects/interface.js +0 -1
  112. package/dist/projects/schemas.d.ts +0 -1
  113. package/dist/projects/schemas.js +0 -1
  114. package/dist/projects/types.d.ts +0 -1
  115. package/dist/projects/types.js +0 -1
  116. package/dist/shareLinks/index.d.ts +0 -1
  117. package/dist/shareLinks/index.js +0 -1
  118. package/dist/shareLinks/interface.d.ts +6 -3
  119. package/dist/shareLinks/interface.js +0 -1
  120. package/dist/shareLinks/schemas.d.ts +0 -1
  121. package/dist/shareLinks/schemas.js +4 -2
  122. package/dist/shareLinks/types.d.ts +0 -1
  123. package/dist/shareLinks/types.js +0 -1
  124. package/dist/solveCache/interface.d.ts +2 -3
  125. package/dist/solveCache/interface.js +0 -1
  126. package/dist/storage/assetClasses.d.ts +4 -8
  127. package/dist/storage/assetClasses.js +6 -21
  128. package/dist/storage/cacheBust.d.ts +0 -1
  129. package/dist/storage/cacheBust.js +5 -3
  130. package/dist/storage/image.d.ts +8 -5
  131. package/dist/storage/image.js +74 -12
  132. package/dist/storage/index.d.ts +2 -4
  133. package/dist/storage/index.js +2 -3
  134. package/dist/storage/interface.d.ts +11 -3
  135. package/dist/storage/interface.js +0 -1
  136. package/dist/storage/safeKey.d.ts +7 -0
  137. package/dist/storage/safeKey.js +12 -0
  138. package/dist/testing/index.d.ts +5 -2
  139. package/dist/testing/index.js +3 -2
  140. package/dist/testing/suites/apiTokenStoreSuite.d.ts +0 -1
  141. package/dist/testing/suites/apiTokenStoreSuite.js +0 -1
  142. package/dist/testing/suites/auditQuerySuite.d.ts +18 -0
  143. package/dist/testing/suites/auditQuerySuite.js +73 -0
  144. package/dist/testing/suites/authProviderSuite.d.ts +14 -3
  145. package/dist/testing/suites/authProviderSuite.js +57 -32
  146. package/dist/testing/suites/computeServerStoreSuite.d.ts +1 -2
  147. package/dist/testing/suites/computeServerStoreSuite.js +68 -4
  148. package/dist/testing/suites/definitionStoreSuite.d.ts +2 -1
  149. package/dist/testing/suites/definitionStoreSuite.js +19 -16
  150. package/dist/testing/suites/emailLinkAuthSuite.d.ts +2 -5
  151. package/dist/testing/suites/emailLinkAuthSuite.js +1 -9
  152. package/dist/testing/suites/eventSinkSuite.d.ts +0 -1
  153. package/dist/testing/suites/eventSinkSuite.js +9 -1
  154. package/dist/testing/suites/helpers.d.ts +5 -1
  155. package/dist/testing/suites/helpers.js +7 -1
  156. package/dist/testing/suites/inviteStoreSuite.d.ts +0 -1
  157. package/dist/testing/suites/inviteStoreSuite.js +17 -7
  158. package/dist/testing/suites/oauthAuthSuite.d.ts +18 -0
  159. package/dist/testing/suites/oauthAuthSuite.js +26 -0
  160. package/dist/testing/suites/orgStoreSuite.d.ts +5 -1
  161. package/dist/testing/suites/orgStoreSuite.js +26 -7
  162. package/dist/testing/suites/platformPermissionStoreSuite.d.ts +0 -5
  163. package/dist/testing/suites/platformPermissionStoreSuite.js +12 -63
  164. package/dist/testing/suites/platformProjectGrantStoreSuite.d.ts +0 -1
  165. package/dist/testing/suites/platformProjectGrantStoreSuite.js +0 -5
  166. package/dist/testing/suites/projectStoreSuite.d.ts +5 -1
  167. package/dist/testing/suites/projectStoreSuite.js +6 -6
  168. package/dist/testing/suites/shareLinkStoreSuite.d.ts +2 -1
  169. package/dist/testing/suites/shareLinkStoreSuite.js +22 -25
  170. package/dist/testing/suites/solveMetricSinkSuite.d.ts +0 -1
  171. package/dist/testing/suites/solveMetricSinkSuite.js +2 -2
  172. package/dist/testing/suites/storageProviderSuite.d.ts +0 -1
  173. package/dist/testing/suites/storageProviderSuite.js +15 -10
  174. package/dist/testing/suites/userProfileStoreSuite.d.ts +1 -2
  175. package/dist/testing/suites/userProfileStoreSuite.js +42 -7
  176. package/dist/userProfile/index.d.ts +2 -2
  177. package/dist/userProfile/index.js +2 -2
  178. package/dist/userProfile/interface.d.ts +5 -6
  179. package/dist/userProfile/interface.js +0 -1
  180. package/dist/userProfile/types.d.ts +2 -1
  181. package/dist/userProfile/types.js +2 -1
  182. package/dist/utils/audit.d.ts +0 -1
  183. package/dist/utils/audit.js +0 -1
  184. package/dist/utils/index.d.ts +0 -2
  185. package/dist/utils/index.js +0 -1
  186. package/package.json +7 -6
  187. package/dist/access/index.d.ts.map +0 -1
  188. package/dist/access/index.js.map +0 -1
  189. package/dist/access/rules.d.ts.map +0 -1
  190. package/dist/access/rules.js.map +0 -1
  191. package/dist/apiTokens/index.d.ts.map +0 -1
  192. package/dist/apiTokens/index.js.map +0 -1
  193. package/dist/apiTokens/interface.d.ts.map +0 -1
  194. package/dist/apiTokens/interface.js.map +0 -1
  195. package/dist/apiTokens/scope.d.ts.map +0 -1
  196. package/dist/apiTokens/scope.js.map +0 -1
  197. package/dist/apiTokens/types.d.ts.map +0 -1
  198. package/dist/apiTokens/types.js.map +0 -1
  199. package/dist/auth/index.d.ts.map +0 -1
  200. package/dist/auth/index.js.map +0 -1
  201. package/dist/auth/interface.d.ts.map +0 -1
  202. package/dist/auth/interface.js.map +0 -1
  203. package/dist/auth/types.d.ts.map +0 -1
  204. package/dist/auth/types.js.map +0 -1
  205. package/dist/bindings/index.d.ts.map +0 -1
  206. package/dist/bindings/index.js.map +0 -1
  207. package/dist/bindings/interface.d.ts.map +0 -1
  208. package/dist/bindings/interface.js.map +0 -1
  209. package/dist/computeServer/index.d.ts.map +0 -1
  210. package/dist/computeServer/index.js.map +0 -1
  211. package/dist/computeServer/interface.d.ts.map +0 -1
  212. package/dist/computeServer/interface.js.map +0 -1
  213. package/dist/computeServer/secrets.d.ts.map +0 -1
  214. package/dist/computeServer/secrets.js.map +0 -1
  215. package/dist/computeServer/types.d.ts.map +0 -1
  216. package/dist/computeServer/types.js.map +0 -1
  217. package/dist/computeServer/utils.d.ts.map +0 -1
  218. package/dist/computeServer/utils.js.map +0 -1
  219. package/dist/config.d.ts.map +0 -1
  220. package/dist/config.js.map +0 -1
  221. package/dist/context.d.ts.map +0 -1
  222. package/dist/context.js.map +0 -1
  223. package/dist/data/index.d.ts.map +0 -1
  224. package/dist/data/index.js.map +0 -1
  225. package/dist/data/interface.d.ts.map +0 -1
  226. package/dist/data/interface.js.map +0 -1
  227. package/dist/definitions/index.d.ts.map +0 -1
  228. package/dist/definitions/index.js.map +0 -1
  229. package/dist/definitions/interface.d.ts.map +0 -1
  230. package/dist/definitions/interface.js.map +0 -1
  231. package/dist/definitions/paths.d.ts.map +0 -1
  232. package/dist/definitions/paths.js.map +0 -1
  233. package/dist/definitions/schemas.d.ts.map +0 -1
  234. package/dist/definitions/schemas.js.map +0 -1
  235. package/dist/definitions/types.d.ts.map +0 -1
  236. package/dist/definitions/types.js.map +0 -1
  237. package/dist/errors/interface.d.ts.map +0 -1
  238. package/dist/errors/interface.js.map +0 -1
  239. package/dist/errors.d.ts.map +0 -1
  240. package/dist/errors.js.map +0 -1
  241. package/dist/events/audit.d.ts.map +0 -1
  242. package/dist/events/audit.js.map +0 -1
  243. package/dist/events/index.d.ts.map +0 -1
  244. package/dist/events/index.js.map +0 -1
  245. package/dist/events/interface.d.ts.map +0 -1
  246. package/dist/events/interface.js.map +0 -1
  247. package/dist/index.d.ts.map +0 -1
  248. package/dist/index.js.map +0 -1
  249. package/dist/invites/index.d.ts.map +0 -1
  250. package/dist/invites/index.js.map +0 -1
  251. package/dist/invites/interface.d.ts.map +0 -1
  252. package/dist/invites/interface.js.map +0 -1
  253. package/dist/invites/types.d.ts.map +0 -1
  254. package/dist/invites/types.js.map +0 -1
  255. package/dist/logging/interface.d.ts.map +0 -1
  256. package/dist/logging/interface.js.map +0 -1
  257. package/dist/metrics/interface.d.ts.map +0 -1
  258. package/dist/metrics/interface.js.map +0 -1
  259. package/dist/notifications/index.d.ts.map +0 -1
  260. package/dist/notifications/index.js.map +0 -1
  261. package/dist/notifications/interface.d.ts.map +0 -1
  262. package/dist/notifications/interface.js.map +0 -1
  263. package/dist/notifications/types.d.ts.map +0 -1
  264. package/dist/notifications/types.js.map +0 -1
  265. package/dist/organizations/index.d.ts.map +0 -1
  266. package/dist/organizations/index.js.map +0 -1
  267. package/dist/organizations/interface.d.ts.map +0 -1
  268. package/dist/organizations/interface.js.map +0 -1
  269. package/dist/organizations/paths.d.ts.map +0 -1
  270. package/dist/organizations/paths.js.map +0 -1
  271. package/dist/organizations/schemas.d.ts.map +0 -1
  272. package/dist/organizations/schemas.js.map +0 -1
  273. package/dist/organizations/types.d.ts.map +0 -1
  274. package/dist/organizations/types.js.map +0 -1
  275. package/dist/pagination.d.ts.map +0 -1
  276. package/dist/pagination.js.map +0 -1
  277. package/dist/permissions/index.d.ts.map +0 -1
  278. package/dist/permissions/index.js.map +0 -1
  279. package/dist/permissions/interface.d.ts.map +0 -1
  280. package/dist/permissions/interface.js.map +0 -1
  281. package/dist/permissions/types.d.ts.map +0 -1
  282. package/dist/permissions/types.js.map +0 -1
  283. package/dist/platformProjects/index.d.ts.map +0 -1
  284. package/dist/platformProjects/index.js.map +0 -1
  285. package/dist/platformProjects/interface.d.ts.map +0 -1
  286. package/dist/platformProjects/interface.js.map +0 -1
  287. package/dist/platformProjects/schemas.d.ts.map +0 -1
  288. package/dist/platformProjects/schemas.js.map +0 -1
  289. package/dist/platformProjects/types.d.ts.map +0 -1
  290. package/dist/platformProjects/types.js.map +0 -1
  291. package/dist/projects/index.d.ts.map +0 -1
  292. package/dist/projects/index.js.map +0 -1
  293. package/dist/projects/interface.d.ts.map +0 -1
  294. package/dist/projects/interface.js.map +0 -1
  295. package/dist/projects/schemas.d.ts.map +0 -1
  296. package/dist/projects/schemas.js.map +0 -1
  297. package/dist/projects/types.d.ts.map +0 -1
  298. package/dist/projects/types.js.map +0 -1
  299. package/dist/shareLinks/index.d.ts.map +0 -1
  300. package/dist/shareLinks/index.js.map +0 -1
  301. package/dist/shareLinks/interface.d.ts.map +0 -1
  302. package/dist/shareLinks/interface.js.map +0 -1
  303. package/dist/shareLinks/schemas.d.ts.map +0 -1
  304. package/dist/shareLinks/schemas.js.map +0 -1
  305. package/dist/shareLinks/types.d.ts.map +0 -1
  306. package/dist/shareLinks/types.js.map +0 -1
  307. package/dist/solveCache/interface.d.ts.map +0 -1
  308. package/dist/solveCache/interface.js.map +0 -1
  309. package/dist/storage/assetClasses.d.ts.map +0 -1
  310. package/dist/storage/assetClasses.js.map +0 -1
  311. package/dist/storage/cacheBust.d.ts.map +0 -1
  312. package/dist/storage/cacheBust.js.map +0 -1
  313. package/dist/storage/image.d.ts.map +0 -1
  314. package/dist/storage/image.js.map +0 -1
  315. package/dist/storage/index.d.ts.map +0 -1
  316. package/dist/storage/index.js.map +0 -1
  317. package/dist/storage/interface.d.ts.map +0 -1
  318. package/dist/storage/interface.js.map +0 -1
  319. package/dist/testing/index.d.ts.map +0 -1
  320. package/dist/testing/index.js.map +0 -1
  321. package/dist/testing/suites/apiTokenStoreSuite.d.ts.map +0 -1
  322. package/dist/testing/suites/apiTokenStoreSuite.js.map +0 -1
  323. package/dist/testing/suites/authProviderSuite.d.ts.map +0 -1
  324. package/dist/testing/suites/authProviderSuite.js.map +0 -1
  325. package/dist/testing/suites/computeServerStoreSuite.d.ts.map +0 -1
  326. package/dist/testing/suites/computeServerStoreSuite.js.map +0 -1
  327. package/dist/testing/suites/definitionStoreSuite.d.ts.map +0 -1
  328. package/dist/testing/suites/definitionStoreSuite.js.map +0 -1
  329. package/dist/testing/suites/emailLinkAuthSuite.d.ts.map +0 -1
  330. package/dist/testing/suites/emailLinkAuthSuite.js.map +0 -1
  331. package/dist/testing/suites/eventSinkSuite.d.ts.map +0 -1
  332. package/dist/testing/suites/eventSinkSuite.js.map +0 -1
  333. package/dist/testing/suites/helpers.d.ts.map +0 -1
  334. package/dist/testing/suites/helpers.js.map +0 -1
  335. package/dist/testing/suites/inviteStoreSuite.d.ts.map +0 -1
  336. package/dist/testing/suites/inviteStoreSuite.js.map +0 -1
  337. package/dist/testing/suites/orgStoreSuite.d.ts.map +0 -1
  338. package/dist/testing/suites/orgStoreSuite.js.map +0 -1
  339. package/dist/testing/suites/platformPermissionStoreSuite.d.ts.map +0 -1
  340. package/dist/testing/suites/platformPermissionStoreSuite.js.map +0 -1
  341. package/dist/testing/suites/platformProjectGrantStoreSuite.d.ts.map +0 -1
  342. package/dist/testing/suites/platformProjectGrantStoreSuite.js.map +0 -1
  343. package/dist/testing/suites/projectStoreSuite.d.ts.map +0 -1
  344. package/dist/testing/suites/projectStoreSuite.js.map +0 -1
  345. package/dist/testing/suites/shareLinkStoreSuite.d.ts.map +0 -1
  346. package/dist/testing/suites/shareLinkStoreSuite.js.map +0 -1
  347. package/dist/testing/suites/solveMetricSinkSuite.d.ts.map +0 -1
  348. package/dist/testing/suites/solveMetricSinkSuite.js.map +0 -1
  349. package/dist/testing/suites/storageProviderSuite.d.ts.map +0 -1
  350. package/dist/testing/suites/storageProviderSuite.js.map +0 -1
  351. package/dist/testing/suites/userProfileStoreSuite.d.ts.map +0 -1
  352. package/dist/testing/suites/userProfileStoreSuite.js.map +0 -1
  353. package/dist/userProfile/index.d.ts.map +0 -1
  354. package/dist/userProfile/index.js.map +0 -1
  355. package/dist/userProfile/interface.d.ts.map +0 -1
  356. package/dist/userProfile/interface.js.map +0 -1
  357. package/dist/userProfile/types.d.ts.map +0 -1
  358. package/dist/userProfile/types.js.map +0 -1
  359. package/dist/utils/audit.d.ts.map +0 -1
  360. package/dist/utils/audit.js.map +0 -1
  361. package/dist/utils/index.d.ts.map +0 -1
  362. package/dist/utils/index.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @selvajs/platform
2
2
 
3
- Pure TypeScript interfaces defining Selva's contract with its backends — no runtime dependency on a specific database, storage, or auth service. Concrete adapters live in their own packages: `@selvajs/local-provider`, `@selvajs/supabase-provider`, `@selvajs/header-auth-provider`.
3
+ Selva's contract with its backends: the interfaces, plus the logic every adapter shares (access rules, API-token scopes, at-rest secret encryption, Zod request schemas, slugs, the image transcoder) and vitest conformance suites. No dependency on a specific database, storage or auth service. Concrete adapters live in their own packages: `@selvajs/local-provider`, `@selvajs/supabase-provider`, `@selvajs/header-auth-provider`.
4
4
 
5
5
  This README is the contract. Read it before writing an adapter.
6
6
 
@@ -20,6 +20,7 @@ This README is the contract. Read it before writing an adapter.
20
20
  | `IShareLinkStore` | Per-definition anonymous-access tokens. | Yes — every method. |
21
21
  | `IInviteStore` | Pending org-membership invitations. | Yes — every method. |
22
22
  | `IComputeServerStore` | Global + per-org compute-server config. | No — not tenant-scoped. |
23
+ | `IApiTokenStore` | API tokens and their revocation. | Yes — every method. |
23
24
  | `IStorageProvider` | Path-based blob storage. Authorization is the caller's responsibility. | No — callers pass already-authorized paths. |
24
25
 
25
26
  `IDataProvider` composes every store, plus optional `auditQuery` / `events` hooks, an optional `verifySchemaVersion` handshake (for adapters whose schema migrates out-of-band), and the `ensureUser` / `onUserDeleted` lifecycle methods. An adapter typically implements one class per store and aggregates them.
@@ -49,12 +50,12 @@ Providers are two-phase: a metadata store (`IDataProvider`) and a blob store (`I
49
50
 
50
51
  List queries filter `'pending'` and `'archived'` by default (`DefinitionListOptions.includePending` / `includeArchived` opt in). If step 2 fails, the record stays `'pending'` and is invisible to consumers.
51
52
 
52
- **Delete — blob-first:**
53
+ **Delete — metadata-first:**
53
54
 
54
- 1. `storage.deletePrefix(prefix)`.
55
- 2. `data.delete(guid)`.
55
+ 1. `data.delete(guid)`, which hides the record at once.
56
+ 2. `storage.deletePrefix(prefix)`.
56
57
 
57
- If step 2 fails, a retry re-deletes blobs (no-op) and succeeds.
58
+ If step 2 fails, the blobs are orphans no reader can reach. The reverse order would leave a live record with its blobs gone.
58
59
 
59
60
  ---
60
61
 
@@ -84,7 +85,7 @@ The Selva app normally picks bundled providers from `SELVA_AUTH_PROVIDER` / `SEL
84
85
 
85
86
  ## Testing your adapter
86
87
 
87
- `@selvajs/platform/testing` ships a conformance suite per store. Adapters import it and run it against their own instance — passing means the adapter behaves the same as the in-memory reference.
88
+ `@selvajs/platform/testing` ships a conformance suite per store. Adapters import it and run it against their own instance — passing means the adapter honours the contract the bundled providers do.
88
89
 
89
90
  ```ts
90
91
  import { runDefinitionStoreConformance } from '@selvajs/platform/testing';
@@ -96,7 +97,9 @@ runDefinitionStoreConformance({
96
97
  });
97
98
  ```
98
99
 
99
- A suite exists per store, plus auth, email-link auth, storage, event sink, and solve-metric sink. They cover `ctx` scoping, `pending` filtering, `includePending` opt-in, and `ProviderError` shapes; they do not cover performance or concurrency.
100
+ A suite exists per store, plus auth, email-link auth, storage, event sink and solve-metric sink. `runAuthProviderConformance` wraps `runAuthCoreConformance` and `runPasswordAuthConformance`, so a provider without passwords can run the core alone or pass `passwordless`. New cases go behind an option, so an upgrade doesn't turn a third-party adapter red. The suites cover `ctx` scoping, `pending` filtering, `includePending` opt-in, `ProviderError` shapes, and the races a store exists to prevent, such as the share-link solve cap. They don't cover performance.
101
+
102
+ `sharp` is an optional peer dependency. An adapter that calls `transcodeImageIfNeeded` has to install it.
100
103
 
101
104
  ---
102
105
 
@@ -108,7 +111,7 @@ Throw `ProviderError` for user-facing failures (`new ProviderError('...', 404)`)
108
111
 
109
112
  ## Data privacy
110
113
 
111
- Identity and credentials belong to whichever `IAuthProvider` is configured — for Supabase that's a separate service; for the local provider, Selva itself is the auth provider and holds email addresses and password hashes on disk. This package's own stores hold user IDs, authorization metadata, display names, invite email addresses, and audit payloads. **The operator is the data controller.** See [data privacy](../../docs/self-hosting/concepts/data-privacy.md) for the full inventory and what erasure reaches, plus [providers](../../docs/self-hosting/providers/overview.md) and [security & limits](../../docs/self-hosting/concepts/security-and-limits.md).
114
+ Identity and credentials belong to whichever `IAuthProvider` is configured — for Supabase that's a separate service; for the local provider, Selva itself is the auth provider and holds email addresses and password hashes on disk. This package's own stores hold user IDs, authorization metadata, display names, invite email addresses, and audit payloads. **The operator is the data controller.** See [data privacy](../../docs/self-hosting/concepts/data-privacy.md) for the full inventory and what erasure reaches, plus [providers](../../docs/self-hosting/providers/overview.md) and [security & limits](../../docs/self-hosting/deployment/security-and-limits.md).
112
115
 
113
116
  ---
114
117
 
@@ -1,3 +1,2 @@
1
1
  export type { ProjectAccessInput, DefinitionAccessInput, VisibilityChangeInput, ReclaimAccessInput, CreateProjectAccessInput, OrgOwnerAuthorityInput, OwnerRemovalInput, OwnerRemovalCheck } from './rules.js';
2
2
  export { canView, canSolve, canEdit, canCreateDefinition, canManage, canEditProjectSettings, canChangeVisibilityToPublic, canEditDefinition, canReclaim, canCreateProject, canChangeOrgRole, checkOwnerRemoval, withAdminBypass } from './rules.js';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1 @@
1
1
  export { canView, canSolve, canEdit, canCreateDefinition, canManage, canEditProjectSettings, canChangeVisibilityToPublic, canEditDefinition, canReclaim, canCreateProject, canChangeOrgRole, checkOwnerRemoval, withAdminBypass } from './rules.js';
2
- //# sourceMappingURL=index.js.map
@@ -52,8 +52,8 @@ export declare function canSolve(input: ProjectAccessInput): boolean;
52
52
  export declare function canEdit(input: ProjectAccessInput): boolean;
53
53
  /**
54
54
  * Adding a new definition. Commons projects (`autoJoinOnUpload`) take uploads
55
- * from anyone who reaches them, and the uploader owns what they upload;
56
- * everywhere else it needs {@link canEdit}.
55
+ * from anyone who can {@link canView} them, and the uploader owns what they
56
+ * upload; everywhere else it needs {@link canEdit}.
57
57
  */
58
58
  export declare function canCreateDefinition(input: ProjectAccessInput): boolean;
59
59
  /** Platform projects: `instance_admin` only. All other visibilities: project owner. */
@@ -131,18 +131,12 @@ export interface OrgOwnerAuthorityInput {
131
131
  role: OrgRole;
132
132
  }
133
133
  /**
134
- * Whether the actor may hand out or take away org `owner`/`admin` standing (§3).
134
+ * Whether the actor may hand out or take away org `owner`/`admin` standing.
135
+ * Inviting someone as owner/admin, changing a member's role and removing an owner
136
+ * are one rule, so all three read it here.
135
137
  *
136
- * Three routes decide this — inviting someone as owner/admin, changing an
137
- * existing member's role, and removing an owner — and all three had written it
138
- * out longhand. Two of them had drifted by the time the access audit found
139
- * them: the invite route let an admin mint an `owner` invite for themselves,
140
- * and DELETE let an admin remove an owner that PATCH would not let them demote.
141
- * They are one rule, so they read it from one place.
142
- *
143
- * Reads the actor's **membership row**. `Organization.ownerId` is a separate
144
- * field that can disagree with it — see the sole-`instance_admin` finding — and
145
- * is not authority for this decision.
138
+ * Reads the actor's **membership row**. `Organization.ownerId` is a separate field
139
+ * that can disagree with it and is not authority for this decision.
146
140
  */
147
141
  export declare function canChangeOrgRole(input: OrgOwnerAuthorityInput): boolean;
148
142
  /**
@@ -163,4 +157,3 @@ export interface OwnerRemovalInput {
163
157
  confirmed: boolean;
164
158
  }
165
159
  export declare function checkOwnerRemoval(input: OwnerRemovalInput): OwnerRemovalCheck;
166
- //# sourceMappingURL=rules.d.ts.map
@@ -33,10 +33,12 @@ export function canView(input) {
33
33
  return platformGrants.some((g) => (g.granteeType === 'user' && g.granteeId === userId) ||
34
34
  (g.granteeType === 'org' && g.granteeId === actingOrgId));
35
35
  }
36
- if (project.visibility === 'private')
37
- return member !== null;
36
+ // A project member sees the project at any visibility. Leaving the org ends the
37
+ // membership, so this only widens access for someone who reclaimed from outside.
38
+ if (member !== null)
39
+ return true;
38
40
  if (project.visibility === 'org')
39
- return orgMember !== null;
41
+ return orgMember !== null && actingOrgId === project.orgId;
40
42
  if (project.visibility === 'public')
41
43
  return allowCrossOrgPublic ? true : orgMember !== null;
42
44
  return false;
@@ -69,7 +71,9 @@ export function canSolve(input) {
69
71
  */
70
72
  export function canEdit(input) {
71
73
  const { member, project, enablePlatformProjects, platformPermissions } = input;
72
- if (project?.visibility === 'platform') {
74
+ if (!project)
75
+ return false;
76
+ if (project.visibility === 'platform') {
73
77
  if (!enablePlatformProjects)
74
78
  return false;
75
79
  return isInstanceAdmin(platformPermissions);
@@ -78,15 +82,17 @@ export function canEdit(input) {
78
82
  }
79
83
  /**
80
84
  * Adding a new definition. Commons projects (`autoJoinOnUpload`) take uploads
81
- * from anyone who reaches them, and the uploader owns what they upload;
82
- * everywhere else it needs {@link canEdit}.
85
+ * from anyone who can {@link canView} them, and the uploader owns what they
86
+ * upload; everywhere else it needs {@link canEdit}.
83
87
  */
84
88
  export function canCreateDefinition(input) {
85
- return input.project?.autoJoinOnUpload === true || canEdit(input);
89
+ return (input.project?.autoJoinOnUpload === true && canView(input)) || canEdit(input);
86
90
  }
87
91
  /** Platform projects: `instance_admin` only. All other visibilities: project owner. */
88
92
  export function canManage(input) {
89
- if (input.project?.visibility === 'platform') {
93
+ if (!input.project)
94
+ return false;
95
+ if (input.project.visibility === 'platform') {
90
96
  if (!input.enablePlatformProjects)
91
97
  return false;
92
98
  return isInstanceAdmin(input.platformPermissions);
@@ -95,12 +101,7 @@ export function canManage(input) {
95
101
  }
96
102
  /** Platform projects: `instance_admin` only. All other visibilities: project owner. */
97
103
  export function canEditProjectSettings(input) {
98
- if (input.project?.visibility === 'platform') {
99
- if (!input.enablePlatformProjects)
100
- return false;
101
- return isInstanceAdmin(input.platformPermissions);
102
- }
103
- return input.member?.role === 'owner';
104
+ return canManage(input);
104
105
  }
105
106
  /**
106
107
  * Authorization to flip a project to `public`. The `ALLOW_CROSS_ORG_PUBLIC`
@@ -121,6 +122,8 @@ export function canEditDefinition(input) {
121
122
  const { project, definition, member, orgMember, userId, platformPermissions, enablePlatformProjects } = input;
122
123
  if (!project || !definition)
123
124
  return false;
125
+ if (definition.projectId !== project.id)
126
+ return false;
124
127
  if (project.visibility === 'platform') {
125
128
  if (!enablePlatformProjects)
126
129
  return false;
@@ -174,18 +177,12 @@ export function canCreateProject(input) {
174
177
  return false;
175
178
  }
176
179
  /**
177
- * Whether the actor may hand out or take away org `owner`/`admin` standing (§3).
178
- *
179
- * Three routes decide this — inviting someone as owner/admin, changing an
180
- * existing member's role, and removing an owner — and all three had written it
181
- * out longhand. Two of them had drifted by the time the access audit found
182
- * them: the invite route let an admin mint an `owner` invite for themselves,
183
- * and DELETE let an admin remove an owner that PATCH would not let them demote.
184
- * They are one rule, so they read it from one place.
180
+ * Whether the actor may hand out or take away org `owner`/`admin` standing.
181
+ * Inviting someone as owner/admin, changing a member's role and removing an owner
182
+ * are one rule, so all three read it here.
185
183
  *
186
- * Reads the actor's **membership row**. `Organization.ownerId` is a separate
187
- * field that can disagree with it — see the sole-`instance_admin` finding — and
188
- * is not authority for this decision.
184
+ * Reads the actor's **membership row**. `Organization.ownerId` is a separate field
185
+ * that can disagree with it and is not authority for this decision.
189
186
  */
190
187
  export function canChangeOrgRole(input) {
191
188
  if (input.role === 'member')
@@ -202,4 +199,3 @@ export function checkOwnerRemoval(input) {
202
199
  return 'needs_confirm';
203
200
  return 'ok';
204
201
  }
205
- //# sourceMappingURL=rules.js.map
@@ -1,6 +1,5 @@
1
1
  export type { ApiScope, ApiScopeAction, ApiScopeResource, ApiToken, ApiTokenSummary, ApiTokenRevokeReason, ApiTokenLifetimeDays } from './types.js';
2
- export { ApiScopeActionSchema, ApiScopeStringSchema, ApiTokenRevokeReasonSchema, API_TOKEN_LIFETIME_DAYS, parseApiScope, formatApiScope, describeApiScope } from './types.js';
2
+ export { ApiScopeActionSchema, ApiScopeStringSchema, API_TOKEN_LIFETIME_DAYS, parseApiScope, formatApiScope, describeApiScope } from './types.js';
3
3
  export type { IApiTokenStore } from './interface.js';
4
4
  export type { ScopeTarget } from './scope.js';
5
5
  export { scopeAllows, apiScopeActionCovers, narrowApiTokenContext, outsideTokenOrg } from './scope.js';
6
- //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,2 @@
1
- export { ApiScopeActionSchema, ApiScopeStringSchema, ApiTokenRevokeReasonSchema, API_TOKEN_LIFETIME_DAYS, parseApiScope, formatApiScope, describeApiScope } from './types.js';
1
+ export { ApiScopeActionSchema, ApiScopeStringSchema, API_TOKEN_LIFETIME_DAYS, parseApiScope, formatApiScope, describeApiScope } from './types.js';
2
2
  export { scopeAllows, apiScopeActionCovers, narrowApiTokenContext, outsideTokenOrg } from './scope.js';
3
- //# sourceMappingURL=index.js.map
@@ -34,4 +34,3 @@ export interface IApiTokenStore {
34
34
  /** Called from the `deleteOrg` cascade. */
35
35
  deleteByOrg(ctx: RequestContext, orgId: string): Promise<void>;
36
36
  }
37
- //# sourceMappingURL=interface.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=interface.js.map
@@ -34,4 +34,3 @@ export declare function narrowApiTokenContext(ctx: RequestContext, token: Pick<A
34
34
  * not found. Session requests are never outside.
35
35
  */
36
36
  export declare function outsideTokenOrg(ctx: RequestContext, orgId: string): boolean;
37
- //# sourceMappingURL=scope.d.ts.map
@@ -61,4 +61,3 @@ export function narrowApiTokenContext(ctx, token) {
61
61
  export function outsideTokenOrg(ctx, orgId) {
62
62
  return ctx.apiScope !== undefined && orgId !== ctx.actingOrgId;
63
63
  }
64
- //# sourceMappingURL=scope.js.map
@@ -82,4 +82,3 @@ export declare const API_TOKEN_LIFETIME_DAYS: readonly [30, 90, 180];
82
82
  export type ApiTokenLifetimeDays = (typeof API_TOKEN_LIFETIME_DAYS)[number];
83
83
  /** "Read and change everything in the org". Names no ids, so it suits an email. */
84
84
  export declare function describeApiScope(value: string): string;
85
- //# sourceMappingURL=types.d.ts.map
@@ -57,4 +57,3 @@ export function describeApiScope(value) {
57
57
  : 'everything in the org';
58
58
  return `${ACTION_WORDS[scope.action]} ${what}`;
59
59
  }
60
- //# sourceMappingURL=types.js.map
@@ -1,3 +1,2 @@
1
1
  export type { AuthUser, UserManagementResult, LoginResult } from './types.js';
2
2
  export type { IAuthProvider, IOAuthAuth, ISessionRefresh, IPasswordAuth, IEmailLinkAuth, IProxyAuth, IDelegatedSession, DelegatedSessionStatus } from './interface.js';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=index.js.map
@@ -30,9 +30,8 @@ export interface IPasswordAuth {
30
30
  * `getOAuthAuthorizationUrl` and 303s to the result.
31
31
  * 2. IdP redirects back to `/auth/{provider}/callback?code=...` → server
32
32
  * calls `exchangeOAuthCode` to mint the session pair.
33
- * 3. When the access token expires, session-refresh middleware in
34
- * `hooks.server.ts` calls `refreshSession` to swap in a fresh pair
35
- * without bouncing the user.
33
+ *
34
+ * Keeping the session alive afterwards is `ISessionRefresh`'s job.
36
35
  *
37
36
  * Each method returns `null` rather than throwing on irrecoverable
38
37
  * credential failures, so the route layer can pick the right HTTP shape
@@ -64,23 +63,6 @@ export interface IOAuthAuth {
64
63
  sessionToken: string;
65
64
  refreshToken: string;
66
65
  } | null>;
67
- /**
68
- * Swap a refresh token for a fresh access/refresh pair. Returns null on
69
- * irrecoverable failure (revoked, expired, signed by a different secret);
70
- * the session-refresh middleware treats null as "force re-login" by
71
- * clearing the refresh cookie.
72
- *
73
- * @deprecated Session refresh is session lifecycle, not OAuth — a consumer
74
- * that brokers no OAuth still needs it (refreshing a password session, or
75
- * revoking on logout). Moved to `IAuthProvider.sessionRefresh`
76
- * (`ISessionRefresh`). This member delegates for one release and will be
77
- * removed in the next minor; migrate callers to
78
- * `auth.sessionRefresh?.refreshSession(...)`.
79
- */
80
- refreshSession(refreshToken: string): Promise<{
81
- sessionToken: string;
82
- refreshToken: string;
83
- } | null>;
84
66
  }
85
67
  /**
86
68
  * Optional session-lifecycle surface: refresh an expiring session, revoke one
@@ -245,8 +227,8 @@ export interface IDelegatedSession {
245
227
  */
246
228
  status(): Promise<DelegatedSessionStatus>;
247
229
  /**
248
- * A session token acting as `userId` inside `orgId`, valid for about a
249
- * minute. It carries no instance-admin rights, whatever the user holds: those
230
+ * A session token acting as `userId` inside `orgId`, valid for `ttlSeconds`
231
+ * (default 60 s). It carries no instance-admin rights, whatever the user holds: those
250
232
  * never ride on a bearer key. Throws if `status` isn't ok.
251
233
  */
252
234
  mint(userId: string, opts: {
@@ -327,4 +309,3 @@ export interface IAuthProvider {
327
309
  */
328
310
  touchLastLogin?(id: string): Promise<void>;
329
311
  }
330
- //# sourceMappingURL=interface.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=interface.js.map
@@ -21,7 +21,10 @@ export interface AuthUser {
21
21
  * - `last_admin` — refused; would leave zero `instance_admin` users
22
22
  */
23
23
  export type UserManagementResult = 'ok' | 'not_found' | 'not_supported' | 'last_admin';
24
- /** `success` includes the session token the provider minted. */
24
+ /**
25
+ * `success` includes the session token the provider minted. `rate_limited` is for
26
+ * providers that throttle logins; no bundled provider returns it.
27
+ */
25
28
  export type LoginResult = {
26
29
  kind: 'success';
27
30
  user: AuthUser;
@@ -30,4 +33,3 @@ export type LoginResult = {
30
33
  kind: 'failed';
31
34
  reason?: 'invalid_credentials' | 'disabled' | 'rate_limited';
32
35
  };
33
- //# sourceMappingURL=types.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=types.js.map
@@ -1,3 +1,2 @@
1
1
  export type { IBindingResolver } from './interface.js';
2
2
  export { NoopBindingResolver } from './interface.js';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1 @@
1
1
  export { NoopBindingResolver } from './interface.js';
2
- //# sourceMappingURL=index.js.map
@@ -37,4 +37,3 @@ export interface IBindingResolver {
37
37
  export declare class NoopBindingResolver implements IBindingResolver {
38
38
  resolve(): Promise<Map<string, unknown>>;
39
39
  }
40
- //# sourceMappingURL=interface.d.ts.map
@@ -17,4 +17,3 @@ export class NoopBindingResolver {
17
17
  return new Map();
18
18
  }
19
19
  }
20
- //# sourceMappingURL=interface.js.map
@@ -1,8 +1,2 @@
1
- export type { ComputeServerConfig, ComputeConfig, PlatformComputeServer, OrgComputeServer } from './types.js';
2
- export { isPlatformServer, isOrgServer } from './types.js';
3
- export type { IComputeServerStore, GetConfigOptions } from './interface.js';
1
+ export * from './public.js';
4
2
  export { isEncryptedSecret, encryptSecret, decryptSecret, decodeSecretKey } from './secrets.js';
5
- export type { SecretVerificationReport, SecretVerificationFailure, SecretVerificationFailureReason } from './secrets.js';
6
- export type { ResolveOptions } from './utils.js';
7
- export { serversVisibleTo, defaultServerIdFor, resolveServerForOrg, findServerById, platformServers, orgServersFor, scopeConfigToOrg } from './utils.js';
8
- //# sourceMappingURL=index.d.ts.map
@@ -1,9 +1,3 @@
1
- export { isPlatformServer, isOrgServer } from './types.js';
2
- // At-rest secret crypto (uses `node:crypto`). Server-only — deliberately NOT
3
- // re-exported from the root barrel, which client `.svelte` code imports and
4
- // where a browser bundle can't resolve `node:crypto`. Import these from
5
- // `@selvajs/platform/computeServer` in server code. The report TYPES are erased
6
- // at build, so they also stay re-exported from the root barrel for convenience.
1
+ export * from './public.js';
2
+ // Server-only: import these from `@selvajs/platform/computeServer`.
7
3
  export { isEncryptedSecret, encryptSecret, decryptSecret, decodeSecretKey } from './secrets.js';
8
- export { serversVisibleTo, defaultServerIdFor, resolveServerForOrg, findServerById, platformServers, orgServersFor, scopeConfigToOrg } from './utils.js';
9
- //# sourceMappingURL=index.js.map
@@ -28,10 +28,9 @@ export interface GetConfigOptions {
28
28
  *
29
29
  * Org-facing surfaces must pass this. Without it the store hands back every
30
30
  * platform server and every *other* org's private servers, leaving each
31
- * caller to re-apply a filter it can silently forget — the global
32
- * `defaultServerId` in particular passed straight through before this
33
- * existed. Admin surfaces, boot health and the save-diff handlers omit it
34
- * deliberately; they act instance-wide.
31
+ * caller to re-apply a filter it can silently forget. Admin surfaces, boot
32
+ * health and the save-diff handlers omit it deliberately; they act
33
+ * instance-wide.
35
34
  */
36
35
  scopeToOrgId?: string;
37
36
  }
@@ -53,6 +52,10 @@ export interface IComputeServerStore {
53
52
  * authenticated under the current `SELVA_AT_REST_KEY` (the solve fails
54
53
  * later at Rhino.Compute rather than here).
55
54
  *
55
+ * A plaintext key at rest is adapter-defined: local throws, because it never
56
+ * writes one, and Supabase warns and returns it, because rows can predate
57
+ * encryption. Both report it through `verifySecrets`.
58
+ *
56
59
  * The narrow path onto the solve hot path: resolve a server from the
57
60
  * key-free config, then fetch that one key.
58
61
  */
@@ -61,7 +64,8 @@ export interface IComputeServerStore {
61
64
  savePlatformServers(ctx: RequestContext, servers: ComputeServerConfig[], defaultServerId: string | undefined): Promise<void>;
62
65
  /**
63
66
  * Replaces the org-private server set for `orgId` and (optionally) updates
64
- * `orgDefaults[orgId]`. Platform rows are left untouched.
67
+ * `orgDefaults[orgId]`. Platform rows are left untouched, and any non-org entry
68
+ * in `servers` is ignored.
65
69
  *
66
70
  * `defaultServerId: null` clears the org's override; `undefined` leaves
67
71
  * the existing value alone.
@@ -90,4 +94,3 @@ export interface IComputeServerStore {
90
94
  */
91
95
  verifySecrets?(): Promise<SecretVerificationReport>;
92
96
  }
93
- //# sourceMappingURL=interface.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=interface.js.map
@@ -0,0 +1,6 @@
1
+ export type { ComputeServerConfig, ComputeConfig, PlatformComputeServer, OrgComputeServer } from './types.js';
2
+ export { COMPUTE_RETRY_MAX, COMPUTE_TIMEOUT_DEFAULT_MS, COMPUTE_TIMEOUT_MAX_MS, COMPUTE_TIMEOUT_MIN_MS, isPlatformServer, isOrgServer } from './types.js';
3
+ export type { IComputeServerStore, GetConfigOptions } from './interface.js';
4
+ export type { ResolveOptions } from './utils.js';
5
+ export { serversVisibleTo, defaultServerIdFor, clampRetryCount, clampTimeoutMs, pickServer, resolveServerForOrg, findServerById, platformServers, scopeConfigToOrg } from './utils.js';
6
+ export type { SecretVerificationReport, SecretVerificationFailure, SecretVerificationFailureReason } from './secrets.js';
@@ -0,0 +1,2 @@
1
+ export { COMPUTE_RETRY_MAX, COMPUTE_TIMEOUT_DEFAULT_MS, COMPUTE_TIMEOUT_MAX_MS, COMPUTE_TIMEOUT_MIN_MS, isPlatformServer, isOrgServer } from './types.js';
2
+ export { serversVisibleTo, defaultServerIdFor, clampRetryCount, clampTimeoutMs, pickServer, resolveServerForOrg, findServerById, platformServers, scopeConfigToOrg } from './utils.js';
@@ -1,8 +1,12 @@
1
+ export declare function isEncryptedSecret(value: string): boolean;
1
2
  /**
2
3
  * AES-256-GCM envelope for at-rest secrets (e.g. compute API keys).
3
4
  *
4
- * Wire format: `enc:v1:<base64(iv|tag|ciphertext)>`. The version prefix lets
5
- * a future migration to a new algorithm tell old and new envelopes apart.
5
+ * Wire format: `enc:v2:<base64(iv|tag|ciphertext)>`. `v2` authenticates the
6
+ * record the secret belongs to as AAD, so ciphertext copied onto another record
7
+ * fails to decrypt instead of handing the plaintext to that record's URL.
8
+ * `enc:v1:` envelopes have no AAD and still read: a store rewrites them as `v2`
9
+ * on its next save.
6
10
  *
7
11
  * Defends against backup leaks, accidental file/DB sharing, and read-only
8
12
  * storage access — not against an attacker who also has the master key
@@ -11,10 +15,13 @@
11
15
  *
12
16
  * Pure `node:crypto`, no fs or DB coupling, so the local and Supabase
13
17
  * compute-server stores share this one implementation.
18
+ *
19
+ * Pass `aad` to bind the secret to its record. {@link decryptSecret} then needs the
20
+ * same value.
14
21
  */
15
- export declare function isEncryptedSecret(value: string): boolean;
16
- export declare function encryptSecret(plaintext: string, key: Buffer): string;
17
- export declare function decryptSecret(envelope: string, key: Buffer): string;
22
+ export declare function encryptSecret(plaintext: string, key: Buffer, aad?: string): string;
23
+ /** `aad` is ignored for an `enc:v1:` envelope and required for an `enc:v2:` one. */
24
+ export declare function decryptSecret(envelope: string, key: Buffer, aad?: string): string;
18
25
  /**
19
26
  * Decodes a `SELVA_AT_REST_KEY` env var into a 32-byte buffer (64-char hex or
20
27
  * base64, with or without padding). Throws on anything else so a
@@ -46,4 +53,3 @@ export interface SecretVerificationReport {
46
53
  /** True if at least one row holds an unencrypted apiKey at rest. */
47
54
  plaintextFound: boolean;
48
55
  }
49
- //# sourceMappingURL=secrets.d.ts.map
@@ -2,12 +2,19 @@ import { randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';
2
2
  const ALGO = 'aes-256-gcm';
3
3
  const IV_BYTES = 12;
4
4
  const TAG_BYTES = 16;
5
- const PREFIX = 'enc:v1:';
5
+ const PREFIX_V1 = 'enc:v1:';
6
+ const PREFIX_V2 = 'enc:v2:';
7
+ export function isEncryptedSecret(value) {
8
+ return value.startsWith(PREFIX_V1) || value.startsWith(PREFIX_V2);
9
+ }
6
10
  /**
7
11
  * AES-256-GCM envelope for at-rest secrets (e.g. compute API keys).
8
12
  *
9
- * Wire format: `enc:v1:<base64(iv|tag|ciphertext)>`. The version prefix lets
10
- * a future migration to a new algorithm tell old and new envelopes apart.
13
+ * Wire format: `enc:v2:<base64(iv|tag|ciphertext)>`. `v2` authenticates the
14
+ * record the secret belongs to as AAD, so ciphertext copied onto another record
15
+ * fails to decrypt instead of handing the plaintext to that record's URL.
16
+ * `enc:v1:` envelopes have no AAD and still read: a store rewrites them as `v2`
17
+ * on its next save.
11
18
  *
12
19
  * Defends against backup leaks, accidental file/DB sharing, and read-only
13
20
  * storage access — not against an attacker who also has the master key
@@ -16,11 +23,11 @@ const PREFIX = 'enc:v1:';
16
23
  *
17
24
  * Pure `node:crypto`, no fs or DB coupling, so the local and Supabase
18
25
  * compute-server stores share this one implementation.
26
+ *
27
+ * Pass `aad` to bind the secret to its record. {@link decryptSecret} then needs the
28
+ * same value.
19
29
  */
20
- export function isEncryptedSecret(value) {
21
- return value.startsWith(PREFIX);
22
- }
23
- export function encryptSecret(plaintext, key) {
30
+ export function encryptSecret(plaintext, key, aad) {
24
31
  if (key.length !== 32) {
25
32
  throw new Error(`Secret key must be 32 bytes; got ${key.length}`);
26
33
  }
@@ -29,23 +36,33 @@ export function encryptSecret(plaintext, key) {
29
36
  }
30
37
  const iv = randomBytes(IV_BYTES);
31
38
  const cipher = createCipheriv(ALGO, key, iv);
39
+ if (aad !== undefined)
40
+ cipher.setAAD(Buffer.from(aad, 'utf8'));
32
41
  const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
33
42
  const tag = cipher.getAuthTag();
34
- return PREFIX + Buffer.concat([iv, tag, ciphertext]).toString('base64');
43
+ const prefix = aad === undefined ? PREFIX_V1 : PREFIX_V2;
44
+ return prefix + Buffer.concat([iv, tag, ciphertext]).toString('base64');
35
45
  }
36
- export function decryptSecret(envelope, key) {
46
+ /** `aad` is ignored for an `enc:v1:` envelope and required for an `enc:v2:` one. */
47
+ export function decryptSecret(envelope, key, aad) {
37
48
  if (key.length !== 32) {
38
49
  throw new Error(`Secret key must be 32 bytes; got ${key.length}`);
39
50
  }
40
51
  if (!isEncryptedSecret(envelope)) {
41
52
  throw new Error('Value is not an encrypted secret envelope');
42
53
  }
43
- const buf = Buffer.from(envelope.slice(PREFIX.length), 'base64');
54
+ const bound = envelope.startsWith(PREFIX_V2);
55
+ if (bound && aad === undefined) {
56
+ throw new Error('This secret is bound to a record: pass its id');
57
+ }
58
+ const buf = Buffer.from(envelope.slice(PREFIX_V2.length), 'base64');
44
59
  const iv = buf.subarray(0, IV_BYTES);
45
60
  const tag = buf.subarray(IV_BYTES, IV_BYTES + TAG_BYTES);
46
61
  const ciphertext = buf.subarray(IV_BYTES + TAG_BYTES);
47
62
  const decipher = createDecipheriv(ALGO, key, iv);
48
63
  decipher.setAuthTag(tag);
64
+ if (bound)
65
+ decipher.setAAD(Buffer.from(aad, 'utf8'));
49
66
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
50
67
  }
51
68
  /**
@@ -55,12 +72,15 @@ export function decryptSecret(envelope, key) {
55
72
  * ciphertext nothing can decrypt.
56
73
  */
57
74
  export function decodeSecretKey(raw) {
58
- if (/^[0-9a-fA-F]{64}$/.test(raw))
59
- return Buffer.from(raw, 'hex');
60
- const buf = Buffer.from(raw, 'base64');
61
- if (buf.length === 32)
75
+ const key = raw.trim();
76
+ if (/^[0-9a-fA-F]{64}$/.test(key))
77
+ return Buffer.from(key, 'hex');
78
+ // `Buffer.from` skips what it can't decode, so only a round trip proves the
79
+ // whole string was base64 rather than 32 bytes of it.
80
+ const buf = Buffer.from(key, 'base64');
81
+ if (buf.length === 32 && buf.toString('base64').replace(/=+$/, '') === key.replace(/=+$/, '')) {
62
82
  return buf;
83
+ }
63
84
  throw new Error('SELVA_AT_REST_KEY must be 32 bytes encoded as 64-char hex or base64. ' +
64
85
  "Generate one with: node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"");
65
86
  }
66
- //# sourceMappingURL=secrets.js.map
@@ -1,3 +1,7 @@
1
+ export declare const COMPUTE_TIMEOUT_DEFAULT_MS = 30000;
2
+ export declare const COMPUTE_TIMEOUT_MIN_MS = 1000;
3
+ export declare const COMPUTE_TIMEOUT_MAX_MS = 300000;
4
+ export declare const COMPUTE_RETRY_MAX = 5;
1
5
  /**
2
6
  * Compute servers belong to one of two scopes — never both.
3
7
  *
@@ -29,7 +33,7 @@ interface ComputeServerCommon {
29
33
  * they'd only throw away.
30
34
  */
31
35
  hasApiKey?: boolean;
32
- /** Default: 30000. */
36
+ /** Default: {@link COMPUTE_TIMEOUT_DEFAULT_MS}. */
33
37
  timeoutMs?: number;
34
38
  /** Default: 0. */
35
39
  retryCount?: number;
@@ -68,4 +72,3 @@ export interface ComputeConfig {
68
72
  export declare function isPlatformServer(s: ComputeServerConfig): s is PlatformComputeServer;
69
73
  export declare function isOrgServer(s: ComputeServerConfig): s is OrgComputeServer;
70
74
  export {};
71
- //# sourceMappingURL=types.d.ts.map
@@ -1,3 +1,7 @@
1
+ export const COMPUTE_TIMEOUT_DEFAULT_MS = 30000;
2
+ export const COMPUTE_TIMEOUT_MIN_MS = 1000;
3
+ export const COMPUTE_TIMEOUT_MAX_MS = 300000;
4
+ export const COMPUTE_RETRY_MAX = 5;
1
5
  // ============================================================================
2
6
  // Type guards
3
7
  // ============================================================================
@@ -7,4 +11,3 @@ export function isPlatformServer(s) {
7
11
  export function isOrgServer(s) {
8
12
  return s.scope === 'org';
9
13
  }
10
- //# sourceMappingURL=types.js.map