@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,131 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Hono } from 'hono';
3
+ import { buildCsp, hstsValue, securityHeaders } from '@antasphere/chassis-server/middleware';
4
+ import { createRuntimeState } from '@antasphere/chassis-server/util';
5
+
6
+ /**
7
+ * Two properties, and on this repo the first one is the one with scar tissue.
8
+ *
9
+ * 1. The baseline header middleware is a DEFAULT, never an override. The
10
+ * viewer serves USER-AUTHORED HTML under its own `CSP: sandbox` +
11
+ * `Referrer-Policy: no-referrer`; an unconditional overwrite here silently
12
+ * replaces that sandbox with the dashboard CSP, which is the
13
+ * fail-dangerous class the viewer spike caught live. HSTS is added
14
+ * set-if-absent for the same reason.
15
+ * 2. PLT-28: no HSTS was sent at all. It rides EVERY response, not just HTML —
16
+ * the session cookie goes out on XHR too, so an http downgrade on /api/v1
17
+ * is the same theft.
18
+ */
19
+
20
+ const DASHBOARD_CSP = "default-src 'self'";
21
+
22
+ function appWith(hsts: string | null = null): Hono {
23
+ const app = new Hono();
24
+ app.use('*', securityHeaders({ csp: DASHBOARD_CSP, state: createRuntimeState(), hsts }));
25
+ app.get('/dashboard', (c) => c.html('<h1>app</h1>'));
26
+ app.get('/v/abc', (c) =>
27
+ c.body('<h1>user content</h1>', 200, {
28
+ 'content-type': 'text/html; charset=utf-8',
29
+ 'content-security-policy': 'sandbox allow-scripts',
30
+ 'referrer-policy': 'no-referrer'
31
+ })
32
+ );
33
+ return app;
34
+ }
35
+
36
+ describe('securityHeaders set-if-absent guards', () => {
37
+ it('applies the dashboard CSP to app HTML', async () => {
38
+ const res = await appWith().request('/dashboard');
39
+ expect(res.headers.get('content-security-policy')).toBe(DASHBOARD_CSP);
40
+ expect(res.headers.get('x-content-type-options')).toBe('nosniff');
41
+ });
42
+
43
+ it('never clobbers the viewer sandbox CSP or its referrer policy', async () => {
44
+ const res = await appWith().request('/v/abc');
45
+ expect(res.headers.get('content-security-policy')).toBe('sandbox allow-scripts');
46
+ expect(res.headers.get('referrer-policy')).toBe('no-referrer');
47
+ });
48
+ });
49
+
50
+ /**
51
+ * PLT-28. The tool issues a session cookie for the dashboard AND a scoped
52
+ * viewer cookie on share links; one plain-http navigation is one stolen
53
+ * session or one stolen share unlock.
54
+ */
55
+ describe('HSTS', () => {
56
+ it('is absent when the instance declares an http public origin', () => {
57
+ expect(hstsValue({ PUBLIC_BASE_URL: 'http://localhost:3000', HSTS_MAX_AGE: 15552000 })).toBeNull();
58
+ });
59
+
60
+ it('is armed by an https public origin', () => {
61
+ expect(hstsValue({ PUBLIC_BASE_URL: 'https://app.example.com', HSTS_MAX_AGE: 15552000 })).toBe(
62
+ 'max-age=15552000; includeSubDomains'
63
+ );
64
+ });
65
+
66
+ it('honours HSTS_MAX_AGE=0 as the operator opt-out', () => {
67
+ expect(hstsValue({ PUBLIC_BASE_URL: 'https://app.example.com', HSTS_MAX_AGE: 0 })).toBeNull();
68
+ });
69
+
70
+ it('rides EVERY response, not just HTML — the cookie goes out on XHR too', async () => {
71
+ const app = appWith('max-age=15552000; includeSubDomains');
72
+ app.get('/api/v1/me', (c) => c.json({ ok: true }));
73
+ const html = await app.request('/dashboard');
74
+ expect(html.headers.get('strict-transport-security')).toBe('max-age=15552000; includeSubDomains');
75
+ const json = await app.request('/api/v1/me');
76
+ expect(json.headers.get('strict-transport-security')).toBe('max-age=15552000; includeSubDomains');
77
+ });
78
+
79
+ it('rides the viewer too, without touching its sandbox CSP', async () => {
80
+ const res = await appWith('max-age=15552000; includeSubDomains').request('/v/abc');
81
+ expect(res.headers.get('strict-transport-security')).toBe('max-age=15552000; includeSubDomains');
82
+ expect(res.headers.get('content-security-policy')).toBe('sandbox allow-scripts');
83
+ });
84
+
85
+ it('sends none when the instance is not https', async () => {
86
+ const res = await appWith(null).request('/dashboard');
87
+ expect(res.headers.get('strict-transport-security')).toBeNull();
88
+ });
89
+
90
+ describe('buildCsp frame-src (PRDCT-1352, DASH-4)', () => {
91
+ it("frames only 'self' by default — the single-origin preview", () => {
92
+ expect(buildCsp([])).toContain("frame-src 'self';");
93
+ expect(buildCsp([])).not.toContain('decks.example.net');
94
+ });
95
+
96
+ it('adds the viewer origin so the dashboard preview can frame decks from it', () => {
97
+ const csp = buildCsp(["'sha256-abc'"], { frameSrc: ['https://decks.example.net'] });
98
+ expect(csp).toContain("frame-src 'self' https://decks.example.net;");
99
+ // Nothing else widens: the viewer origin gets frames, not scripts or connects.
100
+ expect(csp).toContain("script-src 'self' 'sha256-abc';");
101
+ expect(csp).toContain("connect-src 'self';");
102
+ expect(csp).toContain("frame-ancestors 'none';");
103
+ });
104
+ });
105
+
106
+ describe('buildCsp style-src and font-src (PRDCT-2308, the two font CDNs)', () => {
107
+ it('allows exactly the two stylesheet API hosts and the two font file hosts, nothing wider', () => {
108
+ const csp = buildCsp([]);
109
+ expect(csp).toContain(
110
+ "style-src 'self' 'unsafe-inline' https://api.fontshare.com https://fonts.googleapis.com;"
111
+ );
112
+ expect(csp).toContain("font-src 'self' https://cdn.fontshare.com https://fonts.gstatic.com;");
113
+ // No scheme-wide source anywhere: `https:` or `data:` on styles or fonts
114
+ // would let any host in (verifier round 1, M12/M13).
115
+ for (const directive of csp.split('; ')) {
116
+ if (directive.startsWith('style-src') || directive.startsWith('font-src')) {
117
+ expect(directive).not.toMatch(/\bhttps:(\s|$)/);
118
+ expect(directive).not.toContain('data:');
119
+ }
120
+ }
121
+ expect(csp).toContain("img-src 'self' data:;");
122
+ // A tool's declaration widens img-src by exactly what it names (PRDCT-2725: `blob:`
123
+ // for an image fetched through the API and shown from an object URL); the chassis
124
+ // default carries no scheme source but data:.
125
+ expect(buildCsp([], { imgSrc: ['blob:'] })).toContain("img-src 'self' data: blob:;");
126
+ expect(buildCsp([], { imgSrc: ['blob:'] })).not.toMatch(/img-src[^;]*https:/);
127
+ expect(csp).toContain("connect-src 'self';");
128
+ expect(csp).toContain("default-src 'self';");
129
+ });
130
+ });
131
+ });
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Readable } from 'node:stream';
3
+ import { Hono } from 'hono';
4
+ import { serveBlob } from '@antasphere/chassis-server/files';
5
+ import type { StorageDriver } from '@antasphere/chassis-server/storage';
6
+ import type { Logger } from '@antasphere/chassis-server/logger';
7
+
8
+ /**
9
+ * PLT-5 backstop: versions are immutable, so a manifest `contentType`
10
+ * committed before the contract's media-type gate (or through any path that
11
+ * skips it) lives forever. Serving it verbatim made the `Headers` constructor
12
+ * throw — a permanent anonymous 500 on that asset. The serve helper degrades
13
+ * an invalid stored value to application/octet-stream instead.
14
+ */
15
+
16
+ const noopLogger = {
17
+ error: () => {},
18
+ warn: () => {},
19
+ info: () => {},
20
+ debug: () => {}
21
+ } as unknown as Logger;
22
+
23
+ function storageWith(bytes: Buffer): StorageDriver {
24
+ return {
25
+ name: 'test',
26
+ exists: async () => true,
27
+ getStream: async () => Readable.from([bytes]),
28
+ put: async () => {
29
+ throw new Error('unused');
30
+ },
31
+ delete: async () => {
32
+ throw new Error('unused');
33
+ }
34
+ } as unknown as StorageDriver;
35
+ }
36
+
37
+ async function serve(contentType: string): Promise<Response> {
38
+ const bytes = Buffer.from('hello');
39
+ const app = new Hono();
40
+ app.get('/blob', (c) =>
41
+ serveBlob(c, {
42
+ storage: storageWith(bytes),
43
+ logger: noopLogger,
44
+ workspaceId: '00000000-0000-4000-8000-000000000000',
45
+ sha256: 'a'.repeat(64),
46
+ sizeBytes: bytes.length,
47
+ contentType,
48
+ filename: 'file.bin',
49
+ headOnly: false
50
+ })
51
+ );
52
+ return app.request('/blob');
53
+ }
54
+
55
+ describe('serveBlob content-type sanitization (PLT-5)', () => {
56
+ it('serves a valid stored media type verbatim', async () => {
57
+ const res = await serve('text/plain; charset=utf-8');
58
+ expect(res.status).toBe(200);
59
+ expect(res.headers.get('content-type')).toBe('text/plain; charset=utf-8');
60
+ });
61
+
62
+ it('degrades an invalid stored value to octet-stream instead of throwing', async () => {
63
+ // Includes a value that passes the RFC 7231 grammar but carries a code
64
+ // point above Latin-1 in a quoted parameter (charset="€") — the real
65
+ // ByteString trap the serve backstop must also catch, not just the
66
+ // grammar violations (PLT-5, verifier round-2 coverage note).
67
+ for (const bad of ['text/html; charset="€"', 'text/héml', 'not a type', 'text/html\r\nx-evil: 1']) {
68
+ const res = await serve(bad);
69
+ expect(res.status, bad).toBe(200);
70
+ expect(res.headers.get('content-type'), bad).toBe('application/octet-stream');
71
+ expect(res.headers.get('x-evil')).toBeNull();
72
+ }
73
+ });
74
+ });
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { isSecureSetupOrigin } from '@antasphere/chassis-server/util';
3
+ import { INSECURE_SETUP_ORIGINS, SECURE_SETUP_ORIGINS } from '@antasphere/chassis-server/testing';
4
+
5
+ /**
6
+ * PLT-10: the documented no-domain install path used to open port 3000 to the
7
+ * world and tell the operator to complete the first-boot wizard over
8
+ * plaintext HTTP. That request carries the owner password and the setup
9
+ * token, and it is the one request that decides who owns the instance.
10
+ *
11
+ * The URL table lives in src/testing/setup-origins.ts because the shell
12
+ * twin of this rule (dr_origin_is_secure, which setup.sh uses to warn the
13
+ * operator up front) is driven from the same list — see
14
+ * apps/server/test/unit/dr-lib.test.ts. One table, two implementations, no drift.
15
+ */
16
+ describe('isSecureSetupOrigin', () => {
17
+ it.each(SECURE_SETUP_ORIGINS)('accepts %s', (url) => {
18
+ expect(isSecureSetupOrigin(url)).toBe(true);
19
+ });
20
+
21
+ it.each(INSECURE_SETUP_ORIGINS)('REFUSES %s', (url) => {
22
+ expect(isSecureSetupOrigin(url)).toBe(false);
23
+ });
24
+
25
+ it('fails closed on anything it cannot parse', () => {
26
+ expect(isSecureSetupOrigin('not a url')).toBe(false);
27
+ expect(isSecureSetupOrigin('')).toBe(false);
28
+ });
29
+ });
@@ -0,0 +1,53 @@
1
+ import { mkdtemp, rm } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { Readable } from 'node:stream';
5
+ import { text } from 'node:stream/consumers';
6
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
7
+ import { LocalStorageDriver } from '@antasphere/chassis-server/storage';
8
+
9
+ /**
10
+ * The local driver must fail a missing blob at the getStream() await — never
11
+ * as a late 'error' event on an already-returned stream. The content route
12
+ * commits its status line right after that await, so a lazy open turned a
13
+ * missing blob into a 200 + truncated body (scale drill, I2).
14
+ */
15
+ describe('LocalStorageDriver.getStream', () => {
16
+ let root: string;
17
+ let driver: LocalStorageDriver;
18
+ const KEY = 'ws/ab/cd/blob';
19
+ const PAYLOAD = 'The quick brown fox jumps over the lazy dog';
20
+
21
+ beforeAll(async () => {
22
+ root = await mkdtemp(join(tmpdir(), 'storage-local-test-'));
23
+ driver = new LocalStorageDriver(root);
24
+ });
25
+
26
+ afterAll(async () => {
27
+ await rm(root, { recursive: true, force: true });
28
+ });
29
+
30
+ it('rejects at the await (ENOENT) when the blob is missing', async () => {
31
+ await expect(driver.getStream('ws/ab/cd/missing')).rejects.toMatchObject({ code: 'ENOENT' });
32
+ });
33
+
34
+ it('streams a stored blob back, in full and by range', async () => {
35
+ await driver.put(KEY, Readable.from(PAYLOAD), {
36
+ contentType: 'text/plain',
37
+ sizeBytes: PAYLOAD.length
38
+ });
39
+ expect(await driver.exists(KEY)).toBe(true);
40
+
41
+ const full = await driver.getStream(KEY);
42
+ expect(await text(full)).toBe(PAYLOAD);
43
+
44
+ const slice = await driver.getStream(KEY, { start: 4, end: 8 });
45
+ expect(await text(slice)).toBe('quick');
46
+ });
47
+
48
+ it('rejects at the await after the blob is deleted underneath a live key', async () => {
49
+ await driver.delete(KEY);
50
+ expect(await driver.exists(KEY)).toBe(false);
51
+ await expect(driver.getStream(KEY)).rejects.toMatchObject({ code: 'ENOENT' });
52
+ });
53
+ });
@@ -0,0 +1,32 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseSuperadminEmails } from '@antasphere/chassis-server/accounts';
3
+
4
+ describe('parseSuperadminEmails', () => {
5
+ it('returns an empty set (dormant) for undefined and blank input', () => {
6
+ expect(parseSuperadminEmails(undefined).size).toBe(0);
7
+ expect(parseSuperadminEmails('').size).toBe(0);
8
+ expect(parseSuperadminEmails(' ').size).toBe(0);
9
+ });
10
+
11
+ it('parses a comma-separated list, trimming and lowercasing entries', () => {
12
+ const set = parseSuperadminEmails(' Ops@Example.com ,root@host.io ');
13
+ expect(set.size).toBe(2);
14
+ expect(set.has('ops@example.com')).toBe(true);
15
+ expect(set.has('root@host.io')).toBe(true);
16
+ // Matching is on the lowercased form only.
17
+ expect(set.has('Ops@Example.com')).toBe(false);
18
+ });
19
+
20
+ it('dedupes case-variant duplicates', () => {
21
+ const set = parseSuperadminEmails('a@b.co,A@B.CO');
22
+ expect(set.size).toBe(1);
23
+ });
24
+
25
+ it('throws LOUDLY on malformed entries (a typo must never silently disarm)', () => {
26
+ expect(() => parseSuperadminEmails('not-an-email')).toThrow(/not a valid email/);
27
+ expect(() => parseSuperadminEmails('ok@example.com,broken')).toThrow(/broken/);
28
+ expect(() => parseSuperadminEmails('a b@example.com')).toThrow(/not a valid email/);
29
+ // Only separators, no entries: refuse rather than arm an empty list.
30
+ expect(() => parseSuperadminEmails(',,')).toThrow(/at least one email/);
31
+ });
32
+ });
@@ -0,0 +1,287 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import type PgBoss from 'pg-boss';
3
+ import type pg from 'pg';
4
+ import type { Logger } from '@antasphere/chassis-server/logger';
5
+ import {
6
+ MAX_IN_SECONDS,
7
+ PgBossTimers,
8
+ TIMER_KEY_LOCK_CLASS,
9
+ TimerError,
10
+ startAfterOf,
11
+ type TimerOptions,
12
+ type TimerRef
13
+ } from '../../src/jobs/timers.js';
14
+
15
+ /**
16
+ * The one-off timers' validation and their mapping onto pg-boss (PRDCT-3254),
17
+ * with no database: a fake boss records `send`, a fake pool records
18
+ * `connect` and `query`. A malformed call is refused before ANY of them is
19
+ * touched. The statements themselves run against a real pg-boss in
20
+ * `test/integration/timers.test.ts`.
21
+ */
22
+
23
+ const JOB_ID = '0b9a3c8e-1f2d-4e5a-9b6c-7d8e9f0a1b2c';
24
+
25
+ const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as unknown as Logger;
26
+
27
+ interface FakeClient {
28
+ query: ReturnType<typeof vi.fn>;
29
+ release: ReturnType<typeof vi.fn>;
30
+ }
31
+
32
+ function fakes(sendAnswer: string | null = JOB_ID) {
33
+ const send = vi.fn(async (_queue: string, _data: object, _options: PgBoss.SendOptions) => sendAnswer);
34
+ const client: FakeClient = {
35
+ query: vi.fn(async (_text: string, _values?: unknown[]) => ({ rowCount: 0, rows: [] })),
36
+ release: vi.fn()
37
+ };
38
+ const pool = {
39
+ connect: vi.fn(async () => client),
40
+ query: vi.fn(async (_text: string, _values?: unknown[]) => ({ rowCount: 0 as number | null, rows: [] }))
41
+ };
42
+ const timers = new PgBossTimers({ send } as unknown as PgBoss, pool as unknown as pg.Pool, logger);
43
+ return { send, pool, client, timers };
44
+ }
45
+
46
+ async function expectInvalid(promise: Promise<unknown>): Promise<void> {
47
+ const err = await promise.then(
48
+ () => null,
49
+ (e: unknown) => e
50
+ );
51
+ expect(err).toBeInstanceOf(TimerError);
52
+ expect((err as TimerError).code).toBe('invalid_argument');
53
+ }
54
+
55
+ function expectInvalidSync(fn: () => unknown): void {
56
+ let caught: unknown = null;
57
+ try {
58
+ fn();
59
+ } catch (e) {
60
+ caught = e;
61
+ }
62
+ expect(caught).toBeInstanceOf(TimerError);
63
+ expect((caught as TimerError).code).toBe('invalid_argument');
64
+ }
65
+
66
+ describe('startAfterOf', () => {
67
+ it('answers the same Date for `at`', () => {
68
+ const at = new Date('2030-01-02T03:04:05.000Z');
69
+ expect(startAfterOf({ at })).toBe(at);
70
+ });
71
+
72
+ it('answers the seconds for `inSeconds`', () => {
73
+ expect(startAfterOf({ inSeconds: 30 })).toBe(30);
74
+ expect(startAfterOf({ inSeconds: 0 })).toBe(0);
75
+ });
76
+
77
+ it('refuses neither and both', () => {
78
+ expectInvalidSync(() => startAfterOf({} as TimerOptions));
79
+ expectInvalidSync(() => startAfterOf({ at: new Date(), inSeconds: 3 } as unknown as TimerOptions));
80
+ });
81
+
82
+ it('refuses an invalid Date and a value that is not a Date', () => {
83
+ expectInvalidSync(() => startAfterOf({ at: new Date('not a date') }));
84
+ expectInvalidSync(() => startAfterOf({ at: '2030-01-01T00:00:00Z' } as unknown as TimerOptions));
85
+ });
86
+
87
+ it('refuses a negative, NaN, infinite or over-a-century delay; takes the cap itself', () => {
88
+ expectInvalidSync(() => startAfterOf({ inSeconds: -1 }));
89
+ expectInvalidSync(() => startAfterOf({ inSeconds: Number.NaN }));
90
+ expectInvalidSync(() => startAfterOf({ inSeconds: Number.POSITIVE_INFINITY }));
91
+ expectInvalidSync(() => startAfterOf({ inSeconds: MAX_IN_SECONDS + 1 }));
92
+ expect(startAfterOf({ inSeconds: MAX_IN_SECONDS })).toBe(MAX_IN_SECONDS);
93
+ });
94
+
95
+ it('takes a payload with a null prototype and nested Dates (JSON takes them)', async () => {
96
+ const { send, timers } = fakes();
97
+ await timers.schedule('things-timer', Object.create(null) as object, { inSeconds: 1 });
98
+ await timers.schedule('things-timer', { at: new Date() }, { inSeconds: 1 });
99
+ expect(send).toHaveBeenCalledTimes(2);
100
+ });
101
+ });
102
+
103
+ describe('schedule refuses a malformed call before any IO', () => {
104
+ const cases: Array<[string, (t: PgBossTimers) => Promise<unknown>]> = [
105
+ ['a queue name with a space', (t) => t.schedule('things timer', {}, { inSeconds: 1 })],
106
+ ['an empty queue name', (t) => t.schedule('', {}, { inSeconds: 1 })],
107
+ ['a null payload', (t) => t.schedule('things-timer', null as unknown as object, { inSeconds: 1 })],
108
+ ['a string payload', (t) => t.schedule('things-timer', 'x' as unknown as object, { inSeconds: 1 })],
109
+ ['a number payload', (t) => t.schedule('things-timer', 42 as unknown as object, { inSeconds: 1 })],
110
+ ['an array payload', (t) => t.schedule('things-timer', [1, 2] as unknown as object, { inSeconds: 1 })],
111
+ ['a Date payload', (t) => t.schedule('things-timer', new Date(), { inSeconds: 1 })],
112
+ ['a bigint inside the payload', (t) => t.schedule('things-timer', { n: 1n }, { inSeconds: 1 })],
113
+ [
114
+ 'a circular payload',
115
+ (t) => {
116
+ const data: Record<string, unknown> = {};
117
+ data.self = data;
118
+ return t.schedule('things-timer', data, { inSeconds: 1 });
119
+ }
120
+ ],
121
+ ['a delay over a hundred years', (t) => t.schedule('things-timer', {}, { inSeconds: 1e21 })],
122
+ ['no `at` and no `inSeconds`', (t) => t.schedule('things-timer', {}, {} as TimerOptions)],
123
+ ['an empty key', (t) => t.schedule('things-timer', {}, { inSeconds: 1, key: '' })],
124
+ [
125
+ 'a key carrying the unit separator',
126
+ (t) => t.schedule('things-timer', {}, { inSeconds: 1, key: 'a\u001fb' })
127
+ ],
128
+ ['retry.limit -1', (t) => t.schedule('things-timer', {}, { inSeconds: 1, retry: { limit: -1 } })],
129
+ ['retry.limit 1.5', (t) => t.schedule('things-timer', {}, { inSeconds: 1, retry: { limit: 1.5 } })],
130
+ [
131
+ 'retry.delaySeconds -1',
132
+ (t) => t.schedule('things-timer', {}, { inSeconds: 1, retry: { limit: 1, delaySeconds: -1 } })
133
+ ],
134
+ [
135
+ 'retry.limit -1 on a keyed call',
136
+ (t) => t.schedule('things-timer', {}, { inSeconds: 1, key: 'k', retry: { limit: -1 } })
137
+ ]
138
+ ];
139
+
140
+ it.each(cases)('%s → invalid_argument, no send, no connect', async (_label, call) => {
141
+ const { send, pool, timers } = fakes();
142
+ await expectInvalid(call(timers));
143
+ expect(send).not.toHaveBeenCalled();
144
+ expect(pool.connect).not.toHaveBeenCalled();
145
+ expect(pool.query).not.toHaveBeenCalled();
146
+ });
147
+ });
148
+
149
+ describe('schedule without a key maps onto pg-boss send', () => {
150
+ it('`at` → startAfter the Date, no retry options, no singletonKey, no db', async () => {
151
+ const { send, pool, timers } = fakes();
152
+ const at = new Date(Date.now() + 60_000);
153
+ await expect(timers.schedule('things-timer', { n: 1 }, { at })).resolves.toBe(JOB_ID);
154
+ expect(send).toHaveBeenCalledTimes(1);
155
+ const [queue, data, options] = send.mock.calls[0] as unknown as [string, object, PgBoss.SendOptions];
156
+ expect(queue).toBe('things-timer');
157
+ expect(data).toEqual({ n: 1 });
158
+ expect(options).toEqual({ startAfter: at });
159
+ expect(options.startAfter).toBe(at);
160
+ for (const absent of ['retryLimit', 'retryDelay', 'retryBackoff', 'singletonKey', 'db']) {
161
+ expect(options).not.toHaveProperty(absent);
162
+ }
163
+ expect(pool.connect).not.toHaveBeenCalled();
164
+ });
165
+
166
+ it('`inSeconds` → startAfter the seconds', async () => {
167
+ const { send, timers } = fakes();
168
+ await timers.schedule('things-timer', { n: 2 }, { inSeconds: 30 });
169
+ expect(send.mock.calls[0]?.[2]).toEqual({ startAfter: 30 });
170
+ });
171
+
172
+ it('`retry` → retryLimit, retryDelay, retryBackoff', async () => {
173
+ const { send, timers } = fakes();
174
+ await timers.schedule(
175
+ 'things-timer',
176
+ {},
177
+ { inSeconds: 5, retry: { limit: 3, delaySeconds: 10, backoff: true } }
178
+ );
179
+ expect(send.mock.calls[0]?.[2]).toEqual({
180
+ startAfter: 5,
181
+ retryLimit: 3,
182
+ retryDelay: 10,
183
+ retryBackoff: true
184
+ });
185
+ });
186
+
187
+ it('`retry` with the limit alone → delay 0, no backoff', async () => {
188
+ const { send, timers } = fakes();
189
+ await timers.schedule('things-timer', {}, { inSeconds: 5, retry: { limit: 0 } });
190
+ expect(send.mock.calls[0]?.[2]).toEqual({
191
+ startAfter: 5,
192
+ retryLimit: 0,
193
+ retryDelay: 0,
194
+ retryBackoff: false
195
+ });
196
+ });
197
+
198
+ it('a send that answers null is an unknown queue', async () => {
199
+ const { timers } = fakes(null);
200
+ const err = await timers.schedule('no-such-queue', {}, { inSeconds: 1 }).then(
201
+ () => null,
202
+ (e: unknown) => e
203
+ );
204
+ expect(err).toBeInstanceOf(TimerError);
205
+ expect((err as TimerError).code).toBe('unknown_queue');
206
+ });
207
+ });
208
+
209
+ describe('schedule with a key runs one transaction on a client of its own', () => {
210
+ it('BEGIN, the per-key lock, the withdraw, the insert on the same client, COMMIT, release', async () => {
211
+ const { send, pool, client, timers } = fakes();
212
+ await expect(timers.schedule('things-timer', { n: 3 }, { inSeconds: 7, key: 'k1' })).resolves.toBe(
213
+ JOB_ID
214
+ );
215
+ expect(pool.connect).toHaveBeenCalledTimes(1);
216
+ const statements = client.query.mock.calls.map((c) => String(c[0]).trim().split(/\s+/)[0]);
217
+ // The disarm of a running timer (its retries taken away) FIRST, then the withdraw of the pending one:
218
+ // in that order pg-boss's fail path cannot slip its retry row between the two (verifier round 2, F7).
219
+ expect(statements).toEqual(['BEGIN', 'SELECT', 'UPDATE', 'UPDATE', 'COMMIT']);
220
+ expect(client.query.mock.calls[1]?.[1]).toEqual([TIMER_KEY_LOCK_CLASS, 'things-timer\u001fk1']);
221
+ expect(client.query.mock.calls[2]?.[1]).toEqual(['things-timer', 'k1']);
222
+ expect(String(client.query.mock.calls[2]?.[0])).toContain('SET retry_limit = retry_count');
223
+ expect(String(client.query.mock.calls[2]?.[0])).toContain("state = 'active'");
224
+ expect(client.query.mock.calls[3]?.[1]).toEqual(['things-timer', 'k1']);
225
+ expect(String(client.query.mock.calls[3]?.[0])).toContain("state IN ('created', 'retry')");
226
+ const options = send.mock.calls[0]?.[2] as unknown as PgBoss.SendOptions & {
227
+ db: { executeSql: (t: string, v: unknown[]) => Promise<unknown> };
228
+ };
229
+ expect(options.singletonKey).toBe('k1');
230
+ expect(options.startAfter).toBe(7);
231
+ // The insert's executor is the transaction's client.
232
+ await options.db.executeSql('SELECT 1', []);
233
+ expect(client.query).toHaveBeenLastCalledWith('SELECT 1', []);
234
+ expect(client.release).toHaveBeenCalledTimes(1);
235
+ expect(pool.query).not.toHaveBeenCalled();
236
+ });
237
+
238
+ it('an unknown queue rolls the transaction back and releases the client', async () => {
239
+ const { client, timers } = fakes(null);
240
+ const err = await timers.schedule('no-such-queue', {}, { inSeconds: 1, key: 'k' }).then(
241
+ () => null,
242
+ (e: unknown) => e
243
+ );
244
+ expect((err as TimerError).code).toBe('unknown_queue');
245
+ const statements = client.query.mock.calls.map((c) => String(c[0]).trim().split(/\s+/)[0]);
246
+ expect(statements).toContain('ROLLBACK');
247
+ expect(statements).not.toContain('COMMIT');
248
+ expect(client.release).toHaveBeenCalledTimes(1);
249
+ });
250
+ });
251
+
252
+ describe('cancel refuses a malformed call before any IO', () => {
253
+ const cases: Array<[string, string, TimerRef]> = [
254
+ ['both id and key', 'things-timer', { id: JOB_ID, key: 'k' } as unknown as TimerRef],
255
+ ['neither id nor key', 'things-timer', {} as TimerRef],
256
+ ['an id that is not a UUID', 'things-timer', { id: 'not-a-uuid' }],
257
+ ['an id with SQL in it', 'things-timer', { id: `${JOB_ID}' OR '1'='1` }],
258
+ ['an empty key', 'things-timer', { key: '' }],
259
+ ['a queue name with a space', 'things timer', { id: JOB_ID }]
260
+ ];
261
+
262
+ it.each(cases)('%s → invalid_argument, no query', async (_label, queue, ref) => {
263
+ const { pool, timers } = fakes();
264
+ await expectInvalid(timers.cancel(queue, ref));
265
+ expect(pool.query).not.toHaveBeenCalled();
266
+ expect(pool.connect).not.toHaveBeenCalled();
267
+ });
268
+
269
+ it('a well-formed cancel disarms a running timer first, then answers the PENDING rows withdrawn', async () => {
270
+ const { pool, timers } = fakes();
271
+ pool.query
272
+ .mockResolvedValueOnce({ rowCount: 0, rows: [] })
273
+ .mockResolvedValueOnce({ rowCount: 1, rows: [] });
274
+ await expect(timers.cancel('things-timer', { id: JOB_ID })).resolves.toBe(1);
275
+ expect(pool.query.mock.calls[0]?.[1]).toEqual(['things-timer', JOB_ID]);
276
+ expect(String(pool.query.mock.calls[0]?.[0])).toContain('SET retry_limit = retry_count');
277
+ expect(pool.query.mock.calls[1]?.[1]).toEqual(['things-timer', JOB_ID]);
278
+ expect(String(pool.query.mock.calls[1]?.[0])).toContain("state IN ('created', 'retry')");
279
+ // The disarm's row count is not the answer: a running timer is not withdrawn.
280
+ pool.query
281
+ .mockResolvedValueOnce({ rowCount: 1, rows: [] })
282
+ .mockResolvedValueOnce({ rowCount: 0, rows: [] });
283
+ await expect(timers.cancel('things-timer', { key: 'k1' })).resolves.toBe(0);
284
+ expect(pool.query.mock.calls[2]?.[1]).toEqual(['things-timer', 'k1']);
285
+ expect(pool.query.mock.calls[3]?.[1]).toEqual(['things-timer', 'k1']);
286
+ });
287
+ });