@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,636 @@
1
+ import type { OpenAPIHono } from '@hono/zod-openapi';
2
+ import type { Context, MiddlewareHandler } from 'hono';
3
+ import { COMPOSED_HANDLER } from 'hono/utils/constants';
4
+ import { ulid } from 'ulid';
5
+ import {
6
+ actorHookOf,
7
+ declaredContentLength,
8
+ ENTITLEMENT_DENIED,
9
+ ENTITLEMENT_TIERS,
10
+ featureKeyOf,
11
+ PLAN_REQUIRED,
12
+ type ActorRef,
13
+ type EntitlementDeniedDetails,
14
+ type EntitlementRequest,
15
+ type EntitlementService,
16
+ type EntitlementTier,
17
+ type MeterDeclaration,
18
+ type PlanRequiredDetails,
19
+ type Principal,
20
+ routeEntitlementKey,
21
+ type RouteEntitlementDeclarations,
22
+ type RouteEntitlementEntry,
23
+ type ToolEntitlements,
24
+ type UsageEvent,
25
+ type UsageSink
26
+ } from '@antasphere/chassis-contract';
27
+ import type { Logger } from '../logger.js';
28
+ import { pendingBodyRefusal } from '../middleware/body-refusal.js';
29
+ import type { CreditCheck } from './check.js';
30
+ import { defaultProfile, type EntitlementProfiles, type ResolvedProfile } from './profiles.js';
31
+
32
+ /**
33
+ * The ONE middleware of the billing rail (the spec, §7): installed after the
34
+ * scope gate and before the handler, for every route that declares a meter,
35
+ * a limit or a feature. The dashboard, the CLI and the MCP tools are all
36
+ * clients of `/api/v1`, so this is the one place and the three surfaces need
37
+ * nothing.
38
+ *
39
+ * Per request, once the principal is known:
40
+ *
41
+ * cloud, a hub-projected workspace (an `accountRef`), the METERED path:
42
+ * 1. the actor and the account (the principal's, or the route's `actor`);
43
+ * 2. feature, then limit, against the account's profile (the hub's plan,
44
+ * cached 30 s, the tool's declared values for the tier): a refusal is
45
+ * 403 `plan_required` + `details: { key, plan, requiredPlan, upgradeUrl }`,
46
+ * the link being the hub's upgrade page for the organization with
47
+ * `key` and `requiredPlan` appended (the hub root on an older hub);
48
+ * 3. the deferred body refusal (below);
49
+ * 4. a route that declares a limit refuses a POST, PUT or PATCH that does
50
+ * not declare its size: 411 `length_required` (PRDCT-2652, below);
51
+ * 5. the credit check at the hub (`cloud.credits`, `POST /usage/check`,
52
+ * the price of THIS quantity against the organization's balance):
53
+ * 402 `entitlement_denied` + `details: { credits, balance, topUpUrl }`
54
+ * when the balance is short, 413 `entitlement_denied` with the price
55
+ * and the balance but no top-up link for a quantity the hub cannot
56
+ * price (`unpriceable`: past `Number.MAX_SAFE_INTEGER` credits, beside
57
+ * the instance-limit 413; PRDCT-2677), 403 `account_suspended` for a suspended
58
+ * organization, 403 `hub_unavailable` once an outage has outlasted the
59
+ * fail-open window (the live gate's own codes and wording). The LOCAL
60
+ * `entitlements.check` is NOT asked here: the plan limit is the cap of
61
+ * a metered account (PRDCT-2653: the local service compared every
62
+ * upload against the instance cap, so a pro account above the free
63
+ * value met 413 with the oss message);
64
+ * 6. after a 2xx, one usage event, enriched with the user, the `via` and
65
+ * the resource the handler recorded for its audit row. The event never
66
+ * names the tool: the hub takes the tool from the machine token, the
67
+ * only authority on its name (PRDCT-2629). The hub debits at ingest.
68
+ *
69
+ * oss, and a cloud-LOCAL workspace (no account): no plan exists. The local
70
+ * credit check runs first (today's refusal, byte for byte), then a declared
71
+ * limit is compared against its `oss` value (the operator's env knob; null
72
+ * = unlimited). No 411, no hub call, no event: a self-hosted instance is
73
+ * unmetered by construction (§13).
74
+ *
75
+ * Why 411 on a metered upload with no Content-Length (PRDCT-2652): a limit
76
+ * is judged on the size the request DECLARES before the handler, so a body
77
+ * with no declared size read as 0 bytes and passed the plan limit whatever
78
+ * its size. Refusing before anything is stored is chosen over storing and
79
+ * rolling back: a stored blob is content-addressed and may already be
80
+ * referenced by another deck. Every first-party client declares it (Node's
81
+ * fetch and the browser do for a buffer, a blob or a form; the in-process
82
+ * MCP calls encode the form first).
83
+ *
84
+ * The idempotency middleware sits before this one, so a replayed request is
85
+ * never metered twice; the audit middleware wraps it, so what the handler
86
+ * records for its audit row is what the emit reads.
87
+ *
88
+ * A body-size refusal the size cap deferred (`bodyRefusal`, PRDCT-2632: a
89
+ * declared Content-Length over the tool's cap on a route that declares a
90
+ * limit) fires HERE: after the plan check on a metered account, so the plan
91
+ * refusal with its upgrade link is what an oversize upload meets and the
92
+ * instance cap answers only when the plan allows the size; before anything
93
+ * else when no plan applies (no principal, oss, a cloud-local workspace), so
94
+ * a resolved request no plan applies to answers byte for byte as before.
95
+ * What changed for such a request: the credential resolves and the scope
96
+ * gate answers before the cap does (a key without the scope meets 403
97
+ * insufficient_scope where the cap's 413 came first), and the refusal
98
+ * costs a lookup and a quota token (verifier round 1, accepted).
99
+ */
100
+ /** The cloud edition's half of the gate: the plan source, the upgrade link, the credit check, the hub-subject resolver. */
101
+ export interface EntitlementCloud {
102
+ profiles: EntitlementProfiles;
103
+ /** The fallback upgrade link (the hub root), when the profile carries no upgrade page of the hub's. */
104
+ upgradeUrl: string;
105
+ /** The hub's credit check (`POST /usage/check`), cached per account per action. */
106
+ credits: CreditCheck;
107
+ hubSubject: (userId: string) => Promise<string | null>;
108
+ }
109
+
110
+ export interface EntitlementGateDeps {
111
+ declarations: RouteEntitlementDeclarations;
112
+ tool: ToolEntitlements;
113
+ entitlements: EntitlementService;
114
+ usage: UsageSink;
115
+ /**
116
+ * The cloud edition's plan source, upgrade link and hub-subject resolver;
117
+ * undefined on oss. `hubSubject` maps the tool's local user id to the hub
118
+ * user (the SSO `sub` on the account row), which is what the event reports
119
+ * (the hub's `usageEventSchema`); a user with no hub link reports null.
120
+ */
121
+ cloud: EntitlementCloud | undefined;
122
+ /** The `source` of every event (the instance id is read lazily, cached by the caller). */
123
+ source: () => Promise<UsageEvent['source']>;
124
+ logger: Logger;
125
+ }
126
+
127
+ const err = (code: string, message: string, details?: unknown) => ({
128
+ error: { code, message, ...(details !== undefined ? { details } : {}) }
129
+ });
130
+
131
+ /**
132
+ * The wire code of a metered upload that does not declare its size
133
+ * (PRDCT-2652). A literal here, not a contract export: only this gate
134
+ * answers it.
135
+ */
136
+ const LENGTH_REQUIRED = 'length_required';
137
+
138
+ /**
139
+ * The live gate's wording for the two hub postures (`identity/hub-live-gate.ts`),
140
+ * reused verbatim so a person meets the same sentence whichever gate refused.
141
+ */
142
+ const SUSPENDED_MESSAGE = 'This organization is suspended on Antasphere';
143
+ const UNAVAILABLE_MESSAGE =
144
+ 'The Antasphere hub has been unreachable for too long; requests are refused until it recovers';
145
+ /** What a viewer on an anonymous surface reads when the owner's account cannot pay (PRDCT-2634): no figure, no link. */
146
+ const ANONYMOUS_DENIED_MESSAGE = 'The owner of this content cannot take this action right now';
147
+
148
+ /** The methods that carry a body the limit judges. */
149
+ const BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']);
150
+
151
+ /** Whether the request declares its body size (a Content-Length of digits, `0` included). */
152
+ function declaresLength(ctx: EntitlementRequest): boolean {
153
+ const raw = ctx.headers.get('content-length');
154
+ return raw !== null && /^\s*\d+\s*$/.test(raw);
155
+ }
156
+
157
+ /** `{id}` → `:id`: the OpenAPI path as Hono registers it. */
158
+ export function honoPath(path: string): string {
159
+ return path.replaceAll(/\{([^}]+)\}/g, ':$1');
160
+ }
161
+
162
+ /**
163
+ * The gate handlers of the routes that declare a PLAN LIMIT: the size cap
164
+ * (api/create-api.ts) defers a declared-oversize refusal exactly when one of
165
+ * these is on the request's matched route, so the deferral is keyed on the
166
+ * gate itself, never on a re-reading of the path (PRDCT-2632).
167
+ */
168
+ const deferringGates = new WeakSet<MiddlewareHandler>();
169
+
170
+ /**
171
+ * Whether a matched handler is a gate that judges a plan limit before the
172
+ * size cap may refuse. Hono's `route()` wraps a sub-app's handlers when that
173
+ * sub-app has its own `onError` (`COMPOSED_HANDLER` points at the original),
174
+ * so the wrapper is unwrapped first: otherwise an `api.onError` added one day
175
+ * would switch every deferral off (verifier round 2).
176
+ */
177
+ export function isDeferringGate(handler: unknown): boolean {
178
+ const target = (handler as { [COMPOSED_HANDLER]?: unknown } | null)?.[COMPOSED_HANDLER] ?? handler;
179
+ return typeof target === 'function' && deferringGates.has(target as MiddlewareHandler);
180
+ }
181
+
182
+ /** Register the gate on every declared route, at the caller's place in the chain. */
183
+ export function registerEntitlementGate(api: OpenAPIHono, deps: EntitlementGateDeps): void {
184
+ for (const entry of deps.declarations.values()) {
185
+ const gate = entitlementGate(entry, deps);
186
+ // Only a limit that JUDGES THE SIZE defers the cap's refusal (the plan
187
+ // must see the declared size first, PRDCT-2632); a count limit never
188
+ // judges the size, so the cap answers at once there and no lookup runs
189
+ // on a body the cap has dropped (the code review of PRDCT-2702).
190
+ if (entry.limit?.value === declaredContentLength) deferringGates.add(gate);
191
+ api.on(entry.route.method.toUpperCase(), honoPath(entry.route.path), gate);
192
+ }
193
+ }
194
+
195
+ function requestOf(c: Context, principal: Principal | null): EntitlementRequest {
196
+ // The body, parsed once and shared with the handler's validator (Hono
197
+ // caches the parse on the request): a count limit or a conditional
198
+ // feature reads what the request asks for (PRDCT-2702). A body that is
199
+ // not JSON, or none at all, reads as undefined; the route's own validator
200
+ // answers 400 for it, the gate never does.
201
+ let body: Promise<unknown> | undefined;
202
+ return {
203
+ method: c.req.method,
204
+ path: c.req.path,
205
+ headers: c.req.raw.headers,
206
+ params: c.req.param() as Record<string, string>,
207
+ principal,
208
+ body: () => (body ??= c.req.json().catch(() => undefined))
209
+ };
210
+ }
211
+
212
+ function quantityOf(meter: MeterDeclaration, ctx: EntitlementRequest): number {
213
+ if (!meter.quantity) return 1;
214
+ const q = meter.quantity(ctx);
215
+ return Number.isFinite(q) && q >= 0 ? q : 0;
216
+ }
217
+
218
+ function principalActor(principal: Principal): ActorRef {
219
+ return {
220
+ userId: principal.userId,
221
+ workspaceId: principal.workspaceId,
222
+ ...(principal.accountRef ? { accountRef: principal.accountRef } : {})
223
+ };
224
+ }
225
+
226
+ /**
227
+ * The paying actor of one request. A route that declares an `actor` hook is
228
+ * a SURFACE ATTRIBUTED TO A RESOURCE'S OWNER (a form response through a share
229
+ * link, PRDCT-2634): the hook, and only the hook, names who pays, whether or
230
+ * not a principal happens to be signed in (a person of any workspace may hold
231
+ * a share link; they are a viewer here, never the payer). A hook that resolves
232
+ * nothing, or throws, leaves the route to its own handling with nothing
233
+ * metered, rather than refusing on a lookup error or billing the viewer. A
234
+ * route without a hook meters its principal, or nothing without one.
235
+ */
236
+ async function actorOf(
237
+ entry: RouteEntitlementEntry,
238
+ ctx: EntitlementRequest,
239
+ principal: Principal | null,
240
+ logger: Logger
241
+ ): Promise<ActorRef | null> {
242
+ const hook = actorHookOf(entry);
243
+ if (hook) {
244
+ try {
245
+ return (await hook(ctx)) ?? null;
246
+ } catch (cause: unknown) {
247
+ // A lookup that fails is the route's to answer, never a refusal; but a
248
+ // cap that stops biting must be visible to an operator.
249
+ logger.warn(
250
+ { err: cause, route: routeEntitlementKey(entry.route) },
251
+ 'entitlements: actor hook threw — nothing judged, nothing metered'
252
+ );
253
+ return null;
254
+ }
255
+ }
256
+ return principal ? principalActor(principal) : null;
257
+ }
258
+
259
+ /**
260
+ * Whether the request is a viewer's on an owner-attributed surface: the
261
+ * route declares an actor hook, on its meter or on the entry itself (a
262
+ * public door that checks a limit and meters nothing, PRDCT-2702).
263
+ */
264
+ function viewerSurface(entry: RouteEntitlementEntry): boolean {
265
+ return actorHookOf(entry) !== null;
266
+ }
267
+
268
+ /**
269
+ * The value a limit observes for this request, or null when the hook says
270
+ * there is nothing to judge (PRDCT-2702) or throws: a lookup error must
271
+ * never become a plan refusal, so the route answers on its own then.
272
+ */
273
+ async function observedOf(
274
+ entry: RouteEntitlementEntry,
275
+ ctx: EntitlementRequest,
276
+ logger: Logger
277
+ ): Promise<number | null> {
278
+ try {
279
+ const observed = await entry.limit!.value(ctx);
280
+ return typeof observed === 'number' && Number.isFinite(observed) ? observed : null;
281
+ } catch (cause: unknown) {
282
+ logger.warn(
283
+ { err: cause, route: routeEntitlementKey(entry.route), key: entry.limit!.key },
284
+ 'entitlements: limit hook threw — the limit was not judged on this request'
285
+ );
286
+ return null;
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Whether the route's feature applies to THIS request: a plain key applies
292
+ * always, a conditional one only when its `when` says so (a thing created
293
+ * with the premium option needs the feature, one created without does not).
294
+ * A condition that throws does not apply: the route answers on its own.
295
+ */
296
+ async function featureApplies(
297
+ entry: RouteEntitlementEntry,
298
+ ctx: EntitlementRequest,
299
+ logger: Logger
300
+ ): Promise<boolean> {
301
+ if (!entry.feature) return false;
302
+ if (typeof entry.feature === 'string' || !entry.feature.when) return true;
303
+ try {
304
+ return await entry.feature.when(ctx);
305
+ } catch (cause: unknown) {
306
+ logger.warn(
307
+ { err: cause, route: routeEntitlementKey(entry.route), key: featureKeyOf(entry) },
308
+ 'entitlements: feature condition threw — the feature was not judged on this request'
309
+ );
310
+ return false;
311
+ }
312
+ }
313
+
314
+ /** The first tier above the account's that allows the value, or null when none does. */
315
+ function requiredPlanFor(
316
+ tool: ToolEntitlements,
317
+ plan: EntitlementTier,
318
+ judge: (tier: EntitlementTier) => boolean
319
+ ): EntitlementTier | null {
320
+ const from = ENTITLEMENT_TIERS.indexOf(plan) + 1;
321
+ for (const tier of ENTITLEMENT_TIERS.slice(from)) {
322
+ if (judge(tier)) return tier;
323
+ }
324
+ return null;
325
+ }
326
+
327
+ function planRefusal(c: Context, message: string, details: PlanRequiredDetails): Response {
328
+ return c.json(err(PLAN_REQUIRED, message, details), 403);
329
+ }
330
+
331
+ /**
332
+ * The upgrade link of a plan refusal: the hub's upgrade page for the
333
+ * organization (it carries `org`, `tool` and `plan`) with the refused `key`
334
+ * and the `requiredPlan` appended; the hub root when the profile has no page
335
+ * of the hub's (an older hub, or the default plan).
336
+ */
337
+ function upgradeLink(
338
+ profile: ResolvedProfile,
339
+ key: string,
340
+ requiredPlan: EntitlementTier | null,
341
+ fallback: string
342
+ ): string {
343
+ if (!profile.upgradeUrl) return fallback;
344
+ let url: URL;
345
+ try {
346
+ url = new URL(profile.upgradeUrl);
347
+ } catch {
348
+ return fallback;
349
+ }
350
+ url.searchParams.set('key', key);
351
+ if (requiredPlan !== null) url.searchParams.set('requiredPlan', requiredPlan);
352
+ return url.toString();
353
+ }
354
+
355
+ /**
356
+ * The plan checks (feature, then limit) of one request on the cloud edition.
357
+ * Returns the refusal, or null when the plan allows the action.
358
+ *
359
+ * A limit is judged on what the request DECLARES before the handler (an
360
+ * upload's Content-Length). A metered request that declares none is refused
361
+ * 411 by the gate right after (PRDCT-2652), so a size can no longer pass the
362
+ * plan's value by staying silent; the instance's hard caps (the body limits
363
+ * and the services' mid-stream ceilings) still bound a body that lies.
364
+ */
365
+ function planCheck(
366
+ c: Context,
367
+ entry: RouteEntitlementEntry,
368
+ profile: ResolvedProfile,
369
+ deps: EntitlementGateDeps,
370
+ /** Whether the route's feature applies to this request (judged before the plan was read). */
371
+ feature: boolean,
372
+ /** The value the route's limit observed for this request (read once, before the plan). */
373
+ observed: number | null,
374
+ /** No principal: a viewer, who must learn nothing of the owner's plan or upgrade page. */
375
+ anonymous = false
376
+ ): Response | null {
377
+ const refusal = planCheckDetailed(c, entry, profile, deps, feature, observed);
378
+ if (refusal && anonymous) return c.json(err(PLAN_REQUIRED, ANONYMOUS_DENIED_MESSAGE), 403);
379
+ return refusal;
380
+ }
381
+
382
+ function planCheckDetailed(
383
+ c: Context,
384
+ entry: RouteEntitlementEntry,
385
+ profile: ResolvedProfile,
386
+ deps: EntitlementGateDeps,
387
+ feature: boolean,
388
+ observed: number | null
389
+ ): Response | null {
390
+ const featureKey = featureKeyOf(entry);
391
+ if (featureKey && feature) {
392
+ const key = featureKey;
393
+ if (!profile.features.has(key)) {
394
+ const requiredPlan = requiredPlanFor(
395
+ deps.tool,
396
+ profile.plan,
397
+ (tier) => deps.tool.features[key]?.[tier] === true
398
+ );
399
+ return planRefusal(
400
+ c,
401
+ requiredPlan
402
+ ? `This needs the ${requiredPlan} plan (${key} is not part of the ${profile.plan} plan)`
403
+ : `${key} is not available on any plan`,
404
+ {
405
+ key,
406
+ plan: profile.plan,
407
+ requiredPlan,
408
+ upgradeUrl: upgradeLink(profile, key, requiredPlan, deps.cloud!.upgradeUrl)
409
+ }
410
+ );
411
+ }
412
+ }
413
+ if (entry.limit) {
414
+ const { key } = entry.limit;
415
+ const max = profile.limits[key];
416
+ if (max !== null && max !== undefined && observed !== null && observed > max) {
417
+ const requiredPlan = requiredPlanFor(deps.tool, profile.plan, (tier) => {
418
+ const value = deps.tool.limits[key]?.[tier];
419
+ return value === null || (typeof value === 'number' && observed <= value);
420
+ });
421
+ return planRefusal(
422
+ c,
423
+ requiredPlan
424
+ ? `${key} is limited to ${max} on the ${profile.plan} plan; the ${requiredPlan} plan allows it`
425
+ : `${key} is limited to ${max} on the ${profile.plan} plan`,
426
+ {
427
+ key,
428
+ plan: profile.plan,
429
+ requiredPlan,
430
+ upgradeUrl: upgradeLink(profile, key, requiredPlan, deps.cloud!.upgradeUrl)
431
+ }
432
+ );
433
+ }
434
+ }
435
+ return null;
436
+ }
437
+
438
+ /** The oss half of a limit: the operator's own knob, refused the way the credit check refuses. */
439
+ async function ossLimitCheck(
440
+ c: Context,
441
+ entry: RouteEntitlementEntry,
442
+ ctx: EntitlementRequest,
443
+ tool: ToolEntitlements,
444
+ logger: Logger
445
+ ): Promise<Response | null> {
446
+ if (!entry.limit) return null;
447
+ const { key } = entry.limit;
448
+ const max = tool.limits[key]?.oss;
449
+ // The value is read only when an oss ceiling exists: a null ceiling (the
450
+ // operator's unlimited) costs no lookup for a count hook.
451
+ if (max === null || max === undefined) return null;
452
+ const observed = await observedOf(entry, ctx, logger);
453
+ if (observed !== null && observed > max) {
454
+ return c.json(err('entitlement_denied', `${key}: ${observed} exceeds the instance limit (${max})`), 413);
455
+ }
456
+ return null;
457
+ }
458
+
459
+ /** The hub's credit check of a metered request; the refusal, or null when the action may run. */
460
+ async function creditRefusal(
461
+ c: Context,
462
+ meter: MeterDeclaration,
463
+ ctx: EntitlementRequest,
464
+ actor: ActorRef,
465
+ cloud: EntitlementCloud,
466
+ /** No principal: a viewer on an anonymous surface, who must learn nothing about the owner's account. */
467
+ anonymous: boolean
468
+ ): Promise<Response | null> {
469
+ const verdict = await cloud.credits.check({
470
+ accountRef: actor.accountRef!,
471
+ actionKey: meter.key,
472
+ quantity: quantityOf(meter, ctx),
473
+ unit: meter.unit
474
+ });
475
+ if (verdict.allowed) return null;
476
+ if (anonymous) {
477
+ // The owner's balance, price, top-up page, and even whether the
478
+ // organization is suspended or the hub is down, are the owner's: a
479
+ // viewer gets one code and one neutral sentence, whatever the reason
480
+ // (PRDCT-2634; verifier round 3 on the suspended and unavailable answers).
481
+ return c.json(err(ENTITLEMENT_DENIED, ANONYMOUS_DENIED_MESSAGE), 402);
482
+ }
483
+ if (verdict.reason === 'account_suspended') return c.json(err('account_suspended', SUSPENDED_MESSAGE), 403);
484
+ if (verdict.reason === 'hub_unavailable') return c.json(err('hub_unavailable', UNAVAILABLE_MESSAGE), 403);
485
+ const details: EntitlementDeniedDetails = {
486
+ credits: verdict.check?.credits ?? 0,
487
+ balance: verdict.check?.balance ?? 0,
488
+ topUpUrl: verdict.check?.topUpUrl ?? cloud.upgradeUrl
489
+ };
490
+ // A price past `Number.MAX_SAFE_INTEGER` (the hub clamps `credits` to it):
491
+ // a refusal of the quantity, never an outage (PRDCT-2677). The
492
+ // instance-limit 413's code, with the price and the balance and NO top-up
493
+ // link: no top-up covers it, and the dashboard's and the CLI's readers
494
+ // present a top-up card on any `entitlement_denied` whose details carry
495
+ // `topUpUrl` (verifier round 1).
496
+ if (verdict.reason === 'unpriceable') {
497
+ const figures: Pick<EntitlementDeniedDetails, 'credits' | 'balance'> = {
498
+ credits: details.credits,
499
+ balance: details.balance
500
+ };
501
+ return c.json(
502
+ err(
503
+ ENTITLEMENT_DENIED,
504
+ `This quantity cannot be priced (${figures.credits} credits or more, the organization holds ${figures.balance}); nothing was charged`,
505
+ figures
506
+ ),
507
+ 413
508
+ );
509
+ }
510
+ return c.json(
511
+ err(
512
+ ENTITLEMENT_DENIED,
513
+ `This needs ${details.credits} credits and the organization holds ${details.balance}; top up at ${details.topUpUrl}`,
514
+ details
515
+ ),
516
+ 402
517
+ );
518
+ }
519
+
520
+ export function entitlementGate(entry: RouteEntitlementEntry, deps: EntitlementGateDeps): MiddlewareHandler {
521
+ const meterKey = entry.meter ? routeEntitlementKey(entry.route) : null;
522
+ return async (c, next) => {
523
+ const principal: Principal | null = c.get('principal') ?? null;
524
+ const deferredRefusal = pendingBodyRefusal(c);
525
+ const ctx = requestOf(c, principal);
526
+ // The actor: the principal, or, on an ANONYMOUS surface (a form response
527
+ // through a share link, PRDCT-2634), whoever the route's `actor` hook
528
+ // resolves with no principal at all (the share secret → the deck → its
529
+ // owner → the owner's workspace and account: the owner pays and is
530
+ // reported, the viewer is never identified, §8). No principal and no
531
+ // actor: the route's own auth answers (401/404); nothing to meter, and a
532
+ // deferred size refusal still fires first, as the cap did before.
533
+ // A viewer's request (an owner-attributed surface, with or without a
534
+ // signed-in person) learns nothing of the owner's account: every refusal
535
+ // to it is one neutral sentence (verifier round 3, the code review: a
536
+ // signed-in holder of a share link is a viewer too). On oss the hook is
537
+ // not run: no account can exist, the lookups would cost the database.
538
+ const viewer = viewerSurface(entry);
539
+ const actor = viewer && !deps.cloud ? null : await actorOf(entry, ctx, principal, deps.logger);
540
+ if (!actor) return deferredRefusal ? deferredRefusal() : next();
541
+ const metered = Boolean(deps.cloud && actor.accountRef);
542
+ // A viewer's surface whose owner has no account (a cloud-local
543
+ // workspace) has no plan and no credits: byte for byte as it was.
544
+ if (viewer && !metered) return deferredRefusal ? deferredRefusal() : next();
545
+
546
+ // The plan is read only when the route declares a limit, or a feature
547
+ // that APPLIES to this request: a meter-only route has nothing to judge
548
+ // against it (verifier round 3), and a conditional feature whose
549
+ // condition does not hold costs no hub read (the code review of
550
+ // PRDCT-2702: a link renamed must not wait on a cold plan).
551
+ // A count limit's hook that answers null (or throws) has nothing to judge:
552
+ // the caller is one the handler refuses on its own (a guest at a door
553
+ // closed to guests, a resource the caller cannot see), or the lookup
554
+ // failed. The gate's judgement of that request ends there, on the plan,
555
+ // the feature and the price alike, and nothing is metered: the hub is
556
+ // never asked about an act the handler will refuse, so no refusal can
557
+ // show that caller the organization's plan or balance (the tool
558
+ // template's feedback of 29 September 2026). The hook runs once, before
559
+ // the plan is read. A size limit never answers null (the declared length
560
+ // reads 0 when absent, and the 411 below refuses it).
561
+ const observed = metered && entry.limit ? await observedOf(entry, ctx, deps.logger) : null;
562
+ if (metered && entry.limit && observed === null) return deferredRefusal ? deferredRefusal() : next();
563
+ const feature = metered && entry.feature ? await featureApplies(entry, ctx, deps.logger) : false;
564
+ if (metered && (feature || entry.limit)) {
565
+ const profile = await deps.cloud!.profiles.get(actor.accountRef!, deps.tool).catch((cause: unknown) => {
566
+ deps.logger.warn({ err: cause }, 'entitlements: profile read threw — the free tier applies');
567
+ return defaultProfile(deps.tool);
568
+ });
569
+ const refused = planCheck(c, entry, profile, deps, feature, observed, viewer);
570
+ if (refused) return refused;
571
+ }
572
+ // The size cap's deferred refusal: the plan has had its say (or none
573
+ // applies), the instance's hard ceiling answers now.
574
+ if (deferredRefusal) return deferredRefusal();
575
+ if (metered) {
576
+ // A body with no declared size would pass the plan limit as 0 bytes
577
+ // (PRDCT-2652): refused before anything is stored.
578
+ // A route metered in BYTES needs the size too: with no header the check
579
+ // would price 0 bytes and the emit the stored size, so an anonymous
580
+ // upload door could take an owner's balance below zero (verifier round 3).
581
+ // A limit JUDGES the body when its value is the declared length (the
582
+ // contract's own reader), not any limit (a count limit on a JSON route
583
+ // must not refuse a chunked body: the code review).
584
+ const judgesSize = entry.limit?.value === declaredContentLength || entry.meter?.unit === 'bytes';
585
+ if (judgesSize && BODY_METHODS.has(c.req.method.toUpperCase()) && !declaresLength(ctx)) {
586
+ return c.json(err(LENGTH_REQUIRED, 'A metered upload must declare its size (Content-Length)'), 411);
587
+ }
588
+ if (entry.meter) {
589
+ const refused = await creditRefusal(c, entry.meter, ctx, actor, deps.cloud!, viewer);
590
+ if (refused) return refused;
591
+ }
592
+ } else {
593
+ if (entry.meter) {
594
+ // A principal is certain here: a viewer's surface returned above, and a
595
+ // route without a hook resolved no actor without a principal.
596
+ const decision = await deps.entitlements.check(
597
+ principal!,
598
+ { key: entry.meter.key, quantity: quantityOf(entry.meter, ctx), unit: entry.meter.unit },
599
+ { route: meterKey }
600
+ );
601
+ if (!decision.allowed) {
602
+ return c.json(err('entitlement_denied', decision.reason), 413);
603
+ }
604
+ }
605
+ const refused = await ossLimitCheck(c, entry, ctx, deps.tool, deps.logger);
606
+ if (refused) return refused;
607
+ }
608
+
609
+ await next();
610
+
611
+ if (!metered || !entry.meter || c.res.status < 200 || c.res.status >= 300) return;
612
+ const after: EntitlementRequest = { ...ctx, audit: c.get('audit') };
613
+ const audit = after.audit;
614
+ const hubUser = actor.userId ? await deps.cloud!.hubSubject(actor.userId).catch(() => null) : null;
615
+ const event: UsageEvent = {
616
+ id: ulid(),
617
+ meter: entry.meter.key,
618
+ actionKey: entry.meter.key,
619
+ quantity: quantityOf(entry.meter, after),
620
+ unit: entry.meter.unit,
621
+ occurredAt: new Date().toISOString(),
622
+ workspaceId: actor.workspaceId,
623
+ accountRef: actor.accountRef!,
624
+ userId: hubUser,
625
+ // The hub's channel enum has no value for a share link: an anonymous
626
+ // surface reports the owner as if from a browser session (PRDCT-2634;
627
+ // a dedicated value is the hub's contract to add).
628
+ via: viewer ? 'session' : principal!.via,
629
+ ...(audit?.resourceType ? { resourceType: audit.resourceType } : {}),
630
+ ...(audit?.resourceId ? { resourceId: audit.resourceId } : {}),
631
+ source: await deps.source()
632
+ };
633
+ // Fire and forget: the queue write is durable and the request never waits on billing.
634
+ void deps.usage.emit(event);
635
+ };
636
+ }