@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,649 @@
1
+ import type { OpenAPIHono } from '@hono/zod-openapi';
2
+ import { and, eq, ne, sql } from 'drizzle-orm';
3
+ import { ACTIVE_WORKSPACE_HEADER } from '@antasphere/chassis-contract';
4
+ import {
5
+ defaultWorkspaceSetRoute,
6
+ workspaceCreateRoute,
7
+ workspaceUpdateRoute
8
+ } from '@antasphere/chassis-contract/routes';
9
+ import type { WorkspaceLook } from '@antasphere/chassis-contract';
10
+ import { apiKeys, workspaceMembers, workspaces, type Db, type DbConn } from '@antasphere/chassis-db';
11
+ import type { AuditService } from '../audit/service.js';
12
+ import type { Auth } from '../identity/better-auth.js';
13
+ import { projectOrgMembership } from '../identity/hub-projection.js';
14
+ import { requireAuth, requireRole } from '../middleware/auth-context.js';
15
+ import type { ReconcilePassOutcome } from '../identity/hub-reconcile.js';
16
+ import type { HubOrgCreator } from '../identity/hub-user-client.js';
17
+ import type { Logger } from '../logger.js';
18
+ import type { RateLimiterAbstract } from 'rate-limiter-flexible';
19
+ import type { ClientIpFn } from '../middleware/rate-limit.js';
20
+ import type { PlatformRegistry } from '../platform/registry.js';
21
+
22
+ const err = (code: string, message: string) => ({ error: { code, message } });
23
+
24
+ /**
25
+ * POST /workspaces — a signed-in person creates ANOTHER workspace from inside
26
+ * the product (PRDCT-2444 self-hosted, PRDCT-2443 cloud). ADR 014 kept this
27
+ * door shut in the template ("workspace creation is a product decision");
28
+ * this module is that decision, taken.
29
+ *
30
+ * One route, two editions:
31
+ * - oss: `WorkspaceService.create` (the setup path) under a per-user
32
+ * advisory lock + the operator's `MAX_WORKSPACES_PER_USER` cap — the
33
+ * hub's `POST /orgs` model, mirrored.
34
+ * - cloud: the organization is created AT THE HUB as the caller (their own
35
+ * stored grant, ADR 019 — no service key, no target-user parameter), then
36
+ * the caller's reconcile is forced so the local projection and the
37
+ * `origin='hub'` owner membership exist before the answer. The answer
38
+ * carries the LOCAL workspace id.
39
+ *
40
+ * SESSIONS ONLY, twice: the path is deliberately UNLISTED in the fail-closed
41
+ * machine scope allowlist (middleware/scopes.ts — never list it), and the
42
+ * handler re-checks `via === 'session'` itself. /mcp carries no tool for it.
43
+ *
44
+ * A WORKSPACE NEVER LEARNS WHAT ITS MEMBERS DO ELSEWHERE: the route is
45
+ * exempt from the generic audit middleware (which would attribute the event
46
+ * to the workspace the person happened to be in) and writes its one genesis
47
+ * row, `workspace.create`, into the NEW workspace's own trail — the setup
48
+ * pattern. The idempotency claim is the only trace left in the caller's
49
+ * current scope, and it is a sealed replay cache, never a readable surface.
50
+ */
51
+
52
+ /**
53
+ * Per-user creation lock, two-int advisory form `(classid, hashtext(userId))`.
54
+ * 7432006 is free in this repository (7432001 migrations, 7432002/3 the
55
+ * last-owner guards, 7432004 the grant refresh + pg-boss, 7432005 a tool's
56
+ * uploads) and is, on purpose, the same number the hub uses for the same
57
+ * job in ITS database.
58
+ */
59
+ const WORKSPACE_CREATE_LOCK_NAMESPACE = 7432006;
60
+
61
+ /** Why this caller may not create a workspace right now — also the wire error code. */
62
+ export type WorkspaceCreationRefusal =
63
+ | 'session_required'
64
+ | 'workspace_creation_disabled'
65
+ | 'guest_forbidden'
66
+ | 'workspace_limit_reached'
67
+ | 'hub_link_required'
68
+ | 'restricted_tool';
69
+
70
+ export interface WorkspaceCreationPolicy {
71
+ /** `MAX_WORKSPACES_PER_USER`. 0 closes creation for everyone, on both editions. */
72
+ maxPerUser: number;
73
+ /**
74
+ * `TOOL_RESTRICTED` (PRDCT-2947): on cloud, organizations that open this
75
+ * tool are granted by Antasphere, so nobody creates one from here. Judged
76
+ * only where `hasHubLink` is set (the cloud edition); inert on self-hosted,
77
+ * where a workspace is local and the switch means nothing.
78
+ */
79
+ restricted?: boolean | undefined;
80
+ /**
81
+ * Cloud only: whether the user holds a LIVE hub link (a stored grant) —
82
+ * the organization is created at the hub AS THEM, so without one there is
83
+ * nothing to create it with. undefined on oss.
84
+ */
85
+ hasHubLink?: ((userId: string) => Promise<boolean>) | undefined;
86
+ }
87
+
88
+ /**
89
+ * THE ONE eligibility rule, shared by the route (inside its locked
90
+ * transaction) and by `/me.canCreateWorkspace` — so the flag can never
91
+ * promise what the route refuses. Order = the order the refusals are
92
+ * reported in.
93
+ *
94
+ * - sessions only;
95
+ * - the operator's dial at 0 closes the door on both editions;
96
+ * - a GUEST-ONLY user (every active membership is `origin='guest'`) is an
97
+ * outsider invited to single resources (D2): refused like every other
98
+ * workspace-level act. One non-guest active membership anywhere lifts it
99
+ * — it is a fact about the PERSON, not about the workspace they are in;
100
+ * - oss: fewer ACTIVE OWNER memberships than the cap (setup's counts);
101
+ * - cloud: the tool not restricted (TOOL_RESTRICTED), then a live hub link.
102
+ * The count is the hub's own cap, asked there.
103
+ */
104
+ export async function workspaceCreationRefusal(
105
+ conn: DbConn,
106
+ policy: WorkspaceCreationPolicy,
107
+ caller: { userId: string; via: 'session' | 'api_key' | 'oauth' }
108
+ ): Promise<WorkspaceCreationRefusal | null> {
109
+ if (caller.via !== 'session') return 'session_required';
110
+ if (policy.maxPerUser === 0) return 'workspace_creation_disabled';
111
+ const rows = await conn
112
+ .select({ origin: workspaceMembers.origin, role: workspaceMembers.role })
113
+ .from(workspaceMembers)
114
+ .where(and(eq(workspaceMembers.userId, caller.userId), eq(workspaceMembers.isActive, true)));
115
+ if (rows.length > 0 && rows.every((r) => r.origin === 'guest')) return 'guest_forbidden';
116
+ if (policy.hasHubLink) {
117
+ if (policy.restricted) return 'restricted_tool';
118
+ return (await policy.hasHubLink(caller.userId)) ? null : 'hub_link_required';
119
+ }
120
+ const owned = rows.filter((r) => r.role === 'owner').length;
121
+ return owned >= policy.maxPerUser ? 'workspace_limit_reached' : null;
122
+ }
123
+
124
+ /**
125
+ * Every refusal but `guest_forbidden`, whose sentence is the tool's
126
+ * (`WorkspaceRouteDeps.guestForbiddenMessage`), and `restricted_tool`, whose
127
+ * sentence names the tool (`restrictedToolMessage`).
128
+ */
129
+ const CHASSIS_REFUSAL_MESSAGES: Record<
130
+ Exclude<WorkspaceCreationRefusal, 'guest_forbidden' | 'restricted_tool'>,
131
+ string
132
+ > = {
133
+ session_required: 'Creating a workspace requires a browser session',
134
+ workspace_creation_disabled: 'Workspace creation is closed on this instance',
135
+ workspace_limit_reached: 'This account already owns the maximum number of workspaces',
136
+ hub_link_required: 'Sign in with Antasphere to create an organization'
137
+ };
138
+
139
+ /**
140
+ * The restricted tool's refusal (PRDCT-2947), tool-neutral through the
141
+ * identity: who opens an organization to the tool, and whom to ask.
142
+ */
143
+ export const restrictedToolMessage = (displayName: string): string =>
144
+ `Organizations that open ${displayName} are granted by Antasphere; ask Antasphere to open it for yours`;
145
+
146
+ /**
147
+ * The creation rate wall. Two buckets, spent IN THE HANDLER once the caller
148
+ * is identified — deliberately not a path-mounted middleware:
149
+ *
150
+ * - a path mount counts every method (OPTIONS/HEAD/PUT on a path whose only
151
+ * handler is the POST) and every unauthenticated request, so anyone could
152
+ * spend an address's budget without a session and lock the people behind
153
+ * it out of a route they had never used;
154
+ * - here only an IDENTIFIED caller's POST costs anything. The person bucket
155
+ * is judged FIRST, and a person it refuses never reaches the address
156
+ * bucket — so one person can take at most their own share (a tenth) of
157
+ * the address's budget, and an office behind one NAT address is not
158
+ * locked out by one colleague. A single person still cannot hammer the
159
+ * hub: 60 attempts an hour, refused ones included, zero-membership cloud
160
+ * sessions included (they are identified by their session here, which a
161
+ * principal-keyed middleware could not do).
162
+ *
163
+ * An Idempotency-Key replay never reaches the handler and costs nothing.
164
+ */
165
+ export interface WorkspaceCreateWall {
166
+ /** Per person, key `u:<userId>`. */
167
+ person: RateLimiterAbstract;
168
+ /** Per address — larger than the person bucket, identified callers only. */
169
+ address: RateLimiterAbstract;
170
+ }
171
+
172
+ const personKey = (userId: string) => `u:${userId}`;
173
+
174
+ async function bucketExhausted(limiter: RateLimiterAbstract, key: string): Promise<boolean> {
175
+ // A backend read error fails open, like every other read of a limiter here.
176
+ const state = await limiter.get(key).catch(() => null);
177
+ return Boolean(state && state.remainingPoints <= 0 && state.msBeforeNext > 0);
178
+ }
179
+
180
+ /**
181
+ * Read-only: would the wall refuse this person at this address right now?
182
+ * `/me.canCreateWorkspace` asks it so the flag stays honest about the wall.
183
+ */
184
+ export async function workspaceCreateWallClosed(
185
+ wall: WorkspaceCreateWall,
186
+ address: string,
187
+ userId: string
188
+ ): Promise<boolean> {
189
+ return (
190
+ (await bucketExhausted(wall.person, personKey(userId))) || (await bucketExhausted(wall.address, address))
191
+ );
192
+ }
193
+
194
+ /** Spend one attempt. False = walled (429). */
195
+ async function spendCreationAttempt(
196
+ wall: WorkspaceCreateWall,
197
+ address: string,
198
+ userId: string
199
+ ): Promise<boolean> {
200
+ // A walled ADDRESS refuses without charging the person for it.
201
+ if (await bucketExhausted(wall.address, address)) return false;
202
+ try {
203
+ await wall.person.consume(personKey(userId));
204
+ } catch {
205
+ return false; // the person's own budget is spent: the address bucket is NOT touched
206
+ }
207
+ try {
208
+ await wall.address.consume(address);
209
+ } catch {
210
+ return false;
211
+ }
212
+ return true;
213
+ }
214
+
215
+ /** Control-flow marker: a refusal decided inside the locked transaction. */
216
+ class CreationRefused extends Error {
217
+ constructor(readonly refusal: WorkspaceCreationRefusal) {
218
+ super(refusal);
219
+ }
220
+ }
221
+
222
+ /** Cloud-only collaborators. Absent on oss: the route carries zero hub surface there. */
223
+ export interface WorkspaceCloudDeps {
224
+ /** The ONE hub call, isolated (identity/hub-user-client.ts `createOrg`). */
225
+ createOrg: HubOrgCreator;
226
+ /** The login-grade, TTL-bypassing reconcile pass (identity/hub-reconcile.ts). */
227
+ forceReconcile: (userId: string) => Promise<ReconcilePassOutcome>;
228
+ hasHubLink: (userId: string) => Promise<boolean>;
229
+ /** The hub console: where a hub-origin workspace is renamed (P7's pointer). */
230
+ manageUrl: string;
231
+ }
232
+
233
+ export interface WorkspaceRouteDeps {
234
+ db: Db;
235
+ auth: Auth;
236
+ audit: AuditService;
237
+ registry: PlatformRegistry;
238
+ logger: Logger;
239
+ clientIp: ClientIpFn;
240
+ wall: WorkspaceCreateWall;
241
+ maxPerUser: number;
242
+ /** `TOOL_RESTRICTED` (see `WorkspaceCreationPolicy.restricted`). */
243
+ restricted: boolean;
244
+ cloud?: WorkspaceCloudDeps | undefined;
245
+ /** The tool's `guest_forbidden` sentence (`copy.guestForbidden`), the same one the route guard answers with. */
246
+ guestForbiddenMessage: string;
247
+ /** The tool's name in the restricted refusal (`identity.displayName`). */
248
+ displayName: string;
249
+ }
250
+
251
+ export function workspaceCreationPolicy(
252
+ deps: Pick<WorkspaceRouteDeps, 'maxPerUser' | 'restricted' | 'cloud'>
253
+ ): WorkspaceCreationPolicy {
254
+ return { maxPerUser: deps.maxPerUser, restricted: deps.restricted, hasHubLink: deps.cloud?.hasHubLink };
255
+ }
256
+
257
+ export function registerWorkspaceRoutes(api: OpenAPIHono, deps: WorkspaceRouteDeps): void {
258
+ const { db, auth, audit, registry, logger, cloud } = deps;
259
+ const policy = workspaceCreationPolicy(deps);
260
+ const REFUSAL_MESSAGES: Record<WorkspaceCreationRefusal, string> = {
261
+ ...CHASSIS_REFUSAL_MESSAGES,
262
+ guest_forbidden: deps.guestForbiddenMessage,
263
+ restricted_tool: restrictedToolMessage(deps.displayName)
264
+ };
265
+
266
+ api.openapi(workspaceCreateRoute, async (c) => {
267
+ const principal = c.get('principal');
268
+ // Defense in depth: the scope allowlist already 403s every machine
269
+ // principal (POST /workspaces is unlisted, fail-closed); this explicit
270
+ // check stays so a future allowlist edit cannot silently hand workspace
271
+ // creation to an API key or an OAuth bearer.
272
+ if (principal && principal.via !== 'session') {
273
+ return c.json(err('session_required', REFUSAL_MESSAGES.session_required), 403);
274
+ }
275
+
276
+ // The caller. A resolved principal is a session by now. WITHOUT one, the
277
+ // cloud edition still serves the ZERO-MEMBERSHIP session (the /me zero
278
+ // state: a hub user whose last organization was removed creates their
279
+ // next one here) — resolved route-locally, the /me pattern, and ONLY
280
+ // for the selector-less request. On oss no principal means no active
281
+ // membership of this instance: not someone who may create here.
282
+ let userId: string;
283
+ if (principal) {
284
+ userId = principal.userId;
285
+ } else {
286
+ // A request that NAMED a workspace and failed its membership check keeps
287
+ // the fail-closed 401 (no oracle about that workspace) — /me's rule.
288
+ if (c.req.header(ACTIVE_WORKSPACE_HEADER)?.trim()) {
289
+ return c.json(err('unauthenticated', 'Authentication required'), 401);
290
+ }
291
+ const session = cloud ? await auth.api.getSession({ headers: c.req.raw.headers }) : null;
292
+ if (!session?.user) return c.json(err('unauthenticated', 'Authentication required'), 401);
293
+ userId = session.user.id;
294
+ }
295
+ const actor = { userId, via: 'session' as const };
296
+
297
+ // The rate wall, now that the caller is a known person (see
298
+ // WorkspaceCreateWall): nothing above this line spends a point.
299
+ if (!(await spendCreationAttempt(deps.wall, deps.clientIp(c), userId))) {
300
+ return c.json(err('rate_limited', 'Too many requests, slow down'), 429);
301
+ }
302
+
303
+ const body = c.req.valid('json');
304
+ const name = body.name.trim();
305
+ if (!name) return c.json(err('validation_error', 'The workspace needs a name'), 400);
306
+ // The look the person picked in the dialog: written on the row the
307
+ // moment it exists (oss) or once it is projected (cloud), so the
308
+ // workspace opens with it on every member's screen, not just this browser's.
309
+ const look: WorkspaceLook = {
310
+ theme: body.look?.theme ?? null,
311
+ pattern: body.look?.pattern ?? null,
312
+ field: body.look?.field ?? null,
313
+ grain: body.look?.grain ?? null
314
+ };
315
+
316
+ let workspaceId: string;
317
+ let workspaceName = name;
318
+ if (!cloud) {
319
+ // ── oss: local creation under the per-user lock ─────────────────────
320
+ try {
321
+ workspaceId = await db.transaction(async (tx) => {
322
+ // Serializes THIS user's creations: two concurrent requests can
323
+ // never both count "one under the cap". Transaction-scoped — it
324
+ // cannot leak.
325
+ await tx.execute(
326
+ sql`SELECT pg_advisory_xact_lock(${WORKSPACE_CREATE_LOCK_NAMESPACE}, hashtext(${userId}))`
327
+ );
328
+ const refusal = await workspaceCreationRefusal(tx, policy, actor);
329
+ if (refusal) throw new CreationRefused(refusal);
330
+ // The registry's WorkspaceService (ADR 014), joined to this
331
+ // transaction exactly as setup does: workspace + ACTIVE OWNER
332
+ // membership (origin defaults to 'local') or nothing.
333
+ const created = await registry.workspaces.create(name, userId, tx);
334
+ await tx
335
+ .update(workspaces)
336
+ .set({
337
+ lookTheme: look.theme,
338
+ lookPattern: look.pattern,
339
+ lookField: look.field,
340
+ lookGrain: look.grain
341
+ })
342
+ .where(eq(workspaces.id, created.workspaceId));
343
+ return created.workspaceId;
344
+ });
345
+ } catch (cause) {
346
+ if (cause instanceof CreationRefused) {
347
+ return c.json(err(cause.refusal, REFUSAL_MESSAGES[cause.refusal]), 403);
348
+ }
349
+ throw cause;
350
+ }
351
+ } else {
352
+ // ── cloud: create at the hub as the caller, then project ────────────
353
+ const refusal = await workspaceCreationRefusal(db, policy, actor);
354
+ if (refusal) return c.json(err(refusal, REFUSAL_MESSAGES[refusal]), 403);
355
+
356
+ const created = await cloud.createOrg(userId, name);
357
+ switch (created.kind) {
358
+ case 'created':
359
+ break;
360
+ case 'limit_reached':
361
+ return c.json(
362
+ err('workspace_limit_reached', 'This account already owns the maximum number of organizations'),
363
+ 403
364
+ );
365
+ case 'invalid':
366
+ return c.json(err('validation_error', 'Antasphere refused this organization name'), 400);
367
+ case 'no_link':
368
+ return c.json(err('hub_link_required', REFUSAL_MESSAGES.hub_link_required), 403);
369
+ case 'grant_dead':
370
+ // The existing live-gate verdict, verbatim: a browser SSO re-login heals it.
371
+ return c.json(
372
+ err(
373
+ 'hub_grant_expired',
374
+ 'Your Antasphere grant on this instance has expired — sign in with Antasphere again to renew it'
375
+ ),
376
+ 401
377
+ );
378
+ case 'reauth_required':
379
+ // The grant is alive but predates `orgs:create` (or a CLI connect
380
+ // replaced it). Same status and same cure as hub_grant_expired — a
381
+ // browser sign-in — under its own code, so it is never mistaken
382
+ // for a refusal that signing in again would not lift.
383
+ return c.json(err('hub_reauth_required', 'Sign in again to create a workspace, then retry'), 401);
384
+ case 'refused':
385
+ return c.json(
386
+ err('hub_refused', 'Antasphere did not accept the creation of this organization'),
387
+ 403
388
+ );
389
+ case 'inconclusive':
390
+ // The existing live-gate verdict's code. Never retried here: the
391
+ // hub commits before it answers, so the organization may exist —
392
+ // if it does, the caller's next reconcile pass projects it.
393
+ return c.json(
394
+ err('hub_unavailable', 'Antasphere could not be reached — the organization was not confirmed'),
395
+ 403
396
+ );
397
+ }
398
+
399
+ // The reconcile IS the projection (the login path's rule): force the
400
+ // caller's pass so the workspace row and the origin='hub' membership
401
+ // are written by the same code every other hub org goes through.
402
+ const pass = await cloud.forceReconcile(userId);
403
+ const find = () =>
404
+ db
405
+ .select({ id: workspaces.id, name: workspaces.name })
406
+ .from(workspaces)
407
+ .innerJoin(
408
+ workspaceMembers,
409
+ and(
410
+ eq(workspaceMembers.workspaceId, workspaces.id),
411
+ eq(workspaceMembers.userId, userId),
412
+ eq(workspaceMembers.isActive, true)
413
+ )
414
+ )
415
+ .where(eq(workspaces.centralAccountId, created.org.id))
416
+ .limit(1);
417
+ let [local] = await find();
418
+ if (!local && created.org.role !== null) {
419
+ // The pass right after the 201 was inconclusive (a hub blip). The
420
+ // 201 itself is hub truth read AS THE USER about THIS org — the same
421
+ // standing as one entry of their GET /orgs list — so project that
422
+ // one entry through the same primitive rather than answer an error
423
+ // for an organization that exists (a retry would create a second).
424
+ // Never from a token, never for anyone but the caller.
425
+ logger.warn(
426
+ { userId, pass },
427
+ 'workspace create: forced reconcile did not project the new org — projecting from the hub 201'
428
+ );
429
+ await projectOrgMembership(db, {
430
+ localUserId: userId,
431
+ hubWorkspaceId: created.org.id,
432
+ hubWorkspaceName: created.org.name,
433
+ role: created.org.role
434
+ });
435
+ [local] = await find();
436
+ }
437
+ if (!local) {
438
+ logger.error({ userId, pass }, 'workspace create: hub org created but no local projection');
439
+ return c.json(
440
+ err('hub_unavailable', 'The organization was created at Antasphere and will appear shortly'),
441
+ 403
442
+ );
443
+ }
444
+ workspaceId = local.id;
445
+ workspaceName = local.name;
446
+ // The look is a LOCAL fact of the projection (the hub reconcile writes
447
+ // the name and the status, never the look's columns).
448
+ await db
449
+ .update(workspaces)
450
+ .set({
451
+ lookTheme: look.theme,
452
+ lookPattern: look.pattern,
453
+ lookField: look.field,
454
+ lookGrain: look.grain
455
+ })
456
+ .where(eq(workspaces.id, workspaceId));
457
+ }
458
+
459
+ // Genesis row in the NEW workspace's own trail — and nowhere else.
460
+ await audit.write({
461
+ workspaceId,
462
+ principal: actor,
463
+ action: 'workspace.create',
464
+ resourceType: 'workspace',
465
+ resourceId: workspaceId,
466
+ requestId: c.get('requestId'),
467
+ ip: deps.clientIp(c),
468
+ metadata: { name: workspaceName }
469
+ });
470
+ registry.events.emit('workspace.created', { workspaceId, ownerUserId: userId });
471
+
472
+ return c.json({ workspace: { id: workspaceId, name: workspaceName, look } }, 201);
473
+ });
474
+
475
+ // ── PATCH /workspace: the active workspace's own settings ───────────────
476
+ // Owners and admins, from a browser session (unlisted in the machine scope
477
+ // allowlist like the create; re-checked here). The route names no id: the
478
+ // request's one workspace is the one edited (ADR 014). A guest never
479
+ // holds admin, so requireRole covers D2 too.
480
+ // PUT /me/default-workspace (PRDCT-2815): the person chooses the workspace
481
+ // a request naming none resolves to. `workspace_members.is_default` and
482
+ // the selection rule (identity/resolve-membership.ts) are shared by both
483
+ // editions; only the WRITER differs. On cloud the hub reconcile is the
484
+ // writer (it clears and sets the flag from the person's account at every
485
+ // pass, so a local write would be undone within minutes): the route
486
+ // refuses with the account site to set it on, whatever the workspace. On
487
+ // self-hosted this route is the writer.
488
+ //
489
+ // A setting of the PERSON: it reads `principal.userId` and never the
490
+ // request's workspace, is open to the person's keys and tokens under the
491
+ // write scope (the CLI sets it), and is refused to a key pinned to one
492
+ // workspace, which reaches that workspace alone and must not change where
493
+ // the person's other credentials land. It writes NO audit row: a
494
+ // workspace never learns what its members do elsewhere, and the row would
495
+ // land in the trail of the workspace the request happened to be in. The
496
+ // path is exempt from the generic audit middleware (audit/service.ts),
497
+ // and the change is on the server log.
498
+ api.use('/me/default-workspace', requireAuth());
499
+ api.openapi(defaultWorkspaceSetRoute, async (c) => {
500
+ const principal = c.get('principal')!;
501
+ if (cloud) {
502
+ return c.json(
503
+ {
504
+ error: {
505
+ code: 'hub_managed',
506
+ message: 'Your default workspace is a setting of your Antasphere account — choose it there',
507
+ details: { manageUrl: cloud.manageUrl }
508
+ }
509
+ },
510
+ 403
511
+ );
512
+ }
513
+ if (principal.apiKeyId) {
514
+ const [key] = await db
515
+ .select({ pin: apiKeys.workspaceId })
516
+ .from(apiKeys)
517
+ .where(eq(apiKeys.id, principal.apiKeyId))
518
+ .limit(1);
519
+ if (!key || key.pin !== null) {
520
+ return c.json(
521
+ err('key_pinned', 'This key is pinned to one workspace and cannot change your default workspace'),
522
+ 403
523
+ );
524
+ }
525
+ }
526
+ const { workspaceId } = c.req.valid('json');
527
+
528
+ if (workspaceId !== null) {
529
+ // An ACTIVE membership of the caller, or nothing: a workspace that
530
+ // does not exist and one the caller does not belong to answer alike.
531
+ const [membership] = await db
532
+ .select({ id: workspaceMembers.id })
533
+ .from(workspaceMembers)
534
+ .where(
535
+ and(
536
+ eq(workspaceMembers.userId, principal.userId),
537
+ eq(workspaceMembers.workspaceId, workspaceId),
538
+ eq(workspaceMembers.isActive, true)
539
+ )
540
+ )
541
+ .limit(1);
542
+ if (!membership) return c.json(err('not_found', 'Workspace not found'), 404);
543
+ }
544
+
545
+ // Clear-then-set in ONE transaction: the partial unique index (user_id
546
+ // WHERE is_default) forbids two trues even transiently (the reconcile's
547
+ // own order). The set re-states the active membership, so a membership
548
+ // switched off between the check and the write is never made the default.
549
+ const chosen = await db.transaction(async (tx) => {
550
+ await tx
551
+ .update(workspaceMembers)
552
+ .set({ isDefault: false })
553
+ .where(
554
+ and(
555
+ eq(workspaceMembers.userId, principal.userId),
556
+ eq(workspaceMembers.isDefault, true),
557
+ ...(workspaceId !== null ? [ne(workspaceMembers.workspaceId, workspaceId)] : [])
558
+ )
559
+ );
560
+ if (workspaceId === null) return null;
561
+ const set = await tx
562
+ .update(workspaceMembers)
563
+ .set({ isDefault: true })
564
+ .where(
565
+ and(
566
+ eq(workspaceMembers.userId, principal.userId),
567
+ eq(workspaceMembers.workspaceId, workspaceId),
568
+ eq(workspaceMembers.isActive, true)
569
+ )
570
+ )
571
+ .returning({ workspaceId: workspaceMembers.workspaceId });
572
+ return set[0]?.workspaceId ?? null;
573
+ });
574
+ if (workspaceId !== null && chosen === null) {
575
+ return c.json(err('not_found', 'Workspace not found'), 404);
576
+ }
577
+ logger.info(
578
+ { userId: principal.userId, via: principal.via, defaultWorkspaceId: chosen },
579
+ 'default workspace changed by the person'
580
+ );
581
+ return c.json({ defaultWorkspaceId: chosen }, 200);
582
+ });
583
+
584
+ api.use('/workspace', requireRole('admin'));
585
+ api.openapi(workspaceUpdateRoute, async (c) => {
586
+ const principal = c.get('principal');
587
+ if (!principal) return c.json(err('unauthenticated', 'Authentication required'), 401);
588
+ if (principal.via !== 'session') {
589
+ return c.json(err('session_required', 'Changing a workspace requires a browser session'), 403);
590
+ }
591
+ const body = c.req.valid('json');
592
+ const patch: Partial<{
593
+ name: string;
594
+ lookTheme: string | null;
595
+ lookPattern: string | null;
596
+ lookField: number | null;
597
+ lookGrain: number | null;
598
+ }> = {};
599
+ if (body.name !== undefined) {
600
+ const name = body.name.trim();
601
+ if (!name) return c.json(err('validation_error', 'The workspace needs a name'), 400);
602
+ // P7: a hub-origin workspace's name is the hub's — renamed there, and
603
+ // the next reconcile pass would overwrite a local rename anyway.
604
+ if (principal.accountRef) {
605
+ return c.json(
606
+ {
607
+ error: {
608
+ code: 'hub_managed',
609
+ message: 'This workspace is an Antasphere organization — rename it at Antasphere',
610
+ details: { manageUrl: cloud?.manageUrl ?? null }
611
+ }
612
+ },
613
+ 403
614
+ );
615
+ }
616
+ patch.name = name;
617
+ }
618
+ if (body.look?.theme !== undefined) patch.lookTheme = body.look.theme;
619
+ if (body.look?.pattern !== undefined) patch.lookPattern = body.look.pattern;
620
+ if (body.look?.field !== undefined) patch.lookField = body.look.field;
621
+ if (body.look?.grain !== undefined) patch.lookGrain = body.look.grain;
622
+
623
+ const [row] = await db
624
+ .update(workspaces)
625
+ .set(patch)
626
+ .where(eq(workspaces.id, principal.workspaceId))
627
+ .returning({
628
+ id: workspaces.id,
629
+ name: workspaces.name,
630
+ lookTheme: workspaces.lookTheme,
631
+ lookPattern: workspaces.lookPattern,
632
+ lookField: workspaces.lookField,
633
+ lookGrain: workspaces.lookGrain
634
+ });
635
+ // The principal's workspace exists by construction (a live join built
636
+ // it); a missing row here is a race with a deletion, refused like a gate.
637
+ if (!row) return c.json(err('forbidden', 'Workspace not found'), 403);
638
+ return c.json(
639
+ {
640
+ workspace: {
641
+ id: row.id,
642
+ name: row.name,
643
+ look: { theme: row.lookTheme, pattern: row.lookPattern, field: row.lookField, grain: row.lookGrain }
644
+ }
645
+ },
646
+ 200
647
+ );
648
+ });
649
+ }
@@ -0,0 +1,2 @@
1
+ export { type PepperRegistry, buildPepperRegistry, parseApiKeyPeppers } from './peppers.js';
2
+ export { ApiKeyService } from './service.js';