@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,118 @@
1
+ /**
2
+ * Versioned API-key peppers — the seam that lets API keys survive an
3
+ * AUTH_SECRET rotation (ADR 008).
4
+ *
5
+ * Version 1 is ALWAYS the pepper that hashed keys before versioning existed:
6
+ * the resolved auth secret, used verbatim as `sha256(secret + pepper)`. With
7
+ * no `API_KEY_PEPPERS` set the registry is exactly `{ 1: authSecret }` and
8
+ * new keys mint at version 1 — byte-identical to the pre-versioning
9
+ * behaviour, nothing to migrate.
10
+ *
11
+ * `API_KEY_PEPPERS` supplies additional versions as `<version>:<secret>`
12
+ * entries joined by `;` (e.g. `1:<old secret>;2:<new pepper>`). Entries
13
+ * override the defaults, so pinning `1:<value>` freezes version 1
14
+ * independently of the live AUTH_SECRET — the first step of the rotation
15
+ * runbook in internal/security-runbooks.md. LOUD RULE: whenever version 1 is pinned, its
16
+ * value MUST be the historical AUTH_SECRET-derived pepper (the secret that
17
+ * was live when the version-1 keys were minted), or every existing key stops
18
+ * resolving. New keys always mint under the HIGHEST version in the registry.
19
+ *
20
+ * Resolution looks up the pepper by the key row's stored `pepper_version`
21
+ * and FAILS CLOSED on a version the registry does not contain — same 401 as
22
+ * a revoked key, never a fallback to another pepper.
23
+ */
24
+
25
+ /** `pepper_version` is a Postgres smallint. */
26
+ export const MAX_PEPPER_VERSION = 32767;
27
+
28
+ /** Peppers hold the same bar as AUTH_SECRET. */
29
+ const MIN_PEPPER_LENGTH = 32;
30
+
31
+ export interface PepperRegistry {
32
+ /** The version new keys are minted under — the highest defined. */
33
+ readonly current: number;
34
+ /** True when API_KEY_PEPPERS explicitly pins version 1 (rotation-safe). */
35
+ readonly v1Pinned: boolean;
36
+ /** The pepper for a stored version, or undefined — the caller MUST fail closed. */
37
+ get(version: number): string | undefined;
38
+ /**
39
+ * Every registered version, ascending. For credentials that carry no
40
+ * version indicator (share-token secrets, whose sha256 IS the DB lookup
41
+ * key), resolution computes the candidate hash under EACH version — the
42
+ * registry has one entry per rotation, so this stays O(rotations).
43
+ */
44
+ readonly versions: readonly number[];
45
+ }
46
+
47
+ /**
48
+ * Parse the `API_KEY_PEPPERS` value into a version→secret map. Throws a
49
+ * readable Error on any malformed entry — surfaced by the env schema, so a
50
+ * bad value refuses boot instead of silently mis-hashing keys.
51
+ */
52
+ export function parseApiKeyPeppers(raw: string): Map<number, string> {
53
+ const map = new Map<number, string>();
54
+ // Trailing/duplicated separators are operator noise, not errors.
55
+ const entries = raw
56
+ .split(';')
57
+ .map((e) => e.trim())
58
+ .filter((e) => e.length > 0);
59
+ if (entries.length === 0) {
60
+ throw new Error('API_KEY_PEPPERS is set but contains no <version>:<secret> entries');
61
+ }
62
+ for (const entry of entries) {
63
+ // Split at the FIRST colon only — the secret itself may contain colons.
64
+ const colon = entry.indexOf(':');
65
+ if (colon <= 0) {
66
+ throw new Error(`API_KEY_PEPPERS entry "${describeEntry(entry)}" is not <version>:<secret>`);
67
+ }
68
+ const versionPart = entry.slice(0, colon).trim();
69
+ const secret = entry.slice(colon + 1);
70
+ if (!/^[0-9]+$/.test(versionPart)) {
71
+ // A reversed `<secret>:<version>` entry makes versionPart the whole
72
+ // secret — truncate so a misconfiguration doesn't echo it to stderr.
73
+ throw new Error(`API_KEY_PEPPERS version "${describeEntry(versionPart)}" is not a positive integer`);
74
+ }
75
+ const version = Number(versionPart);
76
+ if (version < 1 || version > MAX_PEPPER_VERSION) {
77
+ throw new Error(`API_KEY_PEPPERS version ${version} is out of range (1..${MAX_PEPPER_VERSION})`);
78
+ }
79
+ if (map.has(version)) {
80
+ throw new Error(`API_KEY_PEPPERS defines version ${version} twice`);
81
+ }
82
+ if (secret.length < MIN_PEPPER_LENGTH) {
83
+ throw new Error(
84
+ `API_KEY_PEPPERS version ${version} secret is shorter than ${MIN_PEPPER_LENGTH} characters`
85
+ );
86
+ }
87
+ map.set(version, secret);
88
+ }
89
+ return map;
90
+ }
91
+
92
+ /** Error context that never echoes a possible secret: version side only. */
93
+ function describeEntry(entry: string): string {
94
+ const head = entry.slice(0, Math.min(8, entry.length));
95
+ return entry.length > 8 ? `${head}…` : head;
96
+ }
97
+
98
+ /**
99
+ * Build the registry from the resolved auth secret plus the optional
100
+ * `API_KEY_PEPPERS` value (already format-validated by the env schema; this
101
+ * re-parses so direct callers get the same guarantees).
102
+ */
103
+ export function buildPepperRegistry(authSecret: string, apiKeyPeppers?: string): PepperRegistry {
104
+ const peppers = new Map<number, string>([[1, authSecret]]);
105
+ let v1Pinned = false;
106
+ if (apiKeyPeppers !== undefined) {
107
+ const parsed = parseApiKeyPeppers(apiKeyPeppers);
108
+ v1Pinned = parsed.has(1);
109
+ for (const [version, secret] of parsed) peppers.set(version, secret);
110
+ }
111
+ const current = Math.max(...peppers.keys());
112
+ return {
113
+ current,
114
+ v1Pinned,
115
+ get: (version) => peppers.get(version),
116
+ versions: [...peppers.keys()].sort((a, b) => a - b)
117
+ };
118
+ }
@@ -0,0 +1,223 @@
1
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { and, eq, gt, isNull, or, sql } from 'drizzle-orm';
3
+ import { apiKeys, type Db } from '@antasphere/chassis-db';
4
+ import type { Principal } from '@antasphere/chassis-contract';
5
+ import {
6
+ isWorkspaceSelector,
7
+ resolveMembership,
8
+ type OnWorkspaceMiss
9
+ } from '../identity/resolve-membership.js';
10
+ import type { PepperRegistry } from './peppers.js';
11
+
12
+ /**
13
+ * API keys: `<prefix>_<keyId8>_<secret>`.
14
+ *
15
+ * The prefix is the TOOL's (`identity.apiKeyPrefix`, e.g. a product called
16
+ * Acme might use `acm`): a constructor value with no default, so the chassis
17
+ * spells none. The shape is fixed: keyId gives an O(1)
18
+ * indexed lookup, the secret is stored as sha256(secret + pepper) and
19
+ * compared constant-time. The pepper defaults to the server auth secret, so
20
+ * a leaked database alone cannot validate keys — and it is VERSIONED
21
+ * (peppers.ts, ADR 008): each row records the pepper version that hashed it,
22
+ * mint uses the registry's current version, and resolution uses the stored
23
+ * version's pepper or fails closed. Keys are minted by sessions only.
24
+ *
25
+ * A key is a USER credential (user-scoped credential model): it acts as its
26
+ * creator, and the target workspace resolves per request through the shared
27
+ * resolveMembership rule — the X-Workspace-Id header, else the creator's
28
+ * default membership. `workspace_id` on the row is an OPTIONAL PIN (least
29
+ * privilege, and the grandfathered binding of pre-model-change keys): a
30
+ * pinned key resolves ONLY its pinned workspace, and a request naming a
31
+ * DIFFERENT one is rejected loudly (WorkspaceMismatchError → 403).
32
+ */
33
+ const KEY_ID_BYTES = 6; // 8 chars base64url
34
+ const SECRET_BYTES = 32; // 43 chars base64url
35
+
36
+ /** The prefix is a literal in the pattern: every RegExp metacharacter in it is escaped. */
37
+ const escapeRegExp = (s: string): string => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
38
+
39
+ /** The key shape for one prefix: `<prefix>_<keyId8>_<secret>`, two capture groups. */
40
+ const keyPattern = (prefix: string): RegExp =>
41
+ new RegExp(`^${escapeRegExp(prefix)}_([A-Za-z0-9_-]{8})_([A-Za-z0-9_-]{20,})$`);
42
+
43
+ /**
44
+ * A VALID pinned key presented with an X-Workspace-Id naming a different
45
+ * workspace — a client bug or a confused-deputy attempt. Distinct from a
46
+ * plain resolution miss (null → 401) so the middleware can answer the loud
47
+ * 403 the pinned model always answered.
48
+ */
49
+ export class WorkspaceMismatchError extends Error {
50
+ constructor() {
51
+ super('This credential is pinned to a different workspace');
52
+ this.name = 'WorkspaceMismatchError';
53
+ }
54
+ }
55
+
56
+ function hashSecret(secret: string, pepper: string): string {
57
+ return createHash('sha256')
58
+ .update(secret + pepper)
59
+ .digest('hex');
60
+ }
61
+
62
+ export interface MintedKey {
63
+ id: string;
64
+ keyId: string;
65
+ /** The full key — returned once, never stored. */
66
+ key: string;
67
+ }
68
+
69
+ export class ApiKeyService {
70
+ private readonly keyRe: RegExp;
71
+
72
+ constructor(
73
+ private readonly db: Db,
74
+ private readonly peppers: PepperRegistry,
75
+ /** The tool's key prefix, without the underscore (`identity.apiKeyPrefix`). Required: no chassis default. */
76
+ private readonly prefix: string,
77
+ /** Cloud only: one cached reconcile on a well-formed selector miss. */
78
+ private readonly onWorkspaceMiss?: OnWorkspaceMiss | undefined
79
+ ) {
80
+ if (!prefix) throw new Error('ApiKeyService: the key prefix is required');
81
+ this.keyRe = keyPattern(prefix);
82
+ }
83
+
84
+ /** Distinguishes an API-key credential from other bearers (the shape only, no lookup). */
85
+ readonly isToken = (token: string): boolean => this.keyRe.test(token);
86
+
87
+ async mint(opts: {
88
+ /** Optional workspace PIN — omitted/null mints a user-scoped key. */
89
+ workspaceId?: string | null;
90
+ createdBy: string;
91
+ name: string;
92
+ scopes: string[];
93
+ expiresAt?: Date | null;
94
+ }): Promise<MintedKey> {
95
+ const keyId = randomBytes(KEY_ID_BYTES).toString('base64url');
96
+ const secret = randomBytes(SECRET_BYTES).toString('base64url');
97
+ // Mint always hashes under the CURRENT pepper version and records which
98
+ // one, so resolution can keep verifying old keys across pepper rotations.
99
+ const pepperVersion = this.peppers.current;
100
+ const pepper = this.peppers.get(pepperVersion);
101
+ if (pepper === undefined) throw new Error(`pepper registry has no current version ${pepperVersion}`);
102
+ const [row] = await this.db
103
+ .insert(apiKeys)
104
+ .values({
105
+ workspaceId: opts.workspaceId ?? null,
106
+ keyId,
107
+ secretHash: hashSecret(secret, pepper),
108
+ pepperVersion,
109
+ name: opts.name,
110
+ scopes: opts.scopes,
111
+ createdBy: opts.createdBy,
112
+ expiresAt: opts.expiresAt ?? null
113
+ })
114
+ .returning({ id: apiKeys.id });
115
+ if (!row) throw new Error('api key insert failed');
116
+ return { id: row.id, keyId, key: `${this.prefix}_${keyId}_${secret}` };
117
+ }
118
+
119
+ /**
120
+ * Resolve a presented key to a live principal, or null. The key dies with
121
+ * its creator: a live active membership is required (same re-check
122
+ * discipline as sessions).
123
+ *
124
+ * `requested` is the request's X-Workspace-Id (or null). An UNPINNED key
125
+ * selects with it exactly like a session (fail closed on non-membership);
126
+ * a PINNED key always resolves its pin and THROWS WorkspaceMismatchError
127
+ * when `requested` names a different workspace — never silently serves
128
+ * the pinned one.
129
+ */
130
+ async resolve(token: string, requested: string | null): Promise<Principal | null> {
131
+ const match = this.keyRe.exec(token);
132
+ if (!match) return null;
133
+ const [, keyId, secret] = match;
134
+ if (!keyId || !secret) return null;
135
+
136
+ const [row] = await this.db
137
+ .select({
138
+ id: apiKeys.id,
139
+ secretHash: apiKeys.secretHash,
140
+ pepperVersion: apiKeys.pepperVersion,
141
+ workspaceId: apiKeys.workspaceId,
142
+ scopes: apiKeys.scopes,
143
+ createdBy: apiKeys.createdBy,
144
+ expiresAt: apiKeys.expiresAt
145
+ })
146
+ .from(apiKeys)
147
+ .where(
148
+ and(
149
+ eq(apiKeys.keyId, keyId),
150
+ isNull(apiKeys.revokedAt),
151
+ // Expired = same lookup miss as revoked — one fail-closed 401, no
152
+ // behavioural fork. DB clock, not app clock.
153
+ or(isNull(apiKeys.expiresAt), gt(apiKeys.expiresAt, sql`now()`))
154
+ )
155
+ )
156
+ .limit(1);
157
+ if (!row) return null;
158
+
159
+ // Verify under the pepper version THIS row was hashed with — never the
160
+ // current one, never a fallback. An unknown version (e.g. the operator
161
+ // dropped a retired pepper from API_KEY_PEPPERS while such keys remain)
162
+ // FAILS CLOSED: same null → same 401 as a revoked or expired key.
163
+ const pepper = this.peppers.get(row.pepperVersion);
164
+ if (pepper === undefined) return null;
165
+
166
+ const presented = Buffer.from(hashSecret(secret, pepper), 'hex');
167
+ const stored = Buffer.from(row.secretHash, 'hex');
168
+ if (presented.length !== stored.length || !timingSafeEqual(presented, stored)) return null;
169
+
170
+ // Live membership through the shared selection rule. A pinned key's pin
171
+ // IS the selector (fail-closed on the live membership, as always); an
172
+ // unpinned key selects like a session. Guest capability limits (D2) bind
173
+ // machine credentials too — the origin of the LIVE membership rides the
174
+ // principal, and accountRef carries the projection id the cloud live
175
+ // gate keys on (internal/federation.md).
176
+ const selector = row.workspaceId ?? requested;
177
+ let member = await resolveMembership(this.db, row.createdBy, selector);
178
+ if (!member && selector && this.onWorkspaceMiss && isWorkspaceSelector(selector)) {
179
+ // Unknown-workspace retry (cloud): the KEY verified above — the miss
180
+ // may be a hub org granted since the last reconcile pass. One cached
181
+ // reconcile, one re-run of the SAME lookup — no recursion. Covers the
182
+ // pin too: a pinned key into a freshly granted hub org resolves.
183
+ await this.onWorkspaceMiss(row.createdBy, selector);
184
+ member = await resolveMembership(this.db, row.createdBy, selector);
185
+ }
186
+ if (!member) return null;
187
+ // A valid PINNED key + a header naming another workspace: reject loudly
188
+ // (the pre-model-change contract). Checked after the membership resolves
189
+ // so a dead key/membership stays the uniform 401, exactly as before. The
190
+ // header may name the pinned workspace by its LOCAL id or by the id of
191
+ // the hub organization it projects (the resolved row's accountRef,
192
+ // PRDCT-2947): both are the same workspace, neither is a mismatch.
193
+ if (row.workspaceId && requested) {
194
+ const named = requested.toLowerCase();
195
+ const isPin =
196
+ named === row.workspaceId.toLowerCase() ||
197
+ (member.accountRef !== null && named === member.accountRef.toLowerCase());
198
+ if (!isPin) throw new WorkspaceMismatchError();
199
+ }
200
+
201
+ // Advisory only — never correctness-load-bearing (statelessness invariant).
202
+ void this.db
203
+ .update(apiKeys)
204
+ .set({ lastUsedAt: sql`now()` })
205
+ .where(eq(apiKeys.id, row.id))
206
+ .catch(() => {});
207
+
208
+ return {
209
+ userId: row.createdBy,
210
+ email: member.email,
211
+ name: member.name,
212
+ workspaceId: member.workspaceId,
213
+ role: member.role,
214
+ origin: member.origin,
215
+ via: 'api_key',
216
+ scopes: new Set(row.scopes),
217
+ apiKeyId: row.id,
218
+ ...(row.expiresAt ? { apiKeyExpiresAt: row.expiresAt.toISOString() } : {}),
219
+ ...(member.accountRef ? { accountRef: member.accountRef } : {}),
220
+ ...(row.workspaceId ? { pinned: true as const } : {})
221
+ };
222
+ }
223
+ }
package/src/app.ts ADDED
@@ -0,0 +1,228 @@
1
+ import { Hono } from 'hono';
2
+ import type { MiddlewareHandler } from 'hono';
3
+ import { HTTPException } from 'hono/http-exception';
4
+ import type { OpenAPIHono } from '@hono/zod-openapi';
5
+ import { serveStatic } from '@hono/node-server/serve-static';
6
+ import { readFile } from 'node:fs/promises';
7
+ import { join } from 'node:path';
8
+ import type { Logger } from './logger.js';
9
+ import type { RuntimeState } from './util/index.js';
10
+ import { requestId } from './middleware/index.js';
11
+ import { buildCsp, inlineScriptHashes, securityHeaders } from './middleware/index.js';
12
+ import { healthRoutes } from './routes/index.js';
13
+
14
+ export interface AppDeps {
15
+ logger: Logger;
16
+ state: RuntimeState;
17
+ /** Directory holding the built dashboard SPA. */
18
+ publicDir: string;
19
+ /** The tool's name on the no-dashboard page, title and heading alike (`identity.displayName`). */
20
+ displayName: string;
21
+ /** The versioned API, mounted at /api/v1. */
22
+ api: OpenAPIHono;
23
+ /** The bundled MCP endpoint, mounted at /mcp (M6). */
24
+ mcp?: Hono;
25
+ /** Root-level OAuth discovery documents (/.well-known/*, M6). */
26
+ wellKnown?: Hono;
27
+ /**
28
+ * The tool's public routes (e.g. a public share-link viewer,
29
+ * /v/{secret}, Phase 4 / ADR 012). Mounted in the public-route slot:
30
+ * OUTSIDE /api/v1, outside the auth/scope middleware — token recipients
31
+ * are anonymous, the path secret is the whole credential, and every
32
+ * user-content response it emits is locked under `CSP: sandbox` (see
33
+ * viewer/routes.ts in the app).
34
+ */
35
+ publicRoutes?: Hono | undefined;
36
+ /** Observability middlewares + the /metrics route (M5). */
37
+ metricsMiddleware?: MiddlewareHandler;
38
+ metricsRoutes?: Hono;
39
+ otelMiddleware?: MiddlewareHandler;
40
+ /**
41
+ * Live readiness probe for the backing store (routes/health.ts). Omitted =
42
+ * /readyz keeps reporting the boot-time storage result forever.
43
+ */
44
+ probeStorage?: () => Promise<void>;
45
+ /** `Strict-Transport-Security` value (security-headers.ts `hstsValue`); null/absent = no HSTS. */
46
+ hsts?: string | null;
47
+ /**
48
+ * The tool's root middleware, installed after the metrics middleware and
49
+ * BEFORE every mount. E.g. a tool with VIEWER_BASE_URL (PRDCT-1352): when
50
+ * present, the host gate (middleware/host-gate.ts in the app) — the viewer
51
+ * hostname answers only `/v/*`, `/api/v1/viewer/*` and the probes, every
52
+ * other hostname redirects `/v/*` there. Absent = single-origin behaviour,
53
+ * gate not installed.
54
+ */
55
+ rootMiddleware?: MiddlewareHandler | undefined;
56
+ /** Extra `frame-src` origins of the dashboard CSP (e.g. a tool's viewer origin, for the preview). */
57
+ cspFrameSrc?: readonly string[] | undefined;
58
+ /** Extra `img-src` sources of the dashboard CSP (e.g. `blob:` for a tool whose dashboard shows images it fetched). */
59
+ cspImgSrc?: readonly string[] | undefined;
60
+ }
61
+
62
+ /**
63
+ * The SQLSTATE of a thrown Postgres error, when it is one. node-postgres puts
64
+ * it on `.code`; drizzle re-throws the same object untouched.
65
+ */
66
+ export function postgresErrorCode(error: unknown): string | undefined {
67
+ if (typeof error !== 'object' || error === null) return undefined;
68
+ const code = (error as { code?: unknown }).code;
69
+ return typeof code === 'string' ? code : undefined;
70
+ }
71
+
72
+ /**
73
+ * Assemble the single Hono app. Route precedence is load-bearing and fixed:
74
+ * health → (api, mcp, well-known — mounted here as the platform grows) →
75
+ * static assets → SPA index fallback LAST. A slot deliberately remains open
76
+ * before the fallback for products that add public content routes.
77
+ */
78
+ export async function createApp({
79
+ logger,
80
+ state,
81
+ publicDir,
82
+ displayName,
83
+ api,
84
+ mcp,
85
+ wellKnown,
86
+ publicRoutes,
87
+ metricsMiddleware,
88
+ metricsRoutes,
89
+ otelMiddleware,
90
+ probeStorage,
91
+ hsts,
92
+ rootMiddleware,
93
+ cspFrameSrc,
94
+ cspImgSrc
95
+ }: AppDeps): Promise<Hono> {
96
+ const app = new Hono();
97
+
98
+ // The SPA fallback is read first: its inline bootstrap script hashes feed
99
+ // the CSP that guards every HTML response.
100
+ let indexHtml: string | null = null;
101
+ try {
102
+ indexHtml = await readFile(join(publicDir, 'index.html'), 'utf8');
103
+ } catch {
104
+ logger.warn({ publicDir }, 'no dashboard build found — serving a placeholder at /');
105
+ }
106
+ const csp = buildCsp(indexHtml ? inlineScriptHashes(indexHtml) : [], {
107
+ frameSrc: [...(cspFrameSrc ?? [])],
108
+ imgSrc: [...(cspImgSrc ?? [])]
109
+ });
110
+
111
+ app.use('*', requestId(logger));
112
+ app.use('*', securityHeaders({ csp, state, hsts: hsts ?? null }));
113
+ // PRDCT-1809: a fail-closed boot verdict closes the whole surface — the
114
+ // operator-facing probes stay so the verdict is visible (/readyz carries
115
+ // the reason) and the process is not restarted into the same state.
116
+ app.use('*', async (c, next) => {
117
+ if (state.closed === null) return next();
118
+ const path = c.req.path;
119
+ if (path === '/healthz' || path === '/readyz' || path === '/metrics') return next();
120
+ return c.json(
121
+ {
122
+ error: {
123
+ code: 'service_closed',
124
+ message: 'This instance refuses to serve until an operator resolves a boot verdict; see /readyz'
125
+ }
126
+ },
127
+ 503
128
+ );
129
+ });
130
+ if (otelMiddleware) app.use('*', otelMiddleware);
131
+ if (metricsMiddleware) app.use('*', metricsMiddleware);
132
+ // PRDCT-1352: the viewer-origin host gate, BEFORE every mount below — the
133
+ // boundary between the two hostnames must be decided before any route
134
+ // (and any cookie-reading middleware) runs.
135
+ if (rootMiddleware) app.use('*', rootMiddleware);
136
+
137
+ // Uncaught handler errors: log with the request id, answer the wire shape.
138
+ app.onError((error, c) => {
139
+ // Malformed request bodies first: Hono's body validators throw
140
+ // HTTPException(400) on unparseable JSON ("Malformed JSON in request
141
+ // body") or broken multipart BEFORE zod runs, so the zod-openapi
142
+ // defaultHook never sees them — and Hono routes a thrown error straight
143
+ // here from the throwing handler's own dispatch frame, so an upstream
144
+ // try/catch middleware never observes it either. This is therefore THE
145
+ // seam: map the framework's 400s to the wire shape with a stable
146
+ // machine code instead of amplifying a pre-auth-reachable client
147
+ // mistake into a 500 + an error-level log. Only hono's validators throw
148
+ // HTTPException(400) on this app (the MCP sub-app handles its own).
149
+ if (error instanceof HTTPException && error.status === 400) {
150
+ const isJson = error.message.includes('JSON');
151
+ return c.json(
152
+ {
153
+ error: {
154
+ code: isJson ? 'invalid_json' : 'invalid_body',
155
+ message: isJson ? 'Request body is not valid JSON' : 'Malformed request body'
156
+ }
157
+ },
158
+ 400
159
+ );
160
+ }
161
+ // A NUL byte (or any byte the server encoding cannot represent) that
162
+ // reaches Postgres inside a text value raises 22021 / 22P05. It is a
163
+ // CLIENT mistake, anonymously drivable on every free-text sink, but the
164
+ // pg error object carries the offending statement AND its bound
165
+ // parameters — so left alone it becomes a 500 whose error-level log line
166
+ // prints the SQL and the values. Contract-level `noNulString` rejects the
167
+ // known sinks at validation time; this is the backstop for the ones a
168
+ // product forgets, and it must answer 400 with no log.
169
+ const pgCode = postgresErrorCode(error);
170
+ if (pgCode === '22021' || pgCode === '22P05') {
171
+ return c.json(
172
+ {
173
+ error: {
174
+ code: 'invalid_characters',
175
+ message: 'Request contains characters that cannot be stored (e.g. a NUL byte)'
176
+ }
177
+ },
178
+ 400
179
+ );
180
+ }
181
+ // JSON.stringify is recursive: a deeply nested body blows the V8 stack
182
+ // (`RangeError`) or exceeds the maximum string length. Both are client
183
+ // input, not server faults. middleware/json-depth.ts rejects these at the
184
+ // edge; this is the backstop for anything that builds a deep structure
185
+ // downstream. Narrow on the two known messages — a RangeError from
186
+ // anywhere else is a real bug and must stay a 500.
187
+ if (
188
+ error instanceof RangeError &&
189
+ /maximum call stack size exceeded|invalid string length/i.test(error.message)
190
+ ) {
191
+ return c.json(
192
+ { error: { code: 'payload_too_deep', message: 'Request payload is nested too deeply' } },
193
+ 400
194
+ );
195
+ }
196
+ (c.get('logger') ?? logger).error({ err: error }, 'unhandled error');
197
+ return c.json({ error: { code: 'internal', message: 'Internal server error' } }, 500);
198
+ });
199
+
200
+ app.route(
201
+ '/',
202
+ healthRoutes(state, {
203
+ ...(probeStorage ? { probeStorage } : {}),
204
+ onStorageFailure: (err) => logger.error({ err }, 'storage probe failed — reporting not ready')
205
+ })
206
+ );
207
+ if (metricsRoutes) app.route('/', metricsRoutes);
208
+
209
+ app.route('/api/v1', api);
210
+ if (mcp) app.route('/mcp', mcp);
211
+ if (wellKnown) app.route('/', wellKnown);
212
+
213
+ // The public-route slot: the share-link viewer lives here — before the
214
+ // static assets and the SPA fallback, after every credentialed surface.
215
+ if (publicRoutes) app.route('/', publicRoutes);
216
+
217
+ app.use('*', serveStatic({ root: publicDir }));
218
+
219
+ app.get('*', (c) => {
220
+ if (indexHtml) return c.html(indexHtml);
221
+ return c.html(
222
+ `<!doctype html><title>${displayName}</title><h1>${displayName} API is running</h1>` +
223
+ '<p>No dashboard build found. API health: <a href="/healthz">/healthz</a></p>'
224
+ );
225
+ });
226
+
227
+ return app;
228
+ }
@@ -0,0 +1,101 @@
1
+ import { and, eq, gte, ilike, inArray, isNull, like, lte, or, type SQL } from 'drizzle-orm';
2
+ import { AUDIT_ACTOR_SYSTEM, type AuditListQuery, type AuditVia } from '@antasphere/chassis-contract';
3
+ import { auditLog, user as userTable } from '@antasphere/chassis-db';
4
+
5
+ /**
6
+ * The audit list's WHERE, from the validated query. Every value arrives as
7
+ * a bound parameter (drizzle's `eq`/`like`/`ilike` never interpolate), and
8
+ * the free-text ones are escaped as LIKE patterns first, so a `%` or `_` in
9
+ * what the admin typed matches itself rather than anything.
10
+ */
11
+
12
+ /** A LIKE pattern for "contains this text", the wildcards of the text itself escaped. */
13
+ export function escapeLike(value: string): string {
14
+ return value.replace(/[\\%_]/g, (ch) => `\\${ch}`);
15
+ }
16
+
17
+ /**
18
+ * The `action` list, split and de-duplicated: an item ending in `.` is a
19
+ * family (a prefix), any other item is one action. The contract has already
20
+ * bounded the count and the alphabet; empty items are dropped here.
21
+ */
22
+ export function parseActionList(value: string): { exact: string[]; families: string[] } {
23
+ const exact = new Set<string>();
24
+ const families = new Set<string>();
25
+ for (const raw of value.split(',')) {
26
+ const item = raw.trim();
27
+ if (!item) continue;
28
+ if (item.endsWith('.')) families.add(item);
29
+ else exact.add(item);
30
+ }
31
+ return { exact: [...exact], families: [...families] };
32
+ }
33
+
34
+ /** The `actorVia` list, split and de-duplicated (the contract pinned the alphabet). */
35
+ export function parseViaList(value: string): AuditVia[] {
36
+ return [...new Set(value.split(',').map((v) => v.trim()))].filter((v): v is AuditVia =>
37
+ ['session', 'api_key', 'oauth', 'system'].includes(v)
38
+ );
39
+ }
40
+
41
+ /**
42
+ * The cursor, or null when absent, malformed or out of range. Malformed
43
+ * cursors serve page 1; out-of-range ones are refused the same way, since
44
+ * Number('99999999999999999999') is finite but past bigint precision, and
45
+ * sending it to the `lt(id, …)` comparison overflows in Postgres.
46
+ * Safe-integer is the honest bound for a JS-roundtripped bigserial id.
47
+ */
48
+ export function cursorId(cursor: string | undefined): number | null {
49
+ const parsed = cursor ? Number(cursor) : null;
50
+ return parsed !== null && Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
51
+ }
52
+
53
+ /**
54
+ * The filter conditions (the workspace scope and the cursor are the
55
+ * caller's): one AND-ed list, each member a parameterised comparison.
56
+ * Returns an empty list when nothing filters.
57
+ */
58
+ export function auditFilterConditions(query: AuditListQuery): SQL[] {
59
+ const conditions: SQL[] = [];
60
+
61
+ if (query.q) {
62
+ const pattern = `%${escapeLike(query.q)}%`;
63
+ const either = or(ilike(userTable.email, pattern), ilike(auditLog.action, pattern));
64
+ if (either) conditions.push(either);
65
+ }
66
+
67
+ if (query.action) {
68
+ const { exact, families } = parseActionList(query.action);
69
+ const alternatives: SQL[] = [];
70
+ if (exact.length) alternatives.push(inArray(auditLog.action, exact));
71
+ for (const family of families) alternatives.push(like(auditLog.action, `${escapeLike(family)}%`));
72
+ const anyOf = alternatives.length ? or(...alternatives) : undefined;
73
+ if (anyOf) conditions.push(anyOf);
74
+ }
75
+
76
+ if (query.actorVia) {
77
+ const vias = parseViaList(query.actorVia);
78
+ if (vias.length) conditions.push(inArray(auditLog.actorVia, vias));
79
+ }
80
+
81
+ if (query.actor) {
82
+ conditions.push(
83
+ query.actor === AUDIT_ACTOR_SYSTEM
84
+ ? isNull(auditLog.actorUserId)
85
+ : eq(auditLog.actorUserId, query.actor)
86
+ );
87
+ }
88
+
89
+ if (query.resourceType) conditions.push(eq(auditLog.resourceType, query.resourceType));
90
+ if (query.resourceId) conditions.push(eq(auditLog.resourceId, query.resourceId));
91
+ if (query.from) conditions.push(gte(auditLog.createdAt, new Date(query.from)));
92
+ if (query.to) conditions.push(lte(auditLog.createdAt, new Date(query.to)));
93
+
94
+ return conditions;
95
+ }
96
+
97
+ /** The whole WHERE: the scope, the filters, and the keyset cursor when there is one. */
98
+ export function auditWhere(scope: SQL, filters: SQL[], cursor: SQL | null): SQL {
99
+ const all = [scope, ...filters, ...(cursor ? [cursor] : [])];
100
+ return and(...all) ?? scope;
101
+ }
@@ -0,0 +1,9 @@
1
+ export {
2
+ auditFilterConditions,
3
+ auditWhere,
4
+ cursorId,
5
+ escapeLike,
6
+ parseActionList,
7
+ parseViaList
8
+ } from './filters.js';
9
+ export { AuditService, type AuditWrite, auditMiddleware } from './service.js';