@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,155 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { OpenAPIHono } from '@hono/zod-openapi';
3
+ import { Readable } from 'node:stream';
4
+ import { pino } from 'pino';
5
+ import { defineChassisContract, type Principal } from '@antasphere/chassis-contract';
6
+ import { defineChassisRoutes } from '@antasphere/chassis-contract/routes';
7
+ import { registerFileRoutes } from '@antasphere/chassis-server/api';
8
+ import type { FileService } from '@antasphere/chassis-server/files';
9
+ import type { PlatformRegistry } from '@antasphere/chassis-server/platform';
10
+ import type { StorageDriver } from '@antasphere/chassis-server/storage';
11
+ import { THINGS_COPY, THINGS_IDENTITY, THINGS_ROUTES_COPY } from '../host/identity.js';
12
+
13
+ /**
14
+ * PLT-39, the half that actually matters: the REGISTERED ROUTE must answer a
15
+ * HEAD with metadata and no body.
16
+ *
17
+ * `head-rewrite.test.ts` pins Hono's rewrite and greps for the dead
18
+ * `api.on('HEAD', …)` idiom, but neither of those touches `api/files.ts` — a
19
+ * mutation that hardcodes `headOnly` to `false` leaves both of them green
20
+ * while every HEAD silently streams the whole blob again. This test drives the
21
+ * real handler, so the `c.req.method === 'HEAD'` derivation is covered by
22
+ * something that fails when it is removed.
23
+ */
24
+ const { fileDeleteRoute } = defineChassisRoutes(
25
+ defineChassisContract({ scopes: [THINGS_IDENTITY.scopes.read] }),
26
+ THINGS_IDENTITY,
27
+ THINGS_ROUTES_COPY
28
+ );
29
+
30
+ const WORKSPACE = '11111111-2222-3333-4444-555555555555';
31
+ const SHA = 'a'.repeat(64);
32
+ const BODY = 'hello head';
33
+
34
+ const principal = {
35
+ userId: 'u1',
36
+ workspaceId: WORKSPACE,
37
+ role: 'owner',
38
+ origin: 'member',
39
+ via: 'session',
40
+ scopes: null
41
+ } as unknown as Principal;
42
+
43
+ function app(): OpenAPIHono {
44
+ const api = new OpenAPIHono();
45
+ api.use('*', async (c, next) => {
46
+ c.set('principal', principal);
47
+ return next();
48
+ });
49
+
50
+ const storage: StorageDriver = {
51
+ name: 'local',
52
+ put: async () => {},
53
+ getStream: async () => Readable.from([Buffer.from(BODY)]),
54
+ exists: async () => true,
55
+ head: async () => ({ sizeBytes: BODY.length }),
56
+ delete: async () => {},
57
+ healthcheck: async () => {}
58
+ } as unknown as StorageDriver;
59
+
60
+ const service = {
61
+ get: async () => ({
62
+ id: '99999999-8888-7777-6666-555555555555',
63
+ workspaceId: WORKSPACE,
64
+ sha256: SHA,
65
+ sizeBytes: BODY.length,
66
+ contentType: 'text/plain',
67
+ originalName: 'head-probe.txt',
68
+ createdBy: 'u1',
69
+ createdAt: new Date()
70
+ })
71
+ } as unknown as FileService;
72
+
73
+ registerFileRoutes(api, {
74
+ service,
75
+ storage,
76
+ registry: { entitlements: { check: async () => ({ allowed: true }) } } as unknown as PlatformRegistry,
77
+ env: { MAX_FILE_SIZE_MB: 100 },
78
+ logger: pino({ level: 'silent' }),
79
+ // PRDCT-1343 made per-deck scoping a required dep. This suite exercises the
80
+ // HEAD route shape (PLT-39), not authorization, so it takes the operator
81
+ // view: undefined = unscoped, exactly what an admin/owner principal gets.
82
+ blobReadScope: () => undefined,
83
+ blobInUse: async () => false,
84
+ fileDeleteRoute,
85
+ fileInUse: THINGS_COPY.fileInUse
86
+ });
87
+ return api;
88
+ }
89
+
90
+ const CONTENT_PATH = '/files/99999999-8888-7777-6666-555555555555/content';
91
+
92
+ describe('GET/HEAD /files/:id/content (one registration, verb read off the request)', () => {
93
+ it('answers HEAD with the metadata headers and NO body', async () => {
94
+ const res = await app().request(CONTENT_PATH, { method: 'HEAD' });
95
+ expect(res.status).toBe(200);
96
+ expect(res.headers.get('content-length')).toBe(String(BODY.length));
97
+ expect(res.headers.get('accept-ranges')).toBe('bytes');
98
+ expect(res.headers.get('etag')).toBe(`"${SHA}"`);
99
+ // The whole point: no bytes were read out of storage for a HEAD.
100
+ expect(await res.text()).toBe('');
101
+ });
102
+
103
+ it('still streams the bytes on GET — the shared handler did not go headOnly for everyone', async () => {
104
+ const res = await app().request(CONTENT_PATH);
105
+ expect(res.status).toBe(200);
106
+ expect(await res.text()).toBe(BODY);
107
+ });
108
+
109
+ it('never opens the blob stream for a HEAD', async () => {
110
+ let streamOpened = 0;
111
+ const api = new OpenAPIHono();
112
+ api.use('*', async (c, next) => {
113
+ c.set('principal', principal);
114
+ return next();
115
+ });
116
+ registerFileRoutes(api, {
117
+ service: {
118
+ get: async () => ({
119
+ id: '99999999-8888-7777-6666-555555555555',
120
+ workspaceId: WORKSPACE,
121
+ sha256: SHA,
122
+ sizeBytes: BODY.length,
123
+ contentType: 'text/plain',
124
+ originalName: 'head-probe.txt',
125
+ createdBy: 'u1',
126
+ createdAt: new Date()
127
+ })
128
+ } as unknown as FileService,
129
+ storage: {
130
+ name: 'local',
131
+ exists: async () => true,
132
+ getStream: async () => {
133
+ streamOpened++;
134
+ return Readable.from([Buffer.from(BODY)]);
135
+ }
136
+ } as unknown as StorageDriver,
137
+ registry: { entitlements: { check: async () => ({ allowed: true }) } } as unknown as PlatformRegistry,
138
+ env: { MAX_FILE_SIZE_MB: 100 },
139
+ logger: pino({ level: 'silent' }),
140
+ // PRDCT-1343 made per-deck scoping a required dep. This suite exercises the
141
+ // HEAD route shape (PLT-39), not authorization, so it takes the operator
142
+ // view: undefined = unscoped, exactly what an admin/owner principal gets.
143
+ blobReadScope: () => undefined,
144
+ blobInUse: async () => false,
145
+ fileDeleteRoute,
146
+ fileInUse: THINGS_COPY.fileInUse
147
+ });
148
+
149
+ await api.request(CONTENT_PATH, { method: 'HEAD' });
150
+ expect(streamOpened).toBe(0);
151
+ const get = await api.request(CONTENT_PATH);
152
+ await get.text();
153
+ expect(streamOpened).toBe(1);
154
+ });
155
+ });
@@ -0,0 +1,97 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { contentDispositionFor, parseRangeHeader } from '@antasphere/chassis-server/files';
3
+ import { blobKey } from '@antasphere/chassis-server/storage';
4
+
5
+ describe('parseRangeHeader', () => {
6
+ const SIZE = 100;
7
+
8
+ it('no header → full body', () => {
9
+ expect(parseRangeHeader(undefined, SIZE)).toEqual({ kind: 'none' });
10
+ });
11
+
12
+ it('closed range', () => {
13
+ expect(parseRangeHeader('bytes=0-3', SIZE)).toEqual({ kind: 'range', range: { start: 0, end: 3 } });
14
+ });
15
+
16
+ it('open-ended range', () => {
17
+ expect(parseRangeHeader('bytes=95-', SIZE)).toEqual({ kind: 'range', range: { start: 95, end: 99 } });
18
+ });
19
+
20
+ it('suffix range (last N bytes)', () => {
21
+ expect(parseRangeHeader('bytes=-10', SIZE)).toEqual({ kind: 'range', range: { start: 90, end: 99 } });
22
+ });
23
+
24
+ it('end clamped to size', () => {
25
+ expect(parseRangeHeader('bytes=90-500', SIZE)).toEqual({ kind: 'range', range: { start: 90, end: 99 } });
26
+ });
27
+
28
+ it('start beyond size → 416', () => {
29
+ expect(parseRangeHeader('bytes=100-', SIZE)).toEqual({ kind: 'unsatisfiable' });
30
+ });
31
+
32
+ it('inverted range → 416', () => {
33
+ expect(parseRangeHeader('bytes=50-10', SIZE)).toEqual({ kind: 'unsatisfiable' });
34
+ });
35
+
36
+ it('multi-range → 416 (single range only, DoS guard)', () => {
37
+ expect(parseRangeHeader('bytes=0-1,5-9', SIZE)).toEqual({ kind: 'unsatisfiable' });
38
+ });
39
+
40
+ it('non-bytes unit ignored → full body', () => {
41
+ expect(parseRangeHeader('items=0-5', SIZE)).toEqual({ kind: 'none' });
42
+ });
43
+
44
+ it('empty spec → 416', () => {
45
+ expect(parseRangeHeader('bytes=-', SIZE)).toEqual({ kind: 'unsatisfiable' });
46
+ });
47
+
48
+ it('zero-size file with any range → 416', () => {
49
+ expect(parseRangeHeader('bytes=-5', 0)).toEqual({ kind: 'unsatisfiable' });
50
+ });
51
+ });
52
+
53
+ describe('contentDispositionFor (safe serving, brief §2.6)', () => {
54
+ it('active content downloads: html, svg, xml never render on the app origin', () => {
55
+ for (const type of [
56
+ 'text/html',
57
+ 'image/svg+xml',
58
+ 'application/xml',
59
+ 'text/xml',
60
+ 'application/xhtml+xml'
61
+ ]) {
62
+ expect(contentDispositionFor(type, 'f')).toMatch(/^attachment/);
63
+ }
64
+ });
65
+
66
+ it('passive media may render inline', () => {
67
+ for (const type of ['image/png', 'image/jpeg', 'video/mp4', 'application/pdf', 'text/plain']) {
68
+ expect(contentDispositionFor(type, 'f')).toMatch(/^inline/);
69
+ }
70
+ });
71
+
72
+ it('unknown types download', () => {
73
+ expect(contentDispositionFor('application/octet-stream', 'f')).toMatch(/^attachment/);
74
+ });
75
+
76
+ it('handles charset suffixes and sanitizes names', () => {
77
+ expect(contentDispositionFor('TEXT/HTML; charset=utf-8', 'a"b\\c.html')).toMatch(/^attachment/);
78
+ const value = contentDispositionFor('image/png', 'héllo.png');
79
+ expect(value).toContain('filename="h_llo.png"');
80
+ expect(value).toContain("filename*=UTF-8''h%C3%A9llo.png");
81
+ });
82
+ });
83
+
84
+ describe('blobKey', () => {
85
+ const WS = '123e4567-e89b-42d3-a456-426614174000';
86
+ const SHA = 'a'.repeat(64);
87
+
88
+ it('shards by the first two sha chars', () => {
89
+ expect(blobKey(WS, SHA)).toBe(`ws/${WS}/aa/${SHA}`);
90
+ });
91
+
92
+ it('rejects traversal-shaped inputs', () => {
93
+ expect(() => blobKey(WS, '../../../etc/passwd')).toThrow();
94
+ expect(() => blobKey('../..', SHA)).toThrow();
95
+ expect(() => blobKey(WS, SHA.toUpperCase())).toThrow();
96
+ });
97
+ });
@@ -0,0 +1,72 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { readdirSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { Hono } from 'hono';
5
+
6
+ /**
7
+ * PLT-39: Hono answers a HEAD request by running the GET handler. A separate
8
+ * `app.on('HEAD', …)` registration therefore NEVER fires — which made the
9
+ * `headOnly` flag on the file-content route dead code, so every HEAD read the
10
+ * blob and streamed the whole body.
11
+ *
12
+ * Two tests: one pins the framework behaviour (so a Hono bump that changes it
13
+ * fails here first), one greps the API surface so the dead idiom cannot come
14
+ * back on a future route.
15
+ */
16
+ describe('Hono HEAD handling (pinned)', () => {
17
+ it('runs the GET handler and never the HEAD registration', async () => {
18
+ const app = new Hono();
19
+ let getHits = 0;
20
+ let headHits = 0;
21
+ app.get('/x', (c) => {
22
+ getHits++;
23
+ return c.text('body');
24
+ });
25
+ app.on('HEAD', '/x', (c) => {
26
+ headHits++;
27
+ return c.body(null, 200);
28
+ });
29
+ const res = await app.request('/x', { method: 'HEAD' });
30
+ expect(res.status).toBe(200);
31
+ expect(getHits).toBe(1);
32
+ expect(headHits).toBe(0);
33
+ });
34
+
35
+ it('still reports HEAD as the method inside the GET handler', async () => {
36
+ const app = new Hono();
37
+ let seen = '';
38
+ app.get('/x', (c) => {
39
+ seen = c.req.method;
40
+ return c.text('body');
41
+ });
42
+ await app.request('/x', { method: 'HEAD' });
43
+ // This is the ONLY signal that survives the rewrite, so it is what the
44
+ // file-content route branches on.
45
+ expect(seen).toBe('HEAD');
46
+ });
47
+ });
48
+
49
+ describe('no dead HEAD registrations under /api/v1', () => {
50
+ it('has none', () => {
51
+ // The routers live in two places since the chassis extraction: the generic
52
+ // ones here, the tool's in the app that composes it. This audits the
53
+ // generic ones; the tool audits its own in its own unit suite
54
+ // (apps/server/test/unit/api-registrations.test.ts in the tool template).
55
+ const apiDirs = [join(import.meta.dirname, '../../src/api')];
56
+ const offenders: string[] = [];
57
+ const files = apiDirs.flatMap((dir) => readdirSync(dir).map((file) => join(dir, file)));
58
+ for (const file of files) {
59
+ if (!file.endsWith('.ts')) continue;
60
+ const source = readFileSync(file, 'utf8');
61
+ const code = source
62
+ .split('\n')
63
+ .filter((line) => {
64
+ const t = line.trim();
65
+ return !t.startsWith('//') && !t.startsWith('*') && !t.startsWith('/*');
66
+ })
67
+ .join('\n');
68
+ if (/\.on\(\s*(\[[^\]]*)?['"]HEAD['"]/.test(code)) offenders.push(file);
69
+ }
70
+ expect(offenders).toEqual([]);
71
+ });
72
+ });
@@ -0,0 +1,181 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { classifyHubOrgCreateAnswer, HubUserClient } from '@antasphere/chassis-server/identity';
3
+ import type { GrantAccess, HubGrantService } from '@antasphere/chassis-server/identity';
4
+ import type { Logger } from '@antasphere/chassis-server/logger';
5
+
6
+ /**
7
+ * The ONE hub call behind POST /workspaces on cloud (PRDCT-2443):
8
+ * `HubUserClient.createOrg` and its pure answer classifier. The grant is a
9
+ * stub — what matters here is WHICH token is presented, how often, and that
10
+ * an ambiguous answer is never retried (a blind second POST could create a
11
+ * second organization).
12
+ */
13
+ const ORG_ID = '7d0c9a52-3f5e-4a4b-9a57-0e6c1c2f9b11';
14
+ const silent = { info() {}, warn() {}, error() {}, debug() {} } as unknown as Logger;
15
+
16
+ function stubGrant(over: Partial<Record<'accessToken' | 'refresh', GrantAccess>> = {}) {
17
+ const grant = {
18
+ accessToken: vi.fn(
19
+ async (): Promise<GrantAccess> => over.accessToken ?? { kind: 'ok', accessToken: 'at-1' }
20
+ ),
21
+ refresh: vi.fn(async (): Promise<GrantAccess> => over.refresh ?? { kind: 'ok', accessToken: 'at-2' }),
22
+ invalidateAccess: vi.fn(),
23
+ prime: vi.fn()
24
+ };
25
+ return grant;
26
+ }
27
+
28
+ function client(grant: ReturnType<typeof stubGrant>, fetchImpl: typeof fetch) {
29
+ return new HubUserClient({
30
+ grant: grant as unknown as HubGrantService,
31
+ issuerUrl: 'https://hub.test/',
32
+ logger: silent,
33
+ timeoutMs: 1_000,
34
+ fetchImpl
35
+ });
36
+ }
37
+
38
+ const answer = (status: number, body: unknown) =>
39
+ new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });
40
+
41
+ describe('classifyHubOrgCreateAnswer', () => {
42
+ it('201 with a well-formed org is created; the role is validated, never invented', () => {
43
+ expect(
44
+ classifyHubOrgCreateAnswer(201, { org: { id: ORG_ID, name: 'Acme', role: 'owner', personal: false } })
45
+ ).toEqual({ kind: 'created', org: { id: ORG_ID, name: 'Acme', role: 'owner' } });
46
+ expect(classifyHubOrgCreateAnswer(201, { org: { id: ORG_ID, name: ' ', role: 'emperor' } })).toEqual({
47
+ kind: 'created',
48
+ org: { id: ORG_ID, name: null, role: null }
49
+ });
50
+ });
51
+
52
+ it('a 2xx without a valid org id is inconclusive — never a guessed id', () => {
53
+ expect(classifyHubOrgCreateAnswer(201, { org: { id: 'not-a-uuid' } })).toEqual({ kind: 'inconclusive' });
54
+ expect(classifyHubOrgCreateAnswer(201, {})).toEqual({ kind: 'inconclusive' });
55
+ expect(classifyHubOrgCreateAnswer(201, null)).toEqual({ kind: 'inconclusive' });
56
+ });
57
+
58
+ it('the hub cap, in either spelling and either status, is limit_reached', () => {
59
+ for (const status of [403, 409]) {
60
+ for (const code of ['org_limit_reached', 'org_cap_reached']) {
61
+ expect(classifyHubOrgCreateAnswer(status, { error: { code } })).toEqual({ kind: 'limit_reached' });
62
+ }
63
+ }
64
+ });
65
+
66
+ it('403 insufficient_scope — exactly that — is reauth_required; no other 403 is', () => {
67
+ expect(classifyHubOrgCreateAnswer(403, { error: { code: 'insufficient_scope' } })).toEqual({
68
+ kind: 'reauth_required'
69
+ });
70
+ // The same code on another status, and the hub's OTHER 403s, are not a
71
+ // reason to send someone through a sign-in that would change nothing.
72
+ expect(classifyHubOrgCreateAnswer(401, { error: { code: 'insufficient_scope' } }).kind).toBe('refused');
73
+ expect(classifyHubOrgCreateAnswer(403, { error: { code: 'forbidden' } }).kind).toBe('refused');
74
+ expect(classifyHubOrgCreateAnswer(403, { error: { code: 'endpoint_not_allowed' } }).kind).toBe('refused');
75
+ expect(classifyHubOrgCreateAnswer(403, null).kind).toBe('refused');
76
+ });
77
+
78
+ it('400/422 is invalid; any other 4xx is a definitive refusal; 5xx is inconclusive', () => {
79
+ expect(classifyHubOrgCreateAnswer(400, { error: { code: 'validation_error' } })).toEqual({
80
+ kind: 'invalid'
81
+ });
82
+ expect(classifyHubOrgCreateAnswer(422, null)).toEqual({ kind: 'invalid' });
83
+ expect(classifyHubOrgCreateAnswer(403, { error: { code: 'forbidden' } })).toEqual({
84
+ kind: 'refused',
85
+ status: 403,
86
+ code: 'forbidden'
87
+ });
88
+ expect(classifyHubOrgCreateAnswer(409, { error: { code: 'other' } })).toEqual({
89
+ kind: 'refused',
90
+ status: 409,
91
+ code: 'other'
92
+ });
93
+ expect(classifyHubOrgCreateAnswer(500, null)).toEqual({ kind: 'inconclusive' });
94
+ expect(classifyHubOrgCreateAnswer(502, 'gateway')).toEqual({ kind: 'inconclusive' });
95
+ });
96
+ });
97
+
98
+ describe('HubUserClient.createOrg', () => {
99
+ it('POSTs { name } to <hub>/api/v1/orgs with the USER grant as the bearer — and nothing naming a user', async () => {
100
+ const grant = stubGrant();
101
+ const fetchImpl = vi.fn(async () => answer(201, { org: { id: ORG_ID, name: 'Acme', role: 'owner' } }));
102
+ const result = await client(grant, fetchImpl as unknown as typeof fetch).createOrg('user-1', 'Acme');
103
+ expect(result).toEqual({ kind: 'created', org: { id: ORG_ID, name: 'Acme', role: 'owner' } });
104
+ expect(grant.accessToken).toHaveBeenCalledWith('user-1');
105
+ expect(fetchImpl).toHaveBeenCalledTimes(1);
106
+ const [url, init] = fetchImpl.mock.calls[0] as unknown as [string, RequestInit];
107
+ expect(url).toBe('https://hub.test/api/v1/orgs');
108
+ expect(init.method).toBe('POST');
109
+ expect((init.headers as Record<string, string>).authorization).toBe('Bearer at-1');
110
+ // The body is the name and ONLY the name: no target-user parameter exists.
111
+ expect(JSON.parse(init.body as string)).toEqual({ name: 'Acme' });
112
+ expect(init.signal).toBeInstanceOf(AbortSignal);
113
+ });
114
+
115
+ it('passes the grant verdicts through without calling the hub', async () => {
116
+ for (const kind of ['no_link', 'grant_dead', 'inconclusive'] as const) {
117
+ const fetchImpl = vi.fn();
118
+ const result = await client(
119
+ stubGrant({ accessToken: { kind } }),
120
+ fetchImpl as unknown as typeof fetch
121
+ ).createOrg('user-1', 'Acme');
122
+ expect(result).toEqual({ kind });
123
+ expect(fetchImpl).not.toHaveBeenCalled();
124
+ }
125
+ });
126
+
127
+ it('a 401 earns ONE forced refresh and ONE retry with the fresh token', async () => {
128
+ const grant = stubGrant();
129
+ const fetchImpl = vi
130
+ .fn()
131
+ .mockResolvedValueOnce(answer(401, { error: { code: 'invalid_token' } }))
132
+ .mockResolvedValueOnce(answer(201, { org: { id: ORG_ID, name: 'Acme', role: 'owner' } }));
133
+ const result = await client(grant, fetchImpl as unknown as typeof fetch).createOrg('user-1', 'Acme');
134
+ expect(result.kind).toBe('created');
135
+ expect(grant.invalidateAccess).toHaveBeenCalledWith('user-1');
136
+ expect(grant.refresh).toHaveBeenCalledTimes(1);
137
+ expect(fetchImpl).toHaveBeenCalledTimes(2);
138
+ const retryInit = fetchImpl.mock.calls[1]?.[1] as RequestInit;
139
+ expect((retryInit.headers as Record<string, string>).authorization).toBe('Bearer at-2');
140
+ });
141
+
142
+ it('a dead grant discovered by that refresh is grant_dead; a second 401 is a refusal, not a loop', async () => {
143
+ const dead = stubGrant({ refresh: { kind: 'grant_dead' } });
144
+ const once = vi.fn(async () => answer(401, {}));
145
+ expect(await client(dead, once as unknown as typeof fetch).createOrg('u', 'A')).toEqual({
146
+ kind: 'grant_dead'
147
+ });
148
+ expect(once).toHaveBeenCalledTimes(1);
149
+
150
+ const twice = vi.fn(async () => answer(401, {}));
151
+ expect(await client(stubGrant(), twice as unknown as typeof fetch).createOrg('u', 'A')).toEqual({
152
+ kind: 'refused',
153
+ status: 401,
154
+ code: null
155
+ });
156
+ expect(twice).toHaveBeenCalledTimes(2);
157
+ });
158
+
159
+ it('NEVER retries a 403, a 5xx, or a network failure — the hub may have committed', async () => {
160
+ const cases: Array<[() => Promise<Response>, string]> = [
161
+ [async () => answer(403, { error: { code: 'org_limit_reached' } }), 'limit_reached'],
162
+ [async () => answer(403, { error: { code: 'forbidden' } }), 'refused'],
163
+ [async () => answer(403, { error: { code: 'insufficient_scope' } }), 'reauth_required'],
164
+ [async () => answer(500, { error: { code: 'internal' } }), 'inconclusive'],
165
+ [
166
+ async () => {
167
+ throw new TypeError('fetch failed');
168
+ },
169
+ 'inconclusive'
170
+ ]
171
+ ];
172
+ for (const [impl, kind] of cases) {
173
+ const grant = stubGrant();
174
+ const fetchImpl = vi.fn(impl);
175
+ const result = await client(grant, fetchImpl as unknown as typeof fetch).createOrg('u', 'A');
176
+ expect(result.kind).toBe(kind);
177
+ expect(fetchImpl).toHaveBeenCalledTimes(1);
178
+ expect(grant.refresh).not.toHaveBeenCalled();
179
+ }
180
+ });
181
+ });
@@ -0,0 +1,93 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
2
+ import { HubJwtVerifier } from '@antasphere/chassis-server/identity';
3
+ import { FakeHub } from '@antasphere/chassis-server/testing';
4
+
5
+ /**
6
+ * HubJwtVerifier — the cloud edition's trust anchor for hub-minted tokens
7
+ * (internal/federation.md). Mirrors oauth-jwt.ts's discipline: hard issuer +
8
+ * audience pinning, RS256 allowlist, one forced key refresh on an unknown
9
+ * signature. Every rejection here is a fail-closed SSO login.
10
+ */
11
+
12
+ const RESOURCE = 'http://localhost:3000/mcp';
13
+
14
+ const USER = {
15
+ sub: 'hub-user-1',
16
+ email: 'user@hub.test',
17
+ workspaceId: '5f0c1f9a-1111-4222-8333-444455556666',
18
+ role: 'member'
19
+ };
20
+
21
+ let hub: FakeHub;
22
+ let verifier: HubJwtVerifier;
23
+
24
+ beforeAll(async () => {
25
+ hub = await FakeHub.start({ clientId: 'tool-things-cloud' });
26
+ verifier = new HubJwtVerifier(hub.issuer);
27
+ });
28
+
29
+ afterAll(async () => {
30
+ await hub.stop();
31
+ });
32
+
33
+ describe('HubJwtVerifier', () => {
34
+ it('accepts a hub-minted token and yields the org claims', async () => {
35
+ const token = await hub.signAccessToken(USER, RESOURCE);
36
+ const payload = await verifier.verify(token, RESOURCE);
37
+ expect(payload.sub).toBe(USER.sub);
38
+ expect(payload.workspace_id).toBe(USER.workspaceId);
39
+ expect(payload.role).toBe('member');
40
+ expect(payload.workspace_name).toBe('Fake Org');
41
+ });
42
+
43
+ it('accepts the array-shaped aud (resource + hub userinfo) by matching our element', async () => {
44
+ const token = await hub.signAccessToken(USER, RESOURCE);
45
+ // The fake mints aud = [resource, <issuer>/…/userinfo], like the real hub
46
+ // when openid is scoped — pinning OUR resource must still verify.
47
+ await expect(verifier.verify(token, RESOURCE)).resolves.toBeTruthy();
48
+ });
49
+
50
+ it('rejects a token minted for another resource (aud pinning)', async () => {
51
+ const token = await hub.signAccessToken(
52
+ { ...USER, overrides: { accessAud: ['http://other-tool.test/mcp'] } },
53
+ RESOURCE
54
+ );
55
+ await expect(verifier.verify(token, RESOURCE)).rejects.toThrow();
56
+ });
57
+
58
+ it('rejects a token from another issuer (iss pinning)', async () => {
59
+ const token = await hub.signAccessToken(
60
+ { ...USER, overrides: { iss: 'http://evil-hub.test' } },
61
+ RESOURCE
62
+ );
63
+ await expect(verifier.verify(token, RESOURCE)).rejects.toThrow();
64
+ });
65
+
66
+ it('rejects an expired token', async () => {
67
+ const token = await hub.signAccessToken({ ...USER, overrides: { expiresInSeconds: -60 } }, RESOURCE);
68
+ await expect(verifier.verify(token, RESOURCE)).rejects.toThrow();
69
+ });
70
+
71
+ it('rejects a non-JWT (opaque) token', async () => {
72
+ await expect(verifier.verify('opaque_not_a_jwt', RESOURCE)).rejects.toThrow();
73
+ });
74
+
75
+ it('survives a hub signing-key rotation (fresh JWKS on unknown kid)', async () => {
76
+ // Prime the key cache with the current key…
77
+ await verifier.verify(await hub.signAccessToken(USER, RESOURCE), RESOURCE);
78
+ // …rotate the hub key, then verify a token signed with the NEW key: the
79
+ // cached set does not know the kid, jose refetches, verification passes.
80
+ await hub.rotateKey();
81
+ const fresh = await hub.signAccessToken(USER, RESOURCE);
82
+ const payload = await verifier.verify(fresh, RESOURCE);
83
+ expect(payload.sub).toBe(USER.sub);
84
+ });
85
+
86
+ it('refuses a discovery document claiming a different issuer', async () => {
87
+ // A verifier configured to trust a DIFFERENT issuer than the one the
88
+ // discovery document self-reports must fail closed before trusting keys.
89
+ const misconfigured = new HubJwtVerifier(hub.issuer.replace('127.0.0.1', 'localhost'));
90
+ const token = await hub.signAccessToken(USER, RESOURCE);
91
+ await expect(misconfigured.verify(token, RESOURCE)).rejects.toThrow(/issuer mismatch|fetch failed/);
92
+ });
93
+ });
@@ -0,0 +1,39 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { ssoAuthorizationUrlParams } from '@antasphere/chassis-server/identity';
3
+
4
+ /**
5
+ * The SL-1 authorize-params whitelist: the ONLY thing the caller-controlled
6
+ * `additionalData` blob may ever contribute to the hub authorize URL is the
7
+ * literal `prompt=none` pair (the dashboard's silent auto-connect). Every
8
+ * other value, key, and shape is dropped — an attacker-shaped sign-in body
9
+ * can never smuggle params (prompt=consent re-prompts, login_hint,
10
+ * request_uri, …) into the hub redirect.
11
+ */
12
+ describe('ssoAuthorizationUrlParams (strict whitelist)', () => {
13
+ it("passes exactly { prompt: 'none' } for additionalData.prompt === 'none'", () => {
14
+ expect(ssoAuthorizationUrlParams({ additionalData: { prompt: 'none' } })).toEqual({ prompt: 'none' });
15
+ });
16
+
17
+ it('drops every other prompt value', () => {
18
+ for (const prompt of ['consent', 'login', 'select_account', 'NONE', 'none ', '', 0, true, null]) {
19
+ expect(ssoAuthorizationUrlParams({ additionalData: { prompt } }), String(prompt)).toEqual({});
20
+ }
21
+ });
22
+
23
+ it('never forwards extra additionalData keys — even next to a valid prompt', () => {
24
+ expect(
25
+ ssoAuthorizationUrlParams({
26
+ additionalData: { prompt: 'none', login_hint: 'a@b.c', request_uri: 'https://evil.example' }
27
+ })
28
+ ).toEqual({ prompt: 'none' });
29
+ });
30
+
31
+ it('answers {} for absent/malformed bodies (plain sign-ins stay param-free)', () => {
32
+ expect(ssoAuthorizationUrlParams(undefined)).toEqual({});
33
+ expect(ssoAuthorizationUrlParams(null)).toEqual({});
34
+ expect(ssoAuthorizationUrlParams({})).toEqual({});
35
+ expect(ssoAuthorizationUrlParams({ additionalData: null })).toEqual({});
36
+ expect(ssoAuthorizationUrlParams({ additionalData: 'none' })).toEqual({});
37
+ expect(ssoAuthorizationUrlParams({ additionalData: ['none'] })).toEqual({});
38
+ });
39
+ });