@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,83 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { hubSubjectCache } from '../../src/entitlements/hub-subject.js';
3
+
4
+ /**
5
+ * The hub-subject cache (PRDCT-2654): a per-user TTL cache over the SSO link
6
+ * lookup, a miss cached too, and bounded on a long-lived replica by a sweep
7
+ * of the expired entries once it holds more than `sweepAbove` users.
8
+ */
9
+
10
+ function setup(opts: { ttlMs?: number; sweepAbove?: number } = {}) {
11
+ let clock = 1_000_000;
12
+ const subs = new Map<string, string>([
13
+ ['u1', 'sub-1'],
14
+ ['u2', 'sub-2']
15
+ ]);
16
+ const lookup = vi.fn(async (userId: string) => subs.get(userId) ?? null);
17
+ const cache = hubSubjectCache(lookup, { ttlMs: 1_000, ...opts, now: () => clock });
18
+ return {
19
+ cache,
20
+ lookup,
21
+ subs,
22
+ advance: (ms: number) => {
23
+ clock += ms;
24
+ }
25
+ };
26
+ }
27
+
28
+ describe('hubSubjectCache', () => {
29
+ it('serves a hit within the TTL without calling the lookup', async () => {
30
+ const { cache, lookup, advance } = setup();
31
+ expect(await cache.resolve('u1')).toBe('sub-1');
32
+ advance(999);
33
+ expect(await cache.resolve('u1')).toBe('sub-1');
34
+ expect(lookup).toHaveBeenCalledTimes(1);
35
+ });
36
+
37
+ it('caches a miss too', async () => {
38
+ const { cache, lookup } = setup();
39
+ expect(await cache.resolve('local-operator')).toBeNull();
40
+ expect(await cache.resolve('local-operator')).toBeNull();
41
+ expect(lookup).toHaveBeenCalledTimes(1);
42
+ expect(cache.size).toBe(1);
43
+ });
44
+
45
+ it('overwrites an expired entry on the next read', async () => {
46
+ const { cache, lookup, subs, advance } = setup();
47
+ expect(await cache.resolve('u1')).toBe('sub-1');
48
+ subs.set('u1', 'sub-1-relinked');
49
+ advance(1_000);
50
+ expect(await cache.resolve('u1')).toBe('sub-1-relinked');
51
+ expect(lookup).toHaveBeenCalledTimes(2);
52
+ expect(cache.size).toBe(1);
53
+ });
54
+
55
+ it('sweeps the expired entries once it holds more than sweepAbove users, keeping the live ones', async () => {
56
+ const { cache, advance } = setup({ sweepAbove: 3 });
57
+ for (const u of ['a', 'b', 'c']) await cache.resolve(u);
58
+ advance(600);
59
+ await cache.resolve('live'); // written at +600, lives until +1600
60
+ expect(cache.size).toBe(4);
61
+ advance(500); // a, b, c expired (until +1000); 'live' has 500 ms left
62
+ await cache.resolve('d');
63
+ expect(cache.size).toBe(2);
64
+ advance(1);
65
+ const before = cache.size;
66
+ await cache.resolve('live');
67
+ expect(cache.size).toBe(before);
68
+ });
69
+
70
+ it('does not sweep at or below the threshold, even with expired entries to drop; one entry above, it does', async () => {
71
+ const { cache, advance } = setup({ sweepAbove: 3, ttlMs: 1_000 });
72
+ for (const u of ['a', 'b']) await cache.resolve(u);
73
+ advance(2_000); // a and b are expired now
74
+ await cache.resolve('c'); // size 2 → not above 3: no sweep, the expired stay
75
+ expect(cache.size).toBe(3);
76
+ await cache.resolve('d'); // size 3 → still not above 3: no sweep (the boundary, verifier round 1)
77
+ expect(cache.size).toBe(4);
78
+ await cache.resolve('e'); // size 4 → above 3: a and b swept, c, d live
79
+ expect(cache.size).toBe(3);
80
+ cache.clear();
81
+ expect(cache.size).toBe(0);
82
+ });
83
+ });
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { open, seal } from '@antasphere/chassis-server/middleware';
3
+
4
+ /**
5
+ * The replay cache stores response bodies AES-256-GCM encrypted because they
6
+ * carry one-shot secrets. seal/open must round-trip, and open must fail
7
+ * CLOSED (null, never throw or return garbage) on tamper or a wrong key —
8
+ * that null is what turns an AUTH_SECRET rotation into a clean re-execute.
9
+ */
10
+
11
+ const SECRET = 'unit-test-secret-0123456789abcdef0123456789abcdef';
12
+ const BODY = JSON.stringify({ apiKey: { id: 'x' }, key: 'key_abcd1234_supersecret' });
13
+
14
+ describe('idempotency seal/open (AES-256-GCM)', () => {
15
+ it('round-trips a response body', () => {
16
+ const sealed = seal(BODY, SECRET);
17
+ expect(open(sealed, SECRET)).toBe(BODY);
18
+ });
19
+
20
+ it('produces the iv.ciphertext.tag shape without leaking the plaintext', () => {
21
+ const sealed = seal(BODY, SECRET);
22
+ expect(sealed.split('.')).toHaveLength(3);
23
+ expect(sealed).not.toContain('supersecret');
24
+ // Fresh iv per seal: the same plaintext never encrypts to the same blob.
25
+ expect(seal(BODY, SECRET)).not.toBe(sealed);
26
+ });
27
+
28
+ it('returns null on tampered ciphertext', () => {
29
+ const sealed = seal(BODY, SECRET);
30
+ const [iv, ciphertext, tag] = sealed.split('.') as [string, string, string];
31
+ const flipped = ciphertext.startsWith('A') ? `B${ciphertext.slice(1)}` : `A${ciphertext.slice(1)}`;
32
+ expect(open(`${iv}.${flipped}.${tag}`, SECRET)).toBeNull();
33
+ });
34
+
35
+ it('returns null under a different key (rotated AUTH_SECRET)', () => {
36
+ const sealed = seal(BODY, SECRET);
37
+ expect(open(sealed, 'another-secret-entirely-9876543210')).toBeNull();
38
+ });
39
+
40
+ it('returns null on malformed input', () => {
41
+ expect(open('not-a-sealed-payload', SECRET)).toBeNull();
42
+ expect(open('a.b', SECRET)).toBeNull();
43
+ expect(open('', SECRET)).toBeNull();
44
+ });
45
+ });
@@ -0,0 +1,88 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Hono } from 'hono';
3
+ import { exceedsJsonDepth, jsonDepthLimit, MAX_JSON_DEPTH } from '@antasphere/chassis-server/middleware';
4
+
5
+ const nest = (depth: number) => '['.repeat(depth) + '1' + ']'.repeat(depth);
6
+
7
+ describe('exceedsJsonDepth', () => {
8
+ it('measures nesting', () => {
9
+ expect(exceedsJsonDepth(nest(10), 100)).toBe(false);
10
+ expect(exceedsJsonDepth(nest(101), 100)).toBe(true);
11
+ expect(exceedsJsonDepth(nest(100), 100)).toBe(false);
12
+ });
13
+
14
+ it('counts objects and arrays together', () => {
15
+ expect(exceedsJsonDepth('{"a":{"b":{"c":[1]}}}', 3)).toBe(true);
16
+ expect(exceedsJsonDepth('{"a":{"b":{"c":[1]}}}', 4)).toBe(false);
17
+ });
18
+
19
+ it('ignores brackets inside string literals, escapes included', () => {
20
+ const payload = `{"a":"${'['.repeat(500)}"}`;
21
+ expect(exceedsJsonDepth(payload, 5)).toBe(false);
22
+ expect(exceedsJsonDepth('{"a":"\\"[[[[[[[[[[","b":1}', 5)).toBe(false);
23
+ });
24
+
25
+ it('does not fire on flat, wide payloads', () => {
26
+ expect(exceedsJsonDepth(JSON.stringify(Array.from({ length: 5000 }, (_, i) => i)), 100)).toBe(false);
27
+ });
28
+ });
29
+
30
+ describe('jsonDepthLimit middleware', () => {
31
+ function app() {
32
+ const a = new Hono();
33
+ a.use('*', jsonDepthLimit());
34
+ a.post('/echo', async (c) => c.json({ ok: true, body: await c.req.json() }));
35
+ a.post('/raw', async (c) => c.text(await c.req.text()));
36
+ return a;
37
+ }
38
+
39
+ it('rejects an over-deep JSON body with a 400 before the handler', async () => {
40
+ const res = await app().request('/echo', {
41
+ method: 'POST',
42
+ headers: { 'content-type': 'application/json' },
43
+ body: nest(MAX_JSON_DEPTH + 1)
44
+ });
45
+ expect(res.status).toBe(400);
46
+ expect(await res.json()).toMatchObject({ error: { code: 'payload_too_deep' } });
47
+ });
48
+
49
+ it('rejects the depth that breaks JSON.stringify inside the shipped image', async () => {
50
+ // node:22-alpine throws RangeError from JSON.stringify past ~5000 levels;
51
+ // a newer host Node does not, which is exactly why the guard is at the
52
+ // edge and not a try/catch around some stringify call.
53
+ const res = await app().request('/echo', {
54
+ method: 'POST',
55
+ headers: { 'content-type': 'application/json' },
56
+ body: nest(6000)
57
+ });
58
+ expect(res.status).toBe(400);
59
+ });
60
+
61
+ it('passes ordinary bodies through untouched', async () => {
62
+ const res = await app().request('/echo', {
63
+ method: 'POST',
64
+ headers: { 'content-type': 'application/json' },
65
+ body: JSON.stringify({ name: 'acme', nested: { a: { b: [1, 2, 3] } } })
66
+ });
67
+ expect(res.status).toBe(200);
68
+ expect(await res.json()).toMatchObject({ ok: true, body: { name: 'acme' } });
69
+ });
70
+
71
+ it('ignores non-JSON bodies (streamed uploads, form-encoded token calls)', async () => {
72
+ const res = await app().request('/raw', {
73
+ method: 'POST',
74
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
75
+ body: nest(6000)
76
+ });
77
+ expect(res.status).toBe(200);
78
+ });
79
+
80
+ it('ignores bodyless requests', async () => {
81
+ const res = await app().request('/echo', {
82
+ method: 'POST',
83
+ headers: { 'content-type': 'application/json' }
84
+ });
85
+ // No body: the handler's own parse decides, the guard does not interfere.
86
+ expect(res.status).not.toBe(400);
87
+ });
88
+ });
@@ -0,0 +1,66 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { pino } from 'pino';
3
+ import { REDACT_PATHS } from '@antasphere/chassis-server/logger';
4
+
5
+ /**
6
+ * The redaction list is the last line between a config payload and the log
7
+ * stream. Two gaps this pins shut:
8
+ * - `*.secret` is an EXACT key match, so `clientSecret` sailed straight
9
+ * through it (PLT-33);
10
+ * - GOOGLE_CLIENT_SECRET and, on this repo, HUB_CLIENT_SECRET (this tool's
11
+ * own identity at the Antasphere hub) are read in env.ts but were absent
12
+ * from the list, so any "parsed env" line carried them.
13
+ */
14
+ type Log = ReturnType<typeof pino>;
15
+
16
+ function capture(fn: (log: Log) => void): string {
17
+ const chunks: string[] = [];
18
+ const log: Log = pino(
19
+ { redact: { paths: REDACT_PATHS, censor: '[redacted]' } },
20
+ { write: (chunk: string) => chunks.push(chunk) }
21
+ ) as Log;
22
+ fn(log);
23
+ return chunks.join('');
24
+ }
25
+
26
+ describe('log redaction', () => {
27
+ it('redacts the env-level credentials', () => {
28
+ const out = capture((log) =>
29
+ log.info({
30
+ DATABASE_URL: 'postgres://u:p@h/db',
31
+ AUTH_SECRET: 'auth-secret-value',
32
+ GOOGLE_CLIENT_SECRET: 'google-secret-value',
33
+ HUB_CLIENT_SECRET: 'hub-client-secret-value'
34
+ })
35
+ );
36
+ expect(out).not.toContain('google-secret-value');
37
+ expect(out).not.toContain('hub-client-secret-value');
38
+ expect(out).not.toContain('auth-secret-value');
39
+ expect(out).not.toContain('postgres://u:p@h/db');
40
+ });
41
+
42
+ it('redacts camelCase and snake_case secret keys the `*.secret` glob misses', () => {
43
+ const out = capture((log) =>
44
+ log.info({
45
+ client: {
46
+ clientId: 'visible-id',
47
+ clientSecret: 'camel-secret-value',
48
+ client_secret: 'snake-secret-value',
49
+ accessToken: 'access-token-value',
50
+ refresh_token: 'refresh-token-value',
51
+ apiKey: 'api-key-value'
52
+ }
53
+ })
54
+ );
55
+ expect(out).toContain('visible-id');
56
+ for (const leaked of [
57
+ 'camel-secret-value',
58
+ 'snake-secret-value',
59
+ 'access-token-value',
60
+ 'refresh-token-value',
61
+ 'api-key-value'
62
+ ]) {
63
+ expect(out).not.toContain(leaked);
64
+ }
65
+ });
66
+ });
@@ -0,0 +1,110 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { ApiToolError, jsonText, wrapToolErrors } from '@antasphere/chassis-server/mcp';
3
+ import { isPublicOauthPath } from '@antasphere/chassis-server/middleware';
4
+
5
+ describe('mcp error → hint mapping', () => {
6
+ it('attaches an actionable hint for known API codes', () => {
7
+ const err = new ApiToolError(403, 'insufficient_scope', 'This credential was not granted "things:write"');
8
+ const text = err.toUserFacingText();
9
+ expect(text).toContain('HTTP 403');
10
+ expect(text).toContain('insufficient_scope');
11
+ expect(text).toContain('consent screen');
12
+ });
13
+
14
+ it('a 402 entitlement_denied carries the top-up hint, the price, the balance and the link, as text', () => {
15
+ const err = new ApiToolError(402, 'entitlement_denied', 'Not enough credits', {
16
+ credits: 1500,
17
+ balance: 20,
18
+ topUpUrl: 'https://account.antasphere.com/orgs/acme/billing'
19
+ });
20
+ const text = err.toUserFacingText();
21
+ expect(text).toContain('HTTP 402');
22
+ expect(text).toContain('lacks the credits');
23
+ expect(text).toContain(
24
+ 'Top up: https://account.antasphere.com/orgs/acme/billing (this needs 1500 credits, the organization holds 20).'
25
+ );
26
+ });
27
+
28
+ it('the gate’s own 402 message already carries the link: no second top-up sentence (verifier round 1)', () => {
29
+ const url = 'https://account.antasphere.com/orgs/acme/billing';
30
+ const err = new ApiToolError(
31
+ 402,
32
+ 'entitlement_denied',
33
+ `This needs 1500 credits and the organization holds 20; top up at ${url}`,
34
+ { credits: 1500, balance: 20, topUpUrl: url }
35
+ );
36
+ const text = err.toUserFacingText();
37
+ expect(text).not.toContain('Top up:');
38
+ expect(text.split(url).length - 1).toBe(1);
39
+ });
40
+
41
+ it('a 413 entitlement_denied without details (the self-hosted cap) has no top-up sentence', () => {
42
+ const err = new ApiToolError(413, 'entitlement_denied', 'File exceeds the 100 MB limit');
43
+ const text = err.toUserFacingText();
44
+ expect(text).toContain('HTTP 413');
45
+ expect(text).toContain('File exceeds the 100 MB limit');
46
+ expect(text).not.toContain('Top up:');
47
+ expect(text).not.toContain('undefined');
48
+ });
49
+
50
+ it('a 413 entitlement_denied with the price and the balance but no link (an unpriceable quantity, PRDCT-2677) has no top-up sentence and its hint says so', () => {
51
+ const err = new ApiToolError(
52
+ 413,
53
+ 'entitlement_denied',
54
+ 'This quantity cannot be priced (9007199254740991 credits or more, the organization holds 3); nothing was charged',
55
+ { credits: 9007199254740991, balance: 3 }
56
+ );
57
+ const text = err.toUserFacingText();
58
+ expect(text).toContain('HTTP 413');
59
+ expect(text).not.toContain('Top up:');
60
+ expect(text).toContain('beyond what can be priced');
61
+ expect(text).not.toContain('undefined');
62
+ });
63
+
64
+ it('degrades gracefully for unknown codes', () => {
65
+ const err = new ApiToolError(422, 'weird_domain_thing', 'nope');
66
+ const text = err.toUserFacingText();
67
+ expect(text).toContain('weird_domain_thing');
68
+ expect(text).not.toContain('undefined');
69
+ });
70
+
71
+ it('wrapToolErrors turns ApiToolError into an isError tool result', async () => {
72
+ const result = await wrapToolErrors(async () => {
73
+ throw new ApiToolError(401, 'invalid_token', 'expired');
74
+ });
75
+ expect(result.isError).toBe(true);
76
+ expect(result.content[0]?.text).toContain('invalid_token');
77
+ });
78
+
79
+ it('wrapToolErrors passes successes through untouched', async () => {
80
+ const result = await wrapToolErrors(async () => jsonText({ ok: true }));
81
+ expect(result.isError).toBeUndefined();
82
+ expect(JSON.parse(result.content[0]?.text ?? '')).toEqual({ ok: true });
83
+ });
84
+
85
+ it('wrapToolErrors lets non-API errors propagate to the SDK', async () => {
86
+ await expect(
87
+ wrapToolErrors(async () => {
88
+ throw new Error('bug');
89
+ })
90
+ ).rejects.toThrow('bug');
91
+ });
92
+ });
93
+
94
+ describe('public OAuth path matcher', () => {
95
+ it('covers exactly the cookie-less endpoints', () => {
96
+ expect(isPublicOauthPath('/api/v1/auth/oauth2/token')).toBe(true);
97
+ expect(isPublicOauthPath('/api/v1/auth/oauth2/register')).toBe(true);
98
+ expect(isPublicOauthPath('/api/v1/auth/oauth2/introspect')).toBe(true);
99
+ expect(isPublicOauthPath('/api/v1/auth/oauth2/revoke')).toBe(true);
100
+ expect(isPublicOauthPath('/api/v1/auth/jwks')).toBe(true);
101
+ expect(isPublicOauthPath('/api/v1/auth/.well-known/openid-configuration')).toBe(true);
102
+ });
103
+
104
+ it('never exempts cookie-bearing endpoints', () => {
105
+ expect(isPublicOauthPath('/api/v1/auth/oauth2/authorize')).toBe(false);
106
+ expect(isPublicOauthPath('/api/v1/auth/oauth2/consent')).toBe(false);
107
+ expect(isPublicOauthPath('/api/v1/auth/sign-in/email')).toBe(false);
108
+ expect(isPublicOauthPath('/api/v1/auth/get-session')).toBe(false);
109
+ });
110
+ });
@@ -0,0 +1,106 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
3
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
4
+ import { z } from 'zod';
5
+ import { buildMcpServer, mcpInputs, type McpToolContext } from '@antasphere/chassis-server/mcp';
6
+
7
+ /**
8
+ * The shared MCP inputs (`mcpInputs`) are the ones the chassis's own three
9
+ * tools are declared with: a tool that builds its set from the same factory
10
+ * shows the same `workspace`, `cursor` and `limit` in `tools/list`.
11
+ */
12
+
13
+ const identity = { serverName: 'things', toolPrefix: 'things_' };
14
+
15
+ async function listedTools() {
16
+ const server = buildMcpServer(
17
+ {} as McpToolContext, // read at call time only; nothing is called here
18
+ { version: '0.0.0-test', instanceName: 'Inputs' },
19
+ {
20
+ registerTools: () => {},
21
+ instructions: () => 'test',
22
+ errorHints: {},
23
+ scopes: { read: 'things:read', write: 'things:write' }
24
+ },
25
+ identity
26
+ );
27
+ const [clientSide, serverSide] = InMemoryTransport.createLinkedPair();
28
+ const client = new Client({ name: 'inputs-test', version: '0.0.0' });
29
+ await Promise.all([server.connect(serverSide), client.connect(clientSide)]);
30
+ const { tools } = await client.listTools();
31
+ await client.close();
32
+ return tools;
33
+ }
34
+
35
+ /** The JSON schema a zod input is listed as, the way the MCP SDK lists it. */
36
+ const listed = (input: z.ZodType) => {
37
+ const schema = z.toJSONSchema(z.object({ v: input }), { io: 'input' }) as unknown as {
38
+ properties: { v: Record<string, unknown> };
39
+ };
40
+ return schema.properties.v;
41
+ };
42
+
43
+ describe('mcpInputs, the shared MCP inputs', () => {
44
+ const inputs = mcpInputs(identity);
45
+
46
+ it('names `<toolPrefix>whoami` in the workspace input, and nothing of a tool elsewhere', () => {
47
+ expect(inputs.workspaceInput.description).toBe(
48
+ 'Target organization (workspace id). Omit to use your default org — see things_whoami.'
49
+ );
50
+ expect(inputs.cursorInput.description).toBe('nextCursor from a previous page.');
51
+ expect(inputs.limitInput.description).toBe('Page size (server max 100). Default 50.');
52
+ expect(mcpInputs({ toolPrefix: 'other_' }).workspaceInput.description).toContain('see other_whoami.');
53
+ });
54
+
55
+ it('are the inputs every chassis tool is listed with, projects included', async () => {
56
+ const tools = await listedTools();
57
+ // The order `tools/list` keeps: the two examples, whoami, then the eleven
58
+ // project tools and the two team reads the chassis registers before a tool's own set.
59
+ expect(tools.map((t) => t.name)).toEqual([
60
+ 'get_me',
61
+ 'list_files',
62
+ 'things_whoami',
63
+ 'things_list_projects',
64
+ 'things_get_project',
65
+ 'things_list_project_members',
66
+ 'things_create_project',
67
+ 'things_update_project',
68
+ 'things_archive_project',
69
+ 'things_add_project_member',
70
+ 'things_set_project_member_role',
71
+ 'things_remove_project_member',
72
+ 'things_set_project_team_role',
73
+ 'things_remove_project_team',
74
+ 'things_list_teams',
75
+ 'things_list_team_members'
76
+ ]);
77
+ const props = (name: string) =>
78
+ tools.find((t) => t.name === name)!.inputSchema.properties as Record<string, Record<string, unknown>>;
79
+
80
+ for (const name of tools.map((t) => t.name)) {
81
+ expect(props(name).workspace).toEqual(listed(inputs.workspaceInput));
82
+ expect(props(name).workspace!.description).toBe(inputs.workspaceInput.description);
83
+ }
84
+ expect(props('list_files').cursor).toEqual(listed(inputs.cursorInput));
85
+ expect(props('list_files').limit).toEqual(listed(inputs.limitInput));
86
+ expect(props('list_files').cursor!.description).toBe(inputs.cursorInput.description);
87
+ expect(props('list_files').limit!.description).toBe(inputs.limitInput.description);
88
+ expect(Object.keys(props('list_files')).sort()).toEqual(['cursor', 'limit', 'workspace']);
89
+
90
+ // The paginated project lists take the same two, from the same factory.
91
+ for (const name of ['things_list_projects', 'things_list_project_members']) {
92
+ expect(props(name).cursor).toEqual(listed(inputs.cursorInput));
93
+ expect(props(name).limit).toEqual(listed(inputs.limitInput));
94
+ }
95
+ });
96
+
97
+ it('validate as the API expects: a uuid, a string, an integer from 1 to 100, all optional', () => {
98
+ expect(inputs.workspaceInput.safeParse(undefined).success).toBe(true);
99
+ expect(inputs.workspaceInput.safeParse('not-a-uuid').success).toBe(false);
100
+ expect(inputs.limitInput.safeParse(100).success).toBe(true);
101
+ expect(inputs.limitInput.safeParse(101).success).toBe(false);
102
+ expect(inputs.limitInput.safeParse(0).success).toBe(false);
103
+ expect(inputs.limitInput.safeParse(1.5).success).toBe(false);
104
+ expect(inputs.cursorInput.safeParse(undefined).success).toBe(true);
105
+ });
106
+ });
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { projectErrorHints } from '@antasphere/chassis-server/mcp';
3
+
4
+ /**
5
+ * The project hints are composed per tool: a hint that names a tool names it
6
+ * as it is registered, under the tool's prefix, or a model follows it to a
7
+ * tool that does not exist.
8
+ */
9
+ describe('projectErrorHints', () => {
10
+ it('names the member tools under the prefix given, never bare', () => {
11
+ const hints = projectErrorHints('acme_');
12
+ for (const name of [
13
+ 'add_project_member',
14
+ 'set_project_member_role',
15
+ 'remove_project_member',
16
+ 'list_project_members'
17
+ ]) {
18
+ expect(hints.member_not_found).toContain(`acme_${name}`);
19
+ expect(hints.member_not_found).not.toMatch(new RegExp(`(^|[^_a-z])${name}`));
20
+ }
21
+ });
22
+
23
+ it('words guest_forbidden, the answer of every project route to a guest', () => {
24
+ expect(projectErrorHints('x_').guest_forbidden).toContain('guests take no part in projects');
25
+ });
26
+ });
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Hono } from 'hono';
3
+ import type { Principal } from '@antasphere/chassis-contract';
4
+ import { noStoreAuthenticated } from '@antasphere/chassis-server/middleware';
5
+
6
+ /**
7
+ * DASH-5: authenticated JSON went out with no Cache-Control at all, leaving
8
+ * the decision to heuristic caching in whatever proxy or bfcache sits in the
9
+ * path — one member's roster served to the next.
10
+ */
11
+ const principal = { userId: 'u1', via: 'session' } as unknown as Principal;
12
+
13
+ function app(withPrincipal: boolean) {
14
+ const a = new Hono();
15
+ a.use('*', noStoreAuthenticated());
16
+ a.use('*', async (c, next) => {
17
+ c.set('principal', withPrincipal ? principal : null);
18
+ return next();
19
+ });
20
+ a.get('/me', (c) => c.json({ user: 'u1' }));
21
+ a.get('/instance', (c) => {
22
+ c.header('Cache-Control', 'public, max-age=60');
23
+ return c.json({ name: 'demo' });
24
+ });
25
+ a.get('/files/1/content', (c) => c.body('bytes', 200, { 'content-type': 'application/octet-stream' }));
26
+ return a;
27
+ }
28
+
29
+ describe('noStoreAuthenticated', () => {
30
+ it('marks authenticated JSON no-store', async () => {
31
+ const res = await app(true).request('/me');
32
+ expect(res.headers.get('cache-control')).toBe('no-store');
33
+ });
34
+
35
+ it('leaves anonymous responses cacheable', async () => {
36
+ const res = await app(false).request('/me');
37
+ expect(res.headers.get('cache-control')).toBeNull();
38
+ });
39
+
40
+ it('never clobbers a Cache-Control the route chose', async () => {
41
+ const res = await app(true).request('/instance');
42
+ expect(res.headers.get('cache-control')).toBe('public, max-age=60');
43
+ });
44
+
45
+ it('leaves streamed byte responses alone (Range/ETag surfaces own their headers)', async () => {
46
+ const res = await app(true).request('/files/1/content');
47
+ expect(res.headers.get('cache-control')).toBeNull();
48
+ });
49
+ });
@@ -0,0 +1,70 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { readdirSync, readFileSync } from 'node:fs';
3
+ import { dirname, join, relative, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ /**
7
+ * No chassis test names a tool's migration FILE (PRDCT-2544). A tool made from
8
+ * the chassis has its own migration history, so a file name of another tool's
9
+ * history is an ENOENT waiting inside a package that tool never edits. The
10
+ * statements a chassis test needs are fixtures the chassis owns
11
+ * (`test/fixtures`). Reading the migration FOLDER by path stays allowed: any
12
+ * coherent folder satisfies it.
13
+ *
14
+ * The pattern is assembled from pieces so that this file does not match itself.
15
+ */
16
+ const FOUR_DIGITS = '\\d{4}';
17
+ const MIGRATION_FILENAME = new RegExp(`${FOUR_DIGITS}_[a-z_]+\\.${'sql'}`);
18
+
19
+ const packagesDir = resolve(dirname(fileURLToPath(import.meta.url)), '../../..');
20
+
21
+ function walk(dir: string): string[] {
22
+ return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
23
+ if (entry.name === 'node_modules') return [];
24
+ const path = join(dir, entry.name);
25
+ return entry.isDirectory() ? walk(path) : [path];
26
+ });
27
+ }
28
+
29
+ const testDirs = readdirSync(packagesDir, { withFileTypes: true })
30
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith('chassis-'))
31
+ .map((entry) => join(packagesDir, entry.name, 'test'));
32
+
33
+ describe('no chassis test names a migration file', () => {
34
+ it('the pattern catches a migration file name', () => {
35
+ expect(MIGRATION_FILENAME.test(['0001', 'some_backfill.sql'].join('_'))).toBe(true);
36
+ expect(MIGRATION_FILENAME.test('fixtures/onboarding-backfill.sql')).toBe(false);
37
+ expect(MIGRATION_FILENAME.test('../../../db/drizzle')).toBe(false);
38
+ });
39
+
40
+ it('it looks where the chassis tests are', () => {
41
+ const names = testDirs.map((dir) => relative(packagesDir, dir));
42
+ expect(names).toContain(join('chassis-server', 'test'));
43
+ expect(names.length).toBeGreaterThanOrEqual(4);
44
+ });
45
+
46
+ it('no file under packages/chassis-*/test contains one', () => {
47
+ const files = testDirs.flatMap((dir) => {
48
+ try {
49
+ return walk(dir);
50
+ } catch {
51
+ return []; // a chassis package without a test folder
52
+ }
53
+ });
54
+ expect(files.length).toBeGreaterThan(50);
55
+ // The walk reaches the fixtures and this guard itself: a walk that skips a
56
+ // folder would leave the count above the floor and the guard blind there.
57
+ const walked = files.map((file) => relative(packagesDir, file));
58
+ expect(walked).toEqual(
59
+ expect.arrayContaining([
60
+ join('chassis-server', 'test', 'fixtures', 'onboarding-backfill.sql'),
61
+ join('chassis-server', 'test', 'fixtures', 'operator-user-id-backfill.sql'),
62
+ join('chassis-server', 'test', 'unit', 'no-tool-migration-filename.test.ts')
63
+ ])
64
+ );
65
+ const offenders = files
66
+ .filter((file) => MIGRATION_FILENAME.test(readFileSync(file, 'utf8')))
67
+ .map((file) => relative(packagesDir, file));
68
+ expect(offenders).toEqual([]);
69
+ });
70
+ });