@fuzdev/fuz_app 0.55.0 → 0.57.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 (333) hide show
  1. package/dist/actions/CLAUDE.md +211 -155
  2. package/dist/actions/action_bridge.d.ts +8 -5
  3. package/dist/actions/action_bridge.d.ts.map +1 -1
  4. package/dist/actions/action_bridge.js +1 -11
  5. package/dist/actions/action_codegen.d.ts +19 -0
  6. package/dist/actions/action_codegen.d.ts.map +1 -1
  7. package/dist/actions/action_codegen.js +20 -14
  8. package/dist/actions/action_registry.d.ts.map +1 -1
  9. package/dist/actions/action_registry.js +5 -2
  10. package/dist/actions/action_rpc.d.ts +110 -44
  11. package/dist/actions/action_rpc.d.ts.map +1 -1
  12. package/dist/actions/action_rpc.js +92 -287
  13. package/dist/actions/action_spec.d.ts +55 -16
  14. package/dist/actions/action_spec.d.ts.map +1 -1
  15. package/dist/actions/action_spec.js +16 -11
  16. package/dist/actions/action_types.d.ts +28 -60
  17. package/dist/actions/action_types.d.ts.map +1 -1
  18. package/dist/actions/action_types.js +13 -5
  19. package/dist/actions/broadcast_api.d.ts +2 -2
  20. package/dist/actions/broadcast_api.js +2 -2
  21. package/dist/actions/compile_action_registry.d.ts +50 -0
  22. package/dist/actions/compile_action_registry.d.ts.map +1 -0
  23. package/dist/actions/compile_action_registry.js +69 -0
  24. package/dist/actions/heartbeat.d.ts +8 -4
  25. package/dist/actions/heartbeat.d.ts.map +1 -1
  26. package/dist/actions/heartbeat.js +5 -4
  27. package/dist/actions/perform_action.d.ts +145 -0
  28. package/dist/actions/perform_action.d.ts.map +1 -0
  29. package/dist/actions/perform_action.js +258 -0
  30. package/dist/actions/register_action_ws.d.ts +44 -38
  31. package/dist/actions/register_action_ws.d.ts.map +1 -1
  32. package/dist/actions/register_action_ws.js +101 -159
  33. package/dist/actions/register_ws_endpoint.d.ts +2 -10
  34. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  35. package/dist/actions/register_ws_endpoint.js +32 -10
  36. package/dist/actions/transports_ws_auth_guard.d.ts +1 -1
  37. package/dist/actions/transports_ws_auth_guard.js +1 -1
  38. package/dist/actions/transports_ws_backend.d.ts +1 -1
  39. package/dist/actions/transports_ws_backend.js +1 -1
  40. package/dist/auth/CLAUDE.md +673 -442
  41. package/dist/auth/account_action_specs.d.ts +28 -7
  42. package/dist/auth/account_action_specs.d.ts.map +1 -1
  43. package/dist/auth/account_action_specs.js +7 -7
  44. package/dist/auth/account_actions.d.ts +8 -14
  45. package/dist/auth/account_actions.d.ts.map +1 -1
  46. package/dist/auth/account_actions.js +26 -32
  47. package/dist/auth/account_queries.d.ts +46 -13
  48. package/dist/auth/account_queries.d.ts.map +1 -1
  49. package/dist/auth/account_queries.js +73 -33
  50. package/dist/auth/account_routes.d.ts +4 -3
  51. package/dist/auth/account_routes.d.ts.map +1 -1
  52. package/dist/auth/account_routes.js +58 -33
  53. package/dist/auth/account_schema.d.ts +46 -54
  54. package/dist/auth/account_schema.d.ts.map +1 -1
  55. package/dist/auth/account_schema.js +21 -48
  56. package/dist/auth/admin_action_specs.d.ts +55 -21
  57. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  58. package/dist/auth/admin_action_specs.js +42 -26
  59. package/dist/auth/admin_actions.d.ts +14 -21
  60. package/dist/auth/admin_actions.d.ts.map +1 -1
  61. package/dist/auth/admin_actions.js +47 -44
  62. package/dist/auth/audit_emitter.d.ts +160 -0
  63. package/dist/auth/audit_emitter.d.ts.map +1 -0
  64. package/dist/auth/audit_emitter.js +83 -0
  65. package/dist/auth/audit_log_queries.d.ts +17 -87
  66. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  67. package/dist/auth/audit_log_queries.js +17 -96
  68. package/dist/auth/audit_log_routes.d.ts +1 -1
  69. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  70. package/dist/auth/audit_log_routes.js +7 -3
  71. package/dist/auth/audit_log_schema.d.ts +48 -42
  72. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  73. package/dist/auth/audit_log_schema.js +56 -43
  74. package/dist/auth/auth_guard_resolver.d.ts +44 -0
  75. package/dist/auth/auth_guard_resolver.d.ts.map +1 -0
  76. package/dist/auth/auth_guard_resolver.js +56 -0
  77. package/dist/auth/bootstrap_account.d.ts +7 -7
  78. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  79. package/dist/auth/bootstrap_account.js +7 -7
  80. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  81. package/dist/auth/bootstrap_routes.js +11 -10
  82. package/dist/auth/cleanup.d.ts +20 -26
  83. package/dist/auth/cleanup.d.ts.map +1 -1
  84. package/dist/auth/cleanup.js +33 -47
  85. package/dist/auth/credential_type_schema.d.ts +115 -0
  86. package/dist/auth/credential_type_schema.d.ts.map +1 -0
  87. package/dist/auth/credential_type_schema.js +127 -0
  88. package/dist/auth/daemon_token_middleware.d.ts +1 -1
  89. package/dist/auth/daemon_token_middleware.js +3 -3
  90. package/dist/auth/ddl.d.ts +2 -2
  91. package/dist/auth/ddl.d.ts.map +1 -1
  92. package/dist/auth/ddl.js +6 -6
  93. package/dist/auth/deps.d.ts +7 -32
  94. package/dist/auth/deps.d.ts.map +1 -1
  95. package/dist/auth/grant_path_schema.d.ts +117 -0
  96. package/dist/auth/grant_path_schema.d.ts.map +1 -0
  97. package/dist/auth/grant_path_schema.js +137 -0
  98. package/dist/auth/invite_queries.d.ts +12 -1
  99. package/dist/auth/invite_queries.d.ts.map +1 -1
  100. package/dist/auth/invite_queries.js +12 -1
  101. package/dist/auth/invite_schema.d.ts +1 -1
  102. package/dist/auth/invite_schema.d.ts.map +1 -1
  103. package/dist/auth/invite_schema.js +1 -1
  104. package/dist/auth/middleware.d.ts.map +1 -1
  105. package/dist/auth/middleware.js +5 -2
  106. package/dist/auth/migrations.d.ts +22 -7
  107. package/dist/auth/migrations.d.ts.map +1 -1
  108. package/dist/auth/migrations.js +64 -25
  109. package/dist/auth/request_context.d.ts +157 -170
  110. package/dist/auth/request_context.d.ts.map +1 -1
  111. package/dist/auth/request_context.js +224 -268
  112. package/dist/auth/{permit_offer_action_specs.d.ts → role_grant_offer_action_specs.d.ts} +130 -100
  113. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -0
  114. package/dist/auth/role_grant_offer_action_specs.js +262 -0
  115. package/dist/auth/role_grant_offer_actions.d.ts +104 -0
  116. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -0
  117. package/dist/auth/{permit_offer_actions.js → role_grant_offer_actions.js} +153 -140
  118. package/dist/auth/{permit_offer_notifications.d.ts → role_grant_offer_notifications.d.ts} +80 -70
  119. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -0
  120. package/dist/auth/role_grant_offer_notifications.js +182 -0
  121. package/dist/auth/{permit_offer_queries.d.ts → role_grant_offer_queries.d.ts} +64 -64
  122. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -0
  123. package/dist/auth/{permit_offer_queries.js → role_grant_offer_queries.js} +136 -123
  124. package/dist/auth/role_grant_offer_schema.d.ts +150 -0
  125. package/dist/auth/role_grant_offer_schema.d.ts.map +1 -0
  126. package/dist/auth/{permit_offer_schema.js → role_grant_offer_schema.js} +55 -36
  127. package/dist/auth/role_grant_queries.d.ts +231 -0
  128. package/dist/auth/role_grant_queries.d.ts.map +1 -0
  129. package/dist/auth/role_grant_queries.js +320 -0
  130. package/dist/auth/role_schema.d.ts +150 -40
  131. package/dist/auth/role_schema.d.ts.map +1 -1
  132. package/dist/auth/role_schema.js +144 -45
  133. package/dist/auth/scope_kind_schema.d.ts +96 -0
  134. package/dist/auth/scope_kind_schema.d.ts.map +1 -0
  135. package/dist/auth/scope_kind_schema.js +94 -0
  136. package/dist/auth/self_service_role_action_specs.d.ts +4 -1
  137. package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
  138. package/dist/auth/self_service_role_action_specs.js +2 -2
  139. package/dist/auth/self_service_role_actions.d.ts +35 -29
  140. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  141. package/dist/auth/self_service_role_actions.js +58 -48
  142. package/dist/auth/session_cookie.d.ts +43 -6
  143. package/dist/auth/session_cookie.d.ts.map +1 -1
  144. package/dist/auth/session_cookie.js +31 -5
  145. package/dist/auth/session_middleware.d.ts +37 -3
  146. package/dist/auth/session_middleware.d.ts.map +1 -1
  147. package/dist/auth/session_middleware.js +33 -7
  148. package/dist/auth/signup_routes.d.ts.map +1 -1
  149. package/dist/auth/signup_routes.js +48 -19
  150. package/dist/auth/standard_action_specs.d.ts +2 -2
  151. package/dist/auth/standard_action_specs.js +4 -4
  152. package/dist/auth/standard_rpc_actions.d.ts +23 -19
  153. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  154. package/dist/auth/standard_rpc_actions.js +12 -12
  155. package/dist/db/migrate.d.ts +1 -1
  156. package/dist/db/migrate.js +1 -1
  157. package/dist/dev/setup.d.ts +2 -2
  158. package/dist/dev/setup.d.ts.map +1 -1
  159. package/dist/dev/setup.js +4 -4
  160. package/dist/env/load.d.ts +1 -1
  161. package/dist/env/load.js +1 -1
  162. package/dist/hono_context.d.ts +27 -45
  163. package/dist/hono_context.d.ts.map +1 -1
  164. package/dist/hono_context.js +14 -28
  165. package/dist/http/CLAUDE.md +235 -121
  166. package/dist/http/auth_shape.d.ts +191 -0
  167. package/dist/http/auth_shape.d.ts.map +1 -0
  168. package/dist/http/auth_shape.js +237 -0
  169. package/dist/http/common_routes.js +3 -3
  170. package/dist/http/db_routes.d.ts +4 -0
  171. package/dist/http/db_routes.d.ts.map +1 -1
  172. package/dist/http/db_routes.js +44 -7
  173. package/dist/http/error_schemas.d.ts +72 -39
  174. package/dist/http/error_schemas.d.ts.map +1 -1
  175. package/dist/http/error_schemas.js +81 -33
  176. package/dist/http/pending_effects.d.ts +71 -18
  177. package/dist/http/pending_effects.d.ts.map +1 -1
  178. package/dist/http/pending_effects.js +87 -18
  179. package/dist/http/proxy.d.ts +52 -5
  180. package/dist/http/proxy.d.ts.map +1 -1
  181. package/dist/http/proxy.js +92 -14
  182. package/dist/http/route_spec.d.ts +89 -75
  183. package/dist/http/route_spec.d.ts.map +1 -1
  184. package/dist/http/route_spec.js +54 -72
  185. package/dist/http/schema_helpers.d.ts +3 -14
  186. package/dist/http/schema_helpers.d.ts.map +1 -1
  187. package/dist/http/schema_helpers.js +2 -14
  188. package/dist/http/surface.d.ts +2 -10
  189. package/dist/http/surface.d.ts.map +1 -1
  190. package/dist/http/surface.js +3 -4
  191. package/dist/http/surface_query.d.ts +39 -35
  192. package/dist/http/surface_query.d.ts.map +1 -1
  193. package/dist/http/surface_query.js +79 -36
  194. package/dist/primitive_schemas.d.ts +39 -0
  195. package/dist/primitive_schemas.d.ts.map +1 -0
  196. package/dist/primitive_schemas.js +40 -0
  197. package/dist/realtime/sse_auth_guard.d.ts +5 -5
  198. package/dist/realtime/sse_auth_guard.js +9 -9
  199. package/dist/runtime/mock.d.ts +1 -1
  200. package/dist/runtime/mock.js +1 -1
  201. package/dist/server/app_backend.d.ts +14 -11
  202. package/dist/server/app_backend.d.ts.map +1 -1
  203. package/dist/server/app_backend.js +12 -8
  204. package/dist/server/app_server.d.ts +7 -7
  205. package/dist/server/app_server.d.ts.map +1 -1
  206. package/dist/server/app_server.js +35 -40
  207. package/dist/server/validate_nginx.d.ts +1 -1
  208. package/dist/server/validate_nginx.js +1 -1
  209. package/dist/testing/CLAUDE.md +50 -38
  210. package/dist/testing/admin_integration.d.ts +5 -6
  211. package/dist/testing/admin_integration.d.ts.map +1 -1
  212. package/dist/testing/admin_integration.js +87 -85
  213. package/dist/testing/app_server.d.ts +11 -14
  214. package/dist/testing/app_server.d.ts.map +1 -1
  215. package/dist/testing/app_server.js +16 -15
  216. package/dist/testing/assertions.d.ts.map +1 -1
  217. package/dist/testing/assertions.js +2 -1
  218. package/dist/testing/attack_surface.d.ts.map +1 -1
  219. package/dist/testing/attack_surface.js +15 -9
  220. package/dist/testing/audit_completeness.d.ts +2 -2
  221. package/dist/testing/audit_completeness.d.ts.map +1 -1
  222. package/dist/testing/audit_completeness.js +36 -36
  223. package/dist/testing/auth_apps.d.ts +5 -4
  224. package/dist/testing/auth_apps.d.ts.map +1 -1
  225. package/dist/testing/auth_apps.js +22 -19
  226. package/dist/testing/data_exposure.d.ts.map +1 -1
  227. package/dist/testing/data_exposure.js +5 -5
  228. package/dist/testing/db.d.ts +1 -1
  229. package/dist/testing/db.d.ts.map +1 -1
  230. package/dist/testing/db.js +4 -4
  231. package/dist/testing/db_entities.d.ts +22 -0
  232. package/dist/testing/db_entities.d.ts.map +1 -0
  233. package/dist/testing/db_entities.js +28 -0
  234. package/dist/testing/entities.d.ts +8 -7
  235. package/dist/testing/entities.d.ts.map +1 -1
  236. package/dist/testing/entities.js +21 -18
  237. package/dist/testing/integration.d.ts.map +1 -1
  238. package/dist/testing/integration.js +13 -14
  239. package/dist/testing/integration_helpers.d.ts +4 -4
  240. package/dist/testing/integration_helpers.d.ts.map +1 -1
  241. package/dist/testing/integration_helpers.js +20 -18
  242. package/dist/testing/middleware.d.ts +4 -4
  243. package/dist/testing/middleware.d.ts.map +1 -1
  244. package/dist/testing/middleware.js +12 -11
  245. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  246. package/dist/testing/rpc_attack_surface.js +40 -24
  247. package/dist/testing/rpc_round_trip.d.ts +1 -1
  248. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  249. package/dist/testing/rpc_round_trip.js +14 -13
  250. package/dist/testing/sse_round_trip.d.ts +3 -4
  251. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  252. package/dist/testing/sse_round_trip.js +7 -11
  253. package/dist/testing/standard.d.ts +1 -1
  254. package/dist/testing/stubs.d.ts +25 -0
  255. package/dist/testing/stubs.d.ts.map +1 -1
  256. package/dist/testing/stubs.js +43 -2
  257. package/dist/testing/surface_invariants.d.ts +14 -6
  258. package/dist/testing/surface_invariants.d.ts.map +1 -1
  259. package/dist/testing/surface_invariants.js +119 -43
  260. package/dist/testing/ws_round_trip.d.ts +12 -13
  261. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  262. package/dist/testing/ws_round_trip.js +19 -11
  263. package/dist/ui/AdminAccounts.svelte +23 -20
  264. package/dist/ui/AdminOverview.svelte +15 -13
  265. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  266. package/dist/ui/{AdminPermitHistory.svelte → AdminRoleGrantHistory.svelte} +12 -12
  267. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts +4 -0
  268. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -0
  269. package/dist/ui/BootstrapForm.svelte +1 -1
  270. package/dist/ui/CLAUDE.md +60 -60
  271. package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +27 -26
  272. package/dist/ui/{PermitOfferForm.svelte.d.ts → RoleGrantOfferForm.svelte.d.ts} +7 -7
  273. package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -0
  274. package/dist/ui/{PermitOfferHistory.svelte → RoleGrantOfferHistory.svelte} +12 -12
  275. package/dist/ui/{PermitOfferHistory.svelte.d.ts → RoleGrantOfferHistory.svelte.d.ts} +4 -4
  276. package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -0
  277. package/dist/ui/{PermitOfferInbox.svelte → RoleGrantOfferInbox.svelte} +14 -14
  278. package/dist/ui/{PermitOfferInbox.svelte.d.ts → RoleGrantOfferInbox.svelte.d.ts} +4 -4
  279. package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -0
  280. package/dist/ui/SignupForm.svelte +1 -1
  281. package/dist/ui/SurfaceExplorer.svelte +35 -15
  282. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  283. package/dist/ui/account_sessions_state.svelte.d.ts +2 -3
  284. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  285. package/dist/ui/account_sessions_state.svelte.js +2 -3
  286. package/dist/ui/admin_accounts_state.svelte.d.ts +18 -18
  287. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  288. package/dist/ui/admin_accounts_state.svelte.js +16 -16
  289. package/dist/ui/admin_rpc_adapters.d.ts +20 -20
  290. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
  291. package/dist/ui/admin_rpc_adapters.js +17 -17
  292. package/dist/ui/admin_sessions_state.svelte.d.ts +2 -2
  293. package/dist/ui/admin_sessions_state.svelte.js +2 -2
  294. package/dist/ui/audit_log_state.svelte.d.ts +7 -7
  295. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  296. package/dist/ui/audit_log_state.svelte.js +6 -6
  297. package/dist/ui/auth_state.svelte.d.ts +3 -3
  298. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  299. package/dist/ui/auth_state.svelte.js +6 -6
  300. package/dist/ui/format_scope.d.ts +2 -2
  301. package/dist/ui/format_scope.js +2 -2
  302. package/dist/ui/{permit_offers_state.svelte.d.ts → role_grant_offers_state.svelte.d.ts} +30 -30
  303. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -0
  304. package/dist/ui/{permit_offers_state.svelte.js → role_grant_offers_state.svelte.js} +18 -18
  305. package/dist/ui/ui_format.js +2 -2
  306. package/package.json +3 -3
  307. package/dist/auth/permit_offer_action_specs.d.ts.map +0 -1
  308. package/dist/auth/permit_offer_action_specs.js +0 -258
  309. package/dist/auth/permit_offer_actions.d.ts +0 -110
  310. package/dist/auth/permit_offer_actions.d.ts.map +0 -1
  311. package/dist/auth/permit_offer_notifications.d.ts.map +0 -1
  312. package/dist/auth/permit_offer_notifications.js +0 -182
  313. package/dist/auth/permit_offer_queries.d.ts.map +0 -1
  314. package/dist/auth/permit_offer_schema.d.ts +0 -125
  315. package/dist/auth/permit_offer_schema.d.ts.map +0 -1
  316. package/dist/auth/permit_queries.d.ts +0 -222
  317. package/dist/auth/permit_queries.d.ts.map +0 -1
  318. package/dist/auth/permit_queries.js +0 -305
  319. package/dist/auth/require_keeper.d.ts +0 -20
  320. package/dist/auth/require_keeper.d.ts.map +0 -1
  321. package/dist/auth/require_keeper.js +0 -35
  322. package/dist/auth/route_guards.d.ts +0 -27
  323. package/dist/auth/route_guards.d.ts.map +0 -1
  324. package/dist/auth/route_guards.js +0 -38
  325. package/dist/auth/session_lifecycle.d.ts +0 -37
  326. package/dist/auth/session_lifecycle.d.ts.map +0 -1
  327. package/dist/auth/session_lifecycle.js +0 -29
  328. package/dist/ui/AdminPermitHistory.svelte.d.ts +0 -4
  329. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +0 -1
  330. package/dist/ui/PermitOfferForm.svelte.d.ts.map +0 -1
  331. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +0 -1
  332. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +0 -1
  333. package/dist/ui/permit_offers_state.svelte.d.ts.map +0 -1
@@ -14,6 +14,8 @@ import { prefix_route_specs } from '../http/route_spec.js';
14
14
  import { create_bootstrap_route_specs } from '../auth/bootstrap_routes.js';
15
15
  import { create_rpc_endpoint } from '../actions/action_rpc.js';
16
16
  import { create_app_surface_spec, } from '../http/surface.js';
17
+ import { AUDIT_LOG_SSE_MAX_PER_SCOPE } from '../realtime/sse_auth_guard.js';
18
+ import { SubscriberRegistry } from '../realtime/subscriber_registry.js';
17
19
  import { BaseServerEnv } from '../server/env.js';
18
20
  /* eslint-disable @typescript-eslint/require-await */
19
21
  /**
@@ -88,6 +90,45 @@ export const stub_handler = () => new Response('stub');
88
90
  /** Stub middleware that passes through. */
89
91
  export const stub_mw = async (_c, next) => next();
90
92
  const stub_db = create_noop_stub('stub_db');
93
+ /**
94
+ * Build a no-op `AuditEmitter` for tests that don't assert on audit fan-out.
95
+ *
96
+ * `emit` / `emit_role_grant_target` are no-ops; `emit_pool` resolves
97
+ * immediately; `notify` is a no-op; `on_event_chain` is a frozen empty
98
+ * array — pushing onto it throws at runtime, so a test that wires a
99
+ * listener fails loudly instead of silently never firing. Tests asserting
100
+ * on real audit-row persistence (or on listener fan-out) build a real
101
+ * emitter via `create_audit_emitter` against a stub or real DB —
102
+ * `create_test_app` already does this on the test backend.
103
+ */
104
+ export const create_test_audit_emitter = () => ({
105
+ emit: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
106
+ emit_role_grant_target: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
107
+ emit_pool: async () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
108
+ notify: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
109
+ on_event_chain: Object.freeze([]),
110
+ });
111
+ /**
112
+ * Build a no-op `AuditLogSse` for tests that wire `audit_sse` into the
113
+ * surface helper but don't assert on SSE fan-out or subscriber state.
114
+ *
115
+ * `subscribe` returns a no-op cleanup; `on_audit_event` is a no-op; the
116
+ * `registry` is a fresh `SubscriberRegistry` instance (call sites that
117
+ * inspect `.size` or call `.close_*` see a real registry, so writes are
118
+ * isolated per test). Tests that need real SSE plumbing build it via
119
+ * `create_audit_log_sse` against `create_test_app`.
120
+ */
121
+ export const create_stub_audit_sse = () => {
122
+ const registry = new SubscriberRegistry({
123
+ max_per_scope: AUDIT_LOG_SSE_MAX_PER_SCOPE,
124
+ });
125
+ return {
126
+ subscribe: () => () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
127
+ log: new Logger('test:audit_sse', { level: 'off' }),
128
+ on_audit_event: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
129
+ registry,
130
+ };
131
+ };
91
132
  /** Stub `AppDeps` for auth surface tests — throws on any method access. */
92
133
  export const stub_app_deps = {
93
134
  stat: create_throwing_stub('stat'),
@@ -97,7 +138,7 @@ export const stub_app_deps = {
97
138
  password: create_throwing_stub('password'),
98
139
  db: create_throwing_stub('db'),
99
140
  log: create_throwing_stub('log'),
100
- on_audit_event: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
141
+ audit: create_test_audit_emitter(),
101
142
  };
102
143
  /**
103
144
  * Create no-op `AppDeps` for auth surface testing.
@@ -110,7 +151,7 @@ export const create_stub_app_deps = () => ({
110
151
  password: create_noop_stub('password'),
111
152
  db: stub_db,
112
153
  log: new Logger('test', { level: 'off' }),
113
- on_audit_event: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
154
+ audit: create_test_audit_emitter(),
114
155
  });
115
156
  /** Create the API middleware stub array matching `create_auth_middleware_specs` output. */
116
157
  export const create_stub_api_middleware = (options) => {
@@ -37,6 +37,10 @@ export declare const assert_middleware_errors_propagated: (surface: AppSurface)
37
37
  * Every route's declared error schemas must have an `error` field at the top level
38
38
  * (conforming to the `ApiError` base shape `{error: string}`).
39
39
  *
40
+ * Walks union branches (`anyOf` from `z.union`, `oneOf` from
41
+ * `z.discriminatedUnion`) so every emit shape inside a merged 400 / 404
42
+ * is checked, not just the top-level wrapper.
43
+ *
40
44
  * Catches typos in error schema definitions and ensures consumers can always
41
45
  * read `.error` from error responses.
42
46
  */
@@ -46,11 +50,15 @@ export declare const assert_error_schemas_structurally_valid: (surface: AppSurfa
46
50
  * across routes.
47
51
  *
48
52
  * Extracts `const` values from error schema `error` properties (which correspond to
49
- * `z.literal()` in the Zod source). Flags when the same literal appears at different
50
- * status codes — e.g., `ERROR_INVALID_CREDENTIALS` at both 401 and 403 would be a bug.
53
+ * `z.literal()` in the Zod source). Walks union branches (`anyOf` from `z.union`,
54
+ * `oneOf` from `z.discriminatedUnion`) so literal codes nested inside merged unions
55
+ * (e.g. validation 400 + actor-resolution 400) are still tracked. Flags when the
56
+ * same literal appears at different status codes — e.g., `ERROR_INVALID_CREDENTIALS`
57
+ * at both 401 and 403 would be a bug.
51
58
  *
52
- * Only checks schemas with `const` values (literal schemas). Generic `z.string()`
53
- * schemas (which produce `{type: 'string'}` in JSON Schema) are ignored.
59
+ * Only checks `const` values (literal schemas). Generic `z.string()` schemas
60
+ * (which produce `{type: 'string'}`) and `z.enum()` schemas are ignored — the
61
+ * literal-only narrow keeps the check unambiguous.
54
62
  */
55
63
  export declare const assert_error_code_status_consistency: (surface: AppSurface) => void;
56
64
  /**
@@ -97,8 +105,8 @@ export interface SurfaceSecurityPolicyOptions {
97
105
  /**
98
106
  * Path patterns for routes that should be rate-limited.
99
107
  * Default: common sensitive REST patterns (login, password, bootstrap).
100
- * `account_token_create` became RPC-only in the 2026-04-23 migration;
101
- * per-method RPC rate limiting is a separate invariant if consumers want it.
108
+ * `account_token_create` lives on the RPC surface; per-method RPC rate
109
+ * limiting is a separate invariant if consumers want it.
102
110
  */
103
111
  sensitive_route_patterns?: Array<string | RegExp>;
104
112
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"surface_invariants.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/surface_invariants.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"surface_invariants.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/surface_invariants.ts"],"names":[],"mappings":"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"}
@@ -105,6 +105,10 @@ export const assert_middleware_errors_propagated = (surface) => {
105
105
  * Every route's declared error schemas must have an `error` field at the top level
106
106
  * (conforming to the `ApiError` base shape `{error: string}`).
107
107
  *
108
+ * Walks union branches (`anyOf` from `z.union`, `oneOf` from
109
+ * `z.discriminatedUnion`) so every emit shape inside a merged 400 / 404
110
+ * is checked, not just the top-level wrapper.
111
+ *
108
112
  * Catches typos in error schema definitions and ensures consumers can always
109
113
  * read `.error` from error responses.
110
114
  */
@@ -113,15 +117,24 @@ export const assert_error_schemas_structurally_valid = (surface) => {
113
117
  if (!route.error_schemas)
114
118
  continue;
115
119
  for (const [status, schema] of Object.entries(route.error_schemas)) {
116
- if (typeof schema !== 'object' || schema === null)
117
- continue;
118
- const s = schema;
119
- // JSON Schema must have properties.error or be an object type
120
- if (s.type === 'object' && s.properties && typeof s.properties === 'object') {
121
- const props = s.properties;
122
- assert.ok('error' in props, `${format_route_key(route)} error schema for status ${status} missing 'error' property`);
123
- }
120
+ assert_branch_has_error_property(schema, format_route_key(route), status);
121
+ }
122
+ }
123
+ };
124
+ const assert_branch_has_error_property = (schema, route_key, status) => {
125
+ const branches = get_union_branches(schema);
126
+ if (branches) {
127
+ for (const branch of branches) {
128
+ assert_branch_has_error_property(branch, route_key, status);
124
129
  }
130
+ return;
131
+ }
132
+ if (typeof schema !== 'object' || schema === null)
133
+ return;
134
+ const s = schema;
135
+ if (s.type === 'object' && s.properties && typeof s.properties === 'object') {
136
+ const props = s.properties;
137
+ assert.ok('error' in props, `${route_key} error schema for status ${status} missing 'error' property`);
125
138
  }
126
139
  };
127
140
  /**
@@ -129,44 +142,42 @@ export const assert_error_schemas_structurally_valid = (surface) => {
129
142
  * across routes.
130
143
  *
131
144
  * Extracts `const` values from error schema `error` properties (which correspond to
132
- * `z.literal()` in the Zod source). Flags when the same literal appears at different
133
- * status codes — e.g., `ERROR_INVALID_CREDENTIALS` at both 401 and 403 would be a bug.
145
+ * `z.literal()` in the Zod source). Walks union branches (`anyOf` from `z.union`,
146
+ * `oneOf` from `z.discriminatedUnion`) so literal codes nested inside merged unions
147
+ * (e.g. validation 400 + actor-resolution 400) are still tracked. Flags when the
148
+ * same literal appears at different status codes — e.g., `ERROR_INVALID_CREDENTIALS`
149
+ * at both 401 and 403 would be a bug.
134
150
  *
135
- * Only checks schemas with `const` values (literal schemas). Generic `z.string()`
136
- * schemas (which produce `{type: 'string'}` in JSON Schema) are ignored.
151
+ * Only checks `const` values (literal schemas). Generic `z.string()` schemas
152
+ * (which produce `{type: 'string'}`) and `z.enum()` schemas are ignored — the
153
+ * literal-only narrow keeps the check unambiguous.
137
154
  */
138
155
  export const assert_error_code_status_consistency = (surface) => {
139
156
  // Map from error code literal → Set of status codes where it appears
140
157
  const code_to_statuses = new Map();
141
- for (const route of surface.routes) {
142
- if (!route.error_schemas)
143
- continue;
144
- for (const [status, schema] of Object.entries(route.error_schemas)) {
145
- const error_const = extract_error_const(schema);
146
- if (error_const === null)
147
- continue;
148
- let statuses = code_to_statuses.get(error_const);
158
+ const record = (status, schema) => {
159
+ for (const code of extract_error_consts(schema)) {
160
+ let statuses = code_to_statuses.get(code);
149
161
  if (!statuses) {
150
162
  statuses = new Set();
151
- code_to_statuses.set(error_const, statuses);
163
+ code_to_statuses.set(code, statuses);
152
164
  }
153
165
  statuses.add(status);
154
166
  }
167
+ };
168
+ for (const route of surface.routes) {
169
+ if (!route.error_schemas)
170
+ continue;
171
+ for (const [status, schema] of Object.entries(route.error_schemas)) {
172
+ record(status, schema);
173
+ }
155
174
  }
156
175
  // Also check middleware error schemas
157
176
  for (const mw of surface.middleware) {
158
177
  if (!mw.error_schemas)
159
178
  continue;
160
179
  for (const [status, schema] of Object.entries(mw.error_schemas)) {
161
- const error_const = extract_error_const(schema);
162
- if (error_const === null)
163
- continue;
164
- let statuses = code_to_statuses.get(error_const);
165
- if (!statuses) {
166
- statuses = new Set();
167
- code_to_statuses.set(error_const, statuses);
168
- }
169
- statuses.add(status);
180
+ record(status, schema);
170
181
  }
171
182
  }
172
183
  for (const [code, statuses] of code_to_statuses) {
@@ -191,31 +202,59 @@ const get_error_property = (schema) => {
191
202
  return props.error;
192
203
  };
193
204
  /**
194
- * Extract the `const` value from a JSON Schema error property, if present.
205
+ * Read the branch array off a JSON Schema union, if present.
206
+ *
207
+ * Zod 4 emits `anyOf` for `z.union(...)` and `oneOf` for
208
+ * `z.discriminatedUnion(...)` via `z.toJSONSchema`; both are union-shaped
209
+ * for tightness/code-extraction purposes. Nested unions are NOT flattened
210
+ * by `toJSONSchema`, so every caller must recurse through the returned
211
+ * branches. Returns the branch array or `null` for non-union schemas.
212
+ */
213
+ const get_union_branches = (schema) => {
214
+ if (typeof schema !== 'object' || schema === null)
215
+ return null;
216
+ const s = schema;
217
+ if (Array.isArray(s.anyOf))
218
+ return s.anyOf;
219
+ if (Array.isArray(s.oneOf))
220
+ return s.oneOf;
221
+ return null;
222
+ };
223
+ /**
224
+ * Extract every `const` value from a JSON Schema error property, walking
225
+ * union branches.
195
226
  *
196
227
  * Looks for `schema.properties.error.const` — the JSON Schema representation
197
- * of `z.literal('some_error_code')`.
228
+ * of `z.literal('some_error_code')` — and recurses into `anyOf` / `oneOf`
229
+ * branches so literals nested inside `z.union` or `z.discriminatedUnion`
230
+ * are still tracked. Returns an empty array for schemas with no literal
231
+ * codes (`z.enum`, `z.string`, non-object schemas).
198
232
  */
199
- const extract_error_const = (schema) => {
233
+ const extract_error_consts = (schema) => {
234
+ const branches = get_union_branches(schema);
235
+ if (branches) {
236
+ const codes = [];
237
+ for (const branch of branches) {
238
+ codes.push(...extract_error_consts(branch));
239
+ }
240
+ return codes;
241
+ }
200
242
  const error_prop = get_error_property(schema);
201
243
  if (!error_prop)
202
- return null;
244
+ return [];
203
245
  if (typeof error_prop.const === 'string')
204
- return error_prop.const;
205
- return null;
246
+ return [error_prop.const];
247
+ return [];
206
248
  };
207
249
  /**
208
250
  * Check if a JSON Schema error property uses specific error codes (`const` or `enum`),
209
251
  * not just generic `z.string()` (`{type: 'string'}`).
210
252
  *
211
- * Returns `true` for `z.literal()` (`{const: '...'}`) and `z.enum()` (`{enum: [...]}`).
253
+ * Returns `true` for `z.literal()` (`{const: '...'}`) and `z.enum()` (`{enum: [...]}`),
254
+ * and for union schemas where every branch is specific. Defers to
255
+ * `classify_error_specificity` so the union walk stays in one place.
212
256
  */
213
- const has_specific_error_schema = (schema) => {
214
- const error_prop = get_error_property(schema);
215
- if (!error_prop)
216
- return false;
217
- return typeof error_prop.const === 'string' || Array.isArray(error_prop.enum);
218
- };
257
+ const has_specific_error_schema = (schema) => classify_error_specificity(schema) !== 'generic';
219
258
  /**
220
259
  * Routes declaring 404 error schemas should use specific `z.literal()` or `z.enum()`
221
260
  * error codes, not generic `z.string()`.
@@ -245,8 +284,29 @@ export const assert_404_schemas_use_specific_errors = (surface) => {
245
284
  * - `'literal'` — `z.literal()` (`{const: '...'}`)
246
285
  * - `'enum'` — `z.enum()` (`{enum: [...]}`)
247
286
  * - `'generic'` — `z.string()` or unrecognized
287
+ *
288
+ * Walks union branches (`anyOf` from `z.union`, `oneOf` from
289
+ * `z.discriminatedUnion`) — `derive_error_schemas` emits `anyOf` when it
290
+ * merges multiple shapes at one status (e.g. validation 400 +
291
+ * actor-resolution 400), and a consumer that explicitly declares a
292
+ * discriminated-union error schema emits `oneOf`. Reports the **minimum**
293
+ * specificity across branches — a union's contract is only as tight as
294
+ * its loosest member.
248
295
  */
249
296
  const classify_error_specificity = (schema) => {
297
+ const branches = get_union_branches(schema);
298
+ if (branches) {
299
+ if (branches.length === 0)
300
+ return 'generic';
301
+ let min = 'literal';
302
+ for (const branch of branches) {
303
+ const branch_specificity = classify_error_specificity(branch);
304
+ if (SPECIFICITY_ORDER[branch_specificity] < SPECIFICITY_ORDER[min]) {
305
+ min = branch_specificity;
306
+ }
307
+ }
308
+ return min;
309
+ }
250
310
  const error_prop = get_error_property(schema);
251
311
  if (!error_prop)
252
312
  return 'generic';
@@ -260,8 +320,24 @@ const classify_error_specificity = (schema) => {
260
320
  * Extract error code values from a JSON Schema error property.
261
321
  *
262
322
  * Returns the literal value or enum array, or `null` for generic schemas.
323
+ *
324
+ * For union schemas (`anyOf` / `oneOf`), collects codes from every branch
325
+ * (deduped). If any branch is generic, returns `null` because the union
326
+ * admits arbitrary strings on that branch.
263
327
  */
264
328
  const extract_error_codes = (schema) => {
329
+ const branches = get_union_branches(schema);
330
+ if (branches) {
331
+ const codes = new Set();
332
+ for (const branch of branches) {
333
+ const branch_codes = extract_error_codes(branch);
334
+ if (branch_codes === null)
335
+ return null;
336
+ for (const code of branch_codes)
337
+ codes.add(code);
338
+ }
339
+ return [...codes];
340
+ }
265
341
  const error_prop = get_error_property(schema);
266
342
  if (!error_prop)
267
343
  return null;
@@ -43,7 +43,7 @@ import { type Uuid } from '@fuzdev/fuz_util/id.js';
43
43
  import type { ActionSpecUnion } from '../actions/action_spec.js';
44
44
  import type { Action } from '../actions/action_types.js';
45
45
  import type { ActionEventEnvironment } from '../actions/action_event_types.js';
46
- import { type BaseHandlerContext, type RegisterActionWsOptions } from '../actions/register_action_ws.js';
46
+ import { type RegisterActionWsOptions } from '../actions/register_action_ws.js';
47
47
  import { BackendWebsocketTransport } from '../actions/transports_ws_backend.js';
48
48
  import { type RequestContext } from '../auth/request_context.js';
49
49
  import { type CredentialType } from '../hono_context.js';
@@ -124,7 +124,7 @@ export interface WsConnectIdentity {
124
124
  session_id?: string;
125
125
  /** Api token id; set for bearer connections, null otherwise. */
126
126
  api_token_id?: string | null;
127
- /** Roles to grant via active permits. Pass `[ROLE_KEEPER]` for keeper actions. */
127
+ /** Roles to grant via active role_grants. Pass `[ROLE_KEEPER]` for keeper actions. */
128
128
  roles?: Array<string>;
129
129
  }
130
130
  /** A mock WS client: send requests, inspect/await incoming messages. */
@@ -214,15 +214,14 @@ export declare const is_notification_with: <P>(method: string, match: (params: P
214
214
  /** Predicate matching a JSON-RPC response frame (success or error) for the given request id. */
215
215
  export declare const is_response_for: (id: number | string) => (msg: unknown) => boolean;
216
216
  /** Options for `create_ws_test_harness`. */
217
- export interface CreateWsTestHarnessOptions<TCtx extends BaseHandlerContext> {
217
+ export interface CreateWsTestHarnessOptions {
218
218
  /**
219
219
  * The actions registered on this endpoint — matches the shape
220
220
  * `register_action_ws` accepts. Each entry is a `{spec, handler?}` tuple;
221
221
  * shared fuz_app primitives (like `heartbeat_action`) can be spread in
222
222
  * alongside consumer-specific actions.
223
223
  */
224
- actions: ReadonlyArray<Action<TCtx>>;
225
- extend_context?: RegisterActionWsOptions<TCtx>['extend_context'];
224
+ actions: ReadonlyArray<Action>;
226
225
  /** Pass a pre-created transport to share with a broadcast API. */
227
226
  transport?: BackendWebsocketTransport;
228
227
  /**
@@ -230,13 +229,13 @@ export interface CreateWsTestHarnessOptions<TCtx extends BaseHandlerContext> {
230
229
  * fake timers + receive-silence detection need explicit opt-in and per-
231
230
  * test tuning to avoid spurious closes.
232
231
  */
233
- heartbeat?: RegisterActionWsOptions<TCtx>['heartbeat'];
232
+ heartbeat?: RegisterActionWsOptions['heartbeat'];
234
233
  /** Optional logger. Defaults to a silent `[ws-test]` logger. */
235
234
  log?: Logger;
236
235
  /** Threaded straight through to `register_action_ws`. */
237
- on_socket_open?: RegisterActionWsOptions<TCtx>['on_socket_open'];
236
+ on_socket_open?: RegisterActionWsOptions['on_socket_open'];
238
237
  /** Threaded straight through to `register_action_ws`. */
239
- on_socket_close?: RegisterActionWsOptions<TCtx>['on_socket_close'];
238
+ on_socket_close?: RegisterActionWsOptions['on_socket_close'];
240
239
  }
241
240
  /** A harness instance — transport handle + connection factory. */
242
241
  export interface WsTestHarness {
@@ -259,24 +258,24 @@ export interface WsTestHarness {
259
258
  * auth identity. Returned clients drive the real
260
259
  * `onOpen`/`onMessage`/`onClose` path against a real `WSContext`.
261
260
  */
262
- export declare const create_ws_test_harness: <TCtx extends BaseHandlerContext>(options: CreateWsTestHarnessOptions<TCtx>) => WsTestHarness;
261
+ export declare const create_ws_test_harness: (options: CreateWsTestHarnessOptions) => WsTestHarness;
263
262
  /** Convenience: default identity for keeper-authenticated connections. */
264
263
  export declare const keeper_identity: () => WsConnectIdentity;
265
264
  /**
266
265
  * Wire a typed broadcast API against the harness's transport, matching
267
266
  * how a consumer's real backend composes the stack. Returns the typed
268
- * API so tests can call `.tx_run_created(...)` / `.workspace_changed(...)`
267
+ * API so tests can call `.zap_run_created(...)` / `.workspace_changed(...)`
269
268
  * etc. directly.
270
269
  *
271
270
  * ```ts
272
- * const harness = create_ws_test_harness<BaseHandlerContext>({specs, handlers});
271
+ * const harness = create_ws_test_harness({actions});
273
272
  * const broadcast = build_broadcast_api<MyBackendActionsApi>({
274
273
  * harness,
275
274
  * specs: my_broadcast_action_specs,
276
275
  * });
277
276
  * const client = await harness.connect(keeper_identity());
278
- * await broadcast.tx_run_created({run_id: '...', ...});
279
- * await client.wait_for(is_notification('tx_run_created'));
277
+ * await broadcast.zap_run_created({run_id: '...', ...});
278
+ * await client.wait_for(is_notification('zap_run_created'));
280
279
  * ```
281
280
  */
282
281
  export declare const build_broadcast_api: <TApi extends object>(options: {
@@ -1 +1 @@
1
- {"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"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"}
@@ -41,14 +41,15 @@ import { Logger } from '@fuzdev/fuz_util/log.js';
41
41
  import { create_uuid } from '@fuzdev/fuz_util/id.js';
42
42
  import { ActionPeer } from '../actions/action_peer.js';
43
43
  import { create_broadcast_api } from '../actions/broadcast_api.js';
44
- import { register_action_ws, } from '../actions/register_action_ws.js';
44
+ import { register_action_ws } from '../actions/register_action_ws.js';
45
+ import { create_stub_db } from './stubs.js';
45
46
  import { BackendWebsocketTransport } from '../actions/transports_ws_backend.js';
46
47
  import { REQUEST_CONTEXT_KEY } from '../auth/request_context.js';
47
48
  import { ROLE_KEEPER } from '../auth/role_schema.js';
48
49
  import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY, TEST_CONTEXT_PRESET_KEY, } from '../hono_context.js';
49
50
  import { JSONRPC_VERSION } from '../http/jsonrpc.js';
50
51
  import { create_jsonrpc_request, is_jsonrpc_error_response, is_jsonrpc_notification, is_jsonrpc_response, } from '../http/jsonrpc_helpers.js';
51
- import { create_test_account, create_test_actor, create_test_permit } from './entities.js';
52
+ import { create_test_account, create_test_actor, create_test_role_grant } from './entities.js';
52
53
  /**
53
54
  * Build a real `WSContext` backed by in-memory `send`/`close` capture.
54
55
  * Parsing of outgoing frames is left to the caller — `sends` holds the
@@ -160,7 +161,7 @@ export const is_notification_with = (method, match) => (msg) => is_jsonrpc_notif
160
161
  export const is_response_for = (id) => (msg) => (is_jsonrpc_response(msg) || is_jsonrpc_error_response(msg)) && msg.id === id;
161
162
  const DEFAULT_TIMEOUT_MS = 1000;
162
163
  /**
163
- * Build a `RequestContext` with a fresh UUID account/actor and permits
164
+ * Build a `RequestContext` with a fresh UUID account/actor and role_grants
164
165
  * for the supplied roles. Used by the high-level harness so callers can
165
166
  * pass `roles: [ROLE_KEEPER, 'admin']`.
166
167
  */
@@ -187,10 +188,11 @@ const build_multi_role_request_context = (account_id, roles) => {
187
188
  updated_at: null,
188
189
  updated_by: null,
189
190
  },
190
- permits: roles.map((role) => ({
191
+ role_grants: roles.map((role) => ({
191
192
  id: create_uuid(),
192
193
  actor_id,
193
194
  role,
195
+ scope_kind: null,
194
196
  scope_id: null,
195
197
  created_at: now,
196
198
  expires_at: null,
@@ -210,7 +212,7 @@ const build_multi_role_request_context = (account_id, roles) => {
210
212
  const build_simple_request_context = (role) => ({
211
213
  account: create_test_account({ id: 'acc_1', username: 'testuser' }),
212
214
  actor: create_test_actor({ id: 'act_1', account_id: 'acc_1', name: 'testuser' }),
213
- permits: role ? [create_test_permit({ id: 'perm_1', actor_id: 'act_1', role })] : [],
215
+ role_grants: role ? [create_test_role_grant({ id: 'perm_1', actor_id: 'act_1', role })] : [],
214
216
  });
215
217
  /**
216
218
  * Create a WebSocket test harness for the given specs + handlers.
@@ -222,16 +224,22 @@ const build_simple_request_context = (role) => ({
222
224
  * `onOpen`/`onMessage`/`onClose` path against a real `WSContext`.
223
225
  */
224
226
  export const create_ws_test_harness = (options) => {
225
- const { actions, extend_context = (base) => base, transport = new BackendWebsocketTransport(), heartbeat = false, log = new Logger('[ws-test]', { level: 'off' }), on_socket_open, on_socket_close, } = options;
227
+ const { actions, transport = new BackendWebsocketTransport(), heartbeat = false, log = new Logger('[ws-test]', { level: 'off' }), on_socket_open, on_socket_close, } = options;
226
228
  const stub = create_stub_upgrade();
227
229
  // Minimal Hono stub — `register_action_ws` only needs `.get(path, handler)`.
228
230
  const stub_app = { get: () => stub_app };
231
+ // Stub DB — the harness pre-bakes `RequestContext` via the test-preset
232
+ // escape hatch so `perform_action` skips the live authorization phase.
233
+ // `db.transaction(fn)` synchronously calls `fn(stub_db)` so handlers
234
+ // declaring `side_effects: true` execute under the same shape they
235
+ // would in production.
236
+ const stub_db = create_stub_db();
229
237
  register_action_ws({
230
238
  path: '/test/ws',
231
239
  app: stub_app,
232
240
  upgradeWebSocket: stub.upgradeWebSocket,
233
241
  actions,
234
- extend_context,
242
+ db: stub_db,
235
243
  transport,
236
244
  heartbeat,
237
245
  log,
@@ -383,18 +391,18 @@ const make_peer = () => new ActionPeer({ environment: new MinimalActionEnvironme
383
391
  /**
384
392
  * Wire a typed broadcast API against the harness's transport, matching
385
393
  * how a consumer's real backend composes the stack. Returns the typed
386
- * API so tests can call `.tx_run_created(...)` / `.workspace_changed(...)`
394
+ * API so tests can call `.zap_run_created(...)` / `.workspace_changed(...)`
387
395
  * etc. directly.
388
396
  *
389
397
  * ```ts
390
- * const harness = create_ws_test_harness<BaseHandlerContext>({specs, handlers});
398
+ * const harness = create_ws_test_harness({actions});
391
399
  * const broadcast = build_broadcast_api<MyBackendActionsApi>({
392
400
  * harness,
393
401
  * specs: my_broadcast_action_specs,
394
402
  * });
395
403
  * const client = await harness.connect(keeper_identity());
396
- * await broadcast.tx_run_created({run_id: '...', ...});
397
- * await client.wait_for(is_notification('tx_run_created'));
404
+ * await broadcast.zap_run_created({run_id: '...', ...});
405
+ * await client.wait_for(is_notification('zap_run_created'));
398
406
  * ```
399
407
  */
400
408
  export const build_broadcast_api = (options) => {