@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,432 @@
1
+ import {
2
+ RateLimiterMemory,
3
+ RateLimiterRedis,
4
+ RateLimiterRes,
5
+ type RateLimiterAbstract
6
+ } from 'rate-limiter-flexible';
7
+ import type { Context, MiddlewareHandler } from 'hono';
8
+ import { getConnInfo } from '@hono/node-server/conninfo';
9
+ import type { EntitlementService, Principal } from '@antasphere/chassis-contract';
10
+ import type { Env } from '../env.js';
11
+ import type { Logger } from '../logger.js';
12
+
13
+ /**
14
+ * Auth-surface rate limits (ours, not Better Auth's built-in): login, OTP
15
+ * sends, invitation acceptance, setup attempts, API-key verification
16
+ * failures. Memory backend per replica by default; Redis when REDIS_URL is
17
+ * set so limits hold across replicas (Profile B).
18
+ */
19
+ /** Workspace-creation attempts one person gets per hour; an address gets ten times that. */
20
+ export const WORKSPACE_CREATE_PER_PERSON = 60;
21
+
22
+ export interface RateLimiters {
23
+ login: RateLimiterAbstract;
24
+ otp: RateLimiterAbstract;
25
+ invitationAccept: RateLimiterAbstract;
26
+ setup: RateLimiterAbstract;
27
+ apiKeyFailures: RateLimiterAbstract;
28
+ /**
29
+ * CLI connect (`POST /sso/cli-connect`, cloud): per client address, sized by
30
+ * CLI_CONNECT_IP_LIMIT_PER_15_MIN for a room behind one address (PRDCT-2958,
31
+ * the lane E verifier's F1: on the login wall, 10 per 15 minutes, the 11th
32
+ * `<tool> login` of a venue was refused). The exchange token it presents is
33
+ * one-time and minted by the hub for one person, behind the hub's own walls.
34
+ */
35
+ cliConnect: RateLimiterAbstract;
36
+ /** RFC 7591 dynamic client registration — unauthenticated by design, so tight. */
37
+ oauthRegister: RateLimiterAbstract;
38
+ /** OAuth token endpoint — a full connector dance is ~8 requests, so generous. */
39
+ oauthToken: RateLimiterAbstract;
40
+ /** The bundled /mcp endpoint — MCP clients are chatty, so generous per IP. */
41
+ mcp: RateLimiterAbstract;
42
+ /** Password reset requests — per IP AND per email, tight (abuse + enumeration). */
43
+ passwordReset: RateLimiterAbstract;
44
+ /** Full-workspace export — expensive streams, keyed per IP AND per user. */
45
+ workspaceExport: RateLimiterAbstract;
46
+ /** Break-glass superadmin recovery — a rare operator action, tight per IP. */
47
+ breakGlass: RateLimiterAbstract;
48
+ /**
49
+ * Workspace creation, the PER-PERSON bucket (key `u:<userId>`) — a rare
50
+ * human act; on cloud every attempt is a call to the hub, and the cloud
51
+ * zero-membership session resolves no principal, so the general quota
52
+ * never sees it. Spent by the handler (api/workspaces.ts) once the caller
53
+ * is IDENTIFIED — never by an unauthenticated request, never by a method
54
+ * that is not the POST.
55
+ */
56
+ workspaceCreate: RateLimiterAbstract;
57
+ /**
58
+ * Workspace creation, the PER-ADDRESS bucket — ten people's worth of the
59
+ * per-person one, spent only by identified callers the per-person bucket
60
+ * let through. One colleague can therefore take at most a tenth of it:
61
+ * an office behind one NAT address is never locked out by one person,
62
+ * while a farm of accounts behind one address is still bounded.
63
+ */
64
+ workspaceCreateAddress: RateLimiterAbstract;
65
+ /** The unauthenticated OpenAPI document — cheap now that it is a boot-time buffer, but still anonymous. */
66
+ openapiDoc: RateLimiterAbstract;
67
+ /**
68
+ * The demo pass redeem (demo sign-in, self-hosted only): an anonymous
69
+ * presentation of a sign-in-equivalent secret, so a guessing wall PER
70
+ * ADDRESS and nothing else — never keyed on the pass, whose legitimate
71
+ * audience it would cap. Mounted only while DEMO_SIGN_IN is on.
72
+ */
73
+ demoRedeem: RateLimiterAbstract;
74
+ /**
75
+ * Shared factory riding the same backend (Redis when REDIS_URL is set,
76
+ * memory otherwise) for buckets sized at runtime — the per-principal
77
+ * request quota creates one limiter per distinct quota tier through this.
78
+ */
79
+ make: (prefix: string, points: number, durationSec: number) => RateLimiterAbstract;
80
+ }
81
+
82
+ /**
83
+ * A tool's own bucket: the three arguments the shared `make` factory is
84
+ * called with. The store prefix is observable (`rl:<prefix>` in Redis), so a
85
+ * declaration is the bucket's identity, not only its size.
86
+ */
87
+ export interface BucketDeclaration {
88
+ prefix: string;
89
+ points: number;
90
+ durationSec: number;
91
+ }
92
+
93
+ /**
94
+ * The chassis buckets plus the TOOL's own (`toolBuckets`, name → declaration),
95
+ * all built by the same `make`: one store (Redis when REDIS_URL is set, memory
96
+ * otherwise), one key-prefix scheme. A tool bucket can never take a chassis
97
+ * bucket's name — the type refuses it.
98
+ */
99
+ export async function createRateLimiters<TBucket extends string = never>(
100
+ env: Pick<Env, 'REDIS_URL'> & Partial<Pick<Env, 'CLI_CONNECT_IP_LIMIT_PER_15_MIN'>>,
101
+ logger: Logger,
102
+ toolBuckets?: { [K in TBucket]: K extends keyof RateLimiters ? never : BucketDeclaration }
103
+ ): Promise<RateLimiters & Record<TBucket, RateLimiterAbstract>> {
104
+ let make: (prefix: string, points: number, durationSec: number) => RateLimiterAbstract;
105
+
106
+ if (env.REDIS_URL) {
107
+ const { Redis } = await import('ioredis');
108
+ const client = new Redis(env.REDIS_URL, { enableOfflineQueue: false });
109
+ client.on('error', (err: Error) => logger.error({ err }, 'redis rate-limit store error'));
110
+ make = (prefix, points, duration) =>
111
+ new RateLimiterRedis({
112
+ storeClient: client,
113
+ keyPrefix: `rl:${prefix}`,
114
+ points,
115
+ duration,
116
+ // If Redis blips, fail open to memory rather than dropping requests.
117
+ insuranceLimiter: new RateLimiterMemory({ points, duration })
118
+ });
119
+ logger.info('rate limiting backed by redis');
120
+ } else {
121
+ make = (prefix, points, duration) =>
122
+ new RateLimiterMemory({ keyPrefix: `rl:${prefix}`, points, duration });
123
+ }
124
+
125
+ const chassis = {
126
+ login: make('login', 10, 15 * 60),
127
+ otp: make('otp', 5, 10 * 60),
128
+ invitationAccept: make('inv-accept', 10, 60 * 60),
129
+ setup: make('setup', 5, 60 * 60),
130
+ apiKeyFailures: make('key-fail', 20, 15 * 60),
131
+ cliConnect: make('cli-connect', Math.max(1, env.CLI_CONNECT_IP_LIMIT_PER_15_MIN ?? 300), 15 * 60),
132
+ oauthRegister: make('oauth-dcr', 10, 60 * 60),
133
+ oauthToken: make('oauth-token', 60, 60),
134
+ // Per ADDRESS: an event's teams' agents share one venue address (2026-10-01: ten times 120).
135
+ mcp: make('mcp', 1200, 60),
136
+ passwordReset: make('pw-reset', 5, 10 * 60),
137
+ workspaceExport: make('ws-export', 5, 600),
138
+ breakGlass: make('break-glass', 10, 60 * 60),
139
+ workspaceCreate: make('ws-create', WORKSPACE_CREATE_PER_PERSON, 60 * 60),
140
+ workspaceCreateAddress: make('ws-create-ip', WORKSPACE_CREATE_PER_PERSON * 10, 60 * 60),
141
+ openapiDoc: make('openapi', 60, 60),
142
+ demoRedeem: make('demo-redeem', 20, 15 * 60)
143
+ } satisfies Omit<RateLimiters, 'make'>;
144
+ // The tool's buckets are built after the chassis ones, in declaration order.
145
+ const tool = {} as Record<TBucket, RateLimiterAbstract>;
146
+ for (const [name, d] of Object.entries(toolBuckets ?? {}) as Array<[TBucket, BucketDeclaration]>) {
147
+ tool[name] = make(d.prefix, d.points, d.durationSec);
148
+ }
149
+
150
+ return { ...chassis, ...tool, make };
151
+ }
152
+
153
+ /** The client-identity function: what keys a per-IP bucket and the audit rows. */
154
+ export type ClientIpFn = (c: Context) => string;
155
+
156
+ /**
157
+ * Client identity for buckets and audit. x-forwarded-for is only believed
158
+ * when TRUST_PROXY says a proxy we control sets it; otherwise the socket
159
+ * address wins and spoofed headers are ignored. We read the RIGHTMOST hop —
160
+ * the one appended (or set) by the trusted proxy itself; the leftmost is
161
+ * client-claimed and spoofable whenever the proxy appends rather than
162
+ * overwrites. Note the compose caveat: without a proxy, Docker NAT can
163
+ * collapse external clients onto one address — per-IP limits are only as
164
+ * granular as what the socket sees.
165
+ */
166
+ export function makeClientIp(trustProxy: boolean): ClientIpFn {
167
+ return (c) => {
168
+ if (trustProxy) {
169
+ const hops = c.req.header('x-forwarded-for')?.split(',');
170
+ const xff = hops?.[hops.length - 1]?.trim();
171
+ if (xff) return xff;
172
+ }
173
+ try {
174
+ return getConnInfo(c).remote.address ?? 'unknown';
175
+ } catch {
176
+ // No socket (tests via app.request()) — one shared bucket.
177
+ return 'unknown';
178
+ }
179
+ };
180
+ }
181
+
182
+ /** 429 with the standard wire shape when the bucket is empty. */
183
+ /**
184
+ * WHEN a request costs a point.
185
+ *
186
+ * - `arrival` (default): consume before the handler runs. Correct for
187
+ * everything whose cost IS the arrival — sending mail, accepting an
188
+ * invitation, registering an OAuth client, running setup.
189
+ * - `failure`: check the bucket before the handler (an exhausted bucket
190
+ * still 429s immediately) but consume only when the handler answered a
191
+ * client error. Correct for CREDENTIAL-VERIFICATION walls: on arrival, the
192
+ * limiter cannot yet know whether the caller is the account owner, so an
193
+ * email-keyed bucket consumed up front lets anyone who knows an address
194
+ * spend that account's whole budget and lock its owner out — including on
195
+ * the requests the owner themselves gets right. Consuming on failure keeps
196
+ * the brute-force wall exactly as tight (a guess is a failure) while a
197
+ * legitimate sign-in costs nothing.
198
+ */
199
+ export interface RateLimitOptions {
200
+ consumeOn?: 'arrival' | 'failure';
201
+ }
202
+
203
+ const RATE_LIMITED_BODY = {
204
+ error: { code: 'rate_limited', message: 'Too many requests, slow down' }
205
+ } as const;
206
+
207
+ export function rateLimit(
208
+ limiter: RateLimiterAbstract,
209
+ clientIp: ClientIpFn,
210
+ extraKeys?: (c: Context) => Promise<string[]>,
211
+ options: RateLimitOptions = {}
212
+ ): MiddlewareHandler {
213
+ const consumeOn = options.consumeOn ?? 'arrival';
214
+ return async (c, next) => {
215
+ const keys = [clientIp(c), ...(extraKeys ? await extraKeys(c) : [])];
216
+
217
+ if (consumeOn === 'arrival') {
218
+ try {
219
+ for (const key of keys) {
220
+ await limiter.consume(key);
221
+ }
222
+ } catch {
223
+ return c.json(RATE_LIMITED_BODY, 429);
224
+ }
225
+ return next();
226
+ }
227
+
228
+ // Failure-only: read the buckets (no consume) and refuse an exhausted one
229
+ // BEFORE the handler, so a drained wall still costs an attacker nothing to
230
+ // hit and no credential check runs. Backend errors fail open, matching
231
+ // every other limiter here.
232
+ for (const key of keys) {
233
+ const state = await limiter.get(key).catch(() => null);
234
+ if (state && state.remainingPoints <= 0 && state.msBeforeNext > 0) {
235
+ return c.json(RATE_LIMITED_BODY, 429);
236
+ }
237
+ }
238
+ await next();
239
+ // 4xx/5xx = the credential was not accepted. Better Auth answers 401 for a
240
+ // bad password, 400 for a malformed attempt, 403 for a refused origin —
241
+ // all of them are attempts that must cost. A 429 is not: it is a wall
242
+ // behind this one (the library's own 3-per-10-seconds on `/sign-in*`)
243
+ // saying "slow down", no credential was judged. Counting it let a burst of
244
+ // ordinary sign-ins from one address (a room behind one public IP) spend
245
+ // this 15-minute wall in seconds (PRDCT-2830, PRDCT-2848).
246
+ if (c.res.status >= 400 && c.res.status !== 429) {
247
+ for (const key of keys) {
248
+ await limiter.consume(key).catch(() => {});
249
+ }
250
+ }
251
+ };
252
+ }
253
+
254
+ /**
255
+ * Per-email key for OTP sends and password login (per IP AND per email): the
256
+ * email key keeps per-account protection intact when NAT or a rotating
257
+ * attacker collapses/expands the IP dimension.
258
+ */
259
+ export async function emailKeyOf(c: Context): Promise<string[]> {
260
+ try {
261
+ const body = (await c.req.raw.clone().json()) as { email?: string };
262
+ if (body?.email && typeof body.email === 'string') {
263
+ return [`email:${body.email.toLowerCase().trim()}`];
264
+ }
265
+ } catch {
266
+ // non-JSON body — the IP key alone applies
267
+ }
268
+ return [];
269
+ }
270
+
271
+ // ── General per-principal request quota (I3) ────────────────────────────────
272
+
273
+ /** Outcome of one quota consume — everything the HTTP layer needs for headers. */
274
+ export interface QuotaDecision {
275
+ ok: boolean;
276
+ /** The sustained per-minute limit (what RateLimit-Limit reports). */
277
+ limit: number;
278
+ remaining: number;
279
+ /** Seconds until the sustained window resets. */
280
+ resetSeconds: number;
281
+ /** Present when ok=false: seconds the client should wait before retrying. */
282
+ retryAfterSeconds?: number;
283
+ }
284
+
285
+ export interface RequestQuotaService {
286
+ /** Consume one request for this principal; null = quota disabled (no headers). */
287
+ consume(principal: Principal): Promise<QuotaDecision | null>;
288
+ }
289
+
290
+ /** RateLimit draft headers + the X- legacy mirror; reset values are delta seconds. */
291
+ export function quotaHeaderEntries(d: QuotaDecision): Array<[string, string]> {
292
+ const remaining = String(Math.max(0, d.remaining));
293
+ const reset = String(Math.max(0, d.resetSeconds));
294
+ const limit = String(d.limit);
295
+ return [
296
+ ['RateLimit-Limit', limit],
297
+ ['RateLimit-Remaining', remaining],
298
+ ['RateLimit-Reset', reset],
299
+ ['X-RateLimit-Limit', limit],
300
+ ['X-RateLimit-Remaining', remaining],
301
+ ['X-RateLimit-Reset', reset]
302
+ ];
303
+ }
304
+
305
+ /**
306
+ * Bucket key for the general API quota, derived ONLY from the authenticated
307
+ * principal — never from a client-supplied header, so a caller can neither
308
+ * rotate its own bucket nor fill another principal's. Two API keys of the
309
+ * same user are separate buckets (a leaked key can be throttled/revoked
310
+ * without starving the owner's other integrations); the same user's session
311
+ * is a third.
312
+ */
313
+ export function principalBucketKey(p: Principal): string {
314
+ if (p.via === 'api_key') return `key:${p.apiKeyId ?? p.userId}`;
315
+ if (p.via === 'oauth') return `oauth:${p.userId}`;
316
+ return `session:${p.userId}`;
317
+ }
318
+
319
+ export interface RequestQuotaDeps {
320
+ entitlements: EntitlementService;
321
+ /** The shared limiter factory from createRateLimiters — Redis-backed when configured. */
322
+ make: RateLimiters['make'];
323
+ logger: Logger;
324
+ }
325
+
326
+ /**
327
+ * Per-principal request quota over the shared rate-limit backend. Two buckets
328
+ * per principal: a sustained per-minute window (the quota agents budget
329
+ * against — headers describe this one) and a 1-second spike cap that smooths
330
+ * thundering herds without burning sustained points on rejected spikes.
331
+ *
332
+ * Limits come from EntitlementService.getRequestQuota so a plan-aware edition
333
+ * varies them per principal; limiter instances are created lazily per
334
+ * distinct quota size (a product has a handful of plans, not thousands) and
335
+ * the size is part of the store prefix, so a plan change moves the principal
336
+ * to a fresh bucket instead of misreading the old counter.
337
+ *
338
+ * Failure posture matches the auth-surface limiters: Redis outages fall back
339
+ * to the per-replica insurance limiter inside `make`; any other backend or
340
+ * entitlement failure logs and FAILS OPEN (availability over throttling — the
341
+ * quota is a cost control, not an authz boundary; authz stays fail-closed).
342
+ */
343
+ export function createRequestQuota({ entitlements, make, logger }: RequestQuotaDeps): RequestQuotaService {
344
+ const sustained = new Map<number, RateLimiterAbstract>();
345
+ const burst = new Map<number, RateLimiterAbstract>();
346
+ const limiterFor = (
347
+ cache: Map<number, RateLimiterAbstract>,
348
+ prefix: string,
349
+ points: number,
350
+ durationSec: number
351
+ ): RateLimiterAbstract => {
352
+ let limiter = cache.get(points);
353
+ if (!limiter) {
354
+ limiter = make(`${prefix}:${points}`, points, durationSec);
355
+ cache.set(points, limiter);
356
+ }
357
+ return limiter;
358
+ };
359
+
360
+ return {
361
+ async consume(principal) {
362
+ let perMinute: number;
363
+ let burstPerSecond: number;
364
+ try {
365
+ ({ perMinute, burstPerSecond } = await entitlements.getRequestQuota(principal));
366
+ } catch (err) {
367
+ logger.error({ err }, 'request quota lookup failed — allowing request');
368
+ return null;
369
+ }
370
+ // Guard a product's custom (non-TS-checked) EntitlementService returning
371
+ // a non-numeric quota: a bare `undefined` would slip past `<= 0` and hand
372
+ // rate-limiter-flexible `points: undefined` (a surprise 4/min). Fail open.
373
+ if (!Number.isFinite(perMinute) || !Number.isFinite(burstPerSecond)) {
374
+ logger.error(
375
+ { perMinute, burstPerSecond },
376
+ 'request quota returned a non-numeric value — allowing request'
377
+ );
378
+ return null;
379
+ }
380
+ if (perMinute <= 0) return null; // 0 = unlimited, a conscious operator opt-out
381
+
382
+ const key = principalBucketKey(principal);
383
+ const sustainedLimiter = limiterFor(sustained, 'apiq', perMinute, 60);
384
+
385
+ // Spike cap first: a rejected burst must not burn sustained points —
386
+ // the 1 s bucket self-heals and the minute budget stays intact.
387
+ if (burstPerSecond > 0) {
388
+ try {
389
+ await limiterFor(burst, 'apiq-burst', burstPerSecond, 1).consume(key);
390
+ } catch (rejection) {
391
+ if (!(rejection instanceof RateLimiterRes)) {
392
+ logger.error({ err: rejection }, 'rate-limit backend failure — allowing request');
393
+ return null;
394
+ }
395
+ // Report the sustained bucket's state (read-only) so headers stay
396
+ // meaningful; the retry hint comes from the 1 s burst window.
397
+ const state = await sustainedLimiter.get(key).catch(() => null);
398
+ return {
399
+ ok: false,
400
+ limit: perMinute,
401
+ remaining: state ? Math.max(0, state.remainingPoints) : perMinute,
402
+ resetSeconds: Math.ceil((state?.msBeforeNext ?? 0) / 1000),
403
+ retryAfterSeconds: Math.max(1, Math.ceil(rejection.msBeforeNext / 1000))
404
+ };
405
+ }
406
+ }
407
+
408
+ try {
409
+ const res = await sustainedLimiter.consume(key);
410
+ return {
411
+ ok: true,
412
+ limit: perMinute,
413
+ remaining: res.remainingPoints,
414
+ resetSeconds: Math.ceil(res.msBeforeNext / 1000)
415
+ };
416
+ } catch (rejection) {
417
+ if (!(rejection instanceof RateLimiterRes)) {
418
+ logger.error({ err: rejection }, 'rate-limit backend failure — allowing request');
419
+ return null;
420
+ }
421
+ const retry = Math.max(1, Math.ceil(rejection.msBeforeNext / 1000));
422
+ return {
423
+ ok: false,
424
+ limit: perMinute,
425
+ remaining: 0,
426
+ resetSeconds: retry,
427
+ retryAfterSeconds: retry
428
+ };
429
+ }
430
+ }
431
+ };
432
+ }
@@ -0,0 +1,54 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ import { randomUUID } from 'node:crypto';
3
+ import type { Logger } from '../logger.js';
4
+ import { routeLabel } from '../route-label.js';
5
+
6
+ declare module 'hono' {
7
+ interface ContextVariableMap {
8
+ requestId: string;
9
+ logger: Logger;
10
+ }
11
+ }
12
+
13
+ /**
14
+ * First middleware in the chain: binds a request id (propagated from
15
+ * x-request-id when a proxy already assigned one) and a child logger carrying
16
+ * it, and writes the completion log line with latency. Everything downstream
17
+ * (traces, audit entries) reuses this id, which is what makes one request
18
+ * followable across log, trace, and audit.
19
+ */
20
+ /** Propagated ids must be sane: bounded length, log-safe charset. */
21
+ const REQUEST_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
22
+
23
+ export function requestId(base: Logger): MiddlewareHandler {
24
+ return async (c, next) => {
25
+ const incoming = c.req.header('x-request-id');
26
+ const id = incoming && REQUEST_ID_RE.test(incoming) ? incoming : randomUUID();
27
+ const log = base.child({ requestId: id });
28
+ c.set('requestId', id);
29
+ c.set('logger', log);
30
+ c.header('x-request-id', id);
31
+ const start = performance.now();
32
+ try {
33
+ await next();
34
+ } finally {
35
+ const latencyMs = Math.round((performance.now() - start) * 100) / 100;
36
+ log.info(
37
+ {
38
+ method: c.req.method,
39
+ // The MATCHED ROUTE PATTERN, never the raw URL path — see
40
+ // route-label.ts. On THIS repo that is not hypothetical: the public
41
+ // viewer is `/v/:secret`, so logging c.req.path wrote every live
42
+ // share-link capability into the log stream, where redaction (which
43
+ // keys off object properties) can do nothing about one opaque
44
+ // string. Read after next() so routing has resolved.
45
+ path: routeLabel(c),
46
+ status: c.error ? 500 : c.res.status,
47
+ latencyMs,
48
+ ...(c.error ? { failed: true } : {})
49
+ },
50
+ 'request'
51
+ );
52
+ }
53
+ };
54
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Fail-closed endpoint allowlist for machine principals (API keys now, OAuth
3
+ * bearer tokens in M6). Ported from a predecessor template's oauth-scopes.ts.
4
+ *
5
+ * Machine principals are deliberately MORE restricted than the user they
6
+ * belong to: they can only reach endpoints listed here, each behind its
7
+ * scope. Anything not listed returns null and the gate 403s — new endpoints
8
+ * are unreachable to keys/tokens until someone consciously opens them.
9
+ * Session principals are role-gated instead and never pass through this.
10
+ *
11
+ * Pure logic (no I/O) so it stays unit-testable. The chassis owns the
12
+ * MECHANISM and the rules of its own endpoints; the scope NAMES and the rules
13
+ * of the tool's endpoints are the tool's (`createScopeAllowlist`).
14
+ */
15
+ export function looksLikeJwt(token: string): boolean {
16
+ return token.split('.').length === 3;
17
+ }
18
+
19
+ /**
20
+ * One tool rule: the scope a machine principal needs for this request, or
21
+ * null when the rule does not speak about it (the next rule is asked; no rule
22
+ * speaking = denied).
23
+ */
24
+ export type ScopeRule<S extends string> = (path: string, method: string, isRead: boolean) => S | null;
25
+
26
+ export interface ScopeAllowlistOptions<S extends string> {
27
+ /** The tool's scope behind the chassis READ surfaces (`/me`, the files reads). */
28
+ read: S;
29
+ /** The tool's scope behind the chassis MUTATIONS (files writes, the CLI key self-revoke). */
30
+ write: S;
31
+ /** The tool's dedicated opt-in scope for the full-workspace export. */
32
+ dataExport: S;
33
+ /** The tool's own rules, asked in order AFTER the chassis rules. */
34
+ rules: ReadonlyArray<ScopeRule<S>>;
35
+ }
36
+
37
+ /**
38
+ * Builds the allowlist function: the chassis rules FIRST, then the tool's
39
+ * rules in order, and `null` (the gate's 403 `endpoint_not_allowed`) when
40
+ * nothing matched — fail-closed by construction.
41
+ */
42
+ /**
43
+ * The chassis project routes a machine principal may reach, EXACT shapes only:
44
+ * reads under the read scope, writes under the write scope. Anything else
45
+ * under `/projects` answers null HERE, so a tool's own route on a project (its
46
+ * link to the project, say) is the tool's rule to open, and an unknown shape
47
+ * stays closed.
48
+ */
49
+ const PROJECT_ID = '[0-9a-fA-F-]{36}';
50
+ const PROJECT_RE = new RegExp(`^/api/v1/projects/${PROJECT_ID}$`);
51
+ const PROJECT_ARCHIVE_RE = new RegExp(`^/api/v1/projects/${PROJECT_ID}/(?:archive|unarchive)$`);
52
+ const PROJECT_MEMBERS_RE = new RegExp(`^/api/v1/projects/${PROJECT_ID}/members$`);
53
+ const PROJECT_MEMBER_RE = new RegExp(`^/api/v1/projects/${PROJECT_ID}/members/[^/]+$`);
54
+ const PROJECT_TEAM_RE = new RegExp(`^/api/v1/projects/${PROJECT_ID}/teams/${PROJECT_ID}$`);
55
+
56
+ function projectAccess(path: string, method: string): 'read' | 'write' | null {
57
+ if (path === '/api/v1/projects') return method === 'GET' ? 'read' : method === 'POST' ? 'write' : null;
58
+ if (PROJECT_RE.test(path)) return method === 'GET' ? 'read' : method === 'PATCH' ? 'write' : null;
59
+ if (PROJECT_ARCHIVE_RE.test(path)) return method === 'POST' ? 'write' : null;
60
+ if (PROJECT_MEMBERS_RE.test(path)) return method === 'GET' ? 'read' : method === 'POST' ? 'write' : null;
61
+ if (PROJECT_MEMBER_RE.test(path)) return method === 'PATCH' || method === 'DELETE' ? 'write' : null;
62
+ if (PROJECT_TEAM_RE.test(path)) return method === 'PATCH' || method === 'DELETE' ? 'write' : null;
63
+ return null;
64
+ }
65
+
66
+ /**
67
+ * The chassis team routes a machine principal may reach (PRDCT-2813), EXACT
68
+ * shapes only, the projects' pattern: the reads under the read scope, the
69
+ * writes under the write scope, the handler's owner-or-admin check staying
70
+ * the authority (and the hub-managed gate the refusal in a hub-origin
71
+ * workspace). A person is named by their user id in the path, never by a
72
+ * membership id.
73
+ */
74
+ const TEAM_RE = new RegExp(`^/api/v1/teams/${PROJECT_ID}$`);
75
+ const TEAM_MEMBERS_RE = new RegExp(`^/api/v1/teams/${PROJECT_ID}/members$`);
76
+ const TEAM_MEMBER_RE = new RegExp(`^/api/v1/teams/${PROJECT_ID}/members/[^/]+$`);
77
+
78
+ function teamAccess(path: string, method: string): 'read' | 'write' | null {
79
+ if (path === '/api/v1/teams') return method === 'GET' ? 'read' : method === 'POST' ? 'write' : null;
80
+ if (TEAM_RE.test(path)) {
81
+ return method === 'GET' ? 'read' : method === 'PATCH' || method === 'DELETE' ? 'write' : null;
82
+ }
83
+ if (TEAM_MEMBERS_RE.test(path)) return method === 'GET' ? 'read' : method === 'POST' ? 'write' : null;
84
+ if (TEAM_MEMBER_RE.test(path)) return method === 'DELETE' ? 'write' : null;
85
+ return null;
86
+ }
87
+
88
+ export function createScopeAllowlist<S extends string>({
89
+ read,
90
+ write,
91
+ dataExport,
92
+ rules
93
+ }: ScopeAllowlistOptions<S>): (path: string, method: string) => S | null {
94
+ return function requiredScopeFor(path: string, method: string): S | null {
95
+ const isRead = method === 'GET' || method === 'HEAD';
96
+ if (path === '/api/v1/me' && isRead) return read;
97
+ // Files: the substrate machine callers actually need (agents pushing and
98
+ // pulling artifacts). Reads → the read scope, mutations → the write scope.
99
+ if (path === '/api/v1/files' || path.startsWith('/api/v1/files/')) {
100
+ return isRead ? read : write;
101
+ }
102
+ // CLI logout self-revoke (api/cli-auth.ts): the ONE /cli/auth route open
103
+ // to machines — DELETE revokes exactly the PRESENTING key. Safe to open
104
+ // because the route names no key (no id parameter): the handler acts only
105
+ // on principal.apiKeyId, resolved from the presented secret, and refuses
106
+ // every non-key principal — so the widest possible effect is a credential
107
+ // revoking ITSELF. The write scope because revocation is a mutation
108
+ // and every CLI-minted key carries it (mirrors the hub's account:write
109
+ // entry). Exact path + method match: any OTHER method on the path, and
110
+ // the PUBLIC mint routes (/cli/auth/request, /cli/auth/complete — which
111
+ // never reach this gate anyway), stay fail-closed.
112
+ if (path === '/api/v1/cli/auth/key' && method === 'DELETE') return write;
113
+ // The person's default workspace (api/workspaces.ts, PRDCT-2815): a
114
+ // setting of the PERSON a credential stands for, which the CLI sets.
115
+ // The route names no workspace and acts on the caller's own membership
116
+ // rows only; a key pinned to one workspace is refused by the handler.
117
+ // Exact path + method: nothing else under /me opens with it.
118
+ if (path === '/api/v1/me/default-workspace' && method === 'PUT') return write;
119
+ // Full-workspace export: a dedicated opt-in scope, NEVER the read scope — any
120
+ // admin read key would otherwise be a whole-tenant exfiltration tool.
121
+ // (Account deletion, DELETE /members/{id}, stays deliberately UNLISTED:
122
+ // machines 403 fail-closed; deleting people is a session-only act.
123
+ // Break-glass, /admin/break-glass/*, is likewise deliberately UNLISTED:
124
+ // superadmin recovery is a human session act — a key or token whose owner
125
+ // is on SUPERADMIN_EMAILS still 403s here, fail-closed. Never list it.)
126
+ if (path === '/api/v1/workspace/export' && isRead) return dataExport;
127
+ // Projects: each route listed by its exact shape and method (above).
128
+ const project = projectAccess(path, method);
129
+ if (project !== null) return project === 'read' ? read : write;
130
+ // Teams: the same discipline (above).
131
+ const team = teamAccess(path, method);
132
+ if (team !== null) return team === 'read' ? read : write;
133
+ for (const rule of rules) {
134
+ const needed = rule(path, method, isRead);
135
+ if (needed !== null) return needed;
136
+ }
137
+ // Deliberately UNLISTED chassis surfaces (fail-closed 403 for
138
+ // keys/tokens), like break-glass:
139
+ // - /sso/logout (cloud) — single logout is a BROWSER act: a machine
140
+ // credential must never be able to end its user's sessions. Never
141
+ // list it.
142
+ // - POST /workspaces — creating a workspace (on cloud: an organization at
143
+ // the hub, as the user) is a HUMAN act from the dashboard. A key or a
144
+ // token must never be able to mint tenants for its holder; the handler
145
+ // re-checks `via === 'session'` on top (api/workspaces.ts). Never list it.
146
+ // - /me/onboarding/dismiss (cloud) — the first-run welcome is a browser
147
+ // concern; machines carry no banner to dismiss.
148
+ return null;
149
+ };
150
+ }