@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,5 @@
1
+ export { AccountDeletionService, LastOwnerError, isLastOwnerDbError } from './deletion.js';
2
+ export { ErasureLog, emailHash } from './erasure-log.js';
3
+ export { mintRefusal } from './mint-refusal.js';
4
+ export { isDuplicateAccountError } from './signup-duplicate.js';
5
+ export { parseSuperadminEmails } from './superadmin.js';
@@ -0,0 +1,67 @@
1
+ import { and, eq, ne } from 'drizzle-orm';
2
+ import { workspaceMembers, type Db } from '@antasphere/chassis-db';
3
+
4
+ /**
5
+ * Shared refusal for the SIGN-IN-EQUIVALENT mint routes (PRDCT-1354,
6
+ * findings AUTH-1/AUTH-2/AUTH-8). `/members/{id}/reset-link`,
7
+ * `/members/{id}/change-email-link` and the demo pass mint
8
+ * (`POST /demo/passes`) all hand the caller a bearer credential
9
+ * for ANOTHER user's GLOBAL account: consuming any one of them signs the
10
+ * target in (LESSONS.md M6: "Consuming a change-email JWT while logged
11
+ * out CREATES a session for the target user"). A `user` row is
12
+ * instance-global, so the blast radius of a mint is every workspace that
13
+ * user belongs to — not just the minter's.
14
+ *
15
+ * Two target classes therefore make a mint a CROSS-TENANT takeover and
16
+ * are refused outright:
17
+ *
18
+ * 1. `origin='guest'` — the guest row exists for principal resolution
19
+ * only (D2). It was created by the per-resource claim path for an
20
+ * EXTERNAL party whose account is not this tenant's to administer;
21
+ * the host tenant never owned that credential. Admins have no
22
+ * recovery duty toward a guest, so there is nothing to trade away.
23
+ * 2. a target holding a membership in ANY OTHER workspace — the minted
24
+ * credential would carry into that workspace too. "Admin of the
25
+ * workspace" is not "owner of the person"; a user who works with two
26
+ * tenants must never be recoverable by either one unilaterally.
27
+ * Recovery for such a user is self-serve (`/request-password-reset`)
28
+ * or the operator's break-glass surface.
29
+ *
30
+ * The role gate is OWNER, not admin (AUTH-8): the old guards only fired
31
+ * when `target.role === 'owner'`, so any admin could mint against any
32
+ * other admin and escalate laterally. Minting someone else's credential
33
+ * is an owner-level act on every route that calls this.
34
+ *
35
+ * Fail-CLOSED shape: a new route that mints a credential for another user
36
+ * must call this helper. Returning `null` means "no refusal found".
37
+ * `guestTargetMessage` is the tool's `guest_target` sentence
38
+ * (`copy.guestTarget`).
39
+ */
40
+ export async function mintRefusal(
41
+ db: Db,
42
+ guestTargetMessage: string,
43
+ principal: { workspaceId: string },
44
+ target: { userId: string; origin: string }
45
+ ): Promise<{ code: string; message: string } | null> {
46
+ if (target.origin === 'guest') {
47
+ return {
48
+ code: 'guest_target',
49
+ message: guestTargetMessage
50
+ };
51
+ }
52
+ const [foreign] = await db
53
+ .select({ id: workspaceMembers.id })
54
+ .from(workspaceMembers)
55
+ .where(
56
+ and(eq(workspaceMembers.userId, target.userId), ne(workspaceMembers.workspaceId, principal.workspaceId))
57
+ )
58
+ .limit(1);
59
+ if (foreign) {
60
+ return {
61
+ code: 'cross_workspace_target',
62
+ message:
63
+ 'This member also belongs to another workspace — an admin-minted link would carry into it, so it is refused'
64
+ };
65
+ }
66
+ return null;
67
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * True when a failed `signUpEmail` means "this email already has an account":
3
+ * either Better Auth's own pre-check (APIError USER_ALREADY_EXISTS / 422) or,
4
+ * in the tight concurrent-signup race where two callers pass an account lookup
5
+ * together, the losing INSERT's Postgres unique_violation (23505) on the user
6
+ * email — found anywhere down the wrapped error's `cause` chain.
7
+ *
8
+ * Shared by every public account-minting endpoint (invitation accept,
9
+ * collaborator claim): the existence-revealing 409 is answered ONLY here, after
10
+ * the caller has committed real credentials, so a credential-less probe can
11
+ * never read it (PRDCT-1437).
12
+ */
13
+ export function isDuplicateAccountError(e: unknown): boolean {
14
+ for (let cur: unknown = e, depth = 0; cur instanceof Error && depth < 10; cur = cur.cause, depth++) {
15
+ const anyErr = cur as { code?: unknown; status?: unknown; body?: { code?: unknown } };
16
+ if (anyErr.code === '23505') return true;
17
+ if (anyErr.body?.code === 'USER_ALREADY_EXISTS') return true;
18
+ if (anyErr.status === 'UNPROCESSABLE_ENTITY' || anyErr.status === 422) return true;
19
+ }
20
+ return false;
21
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Break-glass superadmin recognition (ADR 010).
3
+ *
4
+ * The capability is env-derived and off by default: `SUPERADMIN_EMAILS` is a
5
+ * comma-separated allowlist, and a caller is superadmin ONLY when all three
6
+ * hold at once:
7
+ *
8
+ * 1. the request carries a Better Auth SESSION (never a machine credential —
9
+ * the break-glass routes are unlisted in the fail-closed scope allowlist,
10
+ * and the handlers resolve the session directly);
11
+ * 2. the session user's email is on the allowlist;
12
+ * 3. that email is VERIFIED (`user.email_verified`) — an unverified match
13
+ * must never qualify, or squatting an allowlisted address would be enough.
14
+ *
15
+ * There is deliberately NO persistent DB super-role: unset/empty env means no
16
+ * superadmin exists and the capability is fully dormant.
17
+ */
18
+
19
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
20
+
21
+ /**
22
+ * Parse `SUPERADMIN_EMAILS` into a lowercased set (Better Auth stores emails
23
+ * lowercased, so membership checks compare in that form).
24
+ *
25
+ * Throws on malformed entries so the env schema fails LOUDLY at boot — a typo
26
+ * must never silently disarm break-glass. `undefined` (unset/blank through
27
+ * `blankToUndefined`) yields the empty set: dormant.
28
+ */
29
+ export function parseSuperadminEmails(raw: string | undefined): Set<string> {
30
+ const allowlist = new Set<string>();
31
+ if (raw === undefined || raw.trim() === '') return allowlist;
32
+ const entries = raw
33
+ .split(',')
34
+ .map((entry) => entry.trim())
35
+ .filter((entry) => entry.length > 0);
36
+ if (entries.length === 0) {
37
+ throw new Error('SUPERADMIN_EMAILS must list at least one email address');
38
+ }
39
+ for (const entry of entries) {
40
+ if (!EMAIL_RE.test(entry)) {
41
+ throw new Error(`SUPERADMIN_EMAILS entry "${entry}" is not a valid email address`);
42
+ }
43
+ allowlist.add(entry.toLowerCase());
44
+ }
45
+ return allowlist;
46
+ }
@@ -0,0 +1,147 @@
1
+ import type { OpenAPIHono } from '@hono/zod-openapi';
2
+ import { and, desc, eq } from 'drizzle-orm';
3
+ import type { ScopeRoutes } from './scope-routes.js';
4
+ import { apiKeys, workspaceMembers, type ApiKey, type Db } from '@antasphere/chassis-db';
5
+ import { sql } from 'drizzle-orm';
6
+ import type { ApiKeyService } from '../apikeys/service.js';
7
+ import { cursorRowId, keysetBefore, pageOf } from '../pagination.js';
8
+ import { requireAuth } from '../middleware/auth-context.js';
9
+
10
+ const err = (code: string, message: string) => ({ error: { code, message } });
11
+
12
+ const toWire = (k: ApiKey) => ({
13
+ id: k.id,
14
+ name: k.name,
15
+ keyId: k.keyId,
16
+ scopes: k.scopes as string[],
17
+ workspaceId: k.workspaceId,
18
+ createdBy: k.createdBy,
19
+ createdAt: k.createdAt.toISOString(),
20
+ lastUsedAt: k.lastUsedAt?.toISOString() ?? null,
21
+ revokedAt: k.revokedAt?.toISOString() ?? null,
22
+ expiresAt: k.expiresAt?.toISOString() ?? null
23
+ });
24
+
25
+ /**
26
+ * API keys are USER credentials (user-scoped credential model): the listing
27
+ * and the revoke are keyed on the CREATOR, not on any workspace — a key acts
28
+ * as its holder wherever their memberships reach, so "my keys" is the only
29
+ * honest scope. Mint stays sessions-only and accepts an optional workspace
30
+ * PIN (least privilege); revocation is creator-only (a workspace admin has
31
+ * no standing over another user's credential).
32
+ */
33
+ export function registerApiKeyRoutes(
34
+ api: OpenAPIHono,
35
+ db: Db,
36
+ service: ApiKeyService,
37
+ routes: Pick<ScopeRoutes, 'apiKeysListRoute' | 'apiKeyCreateRoute' | 'apiKeyRevokeRoute'>
38
+ ): void {
39
+ const { apiKeysListRoute, apiKeyCreateRoute, apiKeyRevokeRoute } = routes;
40
+ api.use('/api-keys', requireAuth());
41
+ api.use('/api-keys/*', requireAuth());
42
+
43
+ api.openapi(apiKeysListRoute, async (c) => {
44
+ const principal = c.get('principal')!;
45
+ const { cursor, limit } = c.req.valid('query');
46
+ const cursorId = cursorRowId(cursor);
47
+ const rows = await db
48
+ .select()
49
+ .from(apiKeys)
50
+ .where(
51
+ and(
52
+ eq(apiKeys.createdBy, principal.userId),
53
+ ...(cursorId
54
+ ? [
55
+ keysetBefore({
56
+ table: apiKeys,
57
+ id: apiKeys.id,
58
+ createdAt: apiKeys.createdAt,
59
+ // The keyset scope column (see pagination.ts): the listing
60
+ // is creator-scoped, so a foreign user's cursor can never
61
+ // position inside this user's ordering.
62
+ workspaceId: apiKeys.createdBy,
63
+ cursorId,
64
+ workspace: principal.userId
65
+ })
66
+ ]
67
+ : [])
68
+ )
69
+ )
70
+ .orderBy(desc(apiKeys.createdAt), desc(apiKeys.id))
71
+ .limit(limit + 1);
72
+ const { page, nextCursor } = pageOf(rows, limit);
73
+ return c.json({ apiKeys: page.map(toWire), nextCursor }, 200);
74
+ });
75
+
76
+ api.openapi(apiKeyCreateRoute, async (c) => {
77
+ const principal = c.get('principal')!;
78
+ // Locked rule: a key never mints a key. Sessions only.
79
+ if (principal.via !== 'session') {
80
+ return c.json(err('sessions_only', 'API keys can only be created from a browser session'), 403);
81
+ }
82
+ const body = c.req.valid('json');
83
+ // Optional PIN: an ACTIVE membership of the pinned workspace is required
84
+ // — one uniform 403 whether the workspace does not exist or the caller
85
+ // is not a member (no oracle about other workspaces).
86
+ if (body.workspaceId) {
87
+ const [member] = await db
88
+ .select({ id: workspaceMembers.id })
89
+ .from(workspaceMembers)
90
+ .where(
91
+ and(
92
+ eq(workspaceMembers.userId, principal.userId),
93
+ eq(workspaceMembers.workspaceId, body.workspaceId),
94
+ eq(workspaceMembers.isActive, true)
95
+ )
96
+ )
97
+ .limit(1);
98
+ if (!member) {
99
+ return c.json(err('no_membership', 'This workspace is not available to this account'), 403);
100
+ }
101
+ }
102
+ // The client sends a TTL in days; the server owns the absolute expiry.
103
+ const expiresAt = body.expiresInDays ? new Date(Date.now() + body.expiresInDays * 86_400_000) : null;
104
+ const minted = await service.mint({
105
+ workspaceId: body.workspaceId ?? null,
106
+ createdBy: principal.userId,
107
+ name: body.name,
108
+ scopes: body.scopes,
109
+ expiresAt
110
+ });
111
+ const [row] = await db.select().from(apiKeys).where(eq(apiKeys.id, minted.id)).limit(1);
112
+ c.set('audit', {
113
+ action: 'apikey.create',
114
+ resourceType: 'api_key',
115
+ resourceId: minted.id,
116
+ metadata: {
117
+ name: body.name,
118
+ scopes: body.scopes,
119
+ workspaceId: body.workspaceId ?? null,
120
+ expiresAt: expiresAt?.toISOString() ?? null
121
+ }
122
+ });
123
+ return c.json({ apiKey: toWire(row!), key: minted.key }, 201);
124
+ });
125
+
126
+ api.openapi(apiKeyRevokeRoute, async (c) => {
127
+ const principal = c.get('principal')!;
128
+ const { id } = c.req.valid('param');
129
+ // Creator-only: a key is its creator's credential. A foreign key answers
130
+ // the same 404 as a nonexistent one (existence is not probeable).
131
+ const [key] = await db
132
+ .select()
133
+ .from(apiKeys)
134
+ .where(and(eq(apiKeys.id, id), eq(apiKeys.createdBy, principal.userId)))
135
+ .limit(1);
136
+ if (!key) return c.json(err('not_found', 'API key not found'), 404);
137
+ if (!key.revokedAt) {
138
+ await db
139
+ .update(apiKeys)
140
+ .set({ revokedAt: sql`now()` })
141
+ .where(eq(apiKeys.id, key.id));
142
+ }
143
+ const [updated] = await db.select().from(apiKeys).where(eq(apiKeys.id, key.id)).limit(1);
144
+ c.set('audit', { action: 'apikey.revoke', resourceType: 'api_key', resourceId: key.id });
145
+ return c.json(toWire(updated!), 200);
146
+ });
147
+ }
@@ -0,0 +1,83 @@
1
+ import type { OpenAPIHono } from '@hono/zod-openapi';
2
+ import { count, eq, lt, desc } from 'drizzle-orm';
3
+ import { auditListRoute } from '@antasphere/chassis-contract/routes';
4
+ import { auditLog, user as userTable, type Db } from '@antasphere/chassis-db';
5
+ import { requireRole } from '../middleware/auth-context.js';
6
+ import { auditFilterConditions, auditWhere, cursorId } from '../audit/filters.js';
7
+
8
+ export function registerAuditRoutes(api: OpenAPIHono, db: Db): void {
9
+ api.use('/audit', requireRole('admin'));
10
+ api.openapi(auditListRoute, async (c) => {
11
+ const principal = c.get('principal')!;
12
+ const query = c.req.valid('query');
13
+ const { limit } = query;
14
+
15
+ // SECURITY: the scope is the caller's workspace and nothing in the
16
+ // query can widen it; every filter narrows within it, as a bound
17
+ // parameter (audit/filters.ts). The cursor stays the last row id, so
18
+ // a page is "the next `limit` rows older than the cursor that match
19
+ // the same filters": a client that changes a filter starts over.
20
+ const scope = eq(auditLog.workspaceId, principal.workspaceId);
21
+ const filters = auditFilterConditions(query);
22
+ const cursor = cursorId(query.cursor);
23
+ const where = auditWhere(scope, filters, cursor !== null ? lt(auditLog.id, cursor) : null);
24
+
25
+ const rows = await db
26
+ .select({
27
+ id: auditLog.id,
28
+ actorUserId: auditLog.actorUserId,
29
+ actorEmail: userTable.email,
30
+ actorVia: auditLog.actorVia,
31
+ apiKeyId: auditLog.apiKeyId,
32
+ action: auditLog.action,
33
+ resourceType: auditLog.resourceType,
34
+ resourceId: auditLog.resourceId,
35
+ requestId: auditLog.requestId,
36
+ ip: auditLog.ip,
37
+ metadata: auditLog.metadata,
38
+ createdAt: auditLog.createdAt
39
+ })
40
+ .from(auditLog)
41
+ .leftJoin(userTable, eq(auditLog.actorUserId, userTable.id))
42
+ .where(where)
43
+ .orderBy(desc(auditLog.id))
44
+ .limit(limit + 1);
45
+
46
+ // The total is counted once per listing, on the first page: the count
47
+ // walks the same join and WHERE without the cursor, and a client keeps
48
+ // the figure while it loads the pages after.
49
+ let total: number | null = null;
50
+ if (cursor === null) {
51
+ const [counted] = await db
52
+ .select({ n: count() })
53
+ .from(auditLog)
54
+ .leftJoin(userTable, eq(auditLog.actorUserId, userTable.id))
55
+ .where(auditWhere(scope, filters, null));
56
+ total = counted?.n ?? 0;
57
+ }
58
+
59
+ const page = rows.slice(0, limit);
60
+ const nextCursor = rows.length > limit ? String(page[page.length - 1]?.id ?? '') : null;
61
+ return c.json(
62
+ {
63
+ entries: page.map((r) => ({
64
+ id: r.id,
65
+ actorUserId: r.actorUserId,
66
+ actorEmail: r.actorEmail,
67
+ actorVia: r.actorVia,
68
+ apiKeyId: r.apiKeyId,
69
+ action: r.action,
70
+ resourceType: r.resourceType,
71
+ resourceId: r.resourceId,
72
+ requestId: r.requestId,
73
+ ip: r.ip,
74
+ metadata: r.metadata,
75
+ createdAt: r.createdAt.toISOString()
76
+ })),
77
+ nextCursor,
78
+ total
79
+ },
80
+ 200
81
+ );
82
+ });
83
+ }
@@ -0,0 +1,294 @@
1
+ import type { OpenAPIHono } from '@hono/zod-openapi';
2
+ import type { Context } from 'hono';
3
+ import { and, eq } from 'drizzle-orm';
4
+ import {
5
+ breakGlassClaimOwnershipRoute,
6
+ breakGlassResetTwoFactorRoute
7
+ } from '@antasphere/chassis-contract/routes';
8
+ import { twoFactor, user as userTable, workspaceMembers, workspaces, type Db } from '@antasphere/chassis-db';
9
+ import type { Auth } from '../identity/better-auth.js';
10
+ import type { AuditService } from '../audit/service.js';
11
+ import type { Logger } from '../logger.js';
12
+ import { parseSuperadminEmails } from '../accounts/superadmin.js';
13
+
14
+ /**
15
+ * Break-glass superadmin recovery (ADR 010) — the instance-level recovery
16
+ * surface for the per-tenant hosting model. Two capabilities only:
17
+ *
18
+ * - claim-ownership: make the calling superadmin (or a named existing user)
19
+ * an ACTIVE OWNER of a workspace — creating the membership when absent,
20
+ * reactivating and promoting when present. It ADDS an owner and never
21
+ * removes one, so the migration-0009 last-owner trigger is satisfied by
22
+ * construction. The target workspace is explicit (`workspaceId`) once the
23
+ * instance runs several (ADR 014); a single-workspace instance keeps the
24
+ * old no-argument call.
25
+ * - reset-2fa: clear a locked-out user's second factor (the recovery ADR 009
26
+ * deferred to manual DB surgery). User-level — needs no workspace; its
27
+ * audit row is instance-attributed.
28
+ *
29
+ * SAFETY MODEL (a bug here = full instance compromise — keep all of it):
30
+ *
31
+ * - Off by default: superadmin exists ONLY while SUPERADMIN_EMAILS is set.
32
+ * - Session-only, twice over: these paths are deliberately UNLISTED in the
33
+ * fail-closed scope allowlist (middleware/scopes.ts), so machine
34
+ * principals 403 before reaching the handlers; AND the handlers resolve
35
+ * the Better Auth session directly — a request without a session cookie
36
+ * can never qualify, whatever headers it carries.
37
+ * - Verified email required: an allowlisted address that was never proven
38
+ * (invitation copyable-link accounts are unverified) does NOT match.
39
+ * - Identity is re-read from the DB by session user id — never derived from
40
+ * any header, body field, or client-supplied value.
41
+ * - No requireAuth()/requireRole() gate: the recovering operator may hold a
42
+ * session with NO membership (principal resolves to null), which is
43
+ * exactly the state break-glass exists to fix. The handlers do their own
44
+ * authentication and are exempted from the generic audit middleware
45
+ * because they write their own richer rows (see audit/service.ts).
46
+ */
47
+
48
+ export interface BreakGlassRouteDeps {
49
+ db: Db;
50
+ auth: Auth;
51
+ audit: AuditService;
52
+ logger: Logger;
53
+ /** Raw SUPERADMIN_EMAILS value (already validated by the env schema). */
54
+ superadminEmails: string | undefined;
55
+ }
56
+
57
+ const err = (code: string, message: string) => ({ error: { code, message } });
58
+
59
+ type SuperadminResolution =
60
+ | { kind: 'ok'; caller: { userId: string; email: string; name: string } }
61
+ | { kind: 'unauthenticated' }
62
+ | { kind: 'forbidden' };
63
+
64
+ export function registerBreakGlassRoutes(api: OpenAPIHono, deps: BreakGlassRouteDeps): void {
65
+ const { db, auth, audit, logger } = deps;
66
+ const allowlist = parseSuperadminEmails(deps.superadminEmails);
67
+
68
+ /**
69
+ * Resolve the caller as a superadmin. One uniform `forbidden` for every
70
+ * rejection reason (dormant, not listed, unverified) so the endpoint is
71
+ * not an oracle for the allowlist's contents.
72
+ */
73
+ const resolveSuperadmin = async (c: Context): Promise<SuperadminResolution> => {
74
+ // Session-only: the verified session identity, never a header or bearer.
75
+ // Machine credentials died at the fail-closed scope gate already; a
76
+ // cookieless request has no session and stops here.
77
+ const session = await auth.api.getSession({ headers: c.req.raw.headers });
78
+ if (!session?.user) return { kind: 'unauthenticated' };
79
+ if (allowlist.size === 0) return { kind: 'forbidden' };
80
+ // Fresh row by session user id — email + verification state are read
81
+ // from the database, not trusted from session claims.
82
+ const [u] = await db
83
+ .select({
84
+ id: userTable.id,
85
+ email: userTable.email,
86
+ emailVerified: userTable.emailVerified,
87
+ name: userTable.name
88
+ })
89
+ .from(userTable)
90
+ .where(eq(userTable.id, session.user.id))
91
+ .limit(1);
92
+ if (!u) return { kind: 'forbidden' };
93
+ if (!allowlist.has(u.email.toLowerCase())) return { kind: 'forbidden' };
94
+ if (!u.emailVerified) return { kind: 'forbidden' };
95
+ return { kind: 'ok', caller: { userId: u.id, email: u.email, name: u.name } };
96
+ };
97
+
98
+ /**
99
+ * The workspace an owner-claim targets (ADR 014). Explicit `workspaceId`
100
+ * wins; omitted, it resolves ONLY while the instance runs a single
101
+ * workspace — with several, recovery must name its target (400
102
+ * workspace_required), never guess one.
103
+ */
104
+ const resolveTargetWorkspace = async (
105
+ explicit: string | undefined
106
+ ): Promise<
107
+ { kind: 'ok'; id: string } | { kind: 'not_found' } | { kind: 'not_setup' } | { kind: 'ambiguous' }
108
+ > => {
109
+ if (explicit) {
110
+ const [ws] = await db
111
+ .select({ id: workspaces.id })
112
+ .from(workspaces)
113
+ .where(eq(workspaces.id, explicit))
114
+ .limit(1);
115
+ return ws ? { kind: 'ok', id: ws.id } : { kind: 'not_found' };
116
+ }
117
+ const rows = await db.select({ id: workspaces.id }).from(workspaces).limit(2);
118
+ const [only] = rows;
119
+ if (!only) return { kind: 'not_setup' };
120
+ if (rows.length > 1) return { kind: 'ambiguous' };
121
+ return { kind: 'ok', id: only.id };
122
+ };
123
+
124
+ api.openapi(breakGlassClaimOwnershipRoute, async (c) => {
125
+ const resolved = await resolveSuperadmin(c);
126
+ if (resolved.kind === 'unauthenticated') {
127
+ return c.json(err('unauthenticated', 'Authentication required'), 401);
128
+ }
129
+ if (resolved.kind === 'forbidden') {
130
+ return c.json(err('forbidden', 'Break-glass is not available to this caller'), 403);
131
+ }
132
+ const caller = resolved.caller;
133
+ const body = c.req.valid('json');
134
+
135
+ const targetWs = await resolveTargetWorkspace(body.workspaceId);
136
+ if (targetWs.kind === 'not_setup') {
137
+ return c.json(err('not_setup', 'This instance has not completed setup'), 409);
138
+ }
139
+ if (targetWs.kind === 'not_found') {
140
+ return c.json(err('not_found', 'Target workspace not found'), 404);
141
+ }
142
+ if (targetWs.kind === 'ambiguous') {
143
+ return c.json(
144
+ err('workspace_required', 'This instance has several workspaces — pass workspaceId explicitly'),
145
+ 400
146
+ );
147
+ }
148
+ const ws = { id: targetWs.id };
149
+
150
+ // Target: a named existing user, or the calling superadmin.
151
+ let target = { userId: caller.userId, email: caller.email };
152
+ if (body.userId) {
153
+ const [t] = await db
154
+ .select({ id: userTable.id, email: userTable.email })
155
+ .from(userTable)
156
+ .where(eq(userTable.id, body.userId))
157
+ .limit(1);
158
+ if (!t) return c.json(err('not_found', 'Target user not found'), 404);
159
+ target = { userId: t.id, email: t.email };
160
+ }
161
+
162
+ const [before] = await db
163
+ .select({
164
+ id: workspaceMembers.id,
165
+ role: workspaceMembers.role,
166
+ isActive: workspaceMembers.isActive
167
+ })
168
+ .from(workspaceMembers)
169
+ .where(and(eq(workspaceMembers.workspaceId, ws.id), eq(workspaceMembers.userId, target.userId)))
170
+ .limit(1);
171
+
172
+ // Additive by construction: create-or-promote to an ACTIVE OWNER. The
173
+ // 0009 last-owner trigger only rejects operations that REMOVE the last
174
+ // active owner — adding one is always legal, including the first owner
175
+ // of an ownerless workspace.
176
+ const [after] = await db
177
+ .insert(workspaceMembers)
178
+ // origin='local' on BOTH paths (BG-1, PRDCT-1356): a break-glass owner
179
+ // is the tool's own business, never hub truth. Left at 'hub' (the
180
+ // conflict path once kept the row's old origin), the cloud reconciler's
181
+ // deactivation sweep — which targets origin='hub' rows the hub no
182
+ // longer asserts — undid the recovery ~12 s later.
183
+ .values({ workspaceId: ws.id, userId: target.userId, role: 'owner', isActive: true, origin: 'local' })
184
+ .onConflictDoUpdate({
185
+ target: [workspaceMembers.workspaceId, workspaceMembers.userId],
186
+ set: { role: 'owner', isActive: true, origin: 'local' }
187
+ })
188
+ .returning({ id: workspaceMembers.id });
189
+ if (!after) throw new Error('break-glass ownership upsert returned no row');
190
+
191
+ logger.warn(
192
+ {
193
+ superadminUserId: caller.userId,
194
+ superadminEmail: caller.email,
195
+ targetUserId: target.userId,
196
+ targetEmail: target.email,
197
+ workspaceId: ws.id
198
+ },
199
+ 'BREAK-GLASS: workspace ownership claimed'
200
+ );
201
+ await audit.write({
202
+ workspaceId: ws.id,
203
+ principal: { userId: caller.userId, via: 'session' },
204
+ action: 'break_glass.claim_ownership',
205
+ resourceType: 'member',
206
+ resourceId: after.id,
207
+ requestId: c.get('requestId'),
208
+ metadata: {
209
+ superadminUserId: caller.userId,
210
+ superadminEmail: caller.email,
211
+ targetUserId: target.userId,
212
+ targetEmail: target.email,
213
+ before: before ? { role: before.role, isActive: before.isActive } : null,
214
+ after: { role: 'owner', isActive: true },
215
+ created: !before
216
+ }
217
+ });
218
+
219
+ return c.json(
220
+ {
221
+ memberId: after.id,
222
+ userId: target.userId,
223
+ email: target.email,
224
+ workspaceId: ws.id,
225
+ role: 'owner' as const,
226
+ isActive: true,
227
+ created: !before
228
+ },
229
+ 200
230
+ );
231
+ });
232
+
233
+ api.openapi(breakGlassResetTwoFactorRoute, async (c) => {
234
+ const resolved = await resolveSuperadmin(c);
235
+ if (resolved.kind === 'unauthenticated') {
236
+ return c.json(err('unauthenticated', 'Authentication required'), 401);
237
+ }
238
+ if (resolved.kind === 'forbidden') {
239
+ return c.json(err('forbidden', 'Break-glass is not available to this caller'), 403);
240
+ }
241
+ const caller = resolved.caller;
242
+ const { userId } = c.req.valid('json');
243
+
244
+ const [target] = await db
245
+ .select({
246
+ id: userTable.id,
247
+ email: userTable.email,
248
+ twoFactorEnabled: userTable.twoFactorEnabled
249
+ })
250
+ .from(userTable)
251
+ .where(eq(userTable.id, userId))
252
+ .limit(1);
253
+ if (!target) return c.json(err('not_found', 'Target user not found'), 404);
254
+
255
+ // Clear the factor: the two_factor row (secret + backup codes) and the
256
+ // user flag. Idempotent — resetting an unenrolled user is a no-op 200.
257
+ const removed = await db
258
+ .delete(twoFactor)
259
+ .where(eq(twoFactor.userId, target.id))
260
+ .returning({ id: twoFactor.id });
261
+ await db.update(userTable).set({ twoFactorEnabled: false }).where(eq(userTable.id, target.id));
262
+ const hadTwoFactor = removed.length > 0 || target.twoFactorEnabled === true;
263
+
264
+ logger.warn(
265
+ {
266
+ superadminUserId: caller.userId,
267
+ superadminEmail: caller.email,
268
+ targetUserId: target.id,
269
+ targetEmail: target.email,
270
+ hadTwoFactor
271
+ },
272
+ 'BREAK-GLASS: two-factor reset'
273
+ );
274
+ // INSTANCE-attributed (workspace_id NULL, ADR 014): a second factor is a
275
+ // user property, not any workspace's — the row needs no workspace home.
276
+ await audit.write({
277
+ workspaceId: null,
278
+ principal: { userId: caller.userId, via: 'session' },
279
+ action: 'break_glass.reset_two_factor',
280
+ resourceType: 'user',
281
+ resourceId: target.id,
282
+ requestId: c.get('requestId'),
283
+ metadata: {
284
+ superadminUserId: caller.userId,
285
+ superadminEmail: caller.email,
286
+ targetUserId: target.id,
287
+ targetEmail: target.email,
288
+ hadTwoFactor
289
+ }
290
+ });
291
+
292
+ return c.json({ userId: target.id, hadTwoFactor }, 200);
293
+ });
294
+ }