@antasphere/chassis-server 1.0.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 (798) hide show
  1. package/LICENSE +82 -0
  2. package/dist/accounts/deletion.d.ts +163 -0
  3. package/dist/accounts/deletion.d.ts.map +1 -0
  4. package/dist/accounts/deletion.js +360 -0
  5. package/dist/accounts/deletion.js.map +1 -0
  6. package/dist/accounts/erasure-log.d.ts +66 -0
  7. package/dist/accounts/erasure-log.d.ts.map +1 -0
  8. package/dist/accounts/erasure-log.js +117 -0
  9. package/dist/accounts/erasure-log.js.map +1 -0
  10. package/dist/accounts/index.d.ts +6 -0
  11. package/dist/accounts/index.d.ts.map +1 -0
  12. package/dist/accounts/index.js +6 -0
  13. package/dist/accounts/index.js.map +1 -0
  14. package/dist/accounts/mint-refusal.d.ts +47 -0
  15. package/dist/accounts/mint-refusal.d.ts.map +1 -0
  16. package/dist/accounts/mint-refusal.js +59 -0
  17. package/dist/accounts/mint-refusal.js.map +1 -0
  18. package/dist/accounts/signup-duplicate.d.ts +14 -0
  19. package/dist/accounts/signup-duplicate.d.ts.map +1 -0
  20. package/dist/accounts/signup-duplicate.js +25 -0
  21. package/dist/accounts/signup-duplicate.js.map +1 -0
  22. package/dist/accounts/superadmin.d.ts +27 -0
  23. package/dist/accounts/superadmin.d.ts.map +1 -0
  24. package/dist/accounts/superadmin.js +46 -0
  25. package/dist/accounts/superadmin.js.map +1 -0
  26. package/dist/api/apikeys.d.ts +14 -0
  27. package/dist/api/apikeys.d.ts.map +1 -0
  28. package/dist/api/apikeys.js +124 -0
  29. package/dist/api/apikeys.js.map +1 -0
  30. package/dist/api/audit.d.ts +4 -0
  31. package/dist/api/audit.d.ts.map +1 -0
  32. package/dist/api/audit.js +75 -0
  33. package/dist/api/audit.js.map +1 -0
  34. package/dist/api/break-glass.d.ts +48 -0
  35. package/dist/api/break-glass.d.ts.map +1 -0
  36. package/dist/api/break-glass.js +217 -0
  37. package/dist/api/break-glass.js.map +1 -0
  38. package/dist/api/cli-auth.d.ts +84 -0
  39. package/dist/api/cli-auth.d.ts.map +1 -0
  40. package/dist/api/cli-auth.js +177 -0
  41. package/dist/api/cli-auth.js.map +1 -0
  42. package/dist/api/create-api.d.ts +123 -0
  43. package/dist/api/create-api.d.ts.map +1 -0
  44. package/dist/api/create-api.js +1165 -0
  45. package/dist/api/create-api.js.map +1 -0
  46. package/dist/api/demo-passes.d.ts +45 -0
  47. package/dist/api/demo-passes.d.ts.map +1 -0
  48. package/dist/api/demo-passes.js +143 -0
  49. package/dist/api/demo-passes.js.map +1 -0
  50. package/dist/api/errors.d.ts +5 -0
  51. package/dist/api/errors.d.ts.map +1 -0
  52. package/dist/api/errors.js +5 -0
  53. package/dist/api/errors.js.map +1 -0
  54. package/dist/api/export.d.ts +63 -0
  55. package/dist/api/export.d.ts.map +1 -0
  56. package/dist/api/export.js +393 -0
  57. package/dist/api/export.js.map +1 -0
  58. package/dist/api/files.d.ts +40 -0
  59. package/dist/api/files.d.ts.map +1 -0
  60. package/dist/api/files.js +150 -0
  61. package/dist/api/files.js.map +1 -0
  62. package/dist/api/index.d.ts +17 -0
  63. package/dist/api/index.d.ts.map +1 -0
  64. package/dist/api/index.js +17 -0
  65. package/dist/api/index.js.map +1 -0
  66. package/dist/api/invitations.d.ts +50 -0
  67. package/dist/api/invitations.d.ts.map +1 -0
  68. package/dist/api/invitations.js +313 -0
  69. package/dist/api/invitations.js.map +1 -0
  70. package/dist/api/members.d.ts +25 -0
  71. package/dist/api/members.d.ts.map +1 -0
  72. package/dist/api/members.js +522 -0
  73. package/dist/api/members.js.map +1 -0
  74. package/dist/api/onboarding.d.ts +26 -0
  75. package/dist/api/onboarding.d.ts.map +1 -0
  76. package/dist/api/onboarding.js +23 -0
  77. package/dist/api/onboarding.js.map +1 -0
  78. package/dist/api/openapi-doc.d.ts +21 -0
  79. package/dist/api/openapi-doc.d.ts.map +1 -0
  80. package/dist/api/openapi-doc.js +25 -0
  81. package/dist/api/openapi-doc.js.map +1 -0
  82. package/dist/api/projects.d.ts +24 -0
  83. package/dist/api/projects.d.ts.map +1 -0
  84. package/dist/api/projects.js +624 -0
  85. package/dist/api/projects.js.map +1 -0
  86. package/dist/api/scope-routes.d.ts +12 -0
  87. package/dist/api/scope-routes.d.ts.map +1 -0
  88. package/dist/api/scope-routes.js +2 -0
  89. package/dist/api/scope-routes.js.map +1 -0
  90. package/dist/api/sso-connect.d.ts +70 -0
  91. package/dist/api/sso-connect.d.ts.map +1 -0
  92. package/dist/api/sso-connect.js +164 -0
  93. package/dist/api/sso-connect.js.map +1 -0
  94. package/dist/api/sso-logout.d.ts +41 -0
  95. package/dist/api/sso-logout.d.ts.map +1 -0
  96. package/dist/api/sso-logout.js +34 -0
  97. package/dist/api/sso-logout.js.map +1 -0
  98. package/dist/api/teams.d.ts +43 -0
  99. package/dist/api/teams.d.ts.map +1 -0
  100. package/dist/api/teams.js +447 -0
  101. package/dist/api/teams.js.map +1 -0
  102. package/dist/api/workspaces.d.ts +114 -0
  103. package/dist/api/workspaces.d.ts.map +1 -0
  104. package/dist/api/workspaces.js +462 -0
  105. package/dist/api/workspaces.js.map +1 -0
  106. package/dist/apikeys/index.d.ts +3 -0
  107. package/dist/apikeys/index.d.ts.map +1 -0
  108. package/dist/apikeys/index.js +3 -0
  109. package/dist/apikeys/index.js.map +1 -0
  110. package/dist/apikeys/peppers.d.ts +53 -0
  111. package/dist/apikeys/peppers.d.ts.map +1 -0
  112. package/dist/apikeys/peppers.js +97 -0
  113. package/dist/apikeys/peppers.js.map +1 -0
  114. package/dist/apikeys/service.d.ts +56 -0
  115. package/dist/apikeys/service.d.ts.map +1 -0
  116. package/dist/apikeys/service.js +196 -0
  117. package/dist/apikeys/service.js.map +1 -0
  118. package/dist/app.d.ts +65 -0
  119. package/dist/app.d.ts.map +1 -0
  120. package/dist/app.js +142 -0
  121. package/dist/app.js.map +1 -0
  122. package/dist/audit/filters.d.ts +38 -0
  123. package/dist/audit/filters.d.ts.map +1 -0
  124. package/dist/audit/filters.js +97 -0
  125. package/dist/audit/filters.js.map +1 -0
  126. package/dist/audit/index.d.ts +3 -0
  127. package/dist/audit/index.d.ts.map +1 -0
  128. package/dist/audit/index.js +3 -0
  129. package/dist/audit/index.js.map +1 -0
  130. package/dist/audit/service.d.ts +65 -0
  131. package/dist/audit/service.d.ts.map +1 -0
  132. package/dist/audit/service.js +103 -0
  133. package/dist/audit/service.js.map +1 -0
  134. package/dist/boot.d.ts +8 -0
  135. package/dist/boot.d.ts.map +1 -0
  136. package/dist/boot.js +822 -0
  137. package/dist/boot.js.map +1 -0
  138. package/dist/constant-time.d.ts +3 -0
  139. package/dist/constant-time.d.ts.map +1 -0
  140. package/dist/constant-time.js +10 -0
  141. package/dist/constant-time.js.map +1 -0
  142. package/dist/email/driver.d.ts +16 -0
  143. package/dist/email/driver.d.ts.map +1 -0
  144. package/dist/email/driver.js +136 -0
  145. package/dist/email/driver.js.map +1 -0
  146. package/dist/email/index.d.ts +4 -0
  147. package/dist/email/index.d.ts.map +1 -0
  148. package/dist/email/index.js +4 -0
  149. package/dist/email/index.js.map +1 -0
  150. package/dist/email/shell.d.ts +72 -0
  151. package/dist/email/shell.d.ts.map +1 -0
  152. package/dist/email/shell.js +146 -0
  153. package/dist/email/shell.js.map +1 -0
  154. package/dist/email/templates.d.ts +83 -0
  155. package/dist/email/templates.d.ts.map +1 -0
  156. package/dist/email/templates.js +96 -0
  157. package/dist/email/templates.js.map +1 -0
  158. package/dist/entitlements/check.d.ts +140 -0
  159. package/dist/entitlements/check.d.ts.map +1 -0
  160. package/dist/entitlements/check.js +276 -0
  161. package/dist/entitlements/check.js.map +1 -0
  162. package/dist/entitlements/clock-skew.d.ts +27 -0
  163. package/dist/entitlements/clock-skew.d.ts.map +1 -0
  164. package/dist/entitlements/clock-skew.js +58 -0
  165. package/dist/entitlements/clock-skew.js.map +1 -0
  166. package/dist/entitlements/gate.d.ts +113 -0
  167. package/dist/entitlements/gate.d.ts.map +1 -0
  168. package/dist/entitlements/gate.js +430 -0
  169. package/dist/entitlements/gate.js.map +1 -0
  170. package/dist/entitlements/hub-machine-token.d.ts +83 -0
  171. package/dist/entitlements/hub-machine-token.d.ts.map +1 -0
  172. package/dist/entitlements/hub-machine-token.js +175 -0
  173. package/dist/entitlements/hub-machine-token.js.map +1 -0
  174. package/dist/entitlements/hub-subject.d.ts +32 -0
  175. package/dist/entitlements/hub-subject.d.ts.map +1 -0
  176. package/dist/entitlements/hub-subject.js +68 -0
  177. package/dist/entitlements/hub-subject.js.map +1 -0
  178. package/dist/entitlements/index.d.ts +9 -0
  179. package/dist/entitlements/index.d.ts.map +1 -0
  180. package/dist/entitlements/index.js +9 -0
  181. package/dist/entitlements/index.js.map +1 -0
  182. package/dist/entitlements/poster.d.ts +81 -0
  183. package/dist/entitlements/poster.d.ts.map +1 -0
  184. package/dist/entitlements/poster.js +169 -0
  185. package/dist/entitlements/poster.js.map +1 -0
  186. package/dist/entitlements/profiles.d.ts +121 -0
  187. package/dist/entitlements/profiles.d.ts.map +1 -0
  188. package/dist/entitlements/profiles.js +212 -0
  189. package/dist/entitlements/profiles.js.map +1 -0
  190. package/dist/entitlements/slot.d.ts +33 -0
  191. package/dist/entitlements/slot.d.ts.map +1 -0
  192. package/dist/entitlements/slot.js +75 -0
  193. package/dist/entitlements/slot.js.map +1 -0
  194. package/dist/env.d.ts +293 -0
  195. package/dist/env.d.ts.map +1 -0
  196. package/dist/env.js +383 -0
  197. package/dist/env.js.map +1 -0
  198. package/dist/files/http.d.ts +23 -0
  199. package/dist/files/http.d.ts.map +1 -0
  200. package/dist/files/http.js +66 -0
  201. package/dist/files/http.js.map +1 -0
  202. package/dist/files/index.d.ts +5 -0
  203. package/dist/files/index.d.ts.map +1 -0
  204. package/dist/files/index.js +5 -0
  205. package/dist/files/index.js.map +1 -0
  206. package/dist/files/serve.d.ts +43 -0
  207. package/dist/files/serve.d.ts.map +1 -0
  208. package/dist/files/serve.js +71 -0
  209. package/dist/files/serve.js.map +1 -0
  210. package/dist/files/service.d.ts +69 -0
  211. package/dist/files/service.d.ts.map +1 -0
  212. package/dist/files/service.js +188 -0
  213. package/dist/files/service.js.map +1 -0
  214. package/dist/files/spool.d.ts +21 -0
  215. package/dist/files/spool.d.ts.map +1 -0
  216. package/dist/files/spool.js +47 -0
  217. package/dist/files/spool.js.map +1 -0
  218. package/dist/identity/better-auth.d.ts +3927 -0
  219. package/dist/identity/better-auth.d.ts.map +1 -0
  220. package/dist/identity/better-auth.js +896 -0
  221. package/dist/identity/better-auth.js.map +1 -0
  222. package/dist/identity/demo-pass-plugin.d.ts +29 -0
  223. package/dist/identity/demo-pass-plugin.d.ts.map +1 -0
  224. package/dist/identity/demo-pass-plugin.js +88 -0
  225. package/dist/identity/demo-pass-plugin.js.map +1 -0
  226. package/dist/identity/demo-pass-rules.d.ts +84 -0
  227. package/dist/identity/demo-pass-rules.d.ts.map +1 -0
  228. package/dist/identity/demo-pass-rules.js +223 -0
  229. package/dist/identity/demo-pass-rules.js.map +1 -0
  230. package/dist/identity/demo-pass.d.ts +159 -0
  231. package/dist/identity/demo-pass.d.ts.map +1 -0
  232. package/dist/identity/demo-pass.js +319 -0
  233. package/dist/identity/demo-pass.js.map +1 -0
  234. package/dist/identity/hub-grant.d.ts +182 -0
  235. package/dist/identity/hub-grant.d.ts.map +1 -0
  236. package/dist/identity/hub-grant.js +594 -0
  237. package/dist/identity/hub-grant.js.map +1 -0
  238. package/dist/identity/hub-jwt.d.ts +22 -0
  239. package/dist/identity/hub-jwt.d.ts.map +1 -0
  240. package/dist/identity/hub-jwt.js +77 -0
  241. package/dist/identity/hub-jwt.js.map +1 -0
  242. package/dist/identity/hub-live-gate.d.ts +17 -0
  243. package/dist/identity/hub-live-gate.d.ts.map +1 -0
  244. package/dist/identity/hub-live-gate.js +146 -0
  245. package/dist/identity/hub-live-gate.js.map +1 -0
  246. package/dist/identity/hub-logout.d.ts +88 -0
  247. package/dist/identity/hub-logout.d.ts.map +1 -0
  248. package/dist/identity/hub-logout.js +158 -0
  249. package/dist/identity/hub-logout.js.map +1 -0
  250. package/dist/identity/hub-projection.d.ts +103 -0
  251. package/dist/identity/hub-projection.d.ts.map +1 -0
  252. package/dist/identity/hub-projection.js +130 -0
  253. package/dist/identity/hub-projection.js.map +1 -0
  254. package/dist/identity/hub-reconcile.d.ts +151 -0
  255. package/dist/identity/hub-reconcile.d.ts.map +1 -0
  256. package/dist/identity/hub-reconcile.js +400 -0
  257. package/dist/identity/hub-reconcile.js.map +1 -0
  258. package/dist/identity/hub-sso.d.ts +363 -0
  259. package/dist/identity/hub-sso.d.ts.map +1 -0
  260. package/dist/identity/hub-sso.js +536 -0
  261. package/dist/identity/hub-sso.js.map +1 -0
  262. package/dist/identity/hub-user-client.d.ts +222 -0
  263. package/dist/identity/hub-user-client.d.ts.map +1 -0
  264. package/dist/identity/hub-user-client.js +406 -0
  265. package/dist/identity/hub-user-client.js.map +1 -0
  266. package/dist/identity/index.d.ts +13 -0
  267. package/dist/identity/index.d.ts.map +1 -0
  268. package/dist/identity/index.js +13 -0
  269. package/dist/identity/index.js.map +1 -0
  270. package/dist/identity/oauth-jwt.d.ts +32 -0
  271. package/dist/identity/oauth-jwt.d.ts.map +1 -0
  272. package/dist/identity/oauth-jwt.js +122 -0
  273. package/dist/identity/oauth-jwt.js.map +1 -0
  274. package/dist/identity/resolve-membership.d.ts +28 -0
  275. package/dist/identity/resolve-membership.d.ts.map +1 -0
  276. package/dist/identity/resolve-membership.js +69 -0
  277. package/dist/identity/resolve-membership.js.map +1 -0
  278. package/dist/identity/signing-key.d.ts +49 -0
  279. package/dist/identity/signing-key.d.ts.map +1 -0
  280. package/dist/identity/signing-key.js +144 -0
  281. package/dist/identity/signing-key.js.map +1 -0
  282. package/dist/index.d.ts +21 -0
  283. package/dist/index.d.ts.map +1 -0
  284. package/dist/index.js +11 -0
  285. package/dist/index.js.map +1 -0
  286. package/dist/invitations/index.d.ts +2 -0
  287. package/dist/invitations/index.d.ts.map +1 -0
  288. package/dist/invitations/index.js +2 -0
  289. package/dist/invitations/index.js.map +1 -0
  290. package/dist/invitations/service.d.ts +36 -0
  291. package/dist/invitations/service.d.ts.map +1 -0
  292. package/dist/invitations/service.js +122 -0
  293. package/dist/invitations/service.js.map +1 -0
  294. package/dist/jobs/index.d.ts +3 -0
  295. package/dist/jobs/index.d.ts.map +1 -0
  296. package/dist/jobs/index.js +3 -0
  297. package/dist/jobs/index.js.map +1 -0
  298. package/dist/jobs/pgboss.d.ts +119 -0
  299. package/dist/jobs/pgboss.d.ts.map +1 -0
  300. package/dist/jobs/pgboss.js +521 -0
  301. package/dist/jobs/pgboss.js.map +1 -0
  302. package/dist/jobs/timers.d.ts +127 -0
  303. package/dist/jobs/timers.d.ts.map +1 -0
  304. package/dist/jobs/timers.js +228 -0
  305. package/dist/jobs/timers.js.map +1 -0
  306. package/dist/logger.d.ts +17 -0
  307. package/dist/logger.d.ts.map +1 -0
  308. package/dist/logger.js +64 -0
  309. package/dist/logger.js.map +1 -0
  310. package/dist/mcp/errors.d.ts +37 -0
  311. package/dist/mcp/errors.d.ts.map +1 -0
  312. package/dist/mcp/errors.js +77 -0
  313. package/dist/mcp/errors.js.map +1 -0
  314. package/dist/mcp/http.d.ts +35 -0
  315. package/dist/mcp/http.d.ts.map +1 -0
  316. package/dist/mcp/http.js +80 -0
  317. package/dist/mcp/http.js.map +1 -0
  318. package/dist/mcp/index.d.ts +8 -0
  319. package/dist/mcp/index.d.ts.map +1 -0
  320. package/dist/mcp/index.js +8 -0
  321. package/dist/mcp/index.js.map +1 -0
  322. package/dist/mcp/inputs.d.ts +19 -0
  323. package/dist/mcp/inputs.d.ts.map +1 -0
  324. package/dist/mcp/inputs.js +28 -0
  325. package/dist/mcp/inputs.js.map +1 -0
  326. package/dist/mcp/projects.d.ts +31 -0
  327. package/dist/mcp/projects.d.ts.map +1 -0
  328. package/dist/mcp/projects.js +315 -0
  329. package/dist/mcp/projects.js.map +1 -0
  330. package/dist/mcp/server.d.ts +52 -0
  331. package/dist/mcp/server.d.ts.map +1 -0
  332. package/dist/mcp/server.js +93 -0
  333. package/dist/mcp/server.js.map +1 -0
  334. package/dist/mcp/teams.d.ts +7 -0
  335. package/dist/mcp/teams.d.ts.map +1 -0
  336. package/dist/mcp/teams.js +62 -0
  337. package/dist/mcp/teams.js.map +1 -0
  338. package/dist/mcp/tool-kit.d.ts +67 -0
  339. package/dist/mcp/tool-kit.d.ts.map +1 -0
  340. package/dist/mcp/tool-kit.js +74 -0
  341. package/dist/mcp/tool-kit.js.map +1 -0
  342. package/dist/members/removal.d.ts +95 -0
  343. package/dist/members/removal.d.ts.map +1 -0
  344. package/dist/members/removal.js +143 -0
  345. package/dist/members/removal.js.map +1 -0
  346. package/dist/middleware/auth-body.d.ts +25 -0
  347. package/dist/middleware/auth-body.d.ts.map +1 -0
  348. package/dist/middleware/auth-body.js +55 -0
  349. package/dist/middleware/auth-body.js.map +1 -0
  350. package/dist/middleware/auth-context.d.ts +170 -0
  351. package/dist/middleware/auth-context.d.ts.map +1 -0
  352. package/dist/middleware/auth-context.js +251 -0
  353. package/dist/middleware/auth-context.js.map +1 -0
  354. package/dist/middleware/body-cap.d.ts +17 -0
  355. package/dist/middleware/body-cap.d.ts.map +1 -0
  356. package/dist/middleware/body-cap.js +31 -0
  357. package/dist/middleware/body-cap.js.map +1 -0
  358. package/dist/middleware/body-refusal.d.ts +20 -0
  359. package/dist/middleware/body-refusal.d.ts.map +1 -0
  360. package/dist/middleware/body-refusal.js +5 -0
  361. package/dist/middleware/body-refusal.js.map +1 -0
  362. package/dist/middleware/cross-site.d.ts +61 -0
  363. package/dist/middleware/cross-site.d.ts.map +1 -0
  364. package/dist/middleware/cross-site.js +59 -0
  365. package/dist/middleware/cross-site.js.map +1 -0
  366. package/dist/middleware/hub-managed.d.ts +45 -0
  367. package/dist/middleware/hub-managed.d.ts.map +1 -0
  368. package/dist/middleware/hub-managed.js +56 -0
  369. package/dist/middleware/hub-managed.js.map +1 -0
  370. package/dist/middleware/idempotency.d.ts +14 -0
  371. package/dist/middleware/idempotency.d.ts.map +1 -0
  372. package/dist/middleware/idempotency.js +222 -0
  373. package/dist/middleware/idempotency.js.map +1 -0
  374. package/dist/middleware/index.d.ts +12 -0
  375. package/dist/middleware/index.d.ts.map +1 -0
  376. package/dist/middleware/index.js +12 -0
  377. package/dist/middleware/index.js.map +1 -0
  378. package/dist/middleware/json-depth.d.ts +32 -0
  379. package/dist/middleware/json-depth.d.ts.map +1 -0
  380. package/dist/middleware/json-depth.js +80 -0
  381. package/dist/middleware/json-depth.js.map +1 -0
  382. package/dist/middleware/no-store.d.ts +21 -0
  383. package/dist/middleware/no-store.d.ts.map +1 -0
  384. package/dist/middleware/no-store.js +32 -0
  385. package/dist/middleware/no-store.js.map +1 -0
  386. package/dist/middleware/oauth-public.d.ts +16 -0
  387. package/dist/middleware/oauth-public.d.ts.map +1 -0
  388. package/dist/middleware/oauth-public.js +50 -0
  389. package/dist/middleware/oauth-public.js.map +1 -0
  390. package/dist/middleware/rate-limit.d.ts +182 -0
  391. package/dist/middleware/rate-limit.d.ts.map +1 -0
  392. package/dist/middleware/rate-limit.js +277 -0
  393. package/dist/middleware/rate-limit.js.map +1 -0
  394. package/dist/middleware/request-id.d.ts +10 -0
  395. package/dist/middleware/request-id.d.ts.map +1 -0
  396. package/dist/middleware/request-id.js +42 -0
  397. package/dist/middleware/request-id.js.map +1 -0
  398. package/dist/middleware/scopes.d.ts +33 -0
  399. package/dist/middleware/scopes.d.ts.map +1 -0
  400. package/dist/middleware/scopes.js +139 -0
  401. package/dist/middleware/scopes.js.map +1 -0
  402. package/dist/middleware/security-headers.d.ts +53 -0
  403. package/dist/middleware/security-headers.d.ts.map +1 -0
  404. package/dist/middleware/security-headers.js +96 -0
  405. package/dist/middleware/security-headers.js.map +1 -0
  406. package/dist/observability/index.d.ts +3 -0
  407. package/dist/observability/index.d.ts.map +1 -0
  408. package/dist/observability/index.js +3 -0
  409. package/dist/observability/index.js.map +1 -0
  410. package/dist/observability/metrics.d.ts +17 -0
  411. package/dist/observability/metrics.d.ts.map +1 -0
  412. package/dist/observability/metrics.js +82 -0
  413. package/dist/observability/metrics.js.map +1 -0
  414. package/dist/observability/otel.d.ts +20 -0
  415. package/dist/observability/otel.d.ts.map +1 -0
  416. package/dist/observability/otel.js +54 -0
  417. package/dist/observability/otel.js.map +1 -0
  418. package/dist/pagination.d.ts +57 -0
  419. package/dist/pagination.d.ts.map +1 -0
  420. package/dist/pagination.js +90 -0
  421. package/dist/pagination.js.map +1 -0
  422. package/dist/platform/edition.d.ts +57 -0
  423. package/dist/platform/edition.d.ts.map +1 -0
  424. package/dist/platform/edition.js +90 -0
  425. package/dist/platform/edition.js.map +1 -0
  426. package/dist/platform/entitlements.d.ts +26 -0
  427. package/dist/platform/entitlements.d.ts.map +1 -0
  428. package/dist/platform/entitlements.js +28 -0
  429. package/dist/platform/entitlements.js.map +1 -0
  430. package/dist/platform/events.d.ts +87 -0
  431. package/dist/platform/events.d.ts.map +1 -0
  432. package/dist/platform/events.js +32 -0
  433. package/dist/platform/events.js.map +1 -0
  434. package/dist/platform/index.d.ts +8 -0
  435. package/dist/platform/index.d.ts.map +1 -0
  436. package/dist/platform/index.js +8 -0
  437. package/dist/platform/index.js.map +1 -0
  438. package/dist/platform/local-identity.d.ts +39 -0
  439. package/dist/platform/local-identity.d.ts.map +1 -0
  440. package/dist/platform/local-identity.js +87 -0
  441. package/dist/platform/local-identity.js.map +1 -0
  442. package/dist/platform/registry.d.ts +23 -0
  443. package/dist/platform/registry.d.ts.map +1 -0
  444. package/dist/platform/registry.js +5 -0
  445. package/dist/platform/registry.js.map +1 -0
  446. package/dist/platform/usage.d.ts +11 -0
  447. package/dist/platform/usage.d.ts.map +1 -0
  448. package/dist/platform/usage.js +12 -0
  449. package/dist/platform/usage.js.map +1 -0
  450. package/dist/platform/workspaces.d.ts +25 -0
  451. package/dist/platform/workspaces.d.ts.map +1 -0
  452. package/dist/platform/workspaces.js +44 -0
  453. package/dist/platform/workspaces.js.map +1 -0
  454. package/dist/projects/access.d.ts +40 -0
  455. package/dist/projects/access.d.ts.map +1 -0
  456. package/dist/projects/access.js +133 -0
  457. package/dist/projects/access.js.map +1 -0
  458. package/dist/projects/index.d.ts +2 -0
  459. package/dist/projects/index.d.ts.map +1 -0
  460. package/dist/projects/index.js +2 -0
  461. package/dist/projects/index.js.map +1 -0
  462. package/dist/route-label.d.ts +23 -0
  463. package/dist/route-label.d.ts.map +1 -0
  464. package/dist/route-label.js +29 -0
  465. package/dist/route-label.js.map +1 -0
  466. package/dist/routes/health.d.ts +30 -0
  467. package/dist/routes/health.d.ts.map +1 -0
  468. package/dist/routes/health.js +54 -0
  469. package/dist/routes/health.js.map +1 -0
  470. package/dist/routes/index.d.ts +3 -0
  471. package/dist/routes/index.d.ts.map +1 -0
  472. package/dist/routes/index.js +3 -0
  473. package/dist/routes/index.js.map +1 -0
  474. package/dist/routes/wellknown.d.ts +11 -0
  475. package/dist/routes/wellknown.d.ts.map +1 -0
  476. package/dist/routes/wellknown.js +45 -0
  477. package/dist/routes/wellknown.js.map +1 -0
  478. package/dist/secret.d.ts +30 -0
  479. package/dist/secret.d.ts.map +1 -0
  480. package/dist/secret.js +97 -0
  481. package/dist/secret.js.map +1 -0
  482. package/dist/setup-transport.d.ts +23 -0
  483. package/dist/setup-transport.d.ts.map +1 -0
  484. package/dist/setup-transport.js +52 -0
  485. package/dist/setup-transport.js.map +1 -0
  486. package/dist/state.d.ts +21 -0
  487. package/dist/state.d.ts.map +1 -0
  488. package/dist/state.js +4 -0
  489. package/dist/state.js.map +1 -0
  490. package/dist/storage/driver.d.ts +38 -0
  491. package/dist/storage/driver.d.ts.map +1 -0
  492. package/dist/storage/driver.js +14 -0
  493. package/dist/storage/driver.js.map +1 -0
  494. package/dist/storage/factory.d.ts +4 -0
  495. package/dist/storage/factory.d.ts.map +1 -0
  496. package/dist/storage/factory.js +20 -0
  497. package/dist/storage/factory.js.map +1 -0
  498. package/dist/storage/index.d.ts +4 -0
  499. package/dist/storage/index.d.ts.map +1 -0
  500. package/dist/storage/index.js +4 -0
  501. package/dist/storage/index.js.map +1 -0
  502. package/dist/storage/local.d.ts +21 -0
  503. package/dist/storage/local.d.ts.map +1 -0
  504. package/dist/storage/local.js +88 -0
  505. package/dist/storage/local.js.map +1 -0
  506. package/dist/storage/s3.d.ts +29 -0
  507. package/dist/storage/s3.d.ts.map +1 -0
  508. package/dist/storage/s3.js +82 -0
  509. package/dist/storage/s3.js.map +1 -0
  510. package/dist/testing/fake-hub.d.ts +425 -0
  511. package/dist/testing/fake-hub.d.ts.map +1 -0
  512. package/dist/testing/fake-hub.js +1074 -0
  513. package/dist/testing/fake-hub.js.map +1 -0
  514. package/dist/testing/helpers.d.ts +124 -0
  515. package/dist/testing/helpers.d.ts.map +1 -0
  516. package/dist/testing/helpers.js +174 -0
  517. package/dist/testing/helpers.js.map +1 -0
  518. package/dist/testing/index.d.ts +12 -0
  519. package/dist/testing/index.d.ts.map +1 -0
  520. package/dist/testing/index.js +12 -0
  521. package/dist/testing/index.js.map +1 -0
  522. package/dist/testing/setup-origins.d.ts +21 -0
  523. package/dist/testing/setup-origins.d.ts.map +1 -0
  524. package/dist/testing/setup-origins.js +57 -0
  525. package/dist/testing/setup-origins.js.map +1 -0
  526. package/dist/testing/sso-helpers.d.ts +43 -0
  527. package/dist/testing/sso-helpers.d.ts.map +1 -0
  528. package/dist/testing/sso-helpers.js +159 -0
  529. package/dist/testing/sso-helpers.js.map +1 -0
  530. package/dist/tool-definition.d.ts +382 -0
  531. package/dist/tool-definition.d.ts.map +1 -0
  532. package/dist/tool-definition.js +46 -0
  533. package/dist/tool-definition.js.map +1 -0
  534. package/dist/util/index.d.ts +8 -0
  535. package/dist/util/index.d.ts.map +1 -0
  536. package/dist/util/index.js +8 -0
  537. package/dist/util/index.js.map +1 -0
  538. package/package.json +161 -0
  539. package/src/accounts/deletion.ts +389 -0
  540. package/src/accounts/erasure-log.ts +140 -0
  541. package/src/accounts/index.ts +5 -0
  542. package/src/accounts/mint-refusal.ts +67 -0
  543. package/src/accounts/signup-duplicate.ts +21 -0
  544. package/src/accounts/superadmin.ts +46 -0
  545. package/src/api/apikeys.ts +147 -0
  546. package/src/api/audit.ts +83 -0
  547. package/src/api/break-glass.ts +294 -0
  548. package/src/api/cli-auth.ts +308 -0
  549. package/src/api/create-api.ts +1403 -0
  550. package/src/api/demo-passes.ts +207 -0
  551. package/src/api/errors.ts +13 -0
  552. package/src/api/export.ts +482 -0
  553. package/src/api/files.ts +200 -0
  554. package/src/api/index.ts +29 -0
  555. package/src/api/invitations.ts +396 -0
  556. package/src/api/members.ts +617 -0
  557. package/src/api/onboarding.ts +50 -0
  558. package/src/api/openapi-doc.ts +34 -0
  559. package/src/api/projects.ts +788 -0
  560. package/src/api/scope-routes.ts +12 -0
  561. package/src/api/sso-connect.ts +246 -0
  562. package/src/api/sso-logout.ts +76 -0
  563. package/src/api/teams.ts +575 -0
  564. package/src/api/workspaces.ts +649 -0
  565. package/src/apikeys/index.ts +2 -0
  566. package/src/apikeys/peppers.ts +118 -0
  567. package/src/apikeys/service.ts +223 -0
  568. package/src/app.ts +228 -0
  569. package/src/audit/filters.ts +101 -0
  570. package/src/audit/index.ts +9 -0
  571. package/src/audit/service.ts +154 -0
  572. package/src/boot.ts +953 -0
  573. package/src/constant-time.ts +9 -0
  574. package/src/email/driver.ts +139 -0
  575. package/src/email/index.ts +26 -0
  576. package/src/email/shell.ts +201 -0
  577. package/src/email/templates.ts +184 -0
  578. package/src/entitlements/check.ts +435 -0
  579. package/src/entitlements/clock-skew.ts +58 -0
  580. package/src/entitlements/gate.ts +636 -0
  581. package/src/entitlements/hub-machine-token.ts +205 -0
  582. package/src/entitlements/hub-subject.ts +77 -0
  583. package/src/entitlements/index.ts +37 -0
  584. package/src/entitlements/poster.ts +265 -0
  585. package/src/entitlements/profiles.ts +303 -0
  586. package/src/entitlements/slot.ts +105 -0
  587. package/src/env.ts +456 -0
  588. package/src/files/http.ts +77 -0
  589. package/src/files/index.ts +4 -0
  590. package/src/files/serve.ts +115 -0
  591. package/src/files/service.ts +209 -0
  592. package/src/files/spool.ts +58 -0
  593. package/src/identity/better-auth.ts +1103 -0
  594. package/src/identity/demo-pass-plugin.ts +121 -0
  595. package/src/identity/demo-pass-rules.ts +229 -0
  596. package/src/identity/demo-pass.ts +390 -0
  597. package/src/identity/hub-grant.ts +704 -0
  598. package/src/identity/hub-jwt.ts +78 -0
  599. package/src/identity/hub-live-gate.ts +167 -0
  600. package/src/identity/hub-logout.ts +174 -0
  601. package/src/identity/hub-projection.ts +251 -0
  602. package/src/identity/hub-reconcile.ts +572 -0
  603. package/src/identity/hub-sso.ts +696 -0
  604. package/src/identity/hub-user-client.ts +554 -0
  605. package/src/identity/index.ts +56 -0
  606. package/src/identity/oauth-jwt.ts +128 -0
  607. package/src/identity/resolve-membership.ts +104 -0
  608. package/src/identity/signing-key.ts +164 -0
  609. package/src/index.ts +65 -0
  610. package/src/invitations/index.ts +1 -0
  611. package/src/invitations/service.ts +171 -0
  612. package/src/jobs/index.ts +25 -0
  613. package/src/jobs/pgboss.ts +594 -0
  614. package/src/jobs/timers.ts +287 -0
  615. package/src/logger.ts +67 -0
  616. package/src/mcp/errors.ts +111 -0
  617. package/src/mcp/http.ts +129 -0
  618. package/src/mcp/index.ts +28 -0
  619. package/src/mcp/inputs.ts +33 -0
  620. package/src/mcp/projects.ts +480 -0
  621. package/src/mcp/server.ts +174 -0
  622. package/src/mcp/teams.ts +97 -0
  623. package/src/mcp/tool-kit.ts +148 -0
  624. package/src/members/removal.ts +216 -0
  625. package/src/middleware/auth-body.ts +58 -0
  626. package/src/middleware/auth-context.ts +384 -0
  627. package/src/middleware/body-cap.ts +33 -0
  628. package/src/middleware/body-refusal.ts +23 -0
  629. package/src/middleware/cross-site.ts +122 -0
  630. package/src/middleware/hub-managed.ts +61 -0
  631. package/src/middleware/idempotency.ts +264 -0
  632. package/src/middleware/index.ts +33 -0
  633. package/src/middleware/json-depth.ts +74 -0
  634. package/src/middleware/no-store.ts +30 -0
  635. package/src/middleware/oauth-public.ts +54 -0
  636. package/src/middleware/rate-limit.ts +432 -0
  637. package/src/middleware/request-id.ts +54 -0
  638. package/src/middleware/scopes.ts +150 -0
  639. package/src/middleware/security-headers.ts +129 -0
  640. package/src/observability/index.ts +2 -0
  641. package/src/observability/metrics.ts +102 -0
  642. package/src/observability/otel.ts +79 -0
  643. package/src/pagination.ts +118 -0
  644. package/src/platform/edition.ts +140 -0
  645. package/src/platform/entitlements.ts +47 -0
  646. package/src/platform/events.ts +71 -0
  647. package/src/platform/index.ts +7 -0
  648. package/src/platform/local-identity.ts +90 -0
  649. package/src/platform/registry.ts +28 -0
  650. package/src/platform/usage.ts +13 -0
  651. package/src/platform/workspaces.ts +41 -0
  652. package/src/projects/access.ts +160 -0
  653. package/src/projects/index.ts +7 -0
  654. package/src/route-label.ts +29 -0
  655. package/src/routes/health.ts +79 -0
  656. package/src/routes/index.ts +2 -0
  657. package/src/routes/wellknown.ts +67 -0
  658. package/src/secret.ts +115 -0
  659. package/src/setup-transport.ts +46 -0
  660. package/src/state.ts +23 -0
  661. package/src/storage/driver.ts +48 -0
  662. package/src/storage/factory.ts +34 -0
  663. package/src/storage/index.ts +3 -0
  664. package/src/storage/local.ts +93 -0
  665. package/src/storage/s3.ts +109 -0
  666. package/src/testing/fake-hub.ts +1316 -0
  667. package/src/testing/helpers.ts +254 -0
  668. package/src/testing/index.ts +11 -0
  669. package/src/testing/setup-origins.ts +58 -0
  670. package/src/testing/sso-helpers.ts +178 -0
  671. package/src/tool-definition.ts +463 -0
  672. package/src/util/index.ts +17 -0
  673. package/test/fixtures/onboarding-backfill.sql +13 -0
  674. package/test/fixtures/operator-user-id-backfill.sql +18 -0
  675. package/test/host/dr-lib.sh +253 -0
  676. package/test/host/drizzle/0000_chassis_baseline.sql +438 -0
  677. package/test/host/drizzle/0001_last_owner_guard.sql +42 -0
  678. package/test/host/drizzle/meta/0000_snapshot.json +3566 -0
  679. package/test/host/drizzle/meta/_journal.json +20 -0
  680. package/test/host/drizzle.config.ts +17 -0
  681. package/test/host/identity.ts +41 -0
  682. package/test/host/minimal-host.ts +35 -0
  683. package/test/host/minimal-tool.ts +123 -0
  684. package/test/integration/account-oracle-uniformity.test.ts +284 -0
  685. package/test/integration/account-recovery.test.ts +236 -0
  686. package/test/integration/account.test.ts +120 -0
  687. package/test/integration/api-quota.test.ts +208 -0
  688. package/test/integration/apikey-expiry.test.ts +178 -0
  689. package/test/integration/apikey-pepper-rotation.test.ts +190 -0
  690. package/test/integration/boot-posture.test.ts +169 -0
  691. package/test/integration/break-glass.test.ts +484 -0
  692. package/test/integration/cloud-lifecycle.test.ts +233 -0
  693. package/test/integration/credential-boundaries.test.ts +195 -0
  694. package/test/integration/default-workspace.test.ts +414 -0
  695. package/test/integration/demo-pass-rules.test.ts +332 -0
  696. package/test/integration/demo-pass.test.ts +1222 -0
  697. package/test/integration/dr-verify-real-dump.test.ts +154 -0
  698. package/test/integration/edition.test.ts +868 -0
  699. package/test/integration/email-change.test.ts +394 -0
  700. package/test/integration/empty-tool.test.ts +267 -0
  701. package/test/integration/entitlements-null-hook.test.ts +187 -0
  702. package/test/integration/entitlements.test.ts +693 -0
  703. package/test/integration/export-entries.test.ts +321 -0
  704. package/test/integration/files.test.ts +320 -0
  705. package/test/integration/first-boot-claim.test.ts +131 -0
  706. package/test/integration/gdpr.test.ts +532 -0
  707. package/test/integration/helpers.ts +29 -0
  708. package/test/integration/hub-grant.test.ts +505 -0
  709. package/test/integration/hub-live-gate.test.ts +612 -0
  710. package/test/integration/hub-managed-membership.test.ts +559 -0
  711. package/test/integration/hub-org-selector.test.ts +281 -0
  712. package/test/integration/hub-reconcile.test.ts +646 -0
  713. package/test/integration/hub-sso.test.ts +704 -0
  714. package/test/integration/hub-teams.test.ts +771 -0
  715. package/test/integration/idempotency.test.ts +261 -0
  716. package/test/integration/invite-verification.test.ts +210 -0
  717. package/test/integration/last-owner-race.test.ts +263 -0
  718. package/test/integration/m9-fixes.test.ts +290 -0
  719. package/test/integration/mcp-projects.test.ts +463 -0
  720. package/test/integration/member-removal-race.test.ts +466 -0
  721. package/test/integration/member-removal.test.ts +794 -0
  722. package/test/integration/oauth-mcp.test.ts +477 -0
  723. package/test/integration/oauth-workspace.test.ts +371 -0
  724. package/test/integration/observability.test.ts +183 -0
  725. package/test/integration/onboarding.test.ts +268 -0
  726. package/test/integration/orphan-purge.test.ts +451 -0
  727. package/test/integration/pagination.test.ts +344 -0
  728. package/test/integration/platform-core.test.ts +305 -0
  729. package/test/integration/platform.test.ts +246 -0
  730. package/test/integration/pool-survives-restart.test.ts +80 -0
  731. package/test/integration/projects-cloud.test.ts +193 -0
  732. package/test/integration/projects.test.ts +1310 -0
  733. package/test/integration/security-fixes.test.ts +190 -0
  734. package/test/integration/setup-transport.test.ts +118 -0
  735. package/test/integration/signin-wall.test.ts +103 -0
  736. package/test/integration/signing-key-rotation.test.ts +349 -0
  737. package/test/integration/sso-helpers.ts +15 -0
  738. package/test/integration/sso-logout.test.ts +283 -0
  739. package/test/integration/teams-cloud.test.ts +194 -0
  740. package/test/integration/teams.test.ts +551 -0
  741. package/test/integration/timers.test.ts +377 -0
  742. package/test/integration/two-factor.test.ts +301 -0
  743. package/test/integration/usage-window.test.ts +161 -0
  744. package/test/integration/workspace-create-cloud.test.ts +523 -0
  745. package/test/integration/workspace-create.test.ts +510 -0
  746. package/test/integration/workspace-scoping.test.ts +530 -0
  747. package/test/integration/zz-adv-replay-owner.test.ts +205 -0
  748. package/test/unit/apikey-peppers.test.ts +110 -0
  749. package/test/unit/apikey-prefix.test.ts +34 -0
  750. package/test/unit/audit-filters.test.ts +135 -0
  751. package/test/unit/auth-origins.test.ts +92 -0
  752. package/test/unit/body-cap.test.ts +84 -0
  753. package/test/unit/clock-skew.test.ts +68 -0
  754. package/test/unit/cross-site.test.ts +166 -0
  755. package/test/unit/declared-credits.test.ts +13 -0
  756. package/test/unit/demo-pass-rules.test.ts +192 -0
  757. package/test/unit/email-driver.test.ts +127 -0
  758. package/test/unit/end-pool.test.ts +181 -0
  759. package/test/unit/entitlement-check.test.ts +492 -0
  760. package/test/unit/entitlement-gate.test.ts +1333 -0
  761. package/test/unit/entitlement-profiles.test.ts +289 -0
  762. package/test/unit/erasure-log.test.ts +72 -0
  763. package/test/unit/error-mapping.test.ts +89 -0
  764. package/test/unit/export-entries.test.ts +85 -0
  765. package/test/unit/export-sanitize.test.ts +27 -0
  766. package/test/unit/files-head-route.test.ts +155 -0
  767. package/test/unit/files-http.test.ts +97 -0
  768. package/test/unit/head-rewrite.test.ts +72 -0
  769. package/test/unit/hub-create-org.test.ts +181 -0
  770. package/test/unit/hub-jwt.test.ts +93 -0
  771. package/test/unit/hub-sso-params.test.ts +39 -0
  772. package/test/unit/hub-subject.test.ts +83 -0
  773. package/test/unit/idempotency-crypto.test.ts +45 -0
  774. package/test/unit/json-depth.test.ts +88 -0
  775. package/test/unit/logger-redaction.test.ts +66 -0
  776. package/test/unit/mcp-errors.test.ts +110 -0
  777. package/test/unit/mcp-inputs.test.ts +106 -0
  778. package/test/unit/mcp-project-hints.test.ts +26 -0
  779. package/test/unit/no-store.test.ts +49 -0
  780. package/test/unit/no-tool-migration-filename.test.ts +70 -0
  781. package/test/unit/observability.test.ts +64 -0
  782. package/test/unit/openapi-doc.test.ts +93 -0
  783. package/test/unit/rate-limit-consume-on.test.ts +76 -0
  784. package/test/unit/rate-limit.test.ts +39 -0
  785. package/test/unit/readyz-storage.test.ts +157 -0
  786. package/test/unit/request-quota.test.ts +146 -0
  787. package/test/unit/scopes.test.ts +66 -0
  788. package/test/unit/security-headers.test.ts +131 -0
  789. package/test/unit/serve-content-type.test.ts +74 -0
  790. package/test/unit/setup-transport.test.ts +29 -0
  791. package/test/unit/storage-local.test.ts +53 -0
  792. package/test/unit/superadmin.test.ts +32 -0
  793. package/test/unit/timers.test.ts +287 -0
  794. package/test/unit/tool-definition-guards.test.ts +166 -0
  795. package/test/unit/tool-entitlements-guard.test.ts +168 -0
  796. package/test/unit/usage-poster.test.ts +542 -0
  797. package/test/unit/usage-window.test.ts +48 -0
  798. package/test/unit/workspace-create-audit.test.ts +48 -0
@@ -0,0 +1,128 @@
1
+ import { createLocalJWKSet, jwtVerify, type JWK, type JWTVerifyOptions } from 'jose';
2
+ import { type Db } from '@antasphere/chassis-db';
3
+ import type { Principal } from '@antasphere/chassis-contract';
4
+ import { mcpResourceUrl, type Auth } from './better-auth.js';
5
+ import { isWorkspaceSelector, resolveMembership, type OnWorkspaceMiss } from './resolve-membership.js';
6
+
7
+ /**
8
+ * OAuth Bearer-JWT resolution — the third credential path in auth-context.
9
+ *
10
+ * Access tokens are RS256 JWTs this instance minted itself (Better Auth jwt +
11
+ * oauthProvider plugins), so verification is fully local: JWKS straight from
12
+ * the jwt plugin (DB-backed, no HTTP), hard issuer / audience / algorithm /
13
+ * expiry pinning. On top of the signature, every request re-resolves the
14
+ * workspace membership LIVE — deactivating a member cuts token access on
15
+ * their next request, not at token expiry. Scope enforcement stays in the
16
+ * fail-closed allowlist gate (middleware/scopes.ts), identical to API keys.
17
+ *
18
+ * User-scoped credential model: a token identifies its USER ("act as you")
19
+ * and carries NO workspace authority — claims are never an authorization
20
+ * input. The request's target workspace comes from the X-Workspace-Id
21
+ * selector through the shared resolveMembership rule (explicit → fail-closed
22
+ * membership check; absent → the user's default, else oldest active), the
23
+ * same rule sessions and API keys use.
24
+ */
25
+ const JWKS_TTL_MS = 10 * 60 * 1000;
26
+
27
+ export class OauthJwtVerifier {
28
+ private readonly issuer: string;
29
+ private readonly audience: string;
30
+ // Per-instance cache: the process verifies its OWN tokens, keys rotate
31
+ // rarely; on an unknown-signature failure we refetch once and retry.
32
+ private jwksCache: { keySet: ReturnType<typeof createLocalJWKSet>; fetchedAt: number } | null = null;
33
+
34
+ constructor(
35
+ private readonly auth: Auth,
36
+ private readonly db: Db,
37
+ publicBaseUrl: string,
38
+ /** Cloud only: one cached reconcile on a well-formed selector miss. */
39
+ private readonly onWorkspaceMiss?: OnWorkspaceMiss | undefined
40
+ ) {
41
+ this.issuer = publicBaseUrl;
42
+ this.audience = mcpResourceUrl(publicBaseUrl);
43
+ }
44
+
45
+ private async keySet(forceRefresh = false) {
46
+ if (!forceRefresh && this.jwksCache && Date.now() - this.jwksCache.fetchedAt < JWKS_TTL_MS) {
47
+ return this.jwksCache.keySet;
48
+ }
49
+ const jwks = (await this.auth.api.getJwks()) as { keys: JWK[] };
50
+ this.jwksCache = { keySet: createLocalJWKSet(jwks), fetchedAt: Date.now() };
51
+ return this.jwksCache.keySet;
52
+ }
53
+
54
+ private async verify(token: string) {
55
+ const options: JWTVerifyOptions = {
56
+ issuer: this.issuer,
57
+ audience: this.audience,
58
+ algorithms: ['RS256'], // hard allowlist — implicitly rejects none/HS256
59
+ clockTolerance: 5,
60
+ requiredClaims: ['sub']
61
+ };
62
+ try {
63
+ return await jwtVerify(token, await this.keySet(), options);
64
+ } catch (err) {
65
+ // One retry with fresh keys covers signing-key rotation mid-cache —
66
+ // both directions: a token signed by a key the cache no longer trusts
67
+ // (JWSSignatureVerificationFailed) and a token signed by a key MINTED
68
+ // after the cache was warmed (JWKSNoMatchingKey — the rotate-signing-key
69
+ // drill would otherwise answer 401 for up to JWKS_TTL_MS, ADR 023).
70
+ if (
71
+ err instanceof Error &&
72
+ (err.name === 'JWSSignatureVerificationFailed' || err.name === 'JWKSNoMatchingKey')
73
+ ) {
74
+ return await jwtVerify(token, await this.keySet(true), options);
75
+ }
76
+ throw err;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Resolve a Bearer JWT to its principal, or null when the token fails
82
+ * verification or no live membership answers the selection — the same
83
+ * fail-closed semantics as the session and API-key paths.
84
+ *
85
+ * `requested` is the request's X-Workspace-Id (or null). Resolution is
86
+ * verify → sub → resolveMembership(sub, requested): the LIVE membership
87
+ * lookup is the instant-revocation point AND the whole workspace
88
+ * authorization. A legacy `workspace_id` claim (minted before the
89
+ * user-scoped model) is deliberately ignored — claims stopped being an
90
+ * authorization input entirely.
91
+ */
92
+ async resolve(token: string, requested: string | null): Promise<Principal | null> {
93
+ let payload: Record<string, unknown>;
94
+ try {
95
+ ({ payload } = await this.verify(token));
96
+ } catch {
97
+ return null;
98
+ }
99
+ const sub = typeof payload.sub === 'string' ? payload.sub : null;
100
+ if (!sub) return null;
101
+
102
+ let member = await resolveMembership(this.db, sub, requested);
103
+ if (!member && requested && this.onWorkspaceMiss && isWorkspaceSelector(requested)) {
104
+ // Unknown-workspace retry (cloud): the JWT verified above — the miss
105
+ // may be a hub org granted since the last reconcile pass. One cached
106
+ // reconcile, one re-run of the SAME lookup — no recursion.
107
+ await this.onWorkspaceMiss(sub, requested);
108
+ member = await resolveMembership(this.db, sub, requested);
109
+ }
110
+ if (!member) return null;
111
+
112
+ return {
113
+ userId: sub,
114
+ email: member.email,
115
+ name: member.name,
116
+ workspaceId: member.workspaceId,
117
+ role: member.role,
118
+ origin: member.origin,
119
+ via: 'oauth',
120
+ scopes: new Set(
121
+ String(typeof payload.scope === 'string' ? payload.scope : '')
122
+ .split(' ')
123
+ .filter(Boolean)
124
+ ),
125
+ ...(member.accountRef ? { accountRef: member.accountRef } : {})
126
+ };
127
+ }
128
+ }
@@ -0,0 +1,104 @@
1
+ import { and, asc, desc, eq, or, sql } from 'drizzle-orm';
2
+ import { user as userTable, workspaceMembers, workspaces, type Db } from '@antasphere/chassis-db';
3
+
4
+ /**
5
+ * The ONE membership-selection rule of the user-scoped credential model
6
+ * (ADR 014 as amended): every credential — session, API key, OAuth bearer —
7
+ * identifies a USER, and each request resolves exactly ONE target workspace
8
+ * for that user:
9
+ *
10
+ * - `requested` non-null (the X-Workspace-Id header, or an API key's pin):
11
+ * an ACTIVE membership of that workspace is required, else null — fail
12
+ * closed, so an unknown workspace and a workspace the user does not
13
+ * belong to are indistinguishable (no oracle). The selector is the
14
+ * workspace's LOCAL id, or (PRDCT-2947) the id of the hub organization
15
+ * it projects (`workspaces.central_account_id`): a person names the
16
+ * organization the hub's way (`--org <hub org id>`) and the tool maps
17
+ * it here, in the one rule, so no credential path can fork on it. On
18
+ * self-hosted the column is always empty and the second arm is inert.
19
+ * - `requested` null: the user's DEFAULT membership (`is_default`, at most
20
+ * one per user), else the deterministic fallback — the OLDEST active
21
+ * membership (created_at, then id). While no row carries `is_default`
22
+ * (nothing sets it on oss; the cloud reconcile materializes the hub-level
23
+ * default), the ordering is inert and selection is byte-identical to the
24
+ * pre-default behavior.
25
+ *
26
+ * All three resolvers share THIS query so the selection rule can never fork
27
+ * per credential path.
28
+ */
29
+
30
+ /** Strict UUID shape — a malformed selector must never reach Postgres' uuid cast. */
31
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
32
+
33
+ /**
34
+ * Well-formed-selector check, shared with the cloud miss-retry hook: only a
35
+ * plausibly-real workspace id ever triggers a hub reconcile — a garbage
36
+ * header can never buy a hub round-trip (the reconciler's TTL/throttle
37
+ * bounds even well-formed ones).
38
+ */
39
+ export function isWorkspaceSelector(value: string): boolean {
40
+ return UUID_RE.test(value);
41
+ }
42
+
43
+ /**
44
+ * The cloud edition's unknown-workspace retry hook (internal/federation.md):
45
+ * a verified credential naming a workspace the local join cannot see MAY be
46
+ * a hub org granted since the last reconcile pass — "invited at the hub,
47
+ * clicks a deep link". Boot wires ONE cached reconcile; the resolver then
48
+ * re-runs the SAME lookup once, no recursion. undefined on oss.
49
+ */
50
+ export type OnWorkspaceMiss = (userId: string, requested: string) => Promise<void>;
51
+
52
+ export interface ResolvedMembership {
53
+ workspaceId: string;
54
+ role: 'owner' | 'admin' | 'member';
55
+ origin: 'local' | 'hub' | 'guest';
56
+ /** The workspace's centralAccountId — null outside hub projections. */
57
+ accountRef: string | null;
58
+ /** Live user row fields, for the machine paths that carry no session. */
59
+ email: string;
60
+ name: string;
61
+ }
62
+
63
+ export async function resolveMembership(
64
+ db: Db,
65
+ userId: string,
66
+ requested: string | null
67
+ ): Promise<ResolvedMembership | null> {
68
+ if (requested !== null && !UUID_RE.test(requested)) return null; // fail closed, no uuid-cast 500
69
+
70
+ const [row] = await db
71
+ .select({
72
+ workspaceId: workspaceMembers.workspaceId,
73
+ role: workspaceMembers.role,
74
+ origin: workspaceMembers.origin,
75
+ accountRef: workspaces.centralAccountId,
76
+ email: userTable.email,
77
+ name: userTable.name
78
+ })
79
+ .from(workspaceMembers)
80
+ .innerJoin(workspaces, eq(workspaceMembers.workspaceId, workspaces.id))
81
+ .innerJoin(userTable, eq(workspaceMembers.userId, userTable.id))
82
+ .where(
83
+ and(
84
+ eq(workspaceMembers.userId, userId),
85
+ eq(workspaceMembers.isActive, true),
86
+ ...(requested
87
+ ? [
88
+ or(
89
+ eq(workspaceMembers.workspaceId, requested),
90
+ // Case-blind like the uuid arm and the pin check (verifier
91
+ // round 1, F6): the column is text, the selector a uuid.
92
+ sql`lower(${workspaces.centralAccountId}) = lower(${requested})`
93
+ )
94
+ ]
95
+ : [])
96
+ )
97
+ )
98
+ // Selector-less default: the default membership first, then the
99
+ // deterministic oldest. With a selector the filter pins a single row and
100
+ // the order is inert.
101
+ .orderBy(desc(workspaceMembers.isDefault), asc(workspaceMembers.createdAt), asc(workspaceMembers.id))
102
+ .limit(1);
103
+ return row ?? null;
104
+ }
@@ -0,0 +1,164 @@
1
+ import { desc, eq } from 'drizzle-orm';
2
+ import { exportJWK, generateKeyPair } from 'jose';
3
+ import { generateRandomString, symmetricDecrypt, symmetricEncrypt } from 'better-auth/crypto';
4
+ import { jwks, type Db } from '@antasphere/chassis-db';
5
+
6
+ /**
7
+ * OAuth signing-key durability across AUTH_SECRET rotation (ADR 023).
8
+ *
9
+ * The jwt plugin stores its RS256 private key in the `jwks` table encrypted
10
+ * under the auth secret (bare xchacha20 ciphertext — createAuth passes a
11
+ * plain string `secret`, so no versioned envelope). Signing decrypts the
12
+ * NEWEST row on every mint; with no `rotationInterval` the row never
13
+ * expires, so a rotated AUTH_SECRET turns every token mint into a 500 —
14
+ * forever. The three exports here close that:
15
+ *
16
+ * - `preflightSigningKey` mirrors the plugin's signing path (newest
17
+ * non-expired row → decrypt) at boot and reports failure BEFORE the
18
+ * instance serves an authorize endpoint it cannot follow with a token.
19
+ * - `rotateSigningKey` mints a fresh RS256 key under the CURRENT secret —
20
+ * the same row shape the plugin's own createJwk writes, so the plugin
21
+ * signs with it immediately (newest createdAt wins). Retired rows stay in
22
+ * the table, and /jwks publishes every row without an expiresAt, so
23
+ * already-issued tokens keep verifying for as long as the old row stays —
24
+ * the publish-overlap window.
25
+ * - `retireSigningKey` deletes ONE named retired row after the overlap
26
+ * window; the newest (active signer) is refused.
27
+ *
28
+ * Everything here mirrors the pinned better-auth 1.6.15 jwt plugin
29
+ * (plugins/jwt/{sign,utils,adapter}.mjs) — re-verify the row shape, the
30
+ * newest-key selection, and the /jwks publish filter on ANY bump.
31
+ */
32
+
33
+ /** The alg the chassis pins in `jwks.keyPairConfig` (identity/better-auth.ts). */
34
+ const SIGNING_ALG = 'RS256';
35
+
36
+ export type SigningKeyPreflight =
37
+ { ok: true; kid: string | null } | { ok: false; kid: string; reason: string };
38
+
39
+ /**
40
+ * The operator-facing diagnostic — names the cause and BOTH safe remedies.
41
+ * Kept in one place so boot's fatal log and the tests pin the same message.
42
+ */
43
+ export function signingKeyPreflightMessage(kid: string): string {
44
+ return (
45
+ `OAuth signing-key preflight failed: the stored JWKS private key (kid ${kid}) ` +
46
+ 'cannot be decrypted with the current AUTH_SECRET. This happens when AUTH_SECRET ' +
47
+ 'was rotated without re-keying token signing; booting anyway would serve an ' +
48
+ 'authorize endpoint whose every token mint (POST /oauth2/token) answers 500. ' +
49
+ 'Remedies: restore the previous AUTH_SECRET, or mint a fresh signing key under ' +
50
+ 'the current secret with `node dist/index.js rotate-signing-key` — the retired ' +
51
+ 'key stays published on /jwks so already-issued tokens keep verifying ' +
52
+ '(internal/security-runbooks.md).'
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Boot preflight: attempt one decrypt of the key the jwt plugin would sign
58
+ * with. Exactly the plugin's selection rule (sign.mjs): newest by createdAt;
59
+ * a missing table-or-row and an EXPIRED newest row are both fine — the next
60
+ * mint creates a fresh key under the current secret.
61
+ */
62
+ export async function preflightSigningKey(db: Db, authSecret: string): Promise<SigningKeyPreflight> {
63
+ const [latest] = await db
64
+ .select({ id: jwks.id, privateKey: jwks.privateKey, expiresAt: jwks.expiresAt })
65
+ .from(jwks)
66
+ .orderBy(desc(jwks.createdAt))
67
+ .limit(1);
68
+ if (!latest) return { ok: true, kid: null };
69
+ if (latest.expiresAt && latest.expiresAt < new Date()) return { ok: true, kid: null };
70
+ try {
71
+ // The plugin stores JSON.stringify(symmetricEncrypt(...)) — decrypt takes
72
+ // the JSON.parse'd string (sign.mjs).
73
+ await symmetricDecrypt({ key: authSecret, data: JSON.parse(latest.privateKey) as string });
74
+ return { ok: true, kid: latest.id };
75
+ } catch {
76
+ return { ok: false, kid: latest.id, reason: signingKeyPreflightMessage(latest.id) };
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Mint a new signing key under the given secret — the manual rotation step
82
+ * (a compromise drill, or re-keying after an AUTH_SECRET change). Mirrors
83
+ * the plugin's createJwk (utils.mjs): extractable RS256 pair, JWK-exported,
84
+ * private half encrypted exactly as the plugin decrypts it. The new row's
85
+ * createdAt makes it the active signer; every older row stays published on
86
+ * /jwks (the overlap) until explicitly retired.
87
+ */
88
+ export async function rotateSigningKey(db: Db, authSecret: string): Promise<{ kid: string }> {
89
+ const { publicKey, privateKey } = await generateKeyPair(SIGNING_ALG, { extractable: true });
90
+ const publicWebKey = await exportJWK(publicKey);
91
+ const privateWebKey = await exportJWK(privateKey);
92
+ const kid = generateRandomString(32, 'a-z', 'A-Z', '0-9');
93
+ await db.insert(jwks).values({
94
+ id: kid,
95
+ publicKey: JSON.stringify(publicWebKey),
96
+ privateKey: JSON.stringify(
97
+ await symmetricEncrypt({ key: authSecret, data: JSON.stringify(privateWebKey) })
98
+ ),
99
+ createdAt: new Date(),
100
+ expiresAt: null
101
+ });
102
+ return { kid };
103
+ }
104
+
105
+ /**
106
+ * Delete ONE retired key by kid, after the overlap window. Refuses the
107
+ * newest row: deleting the active signer would silently re-key the instance
108
+ * on the next mint with zero coordination — the exact failure the preflight
109
+ * exists to prevent.
110
+ */
111
+ export async function retireSigningKey(db: Db, kid: string): Promise<void> {
112
+ const [latest] = await db.select({ id: jwks.id }).from(jwks).orderBy(desc(jwks.createdAt)).limit(1);
113
+ if (!latest) throw new Error('no signing keys exist');
114
+ if (latest.id === kid) {
115
+ throw new Error(
116
+ `refusing to retire ${kid}: it is the ACTIVE signing key (newest row). ` +
117
+ 'Mint a replacement first with rotate-signing-key, wait out the overlap window, then retire.'
118
+ );
119
+ }
120
+ const deleted = await db.delete(jwks).where(eq(jwks.id, kid)).returning({ id: jwks.id });
121
+ if (deleted.length === 0) throw new Error(`no signing key with kid ${kid}`);
122
+ }
123
+
124
+ /**
125
+ * `node dist/index.js rotate-signing-key [--retire <kid>]` — the operator
126
+ * entry point (index.ts). Runs against the container's own env (DATABASE_URL
127
+ * + AUTH_SECRET/DATA_DIR), so `docker compose run --rm app …` works while
128
+ * the server is down — the state the preflight refusal leaves you in.
129
+ * Output goes to stdout on purpose: this is an interactive operator command,
130
+ * not the service log.
131
+ */
132
+ export async function runSigningKeyCli(
133
+ db: Db,
134
+ authSecret: string,
135
+ args: readonly string[],
136
+ out: (line: string) => void
137
+ ): Promise<number> {
138
+ const retireAt = args.indexOf('--retire');
139
+ if (retireAt !== -1) {
140
+ const kid = args[retireAt + 1];
141
+ if (!kid) {
142
+ out('usage: rotate-signing-key --retire <kid>');
143
+ return 1;
144
+ }
145
+ await retireSigningKey(db, kid);
146
+ out(`retired signing key ${kid} — it is no longer published on /jwks; tokens it signed no longer verify`);
147
+ return 0;
148
+ }
149
+
150
+ const before = await db.select({ id: jwks.id }).from(jwks).orderBy(desc(jwks.createdAt));
151
+ const { kid } = await rotateSigningKey(db, authSecret);
152
+ out(`minted new signing key ${kid} under the current AUTH_SECRET — it signs from the next mint on`);
153
+ if (before.length > 0) {
154
+ out(
155
+ `retired key(s) still published for the overlap window (already-issued tokens keep verifying): ` +
156
+ before.map((k) => k.id).join(', ')
157
+ );
158
+ out(
159
+ 'after the overlap window (outstanding access tokens live 15 min; allow for external JWKS caches), ' +
160
+ 'retire each with: rotate-signing-key --retire <kid>'
161
+ );
162
+ }
163
+ return 0;
164
+ }
package/src/index.ts ADDED
@@ -0,0 +1,65 @@
1
+ import type { z } from 'zod';
2
+ import { bootPlatform } from './boot.js';
3
+ import type { BootOverrides, BootResult, ToolDefinition } from './tool-definition.js';
4
+
5
+ export { type AppDeps, createApp, postgresErrorCode } from './app.js';
6
+ export type { ExportEntriesFn, ExportEntry } from './api/export.js';
7
+ export {
8
+ EMPTY_TOOL_ENTITLEMENTS,
9
+ HUB_USAGE_SCOPE,
10
+ HubCreditCheck,
11
+ HubMachineToken,
12
+ HubUsagePoster,
13
+ EntitlementProfiles,
14
+ assertToolEntitlements,
15
+ type ToolEntitlementDeclaration
16
+ } from './entitlements/index.js';
17
+ export type {
18
+ ApiContext,
19
+ ApiRateLimitContext,
20
+ ApiRoutesContext,
21
+ BodyCap,
22
+ BodyLimitVerdict,
23
+ BootOverrides,
24
+ BootResult,
25
+ FilePolicy,
26
+ JobsContext,
27
+ PlatformCore,
28
+ ServiceCore,
29
+ ToolApiSlots,
30
+ ToolAppSlots,
31
+ ToolBuckets,
32
+ ToolCopy,
33
+ ToolDefinition,
34
+ ToolRateLimiters
35
+ } from './tool-definition.js';
36
+
37
+ /** A platform: one tool definition bound to the chassis' locked composition. */
38
+ export interface Platform<TEnvShape extends z.ZodRawShape, TDomain, TEvents, TToolOverrides> {
39
+ /**
40
+ * The boot sequence, in its locked order: env → logger → db probe → secret →
41
+ * migrations under advisory lock → app assembly → ready.
42
+ */
43
+ boot: (
44
+ source?: NodeJS.ProcessEnv,
45
+ overrides?: BootOverrides<TToolOverrides>
46
+ ) => Promise<BootResult<TEnvShape, TDomain, TEvents>>;
47
+ }
48
+
49
+ /**
50
+ * THE composition entry. A tool fills the named slots of `ToolDefinition`;
51
+ * the chassis owns every ordering (boot.ts, api/create-api.ts, app.ts).
52
+ */
53
+ export function createPlatform<
54
+ TEnvShape extends z.ZodRawShape,
55
+ TDomain,
56
+ TBuckets extends string = never,
57
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
58
+ TEvents = {},
59
+ TToolOverrides = never
60
+ >(
61
+ tool: ToolDefinition<TEnvShape, TDomain, TBuckets, TEvents, TToolOverrides>
62
+ ): Platform<TEnvShape, TDomain, TEvents, TToolOverrides> {
63
+ return { boot: (source, overrides) => bootPlatform(tool, source, overrides) };
64
+ }
65
+ export type { MembershipRemovalHook, RemovedMembership, RemovalCounts } from './members/removal.js';
@@ -0,0 +1 @@
1
+ export { InvitationError, InvitationService } from './service.js';
@@ -0,0 +1,171 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { and, eq, isNull, or, sql } from 'drizzle-orm';
3
+ import {
4
+ invitations,
5
+ workspaceMembers,
6
+ user as userTable,
7
+ type Db,
8
+ type Invitation
9
+ } from '@antasphere/chassis-db';
10
+
11
+ /**
12
+ * Token-bearing invitations; the database holds only sha256 hashes.
13
+ * Acceptance works with zero SMTP — the copyable link is the primary path,
14
+ * mail is a bonus.
15
+ *
16
+ * Each invitation carries TWO independent tokens for one row (ADR 009):
17
+ * `token` is the copyable link handed to the inviter (always in the create
18
+ * response), `emailToken` appears ONLY inside the invitation email. Both
19
+ * redeem the same invitation, but which one was presented tells the accept
20
+ * flow whether the accepter demonstrably controls the invited mailbox — the
21
+ * admin-visible link never proves that, the emailed token does.
22
+ */
23
+ const INVITATION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
24
+
25
+ function hashToken(token: string): string {
26
+ return createHash('sha256').update(token).digest('hex');
27
+ }
28
+
29
+ /** A live invitation plus which of its two tokens the caller presented. */
30
+ export interface InvitationMatch {
31
+ invitation: Invitation;
32
+ /** True when the presented token is the one that ONLY the email carried. */
33
+ viaEmailToken: boolean;
34
+ }
35
+
36
+ export class InvitationService {
37
+ constructor(private readonly db: Db) {}
38
+
39
+ async create(opts: {
40
+ workspaceId: string;
41
+ email: string;
42
+ role: 'owner' | 'admin' | 'member';
43
+ invitedBy: string;
44
+ }): Promise<{ invitation: Invitation; token: string; emailToken: string }> {
45
+ const email = opts.email.toLowerCase().trim();
46
+
47
+ // Refuse duplicates: an active member or an open invitation.
48
+ const [existingMember] = await this.db
49
+ .select({ id: workspaceMembers.id })
50
+ .from(workspaceMembers)
51
+ .innerJoin(userTable, eq(workspaceMembers.userId, userTable.id))
52
+ .where(
53
+ and(
54
+ eq(userTable.email, email),
55
+ eq(workspaceMembers.workspaceId, opts.workspaceId),
56
+ eq(workspaceMembers.isActive, true)
57
+ )
58
+ )
59
+ .limit(1);
60
+ if (existingMember) throw new InvitationError('already_member', 'This email is already an active member');
61
+
62
+ const [open] = await this.db
63
+ .select({ id: invitations.id })
64
+ .from(invitations)
65
+ .where(
66
+ and(
67
+ eq(invitations.email, email),
68
+ eq(invitations.workspaceId, opts.workspaceId),
69
+ isNull(invitations.acceptedAt),
70
+ isNull(invitations.revokedAt),
71
+ sql`${invitations.expiresAt} > now()`
72
+ )
73
+ )
74
+ .limit(1);
75
+ if (open)
76
+ throw new InvitationError('already_invited', 'An open invitation for this email already exists');
77
+
78
+ const token = randomBytes(32).toString('base64url');
79
+ const emailToken = randomBytes(32).toString('base64url');
80
+ const [invitation] = await this.db
81
+ .insert(invitations)
82
+ .values({
83
+ workspaceId: opts.workspaceId,
84
+ email,
85
+ role: opts.role,
86
+ tokenHash: hashToken(token),
87
+ emailTokenHash: hashToken(emailToken),
88
+ invitedBy: opts.invitedBy,
89
+ expiresAt: new Date(Date.now() + INVITATION_TTL_MS)
90
+ })
91
+ .returning();
92
+ if (!invitation) throw new Error('invitation insert failed');
93
+ return { invitation, token, emailToken };
94
+ }
95
+
96
+ /**
97
+ * Find a live (unaccepted, unrevoked, unexpired) invitation by raw token —
98
+ * either the copyable-link token or the emailed one — and report which.
99
+ */
100
+ async findLiveByToken(token: string): Promise<InvitationMatch | null> {
101
+ const hash = hashToken(token);
102
+ const [row] = await this.db
103
+ .select()
104
+ .from(invitations)
105
+ .where(
106
+ and(
107
+ or(eq(invitations.tokenHash, hash), eq(invitations.emailTokenHash, hash)),
108
+ isNull(invitations.acceptedAt),
109
+ isNull(invitations.revokedAt)
110
+ )
111
+ )
112
+ .limit(1);
113
+ if (!row) return null;
114
+ if (row.expiresAt.getTime() < Date.now()) return null;
115
+ return { invitation: row, viaEmailToken: row.emailTokenHash === hash };
116
+ }
117
+
118
+ /**
119
+ * Join: create the membership and stamp acceptance atomically enough that
120
+ * a token cannot be redeemed twice (the accepted_at guard is the gate).
121
+ */
122
+ async accept(invitation: Invitation, userId: string): Promise<boolean> {
123
+ return this.db.transaction(async (tx) => {
124
+ const updated = await tx
125
+ .update(invitations)
126
+ .set({ acceptedAt: sql`now()` })
127
+ .where(
128
+ and(
129
+ eq(invitations.id, invitation.id),
130
+ isNull(invitations.acceptedAt),
131
+ isNull(invitations.revokedAt),
132
+ sql`${invitations.expiresAt} > now()`
133
+ )
134
+ )
135
+ .returning({ id: invitations.id });
136
+ if (updated.length === 0) return false; // redeemed, revoked, or expired meanwhile
137
+
138
+ // Rejoin after deactivation reactivates the existing row.
139
+ const [existing] = await tx
140
+ .select({ id: workspaceMembers.id })
141
+ .from(workspaceMembers)
142
+ .where(
143
+ and(eq(workspaceMembers.workspaceId, invitation.workspaceId), eq(workspaceMembers.userId, userId))
144
+ )
145
+ .limit(1);
146
+ if (existing) {
147
+ await tx
148
+ .update(workspaceMembers)
149
+ .set({ isActive: true, role: invitation.role, invitedBy: invitation.invitedBy })
150
+ .where(eq(workspaceMembers.id, existing.id));
151
+ } else {
152
+ await tx.insert(workspaceMembers).values({
153
+ workspaceId: invitation.workspaceId,
154
+ userId,
155
+ role: invitation.role,
156
+ invitedBy: invitation.invitedBy
157
+ });
158
+ }
159
+ return true;
160
+ });
161
+ }
162
+ }
163
+
164
+ export class InvitationError extends Error {
165
+ constructor(
166
+ public readonly code: 'already_member' | 'already_invited',
167
+ message: string
168
+ ) {
169
+ super(message);
170
+ }
171
+ }
@@ -0,0 +1,25 @@
1
+ export {
2
+ type JobDeclaration,
3
+ type Jobs,
4
+ type UsageRetry,
5
+ DEFAULT_USAGE_RETRY,
6
+ USAGE_HELD_QUEUE,
7
+ USAGE_QUEUE,
8
+ PgBossUsageSink,
9
+ createJobs,
10
+ usageSendOptions
11
+ } from './pgboss.js';
12
+ export {
13
+ type TimerJob,
14
+ type TimerOptions,
15
+ type TimerRef,
16
+ type TimerRetry,
17
+ type TimerWhen,
18
+ type Timers,
19
+ MAX_IN_SECONDS,
20
+ PGBOSS_SCHEMA,
21
+ PgBossTimers,
22
+ TIMER_KEY_LOCK_CLASS,
23
+ TimerError,
24
+ startAfterOf
25
+ } from './timers.js';