@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,263 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
2
+ import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
3
+ import { and, eq } from 'drizzle-orm';
4
+ import { workspaceMembers } from '@antasphere/chassis-db';
5
+ import {
6
+ createDatabase,
7
+ createTestApp,
8
+ extractCookie,
9
+ readJson,
10
+ startPostgres,
11
+ type TestApp
12
+ } from './helpers.js';
13
+
14
+ /**
15
+ * Last-owner delete race (M9 P0): two concurrent removals of the two last
16
+ * ACTIVE owners must never both succeed — a zero-active-owner workspace is
17
+ * unrecoverable (setup is one-shot). Pinned here across all three surfaces:
18
+ *
19
+ * - self-service POST /auth/delete-user (the confirmed-live finding: both
20
+ * parallel no-password deletes returned 200 and bricked the workspace);
21
+ * - admin DELETE /members/{id};
22
+ * - PATCH /members/{id} demotion.
23
+ *
24
+ * Contract per race: exactly one winner (200), the loser answers a clean
25
+ * 400 last_owner (never a 500), and ≥1 active owner survives in the DB. The
26
+ * DB trigger (migration 0009) is additionally pinned via raw SQL.
27
+ *
28
+ * Each round rebuilds the 2-owner state: the survivor of round N plus a
29
+ * freshly invited owner enter round N+1.
30
+ */
31
+
32
+ const OWNER = { email: 'owner@race.test', name: 'Race Owner', password: 'race-owner-password-12' };
33
+
34
+ let container: StartedPostgreSqlContainer;
35
+ let app: TestApp;
36
+ let workspaceId: string;
37
+ let ipCounter = 0;
38
+
39
+ /** Fresh XFF per actor so per-IP auth buckets never collide across rounds. */
40
+ function nextIp(): string {
41
+ ipCounter += 1;
42
+ return `10.9.${Math.floor(ipCounter / 250)}.${(ipCounter % 250) + 1}`;
43
+ }
44
+
45
+ const json = (body: unknown, extraHeaders: Record<string, string> = {}) => ({
46
+ method: 'POST',
47
+ headers: { 'content-type': 'application/json', ...extraHeaders },
48
+ body: JSON.stringify(body)
49
+ });
50
+
51
+ interface Actor {
52
+ email: string;
53
+ cookie: string;
54
+ ip: string;
55
+ userId: string;
56
+ memberId: string;
57
+ }
58
+
59
+ async function signIn(email: string, password: string, ip: string): Promise<string> {
60
+ const res = await app.app.request(
61
+ '/api/v1/auth/sign-in/email',
62
+ json({ email, password }, { 'x-forwarded-for': ip })
63
+ );
64
+ expect(res.status).toBe(200);
65
+ return extractCookie(res);
66
+ }
67
+
68
+ async function actorOf(email: string, cookie: string, ip: string): Promise<Actor> {
69
+ const me = await readJson(await app.app.request('/api/v1/me', { headers: { cookie } }));
70
+ const { members } = await readJson(
71
+ await app.app.request('/api/v1/members?limit=100', { headers: { cookie } })
72
+ );
73
+ const memberId = members.find((m: { email: string }) => m.email === email).id as string;
74
+ return { email, cookie, ip, userId: me.user.id as string, memberId };
75
+ }
76
+
77
+ /** Invite → accept → sign in a brand-new OWNER-role member. */
78
+ async function addOwner(inviterCookie: string, tag: string): Promise<Actor> {
79
+ const email = `owner-${tag}@race.test`;
80
+ const password = `race-${tag}-password-1234`;
81
+ const inv = await readJson(
82
+ await app.app.request('/api/v1/invitations', {
83
+ ...json({ email, role: 'owner' }),
84
+ headers: { 'content-type': 'application/json', cookie: inviterCookie }
85
+ })
86
+ );
87
+ const token = (inv.acceptUrl as string).split('/invite/')[1]!;
88
+ const acceptIp = nextIp();
89
+ const accept = await app.app.request(
90
+ '/api/v1/invitations/accept',
91
+ json({ token, name: `Owner ${tag}`, password }, { 'x-forwarded-for': acceptIp })
92
+ );
93
+ expect(accept.status).toBe(200);
94
+ const ip = nextIp();
95
+ const cookie = await signIn(email, password, ip);
96
+ return actorOf(email, cookie, ip);
97
+ }
98
+
99
+ async function activeOwnerCount(): Promise<number> {
100
+ const rows = await app.db.db
101
+ .select({ id: workspaceMembers.id })
102
+ .from(workspaceMembers)
103
+ .where(
104
+ and(
105
+ eq(workspaceMembers.workspaceId, workspaceId),
106
+ eq(workspaceMembers.role, 'owner'),
107
+ eq(workspaceMembers.isActive, true)
108
+ )
109
+ );
110
+ return rows.length;
111
+ }
112
+
113
+ /** The two-last-owners invariant assertion shared by every race below. */
114
+ async function assertOneWinner(results: Response[]): Promise<void> {
115
+ const statuses = results.map((r) => r.status);
116
+ const winners = statuses.filter((s) => s === 200).length;
117
+ expect(winners).toBe(1);
118
+ for (const res of results) {
119
+ if (res.status === 200) continue;
120
+ // The loser gets a clean 400 (or a 404 when the winner's cascade already
121
+ // removed its target row) — never a 500.
122
+ expect([400, 404]).toContain(res.status);
123
+ const body = await readJson(res);
124
+ expect(JSON.stringify(body)).toMatch(/active owner|not found|Not found/i);
125
+ }
126
+ expect(await activeOwnerCount()).toBeGreaterThanOrEqual(1);
127
+ }
128
+
129
+ beforeAll(async () => {
130
+ container = await startPostgres();
131
+ app = await createTestApp(await createDatabase(container, 'race'));
132
+ await app.app.request(
133
+ '/api/v1/setup',
134
+ json({ setupToken: 'integration-test-setup-token', instanceName: 'Race', owner: OWNER })
135
+ );
136
+ const cookie = await signIn(OWNER.email, OWNER.password, nextIp());
137
+ const me = await readJson(await app.app.request('/api/v1/me', { headers: { cookie } }));
138
+ workspaceId = me.workspace.id;
139
+ // Round state entry point: the setup owner is the initial survivor.
140
+ survivor = await actorOf(OWNER.email, cookie, nextIp());
141
+ }, 240_000);
142
+
143
+ afterAll(async () => {
144
+ await app?.stop();
145
+ await container?.stop();
146
+ });
147
+
148
+ /** Survivor of the previous round; each race re-arms the 2-owner state from it. */
149
+ let survivor: Actor;
150
+
151
+ /** Re-resolve the survivor after a race (whichever of the pair still resolves). */
152
+ async function resolveSurvivor(a: Actor, b: Actor): Promise<Actor> {
153
+ const aliveA = (await app.app.request('/api/v1/me', { headers: { cookie: a.cookie } })).status === 200;
154
+ return aliveA ? a : b;
155
+ }
156
+
157
+ describe('self-service delete race (the confirmed-live P0)', () => {
158
+ const selfDelete = (actor: Actor) =>
159
+ app.app.request(
160
+ '/api/v1/auth/delete-user',
161
+ // Deliberately passwordless: the session is fresh (< freshAge), the
162
+ // exact shape the live campaign used.
163
+ json({}, { cookie: actor.cookie, 'x-forwarded-for': actor.ip })
164
+ );
165
+
166
+ for (const round of [1, 2, 3]) {
167
+ it(`round ${round}: two parallel last-owner self-deletes leave exactly one owner`, async () => {
168
+ const fresh = await addOwner(survivor.cookie, `self-${round}`);
169
+ expect(await activeOwnerCount()).toBe(2);
170
+
171
+ const results = await Promise.all([selfDelete(survivor), selfDelete(fresh)]);
172
+ await assertOneWinner(results);
173
+ expect(await activeOwnerCount()).toBe(1);
174
+
175
+ survivor = await resolveSurvivor(survivor, fresh);
176
+ }, 60_000);
177
+ }
178
+ });
179
+
180
+ describe('admin member-delete race (DELETE /members/{id})', () => {
181
+ it('two owners deleting each other concurrently leave exactly one owner', async () => {
182
+ const fresh = await addOwner(survivor.cookie, 'admin');
183
+ const a = await actorOf(survivor.email, survivor.cookie, survivor.ip);
184
+ expect(await activeOwnerCount()).toBe(2);
185
+
186
+ const del = (actorCookie: string, targetMemberId: string) =>
187
+ app.app.request(`/api/v1/members/${targetMemberId}`, {
188
+ method: 'DELETE',
189
+ headers: { cookie: actorCookie }
190
+ });
191
+ const results = await Promise.all([del(a.cookie, fresh.memberId), del(fresh.cookie, a.memberId)]);
192
+ await assertOneWinner(results);
193
+ expect(await activeOwnerCount()).toBe(1);
194
+
195
+ survivor = await resolveSurvivor(a, fresh);
196
+ }, 60_000);
197
+ });
198
+
199
+ describe('demotion race (PATCH /members/{id})', () => {
200
+ it('two owners demoting each other concurrently keep one active owner', async () => {
201
+ const fresh = await addOwner(survivor.cookie, 'patch');
202
+ const a = await actorOf(survivor.email, survivor.cookie, survivor.ip);
203
+ expect(await activeOwnerCount()).toBe(2);
204
+
205
+ const demote = (actorCookie: string, targetMemberId: string) =>
206
+ app.app.request(`/api/v1/members/${targetMemberId}`, {
207
+ method: 'PATCH',
208
+ headers: { 'content-type': 'application/json', cookie: actorCookie },
209
+ body: JSON.stringify({ role: 'member' })
210
+ });
211
+ const results = await Promise.all([demote(a.cookie, fresh.memberId), demote(fresh.cookie, a.memberId)]);
212
+ const statuses = results.map((r) => r.status);
213
+ expect(statuses.filter((s) => s === 200).length).toBe(1);
214
+ for (const res of results) {
215
+ if (res.status === 200) continue;
216
+ // 400 last_owner from the guard; 403 only when the loser's principal
217
+ // resolved after the winning demotion already landed.
218
+ expect([400, 403]).toContain(res.status);
219
+ if (res.status === 400) {
220
+ expect((await readJson(res)).error.code).toBe('last_owner');
221
+ }
222
+ }
223
+ expect(await activeOwnerCount()).toBe(1);
224
+
225
+ // The demoted member is re-promotable — re-arm for any later test.
226
+ const alive = await resolveSurvivorByRole(a, fresh);
227
+ survivor = alive;
228
+ }, 60_000);
229
+ });
230
+
231
+ /** After a demotion race the loser still signs in — survivor = the one still an owner. */
232
+ async function resolveSurvivorByRole(a: Actor, b: Actor): Promise<Actor> {
233
+ const rows = await app.db.db
234
+ .select({ userId: workspaceMembers.userId })
235
+ .from(workspaceMembers)
236
+ .where(
237
+ and(
238
+ eq(workspaceMembers.workspaceId, workspaceId),
239
+ eq(workspaceMembers.role, 'owner'),
240
+ eq(workspaceMembers.isActive, true)
241
+ )
242
+ );
243
+ const ownerIds = new Set(rows.map((r) => r.userId));
244
+ return ownerIds.has(a.userId) ? a : b;
245
+ }
246
+
247
+ describe('DB-level backstop (migration 0009 trigger)', () => {
248
+ it('raw SQL cannot drop the workspace to zero active owners', async () => {
249
+ expect(await activeOwnerCount()).toBeGreaterThanOrEqual(1);
250
+ await expect(
251
+ app.db.pool.query(`DELETE FROM workspace_members WHERE role = 'owner' AND is_active`)
252
+ ).rejects.toMatchObject({ code: 'P0409' });
253
+ // Nothing was deleted — the statement aborted atomically.
254
+ expect(await activeOwnerCount()).toBeGreaterThanOrEqual(1);
255
+ });
256
+
257
+ it('raw SQL cannot deactivate the last active owner', async () => {
258
+ await expect(
259
+ app.db.pool.query(`UPDATE workspace_members SET is_active = false WHERE role = 'owner' AND is_active`)
260
+ ).rejects.toMatchObject({ code: 'P0409' });
261
+ expect(await activeOwnerCount()).toBeGreaterThanOrEqual(1);
262
+ });
263
+ });
@@ -0,0 +1,290 @@
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
+
13
+ /**
14
+ * M9 adversarial-campaign regression battery:
15
+ * - /mcp method handling: non-POST → 405 + Allow: POST, malformed or
16
+ * non-JSON-RPC POST → 400 JSON-RPC error (never 500), valid POST works;
17
+ * - every {id} path param validates as a UUID → clean 400/404, never a
18
+ * Postgres uuid-cast 500 (incl. the DELETE /invitations/lookup residue);
19
+ * - audit cursor past bigint precision is ignored like a malformed one;
20
+ * - unmatched /api/v1 paths answer a JSON 404, not the SPA HTML, while the
21
+ * machine-principal 403 endpoint_not_allowed still fires first;
22
+ * - sign-in rejects a foreign Origin header (login-CSRF hardening) while
23
+ * same-origin and origin-less (CLI-style) logins keep working.
24
+ */
25
+
26
+ const OWNER = { email: 'owner@m9.test', name: 'M9 Owner', password: 'm9-owner-password-1234' };
27
+
28
+ let container: StartedPostgreSqlContainer;
29
+ let app: TestApp;
30
+ let ownerCookie: string;
31
+ let apiKey: string;
32
+
33
+ const json = (body: unknown, extraHeaders: Record<string, string> = {}) => ({
34
+ method: 'POST',
35
+ headers: { 'content-type': 'application/json', ...extraHeaders },
36
+ body: JSON.stringify(body)
37
+ });
38
+
39
+ beforeAll(async () => {
40
+ container = await startPostgres();
41
+ app = await createTestApp(await createDatabase(container, 'm9'));
42
+ await app.app.request(
43
+ '/api/v1/setup',
44
+ json({ setupToken: 'integration-test-setup-token', instanceName: 'M9', owner: OWNER })
45
+ );
46
+ const signIn = await app.app.request(
47
+ '/api/v1/auth/sign-in/email',
48
+ json({ email: OWNER.email, password: OWNER.password }, { 'x-forwarded-for': '10.7.0.1' })
49
+ );
50
+ ownerCookie = extractCookie(signIn);
51
+ const minted = await readJson(
52
+ await app.app.request('/api/v1/api-keys', {
53
+ ...json({ name: 'm9-key', scopes: [host.scopes.read] }),
54
+ headers: { 'content-type': 'application/json', cookie: ownerCookie }
55
+ })
56
+ );
57
+ apiKey = minted.key as string;
58
+ }, 240_000);
59
+
60
+ afterAll(async () => {
61
+ await app?.stop();
62
+ await container?.stop();
63
+ });
64
+
65
+ describe('/mcp method handling (ADR 002)', () => {
66
+ const mcp = (init: RequestInit & { headers?: Record<string, string> }) =>
67
+ app.app.request('/mcp', {
68
+ ...init,
69
+ headers: { 'x-forwarded-for': '10.7.1.1', ...init.headers }
70
+ });
71
+
72
+ it('GET is 405 + Allow: POST (no long-lived SSE stream)', async () => {
73
+ const res = await mcp({ method: 'GET', headers: { accept: 'text/event-stream' } });
74
+ expect(res.status).toBe(405);
75
+ expect(res.headers.get('allow')).toBe('POST');
76
+ expect(res.headers.get('content-type')).toContain('application/json');
77
+ const body = await readJson(res);
78
+ expect(body.jsonrpc).toBe('2.0');
79
+ expect(body.error.code).toBe(-32000);
80
+ });
81
+
82
+ it('GET with Accept: application/json is 405, not 500', async () => {
83
+ const res = await mcp({ method: 'GET', headers: { accept: 'application/json' } });
84
+ expect(res.status).toBe(405);
85
+ expect(res.headers.get('allow')).toBe('POST');
86
+ });
87
+
88
+ it('DELETE is 405', async () => {
89
+ const res = await mcp({ method: 'DELETE' });
90
+ expect(res.status).toBe(405);
91
+ expect(res.headers.get('allow')).toBe('POST');
92
+ });
93
+
94
+ it('a malformed (non-JSON) POST body is a 400 JSON-RPC parse error, not a 500', async () => {
95
+ const res = await mcp({
96
+ method: 'POST',
97
+ headers: {
98
+ 'content-type': 'application/json',
99
+ accept: 'application/json, text/event-stream',
100
+ authorization: `Bearer ${apiKey}`
101
+ },
102
+ body: 'this is not json {'
103
+ });
104
+ expect(res.status).toBe(400);
105
+ const body = await readJson(res);
106
+ expect(body.jsonrpc).toBe('2.0');
107
+ expect(body.error.code).toBe(-32700);
108
+ });
109
+
110
+ it('a non-JSON-RPC JSON POST body is a 400 JSON-RPC error, not a 500', async () => {
111
+ const res = await mcp({
112
+ method: 'POST',
113
+ headers: {
114
+ 'content-type': 'application/json',
115
+ accept: 'application/json, text/event-stream',
116
+ authorization: `Bearer ${apiKey}`
117
+ },
118
+ body: JSON.stringify({ hello: 'world' })
119
+ });
120
+ expect(res.status).toBe(400);
121
+ const body = await readJson(res);
122
+ expect(body.jsonrpc).toBe('2.0');
123
+ expect([-32700, -32600]).toContain(body.error.code);
124
+ });
125
+
126
+ it('a valid initialize POST still works (stateless one-server-per-POST)', async () => {
127
+ const res = await mcp({
128
+ method: 'POST',
129
+ headers: {
130
+ 'content-type': 'application/json',
131
+ accept: 'application/json, text/event-stream',
132
+ authorization: `Bearer ${apiKey}`
133
+ },
134
+ body: JSON.stringify({
135
+ jsonrpc: '2.0',
136
+ id: 1,
137
+ method: 'initialize',
138
+ params: {
139
+ protocolVersion: '2025-06-18',
140
+ capabilities: {},
141
+ clientInfo: { name: 'm9-test', version: '0.0.0' }
142
+ }
143
+ })
144
+ });
145
+ expect(res.status).toBe(200);
146
+ const body = await readJson(res);
147
+ expect(body.result.serverInfo.name).toBeTruthy();
148
+ });
149
+
150
+ it('an unauthenticated POST still answers the RFC 9728 challenge', async () => {
151
+ const res = await mcp({ method: 'POST', headers: { 'content-type': 'application/json' } });
152
+ expect(res.status).toBe(401);
153
+ expect(res.headers.get('www-authenticate')).toContain('resource_metadata');
154
+ });
155
+ });
156
+
157
+ describe('non-UUID {id} path params answer clean 4xx, never a PG-cast 500', () => {
158
+ const cases: Array<{ name: string; method: string; path: string; body?: unknown }> = [
159
+ {
160
+ name: 'PATCH /members/{id}',
161
+ method: 'PATCH',
162
+ path: '/api/v1/members/not-a-uuid',
163
+ body: { role: 'member' }
164
+ },
165
+ { name: 'DELETE /members/{id}', method: 'DELETE', path: '/api/v1/members/not-a-uuid' },
166
+ { name: 'POST /members/{id}/reset-link', method: 'POST', path: '/api/v1/members/not-a-uuid/reset-link' },
167
+ {
168
+ name: 'POST /members/{id}/change-email-link',
169
+ method: 'POST',
170
+ path: '/api/v1/members/not-a-uuid/change-email-link',
171
+ body: { newEmail: 'x@m9.test' }
172
+ },
173
+ { name: 'DELETE /api-keys/{id}', method: 'DELETE', path: '/api/v1/api-keys/not-a-uuid' },
174
+ { name: 'DELETE /invitations/{id}', method: 'DELETE', path: '/api/v1/invitations/not-a-uuid' },
175
+ { name: 'GET /files/{id}', method: 'GET', path: '/api/v1/files/not-a-uuid' },
176
+ { name: 'DELETE /files/{id}', method: 'DELETE', path: '/api/v1/files/not-a-uuid' }
177
+ ];
178
+
179
+ for (const { name, method, path, body } of cases) {
180
+ it(`${name} → 400 validation_error`, async () => {
181
+ const res = await app.app.request(path, {
182
+ method,
183
+ headers: { 'content-type': 'application/json', cookie: ownerCookie },
184
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {})
185
+ });
186
+ expect(res.status).toBe(400);
187
+ expect((await readJson(res)).error.code).toBe('validation_error');
188
+ });
189
+ }
190
+
191
+ it('the literal-segment residue: DELETE /invitations/lookup → 400, not 500', async () => {
192
+ const res = await app.app.request('/api/v1/invitations/lookup', {
193
+ method: 'DELETE',
194
+ headers: { cookie: ownerCookie }
195
+ });
196
+ expect(res.status).toBe(400);
197
+ expect((await readJson(res)).error.code).toBe('validation_error');
198
+ });
199
+
200
+ it('the plain-Hono content routes 404 a non-UUID id (GET and HEAD)', async () => {
201
+ const get = await app.app.request('/api/v1/files/not-a-uuid/content', {
202
+ headers: { cookie: ownerCookie }
203
+ });
204
+ expect(get.status).toBe(404);
205
+ const head = await app.app.request('/api/v1/files/not-a-uuid/content', {
206
+ method: 'HEAD',
207
+ headers: { cookie: ownerCookie }
208
+ });
209
+ expect(head.status).toBe(404);
210
+ });
211
+ });
212
+
213
+ describe('audit cursor range guard', () => {
214
+ it('ignores a cursor past bigint precision (page 1, not 500)', async () => {
215
+ const res = await app.app.request('/api/v1/audit?cursor=99999999999999999999', {
216
+ headers: { cookie: ownerCookie }
217
+ });
218
+ expect(res.status).toBe(200);
219
+ const body = await readJson(res);
220
+ expect(Array.isArray(body.entries)).toBe(true);
221
+ expect(body.entries.length).toBeGreaterThan(0); // setup + key mint are audited
222
+ });
223
+
224
+ it('still ignores a NaN cursor', async () => {
225
+ const res = await app.app.request('/api/v1/audit?cursor=banana', {
226
+ headers: { cookie: ownerCookie }
227
+ });
228
+ expect(res.status).toBe(200);
229
+ });
230
+ });
231
+
232
+ describe('JSON 404 terminator on /api/v1', () => {
233
+ it('an unmatched path answers the JSON wire shape for a browser session, not the SPA', async () => {
234
+ const res = await app.app.request('/api/v1/definitely-not-a-route', {
235
+ headers: { cookie: ownerCookie }
236
+ });
237
+ expect(res.status).toBe(404);
238
+ expect(res.headers.get('content-type')).toContain('application/json');
239
+ expect((await readJson(res)).error.code).toBe('not_found');
240
+ });
241
+
242
+ it('an unmatched path is a JSON 404 for anonymous callers too', async () => {
243
+ const res = await app.app.request('/api/v1/definitely-not-a-route');
244
+ expect(res.status).toBe(404);
245
+ expect((await readJson(res)).error.code).toBe('not_found');
246
+ });
247
+
248
+ it('an unmatched method on /auth/* is a JSON 404, not the SPA', async () => {
249
+ const res = await app.app.request('/api/v1/auth/get-session', { method: 'PUT' });
250
+ expect(res.status).toBe(404);
251
+ expect((await readJson(res)).error.code).toBe('not_found');
252
+ });
253
+
254
+ it('machine principals still get 403 endpoint_not_allowed first', async () => {
255
+ const res = await app.app.request('/api/v1/definitely-not-a-route', {
256
+ headers: { authorization: `Bearer ${apiKey}` }
257
+ });
258
+ expect(res.status).toBe(403);
259
+ expect((await readJson(res)).error.code).toBe('endpoint_not_allowed');
260
+ });
261
+
262
+ it('the SPA fallback still serves non-API paths', async () => {
263
+ const res = await app.app.request('/dashboard-route');
264
+ expect(res.status).toBe(200);
265
+ expect(res.headers.get('content-type')).toContain('text/html');
266
+ });
267
+ });
268
+
269
+ describe('sign-in Origin hardening (login CSRF)', () => {
270
+ const signIn = (extraHeaders: Record<string, string>, ip: string) =>
271
+ app.app.request(
272
+ '/api/v1/auth/sign-in/email',
273
+ json({ email: OWNER.email, password: OWNER.password }, { 'x-forwarded-for': ip, ...extraHeaders })
274
+ );
275
+
276
+ it('rejects a foreign Origin on sign-in', async () => {
277
+ const res = await signIn({ origin: 'https://evil.com' }, '10.7.2.1');
278
+ expect(res.status).toBe(403);
279
+ });
280
+
281
+ it('accepts the configured public origin', async () => {
282
+ const res = await signIn({ origin: 'http://localhost:3000' }, '10.7.2.2');
283
+ expect(res.status).toBe(200);
284
+ });
285
+
286
+ it('accepts an origin-less (CLI-style) sign-in', async () => {
287
+ const res = await signIn({}, '10.7.2.3');
288
+ expect(res.status).toBe(200);
289
+ });
290
+ });