@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,131 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
2
+ import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { createDatabase, createTestApp, readJson, startPostgres, type TestApp } from './helpers.js';
6
+
7
+ /**
8
+ * PRDCT-1347 (audit A7): the first-boot claim is NEVER free. With no
9
+ * SETUP_TOKEN configured the server generates one into $DATA_DIR/setup-token
10
+ * at boot and POST /setup refuses every claim that does not present it — the
11
+ * documented `docker run` / plain `docker compose up` paths (which never go
12
+ * through setup.sh) therefore produce an instance claimable only with a
13
+ * secret. The generated token is reused across boots until the claim, and
14
+ * removed once the instance is set up.
15
+ */
16
+
17
+ const OWNER = { email: 'owner@claim.test', name: 'Owner', password: 'owner-password-12345' };
18
+
19
+ const json = (body: unknown) => ({
20
+ method: 'POST',
21
+ headers: { 'content-type': 'application/json' },
22
+ body: JSON.stringify(body)
23
+ });
24
+
25
+ let container: StartedPostgreSqlContainer;
26
+
27
+ beforeAll(async () => {
28
+ container = await startPostgres();
29
+ }, 180_000);
30
+
31
+ afterAll(async () => {
32
+ await container?.stop();
33
+ });
34
+
35
+ describe('first-boot claim without SETUP_TOKEN (generated token)', () => {
36
+ let app: TestApp;
37
+ let url: string;
38
+
39
+ beforeAll(async () => {
40
+ url = await createDatabase(container, 'claim_generated');
41
+ // Blank = unset (blankToUndefined): the minimal-env container shape.
42
+ app = await createTestApp(url, { SETUP_TOKEN: '' });
43
+ });
44
+
45
+ afterAll(async () => {
46
+ await app.stop();
47
+ });
48
+
49
+ it('boot generated a 0600 token file in the data volume', () => {
50
+ const path = join(app.env.DATA_DIR, 'setup-token');
51
+ expect(existsSync(path)).toBe(true);
52
+ expect(readFileSync(path, 'utf8').trim().length).toBeGreaterThanOrEqual(32);
53
+ });
54
+
55
+ it('a tokenless claim is refused (403 invalid_setup_token) and creates nothing', async () => {
56
+ const res = await app.app.request('/api/v1/setup', json({ instanceName: 'Free', owner: OWNER }));
57
+ expect(res.status).toBe(403);
58
+ expect((await readJson(res)).error.code).toBe('invalid_setup_token');
59
+ const rows = await app.db.pool.query('SELECT 1 FROM instance_settings');
60
+ expect(rows.rows).toHaveLength(0);
61
+ const users = await app.db.pool.query('SELECT 1 FROM "user"');
62
+ expect(users.rows).toHaveLength(0);
63
+ });
64
+
65
+ it('a wrong token is refused too', async () => {
66
+ const res = await app.app.request(
67
+ '/api/v1/setup',
68
+ json({ instanceName: 'Guess', owner: OWNER, setupToken: 'not-the-generated-token' })
69
+ );
70
+ expect(res.status).toBe(403);
71
+ });
72
+
73
+ it('a second boot before the claim reuses the same generated token', async () => {
74
+ const path = join(app.env.DATA_DIR, 'setup-token');
75
+ const first = readFileSync(path, 'utf8').trim();
76
+ const again = await createTestApp(url, { SETUP_TOKEN: '', DATA_DIR: app.env.DATA_DIR });
77
+ try {
78
+ expect(readFileSync(path, 'utf8').trim()).toBe(first);
79
+ } finally {
80
+ await again.stop();
81
+ }
82
+ });
83
+
84
+ it('the generated token claims the instance, then the file is gone and the claim is closed', async () => {
85
+ const path = join(app.env.DATA_DIR, 'setup-token');
86
+ const token = readFileSync(path, 'utf8').trim();
87
+ const res = await app.app.request(
88
+ '/api/v1/setup',
89
+ json({ instanceName: 'Claimed', owner: OWNER, setupToken: token })
90
+ );
91
+ expect(res.status).toBe(201);
92
+ expect(existsSync(path)).toBe(false);
93
+ // A replay — even with the right token — meets the 410, never a second owner.
94
+ const replay = await app.app.request(
95
+ '/api/v1/setup',
96
+ json({ instanceName: 'Again', owner: { ...OWNER, email: 'other@claim.test' }, setupToken: token })
97
+ );
98
+ expect(replay.status).toBe(410);
99
+ });
100
+
101
+ it('a boot of the set-up instance generates no token file', async () => {
102
+ const rebooted = await createTestApp(url, { SETUP_TOKEN: '' });
103
+ try {
104
+ expect(existsSync(join(rebooted.env.DATA_DIR, 'setup-token'))).toBe(false);
105
+ const res = await rebooted.app.request('/api/v1/setup', json({ instanceName: 'X', owner: OWNER }));
106
+ expect(res.status).toBe(410);
107
+ } finally {
108
+ await rebooted.stop();
109
+ }
110
+ });
111
+ });
112
+
113
+ describe('first-boot claim with SETUP_TOKEN configured', () => {
114
+ it('the env token is the credential and no file is generated', async () => {
115
+ const app = await createTestApp(await createDatabase(container, 'claim_env'), {
116
+ SETUP_TOKEN: 'operator-chosen-token'
117
+ });
118
+ try {
119
+ expect(existsSync(join(app.env.DATA_DIR, 'setup-token'))).toBe(false);
120
+ const free = await app.app.request('/api/v1/setup', json({ instanceName: 'Free', owner: OWNER }));
121
+ expect(free.status).toBe(403);
122
+ const ok = await app.app.request(
123
+ '/api/v1/setup',
124
+ json({ instanceName: 'Ok', owner: OWNER, setupToken: 'operator-chosen-token' })
125
+ );
126
+ expect(ok.status).toBe(201);
127
+ } finally {
128
+ await app.stop();
129
+ }
130
+ });
131
+ });
@@ -0,0 +1,532 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
2
+ import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
3
+ import AdmZip from 'adm-zip';
4
+ import { and, eq, isNull } from 'drizzle-orm';
5
+ import { apiKeys, auditLog, files, workspaceMembers } from '@antasphere/chassis-db';
6
+ import { AccountDeletionService, LastOwnerError } from '@antasphere/chassis-server/accounts';
7
+ import { AuditService } from '@antasphere/chassis-server/audit';
8
+ import {
9
+ createDatabase,
10
+ createTestApp,
11
+ extractCookie,
12
+ readJson,
13
+ startPostgres,
14
+ type TestApp,
15
+ host
16
+ } from './helpers.js';
17
+
18
+ /**
19
+ * GDPR export + delete (M5):
20
+ * - GET /workspace/export streams a complete zip (sections, no secrets,
21
+ * blobs byte-identical, soft-deleted blobs skipped with a reason);
22
+ * - the dedicated opt-in export scope (the read scope must NOT reach it),
23
+ * the admin role gate, the ws-export rate limit, and audit coverage for
24
+ * session AND machine principals;
25
+ * - self-service and admin account deletion: cascade semantics with files
26
+ * SURVIVING (created_by → NULL), audit anonymization + completion rows,
27
+ * the guard ladder, and the last-owner rule;
28
+ * - (the CLI export command against a real listening server is the tool's:
29
+ * the tool's app keeps it, `apps/server/test/integration/gdpr-cli.test.ts`).
30
+ *
31
+ * Every actor gets its own x-forwarded-for (TRUST_PROXY=true in the test
32
+ * app) so the per-IP login and ws-export buckets never bleed across tests.
33
+ */
34
+
35
+ const OWNER = { email: 'owner@gdpr.test', name: 'GDPR Owner', password: 'gdpr-owner-password-12' };
36
+ const MEMBER = { email: 'member@gdpr.test', name: 'GDPR Member', password: 'gdpr-member-password-1' };
37
+ const ADMIN = { email: 'admin@gdpr.test', name: 'GDPR Admin', password: 'gdpr-admin-password-12' };
38
+ const VICTIM = { email: 'victim@gdpr.test', name: 'GDPR Victim', password: 'gdpr-victim-password-1' };
39
+ const VICTIM2 = { email: 'victim2@gdpr.test', name: 'GDPR Victim2', password: 'gdpr-victim2-passwd-1' };
40
+ const RL = { email: 'rl@gdpr.test', name: 'GDPR RateLimit', password: 'gdpr-rl-password-1234' };
41
+
42
+ const IPS = {
43
+ owner: '10.0.0.1',
44
+ member: '10.0.0.2',
45
+ admin: '10.0.0.3',
46
+ victim: '10.0.0.4',
47
+ victim2: '10.0.0.5',
48
+ rl: '10.0.0.6',
49
+ machine: '10.0.1.1',
50
+ memberKey: '10.0.1.2'
51
+ } as const;
52
+
53
+ const BLOB_BYTES = 'gdpr export blob payload — byte-identical round trip expected';
54
+
55
+ let container: StartedPostgreSqlContainer;
56
+ let app: TestApp;
57
+ let connectionString: string;
58
+
59
+ let ownerCookie: string;
60
+ let memberCookie: string;
61
+ let adminCookie: string;
62
+ let victimCookie: string;
63
+ let victim2Cookie: string;
64
+ let rlCookie: string;
65
+
66
+ let ownerUserId: string;
67
+ let workspaceId: string;
68
+ let ownerMemberId: string;
69
+ let memberMemberId: string;
70
+ let adminMemberId: string;
71
+ let adminUserId: string;
72
+
73
+ let liveFileId: string;
74
+ let trashedFileId: string;
75
+ let exportKey: string; // owner-minted, the export scope only
76
+ let exportKeyId: string; // its row id (audit identity assertion)
77
+
78
+ const json = (body: unknown, extraHeaders: Record<string, string> = {}) => ({
79
+ method: 'POST',
80
+ headers: { 'content-type': 'application/json', ...extraHeaders },
81
+ body: JSON.stringify(body)
82
+ });
83
+
84
+ async function signIn(email: string, password: string, ip: string): Promise<string> {
85
+ const res = await app.app.request(
86
+ '/api/v1/auth/sign-in/email',
87
+ json({ email, password }, { 'x-forwarded-for': ip })
88
+ );
89
+ return extractCookie(res);
90
+ }
91
+
92
+ /** Invite → accept → sign in; returns the session cookie. */
93
+ async function addUser(
94
+ user: { email: string; name: string; password: string },
95
+ role: 'member' | 'admin',
96
+ ip: string
97
+ ): Promise<string> {
98
+ const inv = await readJson(
99
+ await app.app.request('/api/v1/invitations', {
100
+ ...json({ email: user.email, role }),
101
+ headers: { 'content-type': 'application/json', cookie: ownerCookie }
102
+ })
103
+ );
104
+ const token = (inv.acceptUrl as string).split('/invite/')[1]!;
105
+ await app.app.request(
106
+ '/api/v1/invitations/accept',
107
+ json({ token, name: user.name, password: user.password })
108
+ );
109
+ return signIn(user.email, user.password, ip);
110
+ }
111
+
112
+ async function upload(cookie: string, name: string, text: string): Promise<string> {
113
+ const res = await app.app.request(`/api/v1/files?name=${encodeURIComponent(name)}`, {
114
+ method: 'POST',
115
+ headers: { 'content-type': 'application/octet-stream', cookie },
116
+ body: text
117
+ });
118
+ expect(res.status).toBe(201);
119
+ return (await readJson(res)).file.id as string;
120
+ }
121
+
122
+ async function mintKey(cookie: string, name: string, scopes: string[]): Promise<{ key: string; id: string }> {
123
+ // PINNED to the workspace: the export's api-keys.json section is
124
+ // workspace data, so it lists pinned keys only — a user-scoped (unpinned)
125
+ // key belongs to its holder, not to any one workspace's export.
126
+ const res = await app.app.request('/api/v1/api-keys', {
127
+ ...json({ name, scopes, workspaceId }),
128
+ headers: { 'content-type': 'application/json', cookie }
129
+ });
130
+ expect(res.status).toBe(201);
131
+ const body = await readJson(res);
132
+ return { key: body.key as string, id: body.apiKey.id as string };
133
+ }
134
+
135
+ async function memberIdOf(email: string): Promise<string> {
136
+ const { members } = await readJson(
137
+ await app.app.request('/api/v1/members', { headers: { cookie: ownerCookie } })
138
+ );
139
+ return members.find((m: { email: string }) => m.email === email).id as string;
140
+ }
141
+
142
+ async function exportRequest(headers: Record<string, string>): Promise<Response> {
143
+ return app.app.request('/api/v1/workspace/export', { headers });
144
+ }
145
+
146
+ beforeAll(async () => {
147
+ container = await startPostgres();
148
+ connectionString = await createDatabase(container, 'gdpr');
149
+ app = await createTestApp(connectionString);
150
+
151
+ await app.app.request(
152
+ '/api/v1/setup',
153
+ json({ setupToken: 'integration-test-setup-token', instanceName: 'GDPR', owner: OWNER })
154
+ );
155
+ ownerCookie = await signIn(OWNER.email, OWNER.password, IPS.owner);
156
+ const me = await readJson(await app.app.request('/api/v1/me', { headers: { cookie: ownerCookie } }));
157
+ ownerUserId = me.user.id;
158
+ workspaceId = me.workspace.id;
159
+
160
+ memberCookie = await addUser(MEMBER, 'member', IPS.member);
161
+ adminCookie = await addUser(ADMIN, 'admin', IPS.admin);
162
+ victimCookie = await addUser(VICTIM, 'member', IPS.victim);
163
+ victim2Cookie = await addUser(VICTIM2, 'member', IPS.victim2);
164
+ rlCookie = await addUser(RL, 'admin', IPS.rl);
165
+
166
+ ownerMemberId = await memberIdOf(OWNER.email);
167
+ memberMemberId = await memberIdOf(MEMBER.email);
168
+ adminMemberId = await memberIdOf(ADMIN.email);
169
+ const adminMe = await readJson(await app.app.request('/api/v1/me', { headers: { cookie: adminCookie } }));
170
+ adminUserId = adminMe.user.id;
171
+
172
+ // Export fixtures: one live blob with known bytes, one soft-deleted file
173
+ // (file delete removes the blob → the export must list it as skipped).
174
+ liveFileId = await upload(ownerCookie, 'export-blob.bin', BLOB_BYTES);
175
+ trashedFileId = await upload(ownerCookie, 'trashed.txt', 'to be soft-deleted');
176
+ await app.app.request(`/api/v1/files/${trashedFileId}`, {
177
+ method: 'DELETE',
178
+ headers: { cookie: ownerCookie }
179
+ });
180
+
181
+ const minted = await mintKey(ownerCookie, 'export-only', [host.scopes.dataExport]);
182
+ exportKey = minted.key;
183
+ exportKeyId = minted.id;
184
+ }, 240_000);
185
+
186
+ afterAll(async () => {
187
+ await app?.stop();
188
+ await container?.stop();
189
+ });
190
+
191
+ describe('GET /workspace/export', () => {
192
+ it('streams a complete bundle for an owner session (audited exactly once)', async () => {
193
+ const res = await exportRequest({ cookie: ownerCookie, 'x-forwarded-for': IPS.owner });
194
+ expect(res.status).toBe(200);
195
+ expect(res.headers.get('content-type')).toBe('application/zip');
196
+ expect(res.headers.get('content-disposition')).toMatch(/^attachment; filename="export-.+\.zip"$/);
197
+ expect(res.headers.get('x-content-type-options')).toBe('nosniff');
198
+ expect(res.headers.get('cache-control')).toBe('no-store');
199
+
200
+ const zip = new AdmZip(Buffer.from(await res.arrayBuffer()));
201
+ const names = zip.getEntries().map((e) => e.entryName);
202
+ for (const section of [
203
+ 'manifest.json',
204
+ 'workspace.json',
205
+ 'members.json',
206
+ 'invitations.json',
207
+ 'api-keys.json',
208
+ 'projects.json',
209
+ 'project_members.json',
210
+ 'teams.json',
211
+ 'team_members.json',
212
+ 'project_teams.json',
213
+ 'audit-log.ndjson',
214
+ 'files.json',
215
+ 'skipped-blobs.json'
216
+ ]) {
217
+ expect(names).toContain(section);
218
+ }
219
+
220
+ const manifest = JSON.parse(zip.readAsText('manifest.json'));
221
+ expect(manifest.formatVersion).toBe(1);
222
+ expect(manifest.workspaceId).toBe(workspaceId);
223
+ expect(manifest.instance.instanceId).toBeTruthy();
224
+ expect(manifest.instance.name).toBe('GDPR');
225
+
226
+ const members = JSON.parse(zip.readAsText('members.json'));
227
+ expect(members.map((m: { email: string }) => m.email)).toContain(OWNER.email);
228
+
229
+ // Secrets never leave the instance.
230
+ const invitationRows = JSON.parse(zip.readAsText('invitations.json'));
231
+ expect(invitationRows.length).toBeGreaterThan(0);
232
+ for (const row of invitationRows) expect(row).not.toHaveProperty('tokenHash');
233
+ const keyRows = JSON.parse(zip.readAsText('api-keys.json'));
234
+ expect(keyRows.length).toBeGreaterThan(0);
235
+ for (const row of keyRows) {
236
+ expect(row).not.toHaveProperty('secretHash');
237
+ expect(row.keyId).toBeTruthy();
238
+ }
239
+
240
+ const ndjsonLines = zip
241
+ .readAsText('audit-log.ndjson')
242
+ .split('\n')
243
+ .filter((l) => l.length > 0);
244
+ expect(ndjsonLines.length).toBeGreaterThanOrEqual(1);
245
+ for (const line of ndjsonLines) expect(() => JSON.parse(line)).not.toThrow();
246
+
247
+ // files.json carries ALL rows, soft-deleted included.
248
+ const fileRows = JSON.parse(zip.readAsText('files.json'));
249
+ const trashed = fileRows.find((f: { id: string }) => f.id === trashedFileId);
250
+ expect(trashed).toBeTruthy();
251
+ expect(trashed.deletedAt).toBeTruthy();
252
+
253
+ // The live blob is byte-identical; the soft-deleted one is skipped.
254
+ const blobEntry = zip.getEntry(`files/${liveFileId}-export-blob.bin`);
255
+ expect(blobEntry).toBeTruthy();
256
+ expect(blobEntry!.getData().toString('utf8')).toBe(BLOB_BYTES);
257
+ const skipped = JSON.parse(zip.readAsText('skipped-blobs.json'));
258
+ expect(skipped).toContainEqual(
259
+ expect.objectContaining({ fileId: trashedFileId, reason: 'soft-deleted' })
260
+ );
261
+
262
+ // Session GETs are not middleware-audited — the handler writes directly,
263
+ // and exactly once.
264
+ const rows = await app.db.db
265
+ .select()
266
+ .from(auditLog)
267
+ .where(and(eq(auditLog.action, 'workspace.export'), eq(auditLog.actorVia, 'session')));
268
+ expect(rows).toHaveLength(1);
269
+ expect(rows[0]!.actorUserId).toBe(ownerUserId);
270
+ });
271
+
272
+ it('denies a member-role session (403)', async () => {
273
+ const res = await exportRequest({ cookie: memberCookie, 'x-forwarded-for': IPS.member });
274
+ expect(res.status).toBe(403);
275
+ });
276
+
277
+ it('machine access: the export scope required, role still enforced, audited with the key identity', async () => {
278
+ // An admin-minted key WITH the export scope streams the zip.
279
+ const ok = await exportRequest({
280
+ authorization: `Bearer ${exportKey}`,
281
+ 'x-forwarded-for': IPS.machine
282
+ });
283
+ expect(ok.status).toBe(200);
284
+ const zip = new AdmZip(Buffer.from(await ok.arrayBuffer()));
285
+ expect(zip.getEntry('manifest.json')).toBeTruthy();
286
+
287
+ // The read scope alone must NEVER reach the export (exfiltration guard).
288
+ const readOnly = await mintKey(ownerCookie, 'read-only', [host.scopes.read]);
289
+ const denied = await exportRequest({
290
+ authorization: `Bearer ${readOnly.key}`,
291
+ 'x-forwarded-for': IPS.machine
292
+ });
293
+ expect(denied.status).toBe(403);
294
+ expect((await readJson(denied)).error.code).toBe('insufficient_scope');
295
+
296
+ // A member-minted key with the export scope passes the scope gate but fails
297
+ // the role gate.
298
+ const memberKey = await mintKey(memberCookie, 'member-export', [host.scopes.dataExport]);
299
+ const roleDenied = await exportRequest({
300
+ authorization: `Bearer ${memberKey.key}`,
301
+ 'x-forwarded-for': IPS.memberKey
302
+ });
303
+ expect(roleDenied.status).toBe(403);
304
+
305
+ // The machine export landed in the audit log with the key's identity.
306
+ const rows = await app.db.db
307
+ .select()
308
+ .from(auditLog)
309
+ .where(and(eq(auditLog.action, 'workspace.export'), eq(auditLog.actorVia, 'api_key')));
310
+ expect(rows).toHaveLength(1);
311
+ expect(rows[0]!.apiKeyId).toBe(exportKeyId);
312
+ expect(rows[0]!.actorUserId).toBe(ownerUserId);
313
+ });
314
+
315
+ it('rate limits the 6th export in the window (429)', async () => {
316
+ for (let i = 0; i < 5; i++) {
317
+ const res = await exportRequest({ cookie: rlCookie, 'x-forwarded-for': IPS.rl });
318
+ expect(res.status).toBe(200);
319
+ await res.arrayBuffer(); // drain so the pump finishes cleanly
320
+ }
321
+ const sixth = await exportRequest({ cookie: rlCookie, 'x-forwarded-for': IPS.rl });
322
+ expect(sixth.status).toBe(429);
323
+ }, 60_000);
324
+ });
325
+
326
+ describe('self-service account deletion (POST /auth/delete-user)', () => {
327
+ it('rejects a wrong password (400) and leaves the user intact', async () => {
328
+ const res = await app.app.request(
329
+ '/api/v1/auth/delete-user',
330
+ json(
331
+ { password: 'definitely-not-the-password' },
332
+ { cookie: victimCookie, 'x-forwarded-for': IPS.victim }
333
+ )
334
+ );
335
+ expect(res.status).toBe(400);
336
+ expect((await app.app.request('/api/v1/me', { headers: { cookie: victimCookie } })).status).toBe(200);
337
+ });
338
+
339
+ it('blocks the last active owner (400 last_owner)', async () => {
340
+ const res = await app.app.request(
341
+ '/api/v1/auth/delete-user',
342
+ json({ password: OWNER.password }, { cookie: ownerCookie, 'x-forwarded-for': IPS.owner })
343
+ );
344
+ expect(res.status).toBe(400);
345
+ const body = await readJson(res);
346
+ expect(JSON.stringify(body)).toContain('active owner');
347
+ // The owner is untouched.
348
+ expect((await app.app.request('/api/v1/me', { headers: { cookie: ownerCookie } })).status).toBe(200);
349
+ });
350
+
351
+ it('deletes the account with the full cascade — files survive anonymized', async () => {
352
+ // The victim leaves personal traces first: a file, an API key, audit rows.
353
+ const victimMe = await readJson(
354
+ await app.app.request('/api/v1/me', { headers: { cookie: victimCookie } })
355
+ );
356
+ const victimUserId = victimMe.user.id as string;
357
+ const victimFileId = await upload(victimCookie, 'victim-notes.txt', 'personal upload by victim');
358
+ const victimKey = await mintKey(victimCookie, 'victim-key', [host.scopes.read]);
359
+
360
+ const [uploadAudit] = await app.db.db
361
+ .select({ id: auditLog.id, actorUserId: auditLog.actorUserId })
362
+ .from(auditLog)
363
+ .where(and(eq(auditLog.action, 'file.upload'), eq(auditLog.resourceId, victimFileId)));
364
+ expect(uploadAudit!.actorUserId).toBe(victimUserId);
365
+
366
+ const res = await app.app.request(
367
+ '/api/v1/auth/delete-user',
368
+ json({ password: VICTIM.password }, { cookie: victimCookie, 'x-forwarded-for': IPS.victim })
369
+ );
370
+ expect(res.status).toBe(200);
371
+
372
+ // Sign-in is gone; the session is gone.
373
+ const relogin = await app.app.request(
374
+ '/api/v1/auth/sign-in/email',
375
+ json({ email: VICTIM.email, password: VICTIM.password }, { 'x-forwarded-for': IPS.victim })
376
+ );
377
+ expect(relogin.status).toBe(401);
378
+ expect((await app.app.request('/api/v1/me', { headers: { cookie: victimCookie } })).status).toBe(401);
379
+
380
+ // Membership and api_keys rows cascade away; the key stops resolving.
381
+ const memberships = await app.db.db
382
+ .select()
383
+ .from(workspaceMembers)
384
+ .where(eq(workspaceMembers.userId, victimUserId));
385
+ expect(memberships).toHaveLength(0);
386
+ const keys = await app.db.db.select().from(apiKeys).where(eq(apiKeys.createdBy, victimUserId));
387
+ expect(keys).toHaveLength(0);
388
+ const keyProbe = await app.app.request('/api/v1/me', {
389
+ headers: { authorization: `Bearer ${victimKey.key}` }
390
+ });
391
+ expect(keyProbe.status).toBe(401);
392
+
393
+ // Their files SURVIVE with created_by nulled (files are workspace data).
394
+ const [fileRow] = await app.db.db.select().from(files).where(eq(files.id, victimFileId));
395
+ expect(fileRow).toBeTruthy();
396
+ expect(fileRow!.createdBy).toBeNull();
397
+ expect(fileRow!.deletedAt).toBeNull();
398
+
399
+ // Pre-existing audit rows anonymize; a system-actor completion row lands.
400
+ const [anonymized] = await app.db.db
401
+ .select({ actorUserId: auditLog.actorUserId })
402
+ .from(auditLog)
403
+ .where(eq(auditLog.id, uploadAudit!.id));
404
+ expect(anonymized!.actorUserId).toBeNull();
405
+ const completionRows = await app.db.db
406
+ .select()
407
+ .from(auditLog)
408
+ .where(and(eq(auditLog.action, 'user.account_delete'), eq(auditLog.resourceId, victimUserId)));
409
+ expect(completionRows).toHaveLength(1);
410
+ expect(completionRows[0]!.actorVia).toBe('system');
411
+ expect(completionRows[0]!.actorUserId).toBeNull();
412
+ expect((completionRows[0]!.metadata as { email: string }).email).toBe(VICTIM.email);
413
+ });
414
+ });
415
+
416
+ describe('admin member deletion (DELETE /members/{id})', () => {
417
+ it('guards: member 403, admin-on-owner 403, self 400, unknown 404, machine 403', async () => {
418
+ // A member-role caller never reaches the handler.
419
+ const memberCall = await app.app.request(`/api/v1/members/${adminMemberId}`, {
420
+ method: 'DELETE',
421
+ headers: { cookie: memberCookie }
422
+ });
423
+ expect(memberCall.status).toBe(403);
424
+
425
+ // Only an owner deletes an owner.
426
+ const adminOnOwner = await app.app.request(`/api/v1/members/${ownerMemberId}`, {
427
+ method: 'DELETE',
428
+ headers: { cookie: adminCookie }
429
+ });
430
+ expect(adminOnOwner.status).toBe(403);
431
+
432
+ // Self-deletion goes through the account page, not the admin surface.
433
+ const self = await app.app.request(`/api/v1/members/${adminMemberId}`, {
434
+ method: 'DELETE',
435
+ headers: { cookie: adminCookie }
436
+ });
437
+ expect(self.status).toBe(400);
438
+ expect((await readJson(self)).error.code).toBe('cannot_delete_self');
439
+
440
+ // Unknown id.
441
+ const unknown = await app.app.request('/api/v1/members/00000000-0000-0000-0000-000000000000', {
442
+ method: 'DELETE',
443
+ headers: { cookie: adminCookie }
444
+ });
445
+ expect(unknown.status).toBe(404);
446
+
447
+ // Machines are excluded fail-closed: the endpoint is unlisted in the
448
+ // scope allowlist, whatever the key carries.
449
+ const machine = await app.app.request(`/api/v1/members/${memberMemberId}`, {
450
+ method: 'DELETE',
451
+ headers: { authorization: `Bearer ${exportKey}` }
452
+ });
453
+ expect(machine.status).toBe(403);
454
+ expect((await readJson(machine)).error.code).toBe('endpoint_not_allowed');
455
+ });
456
+
457
+ it('enforces the last-owner rule (the guard the ladder shares with self-delete)', async () => {
458
+ // At the wire, guard order makes the admin-surface last_owner rung
459
+ // unreachable without a concurrent role change (an active-owner caller
460
+ // proves another active owner exists, and self-targets 400 first), so
461
+ // the rung is race defense-in-depth. Prove the shared guard directly:
462
+ // the sole owner is not deletable.
463
+ const service = new AccountDeletionService(
464
+ app.db.db,
465
+ new AuditService(app.db.db, app.logger),
466
+ app.logger,
467
+ connectionString
468
+ );
469
+ await expect(service.assertDeletable(ownerUserId)).rejects.toBeInstanceOf(LastOwnerError);
470
+ });
471
+
472
+ it('deletes a member: 200 snapshot, cascade, files survive, audited with the admin actor', async () => {
473
+ const victim2Me = await readJson(
474
+ await app.app.request('/api/v1/me', { headers: { cookie: victim2Cookie } })
475
+ );
476
+ const victim2UserId = victim2Me.user.id as string;
477
+ const victim2FileId = await upload(victim2Cookie, 'victim2-report.txt', 'uploaded by victim2');
478
+ const victim2Key = await mintKey(victim2Cookie, 'victim2-key', [host.scopes.read]);
479
+ const victim2MemberId = await memberIdOf(VICTIM2.email);
480
+
481
+ const res = await app.app.request(`/api/v1/members/${victim2MemberId}`, {
482
+ method: 'DELETE',
483
+ headers: { cookie: adminCookie }
484
+ });
485
+ expect(res.status).toBe(200);
486
+ const snapshot = await readJson(res);
487
+ expect(snapshot.id).toBe(victim2MemberId);
488
+ expect(snapshot.userId).toBe(victim2UserId);
489
+ expect(snapshot.email).toBe(VICTIM2.email);
490
+
491
+ // Same cascade semantics as self-delete.
492
+ expect(
493
+ await app.db.db.select().from(workspaceMembers).where(eq(workspaceMembers.userId, victim2UserId))
494
+ ).toHaveLength(0);
495
+ expect(await app.db.db.select().from(apiKeys).where(eq(apiKeys.createdBy, victim2UserId))).toHaveLength(
496
+ 0
497
+ );
498
+ expect(
499
+ (await app.app.request('/api/v1/me', { headers: { authorization: `Bearer ${victim2Key.key}` } })).status
500
+ ).toBe(401);
501
+ expect((await app.app.request('/api/v1/me', { headers: { cookie: victim2Cookie } })).status).toBe(401);
502
+ const [fileRow] = await app.db.db.select().from(files).where(eq(files.id, victim2FileId));
503
+ expect(fileRow!.createdBy).toBeNull();
504
+
505
+ // Audited with the acting admin as the actor.
506
+ const rows = await app.db.db
507
+ .select()
508
+ .from(auditLog)
509
+ .where(and(eq(auditLog.action, 'member.delete'), eq(auditLog.resourceId, victim2MemberId)));
510
+ expect(rows).toHaveLength(1);
511
+ expect(rows[0]!.actorUserId).toBe(adminUserId);
512
+ expect((rows[0]!.metadata as { targetUserId: string }).targetUserId).toBe(victim2UserId);
513
+ });
514
+ });
515
+
516
+ // The files list still tolerates NULL uploader rows (regression sweep for
517
+ // the migration-0008 nullability): listed here so the suite pins it.
518
+ describe('files list after uploader deletion', () => {
519
+ it('lists workspace files whose creator was deleted', async () => {
520
+ const rows = await app.db.db
521
+ .select()
522
+ .from(files)
523
+ .where(and(eq(files.workspaceId, workspaceId), isNull(files.createdBy)));
524
+ expect(rows.length).toBeGreaterThanOrEqual(2); // victim + victim2 uploads
525
+
526
+ const res = await app.app.request('/api/v1/files?limit=100', { headers: { cookie: ownerCookie } });
527
+ expect(res.status).toBe(200);
528
+ const body = await readJson(res);
529
+ const nullOwned = body.files.filter((f: { createdBy: string | null }) => f.createdBy === null);
530
+ expect(nullOwned.length).toBeGreaterThanOrEqual(2);
531
+ });
532
+ });
@@ -0,0 +1,29 @@
1
+ import { makeCreateTestApp, type TestApp as ChassisTestApp } from '@antasphere/chassis-server/testing';
2
+ import { host, type HostBootResult } from '@chassis-test/host';
3
+
4
+ /**
5
+ * The chassis suite's harness. Every file of this directory (but
6
+ * `empty-tool.test.ts`, which carries its own tool) runs TWICE: by this
7
+ * package against the minimal test tool (`test/host/minimal-host.ts`), and by
8
+ * the tool's app against its real composition. `@chassis-test/host` is the ONE
9
+ * module specifier each runner's vitest config aliases to its own host; the
10
+ * suite never spells a tool's scope names or routes, it takes them from `host`.
11
+ */
12
+ export {
13
+ RecordingEmailDriver,
14
+ SETUP_TOKEN,
15
+ PG_IMAGE,
16
+ startPostgres,
17
+ expectBootRefusal,
18
+ createDatabase,
19
+ truncateAll,
20
+ readJson,
21
+ extractCookie
22
+ } from '@antasphere/chassis-server/testing';
23
+
24
+ export { host };
25
+
26
+ export type TestApp = ChassisTestApp<HostBootResult>;
27
+
28
+ /** Boot the host's real app (real migrations, real Better Auth) against a database. */
29
+ export const createTestApp = makeCreateTestApp(host.boot);