@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,624 @@
1
+ import { and, desc, eq, isNotNull, isNull, sql } from 'drizzle-orm';
2
+ import { projectRoleAtLeast } from '@antasphere/chassis-contract';
3
+ import { projectArchiveRoute, projectCreateRoute, projectGetRoute, projectMemberAddRoute, projectMemberRemoveRoute, projectMemberRoleRoute, projectMembersListRoute, projectsListRoute, projectTeamRemoveRoute, projectTeamRoleRoute, projectUnarchiveRoute, projectUpdateRoute } from '@antasphere/chassis-contract/routes';
4
+ import { projectMembers, projects, projectTeams, user as userTable, workspaceMembers, workspaceTeams } from '@antasphere/chassis-db';
5
+ import { requireAuth, requireNonGuest } from '../middleware/auth-context.js';
6
+ import { holdLiveMembership } from '../members/removal.js';
7
+ import { createdAtText, cursorRowId, decodeKeysetCursor, encodeKeysetCursor, keysetBefore, pageOf } from '../pagination.js';
8
+ import { PROJECTS_ID, projectGrantPredicate, projectRole, projectRoleExpression } from '../projects/access.js';
9
+ const err = (code, message) => ({ error: { code, message } });
10
+ const notFound = () => err('not_found', 'Project not found');
11
+ /** A Postgres foreign-key violation (SQLSTATE 23503) on the named constraint, down drizzle's `cause` chain. */
12
+ function isForeignKeyViolation(e, constraint) {
13
+ for (let cur = e, depth = 0; cur instanceof Error && depth < 10; cur = cur.cause, depth++) {
14
+ const link = cur;
15
+ if (link.code === '23503')
16
+ return link.constraint === constraint;
17
+ }
18
+ return false;
19
+ }
20
+ /** The foreign key a team's place on a project rides on: the one violation that means "the team went". */
21
+ const PROJECT_TEAM_FK = 'project_teams_team_id_workspace_teams_id_fk';
22
+ const toWire = (p) => ({
23
+ id: p.id,
24
+ name: p.name,
25
+ description: p.description,
26
+ metadata: p.metadata,
27
+ archivedAt: p.archivedAt?.toISOString() ?? null,
28
+ createdBy: p.createdBy,
29
+ createdAt: p.createdAt.toISOString(),
30
+ updatedAt: p.updatedAt.toISOString(),
31
+ myRole: p.myRole,
32
+ memberCount: p.memberCount
33
+ });
34
+ const memberToWire = (m) => ({
35
+ kind: 'person',
36
+ userId: m.userId,
37
+ email: m.email,
38
+ name: m.name,
39
+ role: m.role,
40
+ addedBy: m.addedBy,
41
+ createdAt: m.createdAt.toISOString()
42
+ });
43
+ const teamToWire = (t) => ({
44
+ kind: 'team',
45
+ teamId: t.teamId,
46
+ slug: t.slug,
47
+ name: t.name,
48
+ membersCount: Number(t.membersCount),
49
+ hubTeamId: t.hubTeamId,
50
+ role: t.role,
51
+ addedBy: t.addedBy,
52
+ createdAt: t.createdAt.toISOString()
53
+ });
54
+ /** How many people a team seats: the people who hold the project role through it. */
55
+ const TEAM_MEMBERS_COUNT = sql `(SELECT count(*)::int FROM workspace_team_members prj_tmc WHERE prj_tmc.team_id = ${sql.raw('"workspace_teams"."id"')})`;
56
+ /** The wire columns of a project, the caller's role and the member count computed in the same read. */
57
+ const projectSelection = (principal) => ({
58
+ id: projects.id,
59
+ name: projects.name,
60
+ description: projects.description,
61
+ metadata: projects.metadata,
62
+ archivedAt: projects.archivedAt,
63
+ createdBy: projects.createdBy,
64
+ createdAt: projects.createdAt,
65
+ updatedAt: projects.updatedAt,
66
+ myRole: projectRoleExpression(principal, PROJECTS_ID),
67
+ // People and teams alike: what the members list shows.
68
+ memberCount: sql `(SELECT count(*)::int FROM project_members prj_count WHERE prj_count.project_id = ${PROJECTS_ID})
69
+ + (SELECT count(*)::int FROM project_teams prj_tcount WHERE prj_tcount.project_id = ${PROJECTS_ID})`
70
+ });
71
+ // The members list pages with the value-carrying cursor (`keysetBeforeValue`):
72
+ // project_members HARD-deletes rows, so a cursor naming a member removed
73
+ // between two pages would compare against NULL under `keysetBefore` and end
74
+ // the roster early. The timestamp rides as Postgres rendered it, never as a
75
+ // JS Date, so a millisecond shared by two rows loses nobody.
76
+ const memberSelection = {
77
+ id: projectMembers.id,
78
+ createdAtText: createdAtText(projectMembers.createdAt),
79
+ userId: workspaceMembers.userId,
80
+ email: userTable.email,
81
+ name: userTable.name,
82
+ role: projectMembers.role,
83
+ addedBy: projectMembers.addedBy,
84
+ createdAt: projectMembers.createdAt
85
+ };
86
+ /** A team's entry on a project, joined to the team; the same cursor columns as a person's. */
87
+ const teamSelection = {
88
+ id: projectTeams.id,
89
+ createdAtText: createdAtText(projectTeams.createdAt),
90
+ teamId: workspaceTeams.id,
91
+ slug: workspaceTeams.slug,
92
+ name: workspaceTeams.name,
93
+ membersCount: TEAM_MEMBERS_COUNT,
94
+ hubTeamId: workspaceTeams.hubTeamId,
95
+ role: projectTeams.role,
96
+ addedBy: projectTeams.addedBy,
97
+ createdAt: projectTeams.createdAt
98
+ };
99
+ export function registerProjectRoutes(api, deps) {
100
+ const { db } = deps;
101
+ // Projects are a workspace-level surface: a guest membership exists for
102
+ // principal resolution only (D2) and is refused the whole subtree, flat, on
103
+ // both editions and for every credential. The predicates refuse a guest a
104
+ // second time (`projects/access.ts`): this gate is the truthful answer, that
105
+ // clause is what a tool's own rule inherits.
106
+ for (const path of ['/projects', '/projects/*']) {
107
+ api.use(path, requireAuth());
108
+ api.use(path, requireNonGuest());
109
+ }
110
+ /** One project of the request's workspace AS THE CALLER reads it, or null (= 404). */
111
+ async function readProject(conn, principal, id) {
112
+ const [row] = await conn
113
+ .select(projectSelection(principal))
114
+ .from(projects)
115
+ .where(and(eq(projects.id, id), eq(projects.workspaceId, principal.workspaceId)))
116
+ .limit(1);
117
+ if (!row || row.myRole === null)
118
+ return null;
119
+ return { ...row, myRole: row.myRole };
120
+ }
121
+ /**
122
+ * The gate of every mutation, in its fixed order: 404, then 403, then 409.
123
+ * Returns the project as the caller reads it, or the response to send.
124
+ */
125
+ async function gateMutation(c, id, min, opts = {}) {
126
+ const principal = c.get('principal');
127
+ const project = await readProject(db, principal, id);
128
+ if (!project)
129
+ return { response: c.json(notFound(), 404) };
130
+ if (!projectRoleAtLeast(project.myRole, min)) {
131
+ return {
132
+ response: c.json(err('insufficient_project_role', `This needs the ${min} role on the project`), 403)
133
+ };
134
+ }
135
+ if (project.archivedAt !== null && !opts.allowArchived) {
136
+ return { response: c.json(archived(), 409) };
137
+ }
138
+ return { project };
139
+ }
140
+ const archived = () => err('project_archived', 'The project is archived: unarchive it to change it');
141
+ /**
142
+ * Runs a membership write while holding the project row FOR SHARE, after
143
+ * re-reading its archive state under that lock. Archiving UPDATEs the row,
144
+ * so it waits for the write (or the write sees it): an archive landing
145
+ * between the gate and the write can never be overtaken. `null` = archived.
146
+ */
147
+ async function whileLive(principal, id, write) {
148
+ return db.transaction(async (tx) => {
149
+ const [row] = await tx
150
+ .select({ archivedAt: projects.archivedAt })
151
+ .from(projects)
152
+ .where(and(eq(projects.id, id), eq(projects.workspaceId, principal.workspaceId)))
153
+ .for('share')
154
+ .limit(1);
155
+ if (!row || row.archivedAt !== null)
156
+ return null;
157
+ return write(tx);
158
+ });
159
+ }
160
+ // ── List ─────────────────────────────────────────────────────────────────
161
+ api.openapi(projectsListRoute, async (c) => {
162
+ const principal = c.get('principal');
163
+ const { cursor, limit, archived: archivedFilter } = c.req.valid('query');
164
+ const cursorId = cursorRowId(cursor);
165
+ const rows = await db
166
+ .select(projectSelection(principal))
167
+ .from(projects)
168
+ .where(and(eq(projects.workspaceId, principal.workspaceId),
169
+ // The read rule, the same builder a tool uses: a member's page is
170
+ // the projects they hold a grant on, an owner's or admin's is all.
171
+ projectGrantPredicate(principal, PROJECTS_ID, { atLeast: 'viewer', access: 'read' }), ...(archivedFilter === 'false' ? [isNull(projects.archivedAt)] : []), ...(archivedFilter === 'true' ? [isNotNull(projects.archivedAt)] : []), ...(cursorId
172
+ ? [
173
+ keysetBefore({
174
+ table: projects,
175
+ id: projects.id,
176
+ createdAt: projects.createdAt,
177
+ workspaceId: projects.workspaceId,
178
+ cursorId,
179
+ workspace: principal.workspaceId
180
+ })
181
+ ]
182
+ : [])))
183
+ .orderBy(desc(projects.createdAt), desc(projects.id))
184
+ .limit(limit + 1);
185
+ const { page, nextCursor } = pageOf(rows, limit);
186
+ // The WHERE is the rule: every selected row carries a role.
187
+ return c.json({ projects: page.map((p) => toWire({ ...p, myRole: p.myRole })), nextCursor }, 200);
188
+ });
189
+ // ── Create ───────────────────────────────────────────────────────────────
190
+ // Any non-guest member creates a project and becomes its first manager in
191
+ // the SAME transaction: a project is never born without someone to manage it
192
+ // (an owner or admin could anyway, but the creator should not need one).
193
+ api.openapi(projectCreateRoute, async (c) => {
194
+ const principal = c.get('principal');
195
+ const body = c.req.valid('json');
196
+ const created = await db.transaction(async (tx) => {
197
+ const [membership] = await tx
198
+ .select({ id: workspaceMembers.id })
199
+ .from(workspaceMembers)
200
+ .where(and(eq(workspaceMembers.workspaceId, principal.workspaceId), eq(workspaceMembers.userId, principal.userId), eq(workspaceMembers.isActive, true)))
201
+ // Held until the creator's grant commits, like every add's
202
+ // (`holdLiveMembership`): a removal of the creator waits for it.
203
+ .for('share')
204
+ .limit(1);
205
+ if (!membership)
206
+ return null;
207
+ const [project] = await tx
208
+ .insert(projects)
209
+ .values({
210
+ workspaceId: principal.workspaceId,
211
+ name: body.name,
212
+ description: body.description ?? null,
213
+ metadata: body.metadata ?? {},
214
+ createdBy: principal.userId
215
+ })
216
+ .returning({ id: projects.id });
217
+ await tx.insert(projectMembers).values({
218
+ projectId: project.id,
219
+ memberId: membership.id,
220
+ role: 'manager',
221
+ addedBy: principal.userId
222
+ });
223
+ return readProject(tx, principal, project.id);
224
+ });
225
+ // The principal resolved from an active membership a moment ago; losing
226
+ // it mid-request is a revoked caller, answered like any other.
227
+ if (!created)
228
+ return c.json(err('unauthenticated', 'Authentication required'), 401);
229
+ c.set('audit', {
230
+ action: 'project.create',
231
+ resourceType: 'project',
232
+ resourceId: created.id,
233
+ metadata: { name: created.name }
234
+ });
235
+ return c.json(toWire(created), 201);
236
+ });
237
+ // ── Get ──────────────────────────────────────────────────────────────────
238
+ api.openapi(projectGetRoute, async (c) => {
239
+ const principal = c.get('principal');
240
+ const { id } = c.req.valid('param');
241
+ const project = await readProject(db, principal, id);
242
+ if (!project)
243
+ return c.json(notFound(), 404);
244
+ return c.json(toWire(project), 200);
245
+ });
246
+ // ── Update ───────────────────────────────────────────────────────────────
247
+ api.openapi(projectUpdateRoute, async (c) => {
248
+ const principal = c.get('principal');
249
+ const { id } = c.req.valid('param');
250
+ const patch = c.req.valid('json');
251
+ const gate = await gateMutation(c, id, 'manager');
252
+ if ('response' in gate)
253
+ return gate.response;
254
+ // `archived_at IS NULL` again in the WHERE: an archive landing between
255
+ // the gate and this write must win.
256
+ const updated = await db
257
+ .update(projects)
258
+ .set({
259
+ ...(patch.name !== undefined ? { name: patch.name } : {}),
260
+ ...(patch.description !== undefined ? { description: patch.description } : {}),
261
+ ...(patch.metadata !== undefined ? { metadata: patch.metadata } : {}),
262
+ updatedAt: new Date()
263
+ })
264
+ .where(and(eq(projects.id, id), eq(projects.workspaceId, principal.workspaceId), isNull(projects.archivedAt)))
265
+ .returning({ id: projects.id });
266
+ if (updated.length === 0)
267
+ return c.json(archived(), 409);
268
+ const project = (await readProject(db, principal, id));
269
+ c.set('audit', {
270
+ action: 'project.update',
271
+ resourceType: 'project',
272
+ resourceId: id,
273
+ metadata: { fields: Object.keys(patch) }
274
+ });
275
+ return c.json(toWire(project), 200);
276
+ });
277
+ // ── Archive / unarchive ──────────────────────────────────────────────────
278
+ // A project is never deleted. Archiving cascades onto NOTHING: what a tool
279
+ // linked to the project keeps working; only writes THROUGH a project grant
280
+ // stop (the archived-write rule of the predicate).
281
+ api.openapi(projectArchiveRoute, async (c) => {
282
+ const principal = c.get('principal');
283
+ const { id } = c.req.valid('param');
284
+ const gate = await gateMutation(c, id, 'manager');
285
+ if ('response' in gate)
286
+ return gate.response;
287
+ const updated = await db
288
+ .update(projects)
289
+ .set({ archivedAt: new Date(), updatedAt: new Date() })
290
+ .where(and(eq(projects.id, id), eq(projects.workspaceId, principal.workspaceId), isNull(projects.archivedAt)))
291
+ .returning({ id: projects.id });
292
+ if (updated.length === 0)
293
+ return c.json(archived(), 409);
294
+ const project = (await readProject(db, principal, id));
295
+ c.set('audit', { action: 'project.archive', resourceType: 'project', resourceId: id });
296
+ return c.json(toWire(project), 200);
297
+ });
298
+ api.openapi(projectUnarchiveRoute, async (c) => {
299
+ const principal = c.get('principal');
300
+ const { id } = c.req.valid('param');
301
+ const gate = await gateMutation(c, id, 'manager', { allowArchived: true });
302
+ if ('response' in gate)
303
+ return gate.response;
304
+ const updated = await db
305
+ .update(projects)
306
+ .set({ archivedAt: null, updatedAt: new Date() })
307
+ .where(and(eq(projects.id, id), eq(projects.workspaceId, principal.workspaceId), isNotNull(projects.archivedAt)))
308
+ .returning({ id: projects.id });
309
+ if (updated.length === 0) {
310
+ return c.json(err('project_not_archived', 'The project is not archived'), 409);
311
+ }
312
+ const project = (await readProject(db, principal, id));
313
+ c.set('audit', { action: 'project.unarchive', resourceType: 'project', resourceId: id });
314
+ return c.json(toWire(project), 200);
315
+ });
316
+ // ── Members ──────────────────────────────────────────────────────────────
317
+ // People and teams in ONE list, newest first, on one value-carrying cursor
318
+ // (`created_at`, `id`) that both tables answer: the cursor is applied inside
319
+ // each branch, so a page is the same page whichever kind the boundary row
320
+ // is. Raw SQL because a set operation is where drizzle's builder stops.
321
+ api.openapi(projectMembersListRoute, async (c) => {
322
+ const principal = c.get('principal');
323
+ const { id } = c.req.valid('param');
324
+ const { cursor, limit } = c.req.valid('query');
325
+ if ((await projectRole(db, principal, id)) === null)
326
+ return c.json(notFound(), 404);
327
+ const after = decodeKeysetCursor(cursor);
328
+ const before = (createdAt, rowId) => after
329
+ ? sql `AND (${sql.raw(createdAt)}, ${sql.raw(rowId)}) < (${after.createdAt}::timestamptz, ${after.id}::uuid)`
330
+ : sql ``;
331
+ const result = await db.execute(sql `
332
+ SELECT 'person' AS kind, pm.id, pm.created_at::text AS created_at_text, pm.created_at, pm.role, pm.added_by,
333
+ wm.user_id, u.email, u.name,
334
+ NULL::uuid AS team_id, NULL::text AS slug, NULL::int AS members_count, NULL::text AS hub_team_id
335
+ FROM project_members pm
336
+ JOIN workspace_members wm ON wm.id = pm.member_id
337
+ JOIN "user" u ON u.id = wm.user_id
338
+ WHERE pm.project_id = ${id} ${before('pm.created_at', 'pm.id')}
339
+ UNION ALL
340
+ SELECT 'team' AS kind, pt.id, pt.created_at::text AS created_at_text, pt.created_at, pt.role, pt.added_by,
341
+ NULL::text AS user_id, NULL::text AS email, t.name,
342
+ t.id AS team_id, t.slug,
343
+ (SELECT count(*)::int FROM workspace_team_members tm WHERE tm.team_id = t.id) AS members_count,
344
+ t.hub_team_id
345
+ FROM project_teams pt
346
+ JOIN workspace_teams t ON t.id = pt.team_id
347
+ WHERE pt.project_id = ${id} ${before('pt.created_at', 'pt.id')}
348
+ ORDER BY created_at DESC, id DESC
349
+ LIMIT ${limit + 1}
350
+ `);
351
+ const rows = result.rows;
352
+ const page = rows.slice(0, limit);
353
+ const last = rows.length > limit ? page[page.length - 1] : undefined;
354
+ const members = page.map((row) => row.kind === 'person'
355
+ ? memberToWire({
356
+ userId: row.user_id,
357
+ email: row.email,
358
+ name: row.name,
359
+ role: row.role,
360
+ addedBy: row.added_by,
361
+ createdAt: new Date(row.created_at)
362
+ })
363
+ : teamToWire({
364
+ teamId: row.team_id,
365
+ slug: row.slug,
366
+ name: row.name,
367
+ membersCount: row.members_count ?? 0,
368
+ hubTeamId: row.hub_team_id,
369
+ role: row.role,
370
+ addedBy: row.added_by,
371
+ createdAt: new Date(row.created_at)
372
+ }));
373
+ return c.json({
374
+ members,
375
+ nextCursor: last ? encodeKeysetCursor({ id: last.id, createdAtText: last.created_at_text }) : null
376
+ }, 200);
377
+ });
378
+ /** One member of one project, by the person's user id, or undefined. */
379
+ async function findProjectMember(conn, projectId, userId) {
380
+ const [row] = await conn
381
+ .select(memberSelection)
382
+ .from(projectMembers)
383
+ .innerJoin(workspaceMembers, eq(projectMembers.memberId, workspaceMembers.id))
384
+ .innerJoin(userTable, eq(workspaceMembers.userId, userTable.id))
385
+ .where(and(eq(projectMembers.projectId, projectId), eq(workspaceMembers.userId, userId)))
386
+ .limit(1);
387
+ return row;
388
+ }
389
+ /** One team's entry on one project, by the team id, or undefined. */
390
+ async function findProjectTeam(conn, projectId, teamId) {
391
+ const [row] = await conn
392
+ .select(teamSelection)
393
+ .from(projectTeams)
394
+ .innerJoin(workspaceTeams, eq(projectTeams.teamId, workspaceTeams.id))
395
+ .where(and(eq(projectTeams.projectId, projectId), eq(workspaceTeams.id, teamId)))
396
+ .limit(1);
397
+ return row;
398
+ }
399
+ /**
400
+ * Adds a team of the request's workspace to the project (PRDCT-2794): the
401
+ * team side of the members POST. A team of another workspace, or none, is
402
+ * 404 `team_not_found`; a repeat is 409 `already_member`.
403
+ */
404
+ async function addTeam(c, id, teamId, role) {
405
+ const principal = c.get('principal');
406
+ const [team] = await db
407
+ .select({
408
+ teamId: workspaceTeams.id,
409
+ slug: workspaceTeams.slug,
410
+ name: workspaceTeams.name,
411
+ membersCount: TEAM_MEMBERS_COUNT,
412
+ hubTeamId: workspaceTeams.hubTeamId
413
+ })
414
+ .from(workspaceTeams)
415
+ .where(and(eq(workspaceTeams.id, teamId), eq(workspaceTeams.workspaceId, principal.workspaceId)))
416
+ .limit(1);
417
+ if (!team)
418
+ return c.json(err('team_not_found', 'No team of this workspace matches'), 404);
419
+ let inserted;
420
+ try {
421
+ inserted = await whileLive(principal, id, (tx) => tx
422
+ .insert(projectTeams)
423
+ .values({ projectId: id, teamId: team.teamId, role, addedBy: principal.userId })
424
+ .onConflictDoNothing({ target: [projectTeams.projectId, projectTeams.teamId] })
425
+ .returning({ createdAt: projectTeams.createdAt }));
426
+ }
427
+ catch (cause) {
428
+ // The team went between the read above and the insert (a delete at the
429
+ // hub's next pass, an admin's): the foreign key says so, and the answer
430
+ // is the same 404 as a team that never was (verifier round 1, F2).
431
+ if (isForeignKeyViolation(cause, PROJECT_TEAM_FK)) {
432
+ return c.json(err('team_not_found', 'No team of this workspace matches'), 404);
433
+ }
434
+ throw cause;
435
+ }
436
+ if (inserted === null)
437
+ return c.json(archived(), 409);
438
+ const [row] = inserted;
439
+ if (!row)
440
+ return c.json(err('already_member', 'This team is already a member of the project'), 409);
441
+ c.set('audit', {
442
+ action: 'project.team_add',
443
+ resourceType: 'project',
444
+ resourceId: id,
445
+ metadata: { teamId: team.teamId, role }
446
+ });
447
+ return c.json(teamToWire({ ...team, role, addedBy: principal.userId, createdAt: row.createdAt }), 201);
448
+ }
449
+ api.openapi(projectMemberAddRoute, async (c) => {
450
+ const principal = c.get('principal');
451
+ const { id } = c.req.valid('param');
452
+ const body = c.req.valid('json');
453
+ const gate = await gateMutation(c, id, 'manager');
454
+ if ('response' in gate)
455
+ return gate.response;
456
+ if (body.teamId !== undefined)
457
+ return (await addTeam(c, id, body.teamId, body.role));
458
+ // The target comes from THIS workspace's own roster, by user id or by
459
+ // email, and nowhere else: nothing is invited, claimed or minted here.
460
+ const [target] = await db
461
+ .select({
462
+ id: workspaceMembers.id,
463
+ userId: workspaceMembers.userId,
464
+ email: userTable.email,
465
+ name: userTable.name,
466
+ origin: workspaceMembers.origin,
467
+ isActive: workspaceMembers.isActive
468
+ })
469
+ .from(workspaceMembers)
470
+ .innerJoin(userTable, eq(workspaceMembers.userId, userTable.id))
471
+ .where(and(eq(workspaceMembers.workspaceId, principal.workspaceId), body.userId !== undefined
472
+ ? eq(workspaceMembers.userId, body.userId)
473
+ : sql `lower(${userTable.email}) = ${body.email.toLowerCase()}`))
474
+ .limit(1);
475
+ if (!target || !target.isActive) {
476
+ return c.json(err('member_not_found', 'No active member of this workspace matches'), 404);
477
+ }
478
+ // A guest membership exists for principal resolution only (D2): it can
479
+ // never hold a project grant. The predicates would ignore the row anyway;
480
+ // refusing it here keeps the table honest.
481
+ if (target.origin === 'guest') {
482
+ return c.json(err('guest_target', 'A guest cannot be a project member: invite them to the workspace as a member first'), 403);
483
+ }
484
+ // The read above builds the answer; the one that counts is the locked
485
+ // re-read inside the insert's transaction (`holdLiveMembership`): a
486
+ // removal that landed since is seen here, one still running waits for
487
+ // this insert and takes it in its second pass.
488
+ const inserted = await whileLive(principal, id, async (tx) => {
489
+ if (!(await holdLiveMembership(tx, principal.workspaceId, { memberId: target.id }))) {
490
+ return 'member_gone';
491
+ }
492
+ // A grant already there answers `already_member` WITHOUT an insert (the
493
+ // verifier's F4: a duplicate add beside a removal deadlocked, 14 of 40
494
+ // removals answered 500). Under the membership share lock, a removal's
495
+ // uncommitted delete of this grant keeps the row visible to a plain
496
+ // read, so the add answers 409 and never waits on that delete, while
497
+ // the removal's update waits on the add's share lock: the insert's
498
+ // conflict check would wait on the delete, a cycle.
499
+ const [existing] = await tx
500
+ .select({ id: projectMembers.id })
501
+ .from(projectMembers)
502
+ .where(and(eq(projectMembers.projectId, id), eq(projectMembers.memberId, target.id)))
503
+ .limit(1);
504
+ if (existing)
505
+ return [];
506
+ return tx
507
+ .insert(projectMembers)
508
+ .values({ projectId: id, memberId: target.id, role: body.role, addedBy: principal.userId })
509
+ .onConflictDoNothing({ target: [projectMembers.projectId, projectMembers.memberId] })
510
+ .returning({ createdAt: projectMembers.createdAt });
511
+ });
512
+ if (inserted === null)
513
+ return c.json(archived(), 409);
514
+ if (inserted === 'member_gone') {
515
+ return c.json(err('member_not_found', 'No active member of this workspace matches'), 404);
516
+ }
517
+ const [row] = inserted;
518
+ if (!row) {
519
+ return c.json(err('already_member', 'This person is already a member of the project'), 409);
520
+ }
521
+ // The answer is built from what the insert returned and the target row
522
+ // read above, never from a re-read outside the transaction: a removal
523
+ // landing in between would turn a succeeded add into a 500.
524
+ const member = { ...target, role: body.role, addedBy: principal.userId, createdAt: row.createdAt };
525
+ c.set('audit', {
526
+ action: 'project.member_add',
527
+ resourceType: 'project',
528
+ resourceId: id,
529
+ metadata: { targetUserId: target.userId, role: body.role }
530
+ });
531
+ return c.json(memberToWire(member), 201);
532
+ });
533
+ api.openapi(projectMemberRoleRoute, async (c) => {
534
+ const principal = c.get('principal');
535
+ const { id, userId } = c.req.valid('param');
536
+ const { role } = c.req.valid('json');
537
+ const gate = await gateMutation(c, id, 'manager');
538
+ if ('response' in gate)
539
+ return gate.response;
540
+ const target = await findProjectMember(db, id, userId);
541
+ if (!target)
542
+ return c.json(err('member_not_found', 'This person is not a member of the project'), 404);
543
+ const done = await whileLive(principal, id, (tx) => tx.update(projectMembers).set({ role }).where(eq(projectMembers.id, target.id)));
544
+ if (done === null)
545
+ return c.json(archived(), 409);
546
+ c.set('audit', {
547
+ action: 'project.member_role',
548
+ resourceType: 'project',
549
+ resourceId: id,
550
+ metadata: { targetUserId: userId, from: target.role, to: role }
551
+ });
552
+ return c.json(memberToWire({ ...target, role }), 200);
553
+ });
554
+ // A manager removes anyone; any member removes THEMSELVES, whatever their
555
+ // role. There is no last-manager guard: a workspace owner or admin manages
556
+ // every project, so none is ever locked.
557
+ api.openapi(projectMemberRemoveRoute, async (c) => {
558
+ const principal = c.get('principal');
559
+ const { id, userId } = c.req.valid('param');
560
+ const self = userId === principal.userId;
561
+ const gate = await gateMutation(c, id, self ? 'viewer' : 'manager');
562
+ if ('response' in gate)
563
+ return gate.response;
564
+ const target = await findProjectMember(db, id, userId);
565
+ if (!target)
566
+ return c.json(err('member_not_found', 'This person is not a member of the project'), 404);
567
+ const done = await whileLive(principal, id, (tx) => tx.delete(projectMembers).where(eq(projectMembers.id, target.id)));
568
+ if (done === null)
569
+ return c.json(archived(), 409);
570
+ c.set('audit', {
571
+ action: 'project.member_remove',
572
+ resourceType: 'project',
573
+ resourceId: id,
574
+ metadata: { targetUserId: userId, role: target.role, self }
575
+ });
576
+ return c.json(memberToWire(target), 200);
577
+ });
578
+ // ── Teams on a project (PRDCT-2794) ──────────────────────────────────────
579
+ // The same gate and order as a person's entry: 404, 403, 409. A team is
580
+ // never "itself" (no self-removal branch): a manager acts, or an owner or
581
+ // admin of the workspace.
582
+ api.openapi(projectTeamRoleRoute, async (c) => {
583
+ const principal = c.get('principal');
584
+ const { id, teamId } = c.req.valid('param');
585
+ const { role } = c.req.valid('json');
586
+ const gate = await gateMutation(c, id, 'manager');
587
+ if ('response' in gate)
588
+ return gate.response;
589
+ const target = await findProjectTeam(db, id, teamId);
590
+ if (!target)
591
+ return c.json(err('team_not_found', 'This team is not a member of the project'), 404);
592
+ const done = await whileLive(principal, id, (tx) => tx.update(projectTeams).set({ role }).where(eq(projectTeams.id, target.id)));
593
+ if (done === null)
594
+ return c.json(archived(), 409);
595
+ c.set('audit', {
596
+ action: 'project.team_role',
597
+ resourceType: 'project',
598
+ resourceId: id,
599
+ metadata: { teamId, from: target.role, to: role }
600
+ });
601
+ return c.json(teamToWire({ ...target, role }), 200);
602
+ });
603
+ api.openapi(projectTeamRemoveRoute, async (c) => {
604
+ const principal = c.get('principal');
605
+ const { id, teamId } = c.req.valid('param');
606
+ const gate = await gateMutation(c, id, 'manager');
607
+ if ('response' in gate)
608
+ return gate.response;
609
+ const target = await findProjectTeam(db, id, teamId);
610
+ if (!target)
611
+ return c.json(err('team_not_found', 'This team is not a member of the project'), 404);
612
+ const done = await whileLive(principal, id, (tx) => tx.delete(projectTeams).where(eq(projectTeams.id, target.id)));
613
+ if (done === null)
614
+ return c.json(archived(), 409);
615
+ c.set('audit', {
616
+ action: 'project.team_remove',
617
+ resourceType: 'project',
618
+ resourceId: id,
619
+ metadata: { teamId, role: target.role }
620
+ });
621
+ return c.json(teamToWire(target), 200);
622
+ });
623
+ }
624
+ //# sourceMappingURL=projects.js.map