@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,559 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
2
+ import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
3
+ import {
4
+ createDatabase,
5
+ createTestApp,
6
+ extractCookie,
7
+ readJson,
8
+ startPostgres,
9
+ type TestApp,
10
+ host
11
+ } from './helpers.js';
12
+ import { FakeHub, type HubUserFixture } from '@antasphere/chassis-server/testing';
13
+ import { InvitationService } from '@antasphere/chassis-server/invitations';
14
+ import * as sso from './sso-helpers.js';
15
+
16
+ /**
17
+ * P7 — cloud-gating the local membership surfaces on hub-origin workspaces
18
+ * (internal/federation.md "Hub-managed membership"; binding plan §7; patterns §6).
19
+ *
20
+ * On EDITION=cloud a hub org projects into a workspace whose MEMBERSHIP is
21
+ * the hub's source of truth. This suite pins:
22
+ *
23
+ * - every local membership MUTATION on the projected workspace answers
24
+ * 403 `hub_managed` with `details.manageUrl` → the hub (invitation
25
+ * create/accept/revoke, member role-change/deactivate/reactivate/delete/
26
+ * remove, reset-link, change-email-link) — and mutates NOTHING;
27
+ * - READS stay: GET /members serves the projected roster, GET /invitations
28
+ * lists;
29
+ * - the SAME credential performs the SAME mutations on a cloud-LOCAL
30
+ * workspace (centralAccountId NULL) — the operator's own — unhindered:
31
+ * the boundary is the workspace's projection, never the edition alone;
32
+ * - /me carries the new adaptation signals (workspace.hubOrigin, per-entry
33
+ * hubOrigin, membership `origin`, hubManageUrl), and since PRDCT-2947 the
34
+ * hub org id rides on each `workspaces[]` row (`centralAccountId`, null on
35
+ * a local workspace) so a CLI can resolve `--org` by name;
36
+ * - machine credentials: the fail-closed scope allowlist keeps every
37
+ * /members + /invitations shape unreachable to keys (403
38
+ * endpoint_not_allowed) — the P7 gate is a second wall behind it;
39
+ * - MCP needs zero changes: tools re-enter /api/v1 in-process, so the
40
+ * tool surface exposes no membership mutation at all, whoami carries the
41
+ * new /me fields, and per-deck reads work on the projected workspace.
42
+ *
43
+ * The MCP leg (three `it`s that call the deck MCP tools) is the tool's: the
44
+ * tool's app keeps it, `apps/server/test/integration/hub-managed-membership-mcp.test.ts`.
45
+ */
46
+
47
+ const OPERATOR = { email: 'operator@p7.test', name: 'Operator', password: 'operator-pass-p7-1' };
48
+ const HUB_ADMIN_EMAIL = 'hub-admin@p7.test';
49
+ const HUB_MEMBER_EMAIL = 'hub-member@p7.test';
50
+ const ORG_A = '77777777-aaaa-4bbb-8ccc-000000000001';
51
+
52
+ let container: StartedPostgreSqlContainer;
53
+ let hub: FakeHub;
54
+ let app: TestApp;
55
+
56
+ let operatorCookie: string;
57
+ /** The setup workspace — cloud-LOCAL (centralAccountId NULL). */
58
+ let operatorWorkspaceId: string;
59
+ let hubAdminCookie: string;
60
+ /** ORG_A's lazy projection — hub-origin (centralAccountId = ORG_A). */
61
+ let projectedWorkspaceId: string;
62
+ /** The hub member's membership ROW id in the projected workspace. */
63
+ let hubMemberRowId: string;
64
+ /** hubAdmin's API key, bound to the projected workspace at mint. */
65
+ let projectedKey: string;
66
+
67
+ let ipCounter = 0;
68
+ const nextIp = () => `10.96.${Math.floor(ipCounter / 250)}.${(ipCounter++ % 250) + 1}`;
69
+
70
+ const json = (body: unknown, headers: Record<string, string> = {}) => ({
71
+ method: 'POST',
72
+ headers: { 'content-type': 'application/json', 'x-forwarded-for': nextIp(), ...headers },
73
+ body: JSON.stringify(body)
74
+ });
75
+
76
+ const hubAdmin: HubUserFixture = {
77
+ sub: 'hub-admin-p7',
78
+ email: HUB_ADMIN_EMAIL,
79
+ name: 'Hub Admin',
80
+ workspaceId: ORG_A,
81
+ role: 'owner',
82
+ workspaceName: 'Org A'
83
+ };
84
+
85
+ const hubMember: HubUserFixture = {
86
+ sub: 'hub-member-p7',
87
+ email: HUB_MEMBER_EMAIL,
88
+ name: 'Hub Member',
89
+ workspaceId: ORG_A,
90
+ role: 'member',
91
+ workspaceName: 'Org A'
92
+ };
93
+
94
+ /** Headers targeting the projected workspace with hubAdmin's session. */
95
+ const asHubAdmin = (extra: Record<string, string> = {}) => ({
96
+ cookie: hubAdminCookie,
97
+ 'x-workspace-id': projectedWorkspaceId,
98
+ ...extra
99
+ });
100
+
101
+ async function expectHubManaged(res: Response): Promise<void> {
102
+ expect(res.status).toBe(403);
103
+ const body = await readJson(res);
104
+ expect(body.error.code).toBe('hub_managed');
105
+ expect(body.error.details.manageUrl).toBe(hub.issuer);
106
+ }
107
+
108
+ beforeAll(async () => {
109
+ [container, hub] = await Promise.all([startPostgres(), FakeHub.start({ clientId: host.hubClientId })]);
110
+ app = await createTestApp(await createDatabase(container, 'p7_hub_managed'), {
111
+ EDITION: 'cloud',
112
+ HUB_ISSUER_URL: hub.issuer,
113
+ HUB_CLIENT_ID: host.hubClientId,
114
+ HUB_CLIENT_SECRET: 'integration-test-hub-secret-p7'
115
+ });
116
+
117
+ // The operator's own cloud-LOCAL workspace (centralAccountId NULL). Cloud
118
+ // setup mints NO workspace (user-scoped federation), so the fixture seeds
119
+ // it directly — the P7 contrast under test is the workspace's PROJECTION,
120
+ // not how it came to exist.
121
+ const setup = await app.app.request(
122
+ '/api/v1/setup',
123
+ json({ setupToken: 'integration-test-setup-token', instanceName: 'P7 Cloud', owner: OPERATOR })
124
+ );
125
+ expect(setup.status).toBe(201);
126
+ operatorWorkspaceId = await sso.seedLocalWorkspace(app, 'P7 Cloud', OPERATOR.email);
127
+ operatorCookie = extractCookie(
128
+ await app.app.request(
129
+ '/api/v1/auth/sign-in/email',
130
+ json({ email: OPERATOR.email, password: OPERATOR.password })
131
+ )
132
+ );
133
+
134
+ // Two hub identities SSO into ORG_A: the first login lazily projects the
135
+ // org into a workspace; the second lands a 'member' row in the SAME one.
136
+ hubAdminCookie = await sso.ssoLogin(app, hub, hubAdmin);
137
+ const me = await readJson(await app.app.request('/api/v1/me', { headers: { cookie: hubAdminCookie } }));
138
+ projectedWorkspaceId = me.activeWorkspaceId;
139
+ const memberCookie = await sso.ssoLogin(app, hub, hubMember);
140
+ const memberMe = await readJson(await app.app.request('/api/v1/me', { headers: { cookie: memberCookie } }));
141
+ expect(memberMe.activeWorkspaceId).toBe(projectedWorkspaceId);
142
+
143
+ const { rows } = await app.db.pool.query(
144
+ `SELECT wm.id FROM workspace_members wm JOIN "user" u ON u.id = wm.user_id
145
+ WHERE wm.workspace_id = $1 AND u.email = $2`,
146
+ [projectedWorkspaceId, HUB_MEMBER_EMAIL]
147
+ );
148
+ hubMemberRowId = rows[0].id;
149
+
150
+ // hubAdmin's API key, minted from the session and PINNED to the projected
151
+ // workspace (the optional least-privilege pin of the user-scoped model) —
152
+ // so every request it makes lands on the projection, as this suite pins.
153
+ const minted = await readJson(
154
+ await app.app.request(
155
+ '/api/v1/api-keys',
156
+ json(
157
+ {
158
+ name: 'p7-key',
159
+ scopes: [host.scopes.read, host.scopes.write],
160
+ workspaceId: projectedWorkspaceId
161
+ },
162
+ asHubAdmin()
163
+ )
164
+ )
165
+ );
166
+ projectedKey = minted.key;
167
+ }, 180_000);
168
+
169
+ afterAll(async () => {
170
+ await app?.stop();
171
+ await Promise.all([container?.stop(), hub?.stop()]);
172
+ });
173
+
174
+ describe('mutations on the HUB-ORIGIN workspace → 403 hub_managed + pointer', () => {
175
+ it('invitation create', async () => {
176
+ await expectHubManaged(
177
+ await app.app.request(
178
+ '/api/v1/invitations',
179
+ json({ email: 'newcomer@p7.test', role: 'member' }, asHubAdmin())
180
+ )
181
+ );
182
+ });
183
+
184
+ it('invitation revoke (any local invitation mutation is hub-managed)', async () => {
185
+ await expectHubManaged(
186
+ await app.app.request('/api/v1/invitations/99999999-9999-4999-8999-999999999999', {
187
+ method: 'DELETE',
188
+ headers: asHubAdmin({ 'x-forwarded-for': nextIp() })
189
+ })
190
+ );
191
+ });
192
+
193
+ it('member role-change, deactivate, reactivate — and the row never moves', async () => {
194
+ await expectHubManaged(
195
+ await app.app.request(`/api/v1/members/${hubMemberRowId}`, {
196
+ ...json({ role: 'admin' }, asHubAdmin()),
197
+ method: 'PATCH'
198
+ })
199
+ );
200
+ await expectHubManaged(
201
+ await app.app.request(`/api/v1/members/${hubMemberRowId}`, {
202
+ ...json({ isActive: false }, asHubAdmin()),
203
+ method: 'PATCH'
204
+ })
205
+ );
206
+ await expectHubManaged(
207
+ await app.app.request(`/api/v1/members/${hubMemberRowId}`, {
208
+ ...json({ isActive: true }, asHubAdmin()),
209
+ method: 'PATCH'
210
+ })
211
+ );
212
+ const { rows } = await app.db.pool.query(`SELECT role, is_active FROM workspace_members WHERE id = $1`, [
213
+ hubMemberRowId
214
+ ]);
215
+ expect(rows[0]).toEqual({ role: 'member', is_active: true });
216
+ });
217
+
218
+ it('member delete', async () => {
219
+ await expectHubManaged(
220
+ await app.app.request(`/api/v1/members/${hubMemberRowId}`, {
221
+ method: 'DELETE',
222
+ headers: asHubAdmin({ 'x-forwarded-for': nextIp() })
223
+ })
224
+ );
225
+ const { rows } = await app.db.pool.query(`SELECT 1 FROM workspace_members WHERE id = $1`, [
226
+ hubMemberRowId
227
+ ]);
228
+ expect(rows).toHaveLength(1);
229
+ });
230
+
231
+ it('member remove (PRDCT-2816) — the row and its grants never move', async () => {
232
+ // A project grant for the hub member, seeded directly: the local project
233
+ // routes are not what this suite is about, the refusal leaving it is.
234
+ const { rows: projectRows } = await app.db.pool.query(
235
+ `INSERT INTO projects (workspace_id, name) VALUES ($1, 'P7 remove') RETURNING id`,
236
+ [projectedWorkspaceId]
237
+ );
238
+ await app.db.pool.query(
239
+ `INSERT INTO project_members (project_id, member_id, role) VALUES ($1, $2, 'viewer')`,
240
+ [projectRows[0].id, hubMemberRowId]
241
+ );
242
+ await expectHubManaged(
243
+ await app.app.request(`/api/v1/members/${hubMemberRowId}/remove`, {
244
+ method: 'POST',
245
+ headers: asHubAdmin({ 'x-forwarded-for': nextIp() })
246
+ })
247
+ );
248
+ const { rows } = await app.db.pool.query(`SELECT role, is_active FROM workspace_members WHERE id = $1`, [
249
+ hubMemberRowId
250
+ ]);
251
+ expect(rows[0]).toEqual({ role: 'member', is_active: true });
252
+ const { rows: grants } = await app.db.pool.query(`SELECT 1 FROM project_members WHERE member_id = $1`, [
253
+ hubMemberRowId
254
+ ]);
255
+ expect(grants).toHaveLength(1);
256
+ });
257
+
258
+ it('reset-link and change-email-link (the credential surfaces are the hub’s too)', async () => {
259
+ await expectHubManaged(
260
+ await app.app.request(`/api/v1/members/${hubMemberRowId}/reset-link`, {
261
+ method: 'POST',
262
+ headers: asHubAdmin({ 'x-forwarded-for': nextIp() })
263
+ })
264
+ );
265
+ await expectHubManaged(
266
+ await app.app.request(
267
+ `/api/v1/members/${hubMemberRowId}/change-email-link`,
268
+ json({ newEmail: 'sideways@p7.test' }, asHubAdmin())
269
+ )
270
+ );
271
+ });
272
+
273
+ it('the gate covers the WHOLE subtree: unrouted mutation shapes under /members + /invitations are refused before any 404 (fail-closed for future routes)', async () => {
274
+ // None of these paths has a handler today — a future mutation added
275
+ // here must be refused by default, never opened. The refusal must win
276
+ // over the JSON 404 terminator.
277
+ await expectHubManaged(await app.app.request('/api/v1/members', json({ anything: true }, asHubAdmin())));
278
+ await expectHubManaged(
279
+ await app.app.request(`/api/v1/members/${hubMemberRowId}/some-future-mutation`, {
280
+ method: 'POST',
281
+ headers: asHubAdmin({ 'x-forwarded-for': nextIp() })
282
+ })
283
+ );
284
+ await expectHubManaged(
285
+ await app.app.request('/api/v1/invitations/99999999-9999-4999-8999-999999999999/resend', {
286
+ method: 'POST',
287
+ headers: asHubAdmin({ 'x-forwarded-for': nextIp() })
288
+ })
289
+ );
290
+ // The same unrouted shapes OFF the projection stay plain 404s — the
291
+ // subtree mount changes nothing for cloud-LOCAL workspaces.
292
+ const local = await app.app.request('/api/v1/members', {
293
+ ...json({ anything: true }, { cookie: operatorCookie })
294
+ });
295
+ expect(local.status).toBe(404);
296
+ });
297
+
298
+ it('invitation ACCEPT against a projected workspace is refused even for a pre-existing row (defense in depth)', async () => {
299
+ // No API path can create this row (the gate above) — seed it through the
300
+ // service directly, the way legacy/seeded data would exist.
301
+ const service = new InvitationService(app.db.db);
302
+ const { rows } = await app.db.pool.query(`SELECT id FROM "user" WHERE email = $1`, [OPERATOR.email]);
303
+ const seeded = await service.create({
304
+ workspaceId: projectedWorkspaceId,
305
+ email: 'seeded-invitee@p7.test',
306
+ role: 'member',
307
+ invitedBy: rows[0].id
308
+ });
309
+ const res = await app.app.request(
310
+ '/api/v1/invitations/accept',
311
+ json({ token: seeded.token, name: 'Seeded', password: 'a-perfectly-fine-password' })
312
+ );
313
+ await expectHubManaged(res);
314
+ // Nothing was minted for the invitee.
315
+ const { rows: users } = await app.db.pool.query(`SELECT 1 FROM "user" WHERE email = $1`, [
316
+ 'seeded-invitee@p7.test'
317
+ ]);
318
+ expect(users).toHaveLength(0);
319
+ });
320
+ });
321
+
322
+ describe('reads on the HUB-ORIGIN workspace stay open', () => {
323
+ it('GET /members serves the projected roster', async () => {
324
+ const res = await app.app.request('/api/v1/members', { headers: asHubAdmin() });
325
+ expect(res.status).toBe(200);
326
+ const { members } = await readJson(res);
327
+ const emails = members.map((m: { email: string }) => m.email).sort();
328
+ expect(emails).toEqual([HUB_ADMIN_EMAIL, HUB_MEMBER_EMAIL]);
329
+ });
330
+
331
+ it('GET /invitations lists', async () => {
332
+ const res = await app.app.request('/api/v1/invitations', { headers: asHubAdmin() });
333
+ expect(res.status).toBe(200);
334
+ });
335
+ });
336
+
337
+ describe('the SAME mutations on a cloud-LOCAL workspace still work (the boundary is projection, not edition)', () => {
338
+ let acceptToken: string;
339
+
340
+ it('operator invites hubAdmin into the operator workspace — 201', async () => {
341
+ const res = await app.app.request(
342
+ '/api/v1/invitations',
343
+ json({ email: HUB_ADMIN_EMAIL, role: 'admin' }, { cookie: operatorCookie })
344
+ );
345
+ expect(res.status).toBe(201);
346
+ const body = await readJson(res);
347
+ acceptToken = body.acceptUrl.split('/invite/')[1] as string;
348
+ });
349
+
350
+ it('hubAdmin ACCEPTS it (public accept works on cloud for local workspaces)', async () => {
351
+ const res = await app.app.request(
352
+ '/api/v1/invitations/accept',
353
+ json({ token: acceptToken }, { cookie: hubAdminCookie })
354
+ );
355
+ expect(res.status).toBe(200);
356
+ const body = await readJson(res);
357
+ expect(body.workspaceId).toBe(operatorWorkspaceId);
358
+ });
359
+
360
+ it('the ONE credential sees both sides: refused on the projection, free on the local workspace', async () => {
361
+ // Same session, local workspace selected: invitation create + revoke work.
362
+ const created = await app.app.request(
363
+ '/api/v1/invitations',
364
+ json(
365
+ { email: 'local-colleague@p7.test', role: 'member' },
366
+ { cookie: hubAdminCookie, 'x-workspace-id': operatorWorkspaceId }
367
+ )
368
+ );
369
+ expect(created.status).toBe(201);
370
+ const { invitation } = await readJson(created);
371
+ const revoked = await app.app.request(`/api/v1/invitations/${invitation.id}`, {
372
+ method: 'DELETE',
373
+ headers: {
374
+ cookie: hubAdminCookie,
375
+ 'x-workspace-id': operatorWorkspaceId,
376
+ 'x-forwarded-for': nextIp()
377
+ }
378
+ });
379
+ expect(revoked.status).toBe(200);
380
+ // Same session, projected workspace selected: refused (pinned above,
381
+ // re-checked here to make the single-credential contrast explicit).
382
+ await expectHubManaged(
383
+ await app.app.request(
384
+ '/api/v1/invitations',
385
+ json({ email: 'local-colleague-2@p7.test', role: 'member' }, asHubAdmin())
386
+ )
387
+ );
388
+ });
389
+
390
+ it('member mutations on the local workspace work: role change — but reset-link refuses EDITION-wide (P8)', async () => {
391
+ const { rows } = await app.db.pool.query(
392
+ `SELECT wm.id FROM workspace_members wm JOIN "user" u ON u.id = wm.user_id
393
+ WHERE wm.workspace_id = $1 AND u.email = $2`,
394
+ [operatorWorkspaceId, HUB_ADMIN_EMAIL]
395
+ );
396
+ const localRowId = rows[0].id;
397
+ // The reset-link mint is the ONE local-workspace exception to "the
398
+ // boundary is projection, not edition": since the P8 close (ADR 017)
399
+ // the whole password-reset surface refuses on cloud — a minted link
400
+ // would dead-end on the refused POST /reset-password. Distinct code
401
+ // from the projection gate: password_reset_disabled, not hub_managed.
402
+ const reset = await app.app.request(`/api/v1/members/${localRowId}/reset-link`, {
403
+ method: 'POST',
404
+ headers: { cookie: operatorCookie, 'x-forwarded-for': nextIp() }
405
+ });
406
+ expect(reset.status).toBe(403);
407
+ expect((await readJson(reset)).error.code).toBe('password_reset_disabled');
408
+ const rerole = await app.app.request(`/api/v1/members/${localRowId}`, {
409
+ ...json({ role: 'member' }, { cookie: operatorCookie }),
410
+ method: 'PATCH'
411
+ });
412
+ expect(rerole.status).toBe(200);
413
+ expect((await readJson(rerole)).role).toBe('member');
414
+ });
415
+ it('member remove on the local workspace works: 200, the row off, its grant gone (PRDCT-2816)', async () => {
416
+ // The hub member gets a local membership in the operator's workspace
417
+ // (seeded: invitations are not the point here) and a project grant in it.
418
+ const { rows: users } = await app.db.pool.query(`SELECT id FROM "user" WHERE email = $1`, [
419
+ HUB_MEMBER_EMAIL
420
+ ]);
421
+ const { rows: memberRows } = await app.db.pool.query(
422
+ `INSERT INTO workspace_members (workspace_id, user_id, role) VALUES ($1, $2, 'member') RETURNING id`,
423
+ [operatorWorkspaceId, users[0].id]
424
+ );
425
+ const localRowId = memberRows[0].id;
426
+ const { rows: projectRows } = await app.db.pool.query(
427
+ `INSERT INTO projects (workspace_id, name) VALUES ($1, 'P7 local remove') RETURNING id`,
428
+ [operatorWorkspaceId]
429
+ );
430
+ await app.db.pool.query(
431
+ `INSERT INTO project_members (project_id, member_id, role) VALUES ($1, $2, 'viewer')`,
432
+ [projectRows[0].id, localRowId]
433
+ );
434
+ const res = await app.app.request(`/api/v1/members/${localRowId}/remove`, {
435
+ method: 'POST',
436
+ headers: { cookie: operatorCookie, 'x-workspace-id': operatorWorkspaceId, 'x-forwarded-for': nextIp() }
437
+ });
438
+ expect(res.status).toBe(200);
439
+ expect(await readJson(res)).toMatchObject({ id: localRowId, isActive: false });
440
+ const { rows } = await app.db.pool.query(`SELECT is_active FROM workspace_members WHERE id = $1`, [
441
+ localRowId
442
+ ]);
443
+ expect(rows[0]).toEqual({ is_active: false });
444
+ const { rows: grants } = await app.db.pool.query(`SELECT 1 FROM project_members WHERE member_id = $1`, [
445
+ localRowId
446
+ ]);
447
+ expect(grants).toHaveLength(0);
448
+ // The hub member's projected membership is another row, untouched.
449
+ const { rows: projected } = await app.db.pool.query(
450
+ `SELECT is_active FROM workspace_members WHERE id = $1`,
451
+ [hubMemberRowId]
452
+ );
453
+ expect(projected[0]).toEqual({ is_active: true });
454
+ });
455
+ });
456
+
457
+ describe('/me carries the adaptation signals, the hub org id beside hubOrigin', () => {
458
+ it('projected workspace: hubOrigin true, origin hub, hubManageUrl → the hub', async () => {
459
+ const res = await app.app.request('/api/v1/me', { headers: asHubAdmin() });
460
+ expect(res.status).toBe(200);
461
+ const me = await readJson(res);
462
+ expect(me.workspace.hubOrigin).toBe(true);
463
+ expect(me.origin).toBe('hub');
464
+ expect(me.hubManageUrl).toBe(hub.issuer);
465
+ // Per-entry flags across BOTH memberships of the one session.
466
+ const byId = Object.fromEntries(
467
+ me.workspaces.map((w: { id: string; hubOrigin: boolean }) => [w.id, w.hubOrigin])
468
+ );
469
+ expect(byId[projectedWorkspaceId]).toBe(true);
470
+ expect(byId[operatorWorkspaceId]).toBe(false);
471
+ // The hub org id rides beside the flag (PRDCT-2947): the projection's row
472
+ // carries ORG_A, the local workspace's row carries null.
473
+ const rows = Object.fromEntries(
474
+ me.workspaces.map((w: { id: string; centralAccountId: string | null }) => [w.id, w])
475
+ );
476
+ expect(rows[projectedWorkspaceId].centralAccountId).toBe(ORG_A);
477
+ expect(rows[operatorWorkspaceId].centralAccountId).toBeNull();
478
+ });
479
+
480
+ it('local workspace, same session: hubOrigin false, origin local, hubManageUrl null', async () => {
481
+ const res = await app.app.request('/api/v1/me', {
482
+ headers: { cookie: hubAdminCookie, 'x-workspace-id': operatorWorkspaceId }
483
+ });
484
+ const me = await readJson(res);
485
+ expect(me.workspace.hubOrigin).toBe(false);
486
+ expect(me.origin).toBe('local');
487
+ expect(me.hubManageUrl).toBeNull();
488
+ });
489
+
490
+ it('the API key pinned to the projected workspace reports the same signals (via api_key)', async () => {
491
+ const res = await app.app.request('/api/v1/me', {
492
+ headers: { authorization: `Bearer ${projectedKey}` }
493
+ });
494
+ expect(res.status).toBe(200);
495
+ const me = await readJson(res);
496
+ expect(me.via).toBe('api_key');
497
+ expect(me.workspace.hubOrigin).toBe(true);
498
+ expect(me.origin).toBe('hub');
499
+ expect(me.hubManageUrl).toBe(hub.issuer);
500
+ // The ACTIVE workspace is the pin; /me enumerates ALL the holder's
501
+ // memberships (user-scoped model — the pin restricts reach, not what
502
+ // the holder may see about themselves), each with its own flag.
503
+ expect(me.activeWorkspaceId).toBe(projectedWorkspaceId);
504
+ const flags = Object.fromEntries(
505
+ me.workspaces.map((w: { id: string; hubOrigin: boolean }) => [w.id, w.hubOrigin])
506
+ );
507
+ expect(flags[projectedWorkspaceId]).toBe(true);
508
+ });
509
+ });
510
+
511
+ describe('machine credentials: the fail-closed scope map is the first wall', () => {
512
+ it('every /members + /invitations shape 403s endpoint_not_allowed for a key — reads included', async () => {
513
+ const key = { authorization: `Bearer ${projectedKey}` };
514
+ for (const [path, init] of [
515
+ ['/api/v1/members', { headers: { ...key, 'x-forwarded-for': nextIp() } }],
516
+ [`/api/v1/members/${hubMemberRowId}`, { ...json({ role: 'admin' }, key), method: 'PATCH' }],
517
+ [
518
+ `/api/v1/members/${hubMemberRowId}`,
519
+ { method: 'DELETE', headers: { ...key, 'x-forwarded-for': nextIp() } }
520
+ ],
521
+ [
522
+ `/api/v1/members/${hubMemberRowId}/remove`,
523
+ { method: 'POST', headers: { ...key, 'x-forwarded-for': nextIp() } }
524
+ ],
525
+ ['/api/v1/invitations', { headers: { ...key, 'x-forwarded-for': nextIp() } }],
526
+ ['/api/v1/invitations', json({ email: 'x@p7.test', role: 'member' }, key)]
527
+ ] as const) {
528
+ const res = await app.app.request(path, init as RequestInit);
529
+ expect(res.status, `${path} should be scope-refused`).toBe(403);
530
+ expect((await readJson(res)).error.code).toBe('endpoint_not_allowed');
531
+ }
532
+ });
533
+ });
534
+
535
+ describe('the default workspace is the account’s on cloud (PRDCT-2815)', () => {
536
+ it('PUT /me/default-workspace answers hub_managed on the projection, on the cloud-local workspace and for null; no row moves', async () => {
537
+ const readDefaults = async () =>
538
+ (
539
+ await app.db.pool.query(
540
+ `SELECT wm.workspace_id, wm.is_default FROM workspace_members wm JOIN "user" u ON u.id = wm.user_id
541
+ WHERE u.email = $1 ORDER BY wm.workspace_id`,
542
+ [HUB_ADMIN_EMAIL]
543
+ )
544
+ ).rows;
545
+ const before = await readDefaults();
546
+ // The caller holds both kinds of membership: the contrast is the point.
547
+ expect(before.map((r: { workspace_id: string }) => r.workspace_id).sort()).toEqual(
548
+ [projectedWorkspaceId, operatorWorkspaceId].sort()
549
+ );
550
+ for (const workspaceId of [projectedWorkspaceId, operatorWorkspaceId, null]) {
551
+ const res = await app.app.request('/api/v1/me/default-workspace', {
552
+ ...json({ workspaceId }, { cookie: hubAdminCookie }),
553
+ method: 'PUT'
554
+ });
555
+ await expectHubManaged(res);
556
+ }
557
+ expect(await readDefaults()).toEqual(before);
558
+ });
559
+ });